From 57add7b6e7939642ad3bd4240cbaf0c9af324ec6 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Mon, 21 Jul 2025 16:05:50 +0000 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=20sql/tools=20=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E8=BE=93=E5=87=BA=E5=88=B0=20stderr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: dhb52 --- sql/tools/convertor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py index 7ab8ad1ef3..d286d3b238 100644 --- a/sql/tools/convertor.py +++ b/sql/tools/convertor.py @@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql import argparse import pathlib import re +import sys import time from abc import ABC, abstractmethod from typing import Dict, Generator, Optional, Tuple, Union @@ -293,8 +294,10 @@ class Convertor(ABC): # 将parse失败的脚本打印出来 if error_scripts: + print("!!! 以下内容无法正常解析", file=sys.stderr) for script in error_scripts: - print(script) + # print to stderr + print(script, file=sys.stderr) class PostgreSQLConvertor(Convertor): From 1684706e9aa70df8675b4b6bba212b7f5541d078 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 29 Jul 2025 09:36:53 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20[BPM=20=E5=B7=A5=E4=BD=9C=E6=B5=81]?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E8=B7=B3=E8=BF=87=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E5=88=A4=E6=96=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/model/simple/BpmSimpleModelNodeVO.java | 3 ++ .../bpm/enums/task/BpmTaskStatusEnum.java | 2 +- .../flowable/core/util/BpmnModelUtils.java | 30 +++++++++++++++++-- .../flowable/core/util/SimpleModelUtils.java | 18 ++++++++++- .../task/BpmProcessInstanceServiceImpl.java | 26 +++++++++++++--- 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java index 1cbebe06e4..4d34df8308 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java +++ b/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 { @Schema(description = "是否填写审批意见", example = "false") private Boolean reasonRequire; + @Schema(description = "跳过表达式", example = "{true}") + private String skipExpression; // 用于审批节点 + /** * 审批节点拒绝处理 */ diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index a19f122bd8..9153b3e780 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -13,7 +13,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum BpmTaskStatusEnum { - + SKIP(-2, "跳过"), NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 460af124f6..1bafa578db 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -800,9 +800,10 @@ public class BpmnModelUtils { || currentElement instanceof EndEvent || currentElement instanceof UserTask || currentElement instanceof ServiceTask) { - // 添加元素 + // 添加节点 FlowNode flowNode = (FlowNode) currentElement; resultElements.add(flowNode); + // 遍历子节点 flowNode.getOutgoingFlows().forEach( nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); @@ -835,6 +836,31 @@ public class BpmnModelUtils { } } + /** + * 判断是否跳过此节点 + * + * @param flowNode 节点 + * @param variables 流程变量 + */ + public static boolean isSkipNode(FlowElement flowNode, Map variables) { + // 1. 检查节点是否有跳过表达式(支持多种任务节点类型) + String skipExpression = null; + if (flowNode instanceof UserTask) { + skipExpression = ((UserTask) flowNode).getSkipExpression(); + } else if (flowNode instanceof ServiceTask) { + skipExpression = ((ServiceTask) flowNode).getSkipExpression(); + } else if (flowNode instanceof ScriptTask) { + skipExpression = ((ScriptTask) flowNode).getSkipExpression(); + } + + if (StrUtil.isEmpty(skipExpression)) { + return false; + } + + // 2. 计算跳过表达式的值 + return evalConditionExpress(variables, skipExpression); + } + /** * 根据当前节点,获取下一个节点 * @@ -997,7 +1023,7 @@ public class BpmnModelUtils { * @return 是否满足条件 */ public static boolean evalConditionExpress(Map variables, String expression) { - if (expression == null) { + if (StrUtil.isEmpty(expression)) { return Boolean.FALSE; } // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 5e9313840b..b016666a8a 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -464,9 +464,15 @@ public class SimpleModelUtils { addReasonRequire(node.getReasonRequire(), userTask); // 节点类型 addNodeType(node.getType(), userTask); + // 添加跳过表达式 + if (StrUtil.isNotEmpty(node.getSkipExpression())) { + userTask.setSkipExpression(node.getSkipExpression()); + } return userTask; } + + private void addUserTaskListener(BpmSimpleModelNodeVO node, UserTask userTask) { List flowableListeners = new ArrayList<>(3); if (node.getTaskCreateListener() != null @@ -967,7 +973,7 @@ public class SimpleModelUtils { || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { - // 添加元素 + // 添加此节点 resultNodes.add(currentNode); } @@ -1013,6 +1019,16 @@ public class SimpleModelUtils { simulateNextNode(currentNode.getChildNode(), variables, resultNodes); } + /** + * 根据跳过表达式,判断是否跳过此节点。 + */ + public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map variables) { + if (StrUtil.isEmpty(currentNode.getSkipExpression())) { + return false; + } + return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression()); + } + public static boolean evalConditionExpress(Map variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 85d15a6327..524dce20c1 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -398,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) - .setStatus(FlowableUtils.getTaskStatus(task)) + .setStatus(getEndActivityNodeStatus(task)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); @@ -462,6 +462,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService return approvalNodes; } + + /** + * 获取结束节点的状态 + */ + private Integer getEndActivityNodeStatus(HistoricTaskInstance task) { + Integer status = FlowableUtils.getTaskStatus(task); + return status == null ? BpmTaskStatusEnum.SKIP.getStatus() : status; // 结束节点未获取到状态,为跳过状态 + } + /** * 获得【进行中】的活动节点们 */ @@ -565,10 +574,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService if (runActivityIds.contains(node.getId())) { return null; } - + Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); + // 如果节点被跳过。设置状态为跳过 + if (SimpleModelUtils.isSkipNode(node, processVariables)) { + status = BpmTaskStatusEnum.SKIP.getStatus(); + } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); + .setStatus(status); // 1. 开始节点/审批节点 if (ObjectUtils.equalsAny(node.getType(), @@ -608,8 +621,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService if (runActivityIds.contains(node.getId())) { return null; } + Integer status = BpmTaskStatusEnum.NOT_START.getStatus(); + // 如果节点被跳过,状态设置为跳过 + if(BpmnModelUtils.isSkipNode(node, processVariables)){ + status = BpmTaskStatusEnum.SKIP.getStatus(); + } ActivityNode activityNode = new ActivityNode().setId(node.getId()) - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); + .setStatus(status); // 1. 开始节点 if (node instanceof StartEvent) { From e07fb6c8fe31e9cde0fd8a1cf46a5db3e1b01f55 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 29 Jul 2025 13:13:47 +0800 Subject: [PATCH 3/5] =?UTF-8?q?review=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91usertask=20=E7=9A=84=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E8=A1=A8=E8=BE=BE=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/web/core/handler/GlobalExceptionHandler.java | 6 +++--- .../yudao/module/bpm/enums/task/BpmTaskStatusEnum.java | 1 + .../bpm/framework/flowable/core/util/SimpleModelUtils.java | 4 +--- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 7 +++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index e27d04ec68..2f103732cc 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java @@ -171,15 +171,15 @@ public class GlobalExceptionHandler { /** * 处理 SpringMVC 请求参数类型错误 * - * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String + * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String */ @ExceptionHandler(HttpMessageNotReadableException.class) public CommonResult methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex); - if(ex.getCause() instanceof InvalidFormatException) { + if (ex.getCause() instanceof InvalidFormatException) { InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause(); return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue())); - }else { + } else { return defaultExceptionHandler(ServletUtils.getRequest(), ex); } } diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java index 9153b3e780..9ba3b5cb3a 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -13,6 +13,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum BpmTaskStatusEnum { + SKIP(-2, "跳过"), NOT_START(-1, "未开始"), RUNNING(1, "审批中"), diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java index 373f56339b..2582399a81 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -471,8 +471,6 @@ public class SimpleModelUtils { return userTask; } - - private void addUserTaskListener(BpmSimpleModelNodeVO node, UserTask userTask) { List flowableListeners = new ArrayList<>(3); if (node.getTaskCreateListener() != null @@ -1021,7 +1019,7 @@ public class SimpleModelUtils { } /** - * 根据跳过表达式,判断是否跳过此节点。 + * 根据跳过表达式,判断是否跳过此节点 */ public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map variables) { if (StrUtil.isEmpty(currentNode.getSkipExpression())) { diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 91815f8dd4..30adda6341 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -462,13 +462,16 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService return approvalNodes; } - /** * 获取结束节点的状态 */ private Integer getEndActivityNodeStatus(HistoricTaskInstance task) { Integer status = FlowableUtils.getTaskStatus(task); - return status == null ? BpmTaskStatusEnum.SKIP.getStatus() : status; // 结束节点未获取到状态,为跳过状态 + if (status != null) { + return status; + } + // 结束节点未获取到状态,为跳过状态。可见 bpmn 或者 simple 的 skipExpression + return BpmTaskStatusEnum.SKIP.getStatus(); } /** From ab7d9fdf72f96135efcdc21205d8eee1ba1a9149 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 29 Jul 2025 20:08:27 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E5=A7=94=E6=B4=BE=E3=80=81=E8=BD=AC?= =?UTF-8?q?=E5=8A=9E=E6=97=B6=EF=BC=8Cowner=20=E4=B8=8D=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/service/task/BpmTaskServiceImpl.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 1dbd0b17c0..8ea607a773 100644 --- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -947,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason())); // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) - taskService.setOwner(taskId, task.getAssignee()); + // 特殊:如果已经被委派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153 + if (StrUtil.isEmpty(task.getOwner())) { + taskService.setOwner(taskId, task.getAssignee()); + } // 3.2 执行委派,将任务委派给 delegateUser taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 @@ -973,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason())); // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) - taskService.setOwner(taskId, task.getAssignee()); + // 特殊:如果已经被转派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153 + if (StrUtil.isEmpty(task.getOwner())) { + taskService.setOwner(taskId, task.getAssignee()); + } // 3.2 执行转派(审批人),将任务转派给 assigneeUser // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); From 176e87141eec2ff13ff089eb10bec9f15102a817 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 29 Jul 2025 22:24:20 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat=EF=BC=9AGlobalExceptionHandler=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A=E8=AF=B7=E6=B1=82=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E4=B8=8D=E5=AF=B9=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/handler/GlobalExceptionHandler.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index 2f103732cc..627a5ea784 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/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; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.servlet.JakartaServletUtil; +import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; +import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; -import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; -import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; @@ -29,6 +29,7 @@ import org.springframework.util.Assert; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; @@ -101,6 +102,9 @@ public class GlobalExceptionHandler { if (ex instanceof HttpRequestMethodNotSupportedException) { return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex); } + if (ex instanceof HttpMediaTypeNotSupportedException) { + return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex); + } if (ex instanceof ServiceException) { return serviceExceptionHandler((ServiceException) ex); } @@ -179,9 +183,11 @@ public class GlobalExceptionHandler { if (ex.getCause() instanceof InvalidFormatException) { InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause(); return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue())); - } else { - return defaultExceptionHandler(ServletUtils.getRequest(), ex); } + if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) { + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失"); + } + return defaultExceptionHandler(ServletUtils.getRequest(), ex); } /** @@ -237,6 +243,17 @@ public class GlobalExceptionHandler { return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage())); } + /** + * 处理 SpringMVC 请求的 Content-Type 不正确 + * + * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + public CommonResult httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) { + log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex); + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage())); + } + /** * 处理 Spring Security 权限不足的异常 *