Commit 7c29bf220bef1d01dfb45d932d75a1cc0ab2a79a

Authored by dadiyang
1 parent 8e43004c

fix(bpm):动态表单必填字段后端补校验,缺必填不再建成脏实例

接口发起动态表单(formType=10 流程表单)流程时,必填字段未填也能
code=0 发起成功(前后端均漏校验,A51),建成变量缺失的脏实例。

在管理端 API 发起入口(VO 路径)补动态表单必填校验:按表单字段
定义(兼容 field/key 两种字段标识、数组/对象两种 required 标记)
逐一检查变量,缺失即抛新增业务错误 1_009_004_009。
仅校验 VO 路径:内部业务链路(DTO 路径,如 OA 请假只传部分变量)
不经过本校验,避免误伤。
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
... ... @@ -44,6 +44,7 @@ public interface ErrorCodeConstants {
44 44 ErrorCode PROCESS_INSTANCE_HTTP_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 请求调用失败");
45 45 ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
46 46 ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
  47 + ErrorCode PROCESS_INSTANCE_START_FORM_FIELD_REQUIRED = new ErrorCode(1_009_004_009, "发起流程失败,表单字段({})为必填项,不能为空");
47 48  
48 49 // ========== 流程任务 1-009-005-000 ==========
49 50 ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
... ... @@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
22 22 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
23 23 import cn.iocoder.yudao.module.bpm.dal.redis.BpmProcessIdRedisDAO;
24 24 import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
  25 +import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
25 26 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
26 27 import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum;
27 28 import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum;
... ... @@ -763,11 +764,54 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
763 764 // 获得流程定义
764 765 ProcessDefinition definition = processDefinitionService
765 766 .getProcessDefinition(createReqVO.getProcessDefinitionId());
  767 + // 校验动态表单必填字段(前端校验可被接口直调绕过,后端必须兜底,否则必填缺失也会建成脏实例)
  768 + validateDynamicFormRequiredFields(definition, createReqVO.getVariables());
766 769 // 发起流程
767 770 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
768 771 createReqVO.getStartUserSelectAssignees());
769 772 }
770 773  
  774 + /**
  775 + * 校验动态表单(formType=10 流程表单)的必填字段
  776 + *
  777 + * 注意:仅校验接口发起(VO 路径)入口。内部业务链路(DTO 路径,例如 OA 请假)
  778 + * 由业务代码自行组装变量,不经过本校验,避免误伤。
  779 + */
  780 + private void validateDynamicFormRequiredFields(ProcessDefinition definition, Map<String, Object> variables) {
  781 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService
  782 + .getProcessDefinitionInfo(definition.getId());
  783 + if (processDefinitionInfo == null
  784 + || !BpmModelFormTypeEnum.NORMAL.getType().equals(processDefinitionInfo.getFormType())
  785 + || CollUtil.isEmpty(processDefinitionInfo.getFormFields())) {
  786 + return;
  787 + }
  788 + List<String> fieldStrs = processDefinitionInfo.getFormFields();
  789 + // formFields 是 JSON 数组,每个元素本身又是 JSON 字符串;两种表单设计器并存:
  790 + // 字段标识可能是 field 或 key,必填标记可能是数组 ["required"] 或对象 {"required": true}
  791 + for (String fieldStr : fieldStrs) {
  792 + Map<String, Object> field = JsonUtils.parseMap(fieldStr);
  793 + boolean required = false;
  794 + Object validate = field.get("validate");
  795 + if (validate instanceof List) {
  796 + required = ((List<?>) validate).contains("required");
  797 + } else if (validate instanceof Map) {
  798 + required = Boolean.TRUE.equals(((Map<?, ?>) validate).get("required"));
  799 + }
  800 + if (!required) {
  801 + continue;
  802 + }
  803 + String fieldId = Convert.toStr(field.get("field"), Convert.toStr(field.get("key")));
  804 + if (StrUtil.isEmpty(fieldId)) {
  805 + continue;
  806 + }
  807 + Object value = variables == null ? null : variables.get(fieldId);
  808 + if (value == null || (value instanceof String && StrUtil.isBlank((String) value))) {
  809 + throw exception(PROCESS_INSTANCE_START_FORM_FIELD_REQUIRED,
  810 + Convert.toStr(field.get("title"), fieldId));
  811 + }
  812 + }
  813 + }
  814 +
771 815 @Override
772 816 @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA
773 817 public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
... ...