@microi.net/cli 5.8.5 → 5.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/LICENSE +21 -21
  8. package/README.md +71 -71
  9. package/assets/build-meta.json +6 -6
  10. package/assets/feature-matrix.json +138 -138
  11. package/assets/logo.svg +4 -4
  12. package/cordis.patch.yml +1 -1
  13. package/package.json +1 -1
  14. package/scripts/codex-marketplace.json +20 -20
  15. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  16. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  17. package/scripts/microi-cli-mcp.js +7 -7
  18. package/scripts/microi-cli.js +66 -65
  19. package/scripts/microi-codex-broker.js +450 -450
  20. package/scripts/microi-codex-router.js +618 -618
  21. package/scripts/microi-skills.meta.json +384 -384
  22. package/skills/.microi-skills-version.json +2 -2
  23. package/skills/.progressive-disclosure-manifest.json +3566 -3566
  24. package/skills/README.md +287 -287
  25. package/skills/ai-engine/SKILL.md +269 -265
  26. package/skills/ai-engine/agents/openai.yaml +4 -4
  27. package/skills/ai-engine/references/ai-employees.md +48 -48
  28. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  29. package/skills/ai-platform-governance/SKILL.md +177 -177
  30. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  31. package/skills/app-store/SKILL.md +525 -525
  32. package/skills/app-store/agents/openai.yaml +4 -4
  33. package/skills/business-blueprint/SKILL.md +193 -193
  34. package/skills/datasource-engine/SKILL.md +93 -93
  35. package/skills/datasource-engine/agents/openai.yaml +4 -4
  36. package/skills/dos-orm/SKILL.md +97 -97
  37. package/skills/dos-orm/references/api-reference.md +229 -229
  38. package/skills/email-engine/SKILL.md +81 -81
  39. package/skills/email-engine/references/v8-email.md +34 -34
  40. package/skills/job-engine/SKILL.md +176 -176
  41. package/skills/job-engine/agents/openai.yaml +4 -4
  42. package/skills/message-notification/SKILL.md +156 -156
  43. package/skills/message-notification/agents/openai.yaml +5 -5
  44. package/skills/message-notification/references/contracts.md +102 -102
  45. package/skills/microi/SKILL.md +14 -14
  46. package/skills/microi-ai-app-auth.js +652 -652
  47. package/skills/microi-ai-application/SKILL.md +115 -115
  48. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  49. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  50. package/skills/microi-client-frontend/SKILL.md +244 -244
  51. package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -144
  52. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +196 -196
  53. package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +167 -167
  54. package/skills/microi-codex/SKILL.md +102 -102
  55. package/skills/microi-codex-installer/SKILL.md +231 -231
  56. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  57. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  58. package/skills/microi-db-schema/SKILL.md +175 -175
  59. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  60. package/skills/microi-db-schema/references/core-tables.md +695 -695
  61. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  62. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  63. package/skills/microi-db-schema/references/schema.md +646 -646
  64. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  65. package/skills/microi-deployment/SKILL.md +221 -221
  66. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  67. package/skills/microi-docs-coverage/SKILL.md +133 -133
  68. package/skills/microi-docs-coverage/references/capability-map.md +91 -91
  69. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  70. package/skills/microi-form-engine/SKILL.md +333 -333
  71. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  72. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  73. package/skills/microi-form-layout/SKILL.md +205 -205
  74. package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
  75. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  76. package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +176 -176
  77. package/skills/microi-left-right-layout/SKILL.md +141 -141
  78. package/skills/microi-microservice/SKILL.md +326 -324
  79. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  80. package/skills/microi-mobile-app-quality/SKILL.md +185 -185
  81. package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
  82. package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +128 -128
  83. package/skills/microi-solution-quotation/SKILL.md +78 -78
  84. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  85. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  86. package/skills/microi-sso/SKILL.md +92 -92
  87. package/skills/microi-sso/references/acceptance.md +49 -49
  88. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  89. package/skills/microi-sso/references/inbound.md +53 -53
  90. package/skills/microi-sso/references/outbound.md +39 -39
  91. package/skills/microi-system-delivery/SKILL.md +137 -137
  92. package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +193 -193
  93. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +217 -217
  94. package/skills/microi-ui/SKILL.md +192 -192
  95. package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -183
  96. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  97. package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -225
  98. package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -154
  99. package/skills/microi.v8.js +1921 -1921
  100. package/skills/module-engine/SKILL.md +255 -255
  101. package/skills/module-engine/references/module-config.md +204 -204
  102. package/skills/ocr-engine/SKILL.md +113 -113
  103. package/skills/ocr-engine/agents/openai.yaml +4 -4
  104. package/skills/page-engine/SKILL.md +206 -206
  105. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  106. package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -234
  107. package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -60
  108. package/skills/performance-testing/SKILL.md +221 -221
  109. package/skills/playwright-e2e/SKILL.md +196 -196
  110. package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -173
  111. package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -183
  112. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  113. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  114. package/skills/print-engine/SKILL.md +259 -259
  115. package/skills/production-readonly-audit/SKILL.md +41 -41
  116. package/skills/report-engine/SKILL.md +71 -71
  117. package/skills/report-engine/agents/openai.yaml +4 -4
  118. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  119. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  120. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  121. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  122. package/skills/search-engine/SKILL.md +75 -75
  123. package/skills/search-engine/agents/openai.yaml +4 -4
  124. package/skills/spider-engine/SKILL.md +190 -190
  125. package/skills/system-observability/SKILL.md +248 -248
  126. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  127. package/skills/translate-engine/SKILL.md +140 -140
  128. package/skills/translate-engine/agents/openai.yaml +4 -4
  129. package/skills/ui-design/SKILL.md +223 -223
  130. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  131. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  132. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  133. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  134. package/skills/ui-design/references/design-pattern-library.md +184 -184
  135. package/skills/ui-design/references/mci-design-contract.md +163 -163
  136. package/skills/ui-design/references/motion-and-media.md +78 -78
  137. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  138. package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -218
  139. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  140. package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -235
  141. package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -152
  142. package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -238
  143. package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -194
  144. package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -207
  145. package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -142
  146. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  147. package/skills/unity-integration/SKILL.md +171 -171
  148. package/skills/unity-integration/agents/openai.yaml +4 -4
  149. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  150. package/skills/unity-integration/references/sdk-api.md +82 -82
  151. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  152. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  153. package/skills/v8-api-config/SKILL.md +388 -388
  154. package/skills/v8-cache-pattern/SKILL.md +312 -312
  155. package/skills/v8-crud-api/SKILL.md +178 -178
  156. package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -226
  157. package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -49
  158. package/skills/v8-debugging/SKILL.md +284 -284
  159. package/skills/v8-explorer-tree/SKILL.md +228 -228
  160. package/skills/v8-export-import/SKILL.md +219 -219
  161. package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -211
  162. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  163. package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -42
  164. package/skills/v8-file-upload/SKILL.md +284 -284
  165. package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +263 -263
  166. package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +161 -161
  167. package/skills/v8-formengine-http/SKILL.md +238 -238
  168. package/skills/v8-frontend-events/SKILL.md +180 -180
  169. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  170. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  171. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  172. package/skills/v8-http-integration/SKILL.md +182 -182
  173. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  174. package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -44
  175. package/skills/v8-image-processing/SKILL.md +190 -190
  176. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  177. package/skills/v8-image-processing/references/api-reference.md +623 -623
  178. package/skills/v8-menu-buttons/SKILL.md +186 -186
  179. package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +222 -222
  180. package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
  181. package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -104
  182. package/skills/v8-mongodb/SKILL.md +200 -200
  183. package/skills/v8-mq-mqtt/SKILL.md +176 -176
  184. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  185. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  186. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  187. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  188. package/skills/v8-security/SKILL.md +210 -210
  189. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  190. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  191. package/skills/v8-sql-query/SKILL.md +302 -302
  192. package/skills/v8-table-event/SKILL.md +176 -176
  193. package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -216
  194. package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -46
  195. package/skills/v8-tcp-integration/SKILL.md +147 -147
  196. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  197. package/skills/v8-template-engine/SKILL.md +167 -167
  198. package/skills/v8-utilities/SKILL.md +104 -104
  199. package/skills/v8-utilities/references/client-api-index.md +143 -143
  200. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  201. package/skills/v8-utilities/references/server-api-index.md +188 -188
  202. package/skills/v8-workflow/SKILL.md +252 -252
  203. package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -180
  204. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  205. package/skills/vision-engine/SKILL.md +160 -160
  206. package/skills/vision-engine/agents/openai.yaml +4 -4
  207. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  208. package/skills/workspace-conventions/SKILL.md +273 -273
  209. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +209 -209
  210. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +217 -217
  211. package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -27
@@ -1,388 +1,388 @@
1
- ---
2
- name: v8-api-config
3
- description: Microi V8 接口引擎配置指南。用于设置 ApiEngineKey、ApiAddress、StopHttp、AllowAnonymous、ResponseFile、ResponseType=HTTP、锁、日志、超时和 HTTP 暴露。
4
- ---
5
-
6
- > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
-
8
- # Microi V8 接口引擎配置
9
-
10
- 你正在配置 Microi 吾码平台的接口引擎(API 引擎)。除了 JS 代码本身,每个接口还有一系列**安全/性能配置项**,写代码时必须了解这些选项以决定是否需要调整。
11
-
12
- ## 配置项总览
13
-
14
- | 字段 | 说明 | 默认 |
15
- |------|------|------|
16
- | `ApiEngineKey` | 接口唯一标识(URL 路径) | 必填 |
17
- | `ApiAddress` | 自定义接口地址(覆盖默认 `/apiengine/{Key}`) | 空 |
18
- | `ApiRoutes`(多路由) | 同一接口的兼容地址,多个绝对路径用英文分号分隔 | 空 |
19
- | `RequestType` | `Get` / `Post` / `Both` | `Both` |
20
- | `ParamType` | `form` / `json` / `url` —— 但 V8.Param 都能统一接收 | `Both` |
21
- | `IsAnonymous` | 允许匿名调用(无 Token) | `false` |
22
- | `StopHttp` | 禁止外部 HTTP 调用(仅允许 V8.ApiEngine.Run 内部调用) | `false` |
23
- | `IsResponseFile` | 是否响应文件(开启后 Data 必须是文件结构) | `false` |
24
- | `ResponseType` | `JSON/String/File/HTML/Stream/HTTP`;`HTTP` 返回受控状态码、响应头和正文 | 自动识别 |
25
- | `LockKey` | 用作分布式锁值的请求参数字段名;为空时按接口 Key 串行 | 空 |
26
- | `Timeout` | 接口执行预算;开启锁时也作为单次 Redis 租期(秒) | 租户运行配置 |
27
- | `LockMsg` | 加锁失败时返回提示 | `操作过于频繁` |
28
- | `RateLimit` | 频率限制(如 `60/m` 每分钟60次) | 空 |
29
- | `LogParam` | 是否记录请求参数到 `sys_log` | `false` |
30
- | `LogResult` | 是否记录返回值到 `sys_log` | `false` |
31
-
32
- ### 多路由(ApiRoutes)
33
-
34
- `ApiAddress` 是唯一主路由;`ApiRoutes` 只用于让同一接口引擎继续接收多个历史地址,例如:
35
-
36
- ```text
37
- ApiAddress: /apiengine/platform-sys-menu
38
- ApiRoutes: /api/SysMenu/GetSysMenuModel;/api/SysMenu/GetSysMenuStep
39
- ```
40
-
41
- - 多个地址必须用英文分号 `;` 分隔;每项都必须是 `/` 开头的绝对路径,保存时按不区分大小写去重,最多 128 项。
42
- - `Id`、`ApiEngineKey`、`ApiAddress` 与每一项 `ApiRoutes` 都会成为同一接口的缓存别名。路由只做完整路径精确匹配,不把 Query 计入地址。
43
- - 主路由与多路由不得重复,也不得与其它启用接口的主路由/多路由冲突;保存、启动闭包和缓存初始化都必须失败关闭并指出冲突 Key,禁止后写覆盖先写。
44
- - 新客户端仍使用 `/apiengine/{ApiEngineKey}` 或主 `ApiAddress`。多路由用于 Controller 迁移、旧移动端和第三方已登记回调的兼容,不得拿它复制多份相同接口代码。
45
- - MCP 创建接口时传 `apiRoutes: ['/api/Old/A', '/api/Old/B']`;更新时省略表示保留,传空字符串/空数组表示清空。官方应用包必须同时携带 `ApiRoutes`、醒目 Managed 提示与 `ResourcePolicies.ApiEngines`。
46
- - 迁移旧 Controller 不能只迁方法名:同时核对历史 GET/POST、JSON/Form/Query、无需 `Action` 的调用和原始返回结构。按服务端 `_RequestPath` 精确识别兼容动作,历史只读地址不得被请求 `Action` 改为写操作;现代稳定 Key 的其它合法动作仍须可用。
47
- - 部门树旧地址 `/api/SysDept/GetSysDeptStep` 由 `platform-sys-dept` 的多路由交付,保留 DiyToken、组织范围及 `_Child`。缺引擎兜底仅登记该历史读地址,不能把整个现代引擎地址截获为只读分发器。验收需对比引擎存在、缺失再恢复的同一用户树结构,并验证匿名、伪造身份、停用/StopHttp 不绕过。
48
-
49
- ### 资源预算与嵌套调用(强制理解)
50
-
51
- - `LimitMemory` 是单个 Jint 引擎的**累计托管分配预算**,不是实时堆占用或服务器预留内存。默认 2048MB、节点硬上限默认 8192MB。
52
- - `V8.ApiEngine.Run` 多层嵌套是正常能力。新版默认隔离父子引擎的单层分配计数,子层不会再被每个父层重复计费;根调用树另有默认 8192MB 总预算。
53
- - 接口嵌套深度默认 32、节点硬上限默认 64;它与 `LimitRecursion` 的 JavaScript 函数递归不是同一限制。
54
- - 嵌套调用不重复占用全局/租户并发名额,同一调用树重入同 Key 也不会自锁;不同子接口 Key 仍受自己的 Key 并发门保护。
55
- - `V8.Limits` 可读取本片有效预算和当前深度。异常优先检查 `DataAppend.V8Limit.Code`,不要看到“2GB”就判断服务器真实吃满 2GB。
56
- - 后台任务使用同一执行引擎。总任务可以运行数小时,但单片仍受 `Timeout/MaxStatements/LimitMemory` 约束;超过 10 分钟必须返回 `HasMore + Checkpoint` 分片续跑,不能只把 `Timeout` 调到 1800/3600。
57
- - 接口引擎使用正向 `V8Limit`:默认 `0/false`,不设置当前 Jint Engine 的单次超时、语句、函数递归、累计分配和 Promise 固定等待预算;只有 `1/true` 才应用 `Timeout/MaxStatements/LimitMemory/LimitRecursion`。常驻内存保护、取消令牌、并发、接口嵌套深度、权限沙箱及数据库限制在两种状态下都保留。老 `V8Unlimited` 只作协议兼容;MCP/Manifest 新配置统一写 `v8Limit`。
58
-
59
- ### 流式响应(ResponseType=Stream)
60
-
61
- ```javascript
62
- for (var i = 0; i < rows.length; i++) {
63
- var pushed = await V8.Stream.WriteAsync(rows[i], 'chunk', String(i));
64
- if (pushed.Code !== 1) return pushed;
65
- }
66
- return { Code: 1, Data: { Count: rows.length } };
67
- ```
68
-
69
- - 默认协议为 SSE;客户端请求 `Accept: application/x-ndjson` 或 `streamFormat=ndjson` 可使用 NDJSON。
70
- - `V8.Stream.Write/WriteAsync` 输出的分片统一标记 `Provisional:true`。宿主保留 `open/done/error/heartbeat`,并且只有事务提交后才发送 `done + Committed:true`;收到 `error` 时客户端不得把暂态分片当成已提交数据。
71
- - 每次写入都要检查 `Code`,客户端断开或超过大小上限后立即停止循环。请求取消会传入当前 Jint 执行链,但不能替代业务幂等和事务。
72
- - 当前租户在 `sys_osclients` 配置单分片、累计响应和心跳:`ApiEngineStreamMaxChunkKB` 默认 256(4–1024)、`ApiEngineStreamMaxTotalMB` 默认 16(1–256)、`ApiEngineStreamHeartbeatSeconds` 默认 15(5–60)。
73
- - 流式传输用于在线增量反馈;大型文件走 HDFS/文件响应,可靠长任务走后台任务 + Checkpoint,广播状态走提交后 SignalR。禁止用流式响应绕过这些边界。
74
-
75
- ### 受控原始 HTTP 响应(ResponseType=HTTP)
76
-
77
- 标准协议需要非 200 状态、重定向、XML/纯文本或指定 Content-Type 时,不要新建 Controller。设置 `ResponseType=HTTP`,并返回统一契约:
78
-
79
- MCP 的 `microi_create_engine` 与 `microi_save_engine_code` 使用 `responseType: "HTTP"`。若工具枚举仍拒绝该值,说明当前 MCP 尚未加载支持版本;使用同源新版 MCP 新进程,不把响应降级为 JSON,也不通过 SQL 改写接口配置。更新 MCP 不代表目标后端已经支持此协议,仍需验证真实 HTTP 状态、响应头和正文。
80
-
81
- ```javascript
82
- return {
83
- Code: 1,
84
- DataAppend: { HttpResponse: {
85
- StatusCode: 302,
86
- ContentType: 'text/plain; charset=utf-8',
87
- Body: '',
88
- Headers: {
89
- Location: 'https://identity.example.com/login',
90
- 'Cache-Control': 'no-store'
91
- }
92
- } }
93
- };
94
- ```
95
-
96
- - 普通接口引擎可设置 `Cache-Control`、`Pragma`、`Location`、`WWW-Authenticate`、下载/语言/CSP 等安全白名单响应头;禁止 Host、Content-Length、Transfer-Encoding、Connection 等逐跳或宿主管理头。
97
- - `Location` 只允许站内绝对路径、HTTPS 地址或本机开发地址,禁止 CRLF、协议相对地址、非本机 HTTP 和带用户信息 URL。
98
- - `Set-Cookie` 等高风险头只允许由受限 `V8.Method` 可信原子生成并签名,租户 V8 无法自行伪造签名。SSO 使用 `V8.Method.RunSsoProtocol`;不要把 Secret、Cookie 值或签名密钥暴露给 V8。
99
- - `204/304` 不得带正文;状态码限制为 100–599,正文和响应头有大小/数量限制。HTTP 契约校验失败时宿主返回标准错误,不写出半截协议响应。
100
-
101
- ### 通用实时事件(SignalR)
102
-
103
- 订单、协作、设备、审批或多人房间需要实时刷新时,业务写命令仍由接口引擎执行并提交事务;成功结果通过 `DataAppend.RealtimeEvent` 声明提交后事件。新业务统一使用通用 v2 Hub `/api-engine-realtime`,不要再新建业务专用 Hub 或把权威状态放进 C# 进程内字典。
104
-
105
- ```javascript
106
- return {
107
- Code: 1,
108
- Data: snapshot,
109
- DataAppend: { RealtimeEvent: {
110
- EventId: requestId,
111
- ChannelKey: 'order_updates',
112
- SubjectId: order.Id,
113
- Version: order.VersionNo,
114
- EventType: 'StatusChanged',
115
- Data: { Status: order.Status }
116
- } }
117
- };
118
- ```
119
-
120
- - Hub 方法固定为 `SubscribeChannel({ ChannelKey, SubjectId })` 与 `UnsubscribeChannel(...)`,客户端事件固定为 `RealtimeEvent`。订阅成功会返回 `ProtocolVersion/ChannelKey/SubjectId/Version/Latest/RenewAfterMilliseconds/LeaseExpiresAt`。
121
- - 连接只接受当前有效的普通登录 Token。现有 AccessKey 权限模型没有 `realtime:subscribe` scope,平台会直接拒绝;在平台正式增加并校验该 scope 前,不得用 AccessKey 建立实时订阅。
122
- - 对应订阅授权接口固定为 `realtime_{channel_key}_authorize`。它必须用 `V8.CurrentUser` 校验资源权限,并精确回显 `Authorized/ChannelKey/SubjectId/Version`;不能信任客户端传入的 UserId、OsClient 或 ApiEngineKey。
123
- - 订阅使用 30 秒时隙租约。客户端必须按服务端返回的 `RenewAfterMilliseconds` 再次调用同一个 `SubscribeChannel` 续租;每次续租都会重新验证登录 Token、经过共享 Redis 限流,并重新执行授权接口引擎。不要把一次订阅误当成连接全生命周期永久授权。
124
- - 当前共享 Redis 限流按 `OsClient + UserId` 聚合为 10 秒最多 96 次订阅授权,跨标签页、API 节点和滚动发布共同生效;Redis 不可用时实时订阅失败关闭,业务必须继续走 HTTP Snapshot。
125
- - `EventId` 在业务重试时保持稳定;平台先用 Redis 短 Claim 协调跨节点发布,只有真实广播成功后才写 24 小时完成标记,避免“先去重、后崩溃”永久漏发。客户端仍必须按 `EventId` 去重,因为故障恢复可能产生重复通知。
126
- - `Version` 按同一 `ChannelKey + SubjectId` 单调递增。低版本事件作为过期事件拒绝广播;同版本但内容指纹不同视为版本冲突并拒绝;重放相同事件不推进 latest。
127
- - 宿主只读取成功 DosResult 中固定大小写的 `DataAppend.RealtimeEvent`,并只广播 `EventId/ChannelKey/SubjectId/Version/EventType/Data/OccurredAt`。`Data` 最大 32KB,只能放该群组所有订阅者都可见的安全投影;个性化私有数据通过按当前用户裁剪的 Snapshot 获取。
128
- - 客户端按 `EventId` 去重、按 `Version` 检测乱序和缺口;连接失败、续租失败、重连或发现缺口时立即重新拉 HTTP Snapshot,并保留有界轮询兜底。共享存储/状态机才是事实源。
129
- - 旧 `/game-realtime` 只作兼容。新业务默认使用通用协议,完整契约见官方 `v8-server.md`。
130
-
131
- ## 1. 匿名调用(IsAnonymous)
132
-
133
- 公开接口(登录、注册、忘记密码、验证码、扫码登录、第三方回调)必须开启:
134
-
135
- ```javascript
136
- // 例:发送验证码(匿名)
137
- if (!V8.Param.phone) return { Code: 0, Msg: '手机号不能为空' };
138
- if (!/^1[3-9]\d{9}$/.test(V8.Param.phone)) return { Code: 0, Msg: '手机号格式错误' };
139
-
140
- // 防刷:1分钟同一手机号最多1次
141
- var key = 'Microi:' + V8.OsClient + ':SmsCode:' + V8.Param.phone;
142
- if (V8.Cache.Exists(key)) return { Code: 0, Msg: '请稍后再试' };
143
-
144
- var code = Math.floor(100000 + Math.random() * 900000).toString();
145
- V8.Cache.Set(key, code, 60);
146
- // ... 调短信网关 ...
147
- return { Code: 1, Msg: '验证码已发送' };
148
- ```
149
-
150
- ### 1.1 会员端 Token 优先级
151
-
152
- 移动端/会员端自建 Token 与 Microi 后台 JWT 并存时,会员业务接口应明确 Token 优先级。MCP、后台自动化测试、PC 管理端代理调用常会在 `V8.Header.Token` 中带平台 JWT,如果接口要校验会员登录态,推荐优先读取显式会员参数或专用 Header,再回退平台 Header:
153
-
154
- ```javascript
155
- function getMemberToken() {
156
- var p = V8.Param || {};
157
- var h = V8.Header || {};
158
- var token = p.Token || p.token || h.MallMemberToken || h.mallmembertoken || h.Token || h.token || h.Authorization || h.authorization || '';
159
- token = String(token || '').trim();
160
- if (token.indexOf('Bearer ') === 0) token = token.substring(7).trim();
161
- return token;
162
- }
163
- ```
164
-
165
- 不要让后台 JWT 覆盖前端显式传入的会员 Token,否则 MCP/Playwright 用会员账号做自动化测试时会误判为未登录。
166
-
167
- ## 2. 禁止外部调用(StopHttp)
168
-
169
- 仅供其他接口引擎/V8 事件内部调用,不允许直接 HTTP 请求触发:
170
-
171
- ```javascript
172
- // 例:核心扣款接口(StopHttp=true)
173
- // 只能从 order_pay、refund 等接口通过 V8.ApiEngine.Run 调用
174
- V8.Db.FromSql('UPDATE Account SET Balance = Balance - @p0 WHERE Id = @p1')
175
- .AddInParameter("@p0", V8.Param.amount)
176
- .AddInParameter("@p1", V8.Param.accountId)
177
- .ExecuteNonQuery();
178
- return { Code: 1 };
179
- ```
180
-
181
- 外部调用直接 `/apiengine/account_deduct` 会被拒绝。
182
-
183
- ## 3. 分布式锁(LockKey)
184
-
185
- 集群部署时可用接口引擎 `LockKey` 减少同一任务的并发执行(如:每月对账、自动补单):
186
-
187
- ```javascript
188
- // 配置:LockKey = Month,Timeout = 600
189
- // 调用方传入 Month;平台使用共享锁协调多节点
190
- var month = String(V8.Param.Month || '');
191
- if (!/^\d{4}-\d{2}$/.test(month)) return { Code: 0, Msg: 'Month 格式不正确' };
192
- V8.Db.FromSql('INSERT INTO MonthSettle SELECT ... WHERE Month = @p0')
193
- .AddInParameter("@p0", month)
194
- .ExecuteNonQuery();
195
- return { Code: 1 };
196
- ```
197
-
198
- `LockKey` 填写请求参数字段名;上例调用方应传入 `Month`,平台使用其值区分不同月份。未填写时按 `ApiEngineKey` 串行。平台自动把锁放入当前 `OsClient` 命名空间,不需要也不应让客户端自行拼接其它租户前缀。缺少已配置的参数字段时会退回使用字段名本身,虽然仍能互斥,但会让所有请求共享一把锁,因此保存与 HTTP 复测必须覆盖实际参数。
199
-
200
- ### 普通调用与可信后台任务的租约差异
201
-
202
- - 普通 HTTP 或普通 V8 调用保持固定租期:`Timeout` 是锁成功获取后的 Redis TTL,不会自动延长。它必须大于正常执行时间,但不能靠设置超大数值代替可靠后台任务。
203
- - 通过平台 `RunBackground`/后台任务服务进入的可信持久执行,会在回调期间按持有者令牌自动续租。当前默认最长续租边界为 12 小时;若接口显式配置的单次租期本身更长,平台不会把它缩短到 12 小时。
204
- - 可信身份由服务端建立,并同时校验任务 Id、后台任务信封和正数 fencing token。客户端或普通 V8 自行传入 `_BackgroundTaskId`、`_BackgroundTask`、`_BackgroundTaskFencingToken`、`_TrustedServerInvocation` 或 `_CurrentUser`,不能开启自动续租。
205
- - 续租和释放都以唯一持有者令牌做 Redis 原子比较;锁每次成功获取还会产生单调递增 fencing token。持有者不匹配、锁已过期、Redis 所有权/续租确认失败或达到最长租约时,执行必须失败关闭,旧持有者不得继续提交副作用。
206
-
207
- 分布式锁不是“业务只执行一次”的最终保证。扣款、库存、积分、流水、对账等副作用还必须使用稳定幂等键、数据库唯一约束/条件更新、状态机或 outbox/inbox;锁 Key 至少包含 `OsClient + 业务唯一标识`,超时必须大于正常执行时间。所需唯一索引必须写入 Manifest `tables[].indexes` 并通过 `microi_create_table_index` 创建、`microi_get_table_indexes` 回读,接口引擎本身禁止执行索引 DDL。
208
-
209
- 预计超过 10 分钟的任务即使具备自动续租,也必须使用 `HasMore + Checkpoint` 分片并持久化真实进度。每个业务条件写入使用 `_BackgroundTaskFencingToken` 拒绝租约过期的旧执行者;错误信息包含“分布式锁租约已丢失”时不得捕获后返回成功,任务应保留最后进度与原始原因,交由后台任务的幂等恢复或人工诊断处理。
210
-
211
- ## 4. 自定义路径(ApiAddress)
212
-
213
- 让接口暴露为 `/wechat/notify` 而非 `/apiengine/wechat_notify`,对接第三方时常用:
214
-
215
- ```
216
- ApiAddress: /wechat/notify
217
- ```
218
-
219
- 标准协议还可声明逐段模板,例如:
220
-
221
- ```text
222
- ApiAddress: /sso/{OsClient}/.well-known/openid-configuration
223
- ApiAddress: /saml/{OsClient}/sp/{ConnectionKey}/metadata
224
- ```
225
-
226
- 模板只匹配完整路径段,不支持贪婪通配符。命中的路由值会写入 `V8.Param._RouteValues`,并以权威路径值覆盖同名 Query/Form/JSON 参数,防止调用者伪造另一租户或连接。包含 `{OsClient}` 时租户由该路径段解析;同一路径匹配多个模板视为配置冲突并拒绝执行。
227
-
228
- ## 5. 响应文件(IsResponseFile)
229
-
230
- 开启后接口可直接输出二进制流:
231
-
232
- 后端会统一处理响应头和文件头校验:图片/PDF 浏览器直接打开,其它文件下载;V8 代码只返回文件三字段,不要在接口里手写复杂的魔数判断。`ContentType` 必须匹配真实字节,金蝶 PLM `KD_C_PLM` 等业务封装流不能伪装成 `application/pdf`。
233
-
234
- 响应文件动态路由必须同时接受 `GET` 和 `HEAD`。OnlyOffice 等服务端预览器可能先用 `HEAD` 探测文件类型、长度和可达性;如果浏览器直接下载正常但 `HEAD` 返回 `405`,在线预览仍可能一直停在“加载文档”。
235
-
236
- ```javascript
237
- // 必须返回特定结构
238
- return {
239
- Code: 1,
240
- Data: {
241
- FileName: 'report.xlsx',
242
- ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
243
- FileByteBase64: System.Convert.ToBase64String(byteArr)
244
- }
245
- };
246
- ```
247
-
248
- 详见 `v8-file-upload/SKILL.md`。
249
-
250
- ## 6. 频率限制(RateLimit)
251
-
252
- 防爬虫、防刷:
253
-
254
- | 配置 | 含义 |
255
- |------|------|
256
- | `60/m` | 每分钟 60 次 |
257
- | `1000/h` | 每小时 1000 次 |
258
- | `100/s` | 每秒 100 次 |
259
-
260
- 按客户端 IP + 接口 维度限流。
261
-
262
- ## 7. 日志记录(LogParam / LogResult)
263
-
264
- 支付、撤销等敏感接口建议开启,自动记到 `sys_log` 用于审计回溯:
265
-
266
- ```
267
- LogParam = true # 记录每次入参
268
- LogResult = true # 记录每次返回
269
- ```
270
-
271
- > ❌ 接口返回结果含敏感数据(密码、token、密钥)时不要打开 `LogResult`
272
-
273
- ## 8. 保存后 HTTP 复测
274
-
275
- 通过 MCP 维护接口引擎时,先用 `microi_list_engines` 发现现有接口,再用
276
- `microi_get_engine_code` 读取源码;修改后使用 `microi_save_engine_code`
277
- 保存并回读。只有确认目标不存在时才调用创建工具,避免重复
278
- `ApiEngineKey`。`microi_run_engine` 适合做引擎上下文内的最小调试,但不能
279
- 代替下方真实 HTTP 复测。
280
-
281
- `microi_run_engine` 只能证明引擎代码在 MCP/内部执行上下文可运行,不能证明移动端或外部 HTTP 能调用。新建或更新接口后必须再走一次真实 HTTP 路径:
282
-
283
- ```text
284
- POST /apiengine/{ApiEngineKey}
285
- Headers: Content-Type=application/json, osclient={OsClient}, apiengine=1
286
- Body: {"Action":"Bootstrap","OsClient":"{OsClient}"}
287
-
288
- # 仅用于不能立即升级的旧客户端;新增或可修改代码禁止使用
289
- POST /api/ApiEngine/Run
290
- Headers: Content-Type=application/json, OsClient={OsClient}
291
- Body: {"ApiEngineKey":"your_key","Action":"Bootstrap"}
292
- ```
293
-
294
- 固定业务接口必须调用 `/apiengine/{ApiEngineKey}` 或该引擎配置的唯一
295
- `ApiAddress`。禁止新增 `/api/ApiEngine/Run` 依赖,否则反向代理、限流、审计和
296
- 系统日志/监控只能看到同一个通用入口,难以按真实接口引擎准确归因。SDK 只可在
297
- 显式命名的 `RunLegacy` 兼容方法中保留旧地址,普通 `Run` 必须生成动态地址。
298
-
299
- 复测重点:
300
-
301
- - `IsEnable=1`、`StopHttp=0`、公开接口 `AllowAnonymous=1`。
302
- - JSON Body 会恢复到 `V8.Param`;同名参数已由 Query/Form 绑定时保持既有值,避免改变旧调用优先级。直接动态路由与兼容入口都要覆盖 JSON Body 测试,不能只用 Query 参数证明可用。
303
- - HTTP 请求中的 `_CurrentUser`、`_InvokeType:'Server'`、`_TrustedServerInvocation` 都不能建立可信服务端身份;当前用户和调用类型必须由认证中间件与接口层重新写入。
304
- - `ApiAddress` 不能为空字符串;空字符串可能导致 404。
305
- - 响应不能是空 body、字符串 `null`、非 JSON;业务接口必须返回标准 DosResult。
306
- - 普通 `POST/PUT/PATCH/DELETE` 必须使用稳定路径 `/apiengine/{ApiEngineKey}`,租户放在唯一的 `osclient` Header,并可在 JSON/Form Body 中冗余传入;禁止无脑给路径追加 `--OsClient--...--`。普通 GET 优先 Header 或 `?OsClient=`。只有微信/支付等第三方回调(包括 POST)、浏览器直接下载等调用方确实无法设置 Header 或 Query 的场景,才使用 `--OsClient--{OsClient}--` 特殊路径;Query 参数名固定为 `OsClient`,禁止 `o` 等缩写。
307
- - 需要 C# 验签/AES 解密、协议编解码或隐藏 SaaS 密钥的回调,使用“`Managed` HTTP 接口引擎 + 精确 Key 可调用的最小 `V8.Method` 可信原子 + `CreateIfMissing` 租户 Hook”。公开地址仍归接口引擎,禁止为此恢复 Controller;传给 Hook 的事件必须脱敏,并包含稳定 `EventId` 供幂等。
308
- - 更新接口代码时保留 HTTP 元数据,避免只覆盖 JS 代码却把匿名、启用、自定义地址等配置冲掉。
309
-
310
- ### 路由冷缓存与客户端直达头(强制)
311
-
312
- - `/apiengine/{ApiEngineKey}` 客户端请求必须携带 `apiengine: 1`;标准前端 SDK 应从稳定路径自动识别并补齐,不能要求每个业务页面手写 Header。自定义 `ApiAddress` 无法从路径识别时,调用方显式设置 `apiEngine: true`。
313
- - 动态路由缓存未命中后允许从 `sys_apiengine` 权威回源并重建 `ApiEngineKey`、`ApiAddress` 两个别名。回源对象如果来自 `dynamic`,先转换为 `JObject`/`object`,并把字段显式赋给 `string`、`bool` 等强类型局部变量,再调用普通方法或扩展方法;禁止让 `dynamic` 调用链延续到 `DosIsNullOrWhiteSpace`、LINQ 或 JToken 扩展。
314
- - 自动化必须覆盖“缓存预热命中”和“缓存为空首次请求”两条路径;首次请求不得 404,且回源后两个缓存别名均可再次命中。滚动发布、节点重启或缓存清理后要重复执行无 Header 与带 `apiengine: 1` 的真实 HTTP smoke test。
315
-
316
- ### 复盘:接口引擎冷缓存回源异常被吞成空 404
317
-
318
- - 触发场景:接口配置、启用和匿名设置都正确,接口昨天可用;节点重启或缓存缺失后,小程序首次请求 `/apiengine/{key}` 返回空 body 404。
319
- - 根因:动态路由从数据库回源成功后,局部变量仍沿着 `dynamic` 调用链传播;运行时对实际 `string` 绑定扩展方法失败,外层异常处理返回原路由值,最终由 ASP.NET Core 表现为无正文 404。
320
- - 通用规则:数据库/缓存的动态对象在进入路由、鉴权、缓存键和 LINQ 逻辑前必须强类型落地;稳定接口引擎路径由 SDK 自动携带 `apiengine: 1` 作为直达兜底。
321
- - 自动化检查:单元测试直接传入 `JObject` 验证回源别名强类型归一化;前端传输测试断言 `/apiengine/*` 自动携带 `apiengine: 1`,普通 `/api/*` 不误带;本地启动后清空专用测试别名并验证首次 HTTP 请求成功及缓存重建。
322
-
323
- ### 复盘:单段自定义 ApiAddress 被误判为 ApiEngineKey
324
-
325
- - 触发场景:接口行已启用并允许匿名,显式配置了 `/apiengine/external-name`,但真实 `ApiEngineKey` 是另一个值;Query `?OsClient=` 与 `--OsClient--...--` 两种调用都返回 `NoExistData[ApiAddress]`。
326
- - 根因:动态路由把所有单段 `/apiengine/{value}` 先解释成 `ApiEngineKey=value`,跳过了显式 `ApiAddress`;模型未加载时匿名开关尚未进入判断,因此调整 `AllowAnonymous` 无法修复。
327
- - 通用规则:完整 `ApiAddress` 与 `ApiRoutes` 是路由第一事实源,只有权威主库确认该地址从未配置时,才允许把尾段作为兼容 Key 回退。停用但未软删除的地址仍占用路由,不能旁路到另一条同名 Key;Controller 只信任动态路由写入的真实 Key,未解析时保留完整地址,禁止再次猜 Key。
328
- - 自动化检查:至少覆盖“显式地址尾段与 Key 不同”和“ApiAddress 为空的传统 Key 路由”,并分别验证 `?OsClient=`、`--OsClient--...--`、冷缓存首次请求、停用地址阻断及匿名关闭返回鉴权错误而非不存在。
329
-
330
- ## 请求内异步与可靠后台任务
331
-
332
- 接口默认同步返回。对本次请求必须完成的异步 I/O,调用真实的 `*Async` 方法并 `await`。常用入口包括 `V8.Http.*Async`、`V8.FormEngine.GetTableDataAsync` 和 `V8.ApiEngine.RunAsync`:
333
-
334
- ```javascript
335
- var resp = await V8.Http.GetResponseAsync({
336
- Url: 'https://example.com/health',
337
- Timeout: 5
338
- });
339
- if (resp.StatusCode < 200 || resp.StatusCode >= 300) {
340
- return { Code: 0, Msg: '上游调用失败' };
341
- }
342
-
343
- var users = await V8.FormEngine.GetTableDataAsync('SysUser', {
344
- _Where: [['Status', '=', 1]],
345
- _SelectFields: ['Id', 'Name'],
346
- _PageSize: 20
347
- });
348
-
349
- var summary = await V8.ApiEngine.RunAsync('build-user-summary', {
350
- Users: users.Data
351
- });
352
- return { Code: 1, Data: { Upstream: resp.Content, Summary: summary.Data } };
353
- ```
354
-
355
- 禁止用 `setTimeout` 或 `System.Threading.Tasks.Task.Run` 实现“接口先返回、后台继续执行”:`V8Engine.Run` 返回后会释放 Jint Engine、租户上下文、事务和并发租约,回调不可靠,也没有持久化、重试、幂等或重启恢复保证。
356
-
357
- 需要先响应再处理时,使用接口引擎后台任务按钮(`RunBackground + ApiEngineKey`)、Job、MQ 或 outbox;消费者按全局 `EventId` 幂等处理并持久化进度。AI 发现预计超过 2 分钟、500 条、1000 个扇出子操作、100 次外部调用,或安装/初始化/迁移/备份/全量生成等任务时,必须主动切换为后台任务;预计超过 10 分钟时还必须设计 checkpoint 分片。见 `job-engine`、`v8-menu-buttons`、`v8-mq-mqtt` 和 `microi-system-delivery`。
358
-
359
- ## 接口安全检查清单
360
-
361
- - [ ] 公开接口是否仅开启 `IsAnonymous`,敏感接口是否关闭?
362
- - [ ] 内部接口是否开启 `StopHttp`?
363
- - [ ] 写操作(扣款、对账、补单)是否配置 `LockKey`?
364
- - [ ] `LockKey` 是否指向真实存在的请求参数,`Timeout` 是否是合理的单次租期?
365
- - [ ] 锁之外是否还有幂等键、唯一约束/条件更新或状态机?
366
- - [ ] 长任务是否只由平台可信后台上下文自动续租,并在租约丢失时失败关闭?
367
- - [ ] 频率敏感接口是否配置 `RateLimit`?
368
- - [ ] 审计需求接口是否开启 `LogParam`?
369
- - [ ] 文件响应接口是否开启 `IsResponseFile`?
370
- - [ ] 接口代码内是否仍校验 `V8.CurrentUser`(`IsAnonymous=true` 时尤其重要)?
371
- - [ ] 是否没有使用 `setTimeout` / `Task.Run` 承担请求外后台任务?
372
- - [ ] 大任务是否按阈值主动使用后台任务,超过 10 分钟是否有 `HasMore + Checkpoint`?
373
- - [ ] 是否区分累计分配、调用树预算、JS递归与接口嵌套,而不是盲目抬高全部限制?
374
- - [ ] 是否确认 `V8Limit=false` 表示接口不限 Jint 单次预算、`true` 才启用限制,并避免继续写入旧 `V8Unlimited` 字段?
375
- - [ ] 保存后是否通过稳定路径 `/apiengine/{key}` + `osclient` Header 做过 HTTP 复测?特殊 GET/HEAD 路径是否仅用于无法设置 Header/Form/Query 的场景?
376
-
377
- ## 常见错误
378
-
379
- ❌ 把支付回调接口设为非匿名 → 第三方无 Token → 回调失败
380
- ❌ 内部接口忘开 `StopHttp` → 被外部直接调用绕过校验
381
- ❌ 对账接口未配置 `LockKey` → 集群多实例并发执行 → 数据双倍
382
- ❌ 文件下载接口未开 `IsResponseFile` → 返回 JSON 而非文件流
383
-
384
- ## 复盘:后台任务调度路由与执行目标的参数边界
385
-
386
- - HTTP 调用持久后台任务时使用固定 `/apiengine/platform-background-task?OsClient=` 与 `Action: 'RunApiEngine'`;目标接口放在独立的 `TargetApiEngineKey`,业务参数放在 `Param`,幂等和并发策略放在 `Options`。不可用正文 `ApiEngineKey` 改写路由调度器。
387
- - 回归至少验证嵌套业务参数与 Options 能完整抵达可信后台原子、同一幂等键返回原任务、匿名及未授权身份拒绝执行。页面提交成功后还需回读后台任务终态,不能将“已排队”视为业务完成。
388
- - 原子能力若按持久任务记录中的接口 Key 校验可信执行身份,队列目标必须是该原子允许的工作器本身;嵌套调用不会改写任务记录的接口身份。禁止把兼容提交器排入队列后再调用工作器,并通过扩大 Key 白名单或省略栅栏令牌校验掩盖目标不一致。回归应对照队列目标与原子的固定 Key,并走一次真实写流程到终态。
1
+ ---
2
+ name: v8-api-config
3
+ description: Microi V8 接口引擎配置指南。用于设置 ApiEngineKey、ApiAddress、StopHttp、AllowAnonymous、ResponseFile、ResponseType=HTTP、锁、日志、超时和 HTTP 暴露。
4
+ ---
5
+
6
+ > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
+
8
+ # Microi V8 接口引擎配置
9
+
10
+ 你正在配置 Microi 吾码平台的接口引擎(API 引擎)。除了 JS 代码本身,每个接口还有一系列**安全/性能配置项**,写代码时必须了解这些选项以决定是否需要调整。
11
+
12
+ ## 配置项总览
13
+
14
+ | 字段 | 说明 | 默认 |
15
+ |------|------|------|
16
+ | `ApiEngineKey` | 接口唯一标识(URL 路径) | 必填 |
17
+ | `ApiAddress` | 自定义接口地址(覆盖默认 `/apiengine/{Key}`) | 空 |
18
+ | `ApiRoutes`(多路由) | 同一接口的兼容地址,多个绝对路径用英文分号分隔 | 空 |
19
+ | `RequestType` | `Get` / `Post` / `Both` | `Both` |
20
+ | `ParamType` | `form` / `json` / `url` —— 但 V8.Param 都能统一接收 | `Both` |
21
+ | `IsAnonymous` | 允许匿名调用(无 Token) | `false` |
22
+ | `StopHttp` | 禁止外部 HTTP 调用(仅允许 V8.ApiEngine.Run 内部调用) | `false` |
23
+ | `IsResponseFile` | 是否响应文件(开启后 Data 必须是文件结构) | `false` |
24
+ | `ResponseType` | `JSON/String/File/HTML/Stream/HTTP`;`HTTP` 返回受控状态码、响应头和正文 | 自动识别 |
25
+ | `LockKey` | 用作分布式锁值的请求参数字段名;为空时按接口 Key 串行 | 空 |
26
+ | `Timeout` | 接口执行预算;开启锁时也作为单次 Redis 租期(秒) | 租户运行配置 |
27
+ | `LockMsg` | 加锁失败时返回提示 | `操作过于频繁` |
28
+ | `RateLimit` | 频率限制(如 `60/m` 每分钟60次) | 空 |
29
+ | `LogParam` | 是否记录请求参数到 `sys_log` | `false` |
30
+ | `LogResult` | 是否记录返回值到 `sys_log` | `false` |
31
+
32
+ ### 多路由(ApiRoutes)
33
+
34
+ `ApiAddress` 是唯一主路由;`ApiRoutes` 只用于让同一接口引擎继续接收多个历史地址,例如:
35
+
36
+ ```text
37
+ ApiAddress: /apiengine/platform-sys-menu
38
+ ApiRoutes: /api/SysMenu/GetSysMenuModel;/api/SysMenu/GetSysMenuStep
39
+ ```
40
+
41
+ - 多个地址必须用英文分号 `;` 分隔;每项都必须是 `/` 开头的绝对路径,保存时按不区分大小写去重,最多 128 项。
42
+ - `Id`、`ApiEngineKey`、`ApiAddress` 与每一项 `ApiRoutes` 都会成为同一接口的缓存别名。路由只做完整路径精确匹配,不把 Query 计入地址。
43
+ - 主路由与多路由不得重复,也不得与其它启用接口的主路由/多路由冲突;保存、启动闭包和缓存初始化都必须失败关闭并指出冲突 Key,禁止后写覆盖先写。
44
+ - 新客户端仍使用 `/apiengine/{ApiEngineKey}` 或主 `ApiAddress`。多路由用于 Controller 迁移、旧移动端和第三方已登记回调的兼容,不得拿它复制多份相同接口代码。
45
+ - MCP 创建接口时传 `apiRoutes: ['/api/Old/A', '/api/Old/B']`;更新时省略表示保留,传空字符串/空数组表示清空。官方应用包必须同时携带 `ApiRoutes`、醒目 Managed 提示与 `ResourcePolicies.ApiEngines`。
46
+ - 迁移旧 Controller 不能只迁方法名:同时核对历史 GET/POST、JSON/Form/Query、无需 `Action` 的调用和原始返回结构。按服务端 `_RequestPath` 精确识别兼容动作,历史只读地址不得被请求 `Action` 改为写操作;现代稳定 Key 的其它合法动作仍须可用。
47
+ - 部门树旧地址 `/api/SysDept/GetSysDeptStep` 由 `platform-sys-dept` 的多路由交付,保留 DiyToken、组织范围及 `_Child`。缺引擎兜底仅登记该历史读地址,不能把整个现代引擎地址截获为只读分发器。验收需对比引擎存在、缺失再恢复的同一用户树结构,并验证匿名、伪造身份、停用/StopHttp 不绕过。
48
+
49
+ ### 资源预算与嵌套调用(强制理解)
50
+
51
+ - `LimitMemory` 是单个 Jint 引擎的**累计托管分配预算**,不是实时堆占用或服务器预留内存。默认 2048MB、节点硬上限默认 8192MB。
52
+ - `V8.ApiEngine.Run` 多层嵌套是正常能力。新版默认隔离父子引擎的单层分配计数,子层不会再被每个父层重复计费;根调用树另有默认 8192MB 总预算。
53
+ - 接口嵌套深度默认 32、节点硬上限默认 64;它与 `LimitRecursion` 的 JavaScript 函数递归不是同一限制。
54
+ - 嵌套调用不重复占用全局/租户并发名额,同一调用树重入同 Key 也不会自锁;不同子接口 Key 仍受自己的 Key 并发门保护。
55
+ - `V8.Limits` 可读取本片有效预算和当前深度。异常优先检查 `DataAppend.V8Limit.Code`,不要看到“2GB”就判断服务器真实吃满 2GB。
56
+ - 后台任务使用同一执行引擎。总任务可以运行数小时,但单片仍受 `Timeout/MaxStatements/LimitMemory` 约束;超过 10 分钟必须返回 `HasMore + Checkpoint` 分片续跑,不能只把 `Timeout` 调到 1800/3600。
57
+ - 接口引擎使用正向 `V8Limit`:默认 `0/false`,不设置当前 Jint Engine 的单次超时、语句、函数递归、累计分配和 Promise 固定等待预算;只有 `1/true` 才应用 `Timeout/MaxStatements/LimitMemory/LimitRecursion`。常驻内存保护、取消令牌、并发、接口嵌套深度、权限沙箱及数据库限制在两种状态下都保留。老 `V8Unlimited` 只作协议兼容;MCP/Manifest 新配置统一写 `v8Limit`。
58
+
59
+ ### 流式响应(ResponseType=Stream)
60
+
61
+ ```javascript
62
+ for (var i = 0; i < rows.length; i++) {
63
+ var pushed = await V8.Stream.WriteAsync(rows[i], 'chunk', String(i));
64
+ if (pushed.Code !== 1) return pushed;
65
+ }
66
+ return { Code: 1, Data: { Count: rows.length } };
67
+ ```
68
+
69
+ - 默认协议为 SSE;客户端请求 `Accept: application/x-ndjson` 或 `streamFormat=ndjson` 可使用 NDJSON。
70
+ - `V8.Stream.Write/WriteAsync` 输出的分片统一标记 `Provisional:true`。宿主保留 `open/done/error/heartbeat`,并且只有事务提交后才发送 `done + Committed:true`;收到 `error` 时客户端不得把暂态分片当成已提交数据。
71
+ - 每次写入都要检查 `Code`,客户端断开或超过大小上限后立即停止循环。请求取消会传入当前 Jint 执行链,但不能替代业务幂等和事务。
72
+ - 当前租户在 `sys_osclients` 配置单分片、累计响应和心跳:`ApiEngineStreamMaxChunkKB` 默认 256(4–1024)、`ApiEngineStreamMaxTotalMB` 默认 16(1–256)、`ApiEngineStreamHeartbeatSeconds` 默认 15(5–60)。
73
+ - 流式传输用于在线增量反馈;大型文件走 HDFS/文件响应,可靠长任务走后台任务 + Checkpoint,广播状态走提交后 SignalR。禁止用流式响应绕过这些边界。
74
+
75
+ ### 受控原始 HTTP 响应(ResponseType=HTTP)
76
+
77
+ 标准协议需要非 200 状态、重定向、XML/纯文本或指定 Content-Type 时,不要新建 Controller。设置 `ResponseType=HTTP`,并返回统一契约:
78
+
79
+ MCP 的 `microi_create_engine` 与 `microi_save_engine_code` 使用 `responseType: "HTTP"`。若工具枚举仍拒绝该值,说明当前 MCP 尚未加载支持版本;使用同源新版 MCP 新进程,不把响应降级为 JSON,也不通过 SQL 改写接口配置。更新 MCP 不代表目标后端已经支持此协议,仍需验证真实 HTTP 状态、响应头和正文。
80
+
81
+ ```javascript
82
+ return {
83
+ Code: 1,
84
+ DataAppend: { HttpResponse: {
85
+ StatusCode: 302,
86
+ ContentType: 'text/plain; charset=utf-8',
87
+ Body: '',
88
+ Headers: {
89
+ Location: 'https://identity.example.com/login',
90
+ 'Cache-Control': 'no-store'
91
+ }
92
+ } }
93
+ };
94
+ ```
95
+
96
+ - 普通接口引擎可设置 `Cache-Control`、`Pragma`、`Location`、`WWW-Authenticate`、下载/语言/CSP 等安全白名单响应头;禁止 Host、Content-Length、Transfer-Encoding、Connection 等逐跳或宿主管理头。
97
+ - `Location` 只允许站内绝对路径、HTTPS 地址或本机开发地址,禁止 CRLF、协议相对地址、非本机 HTTP 和带用户信息 URL。
98
+ - `Set-Cookie` 等高风险头只允许由受限 `V8.Method` 可信原子生成并签名,租户 V8 无法自行伪造签名。SSO 使用 `V8.Method.RunSsoProtocol`;不要把 Secret、Cookie 值或签名密钥暴露给 V8。
99
+ - `204/304` 不得带正文;状态码限制为 100–599,正文和响应头有大小/数量限制。HTTP 契约校验失败时宿主返回标准错误,不写出半截协议响应。
100
+
101
+ ### 通用实时事件(SignalR)
102
+
103
+ 订单、协作、设备、审批或多人房间需要实时刷新时,业务写命令仍由接口引擎执行并提交事务;成功结果通过 `DataAppend.RealtimeEvent` 声明提交后事件。新业务统一使用通用 v2 Hub `/api-engine-realtime`,不要再新建业务专用 Hub 或把权威状态放进 C# 进程内字典。
104
+
105
+ ```javascript
106
+ return {
107
+ Code: 1,
108
+ Data: snapshot,
109
+ DataAppend: { RealtimeEvent: {
110
+ EventId: requestId,
111
+ ChannelKey: 'order_updates',
112
+ SubjectId: order.Id,
113
+ Version: order.VersionNo,
114
+ EventType: 'StatusChanged',
115
+ Data: { Status: order.Status }
116
+ } }
117
+ };
118
+ ```
119
+
120
+ - Hub 方法固定为 `SubscribeChannel({ ChannelKey, SubjectId })` 与 `UnsubscribeChannel(...)`,客户端事件固定为 `RealtimeEvent`。订阅成功会返回 `ProtocolVersion/ChannelKey/SubjectId/Version/Latest/RenewAfterMilliseconds/LeaseExpiresAt`。
121
+ - 连接只接受当前有效的普通登录 Token。现有 AccessKey 权限模型没有 `realtime:subscribe` scope,平台会直接拒绝;在平台正式增加并校验该 scope 前,不得用 AccessKey 建立实时订阅。
122
+ - 对应订阅授权接口固定为 `realtime_{channel_key}_authorize`。它必须用 `V8.CurrentUser` 校验资源权限,并精确回显 `Authorized/ChannelKey/SubjectId/Version`;不能信任客户端传入的 UserId、OsClient 或 ApiEngineKey。
123
+ - 订阅使用 30 秒时隙租约。客户端必须按服务端返回的 `RenewAfterMilliseconds` 再次调用同一个 `SubscribeChannel` 续租;每次续租都会重新验证登录 Token、经过共享 Redis 限流,并重新执行授权接口引擎。不要把一次订阅误当成连接全生命周期永久授权。
124
+ - 当前共享 Redis 限流按 `OsClient + UserId` 聚合为 10 秒最多 96 次订阅授权,跨标签页、API 节点和滚动发布共同生效;Redis 不可用时实时订阅失败关闭,业务必须继续走 HTTP Snapshot。
125
+ - `EventId` 在业务重试时保持稳定;平台先用 Redis 短 Claim 协调跨节点发布,只有真实广播成功后才写 24 小时完成标记,避免“先去重、后崩溃”永久漏发。客户端仍必须按 `EventId` 去重,因为故障恢复可能产生重复通知。
126
+ - `Version` 按同一 `ChannelKey + SubjectId` 单调递增。低版本事件作为过期事件拒绝广播;同版本但内容指纹不同视为版本冲突并拒绝;重放相同事件不推进 latest。
127
+ - 宿主只读取成功 DosResult 中固定大小写的 `DataAppend.RealtimeEvent`,并只广播 `EventId/ChannelKey/SubjectId/Version/EventType/Data/OccurredAt`。`Data` 最大 32KB,只能放该群组所有订阅者都可见的安全投影;个性化私有数据通过按当前用户裁剪的 Snapshot 获取。
128
+ - 客户端按 `EventId` 去重、按 `Version` 检测乱序和缺口;连接失败、续租失败、重连或发现缺口时立即重新拉 HTTP Snapshot,并保留有界轮询兜底。共享存储/状态机才是事实源。
129
+ - 旧 `/game-realtime` 只作兼容。新业务默认使用通用协议,完整契约见官方 `v8-server.md`。
130
+
131
+ ## 1. 匿名调用(IsAnonymous)
132
+
133
+ 公开接口(登录、注册、忘记密码、验证码、扫码登录、第三方回调)必须开启:
134
+
135
+ ```javascript
136
+ // 例:发送验证码(匿名)
137
+ if (!V8.Param.phone) return { Code: 0, Msg: '手机号不能为空' };
138
+ if (!/^1[3-9]\d{9}$/.test(V8.Param.phone)) return { Code: 0, Msg: '手机号格式错误' };
139
+
140
+ // 防刷:1分钟同一手机号最多1次
141
+ var key = 'Microi:' + V8.OsClient + ':SmsCode:' + V8.Param.phone;
142
+ if (V8.Cache.Exists(key)) return { Code: 0, Msg: '请稍后再试' };
143
+
144
+ var code = Math.floor(100000 + Math.random() * 900000).toString();
145
+ V8.Cache.Set(key, code, 60);
146
+ // ... 调短信网关 ...
147
+ return { Code: 1, Msg: '验证码已发送' };
148
+ ```
149
+
150
+ ### 1.1 会员端 Token 优先级
151
+
152
+ 移动端/会员端自建 Token 与 Microi 后台 JWT 并存时,会员业务接口应明确 Token 优先级。MCP、后台自动化测试、PC 管理端代理调用常会在 `V8.Header.Token` 中带平台 JWT,如果接口要校验会员登录态,推荐优先读取显式会员参数或专用 Header,再回退平台 Header:
153
+
154
+ ```javascript
155
+ function getMemberToken() {
156
+ var p = V8.Param || {};
157
+ var h = V8.Header || {};
158
+ var token = p.Token || p.token || h.MallMemberToken || h.mallmembertoken || h.Token || h.token || h.Authorization || h.authorization || '';
159
+ token = String(token || '').trim();
160
+ if (token.indexOf('Bearer ') === 0) token = token.substring(7).trim();
161
+ return token;
162
+ }
163
+ ```
164
+
165
+ 不要让后台 JWT 覆盖前端显式传入的会员 Token,否则 MCP/Playwright 用会员账号做自动化测试时会误判为未登录。
166
+
167
+ ## 2. 禁止外部调用(StopHttp)
168
+
169
+ 仅供其他接口引擎/V8 事件内部调用,不允许直接 HTTP 请求触发:
170
+
171
+ ```javascript
172
+ // 例:核心扣款接口(StopHttp=true)
173
+ // 只能从 order_pay、refund 等接口通过 V8.ApiEngine.Run 调用
174
+ V8.Db.FromSql('UPDATE Account SET Balance = Balance - @p0 WHERE Id = @p1')
175
+ .AddInParameter("@p0", V8.Param.amount)
176
+ .AddInParameter("@p1", V8.Param.accountId)
177
+ .ExecuteNonQuery();
178
+ return { Code: 1 };
179
+ ```
180
+
181
+ 外部调用直接 `/apiengine/account_deduct` 会被拒绝。
182
+
183
+ ## 3. 分布式锁(LockKey)
184
+
185
+ 集群部署时可用接口引擎 `LockKey` 减少同一任务的并发执行(如:每月对账、自动补单):
186
+
187
+ ```javascript
188
+ // 配置:LockKey = Month,Timeout = 600
189
+ // 调用方传入 Month;平台使用共享锁协调多节点
190
+ var month = String(V8.Param.Month || '');
191
+ if (!/^\d{4}-\d{2}$/.test(month)) return { Code: 0, Msg: 'Month 格式不正确' };
192
+ V8.Db.FromSql('INSERT INTO MonthSettle SELECT ... WHERE Month = @p0')
193
+ .AddInParameter("@p0", month)
194
+ .ExecuteNonQuery();
195
+ return { Code: 1 };
196
+ ```
197
+
198
+ `LockKey` 填写请求参数字段名;上例调用方应传入 `Month`,平台使用其值区分不同月份。未填写时按 `ApiEngineKey` 串行。平台自动把锁放入当前 `OsClient` 命名空间,不需要也不应让客户端自行拼接其它租户前缀。缺少已配置的参数字段时会退回使用字段名本身,虽然仍能互斥,但会让所有请求共享一把锁,因此保存与 HTTP 复测必须覆盖实际参数。
199
+
200
+ ### 普通调用与可信后台任务的租约差异
201
+
202
+ - 普通 HTTP 或普通 V8 调用保持固定租期:`Timeout` 是锁成功获取后的 Redis TTL,不会自动延长。它必须大于正常执行时间,但不能靠设置超大数值代替可靠后台任务。
203
+ - 通过平台 `RunBackground`/后台任务服务进入的可信持久执行,会在回调期间按持有者令牌自动续租。当前默认最长续租边界为 12 小时;若接口显式配置的单次租期本身更长,平台不会把它缩短到 12 小时。
204
+ - 可信身份由服务端建立,并同时校验任务 Id、后台任务信封和正数 fencing token。客户端或普通 V8 自行传入 `_BackgroundTaskId`、`_BackgroundTask`、`_BackgroundTaskFencingToken`、`_TrustedServerInvocation` 或 `_CurrentUser`,不能开启自动续租。
205
+ - 续租和释放都以唯一持有者令牌做 Redis 原子比较;锁每次成功获取还会产生单调递增 fencing token。持有者不匹配、锁已过期、Redis 所有权/续租确认失败或达到最长租约时,执行必须失败关闭,旧持有者不得继续提交副作用。
206
+
207
+ 分布式锁不是“业务只执行一次”的最终保证。扣款、库存、积分、流水、对账等副作用还必须使用稳定幂等键、数据库唯一约束/条件更新、状态机或 outbox/inbox;锁 Key 至少包含 `OsClient + 业务唯一标识`,超时必须大于正常执行时间。所需唯一索引必须写入 Manifest `tables[].indexes` 并通过 `microi_create_table_index` 创建、`microi_get_table_indexes` 回读,接口引擎本身禁止执行索引 DDL。
208
+
209
+ 预计超过 10 分钟的任务即使具备自动续租,也必须使用 `HasMore + Checkpoint` 分片并持久化真实进度。每个业务条件写入使用 `_BackgroundTaskFencingToken` 拒绝租约过期的旧执行者;错误信息包含“分布式锁租约已丢失”时不得捕获后返回成功,任务应保留最后进度与原始原因,交由后台任务的幂等恢复或人工诊断处理。
210
+
211
+ ## 4. 自定义路径(ApiAddress)
212
+
213
+ 让接口暴露为 `/wechat/notify` 而非 `/apiengine/wechat_notify`,对接第三方时常用:
214
+
215
+ ```
216
+ ApiAddress: /wechat/notify
217
+ ```
218
+
219
+ 标准协议还可声明逐段模板,例如:
220
+
221
+ ```text
222
+ ApiAddress: /sso/{OsClient}/.well-known/openid-configuration
223
+ ApiAddress: /saml/{OsClient}/sp/{ConnectionKey}/metadata
224
+ ```
225
+
226
+ 模板只匹配完整路径段,不支持贪婪通配符。命中的路由值会写入 `V8.Param._RouteValues`,并以权威路径值覆盖同名 Query/Form/JSON 参数,防止调用者伪造另一租户或连接。包含 `{OsClient}` 时租户由该路径段解析;同一路径匹配多个模板视为配置冲突并拒绝执行。
227
+
228
+ ## 5. 响应文件(IsResponseFile)
229
+
230
+ 开启后接口可直接输出二进制流:
231
+
232
+ 后端会统一处理响应头和文件头校验:图片/PDF 浏览器直接打开,其它文件下载;V8 代码只返回文件三字段,不要在接口里手写复杂的魔数判断。`ContentType` 必须匹配真实字节,金蝶 PLM `KD_C_PLM` 等业务封装流不能伪装成 `application/pdf`。
233
+
234
+ 响应文件动态路由必须同时接受 `GET` 和 `HEAD`。OnlyOffice 等服务端预览器可能先用 `HEAD` 探测文件类型、长度和可达性;如果浏览器直接下载正常但 `HEAD` 返回 `405`,在线预览仍可能一直停在“加载文档”。
235
+
236
+ ```javascript
237
+ // 必须返回特定结构
238
+ return {
239
+ Code: 1,
240
+ Data: {
241
+ FileName: 'report.xlsx',
242
+ ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
243
+ FileByteBase64: System.Convert.ToBase64String(byteArr)
244
+ }
245
+ };
246
+ ```
247
+
248
+ 详见 `v8-file-upload/SKILL.md`。
249
+
250
+ ## 6. 频率限制(RateLimit)
251
+
252
+ 防爬虫、防刷:
253
+
254
+ | 配置 | 含义 |
255
+ |------|------|
256
+ | `60/m` | 每分钟 60 次 |
257
+ | `1000/h` | 每小时 1000 次 |
258
+ | `100/s` | 每秒 100 次 |
259
+
260
+ 按客户端 IP + 接口 维度限流。
261
+
262
+ ## 7. 日志记录(LogParam / LogResult)
263
+
264
+ 支付、撤销等敏感接口建议开启,自动记到 `sys_log` 用于审计回溯:
265
+
266
+ ```
267
+ LogParam = true # 记录每次入参
268
+ LogResult = true # 记录每次返回
269
+ ```
270
+
271
+ > ❌ 接口返回结果含敏感数据(密码、token、密钥)时不要打开 `LogResult`
272
+
273
+ ## 8. 保存后 HTTP 复测
274
+
275
+ 通过 MCP 维护接口引擎时,先用 `microi_list_engines` 发现现有接口,再用
276
+ `microi_get_engine_code` 读取源码;修改后使用 `microi_save_engine_code`
277
+ 保存并回读。只有确认目标不存在时才调用创建工具,避免重复
278
+ `ApiEngineKey`。`microi_run_engine` 适合做引擎上下文内的最小调试,但不能
279
+ 代替下方真实 HTTP 复测。
280
+
281
+ `microi_run_engine` 只能证明引擎代码在 MCP/内部执行上下文可运行,不能证明移动端或外部 HTTP 能调用。新建或更新接口后必须再走一次真实 HTTP 路径:
282
+
283
+ ```text
284
+ POST /apiengine/{ApiEngineKey}
285
+ Headers: Content-Type=application/json, osclient={OsClient}, apiengine=1
286
+ Body: {"Action":"Bootstrap","OsClient":"{OsClient}"}
287
+
288
+ # 仅用于不能立即升级的旧客户端;新增或可修改代码禁止使用
289
+ POST /api/ApiEngine/Run
290
+ Headers: Content-Type=application/json, OsClient={OsClient}
291
+ Body: {"ApiEngineKey":"your_key","Action":"Bootstrap"}
292
+ ```
293
+
294
+ 固定业务接口必须调用 `/apiengine/{ApiEngineKey}` 或该引擎配置的唯一
295
+ `ApiAddress`。禁止新增 `/api/ApiEngine/Run` 依赖,否则反向代理、限流、审计和
296
+ 系统日志/监控只能看到同一个通用入口,难以按真实接口引擎准确归因。SDK 只可在
297
+ 显式命名的 `RunLegacy` 兼容方法中保留旧地址,普通 `Run` 必须生成动态地址。
298
+
299
+ 复测重点:
300
+
301
+ - `IsEnable=1`、`StopHttp=0`、公开接口 `AllowAnonymous=1`。
302
+ - JSON Body 会恢复到 `V8.Param`;同名参数已由 Query/Form 绑定时保持既有值,避免改变旧调用优先级。直接动态路由与兼容入口都要覆盖 JSON Body 测试,不能只用 Query 参数证明可用。
303
+ - HTTP 请求中的 `_CurrentUser`、`_InvokeType:'Server'`、`_TrustedServerInvocation` 都不能建立可信服务端身份;当前用户和调用类型必须由认证中间件与接口层重新写入。
304
+ - `ApiAddress` 不能为空字符串;空字符串可能导致 404。
305
+ - 响应不能是空 body、字符串 `null`、非 JSON;业务接口必须返回标准 DosResult。
306
+ - 普通 `POST/PUT/PATCH/DELETE` 必须使用稳定路径 `/apiengine/{ApiEngineKey}`,租户放在唯一的 `osclient` Header,并可在 JSON/Form Body 中冗余传入;禁止无脑给路径追加 `--OsClient--...--`。普通 GET 优先 Header 或 `?OsClient=`。只有微信/支付等第三方回调(包括 POST)、浏览器直接下载等调用方确实无法设置 Header 或 Query 的场景,才使用 `--OsClient--{OsClient}--` 特殊路径;Query 参数名固定为 `OsClient`,禁止 `o` 等缩写。
307
+ - 需要 C# 验签/AES 解密、协议编解码或隐藏 SaaS 密钥的回调,使用“`Managed` HTTP 接口引擎 + 精确 Key 可调用的最小 `V8.Method` 可信原子 + `CreateIfMissing` 租户 Hook”。公开地址仍归接口引擎,禁止为此恢复 Controller;传给 Hook 的事件必须脱敏,并包含稳定 `EventId` 供幂等。
308
+ - 更新接口代码时保留 HTTP 元数据,避免只覆盖 JS 代码却把匿名、启用、自定义地址等配置冲掉。
309
+
310
+ ### 路由冷缓存与客户端直达头(强制)
311
+
312
+ - `/apiengine/{ApiEngineKey}` 客户端请求必须携带 `apiengine: 1`;标准前端 SDK 应从稳定路径自动识别并补齐,不能要求每个业务页面手写 Header。自定义 `ApiAddress` 无法从路径识别时,调用方显式设置 `apiEngine: true`。
313
+ - 动态路由缓存未命中后允许从 `sys_apiengine` 权威回源并重建 `ApiEngineKey`、`ApiAddress` 两个别名。回源对象如果来自 `dynamic`,先转换为 `JObject`/`object`,并把字段显式赋给 `string`、`bool` 等强类型局部变量,再调用普通方法或扩展方法;禁止让 `dynamic` 调用链延续到 `DosIsNullOrWhiteSpace`、LINQ 或 JToken 扩展。
314
+ - 自动化必须覆盖“缓存预热命中”和“缓存为空首次请求”两条路径;首次请求不得 404,且回源后两个缓存别名均可再次命中。滚动发布、节点重启或缓存清理后要重复执行无 Header 与带 `apiengine: 1` 的真实 HTTP smoke test。
315
+
316
+ ### 复盘:接口引擎冷缓存回源异常被吞成空 404
317
+
318
+ - 触发场景:接口配置、启用和匿名设置都正确,接口昨天可用;节点重启或缓存缺失后,小程序首次请求 `/apiengine/{key}` 返回空 body 404。
319
+ - 根因:动态路由从数据库回源成功后,局部变量仍沿着 `dynamic` 调用链传播;运行时对实际 `string` 绑定扩展方法失败,外层异常处理返回原路由值,最终由 ASP.NET Core 表现为无正文 404。
320
+ - 通用规则:数据库/缓存的动态对象在进入路由、鉴权、缓存键和 LINQ 逻辑前必须强类型落地;稳定接口引擎路径由 SDK 自动携带 `apiengine: 1` 作为直达兜底。
321
+ - 自动化检查:单元测试直接传入 `JObject` 验证回源别名强类型归一化;前端传输测试断言 `/apiengine/*` 自动携带 `apiengine: 1`,普通 `/api/*` 不误带;本地启动后清空专用测试别名并验证首次 HTTP 请求成功及缓存重建。
322
+
323
+ ### 复盘:单段自定义 ApiAddress 被误判为 ApiEngineKey
324
+
325
+ - 触发场景:接口行已启用并允许匿名,显式配置了 `/apiengine/external-name`,但真实 `ApiEngineKey` 是另一个值;Query `?OsClient=` 与 `--OsClient--...--` 两种调用都返回 `NoExistData[ApiAddress]`。
326
+ - 根因:动态路由把所有单段 `/apiengine/{value}` 先解释成 `ApiEngineKey=value`,跳过了显式 `ApiAddress`;模型未加载时匿名开关尚未进入判断,因此调整 `AllowAnonymous` 无法修复。
327
+ - 通用规则:完整 `ApiAddress` 与 `ApiRoutes` 是路由第一事实源,只有权威主库确认该地址从未配置时,才允许把尾段作为兼容 Key 回退。停用但未软删除的地址仍占用路由,不能旁路到另一条同名 Key;Controller 只信任动态路由写入的真实 Key,未解析时保留完整地址,禁止再次猜 Key。
328
+ - 自动化检查:至少覆盖“显式地址尾段与 Key 不同”和“ApiAddress 为空的传统 Key 路由”,并分别验证 `?OsClient=`、`--OsClient--...--`、冷缓存首次请求、停用地址阻断及匿名关闭返回鉴权错误而非不存在。
329
+
330
+ ## 请求内异步与可靠后台任务
331
+
332
+ 接口默认同步返回。对本次请求必须完成的异步 I/O,调用真实的 `*Async` 方法并 `await`。常用入口包括 `V8.Http.*Async`、`V8.FormEngine.GetTableDataAsync` 和 `V8.ApiEngine.RunAsync`:
333
+
334
+ ```javascript
335
+ var resp = await V8.Http.GetResponseAsync({
336
+ Url: 'https://example.com/health',
337
+ Timeout: 5
338
+ });
339
+ if (resp.StatusCode < 200 || resp.StatusCode >= 300) {
340
+ return { Code: 0, Msg: '上游调用失败' };
341
+ }
342
+
343
+ var users = await V8.FormEngine.GetTableDataAsync('SysUser', {
344
+ _Where: [['Status', '=', 1]],
345
+ _SelectFields: ['Id', 'Name'],
346
+ _PageSize: 20
347
+ });
348
+
349
+ var summary = await V8.ApiEngine.RunAsync('build-user-summary', {
350
+ Users: users.Data
351
+ });
352
+ return { Code: 1, Data: { Upstream: resp.Content, Summary: summary.Data } };
353
+ ```
354
+
355
+ 禁止用 `setTimeout` 或 `System.Threading.Tasks.Task.Run` 实现“接口先返回、后台继续执行”:`V8Engine.Run` 返回后会释放 Jint Engine、租户上下文、事务和并发租约,回调不可靠,也没有持久化、重试、幂等或重启恢复保证。
356
+
357
+ 需要先响应再处理时,使用接口引擎后台任务按钮(`RunBackground + ApiEngineKey`)、Job、MQ 或 outbox;消费者按全局 `EventId` 幂等处理并持久化进度。AI 发现预计超过 2 分钟、500 条、1000 个扇出子操作、100 次外部调用,或安装/初始化/迁移/备份/全量生成等任务时,必须主动切换为后台任务;预计超过 10 分钟时还必须设计 checkpoint 分片。见 `job-engine`、`v8-menu-buttons`、`v8-mq-mqtt` 和 `microi-system-delivery`。
358
+
359
+ ## 接口安全检查清单
360
+
361
+ - [ ] 公开接口是否仅开启 `IsAnonymous`,敏感接口是否关闭?
362
+ - [ ] 内部接口是否开启 `StopHttp`?
363
+ - [ ] 写操作(扣款、对账、补单)是否配置 `LockKey`?
364
+ - [ ] `LockKey` 是否指向真实存在的请求参数,`Timeout` 是否是合理的单次租期?
365
+ - [ ] 锁之外是否还有幂等键、唯一约束/条件更新或状态机?
366
+ - [ ] 长任务是否只由平台可信后台上下文自动续租,并在租约丢失时失败关闭?
367
+ - [ ] 频率敏感接口是否配置 `RateLimit`?
368
+ - [ ] 审计需求接口是否开启 `LogParam`?
369
+ - [ ] 文件响应接口是否开启 `IsResponseFile`?
370
+ - [ ] 接口代码内是否仍校验 `V8.CurrentUser`(`IsAnonymous=true` 时尤其重要)?
371
+ - [ ] 是否没有使用 `setTimeout` / `Task.Run` 承担请求外后台任务?
372
+ - [ ] 大任务是否按阈值主动使用后台任务,超过 10 分钟是否有 `HasMore + Checkpoint`?
373
+ - [ ] 是否区分累计分配、调用树预算、JS递归与接口嵌套,而不是盲目抬高全部限制?
374
+ - [ ] 是否确认 `V8Limit=false` 表示接口不限 Jint 单次预算、`true` 才启用限制,并避免继续写入旧 `V8Unlimited` 字段?
375
+ - [ ] 保存后是否通过稳定路径 `/apiengine/{key}` + `osclient` Header 做过 HTTP 复测?特殊 GET/HEAD 路径是否仅用于无法设置 Header/Form/Query 的场景?
376
+
377
+ ## 常见错误
378
+
379
+ ❌ 把支付回调接口设为非匿名 → 第三方无 Token → 回调失败
380
+ ❌ 内部接口忘开 `StopHttp` → 被外部直接调用绕过校验
381
+ ❌ 对账接口未配置 `LockKey` → 集群多实例并发执行 → 数据双倍
382
+ ❌ 文件下载接口未开 `IsResponseFile` → 返回 JSON 而非文件流
383
+
384
+ ## 复盘:后台任务调度路由与执行目标的参数边界
385
+
386
+ - HTTP 调用持久后台任务时使用固定 `/apiengine/platform-background-task?OsClient=` 与 `Action: 'RunApiEngine'`;目标接口放在独立的 `TargetApiEngineKey`,业务参数放在 `Param`,幂等和并发策略放在 `Options`。不可用正文 `ApiEngineKey` 改写路由调度器。
387
+ - 回归至少验证嵌套业务参数与 Options 能完整抵达可信后台原子、同一幂等键返回原任务、匿名及未授权身份拒绝执行。页面提交成功后还需回读后台任务终态,不能将“已排队”视为业务完成。
388
+ - 原子能力若按持久任务记录中的接口 Key 校验可信执行身份,队列目标必须是该原子允许的工作器本身;嵌套调用不会改写任务记录的接口身份。禁止把兼容提交器排入队列后再调用工作器,并通过扩大 Key 白名单或省略栅栏令牌校验掩盖目标不一致。回归应对照队列目标与原子的固定 Key,并走一次真实写流程到终态。