消息接口

This commit is contained in:
曹佳豪 2025-06-19 09:30:09 +08:00
parent 3b9712b361
commit e815bc6e05
34 changed files with 1808 additions and 15 deletions

View File

@ -31,11 +31,11 @@ public class BaseEntity implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private String searchValue; private String searchValue;
/** // /**
* 创建部门 // * 创建部门
*/ // */
@TableField(fill = FieldFill.INSERT) // @TableField(fill = FieldFill.INSERT)
private Long createDept; // private Long createDept;
/** /**
* 创建者 * 创建者

View File

@ -45,7 +45,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
// 填充创建人更新人和创建部门信息 // 填充创建人更新人和创建部门信息
baseEntity.setCreateBy(userId); baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId); baseEntity.setUpdateBy(userId);
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId())); // baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
} }
} }
} else { } else {

View File

@ -20,6 +20,7 @@
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId> <artifactId>ruoyi-common-core</artifactId>
<version>${revision}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -112,6 +113,23 @@
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
</dependency> </dependency>
<!-- Spring Boot WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Jakarta WebSocket API -->
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
</dependency>
<!-- Jakarta WebSocket Implementation -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,178 @@
package org.dromara.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.service.UserService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
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.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysMessageBo;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysMessageVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysMessageService;
import org.dromara.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 消息管理
*
* @author ruoyi
*/
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/message")
public class SysMessageController extends BaseController {
private final ISysMessageService messageService;
private final ISysUserService userService;
/**
* 获取当前用户ID
*/
private Long getUserId() {
return LoginHelper.getUserId();
}
/**
* 查询消息列表
*/
@SaCheckPermission("system:message:list")
@PostMapping("/list")
public R<Page<SysMessageVo>> list(@RequestBody SysMessageBo bo, @RequestBody Page page) {
Page<SysMessage> messagePage = messageService.page(page, bo.toWrapper());
Page<SysMessageVo> voPage = new Page<>(messagePage.getCurrent(), messagePage.getSize(), messagePage.getTotal());
voPage.setRecords(MapstructUtils.convert(messagePage.getRecords(), SysMessageVo.class));
return R.ok(voPage);
}
/**
* 获取消息详细信息
*
* @param id 消息ID
*/
@SaCheckPermission("system:message:query")
@GetMapping("/{id}")
public R<SysMessageVo> getInfo(@NotNull(message = "消息ID不能为空") @PathVariable Long id) {
return R.ok(messageService.selectMessageById(id));
}
/**
* 发送消息
*/
@SaCheckPermission("system:message:send")
@Log(title = "消息管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/send")
public R<Void> send(@Validated(AddGroup.class) @RequestBody SysMessageBo bo) {
List<SysUserVo> users = userService.selectUserListByDept(null);
List<Long> userIds;
switch (bo.getSendScope()) {
case "all":
// 全部用户
userIds = users.stream().map(SysUserVo::getUserId).toList();
break;
case "expert":
// 达人
userIds = users.stream()
.filter(user -> "expert".equals(user.getUserType()))
.map(SysUserVo::getUserId)
.toList();
break;
case "merchant":
// 商户
userIds = users.stream()
.filter(user -> "merchant".equals(user.getUserType()))
.map(SysUserVo::getUserId)
.toList();
break;
case "user":
// 普通用户
userIds = users.stream()
.filter(user -> "user".equals(user.getUserType()))
.map(SysUserVo::getUserId)
.toList();
break;
default:
// 其他情况如指定userIds
userIds = bo.getUserIds();
}
return toAjax(messageService.sendMessageToUsers(bo, userIds));
}
/**
* 标记消息为已读
*/
@SaCheckPermission("system:message:mark")
@Log(title = "消息管理", businessType = BusinessType.UPDATE)
@PutMapping("/mark/{id}")
public R<Void> markAsRead(@NotNull(message = "消息ID不能为空") @PathVariable Long id) {
return toAjax(messageService.markAsRead(id, getUserId()));
}
/**
* 删除消息
*
* @param ids 消息ID串
*/
@SaCheckPermission("system:message:remove")
@Log(title = "消息管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "消息ID不能为空") @PathVariable Long[] ids) {
return toAjax(messageService.deleteMessageByIds(ids));
}
/**
* 获取未读消息列表
*/
@SaCheckPermission("system:message:list")
@GetMapping("/unread")
public TableDataInfo<SysMessageVo> unreadList(PageQuery pageQuery) {
return messageService.selectPageUnreadMessages(getUserId(), pageQuery);
}
/**
* 获取已读消息列表
*/
@SaCheckPermission("system:message:list")
@GetMapping("/read")
public TableDataInfo<SysMessageVo> readList(PageQuery pageQuery) {
return messageService.selectPageReadMessages(getUserId(), pageQuery);
}
/**
* 获取用户列表
*/
// @SaCheckPermission("system:message:list")
// @GetMapping("/user/list")
// public R<TableDataInfo<SysUserVo>> getUserList() {
// PageQuery pageQuery = new PageQuery();
// pageQuery.setPageNum(1);
// pageQuery.setPageSize(Integer.MAX_VALUE);
// TableDataInfo<SysUserVo> users = userService.selectPageUserList(new SysUserBo(), pageQuery);
// return R.ok(users);
// }
}

View File

@ -0,0 +1,90 @@
package org.dromara.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
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.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysMessageTemplateBo;
import org.dromara.system.domain.vo.SysMessageTemplateVo;
import org.dromara.system.service.ISysMessageTemplateService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 消息模板管理
*
* @author ruoyi
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/message/template")
public class SysMessageTemplateController extends BaseController {
private final ISysMessageTemplateService templateService;
/**
* 查询消息模板列表
*/
@SaCheckPermission("system:message:template:list")
@GetMapping("/list")
public TableDataInfo<SysMessageTemplateVo> list(SysMessageTemplateBo bo, PageQuery pageQuery) {
return templateService.selectTemplatePage(bo, pageQuery);
}
/**
* 获取消息模板详细信息
*
* @param id 消息模板ID
*/
@SaCheckPermission("system:message:template:query")
@GetMapping("/{id}")
public R<SysMessageTemplateVo> getInfo(@NotNull(message = "消息模板ID不能为空") @PathVariable Long id) {
return R.ok(templateService.selectTemplateById(id));
}
/**
* 新增消息模板
*/
@SaCheckPermission("system:message:template:add")
@Log(title = "消息模板管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysMessageTemplateBo bo) {
return toAjax(templateService.insertTemplate(bo));
}
/**
* 修改消息模板
*/
@SaCheckPermission("system:message:template:edit")
@Log(title = "消息模板管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysMessageTemplateBo bo) {
return toAjax(templateService.updateTemplate(bo));
}
/**
* 删除消息模板
*
* @param ids 消息模板ID串
*/
@SaCheckPermission("system:message:template:remove")
@Log(title = "消息模板管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "消息模板ID不能为空") @PathVariable Long[] ids) {
return toAjax(templateService.deleteTemplateByIds(ids));
}
}

View File

@ -0,0 +1,39 @@
package org.dromara.system.converter;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.vo.SysMessageVo;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* 消息对象转换器
*
* @author ruoyi
*/
@Mapper(componentModel = "spring")
public interface SysMessageConvert {
SysMessageConvert INSTANCE = Mappers.getMapper(SysMessageConvert.class);
/**
* SysMessage转SysMessageVo
*/
SysMessageVo convert(SysMessage sysMessage);
/**
* SysMessage列表转SysMessageVo列表
*/
List<SysMessageVo> convertList(List<SysMessage> sysMessageList);
/**
* SysMessageVo转SysMessage
*/
SysMessage convert(SysMessageVo sysMessageVo);
/**
* SysMessageVo列表转SysMessage列表
*/
List<SysMessage> convertListVoToEntity(List<SysMessageVo> sysMessageVoList);
}

View File

@ -0,0 +1,49 @@
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;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serializable;
import java.util.Date;
/**
* 消息对象 sys_message
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_message")
public class SysMessage extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(value = "id")
private Long id;
/** 消息标题 */
private String title;
/** 消息内容 */
private String content;
/** 消息类型AUTO自动/MANUAL手动 */
private String msgType;
/** 触发条件 */
private String subType;
/** 发送者ID */
private Long senderId;
/** 定时发送时间 */
private Date scheduledTime;
// /** 状态0正常 1停用 */
// private String status;
}

View File

@ -0,0 +1,62 @@
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;
import org.dromara.common.tenant.core.TenantEntity;
/**
* 消息模板对象 sys_message_template
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_message_template")
public class SysMessageTemplate extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 模板ID
*/
@TableId(value = "id")
private Long id;
/**
* 模板类型1通知 2公告 3消息
*/
private String templateType;
/**
* 模板编码
*/
private String templateCode;
/**
* 模板名称
*/
private String templateName;
/**
* 模板内容
*/
private String templateContent;
/**
* 模板参数
*/
private String templateParams;
/**
* 状态0正常 1停用
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,49 @@
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;
import org.dromara.common.tenant.core.TenantEntity;
import java.util.Date;
/**
* 消息用户关联对象 sys_message_user
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_message_user")
public class SysMessageUser extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 消息ID
*/
private Long messageId;
/**
* 用户ID
*/
private Long userId;
/**
* 是否已读0未读 1已读
*/
private Boolean isRead;
/**
* 阅读时间
*/
private Date readTime;
}

View File

@ -0,0 +1,104 @@
package org.dromara.system.domain.bo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysMessage;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import java.util.Date;
import java.util.List;
/**
* 消息业务对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ExcelIgnoreUnannotated
public class SysMessageBo extends BaseEntity {
/** 主键ID */
@ExcelProperty(value = "消息ID")
private Long id;
/** 消息标题 */
@NotBlank(message = "消息标题不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(min = 0, max = 100, message = "消息标题长度不能超过100个字符")
@ExcelProperty(value = "消息标题")
private String title;
/** 消息内容 */
@NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class })
@ExcelProperty(value = "消息内容")
private String content;
/** 消息类型AUTO自动/MANUAL手动 */
@NotBlank(message = "消息类型不能为空", groups = { AddGroup.class, EditGroup.class })
@ExcelProperty(value = "消息类型")
private String msgType;
/** 触发条件 */
@ExcelProperty(value = "触发条件")
private String subType;
/** 发送者ID */
@ExcelProperty(value = "发送者ID")
private Long senderId;
/** 定时发送时间 */
@ExcelProperty(value = "定时发送时间")
private Date scheduledTime;
// /** 状态0正常 1停用 */
// @ExcelProperty(value = "状态")
// private String status;
/** 发送范围all:全部用户, userType:按用户类型, userIds:指定用户) */
private String sendScope;
/** 接收用户ID列表 */
private List<Long> userIds;
// /** 用户类型1普通用户 2商家 3达人 4代理 */
// private String userType;
/** 是否发送给所有用户 */
private Boolean sendToAll;
public SysMessage toEntity() {
SysMessage entity = new SysMessage();
entity.setId(this.getId());
entity.setTitle(this.getTitle());
entity.setContent(this.getContent());
entity.setMsgType(this.getMsgType());
entity.setSubType(this.getSubType());
entity.setSenderId(this.getSenderId());
entity.setScheduledTime(this.getScheduledTime());
// entity.setStatus(this.getStatus());
entity.setCreateBy(this.getCreateBy());
entity.setCreateTime(this.getCreateTime());
entity.setUpdateBy(this.getUpdateBy());
entity.setUpdateTime(this.getUpdateTime());
return entity;
}
/**
* 转换为查询条件
*/
public LambdaQueryWrapper<SysMessage> toWrapper() {
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
lqw.like(StringUtils.isNotBlank(this.getTitle()), SysMessage::getTitle, this.getTitle())
.eq(StringUtils.isNotBlank(this.getMsgType()), SysMessage::getMsgType, this.getMsgType())
.orderByDesc(SysMessage::getCreateTime);
return lqw;
}
}

View File

@ -0,0 +1,90 @@
package org.dromara.system.domain.bo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.system.domain.SysMessageTemplate;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
/**
* 消息模板业务对象 sys_message_template
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysMessageTemplateBo extends BaseEntity {
/**
* 模板ID
*/
@NotNull(message = "模板ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 模板类型1通知 2公告 3消息
*/
@NotBlank(message = "模板类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String templateType;
/**
* 模板编码
*/
@NotBlank(message = "模板编码不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(min = 0, max = 64, message = "模板编码长度不能超过64个字符")
private String templateCode;
/**
* 模板名称
*/
@NotBlank(message = "模板名称不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(min = 0, max = 100, message = "模板名称长度不能超过100个字符")
private String templateName;
/**
* 模板内容
*/
@NotBlank(message = "模板内容不能为空", groups = { AddGroup.class, EditGroup.class })
private String templateContent;
/**
* 模板参数
*/
private String templateParams;
// /**
// * 状态0正常 1停用
// */
// @NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class })
// private String status;
/**
* 备注
*/
private String remark;
/**
* 转换为实体对象
*/
public SysMessageTemplate toEntity() {
SysMessageTemplate entity = new SysMessageTemplate();
entity.setId(this.getId());
entity.setTemplateType(this.getTemplateType());
entity.setTemplateCode(this.getTemplateCode());
entity.setTemplateName(this.getTemplateName());
entity.setTemplateContent(this.getTemplateContent());
entity.setTemplateParams(this.getTemplateParams());
// entity.setStatus(this.getStatus());
entity.setRemark(this.getRemark());
entity.setCreateBy(this.getCreateBy());
entity.setCreateTime(this.getCreateTime());
entity.setUpdateBy(this.getUpdateBy());
entity.setUpdateTime(this.getUpdateTime());
return entity;
}
}

View File

@ -0,0 +1,23 @@
package org.dromara.system.domain.event;
import lombok.Getter;
import org.dromara.system.domain.vo.SysMessageVo;
import org.springframework.context.ApplicationEvent;
/**
* 消息事件
*
* @author ruoyi
*/
@Getter
public class MessageEvent extends ApplicationEvent {
private final SysMessageVo message;
private final Long userId;
public MessageEvent(Object source, SysMessageVo message, Long userId) {
super(source);
this.message = message;
this.userId = userId;
}
}

View File

@ -0,0 +1,59 @@
package org.dromara.system.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serializable;
/**
* 消息模板视图对象 sys_message_template
*
* @author ruoyi
*/
@Data
//@EqualsAndHashCode(callSuper = true)
public class SysMessageTemplateVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 模板ID
*/
private Long id;
/**
* 模板类型1通知 2公告 3消息
*/
private String templateType;
/**
* 模板编码
*/
private String templateCode;
/**
* 模板名称
*/
private String templateName;
/**
* 模板内容
*/
private String templateContent;
/**
* 模板参数
*/
private String templateParams;
// /**
// * 状态0正常 1停用
// */
// private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,45 @@
package org.dromara.system.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.tenant.core.TenantEntity;
import java.io.Serializable;
import java.util.Date;
/**
* 消息用户关联视图对象 sys_message_user
*
* @author ruoyi
*/
@Data
//@EqualsAndHashCode(callSuper = true)
public class SysMessageUserVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Long id;
/**
* 消息ID
*/
private Long messageId;
/**
* 用户ID
*/
private Long userId;
/**
* 是否已读0未读 1已读
*/
private Boolean isRead;
/**
* 阅读时间
*/
private Date readTime;
}

View File

@ -0,0 +1,65 @@
package org.dromara.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.SysNotice;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 消息视图对象
*
* @author ruoyi
*/
@Data
@AutoMapper(target = SysMessage.class)
public class SysMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@ExcelProperty(value = "消息ID")
private Long id;
/** 消息标题 */
@ExcelProperty(value = "消息标题")
private String title;
/** 消息内容 */
@ExcelProperty(value = "消息内容")
private String content;
/** 消息类型AUTO自动/MANUAL手动 */
@ExcelProperty(value = "消息类型")
private String msgType;
/** 触发条件 */
@ExcelProperty(value = "触发条件")
private String subType;
/** 发送者ID */
@ExcelProperty(value = "发送者ID")
private Long senderId;
/** 发送者名称 */
@ExcelProperty(value = "发送者")
private String senderName;
/** 定时发送时间 */
@ExcelProperty(value = "定时发送时间")
private Date scheduledTime;
// /** 状态0正常 1停用 */
// @ExcelProperty(value = "状态")
// private String status;
/** 创建时间 */
@ExcelProperty(value = "创建时间")
private Date createTime;
}

View File

@ -0,0 +1,35 @@
package org.dromara.system.event;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.domain.event.MessageEvent;
import org.dromara.system.websocket.MessageWebSocketServer;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 消息事件监听器
*
* @author ruoyi
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MessageEventListener {
private final MessageWebSocketServer messageWebSocketServer;
/**
* 处理消息事件
*/
@Async
@EventListener
public void handleMessageEvent(MessageEvent event) {
try {
messageWebSocketServer.sendMessage(event.getUserId(), String.valueOf(event.getMessage()));
} catch (Exception e) {
log.error("处理消息事件失败", e);
}
}
}

View File

@ -0,0 +1,23 @@
package org.dromara.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.vo.SysMessageVo;
/**
* 消息Mapper接口
*
* @author ruoyi
*/
public interface SysMessageMapper extends BaseMapper<SysMessage> {
/**
* 查询用户未读消息分页列表
*
* @param page 分页参数
* @param userId 用户ID
* @return 消息分页列表
*/
Page<SysMessageVo> selectPageUnreadMessages(Page<SysMessageVo> page, Long userId);
}

View File

@ -0,0 +1,14 @@
package org.dromara.system.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysMessageTemplate;
import org.dromara.system.domain.vo.SysMessageTemplateVo;
/**
* 消息模板Mapper接口
*
* @author ruoyi
*/
public interface SysMessageTemplateMapper extends BaseMapperPlus<SysMessageTemplate, SysMessageTemplateVo> {
}

View File

@ -0,0 +1,32 @@
package org.dromara.system.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.SysMessageUser;
import org.dromara.system.domain.vo.SysMessageUserVo;
/**
* 消息用户关联Mapper接口
*
* @author ruoyi
*/
public interface SysMessageUserMapper extends BaseMapperPlus<SysMessageUser, SysMessageUserVo> {
/**
* 更新消息读取状态
*
* @param messageId 消息ID
* @param userId 用户ID
* @param isRead 是否已读
* @return 结果
*/
default int updateReadStatus(Long messageId, Long userId, boolean isRead) {
LambdaQueryWrapper<SysMessageUser> lqw = Wrappers.lambdaQuery();
lqw.eq(SysMessageUser::getMessageId, messageId)
.eq(SysMessageUser::getUserId, userId);
SysMessageUser messageUser = new SysMessageUser();
messageUser.setIsRead(isRead);
return update(messageUser, lqw);
}
}

View File

@ -120,4 +120,19 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
}) })
int updateById(@Param(Constants.ENTITY) SysUser user); int updateById(@Param(Constants.ENTITY) SysUser user);
/**
* 根据用户类型查询用户ID列表
*
* @param userType 用户类型
* @return 用户ID列表
*/
List<Long> selectUserIdsByType(@Param("userType") String userType);
/**
* 查询所有用户ID
*
* @return 用户ID列表
*/
List<Long> selectAllUserIds();
} }

View File

@ -0,0 +1,26 @@
package org.dromara.system.service;
import org.dromara.system.domain.vo.SysMessageVo;
/**
* WebSocket消息服务接口
*
* @author ruoyi
*/
public interface IMessageWebSocketService {
/**
* 发送消息给指定用户
*
* @param userId 用户ID
* @param message 消息内容
*/
void sendMessage(Long userId, SysMessageVo message);
/**
* 广播消息给所有在线用户
*
* @param message 消息内容
*/
void broadcastMessage(SysMessageVo message);
}

View File

@ -0,0 +1,136 @@
package org.dromara.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.bo.SysMessageBo;
import org.dromara.system.domain.vo.SysMessageVo;
import java.util.List;
/**
* 消息Service接口
*
* @author ruoyi
*/
public interface ISysMessageService extends IService<SysMessage> {
/**
* 分页查询消息列表
*/
TableDataInfo<SysMessageVo> selectPageMessageList(SysMessageBo bo, PageQuery pageQuery);
/**
* 发送消息给指定用户
*
* @param message 消息内容
* @param userId 用户ID
* @return 结果
*/
int sendMessageToUser(SysMessageBo message, Long userId);
/**
* 发送消息给多个用户
*
* @param message 消息内容
* @param userIds 用户ID列表
* @return 结果
*/
int sendMessageToUsers(SysMessageBo message, List<Long> userIds);
/**
* 发送自动消息
*
* @param message 消息信息
* @param userIds 用户ID列表
* @return 结果
*/
int sendAutoMessage(SysMessageBo message, List<Long> userIds);
/**
* 标记消息为已读
*
* @param messageId 消息ID
* @param userId 用户ID
* @return 结果
*/
int markAsRead(Long messageId, Long userId);
/**
* 查询用户未读消息分页列表
*
* @param userId 用户ID
* @param pageQuery 分页参数
* @return 消息分页列表
*/
TableDataInfo<SysMessageVo> selectPageUnreadMessages(Long userId, PageQuery pageQuery);
/**
* 查询用户已读消息分页列表
*
* @param userId 用户ID
* @param pageQuery 分页参数
* @return 消息分页列表
*/
TableDataInfo<SysMessageVo> selectPageReadMessages(Long userId, PageQuery pageQuery);
/**
* 查询消息列表
*
* @param message 消息信息
* @return 消息列表
*/
List<SysMessageVo> selectMessageList(SysMessageBo message);
/**
* 查询消息详细信息
*
* @param id 消息ID
* @return 消息信息
*/
SysMessageVo selectMessageById(Long id);
/**
* 新增消息
*
* @param message 消息信息
* @return 结果
*/
int insertMessage(SysMessageBo message);
/**
* 修改消息
*
* @param message 消息信息
* @return 结果
*/
int updateMessage(SysMessageBo message);
/**
* 批量删除消息
*
* @param ids 需要删除的消息ID数组
* @return 结果
*/
int deleteMessageByIds(Long[] ids);
/**
* 删除消息信息
*
* @param id 消息ID
* @return 结果
*/
int deleteMessageById(Long id);
/**
* 将BO对象转换为查询条件
*
* @param bo 业务对象
* @return 查询条件
*/
LambdaQueryWrapper<SysMessage> toWrapper(SysMessageBo bo);
}

View File

@ -0,0 +1,75 @@
package org.dromara.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysMessageTemplate;
import org.dromara.system.domain.bo.SysMessageTemplateBo;
import org.dromara.system.domain.vo.SysMessageTemplateVo;
import java.util.List;
/**
* 消息模板Service接口
*
* @author ruoyi
*/
public interface ISysMessageTemplateService extends IService<SysMessageTemplate> {
/**
* 查询消息模板列表
*
* @param template 消息模板信息
* @return 消息模板列表
*/
List<SysMessageTemplateVo> selectTemplateList(SysMessageTemplateBo template);
/**
* 查询消息模板分页列表
*
* @param template 消息模板信息
* @param pageQuery 分页参数
* @return 消息模板分页列表
*/
TableDataInfo<SysMessageTemplateVo> selectTemplatePage(SysMessageTemplateBo template, PageQuery pageQuery);
/**
* 查询消息模板详细信息
*
* @param id 消息模板ID
* @return 消息模板信息
*/
SysMessageTemplateVo selectTemplateById(Long id);
/**
* 新增消息模板
*
* @param template 消息模板信息
* @return 结果
*/
int insertTemplate(SysMessageTemplateBo template);
/**
* 修改消息模板
*
* @param template 消息模板信息
* @return 结果
*/
int updateTemplate(SysMessageTemplateBo template);
/**
* 批量删除消息模板
*
* @param ids 需要删除的消息模板ID数组
* @return 结果
*/
int deleteTemplateByIds(Long[] ids);
/**
* 删除消息模板信息
*
* @param id 消息模板ID
* @return 结果
*/
int deleteTemplateById(Long id);
}

View File

@ -0,0 +1,31 @@
package org.dromara.system.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.domain.vo.SysMessageVo;
import org.dromara.system.service.IMessageWebSocketService;
import org.dromara.system.websocket.MessageWebSocketServer;
import org.springframework.stereotype.Service;
/**
* WebSocket消息服务实现类
*
* @author ruoyi
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageWebSocketServiceImpl implements IMessageWebSocketService {
private final MessageWebSocketServer messageWebSocketServer;
@Override
public void sendMessage(Long userId, SysMessageVo message) {
messageWebSocketServer.sendMessage(userId, String.valueOf(message));
}
@Override
public void broadcastMessage(SysMessageVo message) {
messageWebSocketServer.broadcastMessage(String.valueOf(message));
}
}

View File

@ -0,0 +1,182 @@
package org.dromara.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysMessage;
import org.dromara.system.domain.SysMessageUser;
import org.dromara.system.domain.bo.SysMessageBo;
import org.dromara.system.domain.event.MessageEvent;
import org.dromara.system.domain.vo.SysMessageVo;
import org.dromara.system.mapper.SysMessageMapper;
import org.dromara.system.mapper.SysMessageUserMapper;
import org.dromara.system.service.ISysMessageService;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 消息Service业务层处理
*
* @author ruoyi
*/
@Service
@RequiredArgsConstructor
public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMessage> implements ISysMessageService {
private final SysMessageMapper messageMapper;
private final SysMessageUserMapper messageUserMapper;
private final ApplicationEventPublisher eventPublisher;
@Override
public TableDataInfo<SysMessageVo> selectPageMessageList(SysMessageBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
lqw.like(StringUtils.isNotBlank(bo.getTitle()), SysMessage::getTitle, bo.getTitle())
.eq(StringUtils.isNotBlank(bo.getMsgType()), SysMessage::getMsgType, bo.getMsgType())
// .eq(StringUtils.isNotBlank(bo.getStatus()), SysMessage::getStatus, bo.getStatus())
.orderByDesc(SysMessage::getCreateTime);
Page<SysMessage> page = messageMapper.selectPage(pageQuery.build(), lqw);
List<SysMessageVo> records = MapstructUtils.convert(page.getRecords(), SysMessageVo.class);
Page<SysMessageVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
voPage.setRecords(records);
return TableDataInfo.build(voPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int sendMessageToUser(SysMessageBo message, Long userId) {
// 保存消息
SysMessage entity = message.toEntity();
messageMapper.insert(entity);
// 创建消息用户关联
SysMessageUser messageUser = new SysMessageUser();
messageUser.setMessageId(entity.getId());
messageUser.setUserId(userId);
messageUser.setIsRead(false);
int rows = messageUserMapper.insert(messageUser);
// 发送WebSocket消息
if (rows > 0) {
SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class);
eventPublisher.publishEvent(new MessageEvent(this, messageVo, userId));
}
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int sendMessageToUsers(SysMessageBo message, List<Long> userIds) {
// 保存消息
SysMessage entity = message.toEntity();
messageMapper.insert(entity);
// 批量创建消息用户关联
int count = 0;
for (Long userId : userIds) {
SysMessageUser messageUser = new SysMessageUser();
messageUser.setMessageId(entity.getId());
messageUser.setUserId(userId);
messageUser.setIsRead(false);
int rows = messageUserMapper.insert(messageUser);
if (rows > 0) {
count++;
// 发送WebSocket消息
SysMessageVo messageVo = MapstructUtils.convert(entity, SysMessageVo.class);
eventPublisher.publishEvent(new MessageEvent(this, messageVo, userId));
}
}
return count;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int sendAutoMessage(SysMessageBo message, List<Long> userIds) {
message.setMsgType("AUTO");
return sendMessageToUsers(message, userIds);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int markAsRead(Long messageId, Long userId) {
LambdaQueryWrapper<SysMessageUser> lqw = new LambdaQueryWrapper<>();
lqw.eq(SysMessageUser::getMessageId, messageId)
.eq(SysMessageUser::getUserId, userId);
SysMessageUser messageUser = new SysMessageUser();
messageUser.setIsRead(true);
return messageUserMapper.update(messageUser, lqw);
}
@Override
public TableDataInfo<SysMessageVo> selectPageUnreadMessages(Long userId, PageQuery pageQuery) {
Page<SysMessageVo> page = messageMapper.selectPageUnreadMessages(pageQuery.build(), userId);
return TableDataInfo.build(page);
}
@Override
public TableDataInfo<SysMessageVo> selectPageReadMessages(Long userId, PageQuery pageQuery) {
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
// lqw.eq(SysMessage::getStatus, "0")
// .inSql(SysMessage::getId,
// "SELECT message_id FROM sys_message_user WHERE user_id = " + userId + " AND is_read = 1")
// .orderByDesc(SysMessage::getCreateTime);
//
Page<SysMessage> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<SysMessage> resultPage = messageMapper.selectPage(page, lqw);
List<SysMessageVo> records = MapstructUtils.convert(resultPage.getRecords(), SysMessageVo.class);
Page<SysMessageVo> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
voPage.setRecords(records);
return TableDataInfo.build(voPage);
}
@Override
public List<SysMessageVo> selectMessageList(SysMessageBo message) {
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(message.getMsgType()), SysMessage::getMsgType, message.getMsgType())
.eq(StringUtils.isNotBlank(message.getSubType()), SysMessage::getSubType, message.getSubType())
.eq(message.getSenderId() != null, SysMessage::getSenderId, message.getSenderId())
// .eq(StringUtils.isNotBlank(message.getStatus()), SysMessage::getStatus, message.getStatus())
.orderByDesc(SysMessage::getCreateTime);
return MapstructUtils.convert(messageMapper.selectList(lqw), SysMessageVo.class);
}
@Override
public SysMessageVo selectMessageById(Long id) {
return MapstructUtils.convert(messageMapper.selectById(id), SysMessageVo.class);
}
@Override
public int insertMessage(SysMessageBo message) {
return messageMapper.insert(message.toEntity());
}
@Override
public int updateMessage(SysMessageBo message) {
return messageMapper.updateById(message.toEntity());
}
@Override
public int deleteMessageByIds(Long[] ids) {
return messageMapper.deleteBatchIds(List.of(ids));
}
@Override
public int deleteMessageById(Long id) {
return messageMapper.deleteById(id);
}
@Override
public LambdaQueryWrapper<SysMessage> toWrapper(SysMessageBo bo) {
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
lqw.like(StringUtils.isNotBlank(bo.getTitle()), SysMessage::getTitle, bo.getTitle())
.eq(StringUtils.isNotBlank(bo.getMsgType()), SysMessage::getMsgType, bo.getMsgType())
.orderByDesc(SysMessage::getCreateTime);
return lqw;
}
}

View File

@ -0,0 +1,83 @@
package org.dromara.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.SysMessageTemplate;
import org.dromara.system.domain.bo.SysMessageTemplateBo;
import org.dromara.system.domain.vo.SysMessageTemplateVo;
import org.dromara.system.mapper.SysMessageTemplateMapper;
import org.dromara.system.service.ISysMessageTemplateService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 消息模板Service业务层处理
*
* @author ruoyi
*/
@RequiredArgsConstructor
@Service
public class SysMessageTemplateServiceImpl extends ServiceImpl<SysMessageTemplateMapper, SysMessageTemplate> implements ISysMessageTemplateService {
private final SysMessageTemplateMapper templateMapper;
@Override
public TableDataInfo<SysMessageTemplateVo> selectTemplatePage(SysMessageTemplateBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysMessageTemplate> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(bo.getTemplateType()), SysMessageTemplate::getTemplateType, bo.getTemplateType())
.eq(StringUtils.isNotBlank(bo.getStatus()), SysMessageTemplate::getStatus, bo.getStatus())
.like(StringUtils.isNotBlank(bo.getTemplateName()), SysMessageTemplate::getTemplateName, bo.getTemplateName())
.like(StringUtils.isNotBlank(bo.getTemplateCode()), SysMessageTemplate::getTemplateCode, bo.getTemplateCode())
.orderByDesc(SysMessageTemplate::getCreateTime);
Page<SysMessageTemplate> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
Page<SysMessageTemplate> resultPage = templateMapper.selectPage(page, lqw);
List<SysMessageTemplateVo> records = MapstructUtils.convert(resultPage.getRecords(), SysMessageTemplateVo.class);
Page<SysMessageTemplateVo> voPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
voPage.setRecords(records);
return TableDataInfo.build(voPage);
}
@Override
public List<SysMessageTemplateVo> selectTemplateList(SysMessageTemplateBo bo) {
LambdaQueryWrapper<SysMessageTemplate> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(bo.getTemplateType()), SysMessageTemplate::getTemplateType, bo.getTemplateType())
.eq(StringUtils.isNotBlank(bo.getStatus()), SysMessageTemplate::getStatus, bo.getStatus())
.like(StringUtils.isNotBlank(bo.getTemplateName()), SysMessageTemplate::getTemplateName, bo.getTemplateName())
.like(StringUtils.isNotBlank(bo.getTemplateCode()), SysMessageTemplate::getTemplateCode, bo.getTemplateCode())
.orderByDesc(SysMessageTemplate::getCreateTime);
return MapstructUtils.convert(templateMapper.selectList(lqw), SysMessageTemplateVo.class);
}
@Override
public SysMessageTemplateVo selectTemplateById(Long id) {
return MapstructUtils.convert(templateMapper.selectById(id), SysMessageTemplateVo.class);
}
@Override
public int insertTemplate(SysMessageTemplateBo bo) {
return templateMapper.insert(bo.toEntity());
}
@Override
public int updateTemplate(SysMessageTemplateBo bo) {
return templateMapper.updateById(bo.toEntity());
}
@Override
public int deleteTemplateByIds(Long[] ids) {
return templateMapper.deleteBatchIds(List.of(ids));
}
@Override
public int deleteTemplateById(Long id) {
return templateMapper.deleteById(id);
}
}

View File

@ -179,7 +179,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
for (SysDictType dictType : dictTypeList) { for (SysDictType dictType : dictTypeList) {
dictType.setDictId(null); dictType.setDictId(null);
dictType.setTenantId(tenantId); dictType.setTenantId(tenantId);
dictType.setCreateDept(null); // dictType.setCreateDept(null);
dictType.setCreateBy(null); dictType.setCreateBy(null);
dictType.setCreateTime(null); dictType.setCreateTime(null);
dictType.setUpdateBy(null); dictType.setUpdateBy(null);
@ -188,7 +188,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
for (SysDictData dictData : dictDataList) { for (SysDictData dictData : dictDataList) {
dictData.setDictCode(null); dictData.setDictCode(null);
dictData.setTenantId(tenantId); dictData.setTenantId(tenantId);
dictData.setCreateDept(null); // dictData.setCreateDept(null);
dictData.setCreateBy(null); dictData.setCreateBy(null);
dictData.setCreateTime(null); dictData.setCreateTime(null);
dictData.setUpdateBy(null); dictData.setUpdateBy(null);
@ -202,7 +202,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
for (SysConfig config : sysConfigList) { for (SysConfig config : sysConfigList) {
config.setConfigId(null); config.setConfigId(null);
config.setTenantId(tenantId); config.setTenantId(tenantId);
config.setCreateDept(null); // config.setCreateDept(null);
config.setCreateBy(null); config.setCreateBy(null);
config.setCreateTime(null); config.setCreateTime(null);
config.setUpdateBy(null); config.setUpdateBy(null);
@ -448,7 +448,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
data.setTenantId(tenantId); data.setTenantId(tenantId);
data.setCreateTime(null); data.setCreateTime(null);
data.setUpdateTime(null); data.setUpdateTime(null);
data.setCreateDept(null); // data.setCreateDept(null);
data.setCreateBy(null); data.setCreateBy(null);
data.setUpdateBy(null); data.setUpdateBy(null);
set.add(tenantId); set.add(tenantId);
@ -472,7 +472,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
data.setTenantId(tenantId); data.setTenantId(tenantId);
data.setCreateTime(null); data.setCreateTime(null);
data.setUpdateTime(null); data.setUpdateTime(null);
data.setCreateDept(null); // data.setCreateDept(null);
data.setCreateBy(null); data.setCreateBy(null);
data.setUpdateBy(null); data.setUpdateBy(null);
set.add(tenantId); set.add(tenantId);

View File

@ -0,0 +1,78 @@
package org.dromara.system.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.system.domain.SysMessage;
//import org.dromara.system.domain.SysMessageUser;
import org.dromara.system.domain.SysMessageUser;
import org.dromara.system.domain.vo.SysMessageVo;
import org.dromara.system.mapper.SysMessageMapper;
//import org.dromara.system.mapper.SysMessageUserMapper
import org.dromara.system.mapper.SysMessageUserMapper;
import org.dromara.system.websocket.MessageWebSocketServer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/**
* 消息定时任务
*
* @author ruoyi
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MessageScheduledTask {
private final SysMessageMapper messageMapper;
private final SysMessageUserMapper messageUserMapper;
private final MessageWebSocketServer messageWebSocketServer;
/**
* 每分钟执行一次处理待发送的消息
*/
@Scheduled(cron = "0 */1 * * * ?")
@Transactional(rollbackFor = Exception.class)
public void processScheduledMessages() {
log.info("开始处理定时消息...");
try {
// 查询待发送的消息
LambdaQueryWrapper<SysMessage> lqw = new LambdaQueryWrapper<>();
// lqw.eq(SysMessage::getStatus, "0")
// .le(SysMessage::getScheduledTime, LocalDateTime.now())
// .isNotNull(SysMessage::getScheduledTime);
List<SysMessage> messages = messageMapper.selectList(lqw);
for (SysMessage message : messages) {
// 查询消息接收者
LambdaQueryWrapper<SysMessageUser> userLqw = new LambdaQueryWrapper<>();
userLqw.eq(SysMessageUser::getMessageId, message.getId());
List<SysMessageUser> messageUsers = messageUserMapper.selectList(userLqw);
// 发送消息
SysMessageVo messageVo = MapstructUtils.convert(message, SysMessageVo.class);
for (SysMessageUser messageUser : messageUsers) {
try {
messageWebSocketServer.sendMessage(messageUser.getUserId(), String.valueOf(messageVo));
} catch (Exception e) {
log.error("发送消息失败消息ID{}用户ID{}", message.getId(), messageUser.getUserId(), e);
}
}
// 更新消息状态为已发送
// message.setStatus("1");
messageMapper.updateById(message);
}
log.info("定时消息处理完成,共处理{}条消息", messages.size());
} catch (Exception e) {
log.error("处理定时消息时发生错误", e);
}
}
}

View File

@ -0,0 +1,116 @@
package org.dromara.system.websocket;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.websocket.holder.WebSocketSessionHolder;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 站内信 WebSocket 服务
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/message")
public class MessageWebSocketServer {
/**
* 用户ID和会话的映射关系
*/
private static final Map<Long, Session> SESSION_MAP = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
log.info("[WebSocket] 新的连接建立sessionId: {}", session.getId());
try {
// 获取当前登录用户
LoginUser loginUser = (LoginUser) session.getUserProperties().get("loginUser");
if (loginUser != null) {
SESSION_MAP.put(loginUser.getUserId(), session);
log.info("[WebSocket] 用户 {} 连接成功", loginUser.getUserId());
} else {
log.warn("[WebSocket] 未获取到用户信息,关闭连接");
session.close();
}
} catch (Exception e) {
log.error("[WebSocket] 连接建立失败", e);
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
log.info("[WebSocket] 连接关闭sessionId: {}", session.getId());
// 从映射中移除会话
SESSION_MAP.values().removeIf(s -> s.getId().equals(session.getId()));
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("[WebSocket] 收到消息: {}", message);
// 这里可以处理客户端发送的消息
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("[WebSocket] 发生错误", error);
// 从映射中移除会话
SESSION_MAP.values().removeIf(s -> s.getId().equals(session.getId()));
}
/**
* 发送消息给指定用户
*
* @param userId 用户ID
* @param message 消息内容
*/
public void sendMessage(Long userId, String message) {
Session session = SESSION_MAP.get(userId);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
log.info("[WebSocket] 消息发送成功,接收者: {}", userId);
} catch (IOException e) {
log.error("[WebSocket] 消息发送失败", e);
}
} else {
log.warn("[WebSocket] 用户 {} 不在线或会话已关闭", userId);
}
}
/**
* 广播消息给所有在线用户
*
* @param message 消息内容
*/
public void broadcastMessage(String message) {
SESSION_MAP.forEach((userId, session) -> {
if (session.isOpen()) {
try {
session.getBasicRemote().sendText(message);
log.info("[WebSocket] 广播消息发送成功,接收者: {}", userId);
} catch (IOException e) {
log.error("[WebSocket] 广播消息发送失败,接收者: {}", userId, e);
}
}
});
}
}

View File

@ -1,3 +0,0 @@
java包使用 `.` 分割 resource 目录使用 `/` 分割
<br>
此文件目的 防止文件夹粘连找不到 `xml` 文件

View File

@ -0,0 +1,32 @@
<?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">
<resultMap type="org.dromara.system.domain.vo.SysMessageVo" id="SysMessageResult">
<id property="id" column="id" />
<result property="title" column="title" />
<result property="content" column="content" />
<result property="msgType" column="msg_type" />
<result property="subType" column="sub_type" />
<result property="senderId" column="sender_id" />
<result property="scheduledTime" column="scheduled_time"/>
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<select id="selectPageUnreadMessages" resultMap="SysMessageResult">
SELECT m.*
FROM sys_message m
INNER JOIN sys_message_user mu ON m.id = mu.message_id
WHERE mu.user_id = #{userId}
AND mu.is_read = 0
AND m.status = '0'
ORDER BY m.create_time DESC
</select>
</mapper>

View File

@ -0,0 +1,33 @@
<?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.SysMessageUserMapper">
<resultMap type="org.dromara.system.domain.vo.SysMessageUserVo" id="SysMessageUserResult">
<id property="id" column="id" />
<result property="messageId" column="message_id" />
<result property="userId" column="user_id" />
<result property="isRead" column="is_read" />
<result property="readTime" column="read_time" />
<!-- <result property="createBy" column="create_by" />-->
<!-- <result property="createTime" column="create_time" />-->
<!-- <result property="updateBy" column="update_by" />-->
<!-- <result property="updateTime" column="update_time" />-->
<!-- <result property="tenantId" column="tenant_id" />-->
</resultMap>
<sql id="selectSysMessageUserVo">
select id, message_id, user_id, is_read, read_time, create_time, update_time
from sys_message_user
</sql>
<update id="updateReadStatus">
update sys_message_user
set is_read = #{isRead},
read_time = sysdate()
where message_id = #{messageId}
and user_id = #{userId}
</update>
</mapper>

View File

@ -69,5 +69,19 @@
select count(*) from sys_user where del_flag = '0' and user_id = #{userId} select count(*) from sys_user where del_flag = '0' and user_id = #{userId}
</select> </select>
<select id="selectUserIdsByType" resultType="java.lang.Long">
select user_id
from sys_user
where user_type = #{userType}
and del_flag = '0'
and status = '0'
</select>
<select id="selectAllUserIds" resultType="java.lang.Long">
select user_id
from sys_user
where del_flag = '0'
and status = '0'
</select>
</mapper> </mapper>

View File

@ -220,7 +220,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
.eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID)); .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID));
flowCategory.setCategoryId(null); flowCategory.setCategoryId(null);
flowCategory.setTenantId(tenantId); flowCategory.setTenantId(tenantId);
flowCategory.setCreateDept(null); // flowCategory.setCreateDept(null);
flowCategory.setCreateBy(null); flowCategory.setCreateBy(null);
flowCategory.setCreateTime(null); flowCategory.setCreateTime(null);
flowCategory.setUpdateBy(null); flowCategory.setUpdateBy(null);