28 changed files with 345 additions and 762 deletions
@ -1,206 +0,0 @@ |
|||||
package org.dromara.workflow.utils; |
|
||||
|
|
||||
import cn.hutool.core.collection.CollUtil; |
|
||||
import cn.hutool.core.util.ObjectUtil; |
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
||||
import lombok.AccessLevel; |
|
||||
import lombok.NoArgsConstructor; |
|
||||
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.mail.utils.MailUtils; |
|
||||
import org.dromara.common.sse.dto.SseMessageDto; |
|
||||
import org.dromara.common.sse.utils.SseMessageUtils; |
|
||||
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.enums.MessageTypeEnum; |
|
||||
import org.dromara.workflow.service.IFlwTaskAssigneeService; |
|
||||
import org.dromara.workflow.service.IFlwTaskService; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
import java.util.HashSet; |
|
||||
import java.util.List; |
|
||||
import java.util.Set; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 工作流工具 |
|
||||
* |
|
||||
* @author may |
|
||||
*/ |
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE) |
|
||||
public class WorkflowUtils { |
|
||||
|
|
||||
private static final IFlwTaskAssigneeService TASK_ASSIGNEE_SERVICE = SpringUtils.getBean(IFlwTaskAssigneeService.class); |
|
||||
private static final IFlwTaskService FLW_TASK_SERVICE = SpringUtils.getBean(IFlwTaskService.class); |
|
||||
private static final FlowNodeMapper FLOW_NODE_MAPPER = SpringUtils.getBean(FlowNodeMapper.class); |
|
||||
private static final FlowTaskMapper FLOW_TASK_MAPPER = SpringUtils.getBean(FlowTaskMapper.class); |
|
||||
private static final UserService USER_SERVICE = SpringUtils.getBean(UserService.class); |
|
||||
private static final TaskService TASK_SERVICE = SpringUtils.getBean(TaskService.class); |
|
||||
private static final NodeService NODE_SERVICE = SpringUtils.getBean(NodeService.class); |
|
||||
|
|
||||
/** |
|
||||
* 获取工作流用户service |
|
||||
*/ |
|
||||
public static UserService getFlowUserService() { |
|
||||
return USER_SERVICE; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 构建工作流用户 |
|
||||
* |
|
||||
* @param userList 办理用户 |
|
||||
* @param taskId 任务ID |
|
||||
* @return 用户 |
|
||||
*/ |
|
||||
public static 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<>(); |
|
||||
for (User user : userList) { |
|
||||
// 根据 processedBy 前缀判断处理人类型,分别获取用户列表
|
|
||||
List<RemoteUserVo> users = TASK_ASSIGNEE_SERVICE.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 flowName 流程定义名称 |
|
||||
* @param messageType 消息类型 |
|
||||
* @param message 消息内容,为空则发送默认配置的消息内容 |
|
||||
*/ |
|
||||
public static void sendMessage(String flowName, Long instId, List<String> messageType, String message) { |
|
||||
List<RemoteUserVo> userList = new ArrayList<>(); |
|
||||
List<FlowTask> list = FLW_TASK_SERVICE.selectByInstId(instId); |
|
||||
if (StringUtils.isBlank(message)) { |
|
||||
message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。"; |
|
||||
} |
|
||||
for (Task task : list) { |
|
||||
List<RemoteUserVo> users = FLW_TASK_SERVICE.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: |
|
||||
SseMessageDto dto = new SseMessageDto(); |
|
||||
dto.setUserIds(StreamUtils.toList(userList, RemoteUserVo::getUserId).stream().distinct().collect(Collectors.toList())); |
|
||||
dto.setMessage(message); |
|
||||
SseMessageUtils.publishMessage(dto); |
|
||||
break; |
|
||||
case EMAIL_MESSAGE: |
|
||||
MailUtils.sendText(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 节点操作状态 |
|
||||
*/ |
|
||||
public static void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) { |
|
||||
List<FlowTask> list = FLW_TASK_SERVICE.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 = FLW_TASK_SERVICE.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()); |
|
||||
} |
|
||||
TASK_SERVICE.skip(task.getId(), flowParams); |
|
||||
} |
|
||||
//解决会签多人审批问题
|
|
||||
backTask(message, instanceId, targetNodeCode, flowStatus, flowHisStatus); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 申请人节点编码 |
|
||||
* |
|
||||
* @param definitionId 流程定义id |
|
||||
* @return 申请人节点编码 |
|
||||
*/ |
|
||||
public static String applyNodeCode(Long definitionId) { |
|
||||
//获取已发布的流程节点
|
|
||||
List<FlowNode> flowNodes = FLOW_NODE_MAPPER.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 = NODE_SERVICE.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey()); |
|
||||
return nextNode.getNodeCode(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 删除运行中的任务 |
|
||||
* |
|
||||
* @param taskIds 任务id |
|
||||
*/ |
|
||||
public static void deleteRunTask(List<Long> taskIds) { |
|
||||
if (CollUtil.isEmpty(taskIds)) { |
|
||||
return; |
|
||||
} |
|
||||
USER_SERVICE.deleteByTaskIds(taskIds); |
|
||||
FLOW_TASK_MAPPER.deleteByIds(taskIds); |
|
||||
} |
|
||||
} |
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.4 KiB |
@ -1,216 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html> |
|
||||
|
|
||||
<head> |
|
||||
<meta charset="utf-8"> |
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
|
||||
<meta name="renderer" content="webkit"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
|
||||
<link rel="icon" href="./ico/favicon-FqvijpIH.ico"> |
|
||||
<title>Warm-Flow设计器</title> |
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> |
|
||||
<style> |
|
||||
html, |
|
||||
body, |
|
||||
#app { |
|
||||
height: 100%; |
|
||||
margin: 0px; |
|
||||
padding: 0px; |
|
||||
} |
|
||||
|
|
||||
.chromeframe { |
|
||||
margin: 0.2em 0; |
|
||||
background: #ccc; |
|
||||
color: #000; |
|
||||
padding: 0.2em 0; |
|
||||
} |
|
||||
|
|
||||
#loader-wrapper { |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
z-index: 999999; |
|
||||
} |
|
||||
|
|
||||
#loader { |
|
||||
display: block; |
|
||||
position: relative; |
|
||||
left: 50%; |
|
||||
top: 50%; |
|
||||
width: 150px; |
|
||||
height: 150px; |
|
||||
margin: -75px 0 0 -75px; |
|
||||
border-radius: 50%; |
|
||||
border: 3px solid transparent; |
|
||||
border-top-color: #FFF; |
|
||||
-webkit-animation: spin 2s linear infinite; |
|
||||
-ms-animation: spin 2s linear infinite; |
|
||||
-moz-animation: spin 2s linear infinite; |
|
||||
-o-animation: spin 2s linear infinite; |
|
||||
animation: spin 2s linear infinite; |
|
||||
z-index: 1001; |
|
||||
} |
|
||||
|
|
||||
#loader:before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
top: 5px; |
|
||||
left: 5px; |
|
||||
right: 5px; |
|
||||
bottom: 5px; |
|
||||
border-radius: 50%; |
|
||||
border: 3px solid transparent; |
|
||||
border-top-color: #FFF; |
|
||||
-webkit-animation: spin 3s linear infinite; |
|
||||
-moz-animation: spin 3s linear infinite; |
|
||||
-o-animation: spin 3s linear infinite; |
|
||||
-ms-animation: spin 3s linear infinite; |
|
||||
animation: spin 3s linear infinite; |
|
||||
} |
|
||||
|
|
||||
#loader:after { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
top: 15px; |
|
||||
left: 15px; |
|
||||
right: 15px; |
|
||||
bottom: 15px; |
|
||||
border-radius: 50%; |
|
||||
border: 3px solid transparent; |
|
||||
border-top-color: #FFF; |
|
||||
-moz-animation: spin 1.5s linear infinite; |
|
||||
-o-animation: spin 1.5s linear infinite; |
|
||||
-ms-animation: spin 1.5s linear infinite; |
|
||||
-webkit-animation: spin 1.5s linear infinite; |
|
||||
animation: spin 1.5s linear infinite; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@-webkit-keyframes spin { |
|
||||
0% { |
|
||||
-webkit-transform: rotate(0deg); |
|
||||
-ms-transform: rotate(0deg); |
|
||||
transform: rotate(0deg); |
|
||||
} |
|
||||
|
|
||||
100% { |
|
||||
-webkit-transform: rotate(360deg); |
|
||||
-ms-transform: rotate(360deg); |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@keyframes spin { |
|
||||
0% { |
|
||||
-webkit-transform: rotate(0deg); |
|
||||
-ms-transform: rotate(0deg); |
|
||||
transform: rotate(0deg); |
|
||||
} |
|
||||
|
|
||||
100% { |
|
||||
-webkit-transform: rotate(360deg); |
|
||||
-ms-transform: rotate(360deg); |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
#loader-wrapper .loader-section { |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
width: 51%; |
|
||||
height: 100%; |
|
||||
background: #7171C6; |
|
||||
z-index: 1000; |
|
||||
-webkit-transform: translateX(0); |
|
||||
-ms-transform: translateX(0); |
|
||||
transform: translateX(0); |
|
||||
} |
|
||||
|
|
||||
#loader-wrapper .loader-section.section-left { |
|
||||
left: 0; |
|
||||
} |
|
||||
|
|
||||
#loader-wrapper .loader-section.section-right { |
|
||||
right: 0; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
.loaded #loader-wrapper .loader-section.section-left { |
|
||||
-webkit-transform: translateX(-100%); |
|
||||
-ms-transform: translateX(-100%); |
|
||||
transform: translateX(-100%); |
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); |
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); |
|
||||
} |
|
||||
|
|
||||
.loaded #loader-wrapper .loader-section.section-right { |
|
||||
-webkit-transform: translateX(100%); |
|
||||
-ms-transform: translateX(100%); |
|
||||
transform: translateX(100%); |
|
||||
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); |
|
||||
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); |
|
||||
} |
|
||||
|
|
||||
.loaded #loader { |
|
||||
opacity: 0; |
|
||||
-webkit-transition: all 0.3s ease-out; |
|
||||
transition: all 0.3s ease-out; |
|
||||
} |
|
||||
|
|
||||
.loaded #loader-wrapper { |
|
||||
visibility: hidden; |
|
||||
-webkit-transform: translateY(-100%); |
|
||||
-ms-transform: translateY(-100%); |
|
||||
transform: translateY(-100%); |
|
||||
-webkit-transition: all 0.3s 1s ease-out; |
|
||||
transition: all 0.3s 1s ease-out; |
|
||||
} |
|
||||
|
|
||||
.no-js #loader-wrapper { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
.no-js h1 { |
|
||||
color: #222222; |
|
||||
} |
|
||||
|
|
||||
#loader-wrapper .load_title { |
|
||||
font-family: 'Open Sans'; |
|
||||
color: #FFF; |
|
||||
font-size: 19px; |
|
||||
width: 100%; |
|
||||
text-align: center; |
|
||||
z-index: 9999999999999; |
|
||||
position: absolute; |
|
||||
top: 60%; |
|
||||
opacity: 1; |
|
||||
line-height: 30px; |
|
||||
} |
|
||||
|
|
||||
#loader-wrapper .load_title span { |
|
||||
font-weight: normal; |
|
||||
font-style: italic; |
|
||||
font-size: 13px; |
|
||||
color: #FFF; |
|
||||
opacity: 0.5; |
|
||||
} |
|
||||
</style> |
|
||||
<script type="module" crossorigin src="./js/index-iDzH0SQw.js"></script> |
|
||||
<link rel="stylesheet" crossorigin href="./css/index-Dcqqxojt.css"> |
|
||||
</head> |
|
||||
|
|
||||
<body> |
|
||||
<div id="app"> |
|
||||
<div id="loader-wrapper"> |
|
||||
<div id="loader"></div> |
|
||||
<div class="loader-section section-left"></div> |
|
||||
<div class="loader-section section-right"></div> |
|
||||
<div class="load_title">正在加载系统资源,请耐心等待</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</body> |
|
||||
|
|
||||
</html> |
|
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue