Commit ff70aaa6a9dea2ede66eaa8b57d1a343045af2e6

Authored by YunaiV
2 parents 8ffe9272 176e8714

Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

sql/tools/convertor.py
... ... @@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
17 17 import argparse
18 18 import pathlib
19 19 import re
  20 +import sys
20 21 import time
21 22 from abc import ABC, abstractmethod
22 23 from typing import Dict, Generator, Optional, Tuple, Union
... ... @@ -293,8 +294,10 @@ class Convertor(ABC):
293 294  
294 295 # 将parse失败的脚本打印出来
295 296 if error_scripts:
  297 + print("!!! 以下内容无法正常解析", file=sys.stderr)
296 298 for script in error_scripts:
297   - print(script)
  299 + # print to stderr
  300 + print(script, file=sys.stderr)
298 301  
299 302  
300 303 class PostgreSQLConvertor(Convertor):
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -6,6 +6,8 @@ import cn.hutool.core.map.MapUtil;
6 6 import cn.hutool.core.util.ObjUtil;
7 7 import cn.hutool.core.util.StrUtil;
8 8 import cn.hutool.extra.servlet.JakartaServletUtil;
  9 +import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
  10 +import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
9 11 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 12 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
11 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 16 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 17 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 18 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
17   -import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
18   -import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
19 19 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
20 20 import jakarta.servlet.http.HttpServletRequest;
21 21 import jakarta.validation.ConstraintViolation;
... ... @@ -29,6 +29,7 @@ import org.springframework.util.Assert;
29 29 import org.springframework.validation.BindException;
30 30 import org.springframework.validation.FieldError;
31 31 import org.springframework.validation.ObjectError;
  32 +import org.springframework.web.HttpMediaTypeNotSupportedException;
32 33 import org.springframework.web.HttpRequestMethodNotSupportedException;
33 34 import org.springframework.web.bind.MethodArgumentNotValidException;
34 35 import org.springframework.web.bind.MissingServletRequestParameterException;
... ... @@ -101,6 +102,9 @@ public class GlobalExceptionHandler {
101 102 if (ex instanceof HttpRequestMethodNotSupportedException) {
102 103 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
103 104 }
  105 + if (ex instanceof HttpMediaTypeNotSupportedException) {
  106 + return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
  107 + }
104 108 if (ex instanceof ServiceException) {
105 109 return serviceExceptionHandler((ServiceException) ex);
106 110 }
... ... @@ -171,17 +175,19 @@ public class GlobalExceptionHandler {
171 175 /**
172 176 * 处理 SpringMVC 请求参数类型错误
173 177 *
174   - * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
  178 + * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
175 179 */
176 180 @ExceptionHandler(HttpMessageNotReadableException.class)
177 181 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
178 182 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
179   - if(ex.getCause() instanceof InvalidFormatException) {
  183 + if (ex.getCause() instanceof InvalidFormatException) {
180 184 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
181 185 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
182   - }else {
183   - return defaultExceptionHandler(ServletUtils.getRequest(), ex);
184 186 }
  187 + if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
  188 + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
  189 + }
  190 + return defaultExceptionHandler(ServletUtils.getRequest(), ex);
185 191 }
186 192  
187 193 /**
... ... @@ -238,6 +244,17 @@ public class GlobalExceptionHandler {
238 244 }
239 245  
240 246 /**
  247 + * 处理 SpringMVC 请求的 Content-Type 不正确
  248 + *
  249 + * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
  250 + */
  251 + @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  252 + public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
  253 + log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
  254 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
  255 + }
  256 +
  257 + /**
241 258 * 处理 Spring Security 权限不足的异常
242 259 *
243 260 * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
... ... @@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO {
71 71 @Schema(description = "是否填写审批意见", example = "false")
72 72 private Boolean reasonRequire;
73 73  
  74 + @Schema(description = "跳过表达式", example = "{true}")
  75 + private String skipExpression; // 用于审批节点
  76 +
74 77 /**
75 78 * 审批节点拒绝处理
76 79 */
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
... ... @@ -14,6 +14,7 @@ import lombok.Getter;
14 14 @AllArgsConstructor
15 15 public enum BpmTaskStatusEnum {
16 16  
  17 + SKIP(-2, "跳过"),
17 18 NOT_START(-1, "未开始"),
18 19 RUNNING(1, "审批中"),
19 20 APPROVE(2, "审批通过"),
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -800,9 +800,10 @@ public class BpmnModelUtils {
800 800 || currentElement instanceof EndEvent
801 801 || currentElement instanceof UserTask
802 802 || currentElement instanceof ServiceTask) {
803   - // 添加元素
  803 + // 添加节点
804 804 FlowNode flowNode = (FlowNode) currentElement;
805 805 resultElements.add(flowNode);
  806 +
806 807 // 遍历子节点
807 808 flowNode.getOutgoingFlows().forEach(
808 809 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
... ... @@ -836,6 +837,31 @@ public class BpmnModelUtils {
836 837 }
837 838  
838 839 /**
  840 + * 判断是否跳过此节点
  841 + *
  842 + * @param flowNode 节点
  843 + * @param variables 流程变量
  844 + */
  845 + public static boolean isSkipNode(FlowElement flowNode, Map<String, Object> variables) {
  846 + // 1. 检查节点是否有跳过表达式(支持多种任务节点类型)
  847 + String skipExpression = null;
  848 + if (flowNode instanceof UserTask) {
  849 + skipExpression = ((UserTask) flowNode).getSkipExpression();
  850 + } else if (flowNode instanceof ServiceTask) {
  851 + skipExpression = ((ServiceTask) flowNode).getSkipExpression();
  852 + } else if (flowNode instanceof ScriptTask) {
  853 + skipExpression = ((ScriptTask) flowNode).getSkipExpression();
  854 + }
  855 +
  856 + if (StrUtil.isEmpty(skipExpression)) {
  857 + return false;
  858 + }
  859 +
  860 + // 2. 计算跳过表达式的值
  861 + return evalConditionExpress(variables, skipExpression);
  862 + }
  863 +
  864 + /**
839 865 * 根据当前节点,获取下一个节点
840 866 *
841 867 * @param currentElement 当前节点
... ... @@ -997,7 +1023,7 @@ public class BpmnModelUtils {
997 1023 * @return 是否满足条件
998 1024 */
999 1025 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
1000   - if (expression == null) {
  1026 + if (StrUtil.isEmpty(expression)) {
1001 1027 return Boolean.FALSE;
1002 1028 }
1003 1029 // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
... ... @@ -464,6 +464,10 @@ public class SimpleModelUtils {
464 464 addReasonRequire(node.getReasonRequire(), userTask);
465 465 // 节点类型
466 466 addNodeType(node.getType(), userTask);
  467 + // 添加跳过表达式
  468 + if (StrUtil.isNotEmpty(node.getSkipExpression())) {
  469 + userTask.setSkipExpression(node.getSkipExpression());
  470 + }
467 471 return userTask;
468 472 }
469 473  
... ... @@ -968,7 +972,7 @@ public class SimpleModelUtils {
968 972 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
969 973 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
970 974 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
971   - // 添加元素
  975 + // 添加此节点
972 976 resultNodes.add(currentNode);
973 977 }
974 978  
... ... @@ -1014,6 +1018,16 @@ public class SimpleModelUtils {
1014 1018 simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
1015 1019 }
1016 1020  
  1021 + /**
  1022 + * 根据跳过表达式,判断是否跳过此节点
  1023 + */
  1024 + public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables) {
  1025 + if (StrUtil.isEmpty(currentNode.getSkipExpression())) {
  1026 + return false;
  1027 + }
  1028 + return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression());
  1029 + }
  1030 +
1017 1031 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
1018 1032 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
1019 1033 }
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
... ... @@ -398,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
398 398 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
399 399 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
400 400 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
401   - .setStatus(FlowableUtils.getTaskStatus(task))
  401 + .setStatus(getEndActivityNodeStatus(task))
402 402 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
403 403 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
404 404 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
... ... @@ -463,6 +463,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
463 463 }
464 464  
465 465 /**
  466 + * 获取结束节点的状态
  467 + */
  468 + private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
  469 + Integer status = FlowableUtils.getTaskStatus(task);
  470 + if (status != null) {
  471 + return status;
  472 + }
  473 + // 结束节点未获取到状态,为跳过状态。可见 bpmn 或者 simple 的 skipExpression
  474 + return BpmTaskStatusEnum.SKIP.getStatus();
  475 + }
  476 +
  477 + /**
466 478 * 获得【进行中】的活动节点们
467 479 */
468 480 private List<ActivityNode> getRunApproveNodeList(Long startUserId,
... ... @@ -565,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
565 577 if (runActivityIds.contains(node.getId())) {
566 578 return null;
567 579 }
568   -
  580 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  581 + // 如果节点被跳过。设置状态为跳过
  582 + if (SimpleModelUtils.isSkipNode(node, processVariables)) {
  583 + status = BpmTaskStatusEnum.SKIP.getStatus();
  584 + }
569 585 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
570 586 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
571   - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
  587 + .setStatus(status);
572 588  
573 589 // 1. 开始节点/审批节点
574 590 if (ObjectUtils.equalsAny(node.getType(),
... ... @@ -608,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
608 624 if (runActivityIds.contains(node.getId())) {
609 625 return null;
610 626 }
  627 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  628 + // 如果节点被跳过,状态设置为跳过
  629 + if(BpmnModelUtils.isSkipNode(node, processVariables)){
  630 + status = BpmTaskStatusEnum.SKIP.getStatus();
  631 + }
611 632 ActivityNode activityNode = new ActivityNode().setId(node.getId())
612   - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
  633 + .setStatus(status);
613 634  
614 635 // 1. 开始节点
615 636 if (node instanceof StartEvent) {
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -947,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
947 947 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
948 948  
949 949 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
950   - taskService.setOwner(taskId, task.getAssignee());
  950 + // 特殊:如果已经被委派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  951 + if (StrUtil.isEmpty(task.getOwner())) {
  952 + taskService.setOwner(taskId, task.getAssignee());
  953 + }
951 954 // 3.2 执行委派,将任务委派给 delegateUser
952 955 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
953 956 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
... ... @@ -973,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
973 976 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
974 977  
975 978 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
976   - taskService.setOwner(taskId, task.getAssignee());
  979 + // 特殊:如果已经被转派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  980 + if (StrUtil.isEmpty(task.getOwner())) {
  981 + taskService.setOwner(taskId, task.getAssignee());
  982 + }
977 983 // 3.2 执行转派(审批人),将任务转派给 assigneeUser
978 984 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
979 985 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
... ...