mirror of
https://gitee.com/dromara/RuoYi-Cloud-Plus.git
synced 2026-05-05 03:21:29 +08:00
同步vue版本代码修改
update 使用常量替代硬编码的删除标志,优化查询条件 update 增加消息推送模块注释 update 优化 将全局继承MPJ改为按需求继承 fix 修复 distinct 在 sqlserver 中的限制 补缺排序字段 确保语法正确 update 增加部门Excel转换处理和下拉选项数据源
This commit is contained in:
@@ -5,10 +5,12 @@ import cn.hutool.core.lang.tree.Tree;
|
||||
import cn.hutool.core.lang.tree.TreeNodeConfig;
|
||||
import cn.hutool.core.lang.tree.TreeUtil;
|
||||
import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -120,6 +122,20 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建树节点路径 Map: 路径为 key, 节点为 value
|
||||
*
|
||||
* @param trees 树结构
|
||||
* @param joiner 拼接符
|
||||
* @param fieldGetter 路径拼接字段
|
||||
* @return Map<拼接路径, 原始Tree节点>
|
||||
*/
|
||||
public static <K> Map<String, Tree<K>> buildTreeNodeMap(List<Tree<K>> trees, String joiner, Function<Tree<K>, CharSequence> fieldGetter) {
|
||||
Map<String, Tree<K>> nodeMap = new LinkedHashMap<>();
|
||||
doBuildTreeNodeMap(trees, "", joiner, fieldGetter, nodeMap);
|
||||
return nodeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定节点下的所有叶子节点
|
||||
*
|
||||
@@ -137,4 +153,20 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static <K> void doBuildTreeNodeMap(List<Tree<K>> trees, String parentPath, String joiner,
|
||||
Function<Tree<K>, CharSequence> fieldGetter, Map<String, Tree<K>> nodeMap) {
|
||||
if (CollUtil.isEmpty(trees)) {
|
||||
return;
|
||||
}
|
||||
for (Tree<K> tree : trees) {
|
||||
CharSequence field = fieldGetter.apply(tree);
|
||||
if (StrUtil.isEmpty(field)) {
|
||||
continue;
|
||||
}
|
||||
String currentPath = StrUtil.isEmpty(parentPath) ? field.toString() : parentPath + joiner + field;
|
||||
nodeMap.put(currentPath, tree);
|
||||
doBuildTreeNodeMap(tree.getChildren(), currentPath, joiner, fieldGetter, nodeMap);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
@@ -29,7 +29,7 @@ import java.util.function.Function;
|
||||
* @since 2021-05-13
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface BaseMapperPlus<T, V> extends MPJBaseMapper<T> {
|
||||
public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
|
||||
Log log = LogFactory.getLog(BaseMapperPlus.class);
|
||||
|
||||
|
||||
@@ -16,16 +16,36 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnProperty(prefix = "message", name = "transport", havingValue = "sse", matchIfMissing = true)
|
||||
public class MessageSseConfiguration {
|
||||
|
||||
/**
|
||||
* 注册 SSE 会话管理器
|
||||
* 负责管理用户 SSE 连接、消息发送、会话清理
|
||||
*
|
||||
* @return SseEmitterSessionManager 实例
|
||||
*/
|
||||
@Bean
|
||||
public SseEmitterSessionManager sseEmitterManager() {
|
||||
return new SseEmitterSessionManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册消息主题监听器
|
||||
* 监听 Redis 全局消息,用于集群环境下的消息分发
|
||||
*
|
||||
* @param manager SSE 会话管理器
|
||||
* @return MessageTopicListener 实例
|
||||
*/
|
||||
@Bean
|
||||
public MessageTopicListener messageTopicListener(SseEmitterSessionManager manager) {
|
||||
return new MessageTopicListener(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 SSE 控制器
|
||||
* 提供前端建立 SSE 连接的接口
|
||||
*
|
||||
* @param manager SSE 会话管理器
|
||||
* @return SseController 实例
|
||||
*/
|
||||
@Bean
|
||||
public SseController sseController(SseEmitterSessionManager manager) {
|
||||
return new SseController(manager);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.dromara.common.push.config;
|
||||
|
||||
import org.dromara.common.push.listener.MessageTopicListener;
|
||||
import org.dromara.common.push.core.WebSocketSessionManager;
|
||||
import org.dromara.common.push.handler.PlusWebSocketHandler;
|
||||
import org.dromara.common.push.interceptor.PlusWebSocketInterceptor;
|
||||
import org.dromara.common.push.listener.MessageTopicListener;
|
||||
import org.dromara.common.push.properties.MessageProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -23,6 +23,10 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
@ConditionalOnProperty(prefix = "message", name = "transport", havingValue = "websocket")
|
||||
public class MessageWebSocketConfiguration {
|
||||
|
||||
/**
|
||||
* WebSocket 配置注册
|
||||
* 配置连接路径、拦截器、跨域
|
||||
*/
|
||||
@Bean
|
||||
public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
|
||||
WebSocketHandler webSocketHandler,
|
||||
@@ -33,21 +37,37 @@ public class MessageWebSocketConfiguration {
|
||||
.setAllowedOrigins(messageProperties.getAllowedOrigins());
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 会话管理器
|
||||
* 负责连接管理、消息发送、定时清理失效会话
|
||||
*/
|
||||
@Bean
|
||||
public WebSocketSessionManager webSocketSessionManager() {
|
||||
return new WebSocketSessionManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 握手拦截器
|
||||
* 建立连接前做登录校验、客户端ID校验
|
||||
*/
|
||||
@Bean
|
||||
public HandshakeInterceptor handshakeInterceptor() {
|
||||
return new PlusWebSocketInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 消息处理器
|
||||
* 处理连接、消息、心跳、断开、异常等事件
|
||||
*/
|
||||
@Bean
|
||||
public WebSocketHandler webSocketHandler(WebSocketSessionManager webSocketSessionManager) {
|
||||
return new PlusWebSocketHandler(webSocketSessionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息主题监听器
|
||||
* 订阅 Redis 消息,实现集群环境下的消息分发
|
||||
*/
|
||||
@Bean
|
||||
public MessageTopicListener messageTopicListener(WebSocketSessionManager webSocketSessionManager) {
|
||||
return new MessageTopicListener(webSocketSessionManager);
|
||||
|
||||
@@ -7,13 +7,28 @@ package org.dromara.common.push.constant;
|
||||
*/
|
||||
public interface MessageConstants {
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*/
|
||||
String LOGIN_USER_KEY = "loginUser";
|
||||
|
||||
/**
|
||||
* 登录令牌
|
||||
*/
|
||||
String LOGIN_TOKEN_KEY = "token";
|
||||
|
||||
/**
|
||||
* 全局消息订阅主题
|
||||
*/
|
||||
String MESSAGE_TOPIC = "global:message";
|
||||
|
||||
/**
|
||||
* 心跳请求标识
|
||||
*/
|
||||
String PING = "ping";
|
||||
|
||||
/**
|
||||
* 心跳响应标识
|
||||
*/
|
||||
String PONG = "pong";
|
||||
}
|
||||
|
||||
@@ -12,13 +12,40 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public interface PushSessionManager {
|
||||
|
||||
/**
|
||||
* 订阅消息通道
|
||||
* 注册消息消费者,用于监听并处理消息推送事件
|
||||
*
|
||||
* @param consumer 消息消费逻辑
|
||||
*/
|
||||
void subscribeMessage(Consumer<PushDTO> consumer);
|
||||
|
||||
/**
|
||||
* 发送消息给指定用户
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param payload 消息体
|
||||
*/
|
||||
void sendMessage(Long userId, PushPayloadDTO payload);
|
||||
|
||||
/**
|
||||
* 全局广播消息(所有在线用户)
|
||||
*
|
||||
* @param payload 消息体
|
||||
*/
|
||||
void sendMessage(PushPayloadDTO payload);
|
||||
|
||||
/**
|
||||
* 批量发布消息给指定用户列表
|
||||
*
|
||||
* @param pushDTO 推送参数封装对象
|
||||
*/
|
||||
void publishMessage(PushDTO pushDTO);
|
||||
|
||||
/**
|
||||
* 全局广播消息(所有用户)
|
||||
*
|
||||
* @param payload 消息体
|
||||
*/
|
||||
void publishAll(PushPayloadDTO payload);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package org.dromara.common.push.core;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.push.constant.MessageConstants;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.push.constant.MessageConstants;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
|
||||
@@ -5,14 +5,10 @@ import cn.hutool.core.map.MapUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.PongMessage;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -33,20 +29,44 @@ import static org.dromara.common.push.constant.MessageConstants.MESSAGE_TOPIC;
|
||||
@Slf4j
|
||||
public class WebSocketSessionManager implements PushSessionManager {
|
||||
|
||||
/**
|
||||
* 用户会话存储集合
|
||||
* 结构:userId -> (token -> WebSocketSession)
|
||||
* 支持同一用户多终端、多设备同时在线
|
||||
*/
|
||||
private static final Map<Long, Map<String, WebSocketSession>> USER_TOKEN_SESSIONS = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* 初始化定时任务:每60秒执行一次会话监控,自动清理无效连接
|
||||
*/
|
||||
public WebSocketSessionManager() {
|
||||
SpringUtils.getBean(ScheduledExecutorService.class)
|
||||
.scheduleWithFixedDelay(this::sessionMonitor, 60L, 60L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户建立WebSocket连接
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param token 客户端唯一标识(区分不同设备/终端)
|
||||
* @param session WebSocket会话对象
|
||||
*/
|
||||
public void connect(Long userId, String token, WebSocketSession session) {
|
||||
Map<String, WebSocketSession> sessions = USER_TOKEN_SESSIONS.computeIfAbsent(userId, key -> new ConcurrentHashMap<>());
|
||||
// 移除并关闭旧的同token会话,避免重复连接
|
||||
WebSocketSession oldSession = sessions.remove(token);
|
||||
closeSession(oldSession, CloseStatus.NORMAL);
|
||||
// 存储新会话
|
||||
sessions.put(token, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户断开WebSocket连接
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param token 客户端唯一标识
|
||||
*/
|
||||
public void disconnect(Long userId, String token) {
|
||||
if (userId == null || token == null) {
|
||||
return;
|
||||
@@ -56,12 +76,18 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
USER_TOKEN_SESSIONS.remove(userId);
|
||||
return;
|
||||
}
|
||||
// 移除指定token会话并关闭
|
||||
closeSession(sessions.remove(token), CloseStatus.NORMAL);
|
||||
// 该用户无任何会话时,从缓存中移除
|
||||
if (sessions.isEmpty()) {
|
||||
USER_TOKEN_SESSIONS.remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话监控定时任务
|
||||
* 定期清理已关闭、失效的WebSocket会话,防止内存泄漏
|
||||
*/
|
||||
public void sessionMonitor() {
|
||||
List<Long> toRemoveUsers = new ArrayList<>();
|
||||
USER_TOKEN_SESSIONS.forEach((userId, sessionMap) -> {
|
||||
@@ -69,6 +95,7 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
toRemoveUsers.add(userId);
|
||||
return;
|
||||
}
|
||||
// 移除已关闭的无效会话
|
||||
sessionMap.entrySet().removeIf(entry -> {
|
||||
WebSocketSession session = entry.getValue();
|
||||
if (session == null || !session.isOpen()) {
|
||||
@@ -77,18 +104,32 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// 无有效会话,标记用户待删除
|
||||
if (sessionMap.isEmpty()) {
|
||||
toRemoveUsers.add(userId);
|
||||
}
|
||||
});
|
||||
// 批量清理无会话用户
|
||||
toRemoveUsers.forEach(USER_TOKEN_SESSIONS::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅消息通道
|
||||
* 注册消息消费者,监听Redis消息推送
|
||||
*
|
||||
* @param consumer 消息消费逻辑
|
||||
*/
|
||||
@Override
|
||||
public void subscribeMessage(Consumer<PushDTO> consumer) {
|
||||
RedisUtils.subscribe(MESSAGE_TOPIC, PushDTO.class, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定用户发送消息
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param payload 消息体
|
||||
*/
|
||||
@Override
|
||||
public void sendMessage(Long userId, PushPayloadDTO payload) {
|
||||
if (payload == null) {
|
||||
@@ -99,24 +140,38 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
USER_TOKEN_SESSIONS.remove(userId);
|
||||
return;
|
||||
}
|
||||
// 发送消息并自动清理失效会话
|
||||
sessions.entrySet().removeIf(entry -> {
|
||||
WebSocketSession session = entry.getValue();
|
||||
if (session == null || !session.isOpen()) {
|
||||
closeSession(session, CloseStatus.NORMAL);
|
||||
return true;
|
||||
}
|
||||
// 发送失败的会话也会被移除
|
||||
return !sendMessage(session, new TextMessage(JsonUtils.toJsonString(payload)));
|
||||
});
|
||||
// 无有效会话则移除用户
|
||||
if (sessions.isEmpty()) {
|
||||
USER_TOKEN_SESSIONS.remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向所有在线用户广播消息
|
||||
*
|
||||
* @param payload 消息体
|
||||
*/
|
||||
@Override
|
||||
public void sendMessage(PushPayloadDTO payload) {
|
||||
USER_TOKEN_SESSIONS.keySet().forEach(userId -> sendMessage(userId, payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布消息到Redis订阅通道
|
||||
* 支持集群环境下的分布式消息推送
|
||||
*
|
||||
* @param pushDTO 推送消息封装对象
|
||||
*/
|
||||
@Override
|
||||
public void publishMessage(PushDTO pushDTO) {
|
||||
RedisUtils.publish(MESSAGE_TOPIC, pushDTO, consumer -> log.info(
|
||||
@@ -127,6 +182,11 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播消息(所有用户)
|
||||
*
|
||||
* @param payload 消息体
|
||||
*/
|
||||
@Override
|
||||
public void publishAll(PushPayloadDTO payload) {
|
||||
PushDTO dto = new PushDTO();
|
||||
@@ -134,14 +194,33 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳Pong消息
|
||||
* 用于维持WebSocket长连接存活
|
||||
*
|
||||
* @param session WebSocket会话
|
||||
*/
|
||||
public void sendPongMessage(WebSocketSession session) {
|
||||
sendMessage(session, new PongMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本消息
|
||||
*
|
||||
* @param session WebSocket会话
|
||||
* @param message 文本内容
|
||||
*/
|
||||
public void sendMessage(WebSocketSession session, String message) {
|
||||
sendMessage(session, new TextMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 底层消息发送方法
|
||||
*
|
||||
* @param session 会话对象
|
||||
* @param message WebSocket消息对象
|
||||
* @return 发送是否成功
|
||||
*/
|
||||
private boolean sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
|
||||
if (session == null || !session.isOpen()) {
|
||||
log.warn("[send] session会话已经关闭");
|
||||
@@ -156,6 +235,12 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全关闭WebSocket会话
|
||||
*
|
||||
* @param session 待关闭的会话
|
||||
* @param status 关闭状态码
|
||||
*/
|
||||
private void closeSession(WebSocketSession session, CloseStatus status) {
|
||||
if (session == null) {
|
||||
return;
|
||||
@@ -163,6 +248,7 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
try {
|
||||
session.close(status);
|
||||
} catch (Exception ignored) {
|
||||
// 关闭异常忽略,防止影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,40 @@ import java.util.Arrays;
|
||||
@AllArgsConstructor
|
||||
public enum MessageTransportEnum {
|
||||
|
||||
/**
|
||||
* SSE 传输方式
|
||||
* 服务端推送事件,单向轻量传输
|
||||
*/
|
||||
SSE("sse"),
|
||||
|
||||
/**
|
||||
* WebSocket 传输方式
|
||||
* 全双工长连接,支持双向实时通信
|
||||
*/
|
||||
WEBSOCKET("websocket");
|
||||
|
||||
/**
|
||||
* 传输类型编码
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 判断传输方式是否匹配
|
||||
*
|
||||
* @param transport 传输方式字符串
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean matches(String transport) {
|
||||
return code.equalsIgnoreCase(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传输类型字符串获取枚举
|
||||
* 找不到则默认返回 SSE
|
||||
*
|
||||
* @param transport 传输方式字符串
|
||||
* @return 对应的消息传输枚举
|
||||
*/
|
||||
public static MessageTransportEnum of(String transport) {
|
||||
return Arrays.stream(values())
|
||||
.filter(item -> item.matches(transport))
|
||||
|
||||
@@ -7,14 +7,10 @@ import org.dromara.common.core.enums.PushSourceEnum;
|
||||
import org.dromara.common.core.enums.PushTypeEnum;
|
||||
import org.dromara.common.push.constant.MessageConstants;
|
||||
import org.dromara.common.push.core.WebSocketSessionManager;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.PongMessage;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.*;
|
||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;
|
||||
|
||||
@@ -22,7 +18,8 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WebSocket Handler。
|
||||
* WebSocket 请求处理器
|
||||
* 处理WebSocket连接建立、消息接收、异常、断开等全生命周期事件
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@@ -30,17 +27,31 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
|
||||
/**
|
||||
* WebSocket 会话管理器
|
||||
*/
|
||||
private final WebSocketSessionManager webSocketSessionManager;
|
||||
|
||||
/**
|
||||
* 建立WebSocket连接后触发
|
||||
* 校验用户登录信息,注册会话
|
||||
*
|
||||
* @param session WebSocket会话
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
|
||||
// 从会话属性中获取登录用户信息和Token
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(MessageConstants.LOGIN_USER_KEY);
|
||||
String token = (String) session.getAttributes().get(MessageConstants.LOGIN_TOKEN_KEY);
|
||||
|
||||
// 校验用户信息是否为空,无效则直接关闭连接
|
||||
if (ObjectUtil.hasNull(loginUser, token)) {
|
||||
session.close(CloseStatus.BAD_DATA);
|
||||
log.info("[connect] invalid token received. sessionId: {}", session.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 并发安全包装会话,并注册到会话管理器
|
||||
webSocketSessionManager.connect(
|
||||
loginUser.getUserId(),
|
||||
token,
|
||||
@@ -49,16 +60,27 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
log.info("[connect] sessionId: {}, userId:{}, token:{}", session.getId(), loginUser.getUserId(), token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理客户端发送的文本消息
|
||||
* 支持心跳ping/pong,以及自定义消息转发
|
||||
*
|
||||
* @param session WebSocket会话
|
||||
* @param message 文本消息
|
||||
*/
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(MessageConstants.LOGIN_USER_KEY);
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 心跳处理:客户端发送ping,服务端回复pong
|
||||
if (MessageConstants.PING.equalsIgnoreCase(message.getPayload())) {
|
||||
webSocketSessionManager.sendMessage(session, MessageConstants.PONG);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建客户端自定义消息并发布
|
||||
PushDTO dto = new PushDTO();
|
||||
dto.setUserIds(List.of(loginUser.getUserId()));
|
||||
dto.setPayload(PushPayloadDTO.of(
|
||||
@@ -70,33 +92,55 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
webSocketSessionManager.publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理二进制消息(默认实现)
|
||||
*/
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
|
||||
super.handleBinaryMessage(session, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Pong心跳响应
|
||||
* 维持长连接存活
|
||||
*/
|
||||
@Override
|
||||
protected void handlePongMessage(WebSocketSession session, PongMessage message) {
|
||||
webSocketSessionManager.sendPongMessage(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 传输异常处理
|
||||
* 记录异常日志
|
||||
*/
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) {
|
||||
log.error("[transport error] sessionId: {}, exception:{}", session.getId(), exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接关闭后触发
|
||||
* 注销用户会话
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(MessageConstants.LOGIN_USER_KEY);
|
||||
String token = (String) session.getAttributes().get(MessageConstants.LOGIN_TOKEN_KEY);
|
||||
|
||||
if (ObjectUtil.hasNull(loginUser, token)) {
|
||||
log.info("[disconnect] invalid token received. sessionId: {}", session.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 从会话管理器中移除连接
|
||||
webSocketSessionManager.disconnect(loginUser.getUserId(), token);
|
||||
log.info("[disconnect] sessionId: {}, userId:{}, token:{}", session.getId(), loginUser.getUserId(), token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持分片消息
|
||||
* 关闭:不支持分片传输
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsPartialMessages() {
|
||||
return false;
|
||||
|
||||
@@ -19,14 +19,31 @@ import java.util.List;
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class PushHelper {
|
||||
|
||||
/**
|
||||
* 发送指定用户文本消息
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param message 文本消息内容
|
||||
*/
|
||||
public static void sendMessage(Long userId, String message) {
|
||||
sendMessage(userId, buildMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播文本消息
|
||||
*
|
||||
* @param message 文本消息内容
|
||||
*/
|
||||
public static void sendMessage(String message) {
|
||||
sendMessage(buildMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送指定用户自定义消息体
|
||||
*
|
||||
* @param userId 目标用户ID
|
||||
* @param payload 消息推送体
|
||||
*/
|
||||
public static void sendMessage(Long userId, PushPayloadDTO payload) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
@@ -34,6 +51,11 @@ public class PushHelper {
|
||||
getSessionManager().sendMessage(userId, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局广播自定义消息体
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
*/
|
||||
public static void sendMessage(PushPayloadDTO payload) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
@@ -41,6 +63,12 @@ public class PushHelper {
|
||||
getSessionManager().sendMessage(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量发布消息给指定用户列表
|
||||
*
|
||||
* @param userIds 用户ID集合
|
||||
* @param payload 消息推送体
|
||||
*/
|
||||
public static void publishMessage(List<Long> userIds, PushPayloadDTO payload) {
|
||||
PushDTO dto = new PushDTO();
|
||||
dto.setUserIds(userIds);
|
||||
@@ -48,6 +76,11 @@ public class PushHelper {
|
||||
publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量发布消息(使用完整推送DTO)
|
||||
*
|
||||
* @param dto 推送参数封装对象
|
||||
*/
|
||||
public static void publishMessage(PushDTO dto) {
|
||||
if (!isEnabled() || dto == null || dto.getPayload() == null) {
|
||||
return;
|
||||
@@ -55,10 +88,20 @@ public class PushHelper {
|
||||
getSessionManager().publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布全局广播文本消息
|
||||
*
|
||||
* @param message 文本消息内容
|
||||
*/
|
||||
public static void publishAll(String message) {
|
||||
publishAll(buildMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布全局广播自定义消息体
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
*/
|
||||
public static void publishAll(PushPayloadDTO payload) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
@@ -66,15 +109,33 @@ public class PushHelper {
|
||||
getSessionManager().publishAll(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断消息推送功能是否开启
|
||||
* 读取配置:message.enabled
|
||||
*
|
||||
* @return 是否开启推送
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return Boolean.TRUE.equals(SpringUtils.getProperty("message.enabled", Boolean.class, Boolean.TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推送会话管理器Bean
|
||||
*
|
||||
* @return PushSessionManager 实例
|
||||
*/
|
||||
private static PushSessionManager getSessionManager() {
|
||||
return SpringUtils.getBean(PushSessionManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建默认格式的消息推送体
|
||||
*
|
||||
* @param message 消息内容
|
||||
* @return 封装好的 PushPayloadDTO
|
||||
*/
|
||||
private static PushPayloadDTO buildMessage(String message) {
|
||||
return PushPayloadDTO.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WebSocket 握手拦截器。
|
||||
*
|
||||
@@ -22,36 +23,54 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class PlusWebSocketInterceptor implements HandshakeInterceptor {
|
||||
|
||||
/**
|
||||
* 握手前拦截(核心认证逻辑)
|
||||
* 校验登录状态、Token、客户端ID,认证通过才允许建立 WebSocket 连接
|
||||
*
|
||||
* @param attributes 用于传递到 WebSocketSession 的属性集合
|
||||
* @return 是否允许握手(true=允许,false=拒绝)
|
||||
*/
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
|
||||
Map<String, Object> attributes) {
|
||||
try {
|
||||
// 1. 获取当前登录用户与 Token
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
|
||||
// 2. 未登录直接拒绝握手
|
||||
if (loginUser == null || StringUtils.isBlank(tokenValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 校验客户端ID(防止多端冒用)
|
||||
String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
|
||||
String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
|
||||
String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
|
||||
|
||||
// 客户端ID必须与请求头/参数中的一致,否则拒绝连接
|
||||
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
|
||||
throw NotLoginException.newInstance(StpUtil.getLoginType(),
|
||||
"-100", "客户端ID与Token不匹配",
|
||||
StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
// 4. 认证通过,将用户信息存入会话属性,供后续 WebSocketHandler 使用
|
||||
attributes.put(MessageConstants.LOGIN_USER_KEY, loginUser);
|
||||
attributes.put(MessageConstants.LOGIN_TOKEN_KEY, tokenValue);
|
||||
return true;
|
||||
} catch (NotLoginException e) {
|
||||
// 认证失败,记录日志并拒绝连接
|
||||
log.error("WebSocket 认证失败'{}',无法访问系统资源", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 握手完成后触发
|
||||
* 此处无需处理,留空即可
|
||||
*/
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
|
||||
Exception exception) {
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,20 @@ import org.springframework.core.Ordered;
|
||||
@RequiredArgsConstructor
|
||||
public class MessageTopicListener implements ApplicationRunner, Ordered {
|
||||
|
||||
/**
|
||||
* 推送会话管理器
|
||||
*/
|
||||
private final PushSessionManager pushSessionManager;
|
||||
|
||||
/**
|
||||
* 项目启动后执行
|
||||
* 注册消息订阅,监听消息并分发给对应用户/全局广播
|
||||
*
|
||||
* @param args 启动参数
|
||||
*/
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 订阅消息主题,处理消息分发
|
||||
pushSessionManager.subscribeMessage(message -> {
|
||||
log.info("消息主题订阅收到消息userIds={} message={}",
|
||||
message.getUserIds(),
|
||||
@@ -28,15 +38,22 @@ public class MessageTopicListener implements ApplicationRunner, Ordered {
|
||||
if (message.getPayload() == null) {
|
||||
return;
|
||||
}
|
||||
// 有指定用户 -> 单发
|
||||
if (CollUtil.isNotEmpty(message.getUserIds())) {
|
||||
message.getUserIds().forEach(userId -> pushSessionManager.sendMessage(userId, message.getPayload()));
|
||||
} else {
|
||||
// 无指定用户 -> 全局广播
|
||||
pushSessionManager.sendMessage(message.getPayload());
|
||||
}
|
||||
});
|
||||
log.info("初始化消息主题订阅监听器成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行顺序,优先级设为最高,确保消息订阅最先初始化
|
||||
*
|
||||
* @return 优先级,值越小越先执行
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1;
|
||||
|
||||
@@ -31,12 +31,12 @@ public class RemoteMessageServiceImpl implements RemoteMessageService {
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param sessionKey session主键 一般为用户id
|
||||
* @param message 消息文本
|
||||
* @param userIds 用户ID列表
|
||||
* @param message 消息文本
|
||||
*/
|
||||
@Override
|
||||
public void publishMessage(List<Long> sessionKey, String message) {
|
||||
publishMessagePayload(sessionKey, RemotePushPayLoad.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null));
|
||||
public void publishMessage(List<Long> userIds, String message) {
|
||||
publishMessagePayload(userIds, RemotePushPayLoad.of(PushTypeEnum.MESSAGE, PushSourceEnum.BACKEND, message, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,9 +12,29 @@ import java.util.List;
|
||||
*/
|
||||
public interface ISysMessageService {
|
||||
|
||||
/**
|
||||
* 查询当前用户消息盒子数据
|
||||
* 按系统消息、通知公告、工作流消息分类返回
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 消息盒子数据
|
||||
*/
|
||||
SysMessageBoxVo queryMessageBox(Long userId);
|
||||
|
||||
/**
|
||||
* 存储全局广播消息到数据库
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 回填消息ID后的消息体
|
||||
*/
|
||||
PushPayloadDTO storeAll(PushPayloadDTO payload);
|
||||
|
||||
/**
|
||||
* 存储指定用户消息到数据库
|
||||
*
|
||||
* @param userIds 用户ID集合
|
||||
* @param payload 消息推送体
|
||||
* @return 回填消息ID后的消息体
|
||||
*/
|
||||
PushPayloadDTO storeUsers(List<Long> userIds, PushPayloadDTO payload);
|
||||
}
|
||||
|
||||
@@ -34,15 +34,45 @@ import java.util.concurrent.TimeUnit;
|
||||
@Service
|
||||
public class SysMessageServiceImpl implements ISysMessageService {
|
||||
|
||||
/**
|
||||
* 全局广播用户标识(所有用户可见)
|
||||
*/
|
||||
private static final String GLOBAL_USER_IDS = "0";
|
||||
|
||||
/**
|
||||
* 消息分类:系统消息
|
||||
*/
|
||||
private static final String CATEGORY_SYSTEM = "system";
|
||||
|
||||
/**
|
||||
* 消息分类:通知公告
|
||||
*/
|
||||
private static final String CATEGORY_NOTICE = "notice";
|
||||
|
||||
/**
|
||||
* 消息分类:工作流
|
||||
*/
|
||||
private static final String CATEGORY_WORKFLOW = "workflow";
|
||||
|
||||
/**
|
||||
* 消息盒子每页展示最大条数
|
||||
*/
|
||||
private static final int BOX_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* 消息盒子展示消息天数(仅展示30天内)
|
||||
*/
|
||||
private static final long BOX_DAYS = 30L;
|
||||
|
||||
private final SysMessageMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询当前用户消息盒子数据
|
||||
* 按系统消息、通知公告、工作流消息分类返回
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 分类消息盒子数据
|
||||
*/
|
||||
@Override
|
||||
public SysMessageBoxVo queryMessageBox(Long userId) {
|
||||
SysMessageBoxVo box = new SysMessageBoxVo();
|
||||
@@ -52,16 +82,37 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储全局广播消息到数据库
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 回填消息ID后的消息体
|
||||
*/
|
||||
@Override
|
||||
public PushPayloadDTO storeAll(PushPayloadDTO payload) {
|
||||
return storeMessage(null, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储指定用户消息到数据库
|
||||
*
|
||||
* @param userIds 用户ID集合
|
||||
* @param payload 消息推送体
|
||||
* @return 回填消息ID后的消息体
|
||||
*/
|
||||
@Override
|
||||
public PushPayloadDTO storeUsers(List<Long> userIds, PushPayloadDTO payload) {
|
||||
return storeMessage(userIds, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一消息存储逻辑
|
||||
* 判断是否需要存入消息盒子,需要则插入数据库
|
||||
*
|
||||
* @param userIds 用户ID集合(为null则全局广播)
|
||||
* @param payload 消息推送体
|
||||
* @return 回填消息ID后的消息体
|
||||
*/
|
||||
private PushPayloadDTO storeMessage(List<Long> userIds, PushPayloadDTO payload) {
|
||||
if (!supportsMessageBox(payload)) {
|
||||
return payload;
|
||||
@@ -72,6 +123,14 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类和用户ID查询消息列表
|
||||
* 仅查询30天内、最多100条、按时间倒序
|
||||
*
|
||||
* @param category 消息分类
|
||||
* @param userId 用户ID
|
||||
* @return 消息VO列表
|
||||
*/
|
||||
private List<SysMessageVo> selectMessageList(String category, Long userId) {
|
||||
LambdaQueryWrapper<SysMessage> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(SysMessage::getCategory, category);
|
||||
@@ -84,6 +143,13 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return list.stream().map(this::buildVo).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建消息实体(用于数据库存储)
|
||||
*
|
||||
* @param userIds 接收用户ID集合
|
||||
* @param payload 消息推送体
|
||||
* @return 系统消息实体
|
||||
*/
|
||||
private SysMessage buildMessage(List<Long> userIds, PushPayloadDTO payload) {
|
||||
SysMessage message = new SysMessage();
|
||||
message.setMessageId(payload.getMessageId() == null ? IdGeneratorUtil.nextLongId() : payload.getMessageId());
|
||||
@@ -99,6 +165,12 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息实体转换为展示VO
|
||||
*
|
||||
* @param entity 消息实体
|
||||
* @return 消息展示VO
|
||||
*/
|
||||
private SysMessageVo buildVo(SysMessage entity) {
|
||||
SysMessageVo vo = new SysMessageVo();
|
||||
vo.setMessageId(entity.getMessageId());
|
||||
@@ -114,6 +186,13 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断消息是否需要存入消息盒子
|
||||
* 仅系统消息、通知消息需要存入
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 是否支持存入消息盒子
|
||||
*/
|
||||
private boolean supportsMessageBox(PushPayloadDTO payload) {
|
||||
if (payload == null) {
|
||||
return false;
|
||||
@@ -125,6 +204,12 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据消息类型/来源自动解析消息分类
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 消息分类(system/notice/workflow)
|
||||
*/
|
||||
private String resolveCategory(PushPayloadDTO payload) {
|
||||
if (StringUtils.equalsAny(payload.getType(), PushTypeEnum.NOTICE.getType())
|
||||
|| StringUtils.equalsAny(payload.getSource(), PushSourceEnum.NOTICE.getSource())) {
|
||||
@@ -136,6 +221,12 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return CATEGORY_SYSTEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据消息分类自动生成消息标题
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 消息标题
|
||||
*/
|
||||
private String resolveTitle(PushPayloadDTO payload) {
|
||||
return switch (resolveCategory(payload)) {
|
||||
case CATEGORY_NOTICE -> "通知公告消息";
|
||||
@@ -144,6 +235,12 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析消息内容(从data中提取noticeContent)
|
||||
*
|
||||
* @param payload 消息推送体
|
||||
* @return 消息内容
|
||||
*/
|
||||
private String resolveContent(PushPayloadDTO payload) {
|
||||
Object data = payload.getData();
|
||||
if (data instanceof Map<?, ?> map) {
|
||||
@@ -152,6 +249,12 @@ public class SysMessageServiceImpl implements ISysMessageService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析JSON数据字符串为对象
|
||||
*
|
||||
* @param dataJson JSON字符串
|
||||
* @return 解析后对象
|
||||
*/
|
||||
private Object parseData(String dataJson) {
|
||||
if (StringUtils.isBlank(dataJson)) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.dromara.system.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 角色菜单权限视图
|
||||
*/
|
||||
@Data
|
||||
public class SysRoleMenuPermVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long roleId;
|
||||
|
||||
private String perms;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.system.listener.DeptExcelConverter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -35,6 +36,12 @@ public class SysUserExportVo implements Serializable {
|
||||
@ExcelProperty(value = "用户账号")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@ExcelProperty(value = "部门名称", converter = DeptExcelConverter.class)
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@@ -79,12 +86,6 @@ public class SysUserExportVo implements Serializable {
|
||||
@ExcelProperty(value = "最后登录时间")
|
||||
private Date loginDate;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@ExcelProperty(value = "部门名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,10 @@ import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.system.listener.DeptExcelConverter;
|
||||
import org.dromara.system.listener.DeptExcelOptions;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -33,7 +36,8 @@ public class SysUserImportVo implements Serializable {
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@ExcelProperty(value = "部门编号")
|
||||
@ExcelProperty(value = "部门名称", converter = DeptExcelConverter.class)
|
||||
@ExcelDynamicOptions(providerClass = DeptExcelOptions.class)
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.dromara.system.listener;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fesod.sheet.converters.Converter;
|
||||
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
|
||||
import org.apache.fesod.sheet.metadata.GlobalConfiguration;
|
||||
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.TreeBuildUtils;
|
||||
import org.dromara.system.domain.bo.SysDeptBo;
|
||||
import org.dromara.system.service.ISysDeptService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Excel 部门转换处理
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DeptExcelConverter implements Converter<Long> {
|
||||
|
||||
private static final ThreadLocal<Map<Long, String>> TL_ID_TO_NAME = new ThreadLocal<>();
|
||||
|
||||
private static final ThreadLocal<Map<String, Long>> TL_NAME_TO_ID = new ThreadLocal<>();
|
||||
|
||||
private void initThreadCache() {
|
||||
Map<Long, String> idMap = TL_ID_TO_NAME.get();
|
||||
if (CollUtil.isNotEmpty(idMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Tree<Long>> deptPathToTreeMap = TreeBuildUtils.buildTreeNodeMap(
|
||||
SpringUtils.getBean(ISysDeptService.class).selectDeptTreeList(new SysDeptBo()),
|
||||
"/",
|
||||
Tree::getName
|
||||
);
|
||||
|
||||
Map<Long, String> idToName = new HashMap<>();
|
||||
Map<String, Long> nameToId = new HashMap<>();
|
||||
deptPathToTreeMap.forEach((name, treeNode) -> {
|
||||
Long deptId = treeNode.getId();
|
||||
idToName.put(deptId, name);
|
||||
nameToId.put(name, deptId);
|
||||
});
|
||||
|
||||
TL_ID_TO_NAME.set(idToName);
|
||||
TL_NAME_TO_ID.set(nameToId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> supportJavaTypeKey() {
|
||||
return Long.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
return CellDataTypeEnum.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
||||
String deptName = cellData.getStringValue();
|
||||
if (StringUtils.isBlank(deptName)) {
|
||||
return null;
|
||||
}
|
||||
initThreadCache();
|
||||
return TL_NAME_TO_ID.get().get(deptName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteCellData<?> convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
||||
if (value == null) {
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
initThreadCache();
|
||||
String deptName = TL_ID_TO_NAME.get().getOrDefault(value, "");
|
||||
return new WriteCellData<>(deptName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.dromara.system.listener;
|
||||
|
||||
import cn.hutool.core.lang.tree.Tree;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.utils.TreeBuildUtils;
|
||||
import org.dromara.common.excel.core.ExcelOptionsProvider;
|
||||
import org.dromara.system.domain.bo.SysDeptBo;
|
||||
import org.dromara.system.service.ISysDeptService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Excel 部门下拉选项数据源
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DeptExcelOptions implements ExcelOptionsProvider {
|
||||
|
||||
private final ISysDeptService deptService;
|
||||
|
||||
@Override
|
||||
public Set<String> getOptions() {
|
||||
List<Tree<Long>> trees = deptService.selectDeptTreeList(new SysDeptBo());
|
||||
Map<String, Tree<Long>> treeMap = TreeBuildUtils.buildTreeNodeMap(trees, "/", Tree::getName);
|
||||
return treeMap.keySet();
|
||||
}
|
||||
}
|
||||
@@ -3,58 +3,31 @@ package org.dromara.system.mapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||
import org.dromara.system.domain.SysDept;
|
||||
import org.dromara.system.domain.SysRole;
|
||||
import org.dromara.system.domain.SysRoleDept;
|
||||
import org.dromara.system.domain.vo.SysDeptVo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.dromara.common.core.constant.SystemConstants.NORMAL;
|
||||
|
||||
/**
|
||||
* 部门管理 数据层
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
|
||||
|
||||
/**
|
||||
* 构建角色对应的部门 SQL 查询语句
|
||||
*
|
||||
* <p>该 SQL 用于查询某个角色关联的所有部门 ID,常用于数据权限控制</p>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 查询部门ID的 SQL 语句字符串
|
||||
*/
|
||||
default String buildDeptByRoleSql(Long roleId) {
|
||||
return """
|
||||
select srd.dept_id from sys_role_dept srd
|
||||
left join sys_role sr on sr.role_id = srd.role_id
|
||||
where srd.role_id = %d and sr.status = '0'
|
||||
""".formatted(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 SQL 查询,用于获取当前角色拥有的部门中所有的父部门ID
|
||||
*
|
||||
* <p>
|
||||
* 该 SQL 用于 deptCheckStrictly 场景下,排除非叶子节点(父节点)用。
|
||||
* </p>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return SQL 语句字符串,查询角色下部门的所有父部门ID
|
||||
*/
|
||||
default String buildParentDeptByRoleSql(Long roleId) {
|
||||
return """
|
||||
select parent_id from sys_dept where dept_id in (
|
||||
select srd.dept_id from sys_role_dept srd
|
||||
left join sys_role sr on sr.role_id = srd.role_id
|
||||
where srd.role_id = %d and sr.status = '0'
|
||||
)
|
||||
""".formatted(roleId);
|
||||
}
|
||||
public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo>, MPJBaseMapper<SysDept> {
|
||||
|
||||
/**
|
||||
* 查询部门管理数据
|
||||
@@ -129,15 +102,20 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
|
||||
* @return 选中部门列表
|
||||
*/
|
||||
default List<Long> selectDeptListByRoleId(Long roleId, boolean deptCheckStrictly) {
|
||||
LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(SysDept::getDeptId)
|
||||
.inSql(SysDept::getDeptId, this.buildDeptByRoleSql(roleId))
|
||||
.orderByAsc(SysDept::getParentId)
|
||||
.orderByAsc(SysDept::getOrderNum);
|
||||
if (deptCheckStrictly) {
|
||||
wrapper.notInSql(SysDept::getDeptId, this.buildParentDeptByRoleSql(roleId));
|
||||
}
|
||||
return this.selectObjs(wrapper);
|
||||
List<SysDept> depts = this.selectJoinList(SysDept.class, JoinWrappers.lambda("d", SysDept.class)
|
||||
.distinct()
|
||||
.select(SysDept::getDeptId, SysDept::getParentId, SysDept::getOrderNum)
|
||||
.leftJoin(SysRoleDept.class, "srd", SysRoleDept::getDeptId, SysDept::getDeptId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleDept::getRoleId)
|
||||
.eq("srd", SysRoleDept::getRoleId, roleId)
|
||||
.eq("sr", SysRole::getStatus, NORMAL)
|
||||
.orderByAsc("d", SysDept::getParentId)
|
||||
.orderByAsc("d", SysDept::getOrderNum));
|
||||
Set<Long> parentIds = deptCheckStrictly ? new HashSet<>(StreamUtils.toList(depts, SysDept::getParentId)) : Collections.emptySet();
|
||||
return depts.stream()
|
||||
.map(SysDept::getDeptId)
|
||||
.filter(deptId -> !parentIds.contains(deptId))
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ import java.util.List;
|
||||
*/
|
||||
public interface SysDictDataMapper extends BaseMapperPlus<SysDictData, SysDictDataVo> {
|
||||
|
||||
/**
|
||||
* 根据字典类型查询字典数据列表
|
||||
*
|
||||
* @param dictType 字典类型
|
||||
* @return 符合条件的字典数据列表
|
||||
*/
|
||||
default List<SysDictDataVo> selectDictDataByType(String dictType) {
|
||||
return selectVoList(
|
||||
new LambdaQueryWrapper<SysDictData>()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.dromara.system.mapper;
|
||||
|
||||
import org.dromara.system.domain.SysDictType;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.system.domain.SysDictType;
|
||||
import org.dromara.system.domain.vo.SysDictTypeVo;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,19 @@ package org.dromara.system.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.system.domain.SysMenu;
|
||||
import org.dromara.system.domain.SysRole;
|
||||
import org.dromara.system.domain.SysRoleMenu;
|
||||
import org.dromara.system.domain.SysUserRole;
|
||||
import org.dromara.system.domain.bo.SysMenuBo;
|
||||
import org.dromara.system.domain.vo.SysMenuVo;
|
||||
import org.dromara.system.domain.vo.SysRoleMenuPermVo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -16,67 +23,7 @@ import java.util.*;
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
||||
|
||||
/**
|
||||
* 构建用户权限菜单 SQL
|
||||
*
|
||||
* <p>
|
||||
* 查询用户所属角色所拥有的菜单权限,用于权限判断、菜单加载等场景
|
||||
* </p>
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return SQL 字符串,用于 inSql 条件
|
||||
*/
|
||||
default String buildMenuByUserSql(Long userId) {
|
||||
return """
|
||||
select menu_id from sys_role_menu where role_id in (
|
||||
select sur.role_id from sys_user_role sur
|
||||
left join sys_role sr on sr.role_id = sur.role_id
|
||||
where sur.user_id = %d and sr.status = '0'
|
||||
)
|
||||
""".formatted(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建角色对应的菜单ID SQL 子查询
|
||||
*
|
||||
* <p>
|
||||
* 用于根据角色ID查询其所拥有的菜单权限(用于权限标识、菜单显示等场景)
|
||||
* 通常配合 inSql 使用
|
||||
* </p>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 查询菜单ID的 SQL 子查询字符串
|
||||
*/
|
||||
default String buildMenuByRoleSql(Long roleId) {
|
||||
return """
|
||||
select srm.menu_id from sys_role_menu srm
|
||||
left join sys_role sr on sr.role_id = srm.role_id
|
||||
where srm.role_id = %d and sr.status = '0'
|
||||
""".formatted(roleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建角色所关联菜单的父菜单ID查询 SQL
|
||||
*
|
||||
* <p>
|
||||
* 用于配合菜单勾选树结构的 {@code menuCheckStrictly} 模式,过滤掉非叶子节点(父菜单),
|
||||
* 只返回角色实际勾选的末级菜单
|
||||
* </p>
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return SQL 语句字符串(查询菜单的父菜单ID)
|
||||
*/
|
||||
default String buildParentMenuByRoleSql(Long roleId) {
|
||||
return """
|
||||
select parent_id from sys_menu where menu_id in (
|
||||
select srm.menu_id from sys_role_menu srm
|
||||
left join sys_role sr on sr.role_id = srm.role_id
|
||||
where srm.role_id = %d and sr.status = '0'
|
||||
)
|
||||
""".formatted(roleId);
|
||||
}
|
||||
public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo>, MPJBaseMapper<SysMenu> {
|
||||
|
||||
/**
|
||||
* 根据用户ID查询权限
|
||||
@@ -85,13 +32,16 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
||||
* @return 权限列表
|
||||
*/
|
||||
default Set<String> selectMenuPermsByUserId(Long userId) {
|
||||
List<String> list = this.selectObjs(
|
||||
new LambdaQueryWrapper<SysMenu>()
|
||||
.select(SysMenu::getPerms)
|
||||
.inSql(SysMenu::getMenuId, this.buildMenuByUserSql(userId))
|
||||
.isNotNull(SysMenu::getPerms)
|
||||
);
|
||||
return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
|
||||
List<SysMenu> list = this.selectJoinList(SysMenu.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.select(SysMenu::getPerms)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysUserRole.class, "sur", SysUserRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.eq("sur", SysUserRole::getUserId, userId)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.isNotNull("m", SysMenu::getPerms));
|
||||
return new HashSet<>(StreamUtils.filter(StreamUtils.toList(list, SysMenu::getPerms), StringUtils::isNotBlank));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,13 +51,15 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
||||
* @return 权限列表
|
||||
*/
|
||||
default Set<String> selectMenuPermsByRoleId(Long roleId) {
|
||||
List<String> list = this.selectObjs(
|
||||
new LambdaQueryWrapper<SysMenu>()
|
||||
.select(SysMenu::getPerms)
|
||||
.inSql(SysMenu::getMenuId, this.buildMenuByRoleSql(roleId))
|
||||
.isNotNull(SysMenu::getPerms)
|
||||
);
|
||||
return new HashSet<>(StreamUtils.filter(list, StringUtils::isNotBlank));
|
||||
List<SysMenu> list = this.selectJoinList(SysMenu.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.select(SysMenu::getPerms)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.eq("srm", SysRoleMenu::getRoleId, roleId)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.isNotNull("m", SysMenu::getPerms));
|
||||
return new HashSet<>(StreamUtils.filter(StreamUtils.toList(list, SysMenu::getPerms), StringUtils::isNotBlank));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,12 +68,26 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
||||
* @param roleIds 角色ID列表
|
||||
* @return 角色权限映射
|
||||
*/
|
||||
default Map<Long, Set<String>> selectMenuPermsByRoleIds(List<Long> roleIds) {
|
||||
default Map<Long, Set<String>> selectMenuPermsByRoleIds(Collection<Long> roleIds) {
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return Map.of();
|
||||
}
|
||||
List<SysRoleMenuPermVo> list = this.selectJoinList(SysRoleMenuPermVo.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.selectAs("srm", SysRoleMenu::getRoleId, SysRoleMenuPermVo::getRoleId)
|
||||
.selectAs(SysMenu::getPerms, SysRoleMenuPermVo::getPerms)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.in("srm", SysRoleMenu::getRoleId, roleIds)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.isNotNull("m", SysMenu::getPerms));
|
||||
Map<Long, Set<String>> result = new LinkedHashMap<>();
|
||||
roleIds.forEach(roleId -> result.put(roleId, this.selectMenuPermsByRoleId(roleId)));
|
||||
for (SysRoleMenuPermVo item : list) {
|
||||
if (StringUtils.isBlank(item.getPerms())) {
|
||||
continue;
|
||||
}
|
||||
result.computeIfAbsent(item.getRoleId(), key -> new LinkedHashSet<>()).add(item.getPerms());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -147,15 +113,53 @@ public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
|
||||
* @return 选中菜单列表
|
||||
*/
|
||||
default List<Long> selectMenuListByRoleId(Long roleId, boolean menuCheckStrictly) {
|
||||
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(SysMenu::getMenuId)
|
||||
.inSql(SysMenu::getMenuId, buildMenuByRoleSql(roleId))
|
||||
.orderByAsc(SysMenu::getParentId)
|
||||
.orderByAsc(SysMenu::getOrderNum);
|
||||
if (menuCheckStrictly) {
|
||||
wrapper.notInSql(SysMenu::getMenuId, this.buildParentMenuByRoleSql(roleId));
|
||||
}
|
||||
return this.selectObjs(wrapper);
|
||||
List<SysMenu> menus = this.selectJoinList(SysMenu.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.select(SysMenu::getMenuId, SysMenu::getParentId, SysMenu::getOrderNum)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.eq("srm", SysRoleMenu::getRoleId, roleId)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.orderByAsc("m", SysMenu::getParentId)
|
||||
.orderByAsc("m", SysMenu::getOrderNum));
|
||||
Set<Long> parentIds = menuCheckStrictly ? new HashSet<>(StreamUtils.toList(menus, SysMenu::getParentId)) : Collections.emptySet();
|
||||
return menus.stream()
|
||||
.map(SysMenu::getMenuId)
|
||||
.filter(menuId -> !parentIds.contains(menuId))
|
||||
.toList();
|
||||
}
|
||||
|
||||
default List<SysMenuVo> selectMenuListByUserId(SysMenuBo menu, Long userId) {
|
||||
return this.selectJoinList(SysMenuVo.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.selectAll(SysMenu.class)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysUserRole.class, "sur", SysUserRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.eq("sur", SysUserRole::getUserId, userId)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.like(StringUtils.isNotBlank(menu.getMenuName()), "m", SysMenu::getMenuName, menu.getMenuName())
|
||||
.eq(StringUtils.isNotBlank(menu.getVisible()), "m", SysMenu::getVisible, menu.getVisible())
|
||||
.eq(StringUtils.isNotBlank(menu.getStatus()), "m", SysMenu::getStatus, menu.getStatus())
|
||||
.eq(StringUtils.isNotBlank(menu.getMenuType()), "m", SysMenu::getMenuType, menu.getMenuType())
|
||||
.eq(Objects.nonNull(menu.getParentId()), "m", SysMenu::getParentId, menu.getParentId())
|
||||
.orderByAsc("m", SysMenu::getParentId)
|
||||
.orderByAsc("m", SysMenu::getOrderNum));
|
||||
}
|
||||
|
||||
default List<SysMenu> selectMenuTreeByUserId(Long userId) {
|
||||
return this.selectJoinList(SysMenu.class, JoinWrappers.lambda("m", SysMenu.class)
|
||||
.distinct()
|
||||
.selectAll(SysMenu.class)
|
||||
.leftJoin(SysRoleMenu.class, "srm", SysRoleMenu::getMenuId, SysMenu::getMenuId)
|
||||
.leftJoin(SysUserRole.class, "sur", SysUserRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.leftJoin(SysRole.class, "sr", SysRole::getRoleId, SysRoleMenu::getRoleId)
|
||||
.eq("sur", SysUserRole::getUserId, userId)
|
||||
.eq("sr", SysRole::getStatus, SystemConstants.NORMAL)
|
||||
.in("m", SysMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
|
||||
.eq("m", SysMenu::getStatus, SystemConstants.NORMAL)
|
||||
.orderByAsc("m", SysMenu::getParentId)
|
||||
.orderByAsc("m", SysMenu::getOrderNum));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ package org.dromara.system.mapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.system.domain.SysPost;
|
||||
import org.dromara.system.domain.SysUserPost;
|
||||
import org.dromara.system.domain.vo.SysPostVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -16,7 +20,7 @@ import java.util.List;
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
|
||||
public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo>, MPJBaseMapper<SysPost> {
|
||||
|
||||
/**
|
||||
* 分页查询岗位列表
|
||||
@@ -57,7 +61,7 @@ public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "create_by")
|
||||
})
|
||||
default long selectPostCount(List<Long> postIds) {
|
||||
default long selectPostCount(Collection<Long> postIds) {
|
||||
return this.selectCount(new LambdaQueryWrapper<SysPost>().in(SysPost::getPostId, postIds));
|
||||
}
|
||||
|
||||
@@ -68,8 +72,10 @@ public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
|
||||
* @return 岗位信息列表
|
||||
*/
|
||||
default List<SysPostVo> selectPostsByUserId(Long userId) {
|
||||
return this.selectVoList(new LambdaQueryWrapper<SysPost>()
|
||||
.inSql(SysPost::getPostId, "select post_id from sys_user_post where user_id = " + userId));
|
||||
return this.selectJoinList(SysPostVo.class, JoinWrappers.lambda("p", SysPost.class)
|
||||
.selectAll(SysPost.class)
|
||||
.leftJoin(SysUserPost.class, "sup", SysUserPost::getPostId, SysPost::getPostId)
|
||||
.eq("sup", SysUserPost::getUserId, userId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,13 +4,17 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.system.domain.SysRole;
|
||||
import org.dromara.system.domain.SysUserRole;
|
||||
import org.dromara.system.domain.vo.SysRoleVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -18,19 +22,7 @@ import java.util.List;
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo> {
|
||||
|
||||
/**
|
||||
* 构建根据用户ID查询角色ID的SQL子查询
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 查询用户对应角色ID的SQL语句字符串
|
||||
*/
|
||||
default String buildRoleByUserSql(Long userId) {
|
||||
return """
|
||||
select role_id from sys_user_role where user_id = %d
|
||||
""".formatted(userId);
|
||||
}
|
||||
public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo>, MPJBaseMapper<SysRole> {
|
||||
|
||||
/**
|
||||
* 分页查询角色列表
|
||||
@@ -71,7 +63,7 @@ public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo> {
|
||||
@DataColumn(key = "deptName", value = "create_dept"),
|
||||
@DataColumn(key = "userName", value = "create_by")
|
||||
})
|
||||
default long selectRoleCount(List<Long> roleIds) {
|
||||
default long selectRoleCount(Collection<Long> roleIds) {
|
||||
return this.selectCount(new LambdaQueryWrapper<SysRole>().in(SysRole::getRoleId, roleIds));
|
||||
}
|
||||
|
||||
@@ -96,10 +88,11 @@ public interface SysRoleMapper extends BaseMapperPlus<SysRole, SysRoleVo> {
|
||||
* @return 角色列表
|
||||
*/
|
||||
default List<SysRoleVo> selectRolesByUserId(Long userId) {
|
||||
return this.selectVoList(new LambdaQueryWrapper<SysRole>()
|
||||
return this.selectJoinList(SysRoleVo.class, JoinWrappers.lambda("r", SysRole.class)
|
||||
.select(SysRole::getRoleId, SysRole::getRoleName, SysRole::getRoleKey,
|
||||
SysRole::getRoleSort, SysRole::getDataScope, SysRole::getStatus)
|
||||
.inSql(SysRole::getRoleId, this.buildRoleByUserSql(userId)));
|
||||
.leftJoin(SysUserRole.class, "sur", SysUserRole::getRoleId, SysRole::getRoleId)
|
||||
.eq("sur", SysUserRole::getUserId, userId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.system.domain.SysRoleMenu;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 角色与菜单关联表 数据层
|
||||
@@ -19,7 +19,7 @@ public interface SysRoleMenuMapper extends BaseMapperPlus<SysRoleMenu, SysRoleMe
|
||||
* @param menuIds 菜单ID串
|
||||
* @return 结果
|
||||
*/
|
||||
default int deleteByMenuIds(List<Long> menuIds) {
|
||||
default int deleteByMenuIds(Collection<Long> menuIds) {
|
||||
return this.delete(new LambdaUpdateWrapper<SysRoleMenu>().in(SysRoleMenu::getMenuId, menuIds));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -27,7 +28,7 @@ import java.util.List;
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo>, MPJBaseMapper<SysUser> {
|
||||
|
||||
/**
|
||||
* 分页查询用户列表,并进行数据权限控制
|
||||
@@ -61,7 +62,8 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
/**
|
||||
* 根据条件分页查询用户列表
|
||||
*
|
||||
* @param queryWrapper 查询条件
|
||||
* @param user 查询条件
|
||||
* @param deptIds 部门ID集合
|
||||
* @return 用户信息集合信息
|
||||
*/
|
||||
@DataPermission({
|
||||
@@ -71,7 +73,6 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
default List<SysUserExportVo> selectUserExportList(SysUserBo user, List<Long> deptIds) {
|
||||
MPJLambdaWrapper<SysUser> wrapper = JoinWrappers.lambda("u", SysUser.class)
|
||||
.selectAll(SysUser.class)
|
||||
.selectAs(SysDept::getDeptName, SysUserExportVo::getDeptName)
|
||||
.selectAs("u1", SysUser::getUserName, SysUserExportVo::getLeaderName)
|
||||
.leftJoin(SysDept.class, "d", SysDept::getDeptId, SysUser::getDeptId)
|
||||
.leftJoin(SysUser.class, "u1", SysUser::getUserId, SysDept::getLeader)
|
||||
@@ -91,7 +92,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
* 根据条件分页查询已配用户角色列表
|
||||
*
|
||||
* @param page 分页信息
|
||||
* @param queryWrapper 查询条件
|
||||
* @param user 查询条件
|
||||
* @return 用户信息集合信息
|
||||
*/
|
||||
@DataPermission({
|
||||
@@ -108,7 +109,9 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
/**
|
||||
* 根据条件分页查询未分配用户角色列表
|
||||
*
|
||||
* @param queryWrapper 查询条件
|
||||
* @param page 分页信息
|
||||
* @param user 查询条件
|
||||
* @param userIds 未分配用户角色的用户ID列表
|
||||
* @return 用户信息集合信息
|
||||
*/
|
||||
@DataPermission({
|
||||
@@ -163,7 +166,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
|
||||
})
|
||||
int updateById(@Param(Constants.ENTITY) SysUser user);
|
||||
|
||||
private MPJLambdaWrapper<SysUser> buildUserRoleJoinWrapper(SysUserBo user) {
|
||||
default MPJLambdaWrapper<SysUser> buildUserRoleJoinWrapper(SysUserBo user) {
|
||||
return JoinWrappers.lambda("u", SysUser.class)
|
||||
.distinct()
|
||||
.selectAll(SysUser.class)
|
||||
|
||||
@@ -108,4 +108,14 @@ public interface FlowConstant {
|
||||
*/
|
||||
String VAR_IGNORE_COOPERATE = "ignoreCooperate";
|
||||
|
||||
/**
|
||||
* 未删除(正常数据)
|
||||
*/
|
||||
Integer NOT_DELETED = 0;
|
||||
|
||||
/**
|
||||
* 已删除(逻辑删除)
|
||||
*/
|
||||
Integer DELETED = 1;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.dromara.workflow.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
@@ -14,16 +16,20 @@ import org.dromara.workflow.domain.bo.FlowTaskBo;
|
||||
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.dromara.workflow.common.constant.FlowConstant.NOT_DELETED;
|
||||
|
||||
/**
|
||||
* 历史任务查询 Mapper
|
||||
*/
|
||||
public interface FlwHisTaskMapper extends BaseMapperPlus<FlowHisTask, FlowHisTaskVo> {
|
||||
public interface FlwHisTaskMapper extends BaseMapperPlus<FlowHisTask, FlowHisTaskVo>, MPJBaseMapper<FlowHisTask> {
|
||||
|
||||
default Page<FlowHisTaskVo> getListFinishTask(Page<FlowHisTaskVo> page,
|
||||
FlowTaskBo bo,
|
||||
List<String> categoryIds,
|
||||
String userId) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
MPJLambdaWrapper<FlowHisTask> wrapper = JoinWrappers.lambda("a", FlowHisTask.class)
|
||||
.selectAs(FlowHisTask::getId, FlowHisTaskVo::getId)
|
||||
.selectAs(FlowHisTask::getNodeCode, FlowHisTaskVo::getNodeCode)
|
||||
@@ -55,17 +61,18 @@ public interface FlwHisTaskMapper extends BaseMapperPlus<FlowHisTask, FlowHisTas
|
||||
.leftJoin(FlowInstance.class, "b", FlowInstance::getId, FlowHisTask::getInstanceId)
|
||||
.leftJoin(FlowDefinition.class, "c", FlowDefinition::getId, FlowHisTask::getDefinitionId)
|
||||
.leftJoin(FlowInstanceBizExt.class, "biz", FlowInstanceBizExt::getInstanceId, FlowInstance::getId)
|
||||
.eq("a", FlowHisTask::getDelFlag, "0")
|
||||
.eq("b", FlowInstance::getDelFlag, "0")
|
||||
.eq("c", FlowDefinition::getDelFlag, "0")
|
||||
.eq("a", FlowHisTask::getDelFlag, NOT_DELETED)
|
||||
.eq("b", FlowInstance::getDelFlag, NOT_DELETED)
|
||||
.eq("c", FlowDefinition::getDelFlag, NOT_DELETED)
|
||||
.in("a", FlowHisTask::getNodeType, List.of("1", "3", "4"))
|
||||
.like(hasText(bo.getNodeName()), "a", FlowHisTask::getNodeName, bo.getNodeName())
|
||||
.like(hasText(bo.getFlowName()), "c", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(hasText(bo.getFlowCode()), "c", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(hasText(bo.getFlowStatus()), "b", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(hasItems(bo.getCreateByIds()), "b", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(hasItems(categoryIds), "c", FlowDefinition::getCategory, categoryIds)
|
||||
.between(hasBetween(bo), "a", FlowHisTask::getCreateTime, bo.getParams().get("beginTime"), bo.getParams().get("endTime"))
|
||||
.like(StringUtils.isNotBlank(bo.getNodeName()), "a", FlowHisTask::getNodeName, bo.getNodeName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowName()), "c", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowCode()), "c", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowStatus()), "b", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(CollUtil.isNotEmpty(bo.getCreateByIds()), "b", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(CollUtil.isNotEmpty(categoryIds), "c", FlowDefinition::getCategory, categoryIds)
|
||||
.between(params.get("beginTime") != null && params.get("endTime") != null,
|
||||
"a", FlowHisTask::getCreateTime, params.get("beginTime"), params.get("endTime"))
|
||||
.eq(StringUtils.isNotBlank(userId), "a", FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
|
||||
.eq(StringUtils.isNotBlank(userId), "a", FlowHisTask::getApprover, userId)
|
||||
.orderByDesc("a", FlowHisTask::getCreateTime)
|
||||
@@ -73,16 +80,4 @@ public interface FlwHisTaskMapper extends BaseMapperPlus<FlowHisTask, FlowHisTas
|
||||
return wrapper.page(page, FlowHisTaskVo.class);
|
||||
}
|
||||
|
||||
default boolean hasText(String value) {
|
||||
return StringUtils.isNotBlank(value);
|
||||
}
|
||||
|
||||
default boolean hasItems(List<?> values) {
|
||||
return values != null && !values.isEmpty();
|
||||
}
|
||||
|
||||
default boolean hasBetween(FlowTaskBo bo) {
|
||||
return bo != null && bo.getParams() != null && bo.getParams().get("beginTime") != null && bo.getParams().get("endTime") != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.dromara.workflow.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.dromara.common.core.enums.BusinessStatusEnum;
|
||||
@@ -16,6 +18,9 @@ import org.dromara.workflow.domain.bo.FlowTaskBo;
|
||||
import org.dromara.workflow.domain.vo.FlowTaskVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.dromara.workflow.common.constant.FlowConstant.NOT_DELETED;
|
||||
|
||||
/**
|
||||
* 任务信息Mapper接口
|
||||
@@ -23,12 +28,13 @@ import java.util.List;
|
||||
* @author may
|
||||
* @date 2024-03-02
|
||||
*/
|
||||
public interface FlwTaskMapper extends BaseMapperPlus<FlowTask, FlowTaskVo> {
|
||||
public interface FlwTaskMapper extends BaseMapperPlus<FlowTask, FlowTaskVo>, MPJBaseMapper<FlowTask> {
|
||||
|
||||
default Page<FlowTaskVo> getListRunTask(Page<FlowTaskVo> page,
|
||||
FlowTaskBo bo,
|
||||
List<String> categoryIds,
|
||||
String userId) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
MPJLambdaWrapper<FlowTask> wrapper = JoinWrappers.lambda("t", FlowTask.class)
|
||||
.distinct()
|
||||
.selectAs(FlowTask::getId, FlowTaskVo::getId)
|
||||
@@ -57,16 +63,17 @@ public interface FlwTaskMapper extends BaseMapperPlus<FlowTask, FlowTaskVo> {
|
||||
.leftJoin(FlowInstance.class, "i", FlowInstance::getId, FlowTask::getInstanceId)
|
||||
.leftJoin(FlowInstanceBizExt.class, "biz", FlowInstanceBizExt::getInstanceId, FlowInstance::getId)
|
||||
.eq("t", FlowTask::getNodeType, NodeType.BETWEEN.getKey())
|
||||
.eq("t", FlowTask::getDelFlag, "0")
|
||||
.eq("uu", FlowUser::getDelFlag, "0")
|
||||
.eq("t", FlowTask::getDelFlag, NOT_DELETED)
|
||||
.eq("uu", FlowUser::getDelFlag, NOT_DELETED)
|
||||
.in("uu", FlowUser::getType, List.of("1", "2", "3"))
|
||||
.like(hasText(bo.getNodeName()), "t", FlowTask::getNodeName, bo.getNodeName())
|
||||
.like(hasText(bo.getFlowName()), "d", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(hasText(bo.getFlowCode()), "d", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(hasText(bo.getFlowStatus()), "i", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(hasItems(bo.getCreateByIds()), "i", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(hasItems(categoryIds), "d", FlowDefinition::getCategory, categoryIds)
|
||||
.between(hasBetween(bo), "t", FlowTask::getCreateTime, bo.getParams().get("beginTime"), bo.getParams().get("endTime"))
|
||||
.like(StringUtils.isNotBlank(bo.getNodeName()), "t", FlowTask::getNodeName, bo.getNodeName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowName()), "d", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowCode()), "d", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowStatus()), "i", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(CollUtil.isNotEmpty(bo.getCreateByIds()), "i", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(CollUtil.isNotEmpty(categoryIds), "d", FlowDefinition::getCategory, categoryIds)
|
||||
.between(params.get("beginTime") != null && params.get("endTime") != null,
|
||||
"t", FlowTask::getCreateTime, params.get("beginTime"), params.get("endTime"))
|
||||
.eq(StringUtils.isNotBlank(userId), "uu", FlowUser::getProcessedBy, userId)
|
||||
.eq(StringUtils.isNotBlank(userId), "i", FlowInstance::getFlowStatus, BusinessStatusEnum.WAITING.getStatus())
|
||||
.orderByDesc("t", FlowTask::getCreateTime)
|
||||
@@ -74,16 +81,4 @@ public interface FlwTaskMapper extends BaseMapperPlus<FlowTask, FlowTaskVo> {
|
||||
return wrapper.page(page, FlowTaskVo.class);
|
||||
}
|
||||
|
||||
default boolean hasText(String value) {
|
||||
return StringUtils.isNotBlank(value);
|
||||
}
|
||||
|
||||
default boolean hasItems(List<?> values) {
|
||||
return values != null && !values.isEmpty();
|
||||
}
|
||||
|
||||
default boolean hasBetween(FlowTaskBo bo) {
|
||||
return bo != null && bo.getParams() != null && bo.getParams().get("beginTime") != null && bo.getParams().get("endTime") != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.dromara.workflow.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.github.yulichang.toolkit.JoinWrappers;
|
||||
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
@@ -14,16 +16,20 @@ import org.dromara.workflow.domain.bo.FlowTaskBo;
|
||||
import org.dromara.workflow.domain.vo.FlowTaskVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.dromara.workflow.common.constant.FlowConstant.NOT_DELETED;
|
||||
|
||||
/**
|
||||
* 流程用户查询 Mapper
|
||||
*/
|
||||
public interface FlwUserMapper extends BaseMapperPlus<FlowUser, FlowUser> {
|
||||
public interface FlwUserMapper extends BaseMapperPlus<FlowUser, FlowUser>, MPJBaseMapper<FlowUser> {
|
||||
|
||||
default Page<FlowTaskVo> getTaskCopyByPage(Page<FlowTaskVo> page,
|
||||
FlowTaskBo bo,
|
||||
List<String> categoryIds,
|
||||
String userId) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
MPJLambdaWrapper<FlowUser> wrapper = JoinWrappers.lambda("a", FlowUser.class)
|
||||
.selectAs("b", FlowHisTask::getId, FlowTaskVo::getId)
|
||||
.selectAs("b", FlowHisTask::getUpdateTime, FlowTaskVo::getUpdateTime)
|
||||
@@ -47,32 +53,21 @@ public interface FlwUserMapper extends BaseMapperPlus<FlowUser, FlowUser> {
|
||||
.leftJoin(FlowDefinition.class, "d", FlowDefinition::getId, FlowInstance::getDefinitionId)
|
||||
.leftJoin(FlowInstanceBizExt.class, "biz", FlowInstanceBizExt::getInstanceId, FlowInstance::getId)
|
||||
.eq("a", FlowUser::getType, "4")
|
||||
.eq("a", FlowUser::getDelFlag, "0")
|
||||
.eq("b", FlowHisTask::getDelFlag, "0")
|
||||
.eq("d", FlowDefinition::getDelFlag, "0")
|
||||
.like(hasText(bo.getNodeName()), "b", FlowHisTask::getNodeName, bo.getNodeName())
|
||||
.like(hasText(bo.getFlowName()), "d", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(hasText(bo.getFlowCode()), "d", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(hasText(bo.getFlowStatus()), "c", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(hasItems(bo.getCreateByIds()), "c", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(hasItems(categoryIds), "d", FlowDefinition::getCategory, categoryIds)
|
||||
.between(hasBetween(bo), "a", FlowUser::getCreateTime, bo.getParams().get("beginTime"), bo.getParams().get("endTime"))
|
||||
.eq("a", FlowUser::getDelFlag, NOT_DELETED)
|
||||
.eq("b", FlowHisTask::getDelFlag, NOT_DELETED)
|
||||
.eq("d", FlowDefinition::getDelFlag, NOT_DELETED)
|
||||
.like(StringUtils.isNotBlank(bo.getNodeName()), "b", FlowHisTask::getNodeName, bo.getNodeName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowName()), "d", FlowDefinition::getFlowName, bo.getFlowName())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowCode()), "d", FlowDefinition::getFlowCode, bo.getFlowCode())
|
||||
.like(StringUtils.isNotBlank(bo.getFlowStatus()), "c", FlowInstance::getFlowStatus, bo.getFlowStatus())
|
||||
.in(CollUtil.isNotEmpty(bo.getCreateByIds()), "c", FlowInstance::getCreateBy, bo.getCreateByIds())
|
||||
.in(CollUtil.isNotEmpty(categoryIds), "d", FlowDefinition::getCategory, categoryIds)
|
||||
.between(params.get("beginTime") != null && params.get("endTime") != null,
|
||||
"a", FlowUser::getCreateTime, params.get("beginTime"), params.get("endTime"))
|
||||
.eq(StringUtils.isNotBlank(userId), "a", FlowUser::getProcessedBy, userId)
|
||||
.orderByDesc("a", FlowUser::getCreateTime)
|
||||
.orderByDesc("b", FlowHisTask::getUpdateTime);
|
||||
return wrapper.page(page, FlowTaskVo.class);
|
||||
}
|
||||
|
||||
default boolean hasText(String value) {
|
||||
return StringUtils.isNotBlank(value);
|
||||
}
|
||||
|
||||
default boolean hasItems(List<?> values) {
|
||||
return values != null && !values.isEmpty();
|
||||
}
|
||||
|
||||
default boolean hasBetween(FlowTaskBo bo) {
|
||||
return bo != null && bo.getParams() != null && bo.getParams().get("beginTime") != null && bo.getParams().get("endTime") != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user