Browse Source

feat(common-openapi): 添加在线表单操作按钮类型枚举及编码转换功能

- 新增 OnlineFormOperationEnum 枚举类,定义在线表单操作按钮类型映射关系
- 修改 OpenApiConfigServiceImpl 实现类,使用 type 字段派生操作编码替代 code 字段
- 更新 OpenApiExecServiceImpl 实现类,通过操作编码匹配启用状态验证逻辑
- 实现按钮类型到操作编码的稳定转换,解决前端表单设计器按钮改名导致匹配失效问题
- 添加未知类型回退机制,避免设计器新增按钮类型后配置流程阻塞
- 优化参数模式推导逻辑,按操作编码判断保存类操作而非按钮中文名
feature/2026-08/0812-ccc-dev
chenchuchuan 15 hours ago
parent
commit
c3f0277445
  1. 183
      common/common-openapi/src/main/java/apelet/common/openapi/onlineFormOperationEnum/OnlineFormOperationEnum.java
  2. 17
      common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiConfigServiceImpl.java
  3. 13
      common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiExecServiceImpl.java

183
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;
/**
* 在线表单操作按钮类型枚举。
* <p>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} 使用。</p>
* <p>之所以不能按 {@code name} 派生:name 是设计者可改的中文标签且可能重复,而 type 恒定唯一。
* 例如设计者把「保存」改叫「保存草稿」,按 name 就再也匹配不上了。</p>
*
* @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_<n>,避免设计器新增按钮类型后配置流程再次阻塞。
*/
private static final String UNKNOWN_CODE_PREFIX = "type_";
private static final Map<Integer, OnlineFormOperationEnum> 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_<n>。
*/
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);
}
/**
* 判断操作编码是否为保存类操作(保存 / 提交)。
* <p>OpenAPI 的 FIELD_DIRECT 参数模式(字段直传走 executePlugin)以此为界,
* 原先靠按钮中文名 equalsAny("保存", "提交") 判断,按钮改名即失效,故改为按编码判断。</p>
*
* @param code 操作编码。
* @return 是保存类操作返回 true。
*/
public static boolean isSaveLike(String code) {
return StrUtil.isNotBlank(code) && (SAVE.code.equals(code) || SUBMIT.code.equals(code));
}
}

17
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.OpenApiApp;
import apelet.common.openapi.model.OpenApiConfig; import apelet.common.openapi.model.OpenApiConfig;
import apelet.common.openapi.model.OpenAppApiRelation; import apelet.common.openapi.model.OpenAppApiRelation;
import apelet.common.openapi.onlineFormOperationEnum.OnlineFormOperationEnum;
import apelet.common.openapi.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiAppService;
import apelet.common.openapi.service.OpenApiConfigService; import apelet.common.openapi.service.OpenApiConfigService;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
@ -164,7 +165,15 @@ public class OpenApiConfigServiceImpl extends BaseService<OpenApiConfig, Long> i
continue; continue;
} }
OpenApiOperationDto dto = new OpenApiOperationDto(); 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.setName(op.getString("name"));
dto.setEnabled(op.getBoolean("enabled")); dto.setEnabled(op.getBoolean("enabled"));
result.add(dto); result.add(dto);
@ -347,13 +356,15 @@ public class OpenApiConfigServiceImpl extends BaseService<OpenApiConfig, Long> i
} }
/** /**
* 推导参数模式:query→QUERY;保存/提交→FIELD_DIRECT;其他→CONDITION_MATCH。 * 推导参数模式:query→QUERY;save/submit→FIELD_DIRECT;其他→CONDITION_MATCH。
* <p>保存类操作原先按按钮中文名 equalsAny("保存", "提交") 判断,设计者一改按钮名就会掉进
* CONDITION_MATCH(先查单据再逐条执行)分支,对新单据保存是错误行为,故改为按操作编码判断。</p>
*/ */
private String resolveParamMode(OpenApiConfig config) { private String resolveParamMode(OpenApiConfig config) {
if (QUERY_CODE.equals(config.getOperationCode())) { if (QUERY_CODE.equals(config.getOperationCode())) {
return OpenApiParamMode.QUERY; return OpenApiParamMode.QUERY;
} }
if (StrUtil.equalsAny(config.getOperationName(), "保存", "提交")) { if (OnlineFormOperationEnum.isSaveLike(config.getOperationCode())) {
return OpenApiParamMode.FIELD_DIRECT; return OpenApiParamMode.FIELD_DIRECT;
} }
return OpenApiParamMode.CONDITION_MATCH; return OpenApiParamMode.CONDITION_MATCH;

13
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.dao.OpenAppApiRelationMapper;
import apelet.common.openapi.model.OpenApiApp; import apelet.common.openapi.model.OpenApiApp;
import apelet.common.openapi.model.OpenApiConfig; import apelet.common.openapi.model.OpenApiConfig;
import apelet.common.openapi.onlineFormOperationEnum.OnlineFormOperationEnum;
import apelet.common.openapi.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiAppService;
import apelet.common.openapi.service.OpenApiConfigService; import apelet.common.openapi.service.OpenApiConfigService;
import apelet.common.openapi.service.OpenApiExecService; import apelet.common.openapi.service.OpenApiExecService;
@ -100,7 +101,7 @@ public class OpenApiExecServiceImpl implements OpenApiExecService {
// 写类操作(FIELD_DIRECT / CONDITION_MATCH)需目标按钮真实存在且启用并绑定插件,避免误配时 executePlugin 空跑却返回"成功"。 // 写类操作(FIELD_DIRECT / CONDITION_MATCH)需目标按钮真实存在且启用并绑定插件,避免误配时 executePlugin 空跑却返回"成功"。
if (OpenApiParamMode.FIELD_DIRECT.equals(config.getParamMode()) if (OpenApiParamMode.FIELD_DIRECT.equals(config.getParamMode())
|| OpenApiParamMode.CONDITION_MATCH.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, return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST,
"操作按钮[" + config.getOperationName() + "]不存在、未启用或未绑定插件,无法执行!"); "操作按钮[" + config.getOperationName() + "]不存在、未启用或未绑定插件,无法执行!");
} }
@ -320,10 +321,12 @@ public class OpenApiExecServiceImpl implements OpenApiExecService {
} }
/** /**
* 判断表单 widget 是否存在「启用且绑定插件」的同名操作按钮(与 executePlugin 的按钮解析一致,取 pc.operationList)。 * 判断表单 widget 是否存在「启用且绑定插件」的目标操作按钮(与 executePlugin 的按钮解析一致,取 pc.operationList)。
* <p>按 type 派生的操作编码匹配,而不是按钮中文名 —— name 是设计者可改的标签且可能重复。
* 注意 executePlugin 仍按 name 认按钮,所以实际执行时两者都要能对上。</p>
*/ */
private boolean hasEnabledOperation(OnlineForm form, String buttonName) { private boolean hasEnabledOperation(OnlineForm form, String operationCode) {
if (form == null || form.getFormId() == null || StrUtil.isBlank(buttonName)) { if (form == null || form.getFormId() == null || StrUtil.isBlank(operationCode)) {
return false; return false;
} }
OnlineForm cached = onlineFormService.getOnlineFormFromCache(form.getFormId()); OnlineForm cached = onlineFormService.getOnlineFormFromCache(form.getFormId());
@ -337,7 +340,7 @@ public class OpenApiExecServiceImpl implements OpenApiExecService {
} }
for (int i = 0; i < ops.size(); i++) { for (int i = 0; i < ops.size(); i++) {
JSONObject op = ops.getJSONObject(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; continue;
} }
JSONArray pluginList = op.getJSONArray("pluginList"); JSONArray pluginList = op.getJSONArray("pluginList");

Loading…
Cancel
Save