17 Commits

Author SHA1 Message Date
AprilWind
b50904c6ff update 优化工作流流程监听增加节点信息 2025-04-09 10:57:29 +08:00
AprilWind
70aa14ecf8 update 优化工作流办理人权限处理器 2025-04-09 10:35:19 +08:00
疯狂的狮子Li
c37b92978a update 调整注释 删除不存在的错误的东西 2025-04-09 10:23:59 +08:00
疯狂的狮子Li
ef39ad7107 update 增加赞助商 2025-04-08 10:16:31 +08:00
疯狂的狮子Li
48d3ef9818 update 优化 统一校验注解长度 2025-04-08 10:16:20 +08:00
疯狂的狮子Li
5bf901cdcd update 优化 增加api审批简化方法 2025-04-03 13:34:41 +08:00
gssong
8e99dd306a https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IBYCY7
fix 修复选择弹窗会签人员后,会签审批出现每个任务的审批人都是选择的多人
2025-04-02 21:03:58 +08:00
疯狂的狮子Li
07fdc240d7 fix 修复 在线用户设置过期时间与客户端不同步问题 2025-04-02 14:04:41 +08:00
velenooo
023ceaaf91 !666 fix: 模板导出多个字段下拉值超过100个异常,采用多个sheet的方案解决。
* fix: 模板导出多个字段下拉值超过100个异常,采用多个sheet的方案解决。
2025-04-02 05:20:53 +00:00
Binary
9e551a0b2a !665 admin Dockerfile 构建文件新增暴露 snail job 客户端端口 用于定时任务调度中心通信
* admin Dockerfile 构建文件新增暴露 snail job 客户端端口 用于定时任务调度中心通信
2025-04-01 06:29:08 +00:00
疯狂的狮子Li
7c3f3523ea update 优化 使用 record 简化vo代码 2025-03-31 11:31:05 +08:00
疯狂的狮子Li
40eac07789 update 优化 FlwNodeExtServiceImpl 代码实现 2025-03-31 10:56:03 +08:00
疯狂的狮子Li
5868fadbf5 update 优化 sse 删除之后 手动触发完成 防止内存泄漏 2025-03-31 09:42:30 +08:00
疯狂的狮子Li
124bcc4bba update 优化 sse 删除之后 手动触发完成 防止内存泄漏 2025-03-31 09:41:53 +08:00
疯狂的狮子Li
e71d6fa983 update 优化 支持excel方法抛出json异常 2025-03-28 23:12:40 +08:00
疯狂的狮子Li
7129ad4fac Revert "update 优化 支持excel方法抛出json异常"
This reverts commit 16923cc86a.
2025-03-28 23:11:11 +08:00
疯狂的狮子Li
16923cc86a update 优化 支持excel方法抛出json异常 2025-03-28 22:51:32 +08:00
27 changed files with 200 additions and 262 deletions

View File

@@ -34,6 +34,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异

View File

@@ -11,9 +11,11 @@ RUN mkdir -p /ruoyi/server/logs \
WORKDIR /ruoyi/server
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
EXPOSE ${SERVER_PORT}
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
EXPOSE ${SNAIL_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
# 工作流字体文件
@@ -22,6 +24,7 @@ ADD ./zhFonts/ /usr/share/fonts/zhFonts/
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
-Dsnail-job.port=${SNAIL_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \

View File

@@ -1,6 +1,5 @@
package org.dromara.web.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
@@ -35,7 +34,6 @@ import java.time.Duration;
@Slf4j
public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
private final SysLoginService loginService;
/**
@@ -59,10 +57,10 @@ public class UserActionListener implements SaTokenListener {
dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(tokenConfig.getTimeout() == -1) {
if(loginModel.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginModel.getTimeout()));
}
});
// 记录登录日志

View File

@@ -177,9 +177,6 @@ springdoc:
api-docs:
# 是否开启接口文档
enabled: true
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
info:
# 标题
title: '标题RuoYi-Vue-Plus多租户管理系统_接口文档'

View File

@@ -27,10 +27,20 @@ public class ProcessCreateTaskEvent implements Serializable {
private String flowCode;
/**
* 审批节点编码
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 任务id
*/

View File

@@ -33,7 +33,22 @@ public class ProcessEvent implements Serializable {
private String businessId;
/**
* 状态
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程状态
*/
private String status;

View File

@@ -18,14 +18,14 @@ public class RegisterBody extends LoginBody {
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
private String password;
private String userType;

View File

@@ -78,9 +78,18 @@ public interface WorkflowService {
/**
* 办理任务
* 系统后台发起审批 无用户信息 需要忽略权限
* completeTask.getVariables().put("ignore", true);
*
* @param completeTask 参数
* @return 结果
*/
boolean completeTask(CompleteTaskDTO completeTask);
/**
* 办理任务
*
* @param taskId 任务ID
* @param message 办理意见
*/
boolean completeTask(Long taskId, String message);
}

View File

@@ -30,7 +30,7 @@ import java.util.Optional;
import java.util.Set;
/**
* Swagger 文档配置
* 接口文档配置
*
* @author Lion Li
*/

View File

@@ -291,9 +291,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
* @param value 下拉选可选值
*/
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
//由于poi的写出相关问题超过100个会被临时写进硬盘导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
// 创建下拉数据表
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
// 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
// 完善纵向的一级选项数据表
@@ -302,9 +304,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 获取每一选项行,如果没有则创建
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
.orElseGet(() -> simpleDataSheet.createRow(finalI));
// 获取本级选项对应的选项列,如果没有则创建
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
// 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
Cell cell = Optional.ofNullable(row.getCell(0))
.orElseGet(() -> row.createCell(0));
// 设置值
cell.setCellValue(value.get(i));
}
@@ -312,13 +314,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 创建名称管理器
Name name = workbook.createName();
// 设置名称管理器的别名
String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
name.setNameName(nameName);
// 以纵向第一列创建一级下拉拼接引用位置
String function = String.format("%s!$%s$1:$%s$%d",
OPTIONS_SHEET_NAME,
getExcelColumnName(currentOptionsColumnIndex),
getExcelColumnName(currentOptionsColumnIndex),
tmpOptionsSheetName,
getExcelColumnName(0),
getExcelColumnName(0),
value.size());
// 设置名称管理器的引用位置
name.setRefersToFormula(function);

View File

@@ -215,6 +215,9 @@ public class ExcelUtil {
*/
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os);
@@ -233,9 +236,6 @@ public class ExcelUtil {
* @param os 输出流
*/
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
@@ -265,6 +265,9 @@ public class ExcelUtil {
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os);
@@ -285,6 +288,9 @@ public class ExcelUtil {
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiSheet(data, templatePath, os);
@@ -303,9 +309,6 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
@@ -337,9 +340,6 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())

View File

@@ -44,9 +44,24 @@ public class SseEmitterManager {
emitters.put(token, emitter);
// 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
emitter.onCompletion(() -> emitters.remove(token));
emitter.onTimeout(() -> emitters.remove(token));
emitter.onError((e) -> emitters.remove(token));
emitter.onCompletion(() -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
}
});
emitter.onTimeout(() -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
}
});
emitter.onError((e) -> {
SseEmitter remove = emitters.remove(token);
if (remove != null) {
remove.complete();
}
});
try {
// 向客户端发送一条连接成功的事件
@@ -106,7 +121,10 @@ public class SseEmitterManager {
.name("message")
.data(message));
} catch (Exception e) {
emitters.remove(entry.getKey());
SseEmitter remove = emitters.remove(entry.getKey());
if (remove != null) {
remove.complete();
}
}
}
} else {

View File

@@ -1,10 +1,9 @@
package org.dromara.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.domain.vo.CacheListInfoVo;
import lombok.RequiredArgsConstructor;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.web.bind.annotation.GetMapping;
@@ -45,11 +44,11 @@ public class CacheController {
});
}
CacheListInfoVo infoVo = new CacheListInfoVo();
infoVo.setInfo(connection.commands().info());
infoVo.setDbSize(connection.commands().dbSize());
infoVo.setCommandStats(pieList);
return R.ok(infoVo);
return R.ok(new CacheListInfoVo(
connection.commands().info(),
connection.commands().dbSize(), pieList));
}
public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
}

View File

@@ -15,7 +15,6 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.SysMenu;
import org.dromara.system.domain.bo.SysMenuBo;
import org.dromara.system.domain.vo.MenuTreeSelectVo;
import org.dromara.system.domain.vo.RouterVo;
import org.dromara.system.domain.vo.SysMenuVo;
import org.dromara.system.service.ISysMenuService;
@@ -96,9 +95,9 @@ public class SysMenuController extends BaseController {
@GetMapping(value = "/roleMenuTreeselect/{roleId}")
public R<MenuTreeSelectVo> roleMenuTreeselect(@PathVariable("roleId") Long roleId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
selectVo.setCheckedKeys(menuService.selectMenuListByRoleId(roleId));
selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
menuService.selectMenuListByRoleId(roleId),
menuService.buildMenuTreeSelect(menus));
return R.ok(selectVo);
}
@@ -112,9 +111,9 @@ public class SysMenuController extends BaseController {
@GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
MenuTreeSelectVo selectVo = new MenuTreeSelectVo();
selectVo.setCheckedKeys(menuService.selectMenuListByPackageId(packageId));
selectVo.setMenus(menuService.buildMenuTreeSelect(menus));
MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
menuService.selectMenuListByPackageId(packageId),
menuService.buildMenuTreeSelect(menus));
return R.ok(selectVo);
}
@@ -171,4 +170,7 @@ public class SysMenuController extends BaseController {
return toAjax(menuService.deleteMenuById(menuId));
}
public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
}
}

View File

@@ -17,8 +17,6 @@ import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.bo.SysUserPasswordBo;
import org.dromara.system.domain.bo.SysUserProfileBo;
import org.dromara.system.domain.vo.AvatarVo;
import org.dromara.system.domain.vo.ProfileVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysOssService;
@@ -50,10 +48,9 @@ public class SysProfileController extends BaseController {
@GetMapping
public R<ProfileVo> profile() {
SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
ProfileVo profileVo = new ProfileVo();
profileVo.setUser(user);
profileVo.setRoleGroup(userService.selectUserRoleGroup(user.getUserId()));
profileVo.setPostGroup(userService.selectUserPostGroup(user.getUserId()));
String roleGroup = userService.selectUserRoleGroup(user.getUserId());
String postGroup = userService.selectUserPostGroup(user.getUserId());
ProfileVo profileVo = new ProfileVo(user, roleGroup, postGroup);
return R.ok(profileVo);
}
@@ -123,11 +120,14 @@ public class SysProfileController extends BaseController {
String avatar = oss.getUrl();
boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
if (updateSuccess) {
AvatarVo avatarVo = new AvatarVo();
avatarVo.setImgUrl(avatar);
return R.ok(avatarVo);
return R.ok(new AvatarVo(avatar));
}
}
return R.fail("上传图片异常,请联系管理员");
}
public record AvatarVo(String imgUrl) {}
public record ProfileVo(SysUserVo user, String roleGroup, String postGroup) {}
}

View File

@@ -1,6 +1,7 @@
package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
@@ -14,7 +15,6 @@ import org.dromara.system.domain.SysUserRole;
import org.dromara.system.domain.bo.SysDeptBo;
import org.dromara.system.domain.bo.SysRoleBo;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.DeptTreeSelectVo;
import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDeptService;
@@ -221,9 +221,12 @@ public class SysRoleController extends BaseController {
@SaCheckPermission("system:role:list")
@GetMapping(value = "/deptTree/{roleId}")
public R<DeptTreeSelectVo> roleDeptTreeselect(@PathVariable("roleId") Long roleId) {
DeptTreeSelectVo selectVo = new DeptTreeSelectVo();
selectVo.setCheckedKeys(deptService.selectDeptListByRoleId(roleId));
selectVo.setDepts(deptService.selectDeptTreeList(new SysDeptBo()));
DeptTreeSelectVo selectVo = new DeptTreeSelectVo(
deptService.selectDeptListByRoleId(roleId),
deptService.selectDeptTreeList(new SysDeptBo()));
return R.ok(selectVo);
}
public record DeptTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> depts) {}
}

View File

@@ -1,18 +0,0 @@
package org.dromara.system.domain.vo;
import lombok.Data;
/**
* 用户头像信息
*
* @author Michelle.Chung
*/
@Data
public class AvatarVo {
/**
* 头像地址
*/
private String imgUrl;
}

View File

@@ -1,23 +0,0 @@
package org.dromara.system.domain.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* 缓存监控列表信息
*
* @author Michelle.Chung
*/
@Data
public class CacheListInfoVo {
private Properties info;
private Long dbSize;
private List<Map<String, String>> commandStats;
}

View File

@@ -1,26 +0,0 @@
package org.dromara.system.domain.vo;
import cn.hutool.core.lang.tree.Tree;
import lombok.Data;
import java.util.List;
/**
* 角色部门列表树信息
*
* @author Michelle.Chung
*/
@Data
public class DeptTreeSelectVo {
/**
* 选中部门列表
*/
private List<Long> checkedKeys;
/**
* 下拉树结构列表
*/
private List<Tree<Long>> depts;
}

View File

@@ -1,26 +0,0 @@
package org.dromara.system.domain.vo;
import cn.hutool.core.lang.tree.Tree;
import lombok.Data;
import java.util.List;
/**
* 角色菜单列表树信息
*
* @author Michelle.Chung
*/
@Data
public class MenuTreeSelectVo {
/**
* 选中菜单列表
*/
private List<Long> checkedKeys;
/**
* 菜单下拉树结构列表
*/
private List<Tree<Long>> menus;
}

View File

@@ -1,29 +0,0 @@
package org.dromara.system.domain.vo;
import lombok.Data;
/**
* 用户个人信息
*
* @author Michelle.Chung
*/
@Data
public class ProfileVo {
/**
* 用户信息
*/
private SysUserVo user;
/**
* 用户所属角色组
*/
private String roleGroup;
/**
* 用户所属岗位组
*/
private String postGroup;
}

View File

@@ -27,16 +27,27 @@ public class FlowProcessEventHandler {
*
* @param flowCode 流程定义编码
* @param businessId 业务id
* @param status 状态
* @param nodeType 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
* @param nodeCode 流程节点编码
* @param nodeName 流程节点名称
* @param status 流程状态
* @param params 办理参数
* @param submit 当为true时为申请人节点办理
*/
public void processHandler(String flowCode, String businessId, String status, Map<String, Object> params, boolean submit) {
public void processHandler(String flowCode, String businessId, Integer nodeType, String nodeCode, String nodeName,
String status, Map<String, Object> params, boolean submit) {
String tenantId = TenantHelper.getTenantId();
log.info("发布流程事件租户ID: {}, 流程状态: {}, 流程编码: {}, 业务ID: {}, 是否申请人节点办理: {}", tenantId, status, flowCode, businessId, submit);
log.info("流程事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 状态: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 是否申请人节点: {}, 参数: {}",
tenantId, flowCode, businessId, status, nodeType, nodeCode, nodeName, submit, params);
ProcessEvent processEvent = new ProcessEvent();
processEvent.setTenantId(tenantId);
processEvent.setFlowCode(flowCode);
processEvent.setBusinessId(businessId);
processEvent.setNodeType(nodeType);
processEvent.setNodeCode(nodeCode);
processEvent.setNodeName(nodeName);
processEvent.setStatus(status);
processEvent.setParams(params);
processEvent.setSubmit(submit);
@@ -47,17 +58,22 @@ public class FlowProcessEventHandler {
* 执行创建任务监听
*
* @param flowCode 流程定义编码
* @param nodeCode 审批节点编码
* @param nodeType 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
* @param nodeCode 流程节点编码
* @param nodeName 流程节点名称
* @param taskId 任务id
* @param businessId 业务id
*/
public void processCreateTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) {
public void processCreateTaskHandler(String flowCode, Integer nodeType, String nodeCode, String nodeName, Long taskId, String businessId) {
String tenantId = TenantHelper.getTenantId();
log.info("发布流程任务事件, 租户ID: {}, 流程编码: {}, 节点编码: {}, 任务ID: {}, 业务ID: {}", tenantId, flowCode, nodeCode, taskId, businessId);
log.info("发布流程任务事件, 租户ID: {}, 流程编码: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}, 业务ID: {}",
tenantId, flowCode, nodeType, nodeCode, nodeName, taskId, businessId);
ProcessCreateTaskEvent processCreateTaskEvent = new ProcessCreateTaskEvent();
processCreateTaskEvent.setTenantId(tenantId);
processCreateTaskEvent.setFlowCode(flowCode);
processCreateTaskEvent.setNodeType(nodeType);
processCreateTaskEvent.setNodeCode(nodeCode);
processCreateTaskEvent.setNodeName(nodeName);
processCreateTaskEvent.setTaskId(taskId);
processCreateTaskEvent.setBusinessId(businessId);
SpringUtils.context().publishEvent(processCreateTaskEvent);

View File

@@ -1,22 +1,16 @@
package org.dromara.workflow.handler;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.TaskAssigneeEnum;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.handler.PermissionHandler;
import org.dromara.warm.flow.core.service.impl.TaskServiceImpl;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 办理人权限处理器
@@ -36,28 +30,7 @@ public class WorkflowPermissionHandler implements PermissionHandler {
*/
@Override
public List<String> permissions() {
LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
return new ArrayList<>();
}
// 使用一个流来构建权限列表
return Stream.of(
// 角色权限前缀
loginUser.getRoles().stream()
.map(role -> TaskAssigneeEnum.ROLE.getCode() + role.getRoleId()),
// 岗位权限前缀
Stream.ofNullable(loginUser.getPosts())
.flatMap(Collection::stream)
.map(post -> TaskAssigneeEnum.POST.getCode() + post.getPostId()),
// 用户和部门权限
Stream.of(String.valueOf(loginUser.getUserId()),
TaskAssigneeEnum.DEPT.getCode() + loginUser.getDeptId()
)
)
.flatMap(stream -> stream)
.collect(Collectors.toList());
return Collections.singletonList(LoginHelper.getUserIdStr());
}
/**

View File

@@ -52,7 +52,8 @@ public class WorkflowGlobalListener implements GlobalListener {
Task task = listenerVariable.getTask();
if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) {
// 判断流程状态(发布审批中事件)
flowProcessEventHandler.processCreateTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId);
flowProcessEventHandler.processCreateTaskHandler(definition.getFlowCode(), task.getNodeType(),
task.getNodeCode(), task.getNodeName(), task.getId(), businessId);
}
}
@@ -83,8 +84,6 @@ public class WorkflowGlobalListener implements GlobalListener {
public void finish(ListenerVariable listenerVariable) {
Instance instance = listenerVariable.getInstance();
Definition definition = listenerVariable.getDefinition();
String businessId = instance.getBusinessId();
String flowStatus = instance.getFlowStatus();
Map<String, Object> params = new HashMap<>();
FlowParams flowParams = listenerVariable.getFlowParams();
if (ObjectUtil.isNotNull(flowParams)) {
@@ -96,20 +95,21 @@ public class WorkflowGlobalListener implements GlobalListener {
params.put("message", flowParams.getMessage());
}
// 判断流程状态(发布:撤销,退回,作废,终止,已完成事件)
String status = determineFlowStatus(instance, flowStatus);
String status = determineFlowStatus(instance);
if (StringUtils.isNotBlank(status)) {
flowProcessEventHandler.processHandler(definition.getFlowCode(), businessId, status, params, false);
flowProcessEventHandler.processHandler(definition.getFlowCode(), instance.getBusinessId(), instance.getNodeType(),
instance.getNodeCode(), instance.getNodeName(), status, params, false);
}
}
/**
* 根据流程实例和当前流程状态确定最终状态
* 根据流程实例确定最终状态
*
* @param instance 流程实例
* @param flowStatus 流程实例当前状态
* @param instance 流程实例
* @return 流程最终状态
*/
private String determineFlowStatus(Instance instance, String flowStatus) {
private String determineFlowStatus(Instance instance) {
String flowStatus = instance.getFlowStatus();
if (StringUtils.isNotBlank(flowStatus) && BusinessStatusEnum.initialState(flowStatus)) {
log.info("流程实例当前状态: {}", flowStatus);
return flowStatus;

View File

@@ -26,34 +26,16 @@ import java.util.*;
@Service
public class FlwNodeExtServiceImpl implements NodeExtService {
/**
* 权限页code
*/
private static final String PERMISSION_TAB = "wf_button_tab";
/**
* 权限页名称
*/
private static final String PERMISSION_TAB_NAME = "权限";
/**
* 基础设置
*/
private static final int TYPE_BASE_SETTING = 1;
/**
* 新页签
*/
private static final int TYPE_NEW_TAB = 2;
/**
* 存储不同 dictType 对应的配置信息
*/
private static final Map<String, Map<String, Object>> CHILD_NODE_MAP = new HashMap<>();
private static final Map<String, ButtonPermission> CHILD_NODE_MAP = new HashMap<>();
record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) {}
static {
CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(),
Map.of("label", "权限按钮", "type", 4, "must", false, "multiple", true));
new ButtonPermission("权限按钮", 4, false, true));
}
private final DictService dictService;
@@ -67,8 +49,10 @@ public class FlwNodeExtServiceImpl implements NodeExtService {
public List<NodeExt> getNodeExt() {
List<NodeExt> nodeExtList = new ArrayList<>();
// 构建按钮权限页面
nodeExtList.add(buildNodeExt(PERMISSION_TAB, PERMISSION_TAB_NAME, TYPE_NEW_TAB,
nodeExtList.add(buildNodeExt("wf_button_tab", "权限", 2,
List.of(ButtonPermissionEnum.class)));
// 自定义构建 规则参考 NodeExt 与 warm-flow文档说明
// nodeExtList.add(buildNodeExt("xxx_xxx", "xxx", 1, List);
return nodeExtList;
}
@@ -160,15 +144,21 @@ public class FlwNodeExtServiceImpl implements NodeExtService {
*/
private NodeExt.ChildNode buildChildNodeMap(String key) {
NodeExt.ChildNode childNode = new NodeExt.ChildNode();
Map<String, Object> map = CHILD_NODE_MAP.get(key);
ButtonPermission bp = CHILD_NODE_MAP.get(key);
if (bp == null) {
childNode.setType(1);
childNode.setMust(false);
childNode.setMultiple(true);
return childNode;
}
// label名称
childNode.setLabel((String) map.get("label"));
childNode.setLabel(bp.label());
// 1输入框 2输入框 3下拉框 4选择框
childNode.setType(Convert.toInt(map.get("type"), 1));
childNode.setType(bp.type());
// 是否必填
childNode.setMust(Convert.toBool(map.get("must"), false));
childNode.setMust(bp.must());
// 是否多选
childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
childNode.setMultiple(bp.multiple());
return childNode;
}

View File

@@ -162,7 +162,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
Definition definition = defService.getById(flowTask.getDefinitionId());
// 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true);
flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getNodeType(),
ins.getNodeCode(), ins.getNodeName(), ins.getFlowStatus(), null, true);
}
// 设置弹窗处理人
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
@@ -183,7 +184,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
// 消息通知
flwCommonService.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice);
//设置下一环节处理人
setNextHandler(ins.getId());
setNextHandler(ins.getId(), completeTaskBo.getAssigneeMap());
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -194,9 +195,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
/**
* 设置下一环节处理人
*
* @param instanceId 实例ID
* @param instanceId 实例ID
* @param assigneeMap 办理人
*/
private void setNextHandler(Long instanceId) {
private void setNextHandler(Long instanceId, Map<String, Object> assigneeMap) {
if (CollUtil.isEmpty(assigneeMap)) {
return;
}
Instance inst = insService.getById(instanceId);
List<FlowTask> flowTaskList = selectByInstId(instanceId);
Map<String, Object> variableMap = inst.getVariableMap();

View File

@@ -122,6 +122,8 @@ public class WorkflowServiceImpl implements WorkflowService {
/**
* 办理任务
* 系统后台发起审批 无用户信息 需要忽略权限
* completeTask.getVariables().put("ignore", true);
*
* @param completeTask 参数
*/
@@ -129,4 +131,21 @@ public class WorkflowServiceImpl implements WorkflowService {
public boolean completeTask(CompleteTaskDTO completeTask) {
return flwTaskService.completeTask(BeanUtil.toBean(completeTask, CompleteTaskBo.class));
}
/**
* 办理任务
*
* @param taskId 任务ID
* @param message 办理意见
*/
@Override
public boolean completeTask(Long taskId, String message) {
CompleteTaskBo completeTask = new CompleteTaskBo();
completeTask.setTaskId(taskId);
completeTask.setMessage(message);
// 忽略权限(系统后台发起审批 无用户信息 需要忽略权限)
completeTask.getVariables().put("ignore", true);
return flwTaskService.completeTask(completeTask);
}
}