Browse Source

[feat]

1、增加工作流权限按钮
master
杨威 2 months ago
parent
commit
0813b2127d
  1. 46
      dk-api/api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java
  2. 15
      dk-modules/sample/src/main/java/org/dromara/sample/feign/BusinessTaskFeign.java
  3. 80
      dk-modules/workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
  4. 32
      dk-modules/workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java
  5. 38
      dk-modules/workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java
  6. 34
      dk-modules/workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java
  7. 73
      dk-modules/workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
  8. 250
      dk-modules/workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
  9. 177
      dk-modules/workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
  10. 1
      dk-modules/workflow/zhFonts/.uuid
  11. BIN
      dk-modules/workflow/zhFonts/SIMSUN.TTC
  12. 4
      dk-modules/workflow/zhFonts/fonts.dir
  13. 4
      dk-modules/workflow/zhFonts/fonts.scale
  14. 2
      pom.xml

46
dk-api/api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java

@ -0,0 +1,46 @@
package org.dromara.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 字典类型视图对象 sys_dict_type
*
* @author Michelle.Chung
*/
@Data
public class RemoteDictTypeVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 字典主键
*/
private Long dictId;
/**
* 字典名称
*/
private String dictName;
/**
* 字典类型
*/
private String dictType;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
}

15
dk-modules/sample/src/main/java/org/dromara/sample/feign/BusinessTaskFeign.java

@ -0,0 +1,15 @@
package org.dromara.sample.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "gateway",path = "business")
public interface BusinessTaskFeign {
@PostMapping("/update/status")
void updateTaskStatus(@RequestParam String waylineId,
@RequestParam String status,
@RequestParam(required = false) String jobId,
@RequestParam(required = false) String jobName);
}

80
dk-modules/workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java

@ -0,0 +1,80 @@
package org.dromara.workflow.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 按钮权限枚举
*
* @author AprilWind
*/
@Getter
@AllArgsConstructor
public enum ButtonPermissionEnum implements NodeExtEnum {
/**
* 是否弹窗选人
*/
POP("是否弹窗选人", "pop", false),
/**
* 是否能委托
*/
TRUST("是否能委托", "trust", false),
/**
* 是否能转办
*/
TRANSFER("是否能转办", "transfer", false),
/**
* 是否能抄送
*/
COPY("是否能抄送", "copy", false),
/**
* 是否显示退回
*/
BACK("是否显示退回", "back", true),
/**
* 是否能加签
*/
ADD_SIGN("是否能加签", "addSign", false),
/**
* 是否能减签
*/
SUB_SIGN("是否能减签", "subSign", false),
/**
* 是否能终止
*/
TERMINATION("是否能终止", "termination", false),
/**
* 任务指派
*/
TASK_ASSIGN("任务指派","taskAssign",true),
/**
* 完结提交
*/
COM_SUB("完结提交","comSub",false),
/**
* 提交
*/
SUBMIT("提交","submit",false),
/**
* 违法用地认证
*/
ILLEGAL_AUTH("违法用地认证","illegalAuth",false);
private final String label;
private final String value;
private final boolean selected;
}

32
dk-modules/workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java

@ -0,0 +1,32 @@
package org.dromara.workflow.common.enums;
/**
* 节点扩展属性枚举
*
* @author AprilWind
*/
public interface NodeExtEnum {
/**
* 选项label
*
* @return 选项label
*/
String getLabel();
/**
* 选项值
*
* @return 选项值
*/
String getValue();
/**
* 是否默认选中
*
* @return 是否默认选中
*/
boolean isSelected();
}

38
dk-modules/workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java

@ -0,0 +1,38 @@
package org.dromara.workflow.domain.bo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 下一节点信息
*
* @author may
*/
@Data
public class FlowNextNodeBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 任务id
*/
private Long taskId;
/**
* 流程变量
*/
private Map<String, Object> variables;
public Map<String, Object> getVariables() {
if (variables == null) {
return new HashMap<>(16);
}
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
return variables;
}
}

34
dk-modules/workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java

@ -0,0 +1,34 @@
package org.dromara.workflow.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 按钮权限
*
* @author may
* @date 2025-02-28
*/
@Data
public class ButtonPermission implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 枚举路径
*/
private String code;
/**
* 按钮编码
*/
private String value;
/**
* 是否显示
*/
private boolean show;
}

73
dk-modules/workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java

@ -0,0 +1,73 @@
package org.dromara.workflow.service;
import org.dromara.warm.flow.core.entity.User;
import org.dromara.warm.flow.core.service.UserService;
import java.util.List;
import java.util.Set;
/**
* 通用 工作流服务
*
* @author LionLi
*/
public interface IFlwCommonService {
/**
* 获取工作流用户service
*/
UserService getFlowUserService();
/**
* 构建工作流用户
*
* @param userList 办理用户
* @param taskId 任务ID
* @return 用户
*/
Set<User> buildUser(List<User> userList, Long taskId);
/**
* 构建工作流用户
*
* @param userIdList 办理用户
* @param taskId 任务ID
* @return 用户
*/
Set<User> buildFlowUser(List<String> userIdList, Long taskId);
/**
* 发送消息
*
* @param flowName 流程定义名称
* @param messageType 消息类型
* @param message 消息内容为空则发送默认配置的消息内容
*/
void sendMessage(String flowName, Long instId, List<String> messageType, String message);
/**
* 驳回
*
* @param message 审批意见
* @param instanceId 流程实例id
* @param targetNodeCode 目标节点
* @param flowStatus 流程状态
* @param flowHisStatus 节点操作状态
*/
void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus);
/**
* 申请人节点编码
*
* @param definitionId 流程定义id
* @return 申请人节点编码
*/
String applyNodeCode(Long definitionId);
/**
* 删除运行中的任务
*
* @param taskIds 任务id
*/
void deleteRunTask(List<Long> taskIds);
}

250
dk-modules/workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java

@ -0,0 +1,250 @@
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.resource.api.RemoteMailService;
import org.dromara.resource.api.RemoteMessageService;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.warm.flow.core.constant.ExceptionCons;
import org.dromara.warm.flow.core.dto.FlowParams;
import org.dromara.warm.flow.core.entity.Node;
import org.dromara.warm.flow.core.entity.Task;
import org.dromara.warm.flow.core.entity.User;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.enums.SkipType;
import org.dromara.warm.flow.core.service.NodeService;
import org.dromara.warm.flow.core.service.TaskService;
import org.dromara.warm.flow.core.service.UserService;
import org.dromara.warm.flow.core.utils.AssertUtil;
import org.dromara.warm.flow.orm.entity.FlowNode;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.entity.FlowUser;
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.enums.MessageTypeEnum;
import org.dromara.workflow.common.enums.TaskAssigneeType;
import org.dromara.workflow.service.IFlwCommonService;
import org.dromara.workflow.service.IFlwTaskAssigneeService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 工作流工具
*
* @author LionLi
*/
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwCommonServiceImpl implements IFlwCommonService {
private final FlowNodeMapper flowNodeMapper;
private final FlowTaskMapper flowTaskMapper;
private final UserService userService;
private final TaskService taskService;
private final NodeService nodeService;
@DubboReference
private RemoteMessageService remoteMessageService;
@DubboReference
private RemoteMailService remoteMailService;
/**
* 获取工作流用户service
*/
@Override
public UserService getFlowUserService() {
return userService;
}
/**
* 构建工作流用户
*
* @param userList 办理用户
* @param taskId 任务ID
* @return 用户
*/
@Override
public Set<User> buildUser(List<User> userList, Long taskId) {
if (CollUtil.isEmpty(userList)) {
return Set.of();
}
Set<User> list = new HashSet<>();
Set<String> processedBySet = new HashSet<>();
IFlwTaskAssigneeService taskAssigneeService = SpringUtils.getBean(IFlwTaskAssigneeService.class);
for (User user : userList) {
// 根据 processedBy 前缀判断处理人类型,分别获取用户列表
List<RemoteUserVo> users = taskAssigneeService.fetchUsersByStorageId(user.getProcessedBy());
// 转换为 FlowUser 并添加到结果集合
if (CollUtil.isNotEmpty(users)) {
users.forEach(dto -> {
String processedBy = String.valueOf(dto.getUserId());
if (!processedBySet.contains(processedBy)) {
FlowUser flowUser = new FlowUser();
flowUser.setType(user.getType());
flowUser.setProcessedBy(processedBy);
flowUser.setAssociated(taskId);
list.add(flowUser);
processedBySet.add(processedBy);
}
});
}
}
return list;
}
/**
* 构建工作流用户
*
* @param userIdList 办理用户
* @param taskId 任务ID
* @return 用户
*/
@Override
public Set<User> buildFlowUser(List<String> userIdList, Long taskId) {
if (CollUtil.isEmpty(userIdList)) {
return Set.of();
}
Set<User> list = new HashSet<>();
Set<String> processedBySet = new HashSet<>();
for (String userId : userIdList) {
if (!processedBySet.contains(userId)) {
FlowUser flowUser = new FlowUser();
flowUser.setType(TaskAssigneeType.APPROVER.getCode());
flowUser.setProcessedBy(String.valueOf(userId));
flowUser.setAssociated(taskId);
list.add(flowUser);
processedBySet.add(String.valueOf(userId));
}
}
return list;
}
/**
* 发送消息
*
* @param flowName 流程定义名称
* @param messageType 消息类型
* @param message 消息内容为空则发送默认配置的消息内容
*/
@Override
public void sendMessage(String flowName, Long instId, List<String> messageType, String message) {
IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
List<RemoteUserVo> userList = new ArrayList<>();
List<FlowTask> list = flwTaskService.selectByInstId(instId);
if (StringUtils.isBlank(message)) {
message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。";
}
for (Task task : list) {
List<RemoteUserVo> users = flwTaskService.currentTaskAllUser(task.getId());
if (CollUtil.isNotEmpty(users)) {
userList.addAll(users);
}
}
if (CollUtil.isNotEmpty(userList)) {
for (String code : messageType) {
MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
if (ObjectUtil.isNotEmpty(messageTypeEnum)) {
switch (messageTypeEnum) {
case SYSTEM_MESSAGE:
List<Long> userIds = StreamUtils.toList(userList, RemoteUserVo::getUserId).stream().distinct().collect(Collectors.toList());
remoteMessageService.publishMessage(userIds, message);
break;
case EMAIL_MESSAGE:
remoteMailService.send(StreamUtils.join(userList, RemoteUserVo::getEmail), "单据审批提醒", message);
break;
case SMS_MESSAGE:
//todo 短信发送
break;
default:
throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
}
}
}
}
}
/**
* 驳回
*
* @param message 审批意见
* @param instanceId 流程实例id
* @param targetNodeCode 目标节点
* @param flowStatus 流程状态
* @param flowHisStatus 节点操作状态
*/
@Override
public void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) {
IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class);
List<FlowTask> list = flwTaskService.selectByInstId(instanceId);
if (CollUtil.isNotEmpty(list)) {
List<FlowTask> tasks = StreamUtils.filter(list, e -> e.getNodeCode().equals(targetNodeCode));
if (list.size() == tasks.size()) {
return;
}
}
for (FlowTask task : list) {
List<RemoteUserVo> userList = flwTaskService.currentTaskAllUser(task.getId());
FlowParams flowParams = FlowParams.build();
flowParams.nodeCode(targetNodeCode);
flowParams.message(message);
flowParams.skipType(SkipType.PASS.getKey());
flowParams.flowStatus(flowStatus).hisStatus(flowHisStatus);
flowParams.ignore(true);
//解决会签没权限问题
if (CollUtil.isNotEmpty(userList)) {
flowParams.handler(userList.get(0).getUserId().toString());
}
taskService.skip(task.getId(), flowParams);
}
//解决会签多人审批问题
backTask(message, instanceId, targetNodeCode, flowStatus, flowHisStatus);
}
/**
* 申请人节点编码
*
* @param definitionId 流程定义id
* @return 申请人节点编码
*/
@Override
public String applyNodeCode(Long definitionId) {
//获取已发布的流程节点
List<FlowNode> flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper<FlowNode>().eq(FlowNode::getDefinitionId, definitionId));
AssertUtil.isTrue(CollUtil.isEmpty(flowNodes), ExceptionCons.NOT_PUBLISH_NODE);
Node startNode = flowNodes.stream().filter(t -> NodeType.isStart(t.getNodeType())).findFirst().orElse(null);
AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE);
Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
return nextNode.getNodeCode();
}
/**
* 删除运行中的任务
*
* @param taskIds 任务id
*/
@Override
public void deleteRunTask(List<Long> taskIds) {
if (CollUtil.isEmpty(taskIds)) {
return;
}
userService.deleteByTaskIds(taskIds);
flowTaskMapper.deleteByIds(taskIds);
}
}

177
dk-modules/workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java

@ -0,0 +1,177 @@
package org.dromara.workflow.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.system.api.RemoteDictService;
import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
import org.dromara.warm.flow.ui.service.NodeExtService;
import org.dromara.warm.flow.ui.vo.NodeExt;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.ButtonPermissionEnum;
import org.dromara.workflow.common.enums.NodeExtEnum;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 流程设计器-节点扩展属性
*
* @author AprilWind
*/
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@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<>();
static {
CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(),
Map.of("label", "权限按钮", "type", 4, "must", false, "multiple", true));
}
@DubboReference
private RemoteDictService remoteDictService;
/**
* 获取节点扩展属性
*
* @return 节点扩展属性列表
*/
@Override
public List<NodeExt> getNodeExt() {
List<NodeExt> nodeExtList = new ArrayList<>();
// 构建按钮权限页面
nodeExtList.add(buildNodeExt(PERMISSION_TAB, PERMISSION_TAB_NAME, TYPE_NEW_TAB,
List.of(ButtonPermissionEnum.class)));
return nodeExtList;
}
/**
* 构建一个 `NodeExt` 对象
*
* @param code 唯一编码
* @param name 名称新页签时作为页签名称
* @param type 节点类型1: 基础设置2: 新页签
* @param sources 数据来源枚举类或字典类型
* @return 构建的 `NodeExt` 对象
*/
@SuppressWarnings("unchecked cast")
private NodeExt buildNodeExt(String code, String name, int type, List<Object> sources) {
NodeExt nodeExt = new NodeExt();
nodeExt.setCode(code);
nodeExt.setType(type);
nodeExt.setName(name);
nodeExt.setChilds(sources.stream()
.map(source -> {
if (source instanceof Class<?> clazz && NodeExtEnum.class.isAssignableFrom(clazz)) {
return buildChildNode((Class<? extends NodeExtEnum>) clazz);
} else if (source instanceof String dictType) {
return buildChildNode(dictType);
}
return null;
})
.filter(ObjectUtil::isNotNull)
.toList()
);
return nodeExt;
}
/**
* 根据枚举类型构建一个 `ChildNode` 对象
*
* @param enumClass 枚举类必须实现 `NodeExtEnum` 接口
* @return 构建的 `ChildNode` 对象
*/
private NodeExt.ChildNode buildChildNode(Class<? extends NodeExtEnum> enumClass) {
if (!enumClass.isEnum()) {
return null;
}
String simpleName = enumClass.getSimpleName();
NodeExt.ChildNode childNode = buildChildNodeMap(simpleName);
// 编码,此json中唯
childNode.setCode(simpleName);
// 字典,下拉框和复选框时用到
childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
.map(NodeExtEnum.class::cast)
.map(x ->
new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected())
).toList());
return childNode;
}
/**
* 根据字典类型构建 `ChildNode` 对象
*
* @param dictType 字典类型
* @return 构建的 `ChildNode` 对象
*/
private NodeExt.ChildNode buildChildNode(String dictType) {
RemoteDictTypeVo dictTypeDTO = remoteDictService.selectDictTypeByType(dictType);
if (ObjectUtil.isNull(dictTypeDTO)) {
return null;
}
NodeExt.ChildNode childNode = buildChildNodeMap(dictType);
// 编码,此json中唯一
childNode.setCode(dictType);
// label名称
childNode.setLabel(dictTypeDTO.getDictName());
// 描述
childNode.setDesc(dictTypeDTO.getRemark());
// 字典,下拉框和复选框时用到
childNode.setDict(remoteDictService.selectDictDataByType(dictType)
.stream().map(x ->
new NodeExt.DictItem(x.getDictLabel(), x.getDictValue(), Convert.toBool(x.getIsDefault(), false))
).toList());
return childNode;
}
/**
* 根据 CHILD_NODE_MAP 中的配置信息构建一个基本的 ChildNode 对象
* 该方法用于设置 ChildNode 的常规属性例如 labeltype是否必填是否多选等
*
* @param key CHILD_NODE_MAP key
* @return 返回构建好的 ChildNode 对象
*/
private NodeExt.ChildNode buildChildNodeMap(String key) {
NodeExt.ChildNode childNode = new NodeExt.ChildNode();
Map<String, Object> map = CHILD_NODE_MAP.get(key);
// label名称
childNode.setLabel((String) map.get("label"));
// 1:输入框 2:输入框 3:下拉框 4:选择框
childNode.setType(Convert.toInt(map.get("type"), 1));
// 是否必填
childNode.setMust(Convert.toBool(map.get("must"), false));
// 是否多选
childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
return childNode;
}
}

1
dk-modules/workflow/zhFonts/.uuid

@ -0,0 +1 @@
3f2ee348-0303-40ca-bf03-03f48d2d2141

BIN
dk-modules/workflow/zhFonts/SIMSUN.TTC

Binary file not shown.

4
dk-modules/workflow/zhFonts/fonts.dir

@ -0,0 +1,4 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

4
dk-modules/workflow/zhFonts/fonts.scale

@ -0,0 +1,4 @@
3
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r

2
pom.xml

@ -74,7 +74,7 @@
<aliyun-oss.version>3.12.0</aliyun-oss.version>
<!--工作流配置-->
<warm-flow.version>1.6.8</warm-flow.version>
<warm-flow.version>1.6.9</warm-flow.version>
<geotools.version>23.2</geotools.version>
<!--PostgreSQL配置-->
<postgresql.version>42.6.2</postgresql.version>

Loading…
Cancel
Save