update 完成消息盒子功能前后端联动(已读未读在前端浏览器存储)

This commit is contained in:
疯狂的狮子Li
2026-03-27 14:34:37 +08:00
parent 66c23b1dc4
commit 60b6862c9e
25 changed files with 837 additions and 51 deletions

View File

@@ -0,0 +1,34 @@
package org.dromara.system.controller.system;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.vo.SysMessageBoxVo;
import org.dromara.system.service.ISysMessageService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 消息记录控制器
*
* @author Lion Li
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/message")
public class SysMessageController extends BaseController {
private final ISysMessageService messageService;
/**
* 查询当前用户消息盒子数据
*
* @return 消息盒子数据
*/
@GetMapping("/box")
public R<SysMessageBoxVo> getBox() {
return R.ok(messageService.queryMessageBox(LoginHelper.getUserId()));
}
}

View File

@@ -4,14 +4,14 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.dto.PushPayload;
import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.core.enums.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.service.MessageService;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.push.helper.PushHelper;
import org.dromara.common.redis.annotation.RepeatSubmit;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysNoticeBo;
@@ -36,6 +36,7 @@ public class SysNoticeController extends BaseController {
private final ISysNoticeService noticeService;
private final DictService dictService;
private final MessageService messageService;
/**
* 分页查询通知公告列表。
@@ -83,7 +84,9 @@ public class SysNoticeController extends BaseController {
data.put("noticeTypeLabel", type);
data.put("noticeTitle", notice.getNoticeTitle());
data.put("noticeId", notice.getNoticeId());
PushHelper.publishAll(PushPayload.of(
data.put("noticeContent", notice.getNoticeContent());
data.put("status", notice.getStatus());
messageService.publishAll(PushPayloadDTO.of(
PushTypeEnum.NOTICE,
PushSourceEnum.NOTICE,
"[" + type + "] " + notice.getNoticeTitle(),

View File

@@ -0,0 +1,69 @@
package org.dromara.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
/**
* 消息记录表 sys_message
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_message")
public class SysMessage extends BaseEntity {
/**
* 消息ID
*/
@TableId(value = "message_id")
private Long messageId;
/**
* 消息分组
*/
private String category;
/**
* 消息类型
*/
private String type;
/**
* 消息来源
*/
private String source;
/**
* 标题
*/
private String title;
/**
* 摘要消息
*/
private String message;
/**
* 详细内容
*/
private String content;
/**
* 扩展数据 JSON
*/
private String dataJson;
/**
* 前端跳转路径
*/
private String path;
/**
* 目标用户ID串0 表示全局
*/
private String sendUserIds;
}

View File

@@ -0,0 +1,35 @@
package org.dromara.system.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 消息盒子视图对象
*
* @author Lion Li
*/
@Data
public class SysMessageBoxVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 系统消息
*/
private List<SysMessageVo> systemList = new ArrayList<>();
/**
* 通知公告消息
*/
private List<SysMessageVo> noticeList = new ArrayList<>();
/**
* 工作流消息
*/
private List<SysMessageVo> workflowList = new ArrayList<>();
}

View File

@@ -0,0 +1,72 @@
package org.dromara.system.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.system.domain.SysMessage;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 消息记录视图对象 sys_message
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysMessage.class)
public class SysMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 消息ID
*/
private Long messageId;
/**
* 消息分组
*/
private String category;
/**
* 消息类型
*/
private String type;
/**
* 消息来源
*/
private String source;
/**
* 标题
*/
private String title;
/**
* 摘要消息
*/
private String message;
/**
* 详细内容
*/
private String content;
/**
* 扩展数据
*/
private Object data;
/**
* 前端跳转路径
*/
private String path;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,13 @@
package org.dromara.system.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.vo.SysMessageVo;
/**
* 消息记录Mapper接口
*
* @author Lion Li
*/
public interface SysMessageMapper extends BaseMapperPlus<SysMessage, SysMessageVo> {
}

View File

@@ -0,0 +1,91 @@
package org.dromara.system.service;
import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.system.domain.vo.SysMessageBoxVo;
import java.util.List;
/**
* 消息记录服务接口
*
* @author Lion Li
*/
public interface ISysMessageService {
/**
* 查询当前用户消息盒子数据
*
* @param userId 用户ID
* @return 消息盒子数据
*/
SysMessageBoxVo queryMessageBox(Long userId);
/**
* 发送指定用户文本消息
*
* @param userId 目标用户
* @param message 文本消息
*/
void sendMessage(Long userId, String message);
/**
* 广播文本消息
*
* @param message 文本消息
*/
void sendMessage(String message);
/**
* 发送指定用户消息
*
* @param userId 目标用户
* @param payload 推送消息体
*/
void sendMessage(Long userId, PushPayloadDTO payload);
/**
* 广播消息
*
* @param payload 推送消息体
*/
void sendMessage(PushPayloadDTO payload);
/**
* 发布指定用户消息
*
* @param userIds 用户ID列表
* @param payload 推送消息体
*/
void publishMessage(List<Long> userIds, PushPayloadDTO payload);
/**
* 发布广播文本消息
*
* @param message 文本消息
*/
void publishAll(String message);
/**
* 发布广播消息
*
* @param payload 推送消息体
*/
void publishAll(PushPayloadDTO payload);
/**
* 记录全局消息
*
* @param payload 推送消息体
* @return 回填消息ID后的推送消息体
*/
PushPayloadDTO storeAll(PushPayloadDTO payload);
/**
* 记录指定用户消息
*
* @param userIds 用户ID列表
* @param payload 推送消息体
* @return 回填消息ID后的推送消息体
*/
PushPayloadDTO storeUsers(List<Long> userIds, PushPayloadDTO payload);
}

View File

@@ -0,0 +1,194 @@
package org.dromara.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.core.enums.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.service.MessageService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.mybatis.utils.IdGeneratorUtil;
import org.dromara.common.push.helper.PushHelper;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.vo.SysMessageBoxVo;
import org.dromara.system.domain.vo.SysMessageVo;
import org.dromara.system.mapper.SysMessageMapper;
import org.dromara.system.service.ISysMessageService;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 消息记录服务实现
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysMessageServiceImpl implements ISysMessageService, MessageService {
private static final String GLOBAL_USER_IDS = "0";
private static final String CATEGORY_SYSTEM = "system";
private static final String CATEGORY_NOTICE = "notice";
private static final String CATEGORY_WORKFLOW = "workflow";
private static final int BOX_LIMIT = 100;
private static final long BOX_DAYS = 30L;
private final SysMessageMapper baseMapper;
@Override
public SysMessageBoxVo queryMessageBox(Long userId) {
SysMessageBoxVo box = new SysMessageBoxVo();
box.setSystemList(selectMessageList(CATEGORY_SYSTEM, userId));
box.setNoticeList(selectMessageList(CATEGORY_NOTICE, userId));
box.setWorkflowList(selectMessageList(CATEGORY_WORKFLOW, userId));
return box;
}
@Override
public void sendMessage(Long userId, String message) {
PushHelper.sendMessage(userId, buildDefaultMessage(message));
}
@Override
public void sendMessage(String message) {
PushHelper.sendMessage(buildDefaultMessage(message));
}
@Override
public void sendMessage(Long userId, PushPayloadDTO payload) {
PushHelper.sendMessage(userId, payload);
}
@Override
public void sendMessage(PushPayloadDTO payload) {
PushHelper.sendMessage(payload);
}
@Override
public void publishMessage(List<Long> userIds, PushPayloadDTO payload) {
PushHelper.publishMessage(userIds, storeUsers(userIds, payload));
}
@Override
public void publishAll(String message) {
publishAll(buildDefaultMessage(message));
}
@Override
public void publishAll(PushPayloadDTO payload) {
PushHelper.publishAll(storeAll(payload));
}
@Override
public PushPayloadDTO storeAll(PushPayloadDTO payload) {
return storeMessage(null, payload);
}
@Override
public PushPayloadDTO storeUsers(List<Long> userIds, PushPayloadDTO payload) {
return storeMessage(userIds, payload);
}
private PushPayloadDTO storeMessage(List<Long> userIds, PushPayloadDTO payload) {
if (!supportsMessageBox(payload)) {
return payload;
}
SysMessage message = buildMessage(userIds, payload);
baseMapper.insert(message);
payload.setMessageId(message.getMessageId());
return payload;
}
private List<SysMessageVo> selectMessageList(String category, Long userId) {
LambdaQueryWrapper<SysMessage> lqw = Wrappers.lambdaQuery();
lqw.eq(SysMessage::getCategory, category);
lqw.ge(SysMessage::getCreateTime, new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(BOX_DAYS)));
lqw.and(wrapper -> wrapper.eq(SysMessage::getSendUserIds, GLOBAL_USER_IDS)
.or()
.apply(DataBaseHelper.findInSet(userId, "send_user_ids")));
lqw.orderByDesc(SysMessage::getCreateTime, SysMessage::getMessageId);
List<SysMessage> list = baseMapper.selectList(new Page<>(1, BOX_LIMIT, false), lqw);
return list.stream().map(this::buildVo).toList();
}
private SysMessage buildMessage(List<Long> userIds, PushPayloadDTO payload) {
SysMessage message = new SysMessage();
message.setMessageId(payload.getMessageId() == null ? IdGeneratorUtil.nextLongId() : payload.getMessageId());
message.setCategory(resolveCategory(payload));
message.setType(payload.getType());
message.setSource(payload.getSource());
message.setTitle(resolveTitle(payload));
message.setMessage(payload.getMessage());
message.setContent(resolveContent(payload));
message.setDataJson(JsonUtils.toJsonString(payload.getData()));
message.setPath(payload.getPath());
message.setSendUserIds(CollUtil.isEmpty(userIds) ? GLOBAL_USER_IDS : StringUtils.joinComma(userIds));
return message;
}
private SysMessageVo buildVo(SysMessage entity) {
SysMessageVo vo = MapstructUtils.convert(entity, SysMessageVo.class);
vo.setData(parseData(entity.getDataJson()));
return vo;
}
private boolean supportsMessageBox(PushPayloadDTO payload) {
if (payload == null) {
return false;
}
if (StringUtils.equalsAny(payload.getType(), PushTypeEnum.MESSAGE.getType(), PushTypeEnum.NOTICE.getType())) {
return !StringUtils.equalsAny(payload.getType(), PushTypeEnum.LLM.getType())
&& !StringUtils.equalsAny(payload.getSource(), PushSourceEnum.LLM.getSource());
}
return false;
}
private String resolveCategory(PushPayloadDTO payload) {
if (StringUtils.equalsAny(payload.getType(), PushTypeEnum.NOTICE.getType())
|| StringUtils.equalsAny(payload.getSource(), PushSourceEnum.NOTICE.getSource())) {
return CATEGORY_NOTICE;
}
if (StringUtils.equalsAny(payload.getSource(), PushSourceEnum.WORKFLOW.getSource())) {
return CATEGORY_WORKFLOW;
}
return CATEGORY_SYSTEM;
}
private String resolveTitle(PushPayloadDTO payload) {
return switch (resolveCategory(payload)) {
case CATEGORY_NOTICE -> "通知公告消息";
case CATEGORY_WORKFLOW -> "工作流消息";
default -> "系统消息";
};
}
private String resolveContent(PushPayloadDTO payload) {
Object data = payload.getData();
if (data instanceof Map<?, ?> map) {
return Convert.toStr(map.get("noticeContent"));
}
return null;
}
private Object parseData(String dataJson) {
if (StringUtils.isBlank(dataJson)) {
return null;
}
return JsonUtils.parseObject(dataJson, Object.class);
}
private PushPayloadDTO buildDefaultMessage(String message) {
return PushPayloadDTO.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null);
}
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.SysMessageMapper">
</mapper>