# 模块自定义 API 路由(v0.15) 应用通过 manifest.api_routes 声明模块内 JSON 路由;底座把它们挂在 `/apps//api` 下。H5 由应用编写,API 也由应用定义,但认证与路由治理统一由底座完成,随后经现有 Execute RPC 转发。当前不是把任意端口、URL 或 HTTP 服务代理出来;容器仍断网,不增加挂载或 Docker 参数。 ## 声明与后端处理 ```json "api_routes": [ {"name":"articles-list","method":"GET","path":"/articles","action":"list"}, {"name":"articles-submit","method":"POST","path":"/articles/{id}/submit","action":"submit"} ] ``` 路由属于应用安装包,与清单、二进制和 H5 一起模拟、审批及发布。最多 64 条;名称唯一,方法支持 GET/POST/PUT/PATCH/DELETE。路径最多 240 字符、12 段,静态段/实际参数使用 ASCII 字母、数字、下划线或连字符;变量名使用小写标识符。禁止绝对 URL、点段、百分号编码、重复参数和尾斜杠。同方法的可重叠模板拒绝安装,避免歧义;如 `/items/{id}` 与 `/items/latest` 需换成不重叠路径。 action 必须对应模块已经声明的动作权限;当前使用 list/create/edit/submit/publish/reject,GET 只能映射 list,只读模块只能映射 list。本版没有新增任意动作权限命名机制。每条路由可以在后端按 `req.API.Name` 分派,多个只读接口可映射同一 list 权限。权限控制、记录范围、能力授权和业务状态校验全部保留。 ```go func (c *App) Execute(req modulekit.Request, host modulekit.Host) (modulekit.Response, error) { if req.API != nil { // Name/Method/Path/Params/Query 由底座构造。 // 当前身份只使用 req.ActorID,不信任业务正文中的 actor_id。 switch req.API.Name { case "articles-list": // 处理 req.API.Query 等查询条件,继续执行数据范围校验。 case "articles-submit": // 检查稿件状态和修订号;身份与记录仍通过 host.Call 获取。 } } // 原有动作入口的 req.API 为 nil,可继续兼容。 return modulekit.Response{}, nil } ``` 路径参数同时以字符串绑定到 payload 同名字段,方便已有协议 1 模块,例如 `{id}` 注入 `payload.id`。若正文带不同 id 会拒绝,不能覆盖路径指向的资源。查询不自动合并到 payload,通过 `req.API.Query` 获取;模块需自行校验查询字段。更新 SDK 后可读取 API 元数据;业务 RPC 仍是协议 1。 ## H5 与 Agent 调用 H5 使用 `PlatformUI.request()` 访问当前应用的声明路由。 ```javascript const ui = window.PlatformUI; await ui.ready; const articles = await ui.request('/articles'); const requestID = crypto.randomUUID(); // 保存这个值,以便未知结果时原号重试 await ui.request('/articles/' + articleID + '/submit', { method: 'POST', body: {revision: 2}, requestID }); ``` SDK 通过专属消息桥请求父页面;父页面只能访问当前应用前缀,不允许传模块 ID、任意 URL、Cookie 或自定义认证头。H5 仍不直接 fetch,没有获得平台凭据。上下文的 api_routes 列出当前角色可调用的声明路由,但最终以服务端实时授权为准。 Agent 使用 `Authorization: Bearer <个人 Key>` 访问同一路径。POST/PUT/PATCH/DELETE 发送 JSON 对象,并带 16–128 字符 `X-Request-ID`;浏览器还需要会话和 CSRF。读动作可由底座补请求号。返回沿用 `{data, code, error}` 信封,SDK 解包 data,失败抛出 code;响应包含 X-Trace-ID。 例如 `POST /apps/cms/api/articles//submit` 的正文是 `{"revision":2}`,不再需要 `{request_id,payload}` 包装。业务请求号用于去重;Trace ID 用于监控定位,两者用途不同。 ## 统一处理路径与边界 1. 认证会话/Key、检查 Host/Origin 与浏览器 CSRF。 2. 从当前已注册、正在运行的模块清单匹配方法和路径;非声明路径拒绝。 3. 校验路由对应动作权限、参数/查询/JSON 大小及请求号。 4. 使用原有接纳、排队、10 秒总执行期限和请求级身份通道,调用 Execute。 5. 模块按 API 名称或动作执行业务;反向公共服务继续实时校验身份、能力和数据范围。 6. 标准化返回错误并进入既有 HTTP、动作、回调指标。若解析路由后实例更换,返回 CONFLICT,不把旧路由交给新实例执行。 正文最多 64 KiB;查询总长最多 4096、32 个键、每键 8 个值;H5 消息桥仍有独立的并发、频率和大小限制。请求 body/path/query 不是可信身份。取消不保证撤销已提交写入,OUTCOME_UNKNOWN 应先查询或原号重试。 当前仅支持 JSON 请求与 JSON 响应,没有 multipart 文件上传、二进制流、SSE、WebSocket、自定义响应头或任意 HTTP 状态控制;后端仍按模块串行,不因增加 URL 路由而获得多副本或数据库直连。后续扩展这些能力需要独立协议与资源限制,不能通过放开容器网络绕过底座。 CMS 0.4.0 是完整示例:列表、新建、修改、提交、发布、退回均使用模块 API 路由,旧 `/api/actions/cms/*` 继续兼容。