Commit ff70aaa6a9dea2ede66eaa8b57d1a343045af2e6
Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17
Showing
8 changed files
with
107 additions
and
16 deletions
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
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()); | ... | ... |