refactor: 使用事件解耦业务副作用并优化虚拟线程并发

- 新增登录成功事件,解耦在线用户缓存、登录日志和最近登录信息更新
  - 新增在线用户清理事件,角色授权变更后异步清理受影响用户会话
  - 新增 OSS 配置变更事件,统一处理配置初始化、删除和默认配置切换后的缓存刷新
  - 新增工作流抄送、待办消息、结果消息事件,拆分全局监听器中的副作用逻辑
  - 扩展 ThreadUtils 支持带返回值的虚拟线程批量执行
  - 在线用户监控批量读取 Redis 会话信息时使用虚拟线程并发处理
  - OSS 多文件查询、URL 解析和 DTO 转换使用虚拟线程并发处理
  - 工作流办理人回显和用户解析按类型并发查询
  - 工作流多渠道消息发送改为并发执行
  - WebSocket 与 SSE 广播消息按用户并发发送
This commit is contained in:
疯狂的狮子Li
2026-05-18 17:22:13 +08:00
parent 4383e8f1fc
commit ae685cc0d5
26 changed files with 587 additions and 192 deletions
@@ -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;
return null;
}
userOnlineDTOList.add(RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token));
}
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,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() + "'状态失败,请联系管理员");
@@ -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);
}
}
@@ -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()));
}
}
}
@@ -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;
}