|
@@ -0,0 +1,152 @@
|
|
|
|
|
+package com.ylx.message.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.ylx.common.core.domain.model.WxLoginUser;
|
|
|
|
|
+import com.ylx.common.exception.ServiceException;
|
|
|
|
|
+import com.ylx.common.utils.SecurityUtils;
|
|
|
|
|
+import com.ylx.message.domain.MessageContent;
|
|
|
|
|
+import com.ylx.message.domain.UserMessage;
|
|
|
|
|
+import com.ylx.message.domain.dto.MessageDetailDTO;
|
|
|
|
|
+import com.ylx.message.domain.vo.MessageCategoryVO;
|
|
|
|
|
+import com.ylx.message.domain.vo.MessageDetailVO;
|
|
|
|
|
+import com.ylx.message.mapper.MessageContentMapper;
|
|
|
|
|
+import com.ylx.message.mapper.UserMessageMapper;
|
|
|
|
|
+import com.ylx.message.service.IMessageService;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class MessageServiceImpl implements IMessageService {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private MessageContentMapper messageContentMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private UserMessageMapper userMessageMapper;
|
|
|
|
|
+
|
|
|
|
|
+ /** 消息未读状态 */
|
|
|
|
|
+ private static final int READ_STATUS_UNREAD = 0;
|
|
|
|
|
+ /** 消息已读状态 */
|
|
|
|
|
+ private static final int READ_STATUS_READ = 1;
|
|
|
|
|
+ /** 未删除状态 */
|
|
|
|
|
+ private static final int NOT_DELETE = 0;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 🔴 获取用户未读消息数(小红点核心接口)
|
|
|
|
|
+ * 联合索引 idx_user_read(user_id, is_read) 保证高效查询
|
|
|
|
|
+ */
|
|
|
|
|
+ public long getUnreadCount(Long userId) {
|
|
|
|
|
+ return userMessageMapper.selectCount(
|
|
|
|
|
+ new LambdaQueryWrapper<UserMessage>()
|
|
|
|
|
+ .eq(UserMessage::getUserId, userId)
|
|
|
|
|
+ .eq(UserMessage::getIsRead, READ_STATUS_UNREAD)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发送消息(由订单、营销等业务模块调用)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public void sendMessage(Long userId, String triggerEvent, Map<String, Object> variables) {
|
|
|
|
|
+ // 1. 查找消息配置
|
|
|
|
|
+ MessageContent config = messageContentMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<MessageContent>()
|
|
|
|
|
+ .eq(MessageContent::getTriggerEvent, triggerEvent)
|
|
|
|
|
+ .eq(MessageContent::getIsDelete, NOT_DELETE)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (ObjectUtil.isNull(config)) {
|
|
|
|
|
+ log.warn("未找到触发事件[{}]对应的消息配置", triggerEvent);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 渲染个性化内容
|
|
|
|
|
+ String content = renderTemplate(config.getContentTemplate(), variables);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 生成用户消息(默认未读,触发小红点)
|
|
|
|
|
+ UserMessage userMsg = new UserMessage();
|
|
|
|
|
+ userMsg.setUserId(userId);
|
|
|
|
|
+ userMsg.setMessageContentId(config.getId());
|
|
|
|
|
+ userMsg.setIsRead(READ_STATUS_UNREAD);
|
|
|
|
|
+ userMsg.setPersonalizedContent(content);
|
|
|
|
|
+ userMessageMapper.insert(userMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 标记单条消息已读(点击消息时调用,消除对应小红点)
|
|
|
|
|
+ */
|
|
|
|
|
+ public void markAsRead(Long messageId) {
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前登录用户
|
|
|
|
|
+ WxLoginUser wxLoginUser = getCurrentWxLoginUser();
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = Long.parseLong(wxLoginUser.getId());
|
|
|
|
|
+
|
|
|
|
|
+ userMessageMapper.update(null,
|
|
|
|
|
+ new LambdaUpdateWrapper<UserMessage>()
|
|
|
|
|
+ .eq(UserMessage::getId, messageId)
|
|
|
|
|
+ .eq(UserMessage::getUserId, userId) // 🔒 安全校验,防止越权操作他人消息
|
|
|
|
|
+ .eq(UserMessage::getIsRead, READ_STATUS_UNREAD) // 仅更新未读记录,避免重复更新
|
|
|
|
|
+ .eq(UserMessage::getIsDelete, NOT_DELETE)
|
|
|
|
|
+ .set(UserMessage::getIsRead, READ_STATUS_READ)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<MessageCategoryVO> getCategoryCards() {
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前登录用户
|
|
|
|
|
+ WxLoginUser wxLoginUser = getCurrentWxLoginUser();
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = Long.parseLong(wxLoginUser.getId());
|
|
|
|
|
+ return userMessageMapper.selectLatestByCategory(userId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Page<MessageDetailVO> getCategoryMessages(MessageDetailDTO dto) {
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前登录用户
|
|
|
|
|
+ WxLoginUser wxLoginUser = getCurrentWxLoginUser();
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = Long.parseLong(wxLoginUser.getId());
|
|
|
|
|
+
|
|
|
|
|
+ Page<MessageDetailVO> page = new Page<>(dto.getCurrent(), dto.getSize());
|
|
|
|
|
+ dto.setUserId(userId);
|
|
|
|
|
+ return this.userMessageMapper.selectMessageListByCategory(page, dto);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 模板变量替换
|
|
|
|
|
+ */
|
|
|
|
|
+ private String renderTemplate(String template, Map<String, Object> vars) {
|
|
|
|
|
+ if (StrUtil.isBlank(template) || vars == null || vars.isEmpty()) {
|
|
|
|
|
+ return template;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String result = template;
|
|
|
|
|
+ for (Map.Entry<String, Object> entry : vars.entrySet()) {
|
|
|
|
|
+ String key = entry.getKey();
|
|
|
|
|
+ Object value = entry.getValue();
|
|
|
|
|
+ // 安全转换为字符串:null → "", 非字符串对象调用 toString()
|
|
|
|
|
+ String strValue = value == null ? "" : String.valueOf(value);
|
|
|
|
|
+ result = result.replace("{" + key + "}", strValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private WxLoginUser getCurrentWxLoginUser() {
|
|
|
|
|
+ WxLoginUser loginUser = SecurityUtils.getWxLoginUser();
|
|
|
|
|
+ if (ObjectUtil.isNull(loginUser)) {
|
|
|
|
|
+ throw new ServiceException("用户未登录或登录已过期");
|
|
|
|
|
+ }
|
|
|
|
|
+ return loginUser;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|