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

@@ -12,12 +12,13 @@ import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils; import me.zhyd.oauth.utils.AuthStateUtils;
import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R; 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.domain.model.LoginBody; import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.RegisterBody; import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.domain.model.SocialLoginBody; import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.enums.PushSourceEnum; import org.dromara.common.core.enums.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.service.MessageService;
import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MessageUtils; import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -26,7 +27,6 @@ import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.annotation.RateLimiter; import org.dromara.common.redis.annotation.RateLimiter;
import org.dromara.common.redis.enums.LimitType; import org.dromara.common.redis.enums.LimitType;
import org.dromara.common.push.helper.PushHelper;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties; import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties; import org.dromara.common.social.config.properties.SocialProperties;
@@ -66,6 +66,7 @@ public class AuthController {
private final ISysSocialService socialUserService; private final ISysSocialService socialUserService;
private final ISysClientService clientService; private final ISysClientService clientService;
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
private final MessageService messageService;
/** /**
@@ -95,9 +96,9 @@ public class AuthController {
Long userId = LoginHelper.getUserId(); Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> { scheduledExecutorService.schedule(() -> {
PushHelper.publishMessage( messageService.publishMessage(
List.of(userId), List.of(userId),
PushPayload.of( PushPayloadDTO.of(
PushTypeEnum.MESSAGE, PushTypeEnum.MESSAGE,
PushSourceEnum.BACKEND, PushSourceEnum.BACKEND,
DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Vue-Plus 后台管理系统", DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Vue-Plus 后台管理系统",

View File

@@ -14,11 +14,16 @@ import java.io.Serializable;
* @author Lion Li * @author Lion Li
*/ */
@Data @Data
public class PushPayload implements Serializable { public class PushPayloadDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 消息记录ID
*/
private Long messageId;
/** /**
* 消息类型 * 消息类型
*/ */
@@ -49,8 +54,8 @@ public class PushPayload implements Serializable {
*/ */
private Long timestamp; private Long timestamp;
public static PushPayload of(String type, String source, String message, Object data) { public static PushPayloadDTO of(String type, String source, String message, Object data) {
PushPayload payload = new PushPayload(); PushPayloadDTO payload = new PushPayloadDTO();
payload.setType(StringUtils.defaultIfBlank(type, PushTypeEnum.MESSAGE.getType())); payload.setType(StringUtils.defaultIfBlank(type, PushTypeEnum.MESSAGE.getType()));
payload.setSource(StringUtils.defaultIfBlank(source, PushSourceEnum.BACKEND.getSource())); payload.setSource(StringUtils.defaultIfBlank(source, PushSourceEnum.BACKEND.getSource()));
payload.setMessage(message); payload.setMessage(message);
@@ -59,7 +64,7 @@ public class PushPayload implements Serializable {
return payload; return payload;
} }
public static PushPayload of(PushTypeEnum type, PushSourceEnum source, String message, Object data) { public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data) {
return of( return of(
type == null ? null : type.getType(), type == null ? null : type.getType(),
source == null ? null : source.getSource(), source == null ? null : source.getSource(),
@@ -68,8 +73,8 @@ public class PushPayload implements Serializable {
); );
} }
public static PushPayload of(PushTypeEnum type, PushSourceEnum source, String message, Object data, String path) { public static PushPayloadDTO of(PushTypeEnum type, PushSourceEnum source, String message, Object data, String path) {
PushPayload payload = of(type, source, message, data); PushPayloadDTO payload = of(type, source, message, data);
payload.setPath(path); payload.setPath(path);
return payload; return payload;
} }

View File

@@ -0,0 +1,27 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.PushPayloadDTO;
import java.util.List;
/**
* 通用 消息服务
*
* @author Lion Li
*/
public interface MessageService {
void sendMessage(Long userId, String message);
void sendMessage(String message);
void sendMessage(Long userId, PushPayloadDTO payload);
void sendMessage(PushPayloadDTO payload);
void publishMessage(List<Long> userIds, PushPayloadDTO payload);
void publishAll(String message);
void publishAll(PushPayloadDTO payload);
}

View File

@@ -63,7 +63,7 @@ public class SseController implements DisposableBean {
// public R<Void> send(Long userId, String msg) { // public R<Void> send(Long userId, String msg) {
// PushDTO dto = new PushDTO(); // PushDTO dto = new PushDTO();
// dto.setUserIds(List.of(userId)); // dto.setUserIds(List.of(userId));
// dto.setPayload(PushPayload.of("message", "backend", msg, null)); // dto.setPayload(PushPayloadDTO.of("message", "backend", msg, null));
// sessionManager.publishMessage(dto); // sessionManager.publishMessage(dto);
// return R.ok(); // return R.ok();
// } // }

View File

@@ -1,6 +1,6 @@
package org.dromara.common.push.core; package org.dromara.common.push.core;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.push.dto.PushDTO; import org.dromara.common.push.dto.PushDTO;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -14,11 +14,11 @@ public interface PushSessionManager {
void subscribeMessage(Consumer<PushDTO> consumer); void subscribeMessage(Consumer<PushDTO> consumer);
void sendMessage(Long userId, PushPayload payload); void sendMessage(Long userId, PushPayloadDTO payload);
void sendMessage(PushPayload payload); void sendMessage(PushPayloadDTO payload);
void publishMessage(PushDTO pushDTO); void publishMessage(PushDTO pushDTO);
void publishAll(PushPayload payload); void publishAll(PushPayloadDTO payload);
} }

View File

@@ -3,7 +3,7 @@ package org.dromara.common.push.core;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.push.constant.MessageConstants; import org.dromara.common.push.constant.MessageConstants;
import org.dromara.common.push.dto.PushDTO; import org.dromara.common.push.dto.PushDTO;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
@@ -194,7 +194,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* @param payload 要发送的消息体 * @param payload 要发送的消息体
*/ */
@Override @Override
public void sendMessage(Long userId, PushPayload payload) { public void sendMessage(Long userId, PushPayloadDTO payload) {
sendMessage(userId, JsonUtils.toJsonString(payload)); sendMessage(userId, JsonUtils.toJsonString(payload));
} }
@@ -228,7 +228,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* @param payload 要发送的消息体 * @param payload 要发送的消息体
*/ */
@Override @Override
public void sendMessage(PushPayload payload) { public void sendMessage(PushPayloadDTO payload) {
sendMessage(JsonUtils.toJsonString(payload)); sendMessage(JsonUtils.toJsonString(payload));
} }
@@ -253,7 +253,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* @param message 要发布的消息内容 * @param message 要发布的消息内容
*/ */
public void publishAll(String message) { public void publishAll(String message) {
publishAll(PushPayload.of("message", "backend", message, null)); publishAll(PushPayloadDTO.of("message", "backend", message, null));
} }
/** /**
@@ -262,7 +262,7 @@ public class SseEmitterSessionManager implements PushSessionManager {
* @param payload 要发布的消息体 * @param payload 要发布的消息体
*/ */
@Override @Override
public void publishAll(PushPayload payload) { public void publishAll(PushPayloadDTO payload) {
PushDTO dto = new PushDTO(); PushDTO dto = new PushDTO();
dto.setPayload(payload); dto.setPayload(payload);
RedisUtils.publish(MessageConstants.MESSAGE_TOPIC, dto, consumer -> { RedisUtils.publish(MessageConstants.MESSAGE_TOPIC, dto, consumer -> {

View File

@@ -3,7 +3,7 @@ package org.dromara.common.push.core;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.push.dto.PushDTO; import org.dromara.common.push.dto.PushDTO;
@@ -90,7 +90,7 @@ public class WebSocketSessionManager implements PushSessionManager {
} }
@Override @Override
public void sendMessage(Long userId, PushPayload payload) { public void sendMessage(Long userId, PushPayloadDTO payload) {
if (payload == null) { if (payload == null) {
return; return;
} }
@@ -113,7 +113,7 @@ public class WebSocketSessionManager implements PushSessionManager {
} }
@Override @Override
public void sendMessage(PushPayload payload) { public void sendMessage(PushPayloadDTO payload) {
USER_TOKEN_SESSIONS.keySet().forEach(userId -> sendMessage(userId, payload)); USER_TOKEN_SESSIONS.keySet().forEach(userId -> sendMessage(userId, payload));
} }
@@ -128,7 +128,7 @@ public class WebSocketSessionManager implements PushSessionManager {
} }
@Override @Override
public void publishAll(PushPayload payload) { public void publishAll(PushPayloadDTO payload) {
PushDTO dto = new PushDTO(); PushDTO dto = new PushDTO();
dto.setPayload(payload); dto.setPayload(payload);
publishMessage(dto); publishMessage(dto);

View File

@@ -1,7 +1,7 @@
package org.dromara.common.push.dto; package org.dromara.common.push.dto;
import lombok.Data; import lombok.Data;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
@@ -26,5 +26,5 @@ public class PushDTO implements Serializable {
/** /**
* 推送消息体。 * 推送消息体。
*/ */
private PushPayload payload; private PushPayloadDTO payload;
} }

View File

@@ -3,7 +3,7 @@ package org.dromara.common.push.handler;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.PushSourceEnum; import org.dromara.common.core.enums.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
@@ -61,7 +61,7 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
} }
PushDTO dto = new PushDTO(); PushDTO dto = new PushDTO();
dto.setUserIds(List.of(loginUser.getUserId())); dto.setUserIds(List.of(loginUser.getUserId()));
dto.setPayload(PushPayload.of( dto.setPayload(PushPayloadDTO.of(
PushTypeEnum.CUSTOM, PushTypeEnum.CUSTOM,
PushSourceEnum.CLIENT, PushSourceEnum.CLIENT,
message.getPayload(), message.getPayload(),

View File

@@ -2,7 +2,7 @@ package org.dromara.common.push.helper;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
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.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
@@ -27,21 +27,21 @@ public class PushHelper {
sendMessage(buildMessage(message)); sendMessage(buildMessage(message));
} }
public static void sendMessage(Long userId, PushPayload payload) { public static void sendMessage(Long userId, PushPayloadDTO payload) {
if (!isEnabled()) { if (!isEnabled()) {
return; return;
} }
getSessionManager().sendMessage(userId, payload); getSessionManager().sendMessage(userId, payload);
} }
public static void sendMessage(PushPayload payload) { public static void sendMessage(PushPayloadDTO payload) {
if (!isEnabled()) { if (!isEnabled()) {
return; return;
} }
getSessionManager().sendMessage(payload); getSessionManager().sendMessage(payload);
} }
public static void publishMessage(List<Long> userIds, PushPayload payload) { public static void publishMessage(List<Long> userIds, PushPayloadDTO payload) {
PushDTO dto = new PushDTO(); PushDTO dto = new PushDTO();
dto.setUserIds(userIds); dto.setUserIds(userIds);
dto.setPayload(payload); dto.setPayload(payload);
@@ -59,7 +59,7 @@ public class PushHelper {
publishAll(buildMessage(message)); publishAll(buildMessage(message));
} }
public static void publishAll(PushPayload payload) { public static void publishAll(PushPayloadDTO payload) {
if (!isEnabled()) { if (!isEnabled()) {
return; return;
} }
@@ -74,7 +74,7 @@ public class PushHelper {
return SpringUtils.getBean(PushSessionManager.class); return SpringUtils.getBean(PushSessionManager.class);
} }
private static PushPayload buildMessage(String message) { private static PushPayloadDTO buildMessage(String message) {
return PushPayload.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null); return PushPayloadDTO.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null);
} }
} }

View File

@@ -1,10 +1,10 @@
package org.dromara.demo.controller; package org.dromara.demo.controller;
import org.dromara.common.core.domain.R; 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.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.push.helper.PushHelper; import org.dromara.common.core.service.MessageService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -24,6 +24,8 @@ import java.util.List;
@Slf4j @Slf4j
public class WebSocketController { public class WebSocketController {
private final MessageService messageService;
/** /**
* 发布消息 * 发布消息
* *
@@ -32,16 +34,16 @@ public class WebSocketController {
*/ */
@GetMapping("/send") @GetMapping("/send")
public R<Void> send(Long userId, String message) { public R<Void> send(Long userId, String message) {
PushPayload payload = PushPayload.of( PushPayloadDTO payload = PushPayloadDTO.of(
PushTypeEnum.MESSAGE, PushTypeEnum.MESSAGE,
PushSourceEnum.BACKEND, PushSourceEnum.BACKEND,
message, message,
null null
); );
if (userId == null) { if (userId == null) {
PushHelper.publishAll(payload); messageService.publishAll(payload);
} else { } else {
PushHelper.publishMessage(List.of(userId), payload); messageService.publishMessage(List.of(userId), payload);
} }
return R.ok("操作成功"); return R.ok("操作成功");
} }

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 lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R; 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.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.service.DictService; 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.annotation.Log;
import org.dromara.common.log.enums.BusinessType; import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery; 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.redis.annotation.RepeatSubmit;
import org.dromara.common.web.core.BaseController; import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysNoticeBo; import org.dromara.system.domain.bo.SysNoticeBo;
@@ -36,6 +36,7 @@ public class SysNoticeController extends BaseController {
private final ISysNoticeService noticeService; private final ISysNoticeService noticeService;
private final DictService dictService; private final DictService dictService;
private final MessageService messageService;
/** /**
* 分页查询通知公告列表。 * 分页查询通知公告列表。
@@ -83,7 +84,9 @@ public class SysNoticeController extends BaseController {
data.put("noticeTypeLabel", type); data.put("noticeTypeLabel", type);
data.put("noticeTitle", notice.getNoticeTitle()); data.put("noticeTitle", notice.getNoticeTitle());
data.put("noticeId", notice.getNoticeId()); 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, PushTypeEnum.NOTICE,
PushSourceEnum.NOTICE, PushSourceEnum.NOTICE,
"[" + type + "] " + notice.getNoticeTitle(), "[" + 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>

View File

@@ -4,16 +4,16 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.PushPayload; import org.dromara.common.core.domain.dto.PushPayloadDTO;
import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.domain.dto.UserDTO;
import org.dromara.common.core.enums.PushSourceEnum; import org.dromara.common.core.enums.PushSourceEnum;
import org.dromara.common.core.enums.PushTypeEnum; import org.dromara.common.core.enums.PushTypeEnum;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.MessageService;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mail.utils.MailUtils; import org.dromara.common.mail.utils.MailUtils;
import org.dromara.common.push.helper.PushHelper;
import org.dromara.warm.flow.core.FlowEngine; import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.entity.Node; import org.dromara.warm.flow.core.entity.Node;
import org.dromara.warm.flow.orm.entity.FlowTask; import org.dromara.warm.flow.orm.entity.FlowTask;
@@ -41,6 +41,7 @@ import java.util.Set;
public class FlwCommonServiceImpl implements IFlwCommonService { public class FlwCommonServiceImpl implements IFlwCommonService {
private static final String DEFAULT_SUBJECT = "单据审批提醒"; private static final String DEFAULT_SUBJECT = "单据审批提醒";
private final MessageService messageService;
/** /**
* 根据流程实例发送消息给当前处理人 * 根据流程实例发送消息给当前处理人
@@ -94,7 +95,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
try { try {
switch (messageTypeEnum) { switch (messageTypeEnum) {
case SYSTEM_MESSAGE -> { case SYSTEM_MESSAGE -> {
PushHelper.publishMessage(userIds, PushPayload.of( messageService.publishMessage(userIds, PushPayloadDTO.of(
PushTypeEnum.MESSAGE, PushTypeEnum.MESSAGE,
PushSourceEnum.WORKFLOW, PushSourceEnum.WORKFLOW,
message, message,

View File

@@ -925,7 +925,50 @@ insert into sys_notice values('2', '维护通知2018-07-01 系统凌晨维护
-- ---------------------------- -- ----------------------------
-- 18、代码生成业务 -- 18、消息记录
-- ----------------------------
create table sys_message (
message_id number(20) not null,
category varchar2(20) not null,
type varchar2(20) not null,
source varchar2(20) not null,
title varchar2(100) default '',
message varchar2(500) default '',
content clob default null,
data_json clob default null,
path varchar2(500) default null,
send_user_ids varchar2(2000) default '0' not null,
create_dept number(20) default null,
create_by number(20) default null,
create_time date,
update_by number(20) default null,
update_time date
);
alter table sys_message add constraint pk_sys_message primary key (message_id);
create index idx_sys_message_category_time on sys_message(category, create_time);
comment on table sys_message is '消息记录表';
comment on column sys_message.message_id is '消息ID';
comment on column sys_message.category is '消息分组(system/notice/workflow)';
comment on column sys_message.type is '消息类型';
comment on column sys_message.source is '消息来源';
comment on column sys_message.title is '标题';
comment on column sys_message.message is '摘要消息';
comment on column sys_message.content is '详细内容';
comment on column sys_message.data_json is '扩展数据JSON';
comment on column sys_message.path is '前端跳转路径';
comment on column sys_message.send_user_ids is '目标用户ID串0表示全局';
comment on column sys_message.create_dept is '创建部门';
comment on column sys_message.create_by is '创建者';
comment on column sys_message.create_time is '创建时间';
comment on column sys_message.update_by is '更新者';
comment on column sys_message.update_time is '更新时间';
-- ----------------------------
-- 19、代码生成业务表
-- ---------------------------- -- ----------------------------
create table gen_table ( create table gen_table (
table_id number(20) not null, table_id number(20) not null,
@@ -980,7 +1023,7 @@ comment on column gen_table.remark is '备注';
-- ---------------------------- -- ----------------------------
-- 19、代码生成业务表字段 -- 20、代码生成业务表字段
-- ---------------------------- -- ----------------------------
create table gen_table_column ( create table gen_table_column (
column_id number(20) not null, column_id number(20) not null,

View File

@@ -923,7 +923,50 @@ insert into sys_notice values('2', '维护通知2018-07-01 系统凌晨维护
-- ---------------------------- -- ----------------------------
-- 18、代码生成业务 -- 18、消息记录
-- ----------------------------
create table if not exists sys_message
(
message_id int8,
category varchar(20) not null,
type varchar(20) not null,
source varchar(20) not null,
title varchar(100) default ''::varchar,
message varchar(500) default ''::varchar,
content text,
data_json text,
path varchar(500) default null::varchar,
send_user_ids varchar(2000) not null default '0'::varchar,
create_dept int8,
create_by int8,
create_time timestamp,
update_by int8,
update_time timestamp,
constraint sys_message_pk primary key (message_id)
);
create index if not exists idx_sys_message_category_time on sys_message (category, create_time);
comment on table sys_message is '消息记录表';
comment on column sys_message.message_id is '消息ID';
comment on column sys_message.category is '消息分组(system/notice/workflow)';
comment on column sys_message.type is '消息类型';
comment on column sys_message.source is '消息来源';
comment on column sys_message.title is '标题';
comment on column sys_message.message is '摘要消息';
comment on column sys_message.content is '详细内容';
comment on column sys_message.data_json is '扩展数据JSON';
comment on column sys_message.path is '前端跳转路径';
comment on column sys_message.send_user_ids is '目标用户ID串0表示全局';
comment on column sys_message.create_dept is '创建部门';
comment on column sys_message.create_by is '创建者';
comment on column sys_message.create_time is '创建时间';
comment on column sys_message.update_by is '更新者';
comment on column sys_message.update_time is '更新时间';
-- ----------------------------
-- 19、代码生成业务表
-- ---------------------------- -- ----------------------------
create table if not exists gen_table create table if not exists gen_table
( (
@@ -977,7 +1020,7 @@ comment on column gen_table.update_time is '更新时间';
comment on column gen_table.remark is '备注'; comment on column gen_table.remark is '备注';
-- ---------------------------- -- ----------------------------
-- 19、代码生成业务表字段 -- 20、代码生成业务表字段
-- ---------------------------- -- ----------------------------
create table if not exists gen_table_column create table if not exists gen_table_column
( (

View File

@@ -689,7 +689,31 @@ insert into sys_notice values('2', '维护通知2018-07-01 系统凌晨维护
-- ---------------------------- -- ----------------------------
-- 18、代码生成业务 -- 18、消息记录
-- ----------------------------
create table sys_message (
message_id bigint(20) not null comment '消息ID',
category varchar(20) not null comment '消息分组(system/notice/workflow)',
type varchar(20) not null comment '消息类型',
source varchar(20) not null comment '消息来源',
title varchar(100) default '' comment '标题',
message varchar(500) default '' comment '摘要消息',
content longtext comment '详细内容',
data_json longtext comment '扩展数据JSON',
path varchar(500) default null comment '前端跳转路径',
send_user_ids varchar(2000) not null default '0' comment '目标用户ID串0表示全局',
create_dept bigint(20) default null comment '创建部门',
create_by bigint(20) default null comment '创建者',
create_time datetime comment '创建时间',
update_by bigint(20) default null comment '更新者',
update_time datetime comment '更新时间',
primary key (message_id),
key idx_sys_message_category_time (category, create_time)
) engine=innodb comment = '消息记录表';
-- ----------------------------
-- 19、代码生成业务表
-- ---------------------------- -- ----------------------------
create table gen_table ( create table gen_table (
table_id bigint(20) not null comment '编号', table_id bigint(20) not null comment '编号',

View File

@@ -1674,6 +1674,130 @@ GO
INSERT sys_notice VALUES (2, N'维护通知2018-07-01 若依系统凌晨维护', N'1', N'维护内容', N'0', 103, 1, getdate(), NULL, NULL, N'管理员') INSERT sys_notice VALUES (2, N'维护通知2018-07-01 若依系统凌晨维护', N'1', N'维护内容', N'0', 103, 1, getdate(), NULL, NULL, N'管理员')
GO GO
CREATE TABLE sys_message
(
message_id bigint NOT NULL,
category nvarchar(20) NOT NULL,
type nvarchar(20) NOT NULL,
source nvarchar(20) NOT NULL,
title nvarchar(100) DEFAULT ('') NULL,
message nvarchar(500) DEFAULT ('') NULL,
content nvarchar(max) NULL,
data_json nvarchar(max) NULL,
path nvarchar(500) NULL,
send_user_ids nvarchar(2000) DEFAULT ('0') NOT NULL,
create_dept bigint NULL,
create_by bigint NULL,
create_time datetime2(7) NULL,
update_by bigint NULL,
update_time datetime2(7) NULL,
CONSTRAINT PK__sys_mess__0BBF6EE69F35486A PRIMARY KEY CLUSTERED (message_id)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
)
ON [PRIMARY]
TEXTIMAGE_ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX idx_sys_message_category_time ON sys_message(category, create_time)
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'消息ID' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'message_id'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'消息分组(system/notice/workflow)' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'category'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'消息类型' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'type'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'消息来源' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'source'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'标题' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'title'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'摘要消息' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'message'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'详细内容' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'content'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'扩展数据JSON' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'data_json'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'前端跳转路径' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'path'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'目标用户ID串0表示全局' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'send_user_ids'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'创建部门' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'create_dept'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'创建者' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'create_by'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'创建时间' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'create_time'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'更新者' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'update_by'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'更新时间' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message',
'COLUMN', N'update_time'
GO
EXEC sys.sp_addextendedproperty
'MS_Description', N'消息记录表' ,
'SCHEMA', N'dbo',
'TABLE', N'sys_message'
GO
CREATE TABLE sys_oper_log CREATE TABLE sys_oper_log
( (
oper_id bigint NOT NULL, oper_id bigint NOT NULL,