diff --git a/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java index f1a7edb..eaa15b8 100644 --- a/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java +++ b/common/common-core/src/main/java/apelet/common/core/mcp/config/McpToolScanner.java @@ -233,14 +233,45 @@ public class McpToolScanner { schema.put("required", required); } } else if (info.getType() == McpToolParamType.ARRAY && info.getElementType() != null) { + // List:E 为带 @McpToolParam 的对象时按对象展开 items,否则按基础类型映射 JSONObject items = new JSONObject(true); - items.put("type", this.toJsonType(info.getElementType())); + if (this.hasMcpToolParamFields(info.getElementType())) { + JSONObject properties = new JSONObject(true); + JSONArray required = new JSONArray(); + this.collectObjectSchema(info.getElementType(), properties, required); + items.put("type", "object"); + if (!properties.isEmpty()) { + items.put("properties", properties); + } + if (!required.isEmpty()) { + items.put("required", required); + } + } else { + items.put("type", this.toJsonType(info.getElementType())); + } schema.put("items", items); } return schema; } /** + * 判断类型是否为需要递归展开的对象:其声明字段中存在 @McpToolParam 标注。 + *

用于 List<对象> 场景:元素类型带注解字段时按嵌套对象组装 schema 与运行时绑定, + * 否则(String/数值/布尔等)按基础类型处理。

+ */ + private boolean hasMcpToolParamFields(Class clazz) { + if (clazz == null) { + return false; + } + for (Field field : clazz.getDeclaredFields()) { + if (field.getAnnotation(McpToolParam.class) != null) { + return true; + } + } + return false; + } + + /** * 收集对象类型中 @McpToolParam 标注字段的嵌套 schema。 */ private void collectObjectSchema(Class schemaClass, JSONObject properties, JSONArray required) { @@ -391,7 +422,12 @@ public class McpToolScanner { List source = raw instanceof List ? (List) raw : Arrays.asList((Object[]) raw); List converted = new ArrayList<>(); for (Object item : source) { - converted.add(this.toScalar(item, elementType)); + // List<对象>:元素为 Map 且元素类型带 @McpToolParam 字段时,逐条走 toObject 递归组装 + if (this.hasMcpToolParamFields(elementType) && item instanceof Map) { + converted.add(this.toObject(item, elementType)); + } else { + converted.add(this.toScalar(item, elementType)); + } } if (!paramType.isArray()) { return converted; @@ -405,11 +441,16 @@ public class McpToolScanner { /** * 构造对象实参:反射 new schemaClass 实例,遍历 @McpToolParam 字段取值转换后 set 入。 + *

schemaClass 为 Map 类型(如方法参数是 Map<String,Object> 自由 key-value 结构)时不做 POJO 展开, + * 直接把已解析好的入参 Map 原样返回,由业务方法自行按 key 取值。

*/ private Object toObject(Object raw, Class schemaClass) { if (!(raw instanceof Map) || schemaClass == null) { return null; } + if (Map.class.isAssignableFrom(schemaClass)) { + return raw; + } Map map = (Map) raw; try { Object instance = schemaClass.getDeclaredConstructor().newInstance(); diff --git a/common/common-core/src/main/java/apelet/common/core/object/TokenData.java b/common/common-core/src/main/java/apelet/common/core/object/TokenData.java index cbb36d1..d1c49ad 100644 --- a/common/common-core/src/main/java/apelet/common/core/object/TokenData.java +++ b/common/common-core/src/main/java/apelet/common/core/object/TokenData.java @@ -80,6 +80,10 @@ public class TokenData { */ private Integer deviceType; /** + * 用户类型 + */ + private Integer userType; + /** * 标识不同登录的会话Id。 */ private String sessionId; diff --git a/common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java b/common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java index 1ed1a4a..84feb7e 100644 --- a/common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java +++ b/common/common-online/src/main/java/apelet/common/online/dto/OnlineFormDto.java @@ -61,7 +61,7 @@ public class OnlineFormDto { @Schema(description = "表单类别") @NotNull(message = "数据验证失败,表单类别不能为空!") @ConstDictRef(constDictClass = FormKind.class, message = "数据验证失败,表单类别为无效值!") - @McpToolParam(fieldName = "formKind", type = McpToolParamType.INTEGER, description = "表单类别") + @McpToolParam(fieldName = "formKind", type = McpToolParamType.INTEGER, description = "表单类别:1=弹框列表,5=跳页类别") private Integer formKind; /** @@ -70,7 +70,7 @@ public class OnlineFormDto { @Schema(description = "表单类型") @NotNull(message = "数据验证失败,表单类型不能为空!") @ConstDictRef(constDictClass = FormType.class, message = "数据验证失败,表单类型为无效值!") - @McpToolParam(fieldName = "formType", type = McpToolParamType.INTEGER, description = "表单类型", required = true) + @McpToolParam(fieldName = "formType", type = McpToolParamType.INTEGER, description = "表单类型:1=查询表单,2=左树右表表单,3=一对一关联数据查询,5=编辑表单,10=流程表单,11=流程工单表单,15=快捷表单", required = true) private Integer formType; /** @@ -113,12 +113,13 @@ public class OnlineFormDto { * 排版类型(快捷表单)。 */ @Schema(description = "排版类型") - @McpToolParam(fieldName = "layoutType", type = McpToolParamType.INTEGER, description = "排版类型(快捷表单)") + @McpToolParam(fieldName = "layoutType", type = McpToolParamType.INTEGER, description = "排版类型(快捷表单):0=一行一列,1=一行两列,2=一行三列") private Integer layoutType; /** * 快捷表单字段列表。 */ @Schema(description = "快捷表单字段列表") + @McpToolParam(fieldName = "onlineQuickFormFields", type = McpToolParamType.ARRAY, description = "快捷表单字段列表(每项见 OnlineQuickFormField 字段)") private List onlineQuickFormFields; } diff --git a/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java b/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java index a8d2e4b..52450e6 100644 --- a/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java +++ b/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickForm.java @@ -61,7 +61,8 @@ public class OnlineQuickForm { @TableField(value = "quick_form_name") private String quickFormName; /** - * 排版类型。 + * 排版类型。 0 一行一列,1一行两列,2 一行三列 + * @see apelet.common.online.model.constant.LayoutType */ @TableField(value = "layout_type") private Integer layoutType; diff --git a/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java b/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java index c3201c6..73f2fa4 100644 --- a/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java +++ b/common/common-online/src/main/java/apelet/common/online/model/OnlineQuickFormField.java @@ -1,5 +1,7 @@ package apelet.common.online.model; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -33,126 +35,147 @@ public class OnlineQuickFormField { * 字段名称。 */ @TableField(value = "field_name") + @McpToolParam(fieldName = "fieldName", type = McpToolParamType.STRING, description = "字段名称", required = true) private String fieldName; /** * 字段备注。 */ @TableField(value = "field_remark") + @McpToolParam(fieldName = "fieldRemark", type = McpToolParamType.STRING, description = "字段备注") private String fieldRemark; /** * 字段长度。 */ @TableField(value = "field_length") + @McpToolParam(fieldName = "fieldLength", type = McpToolParamType.INTEGER, description = "字段长度") private Integer fieldLength; /** * 小数点位数。 */ @TableField(value = "field_point") + @McpToolParam(fieldName = "fieldPoint", type = McpToolParamType.INTEGER, description = "小数点位数") private Integer fieldPoint; /** * 字段类型(前端展示类型,即组件widgetType)。 */ @TableField(value = "field_type") + @McpToolParam(fieldName = "fieldType", type = McpToolParamType.INTEGER, description = "字段类型(前端展示类型,即组件widgetType)") private Integer fieldType; /** * 关联 zz_online_column 列ID(前端传入;为空=新增字段待建列,非空=已有字段仅修改)。 */ @TableField(value = "column_id") + @McpToolParam(fieldName = "columnId", type = McpToolParamType.INTEGER, description = "关联在线列Id(空=新增字段,非空=已有字段修改)") private Long columnId; /** * 是否为主键:0-否,1-是。 */ @TableField(value = "is_key") + @McpToolParam(fieldName = "isKey", type = McpToolParamType.INTEGER, description = "是否主键:0-否,1-是") private Integer isKey; /** * 默认值。 */ @TableField(value = "default_value") + @McpToolParam(fieldName = "defaultValue", type = McpToolParamType.STRING, description = "默认值") private String defaultValue; /** * 是否允许为空:0-否,1-是。 */ @TableField(value = "is_empty") + @McpToolParam(fieldName = "isEmpty", type = McpToolParamType.INTEGER, description = "是否允许为空:0-否,1-是") private Integer isEmpty; /** * 是否为默认字段:0-否,1-是。 */ @TableField(value = "is_default") + @McpToolParam(fieldName = "isDefault", type = McpToolParamType.INTEGER, description = "是否默认字段:0-否,1-是") private Integer isDefault; /** * 是否列表展示:0-否,1-是。 */ @TableField(value = "is_list") + @McpToolParam(fieldName = "isList", type = McpToolParamType.INTEGER, description = "是否列表展示:0-否,1-是") private Integer isList; /** * 是否过滤条件:0-否,1-是。 */ @TableField(value = "is_filter") + @McpToolParam(fieldName = "isFilter", type = McpToolParamType.INTEGER, description = "是否过滤条件:0-否,1-是") private Integer isFilter; /** * 过滤方案,参考FieldFilterType常量。 */ @TableField(value = "filter_type") + @McpToolParam(fieldName = "filterType", type = McpToolParamType.INTEGER, description = "过滤方案(参考FieldFilterType常量)") private Integer filterType; /** * 关联元数据ID。 */ @TableField(value = "ref_property") + @McpToolParam(fieldName = "refProperty", type = McpToolParamType.INTEGER, description = "关联元数据Id") private Long refProperty; /** * 关联数据字典ID。 */ @TableField(value = "ref_dict") + @McpToolParam(fieldName = "refDict", type = McpToolParamType.INTEGER, description = "关联数据字典Id") private Long refDict; /** * 关联数据字典名称。 */ @TableField(value = "ref_dict_name") + @McpToolParam(fieldName = "refDictName", type = McpToolParamType.STRING, description = "关联数据字典名称") private String refDictName; /** * 关联在线表单id(F7关联字段,前端传入,用于解析关联表,不落库)。 */ @TableField(exist = false) + @McpToolParam(fieldName = "formId", type = McpToolParamType.INTEGER, description = "关联在线表单Id(F7关联字段)") private Long formId; /** * 关联在线表单显示字段(F7关联字段,前端传入,不落库)。 */ @TableField(exist = false) + @McpToolParam(fieldName = "displayField", type = McpToolParamType.STRING, description = "关联在线表单显示字段(F7关联字段)") private String displayField; /** * 关联表头id(F7关联字段,由关联在线表单解析出 onl_form_head.id,用于 F7 控件 slaveTableId 与 onl_form_field.ref_propert 回写,不落库)。 */ @TableField(exist = false) + @McpToolParam(fieldName = "refTableId", type = McpToolParamType.INTEGER, description = "关联表头Id(F7关联字段)") private Long refTableId; /** * 关联表名(F7关联字段,由关联在线表单解析,不落库)。 */ @TableField(exist = false) + @McpToolParam(fieldName = "refTableName", type = McpToolParamType.STRING, description = "关联表名(F7关联字段)") private String refTableName; /** * 关联在线表单编码(F7关联字段,由关联在线表单解析,用于 relativeFormCode,不落库)。 */ @TableField(exist = false) + @McpToolParam(fieldName = "refFormCode", type = McpToolParamType.STRING, description = "关联在线表单编码(F7关联字段)") private String refFormCode; /** diff --git a/common/common-online/src/main/java/apelet/common/online/model/constant/LayoutType.java b/common/common-online/src/main/java/apelet/common/online/model/constant/LayoutType.java new file mode 100644 index 0000000..90766a5 --- /dev/null +++ b/common/common-online/src/main/java/apelet/common/online/model/constant/LayoutType.java @@ -0,0 +1,50 @@ +package apelet.common.online.model.constant; + +import java.util.HashMap; +import java.util.Map; + +/** + * 快捷表单排版类型常量字典对象。 + * + * @author chenchuchuan + * @date 2026-09-08 + */ +public final class LayoutType { + + /** + * 一行一列。 + */ + public static final int SINGLE_COLUMN = 0; + /** + * 一行两列。 + */ + public static final int DOUBLE_COLUMN = 1; + /** + * 一行三列。 + */ + public static final int TRIPLE_COLUMN = 2; + + private static final Map DICT_MAP = new HashMap<>(3); + + static { + DICT_MAP.put(SINGLE_COLUMN, "一行一列"); + DICT_MAP.put(DOUBLE_COLUMN, "一行两列"); + DICT_MAP.put(TRIPLE_COLUMN, "一行三列"); + } + + /** + * 私有构造函数,明确标识该常量类的作用。 + */ + private LayoutType() { + } + + /** + * 判断参数是否为当前常量字典的合法值。 + * + * @param value 待验证的参数值。 + * @return 合法返回true,否则false。 + */ + public static boolean isValid(Integer value) { + return value != null && DICT_MAP.containsKey(value); + } +} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAppController.java b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAppController.java index 7268a71..70977a2 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAppController.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAppController.java @@ -2,6 +2,9 @@ package apelet.common.openapi.controller; import apelet.common.core.annotation.MyRequestBody; import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.mcp.annotation.McpToolClass; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import apelet.common.core.object.MyPageData; import apelet.common.core.object.MyPageParam; import apelet.common.core.object.ResponseResult; @@ -9,9 +12,7 @@ import apelet.common.core.object.TokenData; import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyPageUtil; import apelet.common.openapi.model.OpenApiApp; -import apelet.common.openapi.model.OpenApiConfig; import apelet.common.openapi.service.OpenApiAppService; -import apelet.common.openapi.service.OpenApiConfigService; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.page.PageMethod; @@ -41,14 +42,14 @@ public class OpenApiAppController { @Autowired private OpenApiAppService openApiAppService; - @Autowired - private OpenApiConfigService openApiConfigService; /** * 分页查询应用列表(不回显 appSecret)。 */ @PostMapping("/list") - public ResponseResult> list(@MyRequestBody OpenApiApp openApiAppFilter, @MyRequestBody MyPageParam pageParam) { + @McpToolClass(name = "openapi_app_list", description = "分页查询 OpenAPI 第三方应用列表(appSecret 已打码),可按 appCode/appName/status 过滤") + public ResponseResult> list(@MyRequestBody @McpToolParam(fieldName = "openApiAppFilter", type = McpToolParamType.OBJECT, description = "应用过滤条件", schemaClass = OpenApiApp.class) OpenApiApp openApiAppFilter + , @MyRequestBody @McpToolParam(fieldName = "pageParam", type = McpToolParamType.OBJECT, description = "分页参数(pageNum/pageSize)", schemaClass = MyPageParam.class) MyPageParam pageParam) { if (pageParam != null) { PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); } @@ -62,7 +63,8 @@ public class OpenApiAppController { * 新增应用:appCode 未填则自动生成,appSecret 仅此刻返回明文。 */ @PostMapping("/add") - public ResponseResult add(@MyRequestBody OpenApiApp openApiApp) { + @McpToolClass(name = "openapi_app_add", description = "新增 OpenAPI 第三方应用(appCode 不传自动生成),返回对象含明文 appSecret,请妥善保存") + public ResponseResult add(@MyRequestBody @McpToolParam(fieldName = "openApiApp", type = McpToolParamType.OBJECT, description = "应用信息(appName 必填)", schemaClass = OpenApiApp.class, required = true) OpenApiApp openApiApp) { if (openApiApp == null || MyCommonUtil.existBlankArgument(openApiApp.getAppName())) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST, "应用名称不能为空!"); } @@ -76,7 +78,8 @@ public class OpenApiAppController { * 更新应用(不修改密钥)。 */ @PostMapping("/update") - public ResponseResult update(@MyRequestBody OpenApiApp openApiApp) { + @McpToolClass(name = "openapi_app_update", description = "更新 OpenAPI 第三方应用(appCode/appSecret 不可改,需传 id)") + public ResponseResult update(@MyRequestBody @McpToolParam(fieldName = "openApiApp", type = McpToolParamType.OBJECT, description = "应用信息(id 必填,appName/status 等可改)", schemaClass = OpenApiApp.class, required = true) OpenApiApp openApiApp) { if (openApiApp == null || MyCommonUtil.existBlankArgument(openApiApp.getId())) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } @@ -84,7 +87,7 @@ public class OpenApiAppController { if (original == null) { return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); } - // 应用编码作为对外唯一标识且被开放接口配置(xy_sys_open_api_config.app_code)引用,不允许修改 + // 应用编码作为对外唯一标识且被授权关联表(xy_sys_open_app_api)引用,不允许修改 if (StrUtil.isNotBlank(openApiApp.getAppCode()) && !openApiApp.getAppCode().equals(original.getAppCode())) { return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "应用编码不允许修改!"); @@ -107,20 +110,14 @@ public class OpenApiAppController { * 删除应用:已被开放配置引用的应用禁止删除。 */ @PostMapping("/delete") - public ResponseResult delete(@MyRequestBody Long id) { + @McpToolClass(name = "openapi_app_delete", description = "删除 OpenAPI 第三方应用(已被开放接口配置引用时禁止删除),需传 id") + public ResponseResult delete(@MyRequestBody @McpToolParam(fieldName = "id", type = McpToolParamType.INTEGER, description = "应用主键Id", required = true) Long id) { if (MyCommonUtil.existBlankArgument(id)) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } - OpenApiApp app = openApiAppService.getById(id); - if (app == null) { - return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); - } - OpenApiConfig configFilter = new OpenApiConfig(); - configFilter.setAppCode(app.getAppCode()); - if (openApiConfigService.existByFilter(configFilter)) { - return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "该应用已被开放接口配置引用,不能删除!"); + if (!openApiAppService.removeAppWithRelationCheck(id)) { + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "该应用已被分配开放接口,不能删除!"); } - openApiAppService.removeById(id); return ResponseResult.success(); } @@ -128,7 +125,8 @@ public class OpenApiAppController { * 重置应用密钥:旧密钥立即失效。 */ @PostMapping("/resetSecret") - public ResponseResult resetSecret(@MyRequestBody Long id) { + @McpToolClass(name = "openapi_app_reset_secret", description = "重置 OpenAPI 第三方应用密钥(旧密钥立即失效),返回新明文 appSecret") + public ResponseResult resetSecret(@MyRequestBody @McpToolParam(fieldName = "id", type = McpToolParamType.INTEGER, description = "应用主键Id", required = true) Long id) { if (MyCommonUtil.existBlankArgument(id)) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } @@ -145,7 +143,8 @@ public class OpenApiAppController { * 查看应用密钥明文(前端"小眼睛"调用;受应用登录鉴权保护)。 */ @PostMapping("/viewSecret") - public ResponseResult viewSecret(@MyRequestBody Long id) { + @McpToolClass(name = "openapi_app_view_secret", description = "查看 OpenAPI 第三方应用密钥明文,需传 id") + public ResponseResult viewSecret(@MyRequestBody @McpToolParam(fieldName = "id", type = McpToolParamType.INTEGER, description = "应用主键Id", required = true) Long id) { if (MyCommonUtil.existBlankArgument(id)) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAuthController.java b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAuthController.java index e5fcd38..2be2619 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAuthController.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiAuthController.java @@ -1,6 +1,9 @@ package apelet.common.openapi.controller; import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.mcp.annotation.McpToolClass; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import apelet.common.core.object.ResponseResult; import apelet.common.openapi.dto.OpenApiTokenDto; import apelet.common.openapi.service.OpenApiAuthService; @@ -34,11 +37,11 @@ public class OpenApiAuthController { * 按应用编码 + 密钥 + 用户名换取 token(第三方标准扁平 JSON body)。 */ @PostMapping("/token") - public ResponseResult token(@RequestBody(required = false) OpenApiTokenDto tokenDto) { + @McpToolClass(name = "openapi_auth_token", description = "按应用编码+密钥+用户名换取 OpenAPI 访问 token(返回 accessToken/expiresIn)") + public ResponseResult token(@RequestBody(required = false) @McpToolParam(fieldName = "tokenDto", type = McpToolParamType.OBJECT, description = "换 token 请求体(appCode/appSecret/username)", schemaClass = OpenApiTokenDto.class) OpenApiTokenDto tokenDto) { if (tokenDto == null) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } - return openApiAuthService.issueToken( - tokenDto.getAppCode(), tokenDto.getAppSecret(), tokenDto.getUsername()); + return openApiAuthService.issueToken(tokenDto.getAppCode(), tokenDto.getAppSecret(), tokenDto.getUsername()); } } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiConfigController.java b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiConfigController.java index e593c22..ae52ffc 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiConfigController.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiConfigController.java @@ -2,17 +2,20 @@ package apelet.common.openapi.controller; import apelet.common.core.annotation.MyRequestBody; import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.mcp.annotation.McpToolClass; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import apelet.common.core.object.MyPageData; import apelet.common.core.object.MyPageParam; import apelet.common.core.object.ResponseResult; import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyPageUtil; -import apelet.common.openapi.dto.OpenApiFieldDto; -import apelet.common.openapi.dto.OpenApiFormOptionDto; -import apelet.common.openapi.dto.OpenApiOperationDto; -import apelet.common.openapi.dto.OpenApiPageOptionDto; +import apelet.common.openapi.dto.*; import apelet.common.openapi.model.OpenApiConfig; +import apelet.common.openapi.model.OpenAppApiRelation; import apelet.common.openapi.service.OpenApiConfigService; +import apelet.common.openapi.vo.OpenApiConfigVo; +import cn.hutool.core.collection.CollectionUtil; import com.github.pagehelper.page.PageMethod; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; @@ -20,7 +23,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * OpenAPI 开放接口配置管理接口(内部,供前端开放平台界面调用)。 @@ -42,7 +48,8 @@ public class OpenApiConfigController { * 在线页面列表(配置步骤1 选择),可按页面名称模糊搜索。 */ @GetMapping("/pages") - public ResponseResult> pages(@RequestParam(required = false) String keyword) { + @McpToolClass(name = "openapi_config_page_options", description = "OpenAPI 配置步骤1:在线页面下拉,可按页面名称模糊搜索") + public ResponseResult> pages(@RequestParam(required = false) @McpToolParam(fieldName = "keyword", type = McpToolParamType.STRING, description = "页面名称关键字,空返回全部") String keyword) { return ResponseResult.success(openApiConfigService.getPageOptionList(keyword)); } @@ -50,7 +57,8 @@ public class OpenApiConfigController { * 该页面关联的表单列表(配置步骤2 联动)。 */ @GetMapping("/forms") - public ResponseResult> forms(@RequestParam Long pageId) { + @McpToolClass(name = "openapi_config_form_options", description = "OpenAPI 配置步骤2:指定页面对应的表单下拉") + public ResponseResult> forms(@RequestParam @McpToolParam(fieldName = "pageId", type = McpToolParamType.INTEGER, description = "页面主键Id", required = true) Long pageId) { return ResponseResult.success(openApiConfigService.getFormOptionList(pageId)); } @@ -58,7 +66,8 @@ public class OpenApiConfigController { * 可选操作下拉:查询(query) + operationList 各项(配置步骤3)。 */ @GetMapping("/operations") - public ResponseResult> operations(@RequestParam Long formId) { + @McpToolClass(name = "openapi_config_operation_options", description = "OpenAPI 配置步骤3:指定表单的可选操作(查询 + operationList 各项)") + public ResponseResult> operations(@RequestParam @McpToolParam(fieldName = "formId", type = McpToolParamType.INTEGER, description = "在线表单主键Id", required = true) Long formId) { return ResponseResult.success(openApiConfigService.getOperationList(formId)); } @@ -66,42 +75,54 @@ public class OpenApiConfigController { * 表单可配置字段树(FIELD_DIRECT 字段直传专用)。 */ @GetMapping("/fields") - public ResponseResult> fields(@RequestParam Long formId) { + @McpToolClass(name = "openapi_config_field_tree", description = "OpenAPI 配置:指定表单的可配置字段树(主表字段/关联字段属性/一对多分录)") + public ResponseResult> fields(@RequestParam @McpToolParam(fieldName = "formId", type = McpToolParamType.INTEGER, description = "在线表单主键Id", required = true) Long formId) { return ResponseResult.success(openApiConfigService.getFieldTree(formId)); } /** - * 分页查询开放接口配置。 + * 分页查询开放接口配置(查询条件:应用Id + 状态)。 */ @PostMapping("/list") - public ResponseResult> list(@MyRequestBody OpenApiConfig openApiConfigFilter, @MyRequestBody MyPageParam pageParam) { + @McpToolClass(name = "openapi_config_list", description = "分页查询 OpenAPI 开放接口配置,查询条件:openApiConfigFilter.appId(应用Id,传了仅返回该应用已授权的配置)+ openApiConfigFilter.status") + public ResponseResult> list(@MyRequestBody @McpToolParam(fieldName = "openApiConfigFilter", type = McpToolParamType.OBJECT, description = "查询条件(appId/status)", schemaClass = OpenApiConfigFilterDto.class) OpenApiConfigFilterDto openApiConfigFilter, @MyRequestBody @McpToolParam(fieldName = "pageParam", type = McpToolParamType.OBJECT, description = "分页参数(pageNum/pageSize)", schemaClass = MyPageParam.class) MyPageParam pageParam) { if (pageParam != null) { PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize()); } - List configList = openApiConfigService.getOpenApiConfigList(openApiConfigFilter, "create_time DESC"); - return ResponseResult.success(MyPageUtil.makeResponseData(configList)); + List configList = openApiConfigService.getOpenApiConfigListByFilter(openApiConfigFilter, "create_time DESC"); + MyPageData configVoMyPageData = MyPageUtil.makeResponseData(configList, OpenApiConfig.INSTANCE); + if (CollectionUtil.isNotEmpty(configVoMyPageData.getDataList())) { + // 一次查出本页全部配置的授权关联行,按 api_id 分组回填 appIdList + List configIdList = configList.stream().map(OpenApiConfig::getId).collect(Collectors.toList()); + Map> appIdMap = openApiConfigService.getAppApiRelationListByConfigIdList(configIdList).stream() + .collect(Collectors.groupingBy(OpenAppApiRelation::getApiId, Collectors.mapping(OpenAppApiRelation::getAppId, Collectors.toList()))); + configVoMyPageData.getDataList().forEach(configVo -> configVo.setAppIdList(appIdMap.getOrDefault(configVo.getId(), new ArrayList<>()))); + } + return ResponseResult.success(configVoMyPageData); } /** - * 查看配置详情。 + * 查看配置详情(返回 VO,含已授权应用编码集合)。 */ @GetMapping("/view") - public ResponseResult view(@RequestParam Long configId) { + @McpToolClass(name = "openapi_config_view", description = "查看 OpenAPI 开放接口配置详情(返回 VO,appCodes 为已授权应用编码集合)") + public ResponseResult view(@RequestParam @McpToolParam(fieldName = "configId", type = McpToolParamType.INTEGER, description = "配置主键Id", required = true) Long configId) { OpenApiConfig config = openApiConfigService.getById(configId); if (config == null) { return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); } - return ResponseResult.success(config); + OpenApiConfigVo configVo = OpenApiConfig.INSTANCE.fromModel(config); + configVo.setAppCodes(openApiConfigService.getAppCodesByConfigId(configId)); + return ResponseResult.success(configVo); } /** * 新增开放接口配置。 */ @PostMapping("/add") - public ResponseResult add(@MyRequestBody OpenApiConfig openApiConfig) { - if (openApiConfig == null || MyCommonUtil.existBlankArgument(openApiConfig.getAppCode(), - openApiConfig.getPageId(), openApiConfig.getFormId(), openApiConfig.getFormCode(), - openApiConfig.getOperationCode())) { + @McpToolClass(name = "openapi_config_add", description = "新增 OpenAPI 开放接口配置(pageId/formId/formCode/operationCode 必填,自动推导 paramMode 与 requestPath;应用关联通过 assignApps 分配)") + public ResponseResult add(@MyRequestBody @McpToolParam(fieldName = "openApiConfig", type = McpToolParamType.OBJECT, description = "开放接口配置信息", schemaClass = OpenApiConfig.class, required = true) OpenApiConfig openApiConfig) { + if (openApiConfig == null || MyCommonUtil.existBlankArgument(openApiConfig.getPageId(), openApiConfig.getFormId(), openApiConfig.getFormCode(), openApiConfig.getOperationCode())) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } return ResponseResult.success(openApiConfigService.addNew(openApiConfig)); @@ -111,7 +132,8 @@ public class OpenApiConfigController { * 更新开放接口配置。 */ @PostMapping("/update") - public ResponseResult update(@MyRequestBody OpenApiConfig openApiConfig) { + @McpToolClass(name = "openapi_config_update", description = "更新 OpenAPI 开放接口配置(重新推导 paramMode/requestPath,需传 id)") + public ResponseResult update(@MyRequestBody @McpToolParam(fieldName = "openApiConfig", type = McpToolParamType.OBJECT, description = "开放接口配置信息(id 必填)", schemaClass = OpenApiConfig.class, required = true) OpenApiConfig openApiConfig) { if (openApiConfig == null || MyCommonUtil.existBlankArgument(openApiConfig.getId())) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } @@ -122,10 +144,24 @@ public class OpenApiConfigController { } /** + * 分配应用:全量替换该接口配置的应用授权(空集合即清空授权)。 + */ + @PostMapping("/assignApps") + @McpToolClass(name = "openapi_config_assign_apps", description = "给 OpenAPI 开放接口配置分配第三方应用(全量替换授权,appIds 空即清空),返回已授权应用Id 集合") + public ResponseResult> assignApps(@MyRequestBody @McpToolParam(fieldName = "configId", type = McpToolParamType.INTEGER, description = "开放接口配置Id", required = true) Long configId, + @MyRequestBody @McpToolParam(fieldName = "appIds", type = McpToolParamType.ARRAY, description = "授权应用Id 集合(全量替换,空集合即清空授权)") List appIds) { + if (MyCommonUtil.existBlankArgument(configId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + return ResponseResult.success(openApiConfigService.assignApps(configId, appIds)); + } + + /** * 删除开放接口配置。 */ @PostMapping("/delete") - public ResponseResult delete(@MyRequestBody Long configId) { + @McpToolClass(name = "openapi_config_delete", description = "删除 OpenAPI 开放接口配置,需传 configId") + public ResponseResult delete(@MyRequestBody @McpToolParam(fieldName = "configId", type = McpToolParamType.INTEGER, description = "配置主键Id", required = true) Long configId) { if (MyCommonUtil.existBlankArgument(configId)) { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiRouterController.java b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiRouterController.java index 69788aa..9555aca 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiRouterController.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/controller/OpenApiRouterController.java @@ -1,5 +1,8 @@ package apelet.common.openapi.controller; +import apelet.common.core.mcp.annotation.McpToolClass; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import apelet.common.core.object.ResponseResult; import apelet.common.openapi.config.OpenApiProperties; import apelet.common.openapi.service.OpenApiExecService; @@ -31,9 +34,20 @@ public class OpenApiRouterController { @Autowired private OpenApiProperties openApiProperties; + /** + * 执行指定开放接口配置(MCP 工具:openapi_route_execute)。 + *

MCP 入参 requestBody 为 key-value 形式的自由对象:key 为该开放接口配置的参数字段(如 param_config.conditions 里的 param、FIELD_DIRECT 的 fields.alias),value 为对应字段值。

+ *

注意:MCP 直调走的是 McpToolScanner 反射调用(method.invoke),不经过本控制器的 HTTP 拦截链路, + * 因此不会触发 OpenApiAuthInterceptor 的开放 token 校验,注入的 TokenData 无 appCode 归属信息, + * OpenApiExecServiceImpl#execute 的 junction 授权校验(token.appCode → app → xy_sys_open_app_api)必然不通过, + * 当前会返回「当前应用无权调用该开放接口」。本工具仅做接口暴露占位,供后续补充内部执行入口(绕过授权校验)后真正可用。

+ */ @PostMapping("/{pageCode}/{formCode}/{operationCode}") - public ResponseResult execute(@PathVariable String pageCode, @PathVariable String formCode, @PathVariable String operationCode, - @RequestBody(required = false) Map requestBody) { + @McpToolClass(name = "openapi_route_execute", description = "执行 OpenAPI 开放接口配置(HTTP 由 token 鉴权;MCP 直调暂返回无权,仅占位)") + public ResponseResult execute(@PathVariable @McpToolParam(fieldName = "pageCode", type = McpToolParamType.STRING, description = "页面编码", required = true) String pageCode, + @PathVariable @McpToolParam(fieldName = "formCode", type = McpToolParamType.STRING, description = "表单编码", required = true) String formCode, + @PathVariable @McpToolParam(fieldName = "operationCode", type = McpToolParamType.STRING, description = "操作编码", required = true) String operationCode, + @RequestBody(required = false) @McpToolParam(fieldName = "requestBody", type = McpToolParamType.OBJECT, description = "请求体,key-value 形式(key 为参数字段,value 为字段值)") Map requestBody) { String requestPath = openApiProperties.getUrlPrefix() + "/v1/" + pageCode + "/" + formCode + "/" + operationCode; return openApiExecService.execute(requestPath, requestBody == null ? Collections.emptyMap() : requestBody); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenApiConfigMapper.java b/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenApiConfigMapper.java index 091667a..46f6cc7 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenApiConfigMapper.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenApiConfigMapper.java @@ -23,4 +23,14 @@ public interface OpenApiConfigMapper extends BaseDaoMapper { */ List getOpenApiConfigList( @Param("openApiConfigFilter") OpenApiConfig openApiConfigFilter, @Param("orderBy") String orderBy); + + /** + * 按应用Id 分页查询其已授权的配置列表(join 多对多授权表;status 为空时不带状态条件)。 + * + * @param appId 应用Id。 + * @param status 状态(0: 停用 1: 启用),可空。 + * @param orderBy 排序字符串,order by从句的参数。 + * @return 配置列表。 + */ + List getOpenApiConfigListByAppId(@Param("appId") Long appId, @Param("status") Integer status, @Param("orderBy") String orderBy); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenAppApiRelationMapper.java b/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenAppApiRelationMapper.java new file mode 100644 index 0000000..2daec3c --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dao/OpenAppApiRelationMapper.java @@ -0,0 +1,65 @@ +package apelet.common.openapi.dao; + +import apelet.common.core.base.dao.BaseDaoMapper; +import apelet.common.openapi.model.OpenAppApiRelation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * OpenAPI 应用-接口配置多对多关联数据操作访问接口。 + * + * @author chenchuchuan + * @date 2026-09-08 + */ +public interface OpenAppApiRelationMapper extends BaseDaoMapper { + + /** + * 批量插入授权关联行。 + * + * @param list 关联行集合。 + * @return 插入行数。 + */ + int batchInsert(@Param("list") List list); + + /** + * 删除指定接口配置的全部授权关联行。 + * + * @param apiId 开放接口配置Id。 + * @return 删除行数。 + */ + int deleteByApiId(@Param("apiId") Long apiId); + + /** + * 判断授权关联是否存在。 + * + * @param appId 应用Id。 + * @param apiId 开放接口配置Id。 + * @return 存在返回 true。 + */ + int existByAppIdAndApiId(@Param("appId") Long appId, @Param("apiId") Long apiId); + + /** + * 按接口配置Id 集合查询授权关联列表。 + * + * @param apiIdList 开放接口配置Id 集合。 + * @return 授权关联列表。 + */ + List selectByApiIdList(@Param("apiIdList") List apiIdList); + + /** + * 统计某应用的授权关联行数(应用删除拦截用)。 + * + * @param appId 应用Id。 + * @return 授权关联行数。 + */ + int countByAppId(@Param("appId") Long appId); + + /** + * 按应用Id 查询已授权接口配置Id 集合。 + * + * @param appId 应用Id。 + * @return 接口配置Id 集合。 + */ + List selectApiIdsByAppId(@Param("appId") Long appId); +} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiAppMapper.xml b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiAppMapper.xml index 12b7c1c..4e44d85 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiAppMapper.xml +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiAppMapper.xml @@ -21,7 +21,7 @@ AND xy_sys_open_app.app_code = #{openApiAppFilter.appCode} - + AND xy_sys_open_app.app_name LIKE #{safeAppName} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiConfigMapper.xml b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiConfigMapper.xml index 208fd93..6c81ef0 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiConfigMapper.xml +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenApiConfigMapper.xml @@ -3,7 +3,6 @@ - @@ -25,9 +24,6 @@ - - AND xy_sys_open_api_config.app_code = #{openApiConfigFilter.appCode} - AND xy_sys_open_api_config.page_id = #{openApiConfigFilter.pageId} @@ -49,4 +45,16 @@ ORDER BY ${orderBy} + + diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenAppApiRelationMapper.xml b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenAppApiRelationMapper.xml new file mode 100644 index 0000000..79fdb86 --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dao/mapper/OpenAppApiRelationMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + INSERT INTO xy_sys_open_app_api_relation (app_id, api_id) VALUES + + (#{item.appId}, #{item.apiId}) + + + + + DELETE FROM xy_sys_open_app_api_relation WHERE api_id = #{apiId} + + + + + + + + + + diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/document/OpenApiDocumentCustomizer.java b/common/common-openapi/src/main/java/apelet/common/openapi/document/OpenApiDocumentCustomizer.java index a079094..bcfb70d 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/document/OpenApiDocumentCustomizer.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/document/OpenApiDocumentCustomizer.java @@ -60,7 +60,7 @@ public class OpenApiDocumentCustomizer implements OpenApiCustomiser { operation.setOperationId(config.getOperationCode() + "_" + config.getFormCode()); operation.setSummary(config.getOperationName()); operation.setDescription(config.getOperationName()); - operation.addTagsItem(config.getAppCode()); + operation.addTagsItem(config.getFormCode()); Schema requestSchema = this.buildRequestSchema(config); if (requestSchema != null) { operation.setRequestBody(new RequestBody().required(true) diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiConfigFilterDto.java b/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiConfigFilterDto.java new file mode 100644 index 0000000..1dec947 --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiConfigFilterDto.java @@ -0,0 +1,30 @@ +package apelet.common.openapi.dto; + +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * OpenAPI 开放接口配置列表查询条件Dto对象。 + * + * @author chenchuchuan + * @date 2026-09-08 + */ +@Schema(description = "OpenAPI开放接口配置列表查询条件Dto对象") +@Data +public class OpenApiConfigFilterDto { + + /** + * 应用Id(传入时仅返回该应用已授权的配置)。 + */ + @Schema(description = "应用Id,传入时仅返回该应用已授权的配置") + @McpToolParam(fieldName = "appId", type = McpToolParamType.INTEGER, description = "应用Id,传入时仅返回该应用已授权的配置") + private Long appId; + /** + * 状态(0: 停用 1: 启用)。 + */ + @Schema(description = "状态(0: 停用 1: 启用)") + @McpToolParam(fieldName = "status", type = McpToolParamType.INTEGER, description = "状态(0: 停用 1: 启用)") + private Integer status; +} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiTokenDto.java b/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiTokenDto.java index 4c9777e..4786b68 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiTokenDto.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/dto/OpenApiTokenDto.java @@ -1,5 +1,7 @@ package apelet.common.openapi.dto; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import lombok.Data; /** @@ -14,13 +16,16 @@ public class OpenApiTokenDto { /** * 应用编码。 */ + @McpToolParam(fieldName = "appCode", type = McpToolParamType.STRING, description = "应用编码", required = true) private String appCode; /** * 应用密钥。 */ + @McpToolParam(fieldName = "appSecret", type = McpToolParamType.STRING, description = "应用密钥", required = true) private String appSecret; /** * 调用用户名。 */ + @McpToolParam(fieldName = "username", type = McpToolParamType.STRING, description = "调用用户名", required = true) private String username; } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiApp.java b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiApp.java index 7fc7b02..1907b46 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiApp.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiApp.java @@ -1,5 +1,7 @@ package apelet.common.openapi.model; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -21,11 +23,13 @@ public class OpenApiApp { * 主键Id。 */ @TableId(value = "id") + @McpToolParam(fieldName = "id", type = McpToolParamType.INTEGER, description = "主键Id(新增时不传)") private Long id; /** * 应用编码(对外暴露唯一)。 */ @TableField(value = "app_code") + @McpToolParam(fieldName = "appCode", type = McpToolParamType.STRING, description = "应用编码(对外唯一;新增时不传则自动生成)") private String appCode; /** * 应用密钥(仅后台可见,可重置)。 @@ -36,16 +40,19 @@ public class OpenApiApp { * 应用名称。 */ @TableField(value = "app_name") + @McpToolParam(fieldName = "appName", type = McpToolParamType.STRING, description = "应用名称", required = true) private String appName; /** * 归属租户Id(本期默认为空,为空表示不限租户)。 */ @TableField(value = "tenant_id") + @McpToolParam(fieldName = "tenantId", type = McpToolParamType.INTEGER, description = "归属租户Id(为空表示不限租户)") private Long tenantId; /** * 状态(0: 停用 1: 启用)。 */ @TableField(value = "status") + @McpToolParam(fieldName = "status", type = McpToolParamType.INTEGER, description = "状态(0: 停用 1: 启用)") private Integer status; /** * 创建时间。 diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiConfig.java b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiConfig.java index 4eba81f..9434c03 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiConfig.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenApiConfig.java @@ -1,9 +1,15 @@ package apelet.common.openapi.model; +import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; +import apelet.common.openapi.vo.OpenApiConfigVo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; import java.util.Date; @@ -21,41 +27,43 @@ public class OpenApiConfig { * 主键Id。 */ @TableId(value = "id") + @McpToolParam(fieldName = "id", type = McpToolParamType.INTEGER, description = "主键Id(新增时不传)") private Long id; /** - * 授权开放的应用编码(关联 xy_sys_open_app.app_code)。 - */ - @TableField(value = "app_code") - private String appCode; - /** * 在线页面主键(OnlinePage.pageId)。 */ @TableField(value = "page_id") + @McpToolParam(fieldName = "pageId", type = McpToolParamType.INTEGER, description = "在线页面主键Id", required = true) private Long pageId; /** * 页面编码(冗余存储,用于组装开放路径)。 */ @TableField(value = "page_code") + @McpToolParam(fieldName = "pageCode", type = McpToolParamType.STRING, description = "页面编码(不传时按 pageId 反查)") private String pageCode; /** * 该页面下关联的在线表单编码(OnlineForm.formCode,用于组装开放路径 URL)。 */ @TableField(value = "form_code") + @McpToolParam(fieldName = "formCode", type = McpToolParamType.STRING, description = "在线表单编码", required = true) private String formCode; /** * 关联的在线表单主键Id(OnlineForm.formId),执行时优先按 formId 精确定位表单。 */ @TableField(value = "form_id") + @McpToolParam(fieldName = "formId", type = McpToolParamType.INTEGER, description = "在线表单主键Id", required = true) private Long formId; /** * 操作编码:query 或 operationList 中操作的 code。 */ @TableField(value = "operation_code") + @McpToolParam(fieldName = "operationCode", type = McpToolParamType.STRING, description = "操作编码:query 或操作按钮的 code", required = true) private String operationCode; /** * 操作名称:查询 或 operationList 中操作的 name。 */ @TableField(value = "operation_name") + @McpToolParam(fieldName = "operationName", type = McpToolParamType.STRING, description = "操作名称:查询 / 操作按钮 name") private String operationName; /** * 参数模式(后端自动推导):FIELD_DIRECT / CONDITION_MATCH / QUERY。 @@ -71,21 +79,25 @@ public class OpenApiConfig { * 请求方法 GET / POST。 */ @TableField(value = "request_method") + @McpToolParam(fieldName = "requestMethod", type = McpToolParamType.STRING, description = "请求方法(默认 POST)") private String requestMethod; /** * JSON:输入参数+条件配置(见设计文档 6.1)。 */ @TableField(value = "param_config") + @McpToolParam(fieldName = "paramConfig", type = McpToolParamType.STRING, description = "参数/条件配置 JSON(字段直传填 fields,条件匹配/查询填 conditions)") private String paramConfig; /** * JSON:输出层级结构(见设计文档 6.2)。 */ @TableField(value = "output_config") + @McpToolParam(fieldName = "outputConfig", type = McpToolParamType.STRING, description = "输出层级配置 JSON(dataFields)") private String outputConfig; /** * 状态(0: 停用 1: 启用)。 */ @TableField(value = "status") + @McpToolParam(fieldName = "status", type = McpToolParamType.INTEGER, description = "状态(0: 停用 1: 启用,不传默认 1)") private Integer status; /** * 创建时间。 @@ -107,4 +119,29 @@ public class OpenApiConfig { */ @TableField(value = "update_user_id") private Long updateUserId; + + /** + * OpenApiConfig 与 OpenApiConfigVo 的转换器(MapStruct,appCodes 引用编码集合由业务层单独填充)。 + */ + + @Mapper + public interface OpenApiConfigModelMapper extends BaseModelMapper { + /** + * 转换Vo对象到实体对象。 + * + * @param openApiConfigVo 域对象。 + * @return 实体对象。 + */ + @Override + OpenApiConfig toModel(OpenApiConfigVo openApiConfigVo); + /** + * 转换实体对象到VO对象。 + * + * @param openApiConfig 实体对象。 + * @return 域对象。 + */ + @Override + OpenApiConfigVo fromModel(OpenApiConfig openApiConfig); + } + public static final OpenApiConfigModelMapper INSTANCE = Mappers.getMapper(OpenApiConfigModelMapper.class); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenAppApiRelation.java b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenAppApiRelation.java new file mode 100644 index 0000000..52a87d6 --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/model/OpenAppApiRelation.java @@ -0,0 +1,32 @@ +package apelet.common.openapi.model; + +import apelet.common.core.mcp.annotation.McpToolParam; +import apelet.common.core.mcp.model.McpToolParamType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * OpenAPI 应用-接口配置多对多关联实体对象。 + *

多对多关联表统一极简设计:仅业务 id 字段 + 联合主键(即唯一索引),无自增主键/审计字段。

+ * + * @author chenchuchuan + * @date 2026-09-08 + */ +@Data +@TableName(value = "xy_sys_open_app_api_relation") +public class OpenAppApiRelation { + + /** + * 应用Id(xy_sys_open_app.id)。 + */ + @TableField(value = "app_id") + @McpToolParam(fieldName = "appId", type = McpToolParamType.INTEGER, description = "应用Id") + private Long appId; + /** + * 开放接口配置Id(xy_sys_open_api_config.id)。 + */ + @TableField(value = "api_id") + @McpToolParam(fieldName = "apiId", type = McpToolParamType.INTEGER, description = "开放接口配置Id") + private Long apiId; +} diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiAppService.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiAppService.java index dca490f..453b974 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiAppService.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiAppService.java @@ -37,4 +37,12 @@ public interface OpenApiAppService extends IBaseService { * @return 新的明文 appSecret。 */ String resetSecret(Long id); + + /** + * 删除应用(已被分配开放接口时拒绝,返回 false)。 + * + * @param id 应用主键Id。 + * @return 删除成功返回 true;应用不存在或已被分配返回 false。 + */ + boolean removeAppWithRelationCheck(Long id); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiConfigService.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiConfigService.java index c8977da..4f46bd4 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiConfigService.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/OpenApiConfigService.java @@ -6,6 +6,7 @@ import apelet.common.openapi.dto.OpenApiFormOptionDto; import apelet.common.openapi.dto.OpenApiOperationDto; import apelet.common.openapi.dto.OpenApiPageOptionDto; import apelet.common.openapi.model.OpenApiConfig; +import apelet.common.openapi.model.OpenAppApiRelation; import java.util.List; @@ -27,6 +28,15 @@ public interface OpenApiConfigService extends IBaseService List getOpenApiConfigList(OpenApiConfig filter, String orderBy); /** + * 列表统一查询入口:appId 有值时按应用已授权过滤(join junction,SQL 内按需叠加 status),否则走普通过滤查询。 + * + * @param filter 查询条件Dto(appId/status 均可空)。 + * @param orderBy 排序字符串。 + * @return 配置列表。 + */ + List getOpenApiConfigListByFilter(apelet.common.openapi.dto.OpenApiConfigFilterDto filter, String orderBy); + + /** * 获取在线页面下拉列表(配置步骤1),可按页面名称模糊搜索。 * * @param keyword 页面名称关键字,为空返回全部。 @@ -81,4 +91,29 @@ public interface OpenApiConfigService extends IBaseService * @return 配置对象。 */ OpenApiConfig getByRequestPath(String requestPath); + + /** + * 分配应用:全量替换该接口配置的应用授权(先删该 configId 全部授权行,再按 appIds 插入;空集合即清空)。 + * + * @param configId 开放接口配置Id。 + * @param appIds 授权应用Id 集合(自动去重)。 + * @return 替换后该配置已授权的应用Id 集合。 + */ + List assignApps(Long configId, List appIds); + + /** + * 查询指定接口配置已授权的应用编码集合(详情 VO 回显用)。 + * + * @param configId 开放接口配置Id。 + * @return 已授权应用的应用编码集合(xy_sys_open_app.app_code)。 + */ + List getAppCodesByConfigId(Long configId); + + /** + * 批量获取接口配置的授权关联列表(app_id + api_id,一次查询;单个 configId 用单元素集合传入)。 + * + * @param configIdList 开放接口配置Id 集合。 + * @return 授权关联列表。 + */ + List getAppApiRelationListByConfigIdList(List configIdList); } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAppServiceImpl.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAppServiceImpl.java index 2630b88..1ce4db5 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAppServiceImpl.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAppServiceImpl.java @@ -4,6 +4,7 @@ import apelet.common.core.base.dao.BaseDaoMapper; import apelet.common.core.base.service.BaseService; import apelet.common.core.object.TokenData; import apelet.common.openapi.dao.OpenApiAppMapper; +import apelet.common.openapi.dao.OpenAppApiRelationMapper; import apelet.common.openapi.model.OpenApiApp; import apelet.common.openapi.service.OpenApiAppService; import cn.hutool.core.util.IdUtil; @@ -28,6 +29,8 @@ public class OpenApiAppServiceImpl extends BaseService impleme @Autowired private OpenApiAppMapper openApiAppMapper; + @Autowired + private OpenAppApiRelationMapper openAppApiRelationMapper; @Override protected BaseDaoMapper mapper() { @@ -79,4 +82,16 @@ public class OpenApiAppServiceImpl extends BaseService impleme this.updateById(app); return newSecret; } + + @Override + public boolean removeAppWithRelationCheck(Long id) { + if (id == null || this.getById(id) == null) { + return false; + } + // 配置与应用的关联已全部走授权关联表,存在授权行即禁止删除 + if (openAppApiRelationMapper.countByAppId(id) > 0) { + return false; + } + return this.removeById(id); + } } diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAuthServiceImpl.java b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAuthServiceImpl.java index 4fa7d56..d1f66fd 100644 --- a/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAuthServiceImpl.java +++ b/common/common-openapi/src/main/java/apelet/common/openapi/service/impl/OpenApiAuthServiceImpl.java @@ -63,8 +63,7 @@ public class OpenApiAuthServiceImpl implements OpenApiAuthService { return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST, "应用编码、密钥、用户名均不能为空!"); } // ① 校验应用凭证与状态 - OpenApiApp app = openApiAppService.getOne(new LambdaQueryWrapper() - .eq(OpenApiApp::getAppCode, appCode)); + OpenApiApp app = openApiAppService.getOne(new LambdaQueryWrapper().eq(OpenApiApp::getAppCode, appCode)); if (app == null || !app.getAppSecret().equals(appSecret)) { return ResponseResult.error(ErrorCodeEnum.UNAUTHORIZED_LOGIN, "应用编码或密钥错误!"); } 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 695999e..617d3c7 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 @@ -17,11 +17,12 @@ import apelet.common.openapi.config.OpenApiProperties; import apelet.common.openapi.constant.OpenApiFieldSource; import apelet.common.openapi.constant.OpenApiParamMode; import apelet.common.openapi.dao.OpenApiConfigMapper; -import apelet.common.openapi.dto.OpenApiFieldDto; -import apelet.common.openapi.dto.OpenApiFormOptionDto; -import apelet.common.openapi.dto.OpenApiOperationDto; -import apelet.common.openapi.dto.OpenApiPageOptionDto; +import apelet.common.openapi.dao.OpenAppApiRelationMapper; +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.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiConfigService; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; @@ -33,9 +34,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; /** * OpenAPI 开放接口配置数据操作服务类。 @@ -53,6 +53,10 @@ public class OpenApiConfigServiceImpl extends BaseService i @Autowired private OpenApiConfigMapper openApiConfigMapper; @Autowired + private OpenAppApiRelationMapper openAppApiRelationMapper; + @Autowired + private OpenApiAppService openApiAppService; + @Autowired private OpenApiProperties openApiProperties; @Autowired private OnlinePageService onlinePageService; @@ -79,6 +83,16 @@ public class OpenApiConfigServiceImpl extends BaseService i } @Override + public List getOpenApiConfigListByFilter(OpenApiConfigFilterDto filter, String orderBy) { + if (filter == null || filter.getAppId() == null) { + OpenApiConfig configFilter = new OpenApiConfig(); + configFilter.setStatus(filter == null ? null : filter.getStatus()); + return openApiConfigMapper.getOpenApiConfigList(configFilter, orderBy); + } + return openApiConfigMapper.getOpenApiConfigListByAppId(filter.getAppId(), filter.getStatus(), orderBy); + } + + @Override public List getPageOptionList(String keyword) { // 按页面名称模糊搜索,空关键字返回全部;结果按创建时间倒序 OnlinePage filter = null; @@ -284,6 +298,54 @@ public class OpenApiConfigServiceImpl extends BaseService i return openApiConfigMapper.selectOne(new LambdaQueryWrapper().eq(OpenApiConfig::getRequestPath, requestPath)); } + @Override + public List assignApps(Long configId, List appIds) { + OpenApiConfig config = this.getById(configId); + if (config == null) { + throw new IllegalArgumentException("数据验证失败,开放接口配置不存在!"); + } + Set appIdSet = appIds == null ? new HashSet<>() : new LinkedHashSet<>(appIds); + // 全量替换:先清空该配置的授权,再按入参重插;空集合即清空授权 + openAppApiRelationMapper.deleteByApiId(configId); + if (!appIdSet.isEmpty()) { + List existApps = openApiAppService.list(new LambdaQueryWrapper().in(OpenApiApp::getId, appIdSet)); + if (existApps.size() < appIdSet.size()) { + throw new IllegalArgumentException("数据验证失败,存在无效的应用Id!"); + } + List relList = new ArrayList<>(appIdSet.size()); + for (Long appId : appIdSet) { + OpenAppApiRelation rel = new OpenAppApiRelation(); + rel.setAppId(appId); + rel.setApiId(configId); + relList.add(rel); + } + openAppApiRelationMapper.batchInsert(relList); + } + return new ArrayList<>(appIdSet); + } + + @Override + public List getAppCodesByConfigId(Long configId) { + if (configId == null) { + return new ArrayList<>(); + } + List relationList = this.getAppApiRelationListByConfigIdList(Collections.singletonList(configId)); + if (relationList.isEmpty()) { + return new ArrayList<>(); + } + List appIds = relationList.stream().map(OpenAppApiRelation::getAppId).collect(Collectors.toList()); + List appList = openApiAppService.list(new LambdaQueryWrapper().in(OpenApiApp::getId, appIds)); + return appList.stream().map(OpenApiApp::getAppCode).collect(Collectors.toList()); + } + + @Override + public List getAppApiRelationListByConfigIdList(List configIdList) { + if (configIdList == null || configIdList.isEmpty()) { + return new ArrayList<>(); + } + return openAppApiRelationMapper.selectByApiIdList(configIdList); + } + /** * 推导参数模式:query→QUERY;保存/提交→FIELD_DIRECT;其他→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 e8fd57f..480dd4d 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 @@ -20,7 +20,10 @@ import apelet.common.online.service.OnlineTableService; import apelet.common.online.util.OnlineOperationHelper; import apelet.common.openapi.constant.OpenApiFieldSource; 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.service.OpenApiAppService; import apelet.common.openapi.service.OpenApiConfigService; import apelet.common.openapi.service.OpenApiExecService; import apelet.common.openapi.service.OpenApiOnlineQueryService; @@ -33,7 +36,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.io.Serializable; import java.math.BigDecimal; import java.util.*; @@ -52,6 +54,10 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { @Autowired private OpenApiConfigService openApiConfigService; @Autowired + private OpenApiAppService openApiAppService; + @Autowired + private OpenAppApiRelationMapper openAppApiRelationMapper; + @Autowired private OnlineFormService onlineFormService; @Autowired private OnlineTableService onlineTableService; @@ -77,7 +83,11 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, "在线表单不存在!"); } TokenData tokenData = TokenData.takeFromRequest(); - if (tokenData == null || !config.getAppCode().equals(tokenData.getAppCode())) { + if (tokenData == null || StrUtil.isBlank(tokenData.getAppCode())) { + return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION, "当前应用无权调用该开放接口!"); + } + // 授权校验走多对多关联表:token.appCode → 应用 → junction (app_id, api_id) 存在才放行(一条接口配置可授权多个应用) + if (!this.hasAssignRelation(tokenData.getAppCode(), config.getId())) { return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION, "当前应用无权调用该开放接口!"); } // 列表类操作(QUERY / CONDITION_MATCH)依赖 pc.tableWidget 走列表引擎,编辑页未配置列表时给出明确提示而非底层空指针。 @@ -242,6 +252,17 @@ public class OpenApiExecServiceImpl implements OpenApiExecService { // ============ 公共辅助 ============ /** + * 校验应用与接口配置的授权关系:appCode → xy_sys_open_app.id → xy_sys_open_app_api (app_id, api_id)。 + */ + private boolean hasAssignRelation(String appCode, Long configId) { + OpenApiApp app = openApiAppService.getOne(new LambdaQueryWrapper().eq(OpenApiApp::getAppCode, appCode)); + if (app == null) { + return false; + } + return openAppApiRelationMapper.existByAppIdAndApiId(app.getId(), configId) > 0; + } + + /** * 定位在线表单:优先按 formId,未配置时回退 pageId + formCode。 */ private OnlineForm findForm(OpenApiConfig config) { diff --git a/common/common-openapi/src/main/java/apelet/common/openapi/vo/OpenApiConfigVo.java b/common/common-openapi/src/main/java/apelet/common/openapi/vo/OpenApiConfigVo.java new file mode 100644 index 0000000..aa1354d --- /dev/null +++ b/common/common-openapi/src/main/java/apelet/common/openapi/vo/OpenApiConfigVo.java @@ -0,0 +1,104 @@ +package apelet.common.openapi.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * OpenAPI 开放接口配置VO对象(详情接口返回,含关联应用编码集合)。 + * + * @author chenchuchuan + * @date 2026-09-08 + */ +@Schema(description = "OpenAPI开放接口配置VO对象") +@Data +public class OpenApiConfigVo { + + /** + * 主键Id。 + */ + @Schema(description = "主键Id") + private Long id; + /** + * 在线页面主键(OnlinePage.pageId)。 + */ + @Schema(description = "在线页面主键Id") + private Long pageId; + /** + * 页面编码(冗余存储,用于组装开放路径)。 + */ + @Schema(description = "页面编码") + private String pageCode; + /** + * 该页面下关联的在线表单编码。 + */ + @Schema(description = "在线表单编码") + private String formCode; + /** + * 关联的在线表单主键Id。 + */ + @Schema(description = "在线表单主键Id") + private Long formId; + /** + * 操作编码:query 或 operationList 中操作的 code。 + */ + @Schema(description = "操作编码") + private String operationCode; + /** + * 操作名称。 + */ + @Schema(description = "操作名称") + private String operationName; + /** + * 参数模式(后端自动推导):FIELD_DIRECT / CONDITION_MATCH / QUERY。 + */ + @Schema(description = "参数模式") + private String paramMode; + /** + * 开放路径:/openapi/v1/{pageCode}/{formCode}/{operationCode},全局唯一。 + */ + @Schema(description = "开放路径") + private String requestPath; + /** + * 请求方法 GET / POST。 + */ + @Schema(description = "请求方法") + private String requestMethod; + /** + * JSON:输入参数+条件配置。 + */ + @Schema(description = "参数/条件配置JSON") + private String paramConfig; + /** + * JSON:输出层级结构。 + */ + @Schema(description = "输出层级配置JSON") + private String outputConfig; + /** + * 状态(0: 停用 1: 启用)。 + */ + @Schema(description = "状态(0: 停用 1: 启用)") + private Integer status; + /** + * 创建时间。 + */ + @Schema(description = "创建时间") + private Date createTime; + /** + * 更新时间。 + */ + @Schema(description = "更新时间") + private Date updateTime; + /** + * 引用编码集合:该配置已授权应用的应用编码(xy_sys_open_app.app_code,经 xy_sys_open_app_api 反查)。 + */ + @Schema(description = "引用编码集合:已授权应用的应用编码集合") + private List appCodes; + /** + * 引用Id集合:该配置已授权应用的应用Id(xy_sys_open_app.app_id,经 xy_sys_open_app_api 反查)。 + */ + @Schema(description = "引用Id集合:已授权应用的应用Id集合") + private List appIdList; +}