消息模块、爬虫
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package org.xyzh.message.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 定时任务和异步任务配置类
|
||||
*
|
||||
* @description 配置消息模块的定时任务线程池和异步任务线程池
|
||||
* @filename SchedulingConfig.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class SchedulingConfig implements SchedulingConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(messageSchedulerExecutor());
|
||||
}
|
||||
|
||||
@Bean(name = "messageSchedulerExecutor")
|
||||
public TaskScheduler messageSchedulerExecutor() {
|
||||
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
|
||||
// 线程池大小:5个线程用于定时任务扫描
|
||||
executor.setPoolSize(5);
|
||||
// 线程名称前缀
|
||||
executor.setThreadNamePrefix("message-scheduler-");
|
||||
// 等待所有任务完成后再关闭
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 等待时间(秒)
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步任务线程池配置
|
||||
*/
|
||||
@Bean(name = "messageAsyncExecutor")
|
||||
public Executor messageAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
// 核心线程数:10个线程用于异步发送消息
|
||||
executor.setCorePoolSize(10);
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(20);
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(500);
|
||||
// 线程名称前缀
|
||||
executor.setThreadNamePrefix("message-async-");
|
||||
// 等待所有任务完成后再关闭
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
// 等待时间(秒)
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package org.xyzh.message.controller;
|
||||
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.xyzh.api.message.MessageService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.dto.message.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 消息控制器
|
||||
*
|
||||
* @description 消息通知模块的REST API接口
|
||||
* @filename MessageController.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/message")
|
||||
public class MessageController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageController.class);
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
// ================== 消息管理接口(管理端) ==================
|
||||
|
||||
/**
|
||||
* 创建消息
|
||||
*
|
||||
* @param message 消息对象
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PostMapping
|
||||
public ResultDomain<TbSysMessage> createMessage(@RequestBody TbSysMessage message) {
|
||||
logger.info("创建消息:{}", message.getTitle());
|
||||
return messageService.createMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息(仅草稿状态)
|
||||
*
|
||||
* @param message 消息对象
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PutMapping
|
||||
public ResultDomain<TbSysMessage> updateMessage(@RequestBody TbSysMessage message) {
|
||||
logger.info("更新消息:{}", message.getMessageID());
|
||||
return messageService.updateMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@DeleteMapping
|
||||
public ResultDomain<TbSysMessage> deleteMessage(@RequestBody TbSysMessage message) {
|
||||
return messageService.deleteMessage(message.getMessageID());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询消息详情
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<MessageVO>
|
||||
*/
|
||||
@GetMapping("/detail/{messageID}")
|
||||
public ResultDomain<MessageVO> getMessageById(@PathVariable String messageID) {
|
||||
return messageService.getMessageById(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询消息列表(管理端)
|
||||
*
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @return ResultDomain<MessageVO>
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
public ResultDomain<MessageVO> getMessagePage(@RequestBody TbSysMessage filter, PageParam pageParam) {
|
||||
return messageService.getMessagePage(filter, pageParam);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ================== 消息发送接口 ==================
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PostMapping("/send/{messageID}")
|
||||
public ResultDomain<TbSysMessage> sendMessage(@PathVariable String messageID) {
|
||||
logger.info("发送消息:{}", messageID);
|
||||
return messageService.sendMessage(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即发送(定时消息改为立即发送)
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PostMapping("/sendNow/{messageID}")
|
||||
public ResultDomain<TbSysMessage> sendNow(@PathVariable String messageID) {
|
||||
logger.info("立即发送消息:{}", messageID);
|
||||
return messageService.sendNow(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消定时消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PostMapping("/cancel/{messageID}")
|
||||
public ResultDomain<TbSysMessage> cancelMessage(@PathVariable String messageID) {
|
||||
logger.info("取消消息:{}", messageID);
|
||||
return messageService.cancelMessage(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改定时发送时间
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @param request 包含scheduledTime的请求对象
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PutMapping("/reschedule/{messageID}")
|
||||
public ResultDomain<TbSysMessage> rescheduleMessage(@PathVariable String messageID,
|
||||
@RequestBody Map<String, Object> request) {
|
||||
logger.info("修改消息发送时间:{}", messageID);
|
||||
Date scheduledTime = (Date) request.get("scheduledTime");
|
||||
return messageService.rescheduleMessage(messageID, scheduledTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试失败消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessage>
|
||||
*/
|
||||
@PostMapping("/retry/{messageID}")
|
||||
public ResultDomain<TbSysMessage> retryMessage(@PathVariable String messageID) {
|
||||
logger.info("重试消息:{}", messageID);
|
||||
return messageService.retryMessage(messageID);
|
||||
}
|
||||
|
||||
// ================== 用户消息接口(用户端) ==================
|
||||
|
||||
/**
|
||||
* 分页查询我的消息列表
|
||||
*
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @return ResultDomain<MessageUserVO>
|
||||
*/
|
||||
@PostMapping("/my/page")
|
||||
public ResultDomain<MessageUserVO> getMyMessagesPage(@RequestBody PageRequest<MessageUserVO> pageRequest) {
|
||||
return messageService.getMyMessagesPage(pageRequest.getFilter(), pageRequest.getPageParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我的消息详情
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<MessageUserVO>
|
||||
*/
|
||||
@GetMapping("/my/detail/{messageID}")
|
||||
public ResultDomain<MessageUserVO> getMyMessageDetail(@PathVariable String messageID) {
|
||||
return messageService.getMyMessageDetail(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记消息为已读
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return ResultDomain<TbSysMessageUser>
|
||||
*/
|
||||
@PostMapping("/my/markRead/{messageID}")
|
||||
public ResultDomain<TbSysMessageUser> markAsRead(@PathVariable String messageID) {
|
||||
return messageService.markAsRead(messageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量标记消息为已读
|
||||
*
|
||||
* @param request 包含messageIDs的请求对象
|
||||
* @return ResultDomain<Integer>
|
||||
*/
|
||||
@PostMapping("/my/batchMarkRead")
|
||||
public ResultDomain<Integer> batchMarkAsRead(@RequestBody Map<String, List<String>> request) {
|
||||
List<String> messageIDs = request.get("messageIDs");
|
||||
return messageService.batchMarkAsRead(messageIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读消息数量
|
||||
*
|
||||
* @return ResultDomain<Integer>
|
||||
*/
|
||||
@GetMapping("/my/unreadCount")
|
||||
public ResultDomain<Integer> getUnreadCount() {
|
||||
return messageService.getUnreadCount();
|
||||
}
|
||||
|
||||
// ================== 辅助接口 ==================
|
||||
|
||||
/**
|
||||
* 获取可选的部门树
|
||||
*
|
||||
* @return ResultDomain<Map>
|
||||
*/
|
||||
@GetMapping("/targets/depts")
|
||||
public ResultDomain<Map<String, Object>> getTargetDepts() {
|
||||
return messageService.getTargetDepts();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可选的角色列表
|
||||
*
|
||||
* @return ResultDomain<Map>
|
||||
*/
|
||||
@GetMapping("/targets/roles")
|
||||
public ResultDomain<Map<String, Object>> getTargetRoles() {
|
||||
return messageService.getTargetRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可选的用户列表
|
||||
*
|
||||
* @return ResultDomain<Map>
|
||||
*/
|
||||
@GetMapping("/targets/users")
|
||||
public ResultDomain<Map<String, Object>> getTargetUsers() {
|
||||
return messageService.getTargetUsers();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.xyzh.message.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.message.TbSysMessage;
|
||||
import org.xyzh.common.dto.message.MessageVO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息Mapper接口
|
||||
*
|
||||
* @description 消息主体表的数据访问接口
|
||||
* @filename MessageMapper.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Mapper
|
||||
public interface MessageMapper extends BaseMapper<TbSysMessage> {
|
||||
|
||||
/**
|
||||
* 根据消息ID查询消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return TbSysMessage 消息信息
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
TbSysMessage selectMessageById(@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 插入消息
|
||||
*
|
||||
* @param message 消息信息
|
||||
* @return int 影响行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int insertMessage(@Param("message") TbSysMessage message);
|
||||
|
||||
/**
|
||||
* 更新消息
|
||||
*
|
||||
* @param message 消息信息
|
||||
* @return int 影响行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int updateMessage(@Param("message") TbSysMessage message);
|
||||
|
||||
/**
|
||||
* 删除消息(逻辑删除)
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return int 影响行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int deleteMessage(@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 统计消息总数(带权限过滤)
|
||||
*
|
||||
* @param filter 过滤条件
|
||||
* @param currentUserDeptID 当前用户部门ID
|
||||
* @return int 总数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int countMessage(@Param("filter") TbSysMessage filter,
|
||||
@Param("currentUserDeptID") String currentUserDeptID);
|
||||
|
||||
/**
|
||||
* 分页查询消息列表(带权限过滤)
|
||||
*
|
||||
* @param filter 过滤条件
|
||||
* @param currentUserDeptID 当前用户部门ID
|
||||
* @return List<MessageVO> 消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<MessageVO> selectMessagePage(@Param("filter") TbSysMessage filter,
|
||||
@Param("currentUserDeptID") String currentUserDeptID);
|
||||
|
||||
/**
|
||||
* 查询消息详情
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return MessageVO 消息详情
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
MessageVO selectMessageDetail(@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 查询待发送的定时消息
|
||||
*
|
||||
* @param currentTime 当前时间
|
||||
* @return List<TbSysMessage> 待发送的消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<TbSysMessage> selectPendingScheduledMessages(@Param("currentTime") LocalDateTime currentTime);
|
||||
|
||||
/**
|
||||
* CAS更新消息状态(防止并发)
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @param expectedStatus 期望的当前状态
|
||||
* @param newStatus 新状态
|
||||
* @return int 更新的行数(1表示成功,0表示失败)
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int compareAndSetStatus(@Param("messageID") String messageID,
|
||||
@Param("expectedStatus") String expectedStatus,
|
||||
@Param("newStatus") String newStatus);
|
||||
|
||||
/**
|
||||
* 更新消息统计信息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @param sentCount 已发送数量
|
||||
* @param successCount 成功数量
|
||||
* @param failedCount 失败数量
|
||||
* @return int 更新的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int updateStatistics(@Param("messageID") String messageID,
|
||||
@Param("sentCount") Integer sentCount,
|
||||
@Param("successCount") Integer successCount,
|
||||
@Param("failedCount") Integer failedCount);
|
||||
|
||||
/**
|
||||
* 更新已读数量
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return int 更新的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int incrementReadCount(@Param("messageID") String messageID);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.xyzh.message.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.message.TbSysMessageTarget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息接收对象Mapper接口
|
||||
*
|
||||
* @description 消息接收对象表的数据访问接口
|
||||
* @filename MessageTargetMapper.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Mapper
|
||||
public interface MessageTargetMapper extends BaseMapper<TbSysMessageTarget> {
|
||||
|
||||
/**
|
||||
* 根据消息ID查询接收对象列表
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return List<TbSysMessageTarget> 接收对象列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<TbSysMessageTarget> selectByMessageID(@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 批量插入接收对象
|
||||
*
|
||||
* @param targets 接收对象列表
|
||||
* @return int 插入的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int batchInsert(@Param("targets") List<TbSysMessageTarget> targets);
|
||||
|
||||
/**
|
||||
* 根据消息ID删除接收对象
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return int 删除的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int deleteByMessageID(@Param("messageID") String messageID);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.xyzh.message.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.common.dto.message.TbSysMessageUser;
|
||||
import org.xyzh.common.dto.message.MessageUserVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户消息Mapper接口
|
||||
*
|
||||
* @description 用户接收消息表的数据访问接口
|
||||
* @filename MessageUserMapper.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Mapper
|
||||
public interface MessageUserMapper extends BaseMapper<TbSysMessageUser> {
|
||||
|
||||
/**
|
||||
* 根据消息ID查询用户消息列表
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
* @return List<MessageUserVO> 用户消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<MessageUserVO> selectByMessageID(@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 批量插入用户消息
|
||||
*
|
||||
* @param userMessages 用户消息列表
|
||||
* @return int 插入的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int batchInsert(@Param("userMessages") List<TbSysMessageUser> userMessages);
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的消息列表
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param filter 过滤条件
|
||||
* @return List<MessageUserVO> 消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<MessageUserVO> selectMyMessages(@Param("userID") String userID,
|
||||
@Param("filter") TbSysMessageUser filter);
|
||||
|
||||
/**
|
||||
* 查询当前用户的消息详情
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param messageID 消息ID
|
||||
* @return MessageUserVO 消息详情
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
MessageUserVO selectMyMessageDetail(@Param("userID") String userID,
|
||||
@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 标记消息为已读
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param messageID 消息ID
|
||||
* @return int 更新的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int markAsRead(@Param("userID") String userID,
|
||||
@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 批量标记消息为已读
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param messageIDs 消息ID列表
|
||||
* @return int 更新的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int batchMarkAsRead(@Param("userID") String userID,
|
||||
@Param("messageIDs") List<String> messageIDs);
|
||||
|
||||
/**
|
||||
* 查询未读消息数量
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @return Integer 未读消息数量
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
Integer countUnread(@Param("userID") String userID);
|
||||
|
||||
/**
|
||||
* 动态计算未读消息数量(基于 target 配置)
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @return Integer 未读消息数量
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
Integer countUnreadWithDynamicTargets(@Param("userID") String userID);
|
||||
|
||||
/**
|
||||
* 更新用户消息的发送状态
|
||||
*
|
||||
* @param id 用户消息ID
|
||||
* @param sendStatus 发送状态
|
||||
* @param failReason 失败原因(可选)
|
||||
* @return int 更新的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int updateSendStatus(@Param("id") String id,
|
||||
@Param("sendStatus") String sendStatus,
|
||||
@Param("failReason") String failReason);
|
||||
|
||||
/**
|
||||
* 查询待发送的用户消息列表
|
||||
*
|
||||
* @param filter 过滤条件
|
||||
* @return List<TbSysMessageUser> 用户消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<TbSysMessageUser> selectPendingUserMessages(@Param("filter") TbSysMessageUser filter);
|
||||
|
||||
/**
|
||||
* 动态查询当前用户可见的消息列表(基于 target 配置计算)
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param filter 过滤条件
|
||||
* @return List<MessageUserVO> 消息列表
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
List<MessageUserVO> selectMyMessagesWithDynamicTargets(@Param("userID") String userID,
|
||||
@Param("filter") MessageUserVO filter);
|
||||
|
||||
/**
|
||||
* 查询或创建用户消息记录(upsert)
|
||||
*
|
||||
* @param userID 用户ID
|
||||
* @param messageID 消息ID
|
||||
* @return MessageUserVO 用户消息记录
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
MessageUserVO selectOrCreateUserMessage(@Param("userID") String userID,
|
||||
@Param("messageID") String messageID);
|
||||
|
||||
/**
|
||||
* 插入用户消息记录(如果不存在)
|
||||
*
|
||||
* @param userMessage 用户消息
|
||||
* @return int 插入的行数
|
||||
* @author Claude
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
int insertIfNotExists(@Param("userMessage") TbSysMessageUser userMessage);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package org.xyzh.message.scheduler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.common.dto.message.TbSysMessage;
|
||||
import org.xyzh.message.mapper.MessageMapper;
|
||||
import org.xyzh.message.service.impl.MessageSendService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息定时任务扫描器
|
||||
*
|
||||
* @description 定时扫描待发送的定时消息并触发发送
|
||||
* @filename MessageScheduler.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Component
|
||||
public class MessageScheduler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageScheduler.class);
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageSendService messageSendService;
|
||||
|
||||
/**
|
||||
* 扫描待发送的定时消息
|
||||
* 每分钟执行一次
|
||||
*/
|
||||
@Scheduled(cron = "0 * * * * ?")
|
||||
public void scanPendingScheduledMessages() {
|
||||
try {
|
||||
logger.debug("开始扫描待发送的定时消息...");
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// 查询满足条件的消息:
|
||||
// 1. status=pending(待发送)
|
||||
// 2. sendMode=scheduled(定时发送)
|
||||
// 3. scheduledTime <= 当前时间
|
||||
// 4. deleted=0(未删除)
|
||||
List<TbSysMessage> messages = messageMapper.selectPendingScheduledMessages(now);
|
||||
|
||||
if (messages.isEmpty()) {
|
||||
logger.debug("没有待发送的定时消息");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("发现 {} 条待发送的定时消息", messages.size());
|
||||
|
||||
// 处理每条消息
|
||||
for (TbSysMessage message : messages) {
|
||||
try {
|
||||
// 使用CAS(Compare-And-Set)更新状态,防止并发重复触发
|
||||
boolean updated = messageMapper.compareAndSetStatus(
|
||||
message.getMessageID(),
|
||||
"pending", // 期望的当前状态
|
||||
"sending" // 新状态
|
||||
) > 0;
|
||||
|
||||
if (!updated) {
|
||||
logger.warn("消息 {} 状态已被修改,跳过处理", message.getMessageID());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 更新实际发送时间和状态
|
||||
message.setActualSendTime(new Date());
|
||||
message.setStatus("sending");
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
// 异步发送消息
|
||||
messageSendService.sendMessageAsync(message.getMessageID());
|
||||
|
||||
logger.info("定时消息 {} 已触发发送", message.getMessageID());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("处理定时消息失败:{}", message.getMessageID(), e);
|
||||
handleSendError(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("定时消息扫描完成,已处理 {} 条消息", messages.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("扫描定时消息时发生错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发送失败,支持重试机制
|
||||
*
|
||||
* @param message 消息对象
|
||||
* @param e 异常信息
|
||||
*/
|
||||
private void handleSendError(TbSysMessage message, Exception e) {
|
||||
try {
|
||||
int retryCount = message.getRetryCount() != null ? message.getRetryCount() : 0;
|
||||
int maxRetryCount = message.getMaxRetryCount() != null ? message.getMaxRetryCount() : 3;
|
||||
|
||||
if (retryCount < maxRetryCount) {
|
||||
// 未达到最大重试次数,增加重试计数
|
||||
message.setRetryCount(retryCount + 1);
|
||||
message.setStatus("pending"); // 重新置为待发送状态
|
||||
|
||||
// 设置下次重试时间(5分钟后)
|
||||
LocalDateTime nextRetryTime = LocalDateTime.now().plusMinutes(5);
|
||||
message.setScheduledTime(convertToDate(nextRetryTime));
|
||||
|
||||
message.setLastError(e.getMessage());
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
logger.warn("消息 {} 发送失败,将在5分钟后重试 ({}/{})",
|
||||
message.getMessageID(), retryCount + 1, maxRetryCount);
|
||||
|
||||
} else {
|
||||
// 超过最大重试次数,标记为失败
|
||||
message.setStatus("failed");
|
||||
message.setLastError("超过最大重试次数:" + e.getMessage());
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
logger.error("消息 {} 发送失败且已超过最大重试次数", message.getMessageID());
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("处理发送错误时发生异常", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将LocalDateTime转换为Date
|
||||
*/
|
||||
private Date convertToDate(LocalDateTime localDateTime) {
|
||||
return java.sql.Timestamp.valueOf(localDateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每小时清理一次已取消和已失败的旧消息(可选功能)
|
||||
* 将超过30天的已取消/失败消息标记为删除
|
||||
*/
|
||||
@Scheduled(cron = "0 0 * * * ?")
|
||||
public void cleanupOldMessages() {
|
||||
try {
|
||||
logger.debug("开始清理旧消息...");
|
||||
|
||||
// 计算30天前的时间
|
||||
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
||||
|
||||
// TODO: 实现清理逻辑
|
||||
// 清理条件:
|
||||
// 1. status in ('cancelled', 'failed')
|
||||
// 2. updateTime < 30天前
|
||||
// 3. deleted = 0
|
||||
|
||||
logger.debug("旧消息清理完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("清理旧消息时发生错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每天凌晨生成消息统计报告(可选功能)
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void generateDailyReport() {
|
||||
try {
|
||||
logger.info("开始生成每日消息统计报告...");
|
||||
|
||||
// TODO: 实现统计报告生成逻辑
|
||||
// 统计内容:
|
||||
// 1. 今日发送消息总数
|
||||
// 2. 发送成功率
|
||||
// 3. 已读率
|
||||
// 4. 各发送方式的使用情况
|
||||
|
||||
logger.info("每日消息统计报告生成完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("生成每日报告时发生错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package org.xyzh.message.service.impl;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.xyzh.common.dto.message.TbSysMessage;
|
||||
import org.xyzh.common.dto.message.TbSysMessageUser;
|
||||
import org.xyzh.common.dto.user.TbSysUser;
|
||||
import org.xyzh.common.utils.EmailUtils;
|
||||
import org.xyzh.common.utils.SmsUtils;
|
||||
import org.xyzh.message.mapper.MessageMapper;
|
||||
import org.xyzh.message.mapper.MessageUserMapper;
|
||||
import org.xyzh.system.mapper.UserMapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息发送服务
|
||||
*
|
||||
* @description 异步发送消息的服务类
|
||||
* @filename MessageSendService.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
|
||||
@Service
|
||||
public class MessageSendService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageSendService.class);
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageUserMapper messageUserMapper;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private EmailUtils emailUtils;
|
||||
|
||||
@Autowired
|
||||
private SmsUtils smsUtils;
|
||||
|
||||
/**
|
||||
* 异步发送消息
|
||||
*
|
||||
* @param messageID 消息ID
|
||||
*/
|
||||
@Async("messageAsyncExecutor")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void sendMessageAsync(String messageID) {
|
||||
try {
|
||||
logger.info("开始发送消息:{}", messageID);
|
||||
|
||||
// 1. 查询消息
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
logger.error("消息不存在:{}", messageID);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 查询所有待发送的用户消息
|
||||
TbSysMessageUser filter = new TbSysMessageUser();
|
||||
filter.setMessageID(messageID);
|
||||
filter.setSendStatus("pending");
|
||||
filter.setDeleted(false);
|
||||
List<TbSysMessageUser> userMessages = messageUserMapper.selectPendingUserMessages(filter);
|
||||
|
||||
if (userMessages.isEmpty()) {
|
||||
logger.warn("没有待发送的用户消息:{}", messageID);
|
||||
updateMessageStatus(message, "sent");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 遍历发送
|
||||
int successCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
for (TbSysMessageUser userMessage : userMessages) {
|
||||
try {
|
||||
// 查询用户信息
|
||||
TbSysUser user = userMapper.selectUserById(userMessage.getUserID());
|
||||
if (user == null || user.getDeleted()) {
|
||||
logger.warn("用户不存在:{}", userMessage.getUserID());
|
||||
updateUserMessageStatus(userMessage.getID(), "failed", "用户不存在");
|
||||
failedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据发送方式发送消息
|
||||
String[] methods = userMessage.getSendMethod().split(",");
|
||||
boolean sent = false;
|
||||
|
||||
for (String method : methods) {
|
||||
method = method.trim();
|
||||
try {
|
||||
switch (method) {
|
||||
case "system":
|
||||
// 系统消息已在数据库中,无需额外操作
|
||||
sent = true;
|
||||
break;
|
||||
|
||||
case "email":
|
||||
sent = sendEmail(user, message);
|
||||
break;
|
||||
|
||||
case "sms":
|
||||
sent = sendSms(user, message);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn("未知的发送方式:{}", method);
|
||||
}
|
||||
|
||||
if (sent) {
|
||||
break; // 任意一种方式成功即可
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送消息失败 [{}] - 用户:{}, 方式:{}", messageID, user.getUsername(), method, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户消息发送状态
|
||||
if (sent) {
|
||||
updateUserMessageStatus(userMessage.getID(), "success", null);
|
||||
successCount++;
|
||||
} else {
|
||||
updateUserMessageStatus(userMessage.getID(), "failed", "所有发送方式均失败");
|
||||
failedCount++;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("处理用户消息失败:{}", userMessage.getID(), e);
|
||||
updateUserMessageStatus(userMessage.getID(), "failed", e.getMessage());
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 更新消息统计信息
|
||||
int sentCount = successCount + failedCount;
|
||||
messageMapper.updateStatistics(messageID, sentCount, successCount, failedCount);
|
||||
|
||||
// 5. 更新消息状态
|
||||
if (failedCount == userMessages.size()) {
|
||||
// 全部失败
|
||||
updateMessageStatus(message, "failed");
|
||||
} else {
|
||||
// 至少有一个成功
|
||||
updateMessageStatus(message, "sent");
|
||||
}
|
||||
|
||||
logger.info("消息发送完成:{} - 成功:{}, 失败:{}", messageID, successCount, failedCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送消息异常:{}", messageID, e);
|
||||
// 更新消息状态为失败
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
if (message != null && !message.getDeleted()) {
|
||||
message.setStatus("failed");
|
||||
message.setLastError(e.getMessage());
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("更新消息状态失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*
|
||||
* @param user 用户
|
||||
* @param message 消息
|
||||
* @return 是否成功
|
||||
*/
|
||||
private boolean sendEmail(TbSysUser user, TbSysMessage message) {
|
||||
try {
|
||||
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
|
||||
logger.warn("用户 {} 没有邮箱地址", user.getUsername());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建HTML邮件内容
|
||||
String htmlContent = buildEmailHtml(message);
|
||||
|
||||
// 发送邮件
|
||||
boolean result = emailUtils.sendHtmlEmail(
|
||||
user.getEmail(),
|
||||
message.getTitle(),
|
||||
htmlContent
|
||||
);
|
||||
|
||||
if (result) {
|
||||
logger.info("邮件发送成功 - 用户:{}, 邮箱:{}", user.getUsername(), user.getEmail());
|
||||
} else {
|
||||
logger.warn("邮件发送失败 - 用户:{}, 邮箱:{}", user.getUsername(), user.getEmail());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送邮件异常 - 用户:{}", user.getUsername(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信
|
||||
*
|
||||
* @param user 用户
|
||||
* @param message 消息
|
||||
* @return 是否成功
|
||||
*/
|
||||
private boolean sendSms(TbSysUser user, TbSysMessage message) {
|
||||
try {
|
||||
if (user.getPhone() == null || user.getPhone().trim().isEmpty()) {
|
||||
logger.warn("用户 {} 没有手机号", user.getUsername());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 短信内容(限制长度)
|
||||
String smsContent = message.getTitle();
|
||||
if (message.getContent() != null && message.getContent().length() < 50) {
|
||||
smsContent += ":" + message.getContent();
|
||||
}
|
||||
|
||||
// 发送短信
|
||||
boolean result = smsUtils.sendSms(user.getPhone(), smsContent, "xx");
|
||||
|
||||
if (result) {
|
||||
logger.info("短信发送成功 - 用户:{}, 手机:{}", user.getUsername(), user.getPhone());
|
||||
} else {
|
||||
logger.warn("短信发送失败 - 用户:{}, 手机:{}", user.getUsername(), user.getPhone());
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送短信异常 - 用户:{}", user.getUsername(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建邮件HTML内容
|
||||
*/
|
||||
private String buildEmailHtml(TbSysMessage message) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<!DOCTYPE html>");
|
||||
html.append("<html>");
|
||||
html.append("<head>");
|
||||
html.append("<meta charset=\"UTF-8\">");
|
||||
html.append("<style>");
|
||||
html.append("body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }");
|
||||
html.append(".container { max-width: 600px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }");
|
||||
html.append(".header { background-color: #c8232c; color: white; padding: 15px; border-radius: 5px 5px 0 0; }");
|
||||
html.append(".content { padding: 20px; background-color: #f9f9f9; }");
|
||||
html.append(".footer { text-align: center; padding: 15px; font-size: 12px; color: #999; }");
|
||||
html.append(".priority-urgent { border-left: 4px solid #ff0000; }");
|
||||
html.append(".priority-important { border-left: 4px solid #ff9900; }");
|
||||
html.append(".priority-normal { border-left: 4px solid #00aa00; }");
|
||||
html.append("</style>");
|
||||
html.append("</head>");
|
||||
html.append("<body>");
|
||||
html.append("<div class=\"container priority-").append(message.getPriority()).append("\">");
|
||||
html.append("<div class=\"header\">");
|
||||
html.append("<h2>").append(message.getTitle()).append("</h2>");
|
||||
html.append("</div>");
|
||||
html.append("<div class=\"content\">");
|
||||
html.append(message.getContent());
|
||||
html.append("</div>");
|
||||
html.append("<div class=\"footer\">");
|
||||
html.append("<p>发送人:").append(message.getSenderName()).append(" (").append(message.getSenderDeptName()).append(")</p>");
|
||||
html.append("<p>发送时间:").append(message.getActualSendTime() != null ? message.getActualSendTime().toString() : "").append("</p>");
|
||||
html.append("<p>此邮件由系统自动发送,请勿回复。</p>");
|
||||
html.append("</div>");
|
||||
html.append("</div>");
|
||||
html.append("</body>");
|
||||
html.append("</html>");
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息状态
|
||||
*/
|
||||
private void updateMessageStatus(TbSysMessage message, String status) {
|
||||
try {
|
||||
message.setStatus(status);
|
||||
if ("sent".equals(status)) {
|
||||
message.setActualSendTime(new Date());
|
||||
}
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
} catch (Exception e) {
|
||||
logger.error("更新消息状态失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户消息发送状态
|
||||
*/
|
||||
private void updateUserMessageStatus(String id, String status, String failReason) {
|
||||
try {
|
||||
messageUserMapper.updateSendStatus(id, status, failReason);
|
||||
} catch (Exception e) {
|
||||
logger.error("更新用户消息状态失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,746 @@
|
||||
package org.xyzh.message.service.impl;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xyzh.api.message.MessageService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.dto.message.*;
|
||||
import org.xyzh.common.utils.IDUtils;
|
||||
import org.xyzh.message.mapper.MessageMapper;
|
||||
import org.xyzh.message.mapper.MessageTargetMapper;
|
||||
import org.xyzh.message.mapper.MessageUserMapper;
|
||||
import org.xyzh.system.mapper.DepartmentMapper;
|
||||
import org.xyzh.system.mapper.UserMapper;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 消息服务实现类
|
||||
*
|
||||
* @description 消息通知模块的服务实现
|
||||
* @filename MessageServiceImpl.java
|
||||
* @author Claude
|
||||
* @copyright xyzh
|
||||
* @since 2025-11-13
|
||||
*/
|
||||
@Service
|
||||
public class MessageServiceImpl implements MessageService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageTargetMapper messageTargetMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageUserMapper messageUserMapper;
|
||||
|
||||
@Autowired
|
||||
private DepartmentMapper departmentMapper;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageSendService messageSendService;
|
||||
|
||||
/**
|
||||
* 创建消息
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> createMessage(TbSysMessage message) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
// 1. 获取当前用户信息(从Session或SecurityContext获取)
|
||||
String currentUserID = getCurrentUserID();
|
||||
String currentDeptID = getCurrentUserDeptID();
|
||||
|
||||
// 2. 设置消息主体基本信息
|
||||
if (message.getID() == null) {
|
||||
message.setID(IDUtils.generateID());
|
||||
}
|
||||
if (message.getMessageID() == null) {
|
||||
message.setMessageID(IDUtils.generateID());
|
||||
}
|
||||
message.setSenderID(currentUserID);
|
||||
message.setSenderDeptID(currentDeptID);
|
||||
|
||||
// 设置状态
|
||||
if (message.getStatus() == null) {
|
||||
if ("immediate".equals(message.getSendMode())) {
|
||||
message.setStatus("sending");
|
||||
} else {
|
||||
message.setStatus("pending");
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getTargetUserCount() == null) {
|
||||
message.setTargetUserCount(0);
|
||||
}
|
||||
if (message.getRetryCount() == null) {
|
||||
message.setRetryCount(0);
|
||||
}
|
||||
if (message.getMaxRetryCount() == null) {
|
||||
message.setMaxRetryCount(3);
|
||||
}
|
||||
message.setCreator(currentUserID);
|
||||
message.setCreateTime(new Date());
|
||||
message.setUpdateTime(new Date());
|
||||
message.setDeleted(false);
|
||||
|
||||
// 保存消息主体
|
||||
messageMapper.insertMessage(message);
|
||||
|
||||
// 3. 保存接收对象配置
|
||||
List<TbSysMessageTarget> targets = message.getTargets();
|
||||
if (targets != null && !targets.isEmpty()) {
|
||||
for (TbSysMessageTarget target : targets) {
|
||||
// 权限校验:scopeDeptID必须是当前部门或子部门
|
||||
if (!isCurrentOrSubDept(currentDeptID, target.getScopeDeptID())) {
|
||||
rt.fail("无权向该部门发送消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
if (target.getID() == null) {
|
||||
target.setID(IDUtils.generateID());
|
||||
}
|
||||
target.setMessageID(message.getMessageID());
|
||||
target.setCreator(currentUserID);
|
||||
target.setCreateTime(new Date());
|
||||
target.setUpdateTime(new Date());
|
||||
target.setDeleted(false);
|
||||
}
|
||||
messageTargetMapper.batchInsert(targets);
|
||||
|
||||
// 4. 解析接收对象,生成用户消息列表
|
||||
List<TbSysMessageUser> userMessages = resolveTargetUsers(message.getMessageID(), targets, currentUserID);
|
||||
if (!userMessages.isEmpty()) {
|
||||
messageUserMapper.batchInsert(userMessages);
|
||||
}
|
||||
|
||||
// 5. 更新目标用户总数
|
||||
message.setTargetUserCount(userMessages.size());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
// 6. 如果是立即发送,异步发送消息
|
||||
if ("immediate".equals(message.getSendMode())) {
|
||||
messageSendService.sendMessageAsync(message.getMessageID());
|
||||
}
|
||||
}
|
||||
|
||||
rt.success("创建成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("创建消息失败", e);
|
||||
rt.fail("创建消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新消息(仅允许更新草稿状态的消息)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> updateMessage(TbSysMessage message) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
// 查询原消息
|
||||
TbSysMessage existingMessage = messageMapper.selectMessageById(message.getMessageID());
|
||||
if (existingMessage == null || existingMessage.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 只允许更新草稿状态的消息
|
||||
if (!"draft".equals(existingMessage.getStatus())) {
|
||||
rt.fail("只能更新草稿状态的消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
message.setUpdateTime(new Date());
|
||||
message.setUpdater(getCurrentUserID());
|
||||
messageMapper.updateMessage(message);
|
||||
rt.success("更新成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("更新消息失败", e);
|
||||
rt.fail("更新消息失败:"+e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息(逻辑删除)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> deleteMessage(String messageID) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
int result = messageMapper.deleteMessage(messageID);
|
||||
if (result > 0) {
|
||||
// 同时删除关联的接收对象和用户消息
|
||||
messageTargetMapper.deleteByMessageID(messageID);
|
||||
rt.success("删除成功", message);
|
||||
} else {
|
||||
rt.fail("删除失败");
|
||||
}
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("删除消息失败", e);
|
||||
rt.fail("删除消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询消息详情
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<MessageVO> getMessageById(String messageID) {
|
||||
ResultDomain<MessageVO> rt = new ResultDomain<>();
|
||||
try {
|
||||
MessageVO messageVO = messageMapper.selectMessageDetail(messageID);
|
||||
if (messageVO == null) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 查询接收对象列表
|
||||
List<TbSysMessageTarget> targets = messageTargetMapper.selectByMessageID(messageID);
|
||||
messageVO.setTargets(targets);
|
||||
|
||||
// 查询用户接收记录
|
||||
List<MessageUserVO> userMessages = messageUserMapper.selectByMessageID(messageID);
|
||||
messageVO.setUserMessages(userMessages);
|
||||
|
||||
rt.success("查询成功", messageVO);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询消息详情失败", e);
|
||||
rt.fail("查询消息详情失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询消息列表(管理端)
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<MessageVO> getMessagePage(TbSysMessage filter, PageParam pageParam) {
|
||||
ResultDomain<MessageVO> rt = new ResultDomain<>();
|
||||
try {
|
||||
if (filter == null) {
|
||||
filter = new TbSysMessage();
|
||||
}
|
||||
filter.setDeleted(false);
|
||||
|
||||
if (pageParam == null) {
|
||||
pageParam = new PageParam();
|
||||
}
|
||||
|
||||
String currentDeptID = getCurrentUserDeptID();
|
||||
|
||||
List<MessageVO> list = messageMapper.selectMessagePage(filter, currentDeptID);
|
||||
int total = messageMapper.countMessage(filter, currentDeptID);
|
||||
|
||||
PageDomain<MessageVO> pageDomain = new PageDomain<>();
|
||||
pageDomain.setDataList(list);
|
||||
pageParam.setTotalElements(total);
|
||||
pageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
|
||||
pageDomain.setPageParam(pageParam);
|
||||
|
||||
rt.success("查询成功", pageDomain);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询消息列表失败", e);
|
||||
rt.fail("查询消息列表失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> sendMessage(String messageID) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 更新状态为发送中
|
||||
message.setStatus("sending");
|
||||
message.setActualSendTime(new Date());
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
// 异步发送
|
||||
messageSendService.sendMessageAsync(messageID);
|
||||
|
||||
rt.success("发送成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("发送消息失败", e);
|
||||
rt.fail("发送消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即发送(将定时消息改为立即发送)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> sendNow(String messageID) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
if (!"pending".equals(message.getStatus())) {
|
||||
rt.fail("只能立即发送待发送状态的消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 更新为立即发送模式
|
||||
message.setSendMode("immediate");
|
||||
message.setStatus("sending");
|
||||
message.setActualSendTime(new Date());
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
// 异步发送
|
||||
messageSendService.sendMessageAsync(messageID);
|
||||
|
||||
rt.success("立即发送成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("立即发送消息失败", e);
|
||||
rt.fail("立即发送消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消定时消息
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> cancelMessage(String messageID) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
if (!"pending".equals(message.getStatus())) {
|
||||
rt.fail("只能取消待发送状态的消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
message.setStatus("cancelled");
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
rt.success("取消成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("取消消息失败", e);
|
||||
rt.fail("取消消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改定时发送时间
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> rescheduleMessage(String messageID, Date scheduledTime) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
if (!"pending".equals(message.getStatus())) {
|
||||
rt.fail("只能修改待发送状态的消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
message.setScheduledTime(scheduledTime);
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
rt.success("修改成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("修改定时时间失败", e);
|
||||
rt.fail("修改定时时间失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试失败消息
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessage> retryMessage(String messageID) {
|
||||
ResultDomain<TbSysMessage> rt = new ResultDomain<>();
|
||||
try {
|
||||
TbSysMessage message = messageMapper.selectMessageById(messageID);
|
||||
|
||||
if (message == null || message.getDeleted()) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
if (!"failed".equals(message.getStatus())) {
|
||||
rt.fail("只能重试失败状态的消息");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
message.setStatus("sending");
|
||||
message.setRetryCount(message.getRetryCount() + 1);
|
||||
message.setUpdateTime(new Date());
|
||||
messageMapper.updateMessage(message);
|
||||
|
||||
// 异步发送
|
||||
messageSendService.sendMessageAsync(messageID);
|
||||
|
||||
rt.success("重试成功", message);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("重试消息失败", e);
|
||||
rt.fail("重试消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 用户消息相关方法 ==================
|
||||
|
||||
@Override
|
||||
public ResultDomain<MessageUserVO> getMyMessagesPage(MessageUserVO filter, PageParam pageParam) {
|
||||
ResultDomain<MessageUserVO> rt = new ResultDomain<>();
|
||||
try {
|
||||
if (filter == null) {
|
||||
filter = new MessageUserVO();
|
||||
}
|
||||
|
||||
if (pageParam == null) {
|
||||
pageParam = new PageParam();
|
||||
}
|
||||
|
||||
String currentUserID = getCurrentUserID();
|
||||
|
||||
// 使用新的动态查询方法
|
||||
List<MessageUserVO> list = messageUserMapper.selectMyMessagesWithDynamicTargets(currentUserID, filter);
|
||||
|
||||
PageDomain<MessageUserVO> pageDomain = new PageDomain<>();
|
||||
pageDomain.setDataList(list);
|
||||
pageParam.setTotalElements(list.size());
|
||||
pageParam.setTotalPages((int) Math.ceil((double) list.size() / pageParam.getPageSize()));
|
||||
pageDomain.setPageParam(pageParam);
|
||||
|
||||
rt.success("查询成功", pageDomain);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询我的消息失败", e);
|
||||
rt.fail("查询我的消息失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<MessageUserVO> getMyMessageDetail(String messageID) {
|
||||
ResultDomain<MessageUserVO> rt = new ResultDomain<>();
|
||||
try {
|
||||
String currentUserID = getCurrentUserID();
|
||||
MessageUserVO messageUserVO = messageUserMapper.selectOrCreateUserMessage(currentUserID, messageID);
|
||||
|
||||
if (messageUserVO == null) {
|
||||
rt.fail("消息不存在");
|
||||
return rt;
|
||||
}
|
||||
|
||||
// 如果用户消息记录不存在(id 为 null),创建新记录
|
||||
if (messageUserVO.getId() == null) {
|
||||
logger.info("用户首次查看消息,创建用户消息记录:userID={}, messageID={}", currentUserID, messageID);
|
||||
|
||||
TbSysMessageUser userMessage = new TbSysMessageUser();
|
||||
userMessage.setID(IDUtils.generateID());
|
||||
userMessage.setMessageID(messageID);
|
||||
userMessage.setUserID(currentUserID);
|
||||
userMessage.setSendMethod("system"); // 默认发送方式
|
||||
userMessage.setIsRead(false); // 初始为未读
|
||||
userMessage.setSendStatus("success");
|
||||
userMessage.setCreator(currentUserID);
|
||||
userMessage.setCreateTime(new Date());
|
||||
userMessage.setUpdateTime(new Date());
|
||||
userMessage.setDeleted(false);
|
||||
|
||||
// 插入新记录
|
||||
messageUserMapper.insertIfNotExists(userMessage);
|
||||
|
||||
// 重新查询以获取完整信息
|
||||
messageUserVO = messageUserMapper.selectMyMessageDetail(currentUserID, messageID);
|
||||
}
|
||||
|
||||
rt.success("查询成功", messageUserVO);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询消息详情失败", e);
|
||||
rt.fail("查询消息详情失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbSysMessageUser> markAsRead(String messageID) {
|
||||
ResultDomain<TbSysMessageUser> rt = new ResultDomain<>();
|
||||
try {
|
||||
String currentUserID = getCurrentUserID();
|
||||
|
||||
// 先尝试更新已有记录
|
||||
int result = messageUserMapper.markAsRead(currentUserID, messageID);
|
||||
|
||||
// 如果没有更新任何记录,说明用户消息记录不存在,需要先插入
|
||||
if (result == 0) {
|
||||
logger.info("用户消息记录不存在,创建新记录:userID={}, messageID={}", currentUserID, messageID);
|
||||
|
||||
TbSysMessageUser userMessage = new TbSysMessageUser();
|
||||
userMessage.setID(IDUtils.generateID());
|
||||
userMessage.setMessageID(messageID);
|
||||
userMessage.setUserID(currentUserID);
|
||||
userMessage.setSendMethod("system"); // 默认发送方式
|
||||
userMessage.setIsRead(true); // 直接设置为已读
|
||||
userMessage.setSendStatus("success");
|
||||
userMessage.setCreator(currentUserID);
|
||||
userMessage.setCreateTime(new Date());
|
||||
userMessage.setUpdateTime(new Date());
|
||||
userMessage.setDeleted(false);
|
||||
|
||||
// 插入新记录
|
||||
int insertResult = messageUserMapper.insertIfNotExists(userMessage);
|
||||
|
||||
if (insertResult > 0) {
|
||||
// 插入成功后再次标记为已读(设置 read_time)
|
||||
messageUserMapper.markAsRead(currentUserID, messageID);
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result > 0) {
|
||||
// 更新消息的已读数量
|
||||
messageMapper.incrementReadCount(messageID);
|
||||
TbSysMessageUser messageUser = new TbSysMessageUser();
|
||||
rt.success("标记成功", messageUser);
|
||||
} else {
|
||||
rt.fail("标记失败");
|
||||
}
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("标记已读失败", e);
|
||||
rt.fail("标记已读失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<Integer> batchMarkAsRead(List<String> messageIDs) {
|
||||
ResultDomain<Integer> rt = new ResultDomain<>();
|
||||
try {
|
||||
String currentUserID = getCurrentUserID();
|
||||
int count = messageUserMapper.batchMarkAsRead(currentUserID, messageIDs);
|
||||
|
||||
// 更新每条消息的已读数量
|
||||
for (String messageID : messageIDs) {
|
||||
messageMapper.incrementReadCount(messageID);
|
||||
}
|
||||
|
||||
rt.success("批量标记成功", count);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("批量标记已读失败", e);
|
||||
rt.fail("批量标记已读失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Integer> getUnreadCount() {
|
||||
ResultDomain<Integer> rt = new ResultDomain<>();
|
||||
try {
|
||||
String currentUserID = getCurrentUserID();
|
||||
// 使用动态计算方法,统计用户应该看到的所有未读消息
|
||||
Integer count = messageUserMapper.countUnreadWithDynamicTargets(currentUserID);
|
||||
rt.success("查询成功", count != null ? count : 0);
|
||||
return rt;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("查询未读数量失败", e);
|
||||
rt.fail("查询未读数量失败:" + e.getMessage());
|
||||
return rt;
|
||||
}
|
||||
}
|
||||
|
||||
// ================== 辅助方法 ==================
|
||||
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getTargetDepts() {
|
||||
ResultDomain<Map<String, Object>> rt = new ResultDomain<>();
|
||||
// TODO: 实现获取可选部门树
|
||||
rt.success("查询成功", new HashMap<>());
|
||||
return rt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getTargetRoles() {
|
||||
ResultDomain<Map<String, Object>> rt = new ResultDomain<>();
|
||||
// TODO: 实现获取可选角色列表
|
||||
rt.success("查询成功", new HashMap<>());
|
||||
return rt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getTargetUsers() {
|
||||
ResultDomain<Map<String, Object>> rt = new ResultDomain<>();
|
||||
// TODO: 实现获取可选用户列表
|
||||
rt.success("查询成功", new HashMap<>());
|
||||
return rt;
|
||||
}
|
||||
|
||||
// ================== 私有辅助方法 ==================
|
||||
|
||||
/**
|
||||
* 解析接收对象,生成用户消息列表
|
||||
*/
|
||||
private List<TbSysMessageUser> resolveTargetUsers(String messageID, List<TbSysMessageTarget> targets, String creator) {
|
||||
Set<String> userIDSet = new HashSet<>();
|
||||
Map<String, String> userMethodMap = new HashMap<>();
|
||||
|
||||
for (TbSysMessageTarget target : targets) {
|
||||
List<String> userIDs = new ArrayList<>();
|
||||
|
||||
switch (target.getTargetType()) {
|
||||
case "dept":
|
||||
// 查询该部门及所有子部门的用户
|
||||
userIDs = userMapper.selectUserIdsByDeptId(target.getTargetID());
|
||||
logger.info("部门 {} 解析到 {} 个用户", target.getTargetID(), userIDs.size());
|
||||
break;
|
||||
case "role":
|
||||
// 查询scopeDeptID及子部门中该角色的用户
|
||||
String scopeDeptID = target.getScopeDeptID();
|
||||
if (scopeDeptID == null || scopeDeptID.isEmpty()) {
|
||||
logger.warn("角色目标缺少 scopeDeptID,跳过:{}", target.getTargetID());
|
||||
break;
|
||||
}
|
||||
userIDs = userMapper.selectUserIdsByDeptRole(scopeDeptID, target.getTargetID());
|
||||
logger.info("部门 {} 中角色 {} 解析到 {} 个用户", scopeDeptID, target.getTargetID(), userIDs.size());
|
||||
break;
|
||||
case "user":
|
||||
userIDs.add(target.getTargetID());
|
||||
break;
|
||||
}
|
||||
|
||||
for (String userID : userIDs) {
|
||||
userIDSet.add(userID);
|
||||
userMethodMap.put(userID, target.getSendMethod());
|
||||
}
|
||||
}
|
||||
|
||||
// 生成用户消息列表
|
||||
List<TbSysMessageUser> userMessages = new ArrayList<>();
|
||||
for (String userID : userIDSet) {
|
||||
TbSysMessageUser userMessage = new TbSysMessageUser();
|
||||
userMessage.setID(IDUtils.generateID());
|
||||
userMessage.setMessageID(messageID);
|
||||
userMessage.setUserID(userID);
|
||||
userMessage.setSendMethod(userMethodMap.get(userID));
|
||||
userMessage.setIsRead(false);
|
||||
userMessage.setSendStatus("pending");
|
||||
userMessage.setCreator(creator);
|
||||
userMessage.setCreateTime(new Date());
|
||||
userMessage.setUpdateTime(new Date());
|
||||
userMessage.setDeleted(false);
|
||||
userMessages.add(userMessage);
|
||||
}
|
||||
|
||||
logger.info("消息 {} 共解析到 {} 个目标用户", messageID, userMessages.size());
|
||||
return userMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目标部门是否是当前部门或子部门
|
||||
*/
|
||||
private boolean isCurrentOrSubDept(String currentDeptID, String targetDeptID) {
|
||||
// TODO: 实现部门层级检查
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
private String getCurrentUserID() {
|
||||
// TODO: 从SecurityContext或Session获取
|
||||
return "1";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户部门ID
|
||||
*/
|
||||
private String getCurrentUserDeptID() {
|
||||
// TODO: 从SecurityContext或Session获取
|
||||
return "root_department";
|
||||
}
|
||||
}
|
||||
44
schoolNewsServ/message/src/main/resources/application.yml
Normal file
44
schoolNewsServ/message/src/main/resources/application.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Message模块配置文件
|
||||
# 此配置文件定义message模块独立运行时的配置
|
||||
# 如需作为微服务集成,请参考admin模块的bootstrap.yml配置
|
||||
|
||||
# 消息模块配置
|
||||
message:
|
||||
# 定时任务扫描配置
|
||||
scheduler:
|
||||
# 扫描频率(cron表达式),默认每分钟扫描一次
|
||||
cron: "0 * * * * ?"
|
||||
# 是否启用定时任务扫描
|
||||
enabled: true
|
||||
|
||||
# 消息发送配置
|
||||
send:
|
||||
# 异步发送线程池大小
|
||||
thread-pool-size: 10
|
||||
# 批量发送每批次大小
|
||||
batch-size: 100
|
||||
|
||||
# Spring Boot配置(如果需要独立运行)
|
||||
# server:
|
||||
# port: 8087
|
||||
#
|
||||
# spring:
|
||||
# application:
|
||||
# name: message-service
|
||||
#
|
||||
# datasource:
|
||||
# url: jdbc:mysql://localhost:3306/school_news?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
# username: root
|
||||
# password: your_password
|
||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# hikari:
|
||||
# maximum-pool-size: 20
|
||||
# minimum-idle: 5
|
||||
# connection-timeout: 30000
|
||||
#
|
||||
# mybatis-plus:
|
||||
# mapper-locations: classpath:mapper/*.xml
|
||||
# type-aliases-package: org.xyzh.common.dto.message
|
||||
# configuration:
|
||||
# map-underscore-to-camel-case: true
|
||||
# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||
@@ -0,0 +1,279 @@
|
||||
<?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.xyzh.message.mapper.MessageMapper">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.message.TbSysMessage">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageID" jdbcType="VARCHAR"/>
|
||||
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||
<result column="content" property="content" jdbcType="VARCHAR"/>
|
||||
<result column="message_type" property="messageType" jdbcType="VARCHAR"/>
|
||||
<result column="priority" property="priority" jdbcType="VARCHAR"/>
|
||||
<result column="sender_id" property="senderID" jdbcType="VARCHAR"/>
|
||||
<result column="sender_name" property="senderName" jdbcType="VARCHAR"/>
|
||||
<result column="sender_dept_id" property="senderDeptID" jdbcType="VARCHAR"/>
|
||||
<result column="sender_dept_name" property="senderDeptName" jdbcType="VARCHAR"/>
|
||||
<result column="send_mode" property="sendMode" jdbcType="VARCHAR"/>
|
||||
<result column="scheduled_time" property="scheduledTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="actual_send_time" property="actualSendTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="status" property="status" jdbcType="VARCHAR"/>
|
||||
<result column="target_user_count" property="targetUserCount" jdbcType="INTEGER"/>
|
||||
<result column="sent_count" property="sentCount" jdbcType="INTEGER"/>
|
||||
<result column="success_count" property="successCount" jdbcType="INTEGER"/>
|
||||
<result column="failed_count" property="failedCount" jdbcType="INTEGER"/>
|
||||
<result column="read_count" property="readCount" jdbcType="INTEGER"/>
|
||||
<result column="retry_count" property="retryCount" jdbcType="INTEGER"/>
|
||||
<result column="max_retry_count" property="maxRetryCount" jdbcType="INTEGER"/>
|
||||
<result column="last_error" property="lastError" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="MessageVOMap" type="org.xyzh.common.dto.message.MessageVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageID" jdbcType="VARCHAR"/>
|
||||
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||
<result column="content" property="content" jdbcType="VARCHAR"/>
|
||||
<result column="message_type" property="messageType" jdbcType="VARCHAR"/>
|
||||
<result column="priority" property="priority" jdbcType="VARCHAR"/>
|
||||
<result column="sender_id" property="senderID" jdbcType="VARCHAR"/>
|
||||
<result column="sender_name" property="senderName" jdbcType="VARCHAR"/>
|
||||
<result column="sender_dept_id" property="senderDeptID" jdbcType="VARCHAR"/>
|
||||
<result column="sender_dept_name" property="senderDeptName" jdbcType="VARCHAR"/>
|
||||
<result column="send_mode" property="sendMode" jdbcType="VARCHAR"/>
|
||||
<result column="scheduled_time" property="scheduledTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="actual_send_time" property="actualSendTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="status" property="status" jdbcType="VARCHAR"/>
|
||||
<result column="target_user_count" property="targetUserCount" jdbcType="INTEGER"/>
|
||||
<result column="sent_count" property="sentCount" jdbcType="INTEGER"/>
|
||||
<result column="success_count" property="successCount" jdbcType="INTEGER"/>
|
||||
<result column="failed_count" property="failedCount" jdbcType="INTEGER"/>
|
||||
<result column="read_count" property="readCount" jdbcType="INTEGER"/>
|
||||
<result column="retry_count" property="retryCount" jdbcType="INTEGER"/>
|
||||
<result column="max_retry_count" property="maxRetryCount" jdbcType="INTEGER"/>
|
||||
<result column="last_error" property="lastError" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 分页查询消息列表(带权限过滤) -->
|
||||
<select id="selectMessagePage" resultMap="MessageVOMap">
|
||||
SELECT
|
||||
m.*
|
||||
FROM tb_sys_message m
|
||||
WHERE m.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND m.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.messageType != null and filter.messageType != ''">
|
||||
AND m.message_type = #{filter.messageType}
|
||||
</if>
|
||||
<if test="filter.status != null and filter.status != ''">
|
||||
AND m.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.sendMode != null and filter.sendMode != ''">
|
||||
AND m.send_mode = #{filter.sendMode}
|
||||
</if>
|
||||
<if test="filter.priority != null and filter.priority != ''">
|
||||
AND m.priority = #{filter.priority}
|
||||
</if>
|
||||
</if>
|
||||
<!-- 权限过滤:只能查看自己部门及子部门的消息 -->
|
||||
AND (
|
||||
m.sender_dept_id = #{currentUserDeptID}
|
||||
OR m.sender_dept_id IN (
|
||||
WITH RECURSIVE dept_tree AS (
|
||||
SELECT dept_id FROM tb_sys_dept
|
||||
WHERE dept_id = #{currentUserDeptID} AND deleted = 0
|
||||
UNION ALL
|
||||
SELECT d.dept_id FROM tb_sys_dept d
|
||||
INNER JOIN dept_tree dt ON d.parent_id = dt.dept_id
|
||||
WHERE d.deleted = 0
|
||||
)
|
||||
SELECT dept_id FROM dept_tree
|
||||
)
|
||||
)
|
||||
ORDER BY m.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询消息详情 -->
|
||||
<select id="selectMessageDetail" resultMap="MessageVOMap">
|
||||
SELECT *
|
||||
FROM tb_sys_message
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 查询待发送的定时消息 -->
|
||||
<select id="selectPendingScheduledMessages" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM tb_sys_message
|
||||
WHERE status = 'pending'
|
||||
AND send_mode = 'scheduled'
|
||||
AND scheduled_time <![CDATA[ <= ]]> #{currentTime}
|
||||
AND deleted = 0
|
||||
ORDER BY scheduled_time ASC
|
||||
LIMIT 100
|
||||
</select>
|
||||
|
||||
<!-- CAS更新消息状态 -->
|
||||
<update id="compareAndSetStatus">
|
||||
UPDATE tb_sys_message
|
||||
SET status = #{newStatus},
|
||||
update_time = NOW()
|
||||
WHERE message_id = #{messageID}
|
||||
AND status = #{expectedStatus}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 更新消息统计信息 -->
|
||||
<update id="updateStatistics">
|
||||
UPDATE tb_sys_message
|
||||
SET sent_count = #{sentCount},
|
||||
success_count = #{successCount},
|
||||
failed_count = #{failedCount},
|
||||
update_time = NOW()
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 更新已读数量 -->
|
||||
<update id="incrementReadCount">
|
||||
UPDATE tb_sys_message
|
||||
SET read_count = read_count + 1,
|
||||
update_time = NOW()
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 根据消息ID查询消息 -->
|
||||
<select id="selectMessageById" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM tb_sys_message
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 插入消息 -->
|
||||
<insert id="insertMessage">
|
||||
INSERT INTO tb_sys_message
|
||||
(id, message_id, title, content, message_type, priority, sender_id, sender_dept_id,
|
||||
send_mode, scheduled_time, status, target_user_count, retry_count, max_retry_count,
|
||||
creator, create_time, update_time, deleted)
|
||||
VALUES
|
||||
(#{message.id}, #{message.messageID}, #{message.title}, #{message.content},
|
||||
#{message.messageType}, #{message.priority}, #{message.senderID}, #{message.senderDeptID},
|
||||
#{message.sendMode}, #{message.scheduledTime}, #{message.status}, #{message.targetUserCount},
|
||||
#{message.retryCount}, #{message.maxRetryCount}, #{message.creator}, #{message.createTime},
|
||||
#{message.updateTime}, #{message.deleted})
|
||||
</insert>
|
||||
|
||||
<!-- 更新消息 -->
|
||||
<update id="updateMessage">
|
||||
UPDATE tb_sys_message
|
||||
<set>
|
||||
<if test="message.title != null and message.title != ''">
|
||||
title = #{message.title},
|
||||
</if>
|
||||
<if test="message.content != null">
|
||||
content = #{message.content},
|
||||
</if>
|
||||
<if test="message.messageType != null and message.messageType != ''">
|
||||
message_type = #{message.messageType},
|
||||
</if>
|
||||
<if test="message.priority != null and message.priority != ''">
|
||||
priority = #{message.priority},
|
||||
</if>
|
||||
<if test="message.sendMode != null and message.sendMode != ''">
|
||||
send_mode = #{message.sendMode},
|
||||
</if>
|
||||
<if test="message.scheduledTime != null">
|
||||
scheduled_time = #{message.scheduledTime},
|
||||
</if>
|
||||
<if test="message.actualSendTime != null">
|
||||
actual_send_time = #{message.actualSendTime},
|
||||
</if>
|
||||
<if test="message.status != null and message.status != ''">
|
||||
status = #{message.status},
|
||||
</if>
|
||||
<if test="message.targetUserCount != null">
|
||||
target_user_count = #{message.targetUserCount},
|
||||
</if>
|
||||
<if test="message.retryCount != null">
|
||||
retry_count = #{message.retryCount},
|
||||
</if>
|
||||
<if test="message.maxRetryCount != null">
|
||||
max_retry_count = #{message.maxRetryCount},
|
||||
</if>
|
||||
<if test="message.lastError != null">
|
||||
last_error = #{message.lastError},
|
||||
</if>
|
||||
<if test="message.updater != null and message.updater != ''">
|
||||
updater = #{message.updater},
|
||||
</if>
|
||||
<if test="message.updateTime != null">
|
||||
update_time = #{message.updateTime},
|
||||
</if>
|
||||
</set>
|
||||
WHERE message_id = #{message.messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 删除消息(逻辑删除) -->
|
||||
<update id="deleteMessage">
|
||||
UPDATE tb_sys_message
|
||||
SET deleted = 1,
|
||||
delete_time = NOW(),
|
||||
update_time = NOW()
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 统计消息总数(带权限过滤) -->
|
||||
<select id="countMessage" resultType="java.lang.Integer">
|
||||
SELECT COUNT(*)
|
||||
FROM tb_sys_message m
|
||||
WHERE m.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND m.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.messageType != null and filter.messageType != ''">
|
||||
AND m.message_type = #{filter.messageType}
|
||||
</if>
|
||||
<if test="filter.status != null and filter.status != ''">
|
||||
AND m.status = #{filter.status}
|
||||
</if>
|
||||
<if test="filter.sendMode != null and filter.sendMode != ''">
|
||||
AND m.send_mode = #{filter.sendMode}
|
||||
</if>
|
||||
<if test="filter.priority != null and filter.priority != ''">
|
||||
AND m.priority = #{filter.priority}
|
||||
</if>
|
||||
</if>
|
||||
<!-- 权限过滤:只能查看自己部门及子部门的消息 -->
|
||||
AND (
|
||||
m.sender_dept_id = #{currentUserDeptID}
|
||||
OR m.sender_dept_id IN (
|
||||
WITH RECURSIVE dept_tree AS (
|
||||
SELECT dept_id FROM tb_sys_dept
|
||||
WHERE dept_id = #{currentUserDeptID} AND deleted = 0
|
||||
UNION ALL
|
||||
SELECT d.dept_id FROM tb_sys_dept d
|
||||
INNER JOIN dept_tree dt ON d.parent_id = dt.dept_id
|
||||
WHERE d.deleted = 0
|
||||
)
|
||||
SELECT dept_id FROM dept_tree
|
||||
)
|
||||
)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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.xyzh.message.mapper.MessageTargetMapper">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.message.TbSysMessageTarget">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageID" jdbcType="VARCHAR"/>
|
||||
<result column="send_method" property="sendMethod" jdbcType="VARCHAR"/>
|
||||
<result column="target_type" property="targetType" jdbcType="VARCHAR"/>
|
||||
<result column="target_id" property="targetID" jdbcType="VARCHAR"/>
|
||||
<result column="target_name" property="targetName" jdbcType="VARCHAR"/>
|
||||
<result column="scope_dept_id" property="scopeDeptID" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据消息ID查询接收对象列表 -->
|
||||
<select id="selectByMessageID" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM tb_sys_message_target
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
ORDER BY create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 批量插入接收对象 -->
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO tb_sys_message_target
|
||||
(id, message_id, send_method, target_type, target_id, target_name, scope_dept_id, creator, create_time, update_time, deleted)
|
||||
VALUES
|
||||
<foreach collection="targets" item="item" separator=",">
|
||||
(#{item.id}, #{item.messageID}, #{item.sendMethod}, #{item.targetType}, #{item.targetID},
|
||||
#{item.targetName}, #{item.scopeDeptID}, #{item.creator}, NOW(), NOW(), 0)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 根据消息ID删除接收对象 -->
|
||||
<update id="deleteByMessageID">
|
||||
UPDATE tb_sys_message_target
|
||||
SET deleted = 1,
|
||||
delete_time = NOW(),
|
||||
update_time = NOW()
|
||||
WHERE message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,352 @@
|
||||
<?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.xyzh.message.mapper.MessageUserMapper">
|
||||
|
||||
<!-- Result Map -->
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.message.TbSysMessageUser">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageID" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="send_method" property="sendMethod" jdbcType="VARCHAR"/>
|
||||
<result column="is_read" property="isRead" jdbcType="BOOLEAN"/>
|
||||
<result column="read_time" property="readTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="send_status" property="sendStatus" jdbcType="VARCHAR"/>
|
||||
<result column="fail_reason" property="failReason" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="MessageUserVOMap" type="org.xyzh.common.dto.message.MessageUserVO">
|
||||
<id column="id" property="id" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageID" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userID" jdbcType="VARCHAR"/>
|
||||
<result column="username" property="username" jdbcType="VARCHAR"/>
|
||||
<result column="full_name" property="fullName" jdbcType="VARCHAR"/>
|
||||
<result column="dept_id" property="deptID" jdbcType="VARCHAR"/>
|
||||
<result column="name" property="deptName" jdbcType="VARCHAR"/>
|
||||
<result column="send_method" property="sendMethod" jdbcType="VARCHAR"/>
|
||||
<result column="is_read" property="isRead" jdbcType="BOOLEAN"/>
|
||||
<result column="read_time" property="readTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="send_status" property="sendStatus" jdbcType="VARCHAR"/>
|
||||
<result column="fail_reason" property="failReason" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||
<result column="content" property="content" jdbcType="VARCHAR"/>
|
||||
<result column="message_type" property="messageType" jdbcType="VARCHAR"/>
|
||||
<result column="priority" property="priority" jdbcType="VARCHAR"/>
|
||||
<result column="sender_name" property="senderName" jdbcType="VARCHAR"/>
|
||||
<result column="sender_dept_name" property="senderDeptName" jdbcType="VARCHAR"/>
|
||||
<result column="actual_send_time" property="actualSendTime" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据消息ID查询用户消息列表 -->
|
||||
<select id="selectByMessageID" resultMap="MessageUserVOMap">
|
||||
SELECT
|
||||
mu.*,
|
||||
u.username,
|
||||
ui.full_name as full_name,
|
||||
d.dept_id as dept_id,
|
||||
d.name as deptName
|
||||
FROM tb_sys_message_user mu
|
||||
LEFT JOIN tb_sys_user u ON mu.user_id = u.id
|
||||
LEFT JOIN tb_sys_user_info ui ON u.id = ui.user_id
|
||||
LEFT JOIN tb_sys_user_dept_role udr ON u.id = udr.user_id AND udr.deleted = 0
|
||||
LEFT JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE mu.message_id = #{messageID}
|
||||
AND mu.deleted = 0
|
||||
ORDER BY mu.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 批量插入用户消息 -->
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO tb_sys_message_user
|
||||
(id, message_id, user_id, send_method, is_read, send_status, creator, create_time, update_time, deleted)
|
||||
VALUES
|
||||
<foreach collection="userMessages" item="item" separator=",">
|
||||
(#{item.id}, #{item.messageID}, #{item.userID}, #{item.sendMethod},
|
||||
0, 'pending', #{item.creator}, NOW(), NOW(), 0)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 分页查询当前用户的消息列表 -->
|
||||
<select id="selectMyMessages" resultMap="MessageUserVOMap">
|
||||
SELECT
|
||||
mu.*,
|
||||
m.title,
|
||||
m.content,
|
||||
m.message_type as message_type,
|
||||
m.priority,
|
||||
m.sender_name as sender_name,
|
||||
m.sender_dept_name as sender_dept_name,
|
||||
m.actual_send_time as actual_send_time
|
||||
FROM tb_sys_message_user mu
|
||||
INNER JOIN tb_sys_message m ON mu.message_id = m.message_id
|
||||
WHERE mu.user_id = #{userID}
|
||||
AND mu.deleted = 0
|
||||
AND m.deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.isRead != null">
|
||||
AND mu.is_read = #{filter.isRead}
|
||||
</if>
|
||||
<if test="filter.sendMethod != null and filter.sendMethod != ''">
|
||||
AND mu.send_method = #{filter.sendMethod}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY m.actual_send_time DESC, mu.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询当前用户的消息详情 -->
|
||||
<select id="selectMyMessageDetail" resultMap="MessageUserVOMap">
|
||||
SELECT
|
||||
mu.*,
|
||||
m.title,
|
||||
m.content,
|
||||
m.message_type as message_type,
|
||||
m.priority,
|
||||
m.sender_name as sender_name,
|
||||
m.sender_dept_name as sender_dept_name,
|
||||
m.actual_send_time as actual_send_time
|
||||
FROM tb_sys_message_user mu
|
||||
INNER JOIN tb_sys_message m ON mu.message_id = m.message_id
|
||||
WHERE mu.user_id = #{userID}
|
||||
AND mu.message_id = #{messageID}
|
||||
AND mu.deleted = 0
|
||||
AND m.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 标记消息为已读 -->
|
||||
<update id="markAsRead">
|
||||
UPDATE tb_sys_message_user
|
||||
SET is_read = 1,
|
||||
send_status = 'sent',
|
||||
read_time = NOW(),
|
||||
update_time = NOW()
|
||||
WHERE user_id = #{userID}
|
||||
AND message_id = #{messageID}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 批量标记消息为已读 -->
|
||||
<update id="batchMarkAsRead">
|
||||
UPDATE tb_sys_message_user
|
||||
SET is_read = 1,
|
||||
read_time = NOW(),
|
||||
update_time = NOW()
|
||||
WHERE user_id = #{userID}
|
||||
AND message_id IN
|
||||
<foreach collection="messageIDs" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 查询未读消息数量 -->
|
||||
<select id="countUnread" resultType="java.lang.Integer">
|
||||
SELECT COUNT(*)
|
||||
FROM tb_sys_message_user
|
||||
WHERE user_id = #{userID}
|
||||
AND is_read = 0
|
||||
AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 动态计算未读消息数量(基于 target 配置) -->
|
||||
<select id="countUnreadWithDynamicTargets" resultType="java.lang.Integer">
|
||||
SELECT COUNT(DISTINCT m.message_id)
|
||||
FROM tb_sys_message m
|
||||
INNER JOIN tb_sys_message_target mt ON m.message_id = mt.message_id AND mt.deleted = 0
|
||||
LEFT JOIN tb_sys_message_user mu ON m.message_id = mu.message_id AND mu.user_id = #{userID} AND mu.deleted = 0
|
||||
WHERE m.deleted = 0
|
||||
AND m.status IN ('sent', 'sending', 'failed')
|
||||
AND COALESCE(mu.is_read, 0) = 0
|
||||
AND (
|
||||
-- 用户类型目标:直接匹配
|
||||
(mt.target_type = 'user' AND mt.target_id = #{userID})
|
||||
OR
|
||||
-- 部门类型目标:用户所在部门是目标部门或其子部门
|
||||
(mt.target_type = 'dept' AND EXISTS (
|
||||
SELECT 1 FROM tb_sys_user_dept_role udr
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
INNER JOIN tb_sys_dept target_dept ON target_dept.dept_id = mt.target_id AND target_dept.deleted = 0
|
||||
WHERE udr.user_id = #{userID}
|
||||
AND udr.deleted = 0
|
||||
AND (
|
||||
d.dept_id = target_dept.dept_id
|
||||
OR d.dept_path LIKE CONCAT(target_dept.dept_path, '%')
|
||||
)
|
||||
))
|
||||
OR
|
||||
-- 角色类型目标:用户在指定部门范围内拥有该角色
|
||||
(mt.target_type = 'role' AND EXISTS (
|
||||
SELECT 1 FROM tb_sys_user_dept_role udr
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE udr.user_id = #{userID}
|
||||
AND udr.deleted = 0
|
||||
AND udr.role_id = mt.target_id
|
||||
AND d.dept_path LIKE CONCAT(
|
||||
(SELECT dept_path FROM tb_sys_dept WHERE dept_id = mt.scope_dept_id AND deleted = 0),
|
||||
'%'
|
||||
)
|
||||
))
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 更新用户消息的发送状态 -->
|
||||
<update id="updateSendStatus">
|
||||
UPDATE tb_sys_message_user
|
||||
SET send_status = #{sendStatus},
|
||||
<if test="failReason != null and failReason != ''">
|
||||
fail_reason = #{failReason},
|
||||
</if>
|
||||
update_time = NOW()
|
||||
WHERE id = #{id}
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 查询待发送的用户消息列表 -->
|
||||
<select id="selectPendingUserMessages" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM tb_sys_message_user
|
||||
WHERE deleted = 0
|
||||
<if test="filter != null">
|
||||
<if test="filter.messageID != null and filter.messageID != ''">
|
||||
AND message_id = #{filter.messageID}
|
||||
</if>
|
||||
<if test="filter.sendStatus != null and filter.sendStatus != ''">
|
||||
AND send_status = #{filter.sendStatus}
|
||||
</if>
|
||||
<if test="filter.deleted != null">
|
||||
AND deleted = #{filter.deleted}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY create_time ASC
|
||||
</select>
|
||||
|
||||
<!-- 动态查询当前用户可见的消息列表(基于 target 配置计算) -->
|
||||
<select id="selectMyMessagesWithDynamicTargets" resultMap="MessageUserVOMap">
|
||||
SELECT
|
||||
m.message_id,
|
||||
m.title,
|
||||
m.content,
|
||||
m.message_type,
|
||||
m.priority,
|
||||
m.sender_name,
|
||||
m.sender_dept_name,
|
||||
m.actual_send_time,
|
||||
MAX(COALESCE(mu.is_read, 0)) as is_read,
|
||||
MAX(mu.read_time) as read_time,
|
||||
MAX(COALESCE(mu.send_status, 'pending')) as send_status,
|
||||
MAX(mu.id) as id,
|
||||
MAX(mu.user_id) as user_id,
|
||||
MAX(mu.send_method) as send_method,
|
||||
MAX(mu.fail_reason) as fail_reason,
|
||||
MAX(mu.create_time) as create_time
|
||||
FROM tb_sys_message m
|
||||
INNER JOIN tb_sys_message_target mt ON m.message_id = mt.message_id AND mt.deleted = 0
|
||||
LEFT JOIN tb_sys_message_user mu ON m.message_id = mu.message_id AND mu.user_id = #{userID} AND mu.deleted = 0
|
||||
WHERE m.deleted = 0
|
||||
AND m.status IN ('sent', 'sending', 'failed')
|
||||
AND (
|
||||
-- 用户类型目标:直接匹配
|
||||
(mt.target_type = 'user' AND mt.target_id = #{userID})
|
||||
OR
|
||||
-- 部门类型目标:用户所在部门是目标部门或其子部门
|
||||
(mt.target_type = 'dept' AND EXISTS (
|
||||
SELECT 1 FROM tb_sys_user_dept_role udr
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
INNER JOIN tb_sys_dept target_dept ON target_dept.dept_id = mt.target_id AND target_dept.deleted = 0
|
||||
WHERE udr.user_id = #{userID}
|
||||
AND udr.deleted = 0
|
||||
AND (
|
||||
d.dept_id = target_dept.dept_id
|
||||
OR d.dept_path LIKE CONCAT(target_dept.dept_path, '%')
|
||||
)
|
||||
))
|
||||
OR
|
||||
-- 角色类型目标:用户在指定部门范围内拥有该角色
|
||||
(mt.target_type = 'role' AND EXISTS (
|
||||
SELECT 1 FROM tb_sys_user_dept_role udr
|
||||
INNER JOIN tb_sys_dept d ON udr.dept_id = d.dept_id AND d.deleted = 0
|
||||
WHERE udr.user_id = #{userID}
|
||||
AND udr.deleted = 0
|
||||
AND udr.role_id = mt.target_id
|
||||
AND d.dept_path LIKE CONCAT(
|
||||
(SELECT dept_path FROM tb_sys_dept WHERE dept_id = mt.scope_dept_id AND deleted = 0),
|
||||
'%'
|
||||
)
|
||||
))
|
||||
)
|
||||
<if test="filter != null">
|
||||
<if test="filter.isRead != null">
|
||||
AND COALESCE(mu.is_read, 0) = #{filter.isRead}
|
||||
</if>
|
||||
<if test="filter.sendMethod != null and filter.sendMethod != ''">
|
||||
AND mt.send_method = #{filter.sendMethod}
|
||||
</if>
|
||||
<if test="filter.sendStatus != null and filter.sendStatus != ''">
|
||||
AND mu.send_status = #{filter.sendStatus}
|
||||
</if>
|
||||
<if test="filter.priority != null and filter.priority != ''">
|
||||
AND m.priority = #{filter.priority}
|
||||
</if>
|
||||
<if test="filter.messageType != null and filter.messageType != ''">
|
||||
AND m.message_type = #{filter.messageType}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND m.title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
</if>
|
||||
GROUP BY m.message_id, m.title, m.content, m.message_type, m.priority,
|
||||
m.sender_name, m.sender_dept_name, m.actual_send_time
|
||||
ORDER BY m.actual_send_time DESC, m.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询或创建用户消息记录 -->
|
||||
<select id="selectOrCreateUserMessage" resultMap="MessageUserVOMap">
|
||||
SELECT
|
||||
mu.*,
|
||||
m.title,
|
||||
m.content,
|
||||
m.message_type,
|
||||
m.priority,
|
||||
m.sender_name,
|
||||
m.sender_dept_name,
|
||||
m.actual_send_time
|
||||
FROM tb_sys_message m
|
||||
LEFT JOIN tb_sys_message_user mu ON m.message_id = mu.message_id
|
||||
AND mu.user_id = #{userID}
|
||||
AND mu.deleted = 0
|
||||
WHERE m.message_id = #{messageID}
|
||||
AND m.deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 插入用户消息记录(如果不存在) -->
|
||||
<insert id="insertIfNotExists">
|
||||
INSERT INTO tb_sys_message_user
|
||||
(id, message_id, user_id, send_method, is_read, send_status, creator, create_time, update_time, deleted)
|
||||
SELECT
|
||||
#{userMessage.id},
|
||||
#{userMessage.messageID},
|
||||
#{userMessage.userID},
|
||||
#{userMessage.sendMethod},
|
||||
0,
|
||||
'pending',
|
||||
#{userMessage.creator},
|
||||
NOW(),
|
||||
NOW(),
|
||||
0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM tb_sys_message_user
|
||||
WHERE message_id = #{userMessage.messageID}
|
||||
AND user_id = #{userMessage.userID}
|
||||
AND deleted = 0
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user