Merge remote-tracking branch 'origin/dev' into futuer/boot4

This commit is contained in:
疯狂的狮子Li
2026-03-12 14:20:36 +08:00
42 changed files with 1074 additions and 125 deletions

View File

@@ -0,0 +1,198 @@
package org.dromara.demo.controller;
import cn.dev33.satoken.annotation.*;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* SaToken 权限测试 接口文档输出测试
*
* @author AprilWind
*/
@Slf4j
@RestController
@RequestMapping("/demo/saTokenDoc")
public class SaTokenTestController {
// ====================== 基础场景:单一校验规则 ======================
/**
* 场景1仅登录校验无角色/权限限制,只需登录态)
*/
@SaCheckLogin
@GetMapping("/basic/loginOnly")
public R<Void> loginOnly() {
log.info("【场景1】仅登录校验通过");
return R.ok("仅登录校验通过,无需角色/权限");
}
/**
* 场景2单一角色校验AND模式默认
*/
@SaCheckRole("admin")
@GetMapping("/basic/singleRole")
public R<Void> singleRole() {
log.info("【场景2】单一角色(admin)校验通过");
return R.ok("拥有admin角色校验通过");
}
/**
* 场景3单一权限校验AND模式默认
*/
@SaCheckPermission("system:user:view")
@GetMapping("/basic/singlePermission")
public R<Void> singlePermission() {
log.info("【场景3】单一权限(system:user:view)校验通过");
return R.ok("拥有system:user:view权限校验通过");
}
/**
* 场景4忽略所有权限校验SaIgnore优先级最高
*/
@SaIgnore
@SaCheckRole("none_exist") // 该注解会被忽略
@GetMapping("/basic/ignoreAll")
public R<Void> ignoreAll() {
log.info("【场景4】SaIgnore忽略所有权限校验");
return R.ok("SaIgnore生效所有权限校验被忽略");
}
// ====================== 进阶场景多条件组合AND/OR ======================
/**
* 场景5多角色AND模式必须同时拥有所有角色
*/
@SaCheckRole(value = {"admin", "operator"}, mode = SaMode.AND)
@GetMapping("/advance/multiRoleAnd")
public R<Void> multiRoleAnd() {
log.info("【场景5】多角色AND模式(admin+operator)校验通过");
return R.ok("同时拥有admin和operator角色校验通过");
}
/**
* 场景6多角色OR模式拥有任一角色即可
*/
@SaCheckRole(value = {"admin", "test"}, mode = SaMode.OR)
@GetMapping("/advance/multiRoleOr")
public R<Void> multiRoleOr() {
log.info("【场景6】多角色OR模式(admin|test)校验通过");
return R.ok("拥有admin或test角色校验通过");
}
/**
* 场景7多权限AND模式必须同时拥有所有权限
*/
@SaCheckPermission(value = {"system:user:edit", "system:log:view"}, mode = SaMode.AND)
@GetMapping("/advance/multiPermAnd")
public R<Void> multiPermAnd() {
log.info("【场景7】多权限AND模式(system:user:edit+system:log:view)校验通过");
return R.ok("同时拥有system:user:edit和system:log:view权限校验通过");
}
/**
* 场景8多权限OR模式拥有任一权限即可
*/
@SaCheckPermission(value = {"system:user:add", "system:user:delete"}, mode = SaMode.OR)
@GetMapping("/advance/multiPermOr")
public R<Void> multiPermOr() {
log.info("【场景8】多权限OR模式(system:user:add|system:user:delete)校验通过");
return R.ok("拥有system:user:add或system:user:delete权限校验通过");
}
// ====================== 高级场景:通配符/混合组合 ======================
/**
* 场景9权限通配符匹配前缀匹配
* 拥有system:user:* 即可匹配所有用户模块权限
*/
@SaCheckPermission("system:user:*")
@GetMapping("/advanced/permWildcardPrefix")
public R<Void> permWildcardPrefix() {
log.info("【场景9】权限通配符(system:user:*)校验通过");
return R.ok("拥有system:user:*前缀权限,校验通过");
}
/**
* 场景10角色通配符匹配前缀匹配
* 拥有admin_* 即可匹配所有admin开头的角色
*/
@SaCheckRole("admin_*")
@GetMapping("/advanced/roleWildcardPrefix")
public R<Void> roleWildcardPrefix() {
log.info("【场景10】角色通配符(admin_*)校验通过");
return R.ok("拥有admin_*前缀角色,校验通过");
}
/**
* 场景11权限+角色混合AND模式所有条件必须满足
* 需同时满足拥有admin角色 + 拥有system:user:all权限
*/
@SaCheckRole("admin")
@SaCheckPermission("system:user:all")
@GetMapping("/advanced/mixRolePermAnd")
public R<Void> mixRolePermAnd() {
log.info("【场景11】角色+权限混合AND(admin+system:user:all)校验通过");
return R.ok("拥有admin角色且拥有system:user:all权限校验通过");
}
/**
* 场景12权限+角色混合OR模式任一条件满足即可
* 满足任一拥有super_admin角色 | 拥有system:manage权限
*/
@SaCheckRole(value = {"super_admin"}, mode = SaMode.OR)
@SaCheckPermission(value = {"system:manage"}, mode = SaMode.OR)
@GetMapping("/advanced/mixRolePermOr")
public R<Void> mixRolePermOr() {
log.info("【场景12】角色+权限混合OR(super_admin|system:manage)校验通过");
return R.ok("拥有super_admin角色或system:manage权限校验通过");
}
/**
* 场景13orRole参数权限校验失败时兜底角色校验
* 核心逻辑无system:user:export权限时检查是否有admin/operator角色
*/
@SaCheckPermission(value = "system:user:export", orRole = {"admin", "operator"})
@GetMapping("/advanced/permWithOrRole")
public R<Void> permWithOrRole() {
log.info("【场景13】权限+orRole兜底校验通过");
return R.ok("拥有system:user:export权限或拥有admin/operator角色校验通过");
}
// ====================== 特殊场景:临时权限/注解覆盖 ======================
/**
* 场景14SaIgnore局部覆盖方法注解覆盖类注解若有
* 假设类上有@SaCheckLogin方法上@SaIgnore会覆盖
*/
@SaIgnore
@GetMapping("/special/ignoreOverride")
public R<Void> ignoreOverride() {
log.info("【场景14】SaIgnore覆盖类级别权限注解");
return R.ok("方法级SaIgnore覆盖类级别权限校验");
}
/**
* 场景15临时权限校验SaCheckPermission逻辑临时权限>永久权限)
* 注临时权限需通过SaToken API手动设置如 SaHolder.getStpLogic().setTempPermission("system:temp:test")
*/
@SaCheckPermission("system:temp:test")
@GetMapping("/special/tempPermission")
public R<Void> tempPermission() {
log.info("【场景15】临时权限(system:temp:test)校验通过");
return R.ok("临时权限校验通过需先通过API设置临时权限");
}
/**
* 场景16登录类型指定多端登录场景如PC/APP/小程序)
* 注需配合SaToken多账号体系配置
*/
@SaCheckLogin(type = "PC") // 仅校验PC端的登录态
@GetMapping("/special/loginTypeSpecify")
public R<Void> loginTypeSpecify() {
log.info("【场景16】指定登录类型(PC)校验通过");
return R.ok("仅PC端登录态校验通过");
}
}

View File

@@ -35,8 +35,8 @@ public class ExportDemoVo implements Serializable {
/**
* 用户昵称
*/
@ExcelProperty(value = "用户", index = 0)
@NotEmpty(message = "用户不能为空", groups = AddGroup.class)
@ExcelProperty(value = "用户昵称", index = 0)
@NotEmpty(message = "用户昵称不能为空", groups = AddGroup.class)
private String nickName;
/**

View File

@@ -523,6 +523,9 @@ public class GenTableServiceImpl implements IGenTableService {
* @param table 业务表信息
*/
public void setPkColumn(GenTable table) {
if (CollUtil.isEmpty(table.getColumns())) {
throw new ServiceException("表【" + table.getTableName() + "】字段为空,请检查表结构");
}
for (GenTableColumn column : table.getColumns()) {
if (column.isPk()) {
table.setPkColumn(column);

View File

@@ -83,7 +83,7 @@ public class SysDictDataController extends BaseController {
}
/**
* 新增字典类型
* 新增字典数据
*/
@SaCheckPermission("system:dict:add")
@Log(title = "字典数据", businessType = BusinessType.INSERT)
@@ -98,7 +98,7 @@ public class SysDictDataController extends BaseController {
}
/**
* 修改保存字典类型
* 修改保存字典数据
*/
@SaCheckPermission("system:dict:edit")
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
@@ -113,12 +113,12 @@ public class SysDictDataController extends BaseController {
}
/**
* 删除字典类型
* 删除字典数据
*
* @param dictCodes 字典code串
*/
@SaCheckPermission("system:dict:remove")
@Log(title = "字典类型", businessType = BusinessType.DELETE)
@Log(title = "字典数据", businessType = BusinessType.DELETE)
@DeleteMapping("/{dictCodes}")
public R<Void> remove(@PathVariable Long[] dictCodes) {
dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));

View File

@@ -21,7 +21,7 @@ public class SysUserOnline {
private String deptName;
/**
* 用户名称
* 用户账号
*/
private String userName;

View File

@@ -32,13 +32,13 @@ public class SysUserExportVo implements Serializable {
/**
* 用户账号
*/
@ExcelProperty(value = "登录名称")
@ExcelProperty(value = "用户账号")
private String userName;
/**
* 用户昵称
*/
@ExcelProperty(value = "用户")
@ExcelProperty(value = "用户")
private String nickName;
/**

View File

@@ -38,13 +38,13 @@ public class SysUserImportVo implements Serializable {
/**
* 用户账号
*/
@ExcelProperty(value = "登录名称")
@ExcelProperty(value = "用户账号")
private String userName;
/**
* 用户昵称
*/
@ExcelProperty(value = "用户")
@ExcelProperty(value = "用户")
private String nickName;
/**

View File

@@ -101,7 +101,7 @@ public interface ISysUserService {
String selectUserPostGroup(Long userId);
/**
* 校验用户名称是否唯一
* 校验用户账号是否唯一
*
* @param user 用户信息
* @return 结果

View File

@@ -229,6 +229,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
@Override
public String getDictLabel(String dictType, String dictValue, String separator) {
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
if (CollUtil.isEmpty(datas)) {
return StringUtils.EMPTY;
}
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel);
if (StringUtils.containsAny(dictValue, separator)) {
return Arrays.stream(dictValue.split(separator))
@@ -250,6 +253,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
@Override
public String getDictValue(String dictType, String dictLabel, String separator) {
List<SysDictDataVo> datas = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
if (CollUtil.isEmpty(datas)) {
return StringUtils.EMPTY;
}
Map<String, String> map = StreamUtils.toMap(datas, SysDictDataVo::getDictLabel, SysDictDataVo::getDictValue);
if (StringUtils.containsAny(dictLabel, separator)) {
return Arrays.stream(dictLabel.split(separator))
@@ -269,6 +275,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
@Override
public Map<String, String> getAllDictByDictType(String dictType) {
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
if (CollUtil.isEmpty(list)) {
return new HashMap<>();
}
// 保证顺序
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (SysDictDataVo vo : list) {
@@ -286,6 +295,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
@Override
public DictTypeDTO getDictType(String dictType) {
SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType);
if (ObjectUtil.isNull(vo)) {
return null;
}
return BeanUtil.toBean(vo, DictTypeDTO.class);
}
@@ -298,6 +310,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
@Override
public List<DictDataDTO> getDictData(String dictType) {
List<SysDictDataVo> list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType);
if (CollUtil.isEmpty(list)) {
return new ArrayList<>();
}
return BeanUtil.copyToList(list, DictDataDTO.class);
}

View File

@@ -120,7 +120,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
/**
* 根据用户ID查询菜单
*
* @param userId 用户名称
* @param userId 用户ID
* @return 菜单列表
*/
@Override

View File

@@ -232,7 +232,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
}
/**
* 校验用户名称是否唯一
* 校验用户账号是否唯一
*
* @param user 用户信息
* @return 结果
@@ -496,6 +496,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
roleList.remove(SystemConstants.SUPER_ADMIN_ID);
}
// 移除超管角色后若无剩余角色,说明仅选了超管角色且不允许分配,显式报错
if (roleList.isEmpty()) {
throw new ServiceException("不允许为普通用户分配超级管理员角色,请至少选择一个其他角色");
}
// 校验是否有权限访问这些角色(含数据权限控制)
if (roleMapper.selectRoleCount(roleList) != roleList.size()) {
throw new ServiceException("没有权限访问角色的数据");
@@ -593,10 +598,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
}
/**
* 通过用户ID查询用户账户
* 通过用户ID查询用户昵称
*
* @param userId 用户ID
* @return 用户账户
* @return 用户昵称
*/
@Override
@Cacheable(cacheNames = CacheNames.SYS_NICKNAME, key = "#userId")
@@ -607,10 +612,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
}
/**
* 通过用户ID查询用户账户
* 通过用户ID查询用户昵称
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户账户
* @return 用户昵称
*/
@Override
public String selectNicknameByIds(String userIds) {
@@ -750,13 +755,13 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
}
/**
* 根据用户 ID 列表查询用户称映射关系
* 根据用户 ID 列表查询用户称映射关系
*
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户
* @return Map其中 key 为用户 IDvalue 为对应的用户
*/
@Override
public Map<Long, String> selectUserNamesByIds(List<Long> userIds) {
public Map<Long, String> selectUserNicksByIds(List<Long> userIds) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyMap();
}

View File

@@ -23,26 +23,6 @@ public interface FlowConstant {
*/
String INITIATOR_DEPT_ID = "initiatorDeptId";
/**
* 委托
*/
String DELEGATE_TASK = "delegateTask";
/**
* 转办
*/
String TRANSFER_TASK = "transferTask";
/**
* 加签
*/
String ADD_SIGNATURE = "addSignature";
/**
* 减签
*/
String REDUCTION_SIGNATURE = "reductionSignature";
/**
* 流程分类Id转名称
*/

View File

@@ -0,0 +1,53 @@
package org.dromara.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 任务操作类型枚举
*
* @author may
*/
@Getter
@AllArgsConstructor
public enum TaskOperationEnum {
/**
* 委派
*/
DELEGATE_TASK("delegateTask", "委派"),
/**
* 转办
*/
TRANSFER_TASK("transferTask", "转办"),
/**
* 加签
*/
ADD_SIGNATURE("addSignature", "加签"),
/**
* 减签
*/
REDUCTION_SIGNATURE("reductionSignature", "减签");
private final String code;
private final String desc;
private static final Map<String, TaskOperationEnum> CODE_MAP = Arrays.stream(values())
.collect(Collectors.toConcurrentMap(TaskOperationEnum::getCode, Function.identity()));
/**
* 根据 code 获取枚举
*/
public static TaskOperationEnum getByCode(String code) {
return CODE_MAP.get(code);
}
}

View File

@@ -61,7 +61,8 @@ public class BackProcessBo implements Serializable {
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
variables = new HashMap<>(16);
return variables;
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;

View File

@@ -23,8 +23,8 @@ public class FlowCopyBo implements Serializable {
private Long userId;
/**
* 用户
* 用户
*/
private String userName;
private String nickName;
}

View File

@@ -30,7 +30,8 @@ public class FlowNextNodeBo implements Serializable {
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
variables = new HashMap<>(16);
return variables;
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;

View File

@@ -53,7 +53,8 @@ public class StartProcessBo implements Serializable {
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
variables = new HashMap<>(16);
return variables;
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;

View File

@@ -40,6 +40,11 @@ public class TaskOperationBo implements Serializable {
@NotNull(message = "任务id不能为空")
private Long taskId;
/**
* 消息类型
*/
private List<String> messageType;
/**
* 意见或备注信息(可选)
*/

View File

@@ -24,10 +24,10 @@ public class FlowCopyVo implements Serializable {
private Long userId;
/**
* 用户
* 用户
*/
@Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "userId")
private String userName;
private String nickName;
public FlowCopyVo(Long userId) {
this.userId = userId;

View File

@@ -80,11 +80,13 @@ public class WorkflowGlobalListener implements GlobalListener {
NodeExtVo nodeExt = nodeExtService.parseNodeExt(ext, variable);
Set<String> copyList = nodeExt.getCopySettings();
if (CollUtil.isNotEmpty(copyList)) {
List<Long> userIds = StreamUtils.toList(copyList, Convert::toLong);
Map<Long, String> nickNameMap = userService.selectUserNicksByIds(userIds);
List<FlowCopyBo> list = StreamUtils.toList(copyList, x -> {
FlowCopyBo bo = new FlowCopyBo();
Long id = Convert.toLong(x);
bo.setUserId(id);
bo.setUserName(userService.selectUserNameById(id));
bo.setNickName(nickNameMap.getOrDefault(id, StringUtils.EMPTY));
return bo;
});
variable.put(FlowConstant.FLOW_COPY_LIST, list);
@@ -159,7 +161,7 @@ public class WorkflowGlobalListener implements GlobalListener {
flowTask.setPermissionList(List.of(userIdArray));
// 移除已处理的状态变量
variable.remove(nodeKey);
FlowEngine.insService().removeVariables(flowTask.getInstanceId(),nodeKey);
FlowEngine.insService().removeVariables(flowTask.getInstanceId(), nodeKey);
}
}

View File

@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.UserDTO;
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;
@@ -126,6 +127,9 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
@Override
public String applyNodeCode(Long definitionId) {
List<Node> firstBetweenNode = FlowEngine.nodeService().getFirstBetweenNode(definitionId, new HashMap<>());
if (CollUtil.isEmpty(firstBetweenNode)) {
throw new ServiceException("流程定义缺少申请人节点,请检查流程定义配置");
}
return firstBetweenNode.get(0).getNodeCode();
}
}

View File

@@ -111,8 +111,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
@Override
public FlowInstanceVo queryByBusinessId(Long businessId) {
FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
if (ObjectUtil.isNull(instance)) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
}
FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
Definition definition = defService.getById(instanceVo.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF);
}
instanceVo.setFlowName(definition.getFlowName());
instanceVo.setFlowCode(definition.getFlowCode());
instanceVo.setVersion(definition.getVersion());
@@ -383,6 +389,9 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
@Override
public Map<String, Object> instanceVariable(Long instanceId) {
FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
if (ObjectUtil.isNull(flowInstance)) {
throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
}
Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
List<Map<String, Object>> variableList = variableMap.entrySet().stream()
.map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))

View File

@@ -1,6 +1,7 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
import org.dromara.common.core.domain.model.TaskAssigneeBody;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -125,7 +127,14 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
* 保存前的数据校验
*/
private void validEntityBeforeSave(FlowSpel entity){
//TODO 做一些数据校验,如唯一约束
if (StringUtils.isNotBlank(entity.getViewSpel())) {
boolean exists = baseMapper.exists(new LambdaQueryWrapper<FlowSpel>()
.eq(FlowSpel::getViewSpel, entity.getViewSpel())
.ne(ObjectUtil.isNotNull(entity.getId()), FlowSpel::getId, entity.getId()));
if (exists) {
throw new ServiceException("SpEL表达式已存在请勿重复添加");
}
}
}
/**
@@ -137,7 +146,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
if (isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;

View File

@@ -244,7 +244,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
Map<Long, String> rawMap = switch (type) {
case USER -> userService.selectUserNamesByIds(longIds);
case USER -> userService.selectUserNicksByIds(longIds);
case ROLE -> roleService.selectRoleNamesByIds(longIds);
case DEPT -> deptService.selectDeptNamesByIds(longIds);
case POST -> postService.selectPostNamesByIds(longIds);

View File

@@ -44,8 +44,8 @@ import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.TaskAssigneeType;
import org.dromara.workflow.common.enums.TaskOperationEnum;
import org.dromara.workflow.common.enums.TaskStatusEnum;
import org.dromara.workflow.domain.FlowInstanceBizExt;
import org.dromara.workflow.domain.bo.*;
@@ -127,6 +127,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
// 已存在流程
BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus());
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId()));
if (CollUtil.isEmpty(taskList)) {
throw new ServiceException("流程实例缺少任务,请检查流程定义配置");
}
taskService.mergeVariable(flowInstance, variables);
insService.updateById(flowInstance);
StartProcessReturnDTO dto = new StartProcessReturnDTO();
@@ -143,9 +146,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
throw new ServiceException("流程【" + startProcessBo.getFlowCode() + "】未发布,请先在流程设计器中发布流程定义");
}
Dict dict = JsonUtils.parseMap(definition.getExt());
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
variables.put(FlowConstant.AUTO_PASS, autoPass);
variables.put(FlowConstant.BUSINESS_CODE, this.generateBusinessCode(bizExt));
boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(AUTO_PASS);
variables.put(AUTO_PASS, autoPass);
variables.put(BUSINESS_CODE, this.generateBusinessCode(bizExt));
FlowParams flowParams = FlowParams.build()
.handler(startProcessBo.getHandler())
.flowCode(startProcessBo.getFlowCode())
@@ -156,6 +159,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
this.buildFlowInstanceBizExt(instance, bizExt);
// 申请人执行流程
List<Task> taskList = taskService.list(new FlowTask().setInstanceId(instance.getId()));
if (CollUtil.isEmpty(taskList)) {
throw new ServiceException("流程启动失败,未生成任务");
}
if (taskList.size() > 1) {
throw new ServiceException("请检查流程第一个环节是否为申请人!");
}
@@ -207,11 +213,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
// 设置抄送人
Map<String, Object> variables = completeTaskBo.getVariables();
variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
variables.put(FLOW_COPY_LIST, flowCopyList);
// 消息类型
variables.put(FlowConstant.MESSAGE_TYPE, messageType);
variables.put(MESSAGE_TYPE, messageType);
// 消息通知
variables.put(FlowConstant.MESSAGE_NOTICE, notice);
variables.put(MESSAGE_NOTICE, notice);
FlowTask flowTask = flowTaskMapper.selectById(taskId);
@@ -219,9 +225,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
throw new ServiceException("流程任务不存在或任务已审批!");
}
Instance ins = insService.getById(flowTask.getInstanceId());
if (ObjectUtil.isNull(ins)) {
throw new ServiceException("流程实例不存在");
}
// 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
variables.put(FlowConstant.SUBMIT, true);
variables.put(SUBMIT, true);
}
// 设置弹窗处理人
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
@@ -274,9 +283,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
flowParams.
message("流程引擎自动审批!").
variable(Map.of(
FlowConstant.SUBMIT, false,
FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
SUBMIT, false,
FLOW_COPY_LIST, Collections.emptyList(),
MESSAGE_NOTICE, StringUtils.EMPTY));
skipTask(task.getId(), flowParams, instanceId, true);
}
}
@@ -341,7 +350,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
FlowParams flowParams = FlowParams.build()
.skipType(SkipType.NONE.getKey())
.hisStatus(TaskStatusEnum.COPY.getStatus())
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName));
.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getNickName));
HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams);
hisTask.setCreateTime(updateTime);
hisTask.setUpdateTime(updateTime);
@@ -482,15 +491,18 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
throw new ServiceException("任务不存在!");
}
Instance inst = insService.getById(task.getInstanceId());
if (ObjectUtil.isNull(inst)) {
throw new ServiceException("流程实例不存在");
}
BusinessStatusEnum.checkBackStatus(inst.getFlowStatus());
Long definitionId = task.getDefinitionId();
String applyNodeCode = flwCommonService.applyNodeCode(definitionId);
Map<String, Object> variable = new HashMap<>();
// 消息类型
variable.put(FlowConstant.MESSAGE_TYPE, messageType);
variable.put(MESSAGE_TYPE, messageType);
// 消息通知
variable.put(FlowConstant.MESSAGE_NOTICE, notice);
variable.put(MESSAGE_NOTICE, notice);
FlowParams flowParams = FlowParams.build()
.nodeCode(bo.getNodeCode())
@@ -513,6 +525,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
@Override
public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
FlowTask task = flowTaskMapper.selectById(taskId);
if (ObjectUtil.isNull(task)) {
throw new ServiceException("任务不存在!");
}
List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
if (!CollUtil.isNotEmpty(nodeCodes)) {
return nodeCodes;
@@ -597,7 +612,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
}
FlowTaskVo flowTaskVo = BeanUtil.toBean(task, FlowTaskVo.class);
Instance instance = insService.getById(task.getInstanceId());
if (ObjectUtil.isNull(instance)) {
throw new ServiceException("流程实例不存在");
}
Definition definition = defService.getById(task.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
throw new ServiceException("流程定义不存在");
}
flowTaskVo.setFlowStatus(instance.getFlowStatus());
flowTaskVo.setVersion(definition.getVersion());
flowTaskVo.setFlowCode(definition.getFlowCode());
@@ -640,11 +661,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
Long taskId = bo.getTaskId();
Map<String, Object> variables = bo.getVariables();
Task task = taskService.getById(taskId);
if (ObjectUtil.isNull(task)) {
throw new ServiceException("任务不存在!");
}
Instance instance = insService.getById(task.getInstanceId());
if (ObjectUtil.isNull(instance)) {
throw new ServiceException("流程实例不存在");
}
Definition definition = defService.getById(task.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
throw new ServiceException("流程定义不存在");
}
Map<String, Object> mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables);
// 获取下一节点列表
List<Node> nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable);
if (CollUtil.isEmpty(nextNodeList)) {
return new ArrayList<>();
}
List<FlowNode> nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class);
// 只获取中间节点
nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType()));
@@ -719,13 +752,19 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
TaskOperationEnum op = TaskOperationEnum.getByCode(taskOperation);
if (op == null) {
log.error("Invalid operation type:{} ", taskOperation);
throw new ServiceException("Invalid operation type " + taskOperation);
}
FlowParams flowParams = FlowParams.build().message(bo.getMessage());
if (LoginHelper.isSuperAdmin()) {
flowParams.ignore(true);
}
// 根据操作类型构建 FlowParams
switch (taskOperation) {
switch (op) {
case DELEGATE_TASK, TRANSFER_TASK -> {
ValidatorUtils.validate(bo, AddGroup.class);
flowParams.addHandlers(Collections.singletonList(bo.getUserId()));
@@ -738,47 +777,63 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
ValidatorUtils.validate(bo, EditGroup.class);
flowParams.reductionHandlers(bo.getUserIds());
}
default -> {
log.error("Invalid operation type:{} ", taskOperation);
throw new ServiceException("Invalid operation type " + taskOperation);
}
}
Long taskId = bo.getTaskId();
Task task = taskService.getById(taskId);
if (ObjectUtil.isNull(task)) {
throw new ServiceException("任务不存在!");
}
FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId());
if (ADD_SIGNATURE.equals(taskOperation) || REDUCTION_SIGNATURE.equals(taskOperation)) {
if (ObjectUtil.isNull(flowNode)) {
throw new ServiceException("流程节点不存在");
}
if (op == TaskOperationEnum.ADD_SIGNATURE || op == TaskOperationEnum.REDUCTION_SIGNATURE) {
if (CooperateType.isOrSign(flowNode.getNodeRatio())) {
throw new ServiceException(task.getNodeName() + "不是会签或票签节点!");
}
}
// 设置任务状态并执行对应的任务操作
switch (taskOperation) {
//委派任务
boolean result = false;
switch (op) {
case DELEGATE_TASK -> {
flowParams.hisStatus(TaskStatusEnum.DEPUTE.getStatus());
return taskService.depute(taskId, flowParams);
result = taskService.depute(taskId, flowParams);
}
//转办任务
case TRANSFER_TASK -> {
flowParams.hisStatus(TaskStatusEnum.TRANSFER.getStatus());
return taskService.transfer(taskId, flowParams);
result = taskService.transfer(taskId, flowParams);
}
//加签,增加办理人
case ADD_SIGNATURE -> {
flowParams.hisStatus(TaskStatusEnum.SIGN.getStatus());
return taskService.addSignature(taskId, flowParams);
result = taskService.addSignature(taskId, flowParams);
}
//减签,减少办理人
case REDUCTION_SIGNATURE -> {
flowParams.hisStatus(TaskStatusEnum.SIGN_OFF.getStatus());
return taskService.reductionSignature(taskId, flowParams);
}
default -> {
log.error("Invalid operation type:{} ", taskOperation);
throw new ServiceException("Invalid operation type " + taskOperation);
result = taskService.reductionSignature(taskId, flowParams);
}
}
// 操作执行成功后再发送消息
if (result && CollUtil.isNotEmpty(bo.getMessageType())) {
List<Long> userIdList = new ArrayList<>();
if (StrUtil.isNotBlank(bo.getUserId())) {
userIdList.add(Convert.toLong(bo.getUserId()));
}
if (CollUtil.isNotEmpty(bo.getUserIds())) {
userIdList.addAll(StreamUtils.toList(bo.getUserIds(), Convert::toLong));
}
if (CollUtil.isNotEmpty(userIdList)) {
flwCommonService.sendMessage(
bo.getMessageType(),
StringUtils.isNotBlank(bo.getMessage()) ? bo.getMessage() : "单据「" + op.getDesc() + "」通知",
"单据「" + op.getDesc() + "」提醒",
userService.selectListByIds(userIdList)
);
}
}
return result;
}
/**