跳转至

API 中心(ApiConfig / ApiGroup)定义编写指南

目标:Agent 直接生成/修改 workspace API 中心定义(分组 + 接口),使 magic-api 可被第三方/前台调用。知识以本文为准。不写原理;不依赖外链。

iScript:写 responseScript 时,先读 iscript-usage(含 GraalVM 差异) → api.md;本文只定路由/分组与落盘。

术语:实体类 cn.myapps.core.common.model.api.ApiConfigcn.myapps.core.common.model.api.group.ApiGroup;产品「API 中心 / 高级工具-API中心」;运行入口 MagicApiController。是软件级 自定义 HTTP 接口(iScript 写响应),**不是**平台内置 /api/runtime /api/designtime,**不是**外部接口 .extinterface(那是脚本里调出站 HTTP/WebService/JAR 的封装),**不是**企业域 Open API Secret。

依赖:挂在**软件(Application)下。设计态新建 API **必须先有 ApiGroup(校验「请选择API分组!」)。parentId/apiGroup = 分组 id。

产出物一览

部分 落盘 形态
API 分组 api/{分组名}.apigroup/{分组名}.apigroup 目录型;JAXB 根 ApiGroupid 为根属性
API api/{分组名}.apigroup/{API名}.api 单文件;JAXB 根 Apiid 为根属性
# 推荐(设计态现行 / 与 Widget 分组同构)
storage/workspace/{软件名}.application/api/{分组名}.apigroup/{分组名}.apigroup
storage/workspace/{软件名}.application/api/{分组名}.apigroup/{API名}.api

# 历史/样例常见:分组元数据为扁平单文件,API 直接躺在 api/ 下(仍可被后缀扫描载入)
storage/workspace/{软件名}.application/api/{分组名}.apigroup
storage/workspace/{软件名}.application/api/{API名}.api

# 极旧:无分组(apiGroup 空、parentId=软件 id)
storage/workspace/{软件名}.application/api/{API名}.api

路径相对 storage/workspace

常量(ModelSuffix
API_PATH_SUFFIX / API_FILE_SUFFIX api
APIGROUP_PATH_SUFFIX / APIGROUP_FILE_SUFFIX apigroup
APIGROUP_FILE_GROUP /api

DAO 按后缀 .api / .apigroup 递归扫描**应用树,扁平与目录型都能载入。运行匹配靠 **HTTP 方法 + Ant 路径模板,不靠文件路径。

新建最低配置:

  1. 若不存在分组:先建 {name}.apigroup/ + 同名元数据(见 §7)
  2. {名}.apiidnameparentId=分组 id、apiGroup=分组 id、statusrequestTyperequestUrlencode=UTF-8responseType、非空倾向 responseScript
  3. requestUrl/ 开头;可含 {pathVar};与调用路径(去掉 /magic-api/{软件名} 后)一致
  4. 同软件内 API name 不重名(设计态按 parent 列表校验「名称已经存在!」)

约定:

  • JAXB;根元素 Api / ApiGroup(PascalCase);id根属性
  • responseScript 用 CDATA
  • 文件名 = name + .api;分组同理 name + .apigroup;目录型时「目录名 = 元数据文件名」
  • name 勿含 / % \(落盘替换为 =47/=37/=92
  • parentId:有分组时 = apiGroup(分组 id);无分组历史样例 = 软件 id
  • applicationid:基类常**无**落盘;靠路径归属 / API save 时 set
  • **勿**放到 module/ 下;永远在应用级 api/
  • requestType 小写get/post/put/delete
  • tags:字符串;UI 多标签用 ; 拼接;可空串

1. 心智模型

软件 Application
  └─ api/
       ├─ {分组}.apigroup/            ← ApiGroup(目录型推荐)
       │    ├─ {分组}.apigroup        ← 分组元数据
       │    └─ {名}.api               ← ApiConfig
       ├─ {分组}.apigroup             ← 历史扁平分组文件
       └─ {名}.api                    ← 历史扁平 / 无分组

外部/前台 HTTP
  {method} {context}/magic-api/{软件名}{requestUrl}
        ▼ MagicApiController.execute
  按软件名→applicationId;findByRequestUrl(method, path)
        ├─ status≠public 且无登录用户 → 401
        ├─ AntPathMatcher 抽 {pathVar} → ParamsTable
        ├─ body → params._content
        └─ 跑 responseScript(空 Document + WebUser)
              └─ 按 responseType 写出 json/xml/raw/binary
概念 说明
分组 ApiGroup 设计器左侧/筛选;物理目录容器;无业务路由字段
requestUrl 自定义路径模板,相对 magic-api/{软件名};**不含**软件名前缀
requestType HTTP 方法;与 request.getMethod() **忽略大小写**比较
status 生命周期标签;鉴权只特殊对待 public(免登录),其它一律要登录用户
responseScript 接口正文 iScript;返回值按 responseType 序列化
responseType json / xml / raw / binary
encode 设计器有字段;现行运行端写响应**固定 UTF-8**,生成写 UTF-8 即可

与相近能力区分:

对象 用途
.api / MagicApi **入站**自定义接口(本文)
.extinterface **出站**接口定义,生成脚本片段给 iScript 调用外部系统
/api/runtime/* /api/designtime/* 平台内置 REST,不是 workspace 文件
域 Open API Secret 第三方调平台开放 API 的令牌密钥,不是 .api

2. ApiConfig 属性 → .api XML

属性表

属性 XML 类型 默认/样例 说明
id 根属性 string 须生成 __ + 短 UUID
name 子元素 string 必填;文件名;同 parent 下唯一
parentId 子元素 string = 分组 id(设计态 create/update 强制 setParentId(apiGroup)
applicationid 常不落盘 string API 侧 = 软件 id
description/remark CDATA string 基类;样例常省
status 子元素 string published 见 §3 状态表
apiGroup 子元素 string = 分组 id;设计态空则拒存
encode 子元素 string UTF-8 UI 仅此选项;运行常忽略
requestType 子元素 string get get/post/put/delete
requestUrl 子元素 string 必填语义;如 /hello/departments/{departmentId}
tags 子元素 string 可空 ; 分隔多标签
responseType 子元素 string json json/xml/raw/binary
responseScript CDATA string 响应 iScript

id 生成

统一规则:__ + 短 UUID。示例:__MpEzTToulqZtNEFisw6。须全局/库内不冲突(.api / .apigroup 同)。

路径计算(保存/搬家)

ApiConfig.getPath()

  1. apiGroup 非空:解析分组 → apiGroup.getPath() + "/" + name + ".api"
    {软件}.application/api/{分组名}.apigroup/{API名}.api
  2. 否则:application.getPath() + "/" + name + ".api"(落在 api/ 组下时由父路径决定)

ApiGroup 目录型 uri = getPath() + "/" + name + ".apigroup"(与 WidgetGroup/Module 同类)。


3. 常量表

status

UI 运行鉴权
published 发布(默认) 要登录
public 公开 免登录user==null 仍执行)
development 开发 要登录
test 测试 要登录
abnormal 异常 要登录
maintain 维护 要登录
abandoned 废弃 要登录

public 外,**全部**走「无用户 → 401 Not Permission」。abandoned/development 等**不会**被运行端自动拦截;想禁用不产生匹配则应删接口或改 requestUrl,勿假设状态会禁用路由。

requestType

HTTP
get GET
post POST
put PUT
delete DELETE

须小写写入 XML;匹配时 equalsIgnoreCase

responseType

脚本应 return 写出
json Map/Bean/Collection/JSONArray 等对象 application/json;经 ObjectMapper→JSONObject/JSONArray
xml 对象 application/xml;对象→JSON→Staxon JSON→XML,外包类简单名标签
raw 字符串 原文追加(无强制 Content-Type 分支)
binary java.io.File(或整体 result 为 File) application/x-download 附件下载

设计器提示摘要:

  • json:return HashMap / 业务对象
  • xml:return VO/对象
  • raw:return "字符串"
  • binary:return new java.io.File("...")

encode

UTF-8。生成固定写它。


4. 运行时调用与匹配

URL

{METHOD} {contextPath}/magic-api/{软件名}{requestUrl}

例:软件名 admintoolsrequestType=getrequestUrl=/departments/{departmentId}

GET /obpm/magic-api/admintools/departments/xxx
GET {runtimeContext}/magic-api/admintools/departments/xxx

控制器剥前缀逻辑(两套):

  1. URI 以 /obpm/magic-api/ 开头 → 去掉该前缀再去掉 appName 长度,余下为匹配 path
  2. 否则:去掉 contextPath + "/magic-api/" + appName,余下为匹配 path

余下 path 通常仍带前导 /,须与 requestUrl 一致(含 /)。

appName = 软件 name(目录 {name}.application 的 name),不是 id。

匹配算法(findByRequestUrl

  1. 列出该软件全部 .api
  2. methodrequestType 忽略大小写相等
  3. AntPathMatcher.match(requestUrl模板, 实际path) 为真 → 取第一个 命中

因此:同方法下更泛的模板可能抢先;勿在两接口间写互相覆盖的模板;若冲突,依赖列表顺序(排序后遍历)不可控,应改路径。

路径变量:extractUriTemplateVariables → 写入 ParamsTable(可用 getParameter("departmentId"))。

Query string:由框架 getParams() 进 ParamsTable;不是 requestUrl 模板的一部分。UI placeholder 里的 ?age={age} 勿当真——查询参数直接用请求 query,不必写进 requestUrl

Body:@RequestBody String contentparams.setParameter("_content", content)。POST/PUT JSON 体用 getParameter("_content")JSONObject.fromObject(...)

脚本上下文

IRunner.initBSFManager(
  new Document(),           // 空文档,无表单 CURDOC 业务态
  params,                   // 含 path 变量、query、_content
  webUser,                  // public 时可为 null
  empty ValidateMessage list
)
ScriptLabel(..., TYPE.API.RESPONSE="API:RESPONSE", apiId, apiName)

常用取参:

写法 含义
getParameter("x") ParamsTable
CONTEXT.getParameter("x") 同上常见写法
getParameter("_content") 原始 body 字符串
getParamsTable().getHttpRequest() 原生 request(日志等)
getWebUser() / getApplication() / getDomainid() 会话与应用(public 时 user 可能空,慎用)

responseScript 返回 null → 不写响应体(仍 200 空)。异常 → printStackTrace 后向 writer 写 e.getMessage()


5. XML 骨架

ApiGroup

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ApiGroup id="__GROUP_UUID">
  <name>部门模块</name>
  <parentId>SOFTWARE_UUID</parentId>
</ApiGroup>

路径:api/部门模块.apigroup/部门模块.apigroup

Api(JSON GET + path 变量)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Api id="__API_UUID">
  <name>根据id获取部门</name>
  <parentId>__GROUP_UUID</parentId>
  <status>published</status>
  <apiGroup>__GROUP_UUID</apiGroup>
  <encode>UTF-8</encode>
  <requestType>get</requestType>
  <requestUrl>/departments/{departmentId}</requestUrl>
  <tags></tags>
  <responseType>json</responseType>
  <responseScript><![CDATA[(function(){
    var departmentId = getParameter("departmentId");
    // …查库/调工具类…
    return resultMap;
})()]]></responseScript>
</Api>

Api(POST + body → JSON)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Api id="__API_UUID">
  <name>卡片保存</name>
  <parentId>__GROUP_UUID</parentId>
  <status>published</status>
  <apiGroup>__GROUP_UUID</apiGroup>
  <encode>UTF-8</encode>
  <requestType>post</requestType>
  <requestUrl>/create_card</requestUrl>
  <tags></tags>
  <responseType>json</responseType>
  <responseScript><![CDATA[(function () {
    var content = getParameter("_content");
    var Apijson = new (Java.type('java.util.HashMap'))();
    if (content == null || content === "") {
        Apijson.put("errcode", 400);
        Apijson.put("errmsg", "参数不能为空");
        return Apijson;
    }
    var requestJson = (Java.type('net.sf.json.JSONObject')).fromObject(content);
    // …业务…
    Apijson.put("errcode", 200);
    Apijson.put("errmsg", "ok");
    return Apijson;
})()]]></responseScript>
</Api>

Api(public + raw)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Api id="__API_UUID">
  <name>消息接收</name>
  <parentId>__GROUP_UUID</parentId>
  <status>public</status>
  <apiGroup>__GROUP_UUID</apiGroup>
  <encode>UTF-8</encode>
  <requestType>get</requestType>
  <requestUrl>/MsgReceived</requestUrl>
  <tags></tags>
  <responseType>raw</responseType>
  <responseScript><![CDATA[(function(){
    var echo = getParameter("echostr");
    return echo;
})()]]></responseScript>
</Api>

Api(binary)

...
  <responseType>binary</responseType>
  <responseScript><![CDATA[(function(){
    var file = new Packages.java.io.File("D://test.txt");
    return file;
})()]]></responseScript>

binary 须返回存在的 File;走下载头 Content-Disposition: attachment


6. 设计态 / 运行态 HTTP(定位用)

设计态基路径(典型):${designerContext}/api/designtime/applications

方法 路径 说明
GET /{appId}/apis 列表;query:nameapiGroupIdrequestTypestatuspageNolinesPerPage
GET /{appId}/apis/{apiId} 详情
POST /{appId}/apis 新建;parentId=apiGroup;空分组 →「请选择API分组!」;重名 →「名称已经存在!」
PUT /{appId}/apis/{apiId} 更新;回写字段见控制器;uri 置 null 以重算路径
DELETE /{appId}/apis body:id JSON 数组

分组:

方法 路径 说明
GET /{appId}/apigroups 列表;parent 实为软件 id
GET /{appId}/apigroups/{apiGroupId} 详情
POST /{appId}/apigroups 新建;parentId=applicationId
PUT /{appId}/apigroups/{apiGroupId} 主要改 name
DELETE /{appId}/apigroups body:id 数组

运行态:

方法 路径 说明
ANY(由各 .api 的 requestType 决定) {runtimeContext}/magic-api/{软件名}/** MagicApiController;无独立 CRUD

设计器 UI 展示完整前缀:/magic-api/{{软件名}} + 用户填的 requestUrl


7. ApiGroup — 本文内自足

实体:ApiGroup。根 ApiGroup。无额外业务字段,仅 id/name/parentId(+ 基类可空描述)。

getFileGroup() = /apigetPath()(parent 为 Application 时)=

{软件路径}/api/{name}.apigroup

目录型 uri(APIGROUP 在「目录型后缀」列表):

{软件路径}/api/{name}.apigroup/{name}.apigroup

骨架

见 §5。parentId = 软件 id

注意

  • 改分组 name = 改目录名;其下 .apigetPath() 依赖分组名,**勿随意改名**除非同步磁盘文件。
  • 删除分组前应先迁走/删除子 API,否则遗留文件。
  • 设计态校验:同软件下分组 name 不重名(「名称已经存在!」)。
  • 存量可能仅有扁平 xxx.apigroup 文件且 API 平铺在 api/:载入仍可;新建优先目录型,与 admintools/demo-basic 一致。

8. 与外部接口 / 平台 API 的边界

能力 载体 本文是否管
自定义入站 HTTP .api + magic-api
脚本调外部 REST/WS/JAR .extinterfaceextinterface/
设计器/运行内置资源 CRUD /api/designtime /api/runtime
第三方调平台标准开放接口 域 Open API Secret + 平台 REST
表单/视图业务数据 API Document/View 控制器

.extinterface 落盘组常为应用级 extinterface/;字段含 type(restful/webservice/jar)、requestUrlexampleCode/jsCode 等,没有 responseScript 入站执行模型。


9. 检查清单(生成后自检)

  • 已有 .apigroup,且 API 的 apiGroup=parentId=该分组 id
  • 文件在 api/{分组}.apigroup/{name}.api(推荐)
  • 根元素 Api / ApiGroup(PascalCase,非 camelCase)
  • requestType 小写;requestUrl/ 开头
  • 调用 URL = /magic-api/{软件名} + requestUrl(软件名而非 id)
  • 免登录需求 → status=public;否则用 published 等且调用带会话
  • responseType 与 return 类型匹配(raw→String,binary→File,json/xml→对象)
  • POST/PUT 读 body 用 _content
  • path 变量名与 {var} 一致,并用 getParameter
  • 同方法下无互相覆盖的 Ant 模板
  • 同 parent 无同名 API;name 无 /\%
  • encode=UTF-8responseScript 为 CDATA

10. 典型生成顺序

  1. 确认软件目录 {app}.application/ 存在(记下软件 nameid
  2. 建分组 api/{G}.apigroup/{G}.apigroupparentId=软件 id)
  3. 定 HTTP 方法、requestUrl、鉴权 statusresponseType
  4. .api 到分组目录;脚本内完成取参与返回
  5. 用对应 METHOD 调 {runtime}/magic-api/{软件名}{requestUrl} 验证
  6. public 时带登录 cookie/token;public 可匿名

附录 A:后缀常量(ModelSuffix)

常量
API_PATH_SUFFIX / API_FILE_SUFFIX api
APIGROUP_PATH_SUFFIX / APIGROUP_FILE_SUFFIX apigroup
APIGROUP_FILE_GROUP /api

类映射:ModelSuffixEx.apiApiConfig.apigroupApiGroup


附录 B:设计器控制器类名

职责
ApiDesignTimeController API CRUD
ApiGroupDesignTimeController 分组 CRUD
ApiDesignTimeServiceImpl query / findByRequestUrl
FileSystemApiDAO 过滤 name/requestType/requestUrl/status/apiGroupId
MagicApiController 运行态执行

包路径摘要:cn.myapps.designtime.api…cn.myapps.core.common.model.api…cn.myapps.runtime.common.controller.MagicApiController


附录 C:常见失败

现象 原因
设计器保存报请选择API分组 apiGroup
名称已经存在 同 parent 下 name 重复
404 Api Not Found 软件名错;或 method/path 无匹配;或软件未扫到该 .api
401 Not Permission status≠public 且未登录
raw 响应怪 / ClassCast responseType=raw 但 return 非 String
binary 失败 未 return File,或文件不存在/SecurityFile 不可读
json 空串字段 运行端把 null 序列化成 ""
POST 取不到体 未读 _content,或客户端未传 body
path 变量空 requestUrl 未写 {名},或实际 URL 段与模板不一致
改组名后路径乱 磁盘目录未随 name 搬家
与另一 API 行为错乱 Ant 模板冲突,先匹配到错误接口
根标签解析失败 写成 <api>/<ApiConfig>;须 <Api> / <ApiGroup>

附录 D:脚本最小模式速查

// GET + path var → JSON
(function(){
  var id = getParameter("id");
  var m = new (Java.type('java.util.HashMap'))();
  m.put("id", id);
  return m;
})()
// POST JSON body
(function(){
  var content = getParameter("_content");
  var jo = (Java.type('net.sf.json.JSONObject')).fromObject(content);
  var m = new (Java.type('java.util.HashMap'))();
  m.put("echo", jo.get("x"));
  return m;
})()
// raw
(function(){ return "ok"; })()

#include "baselibrary" / #include "sysfunction" 等软件宏库(与其它 iScript 一致),按目标软件已有 repository 使用。