refactor: 使用事件解耦业务副作用并优化虚拟线程并发
- 新增登录成功事件,解耦在线用户缓存、登录日志和最近登录信息更新 - 新增在线用户清理事件,角色授权变更后异步清理受影响用户会话 - 新增 OSS 配置变更事件,统一处理配置初始化、删除和默认配置切换后的缓存刷新 - 新增工作流抄送、待办消息、结果消息事件,拆分全局监听器中的副作用逻辑 - 扩展 ThreadUtils 支持带返回值的虚拟线程批量执行 - 在线用户监控批量读取 Redis 会话信息时使用虚拟线程并发处理 - OSS 多文件查询、URL 解析和 DTO 转换使用虚拟线程并发处理 - 工作流办理人回显和用户解析按类型并发查询 - 工作流多渠道消息发送改为并发执行 - WebSocket 与 SSE 广播消息按用户并发发送
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package org.dromara.auth.event;
|
||||
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
|
||||
/**
|
||||
* 用户登录成功事件。
|
||||
*
|
||||
* @param loginId 登录标识
|
||||
* @param tokenValue token 值
|
||||
* @param loginParameter 登录参数
|
||||
*/
|
||||
public record UserLoginSuccessEvent(Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
}
|
||||
@@ -1,79 +1,30 @@
|
||||
package org.dromara.auth.listener;
|
||||
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ip.AddressUtils;
|
||||
import org.dromara.common.log.event.LoginInfoEvent;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.resource.api.RemoteMessageService;
|
||||
import org.dromara.system.api.RemoteUserService;
|
||||
import org.dromara.system.api.domain.SysUserOnline;
|
||||
import org.dromara.auth.event.UserLoginSuccessEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 用户行为 侦听器的实现
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserActionListener implements SaTokenListener {
|
||||
|
||||
@DubboReference
|
||||
private RemoteUserService remoteUserService;
|
||||
@DubboReference
|
||||
private RemoteMessageService remoteMessageService;
|
||||
|
||||
/**
|
||||
* 每次登录时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtils.getClientIP();
|
||||
SysUserOnline userOnline = new SysUserOnline();
|
||||
userOnline.setIpaddr(ip);
|
||||
userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||
userOnline.setBrowser(userAgent.getBrowser().getName());
|
||||
userOnline.setOs(userAgent.getOs().getName());
|
||||
userOnline.setLoginTime(System.currentTimeMillis());
|
||||
userOnline.setTokenId(tokenValue);
|
||||
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
userOnline.setUserName(username);
|
||||
userOnline.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
|
||||
userOnline.setDeviceType(loginParameter.getDeviceType());
|
||||
userOnline.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||
if (loginParameter.getTimeout() == -1) {
|
||||
RedisUtils.setCacheObject(CacheNames.ONLINE_TOKEN_KEY + tokenValue, userOnline);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(CacheNames.ONLINE_TOKEN_KEY + tokenValue, userOnline, Duration.ofSeconds(loginParameter.getTimeout()));
|
||||
}
|
||||
// 记录登录日志
|
||||
LoginInfoEvent loginInfoEvent = new LoginInfoEvent();
|
||||
loginInfoEvent.setUsername(username);
|
||||
loginInfoEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||
loginInfoEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||
SpringUtils.context().publishEvent(loginInfoEvent);
|
||||
// 更新登录信息
|
||||
remoteUserService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
|
||||
log.info("user doLogin, useId:{}, token:***{}", loginId, StringUtils.right(tokenValue, 8));
|
||||
SpringUtils.context().publishEvent(new UserLoginSuccessEvent(loginId, tokenValue, loginParameter));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.dromara.auth.listener;
|
||||
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.dromara.auth.event.UserLoginSuccessEvent;
|
||||
import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ip.AddressUtils;
|
||||
import org.dromara.common.log.event.LoginInfoEvent;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.system.api.RemoteUserService;
|
||||
import org.dromara.system.api.domain.SysUserOnline;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 用户登录成功监听器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserLoginSuccessListener {
|
||||
|
||||
@DubboReference
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
/**
|
||||
* 登录成功后记录在线信息、登录日志与最近登录信息。
|
||||
*
|
||||
* @param event 用户登录成功事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleLoginSuccess(UserLoginSuccessEvent event) {
|
||||
SaLoginParameter loginParameter = event.loginParameter();
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtils.getClientIP();
|
||||
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
String tokenValue = event.tokenValue();
|
||||
|
||||
SysUserOnline userOnline = new SysUserOnline();
|
||||
userOnline.setIpaddr(ip);
|
||||
userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
|
||||
userOnline.setBrowser(userAgent.getBrowser().getName());
|
||||
userOnline.setOs(userAgent.getOs().getName());
|
||||
userOnline.setLoginTime(System.currentTimeMillis());
|
||||
userOnline.setTokenId(tokenValue);
|
||||
userOnline.setUserName(username);
|
||||
userOnline.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
|
||||
userOnline.setDeviceType(loginParameter.getDeviceType());
|
||||
userOnline.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||
if (loginParameter.getTimeout() == -1) {
|
||||
RedisUtils.setCacheObject(CacheNames.ONLINE_TOKEN_KEY + tokenValue, userOnline);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(CacheNames.ONLINE_TOKEN_KEY + tokenValue, userOnline, Duration.ofSeconds(loginParameter.getTimeout()));
|
||||
}
|
||||
|
||||
LoginInfoEvent loginInfoEvent = new LoginInfoEvent();
|
||||
loginInfoEvent.setUsername(username);
|
||||
loginInfoEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||
loginInfoEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||
SpringUtils.context().publishEvent(loginInfoEvent);
|
||||
|
||||
remoteUserService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
|
||||
log.info("user doLogin, userId:{}, token:***{}", event.loginId(), StringUtils.right(tokenValue, 8));
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -4,8 +4,10 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 线程工具
|
||||
@@ -47,4 +49,43 @@ public class ThreadUtils {
|
||||
virtualInvokeAll(runnableList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行有返回值的任务
|
||||
*
|
||||
* @param supplierList 任务列表
|
||||
* @param <T> 返回值类型
|
||||
* @return 按提交顺序返回的任务结果
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> List<T> virtualSubmitAll(Supplier<T>... supplierList) {
|
||||
return virtualSubmitAll(List.of(supplierList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量执行有返回值的任务
|
||||
*
|
||||
* @param supplierList 任务列表
|
||||
* @param <T> 返回值类型
|
||||
* @return 按提交顺序返回的任务结果
|
||||
*/
|
||||
public static <T> List<T> virtualSubmitAll(Collection<? extends Supplier<T>> supplierList) {
|
||||
List<Future<T>> futureList = new ArrayList<>();
|
||||
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (Supplier<T> supplier : supplierList) {
|
||||
futureList.add(executor.submit(supplier::get));
|
||||
}
|
||||
List<T> resultList = new ArrayList<>(futureList.size());
|
||||
for (Future<T> future : futureList) {
|
||||
resultList.add(future.get());
|
||||
}
|
||||
return resultList;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("线程执行被中断", e);
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
throw new RuntimeException("线程执行异常:" + cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-3
@@ -3,6 +3,7 @@ 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.core.utils.ThreadUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.push.constant.MessageConstants;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
@@ -227,9 +228,11 @@ public class SseEmitterSessionManager implements PushSessionManager {
|
||||
* @param message 要发送的消息内容
|
||||
*/
|
||||
public void sendMessage(String message) {
|
||||
for (Long userId : USER_TOKEN_EMITTERS.keySet()) {
|
||||
sendMessage(userId, message);
|
||||
}
|
||||
List<Long> userIds = new ArrayList<>(USER_TOKEN_EMITTERS.keySet());
|
||||
Runnable[] sendTasks = userIds.stream()
|
||||
.map(userId -> (Runnable) () -> sendMessage(userId, message))
|
||||
.toArray(Runnable[]::new);
|
||||
ThreadUtils.virtualInvokeAll(sendTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -3,6 +3,7 @@ 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.core.utils.ThreadUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.push.dto.PushDTO;
|
||||
import org.dromara.common.push.dto.PushPayloadDTO;
|
||||
@@ -181,7 +182,11 @@ public class WebSocketSessionManager implements PushSessionManager {
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
USER_TOKEN_SESSIONS.keySet().forEach(userId -> sendMessage(userId, payload));
|
||||
List<Long> userIds = new ArrayList<>(USER_TOKEN_SESSIONS.keySet());
|
||||
Runnable[] sendTasks = userIds.stream()
|
||||
.map(userId -> (Runnable) () -> sendMessage(userId, payload))
|
||||
.toArray(Runnable[]::new);
|
||||
ThreadUtils.virtualInvokeAll(sendTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Boot 4 下使用 jetty 替代 undertow -->
|
||||
<!-- web 容器使用 jetty -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jetty</artifactId>
|
||||
|
||||
-1
@@ -3,7 +3,6 @@ package org.dromara.common.web.config;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.enhance.JsonValueEnhancer;
|
||||
import org.dromara.common.web.advice.ResponseEnhancementAdvice;
|
||||
import org.dromara.common.web.handler.GlobalExceptionHandler;
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
//package org.dromara.common.web.config;
|
||||
//
|
||||
//import io.undertow.server.DefaultByteBufferPool;
|
||||
//import io.undertow.server.handlers.DisallowedMethodsHandler;
|
||||
//import io.undertow.util.HttpString;
|
||||
//import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
//import org.dromara.common.core.utils.SpringUtils;
|
||||
//import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
//import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
//import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
//
|
||||
///**
|
||||
// * Boot 4 迁移后默认改用 Jetty,这里暂时保留 Undertow 配置实现以便后续回切时参考。
|
||||
// */
|
||||
//@AutoConfiguration
|
||||
//public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
//
|
||||
// @Override
|
||||
// public void customize(UndertowServletWebServerFactory factory) {
|
||||
// factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
|
||||
// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
//
|
||||
// if (SpringUtils.isVirtual()) {
|
||||
// VirtualThreadTaskExecutor executor = new VirtualThreadTaskExecutor("undertow-");
|
||||
// deploymentInfo.setExecutor(executor);
|
||||
// deploymentInfo.setAsyncExecutor(executor);
|
||||
// }
|
||||
//
|
||||
// deploymentInfo.addInitialHandlerChainWrapper(handler -> {
|
||||
// HttpString[] disallowedHttpMethods = {
|
||||
// HttpString.tryFromString("CONNECT"),
|
||||
// HttpString.tryFromString("TRACE"),
|
||||
// HttpString.tryFromString("TRACK")
|
||||
// };
|
||||
// return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
//
|
||||
//}
|
||||
+2
-3
@@ -11,7 +11,6 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.exception.SseException;
|
||||
import org.dromara.common.core.exception.base.BaseException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.springframework.boot.json.JsonParseException;
|
||||
@@ -212,7 +211,7 @@ public class GlobalExceptionHandler {
|
||||
public R<Void> handleJsonParseException(JsonParseException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}' 发生 JSON 解析异常: {}", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求数据格式错误(JSON 解析失败):" + e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求数据格式错误");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +220,7 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
|
||||
log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
|
||||
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.dromara.resource.event;
|
||||
|
||||
/**
|
||||
* OSS 配置变更事件。
|
||||
*
|
||||
* @param configKey 当前配置 key
|
||||
* @param oldConfigKey 变更前配置 key
|
||||
* @param configJson 当前配置 JSON,为空表示清理缓存
|
||||
* @param defaultConfig 是否设置为默认配置
|
||||
*/
|
||||
public record OssConfigChangeEvent(
|
||||
String configKey,
|
||||
String oldConfigKey,
|
||||
String configJson,
|
||||
boolean defaultConfig
|
||||
) {
|
||||
|
||||
public static OssConfigChangeEvent save(String configKey, String oldConfigKey, String configJson) {
|
||||
return new OssConfigChangeEvent(configKey, oldConfigKey, configJson, false);
|
||||
}
|
||||
|
||||
public static OssConfigChangeEvent remove(String configKey) {
|
||||
return new OssConfigChangeEvent(configKey, null, null, false);
|
||||
}
|
||||
|
||||
public static OssConfigChangeEvent useDefault(String configKey) {
|
||||
return new OssConfigChangeEvent(configKey, null, null, true);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.dromara.resource.listener;
|
||||
|
||||
import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.oss.constant.OssConstant;
|
||||
import org.dromara.common.oss.factory.OssFactory;
|
||||
import org.dromara.common.redis.utils.CacheUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.resource.event.OssConfigChangeEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
/**
|
||||
* OSS 配置变更监听器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class OssConfigChangeListener {
|
||||
|
||||
/**
|
||||
* 数据提交后同步刷新 OSS 配置缓存与客户端实例。
|
||||
*
|
||||
* @param event OSS 配置变更事件
|
||||
*/
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
|
||||
public void refreshOssConfig(OssConfigChangeEvent event) {
|
||||
if (event.defaultConfig()) {
|
||||
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, event.configKey());
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotBlank(event.oldConfigKey()) && !StringUtils.equals(event.oldConfigKey(), event.configKey())) {
|
||||
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, event.oldConfigKey());
|
||||
OssFactory.remove(event.oldConfigKey());
|
||||
}
|
||||
if (StringUtils.isBlank(event.configKey())) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(event.configJson())) {
|
||||
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, event.configKey());
|
||||
} else {
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, event.configKey(), event.configJson());
|
||||
}
|
||||
OssFactory.remove(event.configKey());
|
||||
}
|
||||
|
||||
}
|
||||
+21
-13
@@ -13,6 +13,7 @@ import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
@@ -24,6 +25,7 @@ import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.resource.domain.SysOssConfig;
|
||||
import org.dromara.resource.domain.bo.SysOssConfigBo;
|
||||
import org.dromara.resource.domain.vo.SysOssConfigVo;
|
||||
import org.dromara.resource.event.OssConfigChangeEvent;
|
||||
import org.dromara.resource.mapper.SysOssConfigMapper;
|
||||
import org.dromara.resource.service.ISysOssConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -31,7 +33,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 对象存储配置Service业务层处理
|
||||
@@ -93,7 +94,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
|
||||
if (flag) {
|
||||
// 从数据库查询完整的数据做缓存
|
||||
config = ossConfigMapper.selectById(config.getOssConfigId());
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
|
||||
publishOssConfigSaved(config, null);
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
@@ -113,12 +114,7 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
|
||||
if (flag) {
|
||||
// 从数据库查询完整的数据做缓存
|
||||
config = ossConfigMapper.selectById(config.getOssConfigId());
|
||||
if (ObjectUtil.isNotNull(oldConfig) && !Objects.equals(oldConfig.getConfigKey(), config.getConfigKey())) {
|
||||
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, oldConfig.getConfigKey());
|
||||
OssFactory.remove(oldConfig.getConfigKey());
|
||||
}
|
||||
CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
|
||||
OssFactory.remove(config.getConfigKey());
|
||||
publishOssConfigSaved(config, ObjectUtils.notNullGetter(oldConfig, SysOssConfig::getConfigKey));
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
@@ -148,10 +144,8 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
|
||||
}
|
||||
boolean flag = ossConfigMapper.deleteByIds(ids) > 0;
|
||||
if (flag) {
|
||||
list.forEach(sysOssConfig -> {
|
||||
CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey());
|
||||
OssFactory.remove(sysOssConfig.getConfigKey());
|
||||
});
|
||||
list.forEach(sysOssConfig ->
|
||||
SpringUtils.context().publishEvent(OssConfigChangeEvent.remove(sysOssConfig.getConfigKey())));
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
@@ -181,9 +175,23 @@ public class SysOssConfigServiceImpl implements ISysOssConfigService {
|
||||
.set(SysOssConfig::getStatus, SystemConstants.NO));
|
||||
row += ossConfigMapper.updateById(sysOssConfig);
|
||||
if (row > 0) {
|
||||
RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey());
|
||||
SpringUtils.context().publishEvent(OssConfigChangeEvent.useDefault(sysOssConfig.getConfigKey()));
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布 OSS 配置保存事件。
|
||||
*
|
||||
* @param config 当前配置
|
||||
* @param oldConfigKey 变更前配置 key
|
||||
*/
|
||||
private void publishOssConfigSaved(SysOssConfig config, String oldConfigKey) {
|
||||
SpringUtils.context().publishEvent(OssConfigChangeEvent.save(
|
||||
config.getConfigKey(),
|
||||
oldConfigKey,
|
||||
JsonUtils.toJsonString(config)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-10
@@ -15,6 +15,7 @@ import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ThreadUtils;
|
||||
import org.dromara.common.core.utils.file.FileUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
@@ -42,8 +43,10 @@ import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 文件上传 服务层实现
|
||||
@@ -80,19 +83,21 @@ public class SysOssServiceImpl implements ISysOssService {
|
||||
*/
|
||||
@Override
|
||||
public List<SysOssVo> listByIds(Collection<Long> ossIds) {
|
||||
List<SysOssVo> list = new ArrayList<>();
|
||||
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
|
||||
for (Long id : ossIds) {
|
||||
List<Supplier<SysOssVo>> suppliers = ossIds.stream().map(id -> (Supplier<SysOssVo>) () -> {
|
||||
SysOssVo vo = ossService.getById(id);
|
||||
if (ObjectUtil.isNotNull(vo)) {
|
||||
try {
|
||||
list.add(this.matchingUrl(vo));
|
||||
return this.matchingUrl(vo);
|
||||
} catch (Exception ignored) {
|
||||
// 如果oss异常无法连接则将数据直接返回
|
||||
list.add(vo);
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).toList();
|
||||
List<SysOssVo> list = ThreadUtils.virtualSubmitAll(suppliers);
|
||||
list.removeAll(Collections.singleton(null));
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -104,19 +109,22 @@ public class SysOssServiceImpl implements ISysOssService {
|
||||
*/
|
||||
@Override
|
||||
public String selectUrlByIds(String ossIds) {
|
||||
List<String> list = new ArrayList<>();
|
||||
List<Long> ids = StringUtils.splitTo(ossIds, Convert::toLong);
|
||||
SysOssServiceImpl ossService = SpringUtils.getAopProxy(this);
|
||||
for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
|
||||
List<Supplier<String>> suppliers = ids.stream().map(id -> (Supplier<String>) () -> {
|
||||
SysOssVo vo = ossService.getById(id);
|
||||
if (ObjectUtil.isNotNull(vo)) {
|
||||
try {
|
||||
list.add(this.matchingUrl(vo).getUrl());
|
||||
return this.matchingUrl(vo).getUrl();
|
||||
} catch (Exception ignored) {
|
||||
// 如果oss异常无法连接则将数据直接返回
|
||||
list.add(vo.getUrl());
|
||||
}
|
||||
return vo.getUrl();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).toList();
|
||||
List<String> list = ThreadUtils.virtualSubmitAll(suppliers);
|
||||
list.removeAll(Collections.singleton(null));
|
||||
return StringUtils.joinComma(list);
|
||||
}
|
||||
|
||||
|
||||
+15
-12
@@ -9,6 +9,7 @@ import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ThreadUtils;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
@@ -18,11 +19,10 @@ import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.system.api.domain.SysUserOnline;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 在线用户监控
|
||||
@@ -45,15 +45,16 @@ public class SysUserOnlineController extends BaseController {
|
||||
public R<PageResult<SysUserOnline>> list(String ipaddr, String userName) {
|
||||
// 获取所有未过期的 token
|
||||
Collection<String> keys = RedisUtils.keys(CacheNames.ONLINE_TOKEN_KEY + "*");
|
||||
List<SysUserOnline> userOnlineDTOList = new ArrayList<>();
|
||||
for (String key : keys) {
|
||||
List<Supplier<SysUserOnline>> suppliers = keys.stream().map(key -> (Supplier<SysUserOnline>) () -> {
|
||||
String token = StringUtils.substringAfterLast(key, ":");
|
||||
// 如果已经过期则跳过
|
||||
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
|
||||
continue;
|
||||
}
|
||||
userOnlineDTOList.add(RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token));
|
||||
return null;
|
||||
}
|
||||
return RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token);
|
||||
}).toList();
|
||||
List<SysUserOnline> userOnlineDTOList = ThreadUtils.virtualSubmitAll(suppliers);
|
||||
userOnlineDTOList.removeAll(Collections.singleton(null));
|
||||
if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
|
||||
userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
|
||||
StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
|
||||
@@ -69,7 +70,6 @@ public class SysUserOnlineController extends BaseController {
|
||||
);
|
||||
}
|
||||
Collections.reverse(userOnlineDTOList);
|
||||
userOnlineDTOList.removeAll(Collections.singleton(null));
|
||||
List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
|
||||
return R.ok(PageResult.build(userOnlineList));
|
||||
}
|
||||
@@ -98,10 +98,13 @@ public class SysUserOnlineController extends BaseController {
|
||||
public R<PageResult<SysUserOnline>> getInfo() {
|
||||
// 获取指定账号 id 的 token 集合
|
||||
List<String> tokenIds = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
|
||||
List<SysUserOnline> userOnlineDTOList = tokenIds.stream()
|
||||
.filter(token -> StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) >= -1)
|
||||
.map(token -> (SysUserOnline) RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token))
|
||||
.collect(Collectors.toList());
|
||||
List<Supplier<SysUserOnline>> suppliers = tokenIds.stream().map(token -> (Supplier<SysUserOnline>) () -> {
|
||||
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
|
||||
return null;
|
||||
}
|
||||
return RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token);
|
||||
}).toList();
|
||||
List<SysUserOnline> userOnlineDTOList = ThreadUtils.virtualSubmitAll(suppliers);
|
||||
//复制和处理 SysUserOnline 对象列表
|
||||
Collections.reverse(userOnlineDTOList);
|
||||
userOnlineDTOList.removeAll(Collections.singleton(null));
|
||||
|
||||
+5
-3
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.tree.Tree;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
@@ -18,6 +19,7 @@ import org.dromara.system.domain.bo.SysRoleBo;
|
||||
import org.dromara.system.domain.bo.SysUserBo;
|
||||
import org.dromara.system.domain.vo.SysRoleVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.system.event.OnlineUserCleanEvent;
|
||||
import org.dromara.system.service.ISysDeptService;
|
||||
import org.dromara.system.service.ISysRoleService;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
@@ -111,7 +113,7 @@ public class SysRoleController extends BaseController {
|
||||
}
|
||||
|
||||
if (roleService.updateRoleBaseInfo(role) > 0) {
|
||||
roleService.cleanOnlineUserByRole(role.getRoleId());
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byRole(role.getRoleId()));
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("修改角色'" + role.getRoleName() + "'失败,请联系管理员");
|
||||
@@ -131,7 +133,7 @@ public class SysRoleController extends BaseController {
|
||||
roleService.checkRoleAllowed(role);
|
||||
roleService.checkRoleDataScope(role.getRoleId());
|
||||
if (roleService.updateRolePermission(role) > 0) {
|
||||
roleService.cleanOnlineUserByRole(role.getRoleId());
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byRole(role.getRoleId()));
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("修改角色'" + role.getRoleName() + "'权限失败,请联系管理员");
|
||||
@@ -148,7 +150,7 @@ public class SysRoleController extends BaseController {
|
||||
roleService.checkRoleAllowed(role);
|
||||
roleService.checkRoleDataScope(role.getRoleId());
|
||||
if (roleService.updateRoleStatus(role.getRoleId(), role.getStatus()) > 0) {
|
||||
roleService.cleanOnlineUserByRole(role.getRoleId());
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byRole(role.getRoleId()));
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("修改角色'" + role.getRoleName() + "'状态失败,请联系管理员");
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.dromara.system.event;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在线用户清理事件。
|
||||
*
|
||||
* @param roleId 角色 ID,存在时清理拥有该角色的在线用户
|
||||
* @param userIds 用户 ID 集合,存在时清理指定在线用户
|
||||
*/
|
||||
public record OnlineUserCleanEvent(Long roleId, Collection<Long> userIds) {
|
||||
|
||||
public OnlineUserCleanEvent {
|
||||
if (CollUtil.isNotEmpty(userIds)) {
|
||||
userIds = List.copyOf(userIds);
|
||||
}
|
||||
}
|
||||
|
||||
public static OnlineUserCleanEvent byRole(Long roleId) {
|
||||
return new OnlineUserCleanEvent(roleId, null);
|
||||
}
|
||||
|
||||
public static OnlineUserCleanEvent byUsers(Collection<Long> userIds) {
|
||||
return new OnlineUserCleanEvent(null, userIds);
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.dromara.system.listener;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.system.event.OnlineUserCleanEvent;
|
||||
import org.dromara.system.service.ISysRoleService;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在线用户清理监听器。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OnlineUserCleanListener {
|
||||
|
||||
private final ISysRoleService roleService;
|
||||
|
||||
/**
|
||||
* 权限或用户角色关系变化后清理受影响的在线用户。
|
||||
*
|
||||
* @param event 在线用户清理事件
|
||||
*/
|
||||
@Async
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
|
||||
public void cleanOnlineUser(OnlineUserCleanEvent event) {
|
||||
if (event.roleId() != null) {
|
||||
roleService.cleanOnlineUserByRole(event.roleId());
|
||||
}
|
||||
if (CollUtil.isNotEmpty(event.userIds())) {
|
||||
roleService.cleanOnlineUser(List.copyOf(event.userIds()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -15,6 +15,7 @@ import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
@@ -27,6 +28,7 @@ import org.dromara.system.domain.SysRoleMenu;
|
||||
import org.dromara.system.domain.SysUserRole;
|
||||
import org.dromara.system.domain.bo.SysRoleBo;
|
||||
import org.dromara.system.domain.vo.SysRoleVo;
|
||||
import org.dromara.system.event.OnlineUserCleanEvent;
|
||||
import org.dromara.system.mapper.SysRoleDeptMapper;
|
||||
import org.dromara.system.mapper.SysRoleMapper;
|
||||
import org.dromara.system.mapper.SysRoleMenuMapper;
|
||||
@@ -454,7 +456,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
||||
.eq(SysUserRole::getRoleId, userRole.getRoleId())
|
||||
.eq(SysUserRole::getUserId, userRole.getUserId()));
|
||||
if (rows > 0) {
|
||||
cleanOnlineUser(List.of(userRole.getUserId()));
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byUsers(List.of(userRole.getUserId())));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
@@ -476,7 +478,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
||||
.eq(SysUserRole::getRoleId, roleId)
|
||||
.in(SysUserRole::getUserId, ids));
|
||||
if (rows > 0) {
|
||||
cleanOnlineUser(ids);
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byUsers(ids));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
@@ -506,7 +508,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
||||
rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
|
||||
}
|
||||
if (rows > 0) {
|
||||
cleanOnlineUser(ids);
|
||||
SpringUtils.context().publishEvent(OnlineUserCleanEvent.byUsers(ids));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.dromara.workflow.event;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import org.dromara.warm.flow.core.entity.Task;
|
||||
import org.dromara.workflow.domain.bo.FlowCopyBo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流抄送事件。
|
||||
*
|
||||
* @param task 当前任务
|
||||
* @param flowCopyList 抄送人列表
|
||||
*/
|
||||
public record WorkflowCopyEvent(Task task, List<FlowCopyBo> flowCopyList) {
|
||||
|
||||
public WorkflowCopyEvent {
|
||||
if (CollUtil.isNotEmpty(flowCopyList)) {
|
||||
flowCopyList = List.copyOf(flowCopyList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.dromara.workflow.event;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流结果消息事件。
|
||||
*
|
||||
* @param flowName 流程名称
|
||||
* @param status 流程状态
|
||||
* @param createBy 发起人用户 ID
|
||||
* @param messageType 消息类型
|
||||
*/
|
||||
public record WorkflowResultMessageEvent(String flowName, String status, String createBy, List<String> messageType) {
|
||||
|
||||
public WorkflowResultMessageEvent {
|
||||
if (CollUtil.isNotEmpty(messageType)) {
|
||||
messageType = List.copyOf(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.dromara.workflow.event;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流待办消息事件。
|
||||
*
|
||||
* @param flowName 流程名称
|
||||
* @param instanceId 流程实例 ID
|
||||
* @param messageType 消息类型
|
||||
* @param notice 通知内容
|
||||
*/
|
||||
public record WorkflowTaskMessageEvent(String flowName, Long instanceId, List<String> messageType, String notice) {
|
||||
|
||||
public WorkflowTaskMessageEvent {
|
||||
if (CollUtil.isNotEmpty(messageType)) {
|
||||
messageType = List.copyOf(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+8
-18
@@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.dromara.common.core.enums.BusinessStatusEnum;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.system.api.domain.vo.RemoteUserVo;
|
||||
@@ -23,10 +24,12 @@ import org.dromara.warm.flow.core.listener.GlobalListener;
|
||||
import org.dromara.warm.flow.core.listener.ListenerVariable;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.common.constant.FlowConstant;
|
||||
import org.dromara.workflow.common.enums.MessageTypeEnum;
|
||||
import org.dromara.workflow.common.enums.TaskStatusEnum;
|
||||
import org.dromara.workflow.domain.bo.FlowCopyBo;
|
||||
import org.dromara.workflow.domain.vo.NodeExtVo;
|
||||
import org.dromara.workflow.event.WorkflowCopyEvent;
|
||||
import org.dromara.workflow.event.WorkflowResultMessageEvent;
|
||||
import org.dromara.workflow.event.WorkflowTaskMessageEvent;
|
||||
import org.dromara.workflow.handler.FlowProcessEventHandler;
|
||||
import org.dromara.workflow.service.IFlwCommonService;
|
||||
import org.dromara.workflow.service.IFlwInstanceService;
|
||||
@@ -34,7 +37,6 @@ import org.dromara.workflow.service.IFlwNodeExtService;
|
||||
import org.dromara.workflow.service.IFlwTaskService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -238,13 +240,13 @@ public class WorkflowGlobalListener implements GlobalListener {
|
||||
if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) {
|
||||
List<FlowCopyBo> flowCopyList = MapUtil.get(variable, FlowConstant.FLOW_COPY_LIST, new TypeReference<>() {});
|
||||
// 添加抄送人
|
||||
flwTaskService.setCopy(task, flowCopyList);
|
||||
SpringUtils.context().publishEvent(new WorkflowCopyEvent(task, flowCopyList));
|
||||
}
|
||||
if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
|
||||
List<String> messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {});
|
||||
String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE);
|
||||
if (shouldSendTaskMessage(flowParams, definition, nextTasks)) {
|
||||
flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
|
||||
SpringUtils.context().publishEvent(new WorkflowTaskMessageEvent(definition.getFlowName(), instance.getId(), messageType, notice));
|
||||
}
|
||||
}
|
||||
FlowEngine.insService().removeVariables(instance.getId(),
|
||||
@@ -273,23 +275,11 @@ public class WorkflowGlobalListener implements GlobalListener {
|
||||
if (StringUtils.isBlank(instance.getCreateBy())) {
|
||||
return;
|
||||
}
|
||||
Long createBy = Convert.toLong(instance.getCreateBy(), null);
|
||||
if (createBy == null) {
|
||||
return;
|
||||
}
|
||||
BusinessStatusEnum statusEnum = BusinessStatusEnum.getByStatus(status);
|
||||
if (statusEnum == null) {
|
||||
return;
|
||||
}
|
||||
List<RemoteUserVo> initiators = remoteUserService.selectListByIds(Collections.singletonList(createBy));
|
||||
if (CollUtil.isEmpty(initiators)) {
|
||||
return;
|
||||
}
|
||||
List<String> messageType = Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode());
|
||||
List<String> messageType = null;
|
||||
if (MapUtil.isNotEmpty(variable) && variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
|
||||
messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {});
|
||||
}
|
||||
flwCommonService.sendResultMessage(definition.getFlowName(), statusEnum, messageType, initiators);
|
||||
SpringUtils.context().publishEvent(new WorkflowResultMessageEvent(definition.getFlowName(), status, instance.getCreateBy(), messageType));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.dromara.workflow.listener;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.dromara.common.core.enums.BusinessStatusEnum;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.system.api.RemoteUserService;
|
||||
import org.dromara.system.api.domain.vo.RemoteUserVo;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.common.enums.MessageTypeEnum;
|
||||
import org.dromara.workflow.event.WorkflowCopyEvent;
|
||||
import org.dromara.workflow.event.WorkflowResultMessageEvent;
|
||||
import org.dromara.workflow.event.WorkflowTaskMessageEvent;
|
||||
import org.dromara.workflow.service.IFlwCommonService;
|
||||
import org.dromara.workflow.service.IFlwTaskService;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流副作用事件监听器。
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@ConditionalOnEnable
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class WorkflowSideEffectListener {
|
||||
|
||||
private final IFlwTaskService flwTaskService;
|
||||
private final IFlwCommonService flwCommonService;
|
||||
|
||||
@DubboReference
|
||||
private RemoteUserService remoteUserService;
|
||||
|
||||
/**
|
||||
* 保存工作流抄送记录。
|
||||
*
|
||||
* @param event 工作流抄送事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleCopy(WorkflowCopyEvent event) {
|
||||
flwTaskService.setCopy(event.task(), event.flowCopyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作流待办消息。
|
||||
*
|
||||
* @param event 工作流待办消息事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleTaskMessage(WorkflowTaskMessageEvent event) {
|
||||
flwCommonService.sendMessage(event.flowName(), event.instanceId(), event.messageType(), event.notice());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送工作流结果消息。
|
||||
*
|
||||
* @param event 工作流结果消息事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleResultMessage(WorkflowResultMessageEvent event) {
|
||||
if (!StringUtils.equalsAny(event.status(), BusinessStatusEnum.FINISH.getStatus(), BusinessStatusEnum.BACK.getStatus())) {
|
||||
return;
|
||||
}
|
||||
Long createBy = Convert.toLong(event.createBy(), null);
|
||||
if (createBy == null) {
|
||||
return;
|
||||
}
|
||||
BusinessStatusEnum status = BusinessStatusEnum.getByStatus(event.status());
|
||||
if (status == null) {
|
||||
return;
|
||||
}
|
||||
List<RemoteUserVo> initiators = remoteUserService.selectListByIds(Collections.singletonList(createBy));
|
||||
if (CollUtil.isEmpty(initiators)) {
|
||||
return;
|
||||
}
|
||||
List<String> messageType = CollUtil.isNotEmpty(event.messageType())
|
||||
? event.messageType()
|
||||
: Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode());
|
||||
flwCommonService.sendResultMessage(event.flowName(), status, messageType, initiators);
|
||||
}
|
||||
|
||||
}
|
||||
+15
-8
@@ -12,6 +12,7 @@ import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ThreadUtils;
|
||||
import org.dromara.resource.api.RemoteMailService;
|
||||
import org.dromara.resource.api.RemoteMessageService;
|
||||
import org.dromara.resource.api.RemoteSmsService;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.dromara.workflow.common.constant.FlowConstant.PATH_MY_DOCUMENT;
|
||||
import static org.dromara.workflow.common.constant.FlowConstant.PATH_TASK_WAITING;
|
||||
@@ -110,12 +112,20 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
||||
return;
|
||||
}
|
||||
List<Long> userIds = new ArrayList<>(StreamUtils.toSet(userList, RemoteUserVo::getUserId));
|
||||
String emails = StreamUtils.join(userList, RemoteUserVo::getEmail);
|
||||
Set<String> emailSet = StreamUtils.toSet(userList, RemoteUserVo::getEmail);
|
||||
emailSet.removeIf(StringUtils::isBlank);
|
||||
String emails = StringUtils.joinComma(emailSet);
|
||||
|
||||
for (String code : messageType) {
|
||||
Runnable[] sendTasks = messageType.stream()
|
||||
.map(code -> (Runnable) () -> sendMessageByType(code, message, subject, path, userIds, emails, userList.size()))
|
||||
.toArray(Runnable[]::new);
|
||||
ThreadUtils.virtualInvokeAll(sendTasks);
|
||||
}
|
||||
|
||||
private void sendMessageByType(String code, String message, String subject, String path, List<Long> userIds, String emails, int userCount) {
|
||||
MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
|
||||
if (ObjectUtil.isEmpty(messageTypeEnum)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
switch (messageTypeEnum) {
|
||||
@@ -127,15 +137,13 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
||||
);
|
||||
remoteMessageService.publishMessagePayload(userIds, payload);
|
||||
}
|
||||
case EMAIL_MESSAGE -> {
|
||||
remoteMailService.send(emails, subject, message);
|
||||
}
|
||||
case EMAIL_MESSAGE -> remoteMailService.send(emails, subject, message);
|
||||
case SMS_MESSAGE -> {
|
||||
// LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
// // 根据具体短信服务商参数用法传参
|
||||
// map.put("code", "1234");
|
||||
// remoteSmsService.sendMessage(phones, templateId, map);
|
||||
log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
|
||||
log.info("【短信发送 - TODO】用户数量={} 内容={}", userCount, message);
|
||||
}
|
||||
default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
|
||||
}
|
||||
@@ -144,7 +152,6 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
|
||||
log.error("【消息发送失败】类型={},原因={}", messageTypeEnum, ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请人节点编码
|
||||
|
||||
+27
-4
@@ -12,6 +12,7 @@ import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ThreadUtils;
|
||||
import org.dromara.system.api.*;
|
||||
import org.dromara.system.api.domain.bo.RemoteTaskAssigneeBo;
|
||||
import org.dromara.system.api.domain.vo.RemoteDeptVo;
|
||||
@@ -30,6 +31,7 @@ import org.dromara.workflow.service.IFlwTaskAssigneeService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -111,8 +113,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
|
||||
}
|
||||
|
||||
// 查询所有类型对应的 ID 名称映射
|
||||
Map<TaskAssigneeEnum, Map<String, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
|
||||
typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids)));
|
||||
Map<TaskAssigneeEnum, Map<String, String>> nameMap = this.getNamesByTypes(typeIdMap);
|
||||
// 组装返回结果,保持原始顺序
|
||||
return parsedMap.entrySet().stream()
|
||||
.map(entry -> {
|
||||
@@ -207,12 +208,34 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
|
||||
typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
|
||||
}
|
||||
}
|
||||
return typeIdMap.entrySet().stream()
|
||||
.flatMap(entry -> this.getUsersByType(entry.getKey(), entry.getValue()).stream())
|
||||
return this.getUsersByTypes(typeIdMap).stream()
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<RemoteUserVo> getUsersByTypes(Map<TaskAssigneeEnum, List<String>> typeIdMap) {
|
||||
List<Supplier<List<RemoteUserVo>>> suppliers = typeIdMap.entrySet().stream()
|
||||
.map(entry -> (Supplier<List<RemoteUserVo>>) () -> this.getUsersByType(entry.getKey(), entry.getValue()))
|
||||
.toList();
|
||||
return ThreadUtils.virtualSubmitAll(suppliers).stream()
|
||||
.filter(CollUtil::isNotEmpty)
|
||||
.flatMap(Collection::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Map<TaskAssigneeEnum, Map<String, String>> getNamesByTypes(Map<TaskAssigneeEnum, List<String>> typeIdMap) {
|
||||
List<TaskAssigneeEnum> types = new ArrayList<>(typeIdMap.keySet());
|
||||
List<Supplier<Map<String, String>>> suppliers = types.stream()
|
||||
.map(type -> (Supplier<Map<String, String>>) () -> this.getNamesByType(type, typeIdMap.get(type)))
|
||||
.toList();
|
||||
List<Map<String, String>> names = ThreadUtils.virtualSubmitAll(suppliers);
|
||||
Map<TaskAssigneeEnum, Map<String, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
|
||||
for (int i = 0; i < types.size(); i++) {
|
||||
nameMap.put(types.get(i), names.get(i));
|
||||
}
|
||||
return nameMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的任务分配类型(TaskAssigneeEnum)和 ID 列表,获取对应的用户信息列表
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user