diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/onlineFormOperationEnum/OnlineFormOperationEnum.java b/common/common-openapi/src/main/java/apelet/common/openapi/onlineFormOperationEnum/OnlineFormOperationEnum.java new file mode 100644 index 0000000..217ae61 --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/onlineFormOperationEnum/OnlineFormOperationEnum.java @@ -0,0 +1,183 @@ +package apelet.common.openapi.onlineFormOperationEnum; + +import cn.hutool.core.util.StrUtil; + +import java.util.HashMap; +import java.util.Map; + +/** + * 在线表单操作按钮类型枚举。 + *

widgetJson 的 {@code pc}/{@code mobile}.operationList 中,每个按钮靠整数属性 {@code type} 区分, + * 而同一层的 {@code code} 字段目前恒为 null —— 前端表单设计器不产出它,后端快捷表单生成路径 + * (OnlineQuickForm 的 widget 构造)也只写 {@code name} 与 {@code pluginList}。 + * 因此 OpenAPI 配置的可选操作必须由 {@code type} 派生出稳定、唯一的编码,作为 + * {@code xy_sys_open_api_config.operation_code} 使用。

+ *

之所以不能按 {@code name} 派生:name 是设计者可改的中文标签且可能重复,而 type 恒定唯一。 + * 例如设计者把「保存」改叫「保存草稿」,按 name 就再也匹配不上了。

+ * + * @author chenchuchuan + * @date 2026-10-09 + */ +public enum OnlineFormOperationEnum { + + /** + * 新建。 + */ + CREATE(0, "create", "新建"), + /** + * 编辑。 + */ + EDIT(1, "edit", "编辑"), + /** + * 删除。 + */ + DELETE(2, "delete", "删除"), + /** + * 导出。 + */ + EXPORT(3, "export", "导出"), + /** + * 导入。 + */ + IMPORT(4, "import", "导入"), + /** + * 批量删除。 + */ + BATCH_DELETE(10, "batchDelete", "批量删除"), + /** + * 保存。 + */ + SAVE(30, "save", "保存"), + /** + * 上移。 + */ + MOVE_UP(53, "moveUp", "上移"), + /** + * 下移。 + */ + MOVE_DOWN(54, "moveDown", "下移"), + /** + * 下推。 + */ + PUSH_DOWN(650, "pushDown", "下推"), + /** + * 上查。 + */ + QUERY_UP(651, "queryUp", "上查"), + /** + * 下查。 + */ + QUERY_DOWN(652, "queryDown", "下查"), + /** + * 审核。 + */ + AUDIT(654, "audit", "审核"), + /** + * 撤销。 + */ + REVOKE(655, "revoke", "撤销"), + /** + * 反审核。 + */ + UNAUDIT(656, "unaudit", "反审核"), + /** + * 查看。 + */ + VIEW(701, "view", "查看"), + /** + * 流程图。 + */ + DIAGRAM(702, "diagram", "流程图"), + /** + * 提交。 + */ + SUBMIT(703, "submit", "提交"), + /** + * project 导入。 + */ + PROJECT_IMPORT(710, "projectImport", "project导入"), + /** + * project 导出。 + */ + PROJECT_EXPORT(720, "projectExport", "project导出"); + + /** + * 未知 type 的编码前缀,回退为 type_,避免设计器新增按钮类型后配置流程再次阻塞。 + */ + private static final String UNKNOWN_CODE_PREFIX = "type_"; + + private static final Map TYPE_MAP = new HashMap<>(32); + + static { + for (OnlineFormOperationEnum item : values()) { + TYPE_MAP.put(item.type, item); + } + } + + /** + * 按钮类型(widgetJson.operationList[].type)。 + */ + private final Integer type; + /** + * 操作编码,作为开放接口的 operation_code 与请求路径末段。 + */ + private final String code; + /** + * 默认显示名。 + */ + private final String name; + + OnlineFormOperationEnum(Integer type, String code, String name) { + this.type = type; + this.code = code; + this.name = name; + } + + public Integer getType() { + return type; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + /** + * 按按钮类型取枚举,未覆盖的类型返回 null。 + * + * @param type 按钮类型。 + * @return 匹配的枚举项,无匹配返回 null。 + */ + public static OnlineFormOperationEnum ofType(Integer type) { + return type == null ? null : TYPE_MAP.get(type); + } + + /** + * 按按钮类型派生操作编码。 + * + * @param type 按钮类型。 + * @return 操作编码;type 为空返回 null,未覆盖的类型回退为 type_。 + */ + public static String codeOfType(Integer type) { + if (type == null) { + return null; + } + OnlineFormOperationEnum item = TYPE_MAP.get(type); + return item != null ? item.code : (UNKNOWN_CODE_PREFIX + type); + } + + /** + * 判断操作编码是否为保存类操作(保存 / 提交)。 + *

OpenAPI 的 FIELD_DIRECT 参数模式(字段直传走 executePlugin)以此为界, + * 原先靠按钮中文名 equalsAny("保存", "提交") 判断,按钮改名即失效,故改为按编码判断。

+ * + * @param code 操作编码。 + * @return 是保存类操作返回 true。 + */ + public static boolean isSaveLike(String code) { + return StrUtil.isNotBlank(code) && (SAVE.code.equals(code) || SUBMIT.code.equals(code)); + } +} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiConfigServiceImpl.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiConfigServiceImpl.java index 617d3c7..4cfc6e8 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiConfigServiceImpl.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiConfigServiceImpl.java @@ -22,6 +22,7 @@ import apelet.common.openapi.dto.*; import apelet.common.openapi.model.OpenApiApp; import apelet.common.openapi.model.OpenApiConfig; import apelet.common.openapi.model.OpenAppApiRelation; +import apelet.common.openapi.onlineFormOperationEnum.OnlineFormOperationEnum; import apelet.common.openapi.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiConfigService; import cn.hutool.core.util.IdUtil; @@ -164,7 +165,15 @@ public class OpenApiConfigServiceImpl extends BaseService i continue; } OpenApiOperationDto dto = new OpenApiOperationDto(); - dto.setCode(op.getString("code")); + // widgetJson 里按钮的 code 恒为 null(设计器不产出、快捷表单生成路径也不写), + // 必须由 type 派生稳定编码,否则 operation_code 为空会被新增接口的必填校验拦下。 + // 按 name 派生不可靠:name 是可改的中文标签且可能重复。 + String operationCode = OnlineFormOperationEnum.codeOfType(op.getInteger("type")); + if (StrUtil.isBlank(operationCode)) { + log.warn("开放接口可选操作:按钮缺少 type,无法派生操作编码,已跳过。formId [{}], name [{}]", formId, op.getString("name")); + continue; + } + dto.setCode(operationCode); dto.setName(op.getString("name")); dto.setEnabled(op.getBoolean("enabled")); result.add(dto); @@ -347,13 +356,15 @@ public class OpenApiConfigServiceImpl extends BaseService i } /** - * 推导参数模式:query→QUERY;保存/提交→FIELD_DIRECT;其他→CONDITION_MATCH。 + * 推导参数模式:query→QUERY;save/submit→FIELD_DIRECT;其他→CONDITION_MATCH。 + *

保存类操作原先按按钮中文名 equalsAny("保存", "提交") 判断,设计者一改按钮名就会掉进 + * CONDITION_MATCH(先查单据再逐条执行)分支,对新单据保存是错误行为,故改为按操作编码判断。

*/ private String resolveParamMode(OpenApiConfig config) { if (QUERY_CODE.equals(config.getOperationCode())) { return OpenApiParamMode.QUERY; } - if (StrUtil.equalsAny(config.getOperationName(), "保存", "提交")) { + if (OnlineFormOperationEnum.isSaveLike(config.getOperationCode())) { return OpenApiParamMode.FIELD_DIRECT; } return OpenApiParamMode.CONDITION_MATCH; diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiExecServiceImpl.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiExecServiceImpl.java index 480dd4d..3b9304c 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiExecServiceImpl.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiExecServiceImpl.java @@ -23,6 +23,7 @@ import apelet.common.openapi.constant.OpenApiParamMode; import apelet.common.openapi.dao.OpenAppApiRelationMapper; import apelet.common.openapi.model.OpenApiApp; import apelet.common.openapi.model.OpenApiConfig; +import apelet.common.openapi.onlineFormOperationEnum.OnlineFormOperationEnum; import apelet.common.openapi.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiConfigService; import apelet.common.openapi.service.OpenApiExecService; @@ -100,7 +101,7 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { // 写类操作(FIELD_DIRECT / CONDITION_MATCH)需目标按钮真实存在且启用并绑定插件,避免误配时 executePlugin 空跑却返回"成功"。 if (OpenApiParamMode.FIELD_DIRECT.equals(config.getParamMode()) || OpenApiParamMode.CONDITION_MATCH.equals(config.getParamMode())) { - if (!this.hasEnabledOperation(form, config.getOperationName())) { + if (!this.hasEnabledOperation(form, config.getOperationCode())) { return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, "操作按钮[" + config.getOperationName() + "]不存在、未启用或未绑定插件,无法执行!"); } @@ -320,10 +321,12 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { } /** - * 判断表单 widget 是否存在「启用且绑定插件」的同名操作按钮(与 executePlugin 的按钮解析一致,取 pc.operationList)。 + * 判断表单 widget 是否存在「启用且绑定插件」的目标操作按钮(与 executePlugin 的按钮解析一致,取 pc.operationList)。 + *

按 type 派生的操作编码匹配,而不是按钮中文名 —— name 是设计者可改的标签且可能重复。 + * 注意 executePlugin 仍按 name 认按钮,所以实际执行时两者都要能对上。

*/ - private boolean hasEnabledOperation(OnlineForm form, String buttonName) { - if (form == null || form.getFormId() == null || StrUtil.isBlank(buttonName)) { + private boolean hasEnabledOperation(OnlineForm form, String operationCode) { + if (form == null || form.getFormId() == null || StrUtil.isBlank(operationCode)) { return false; } OnlineForm cached = onlineFormService.getOnlineFormFromCache(form.getFormId()); @@ -337,7 +340,7 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { } for (int i = 0; i < ops.size(); i++) { JSONObject op = ops.getJSONObject(i); - if (op == null || !buttonName.equals(op.getString("name"))) { + if (op == null || !operationCode.equals(OnlineFormOperationEnum.codeOfType(op.getInteger("type")))) { continue; } JSONArray pluginList = op.getJSONArray("pluginList");