@microi.net/cli 5.7.8 → 5.7.9

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 (191) 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/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/LICENSE +21 -21
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +6 -6
  9. package/assets/feature-matrix.json +138 -138
  10. package/assets/logo.svg +4 -4
  11. package/cordis.patch.yml +1 -1
  12. package/package.json +1 -1
  13. package/scripts/mcp-codex-stdio-adapter.js +0 -0
  14. package/scripts/mcp-server.js +102 -101
  15. package/scripts/microi-cli.js +57 -57
  16. package/scripts/microi-codex-broker.js +450 -450
  17. package/scripts/microi-codex-router.js +0 -0
  18. package/scripts/microi-skills.meta.json +366 -366
  19. package/skills/.microi-skills-version.json +2 -2
  20. package/skills/.progressive-disclosure-manifest.json +27 -27
  21. package/skills/README.md +286 -286
  22. package/skills/ai-engine/SKILL.md +265 -265
  23. package/skills/ai-engine/agents/openai.yaml +4 -4
  24. package/skills/ai-platform-governance/SKILL.md +177 -177
  25. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  26. package/skills/app-store/SKILL.md +392 -396
  27. package/skills/app-store/agents/openai.yaml +4 -4
  28. package/skills/business-blueprint/SKILL.md +193 -193
  29. package/skills/datasource-engine/SKILL.md +90 -90
  30. package/skills/datasource-engine/agents/openai.yaml +4 -4
  31. package/skills/dos-orm/references/api-reference.md +229 -229
  32. package/skills/email-engine/SKILL.md +78 -78
  33. package/skills/email-engine/references/v8-email.md +34 -34
  34. package/skills/job-engine/SKILL.md +171 -172
  35. package/skills/job-engine/agents/openai.yaml +4 -4
  36. package/skills/message-notification/SKILL.md +155 -156
  37. package/skills/message-notification/agents/openai.yaml +5 -5
  38. package/skills/message-notification/references/contracts.md +102 -102
  39. package/skills/microi-ai-app-auth.js +652 -652
  40. package/skills/microi-ai-application/SKILL.md +102 -106
  41. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  42. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  43. package/skills/microi-client-frontend/SKILL.md +235 -240
  44. 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
  45. 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
  46. 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
  47. package/skills/microi-codex/SKILL.md +72 -76
  48. package/skills/microi-codex-installer/SKILL.md +225 -223
  49. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  50. package/skills/microi-datasource-mapping/SKILL.md +107 -111
  51. package/skills/microi-db-schema/SKILL.md +175 -175
  52. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  53. package/skills/microi-db-schema/references/core-tables.md +695 -695
  54. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  55. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  56. package/skills/microi-db-schema/references/schema.md +646 -646
  57. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  58. package/skills/microi-deployment/references/deployment-matrix.md +101 -101
  59. package/skills/microi-docs-coverage/references/capability-map.md +0 -1
  60. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  61. package/skills/microi-form-engine/SKILL.md +226 -226
  62. package/skills/microi-form-engine/references/component-catalog.md +216 -216
  63. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  64. package/skills/microi-form-layout/SKILL.md +205 -205
  65. 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
  66. package/skills/microi-frontend-sdk/SKILL.md +188 -188
  67. 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
  68. package/skills/microi-left-right-layout/SKILL.md +141 -141
  69. package/skills/microi-microservice/SKILL.md +322 -323
  70. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  71. package/skills/microi-mobile-app-quality/SKILL.md +181 -181
  72. 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
  73. 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
  74. package/skills/microi-solution-quotation/SKILL.md +78 -78
  75. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  76. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  77. package/skills/microi-sso/references/acceptance.md +49 -49
  78. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  79. package/skills/microi-sso/references/inbound.md +53 -53
  80. package/skills/microi-sso/references/outbound.md +39 -39
  81. package/skills/microi-system-delivery/SKILL.md +134 -134
  82. 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
  83. 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 +189 -189
  84. package/skills/microi-ui/SKILL.md +174 -174
  85. 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
  86. package/skills/microi-uniapp-frontend/SKILL.md +193 -193
  87. 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
  88. 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
  89. package/skills/microi.v8.js +1921 -1921
  90. package/skills/module-engine/SKILL.md +237 -250
  91. package/skills/module-engine/references/module-config.md +204 -204
  92. package/skills/ocr-engine/SKILL.md +113 -113
  93. package/skills/ocr-engine/agents/openai.yaml +4 -4
  94. package/skills/page-engine/SKILL.md +180 -191
  95. 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
  96. 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
  97. package/skills/performance-testing/SKILL.md +221 -221
  98. package/skills/playwright-e2e/SKILL.md +197 -197
  99. 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
  100. 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
  101. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  102. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  103. package/skills/print-engine/SKILL.md +259 -259
  104. package/skills/production-readonly-audit/SKILL.md +41 -41
  105. package/skills/report-engine/SKILL.md +71 -71
  106. package/skills/report-engine/agents/openai.yaml +4 -4
  107. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  108. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  109. package/skills/search-engine/SKILL.md +75 -75
  110. package/skills/search-engine/agents/openai.yaml +4 -4
  111. package/skills/spider-engine/SKILL.md +190 -190
  112. package/skills/system-observability/SKILL.md +238 -238
  113. package/skills/system-observability/references/memory-incident-triage.md +70 -70
  114. package/skills/translate-engine/SKILL.md +140 -140
  115. package/skills/translate-engine/agents/openai.yaml +4 -4
  116. package/skills/ui-design/SKILL.md +191 -191
  117. package/skills/ui-design/assets/templates/MCI-DESIGN.md +198 -198
  118. package/skills/ui-design/references/design-pattern-library.md +184 -184
  119. package/skills/ui-design/references/mci-design-contract.md +163 -163
  120. 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
  121. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +137 -137
  122. 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
  123. 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
  124. 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
  125. 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
  126. 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
  127. 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 +117 -117
  128. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  129. package/skills/unity-integration/SKILL.md +155 -155
  130. package/skills/unity-integration/agents/openai.yaml +4 -4
  131. package/skills/unity-integration/references/ai-app-delivery.md +103 -103
  132. package/skills/unity-integration/references/sdk-api.md +82 -82
  133. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  134. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  135. package/skills/v8-cache-pattern/SKILL.md +306 -306
  136. package/skills/v8-crud-api/SKILL.md +175 -175
  137. 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
  138. 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
  139. package/skills/v8-debugging/SKILL.md +284 -284
  140. package/skills/v8-explorer-tree/SKILL.md +228 -228
  141. package/skills/v8-export-import/SKILL.md +209 -209
  142. 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
  143. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  144. 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
  145. package/skills/v8-file-upload/SKILL.md +256 -263
  146. 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 +101 -101
  147. 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 +150 -150
  148. package/skills/v8-formengine-http/SKILL.md +219 -219
  149. package/skills/v8-frontend-events/SKILL.md +178 -178
  150. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  151. package/skills/v8-frontend-events/references/bluetooth-print.md +246 -246
  152. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  153. package/skills/v8-http-integration/SKILL.md +182 -182
  154. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  155. 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
  156. package/skills/v8-image-processing/SKILL.md +190 -190
  157. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  158. package/skills/v8-image-processing/references/api-reference.md +623 -623
  159. package/skills/v8-menu-buttons/SKILL.md +180 -180
  160. 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
  161. 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
  162. 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
  163. package/skills/v8-mongodb/SKILL.md +192 -192
  164. package/skills/v8-mq-mqtt/SKILL.md +175 -176
  165. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  166. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  167. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  168. package/skills/v8-saas-multi-tenant/SKILL.md +219 -219
  169. package/skills/v8-security/SKILL.md +168 -168
  170. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +198 -198
  171. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +158 -158
  172. package/skills/v8-sql-query/SKILL.md +302 -302
  173. package/skills/v8-table-event/SKILL.md +144 -144
  174. 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 +197 -197
  175. 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
  176. package/skills/v8-tcp-integration/SKILL.md +147 -147
  177. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  178. package/skills/v8-template-engine/SKILL.md +167 -167
  179. package/skills/v8-utilities/SKILL.md +90 -90
  180. package/skills/v8-utilities/references/client-api-index.md +143 -143
  181. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  182. package/skills/v8-utilities/references/server-api-index.md +188 -188
  183. package/skills/v8-workflow/SKILL.md +216 -243
  184. 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
  185. package/skills/vision-engine/SKILL.md +159 -159
  186. package/skills/vision-engine/agents/openai.yaml +4 -4
  187. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  188. package/skills/workspace-conventions/SKILL.md +261 -261
  189. 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 +208 -208
  190. 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 +216 -216
  191. 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,8 +1,8 @@
1
- # v8-file-upload 详细参考 1
2
-
3
- > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
-
5
- <!-- microi-progressive:chunk id=v8-file-upload-005 sha256=9efd186d8af26854bbed4d675b31fff57202a0139094798d9a41c3e4769fd092 -->
1
+ # v8-file-upload 详细参考 1
2
+
3
+ > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
+
5
+ <!-- microi-progressive:chunk id=v8-file-upload-005 sha256=9efd186d8af26854bbed4d675b31fff57202a0139094798d9a41c3e4769fd092 -->
6
6
  ## 公有桶 vs 私有桶
7
7
 
8
8
  ### 应用商城 ZIP
@@ -57,9 +57,9 @@ var extractResult = V8.Method.ExtractZip({
57
57
  ```
58
58
 
59
59
  底层断点式单文件工具是 `microi_upload_application_asset_stream`。除诊断或精确恢复单文件外,不要只调用它而遗漏最终清单确认,否则稳定入口不会切换。
60
-
61
- | 类型 | `Limit` | 访问 URL | 用途 |
62
- |------|---------|---------|------|
60
+
61
+ | 类型 | `Limit` | 访问 URL | 用途 |
62
+ |------|---------|---------|------|
63
63
  | 公有桶 | `false` | 直接拼接 `V8.SysConfig.FileServer + Path` | 产品图、Banner、公开文档 |
64
64
  | 私有桶 | `true` | 必须用 `V8.Method.GetPrivateFileUrl` 获取临时 URL | 合同、身份证、敏感数据 |
65
65
 
@@ -118,24 +118,24 @@ var extractResult = V8.Method.ExtractZip({
118
118
  - 验收断言:筛选一条有头像的系统用户,页面中 `/tenant_demo/avatar/` 的直接公有请求数必须为 0,私有代理图片 `naturalWidth > 0`,并同时验证无头像占位图不报错。
119
119
 
120
120
  定制移动端项目的 Hero、Banner、音频、视频、字体等大资源也应优先上传到目标租户公有 HDFS,再通过 FileServer/CDN 引用;小型导航图标和离线关键素材才保留在主包。上传前可适度压缩,但必须在多尺寸截图或试听/试播中确认质量,禁止以明显失真换取包体扫描通过。完整移动端规则见 `microi-uniapp-frontend/SKILL.md`。
121
-
122
- 后台 `ImgUpload` 字段通常保存相对路径或 JSON:接口返回给移动端前先解析出 `Path`,再按公私有场景转换 URL:
123
-
124
- ```javascript
125
- function publicFileUrl(path) {
126
- if (!path) return '';
127
- if (/^https?:/i.test(path)) return path;
128
- return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '');
129
- }
130
- ```
131
-
121
+
122
+ 后台 `ImgUpload` 字段通常保存相对路径或 JSON:接口返回给移动端前先解析出 `Path`,再按公私有场景转换 URL:
123
+
124
+ ```javascript
125
+ function publicFileUrl(path) {
126
+ if (!path) return '';
127
+ if (/^https?:/i.test(path)) return path;
128
+ return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '');
129
+ }
130
+ ```
131
+
132
132
  ### 私有桶临时 URL
133
133
 
134
134
  ```javascript
135
135
  var url = V8.Method.GetPrivateFileUrl({
136
- FilePathName: '/private/contract/2024/abc.pdf',
137
- OsClient: V8.OsClient, // 可选,默认当前
138
- Expires: 600 // 可选,过期秒数
136
+ FilePathName: '/private/contract/2024/abc.pdf',
137
+ OsClient: V8.OsClient, // 可选,默认当前
138
+ Expires: 600 // 可选,过期秒数
139
139
  });
140
140
  // 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
141
141
  ```
@@ -152,83 +152,83 @@ var url = V8.Method.GetPrivateFileUrl({
152
152
  - 代理创建或包装失败时必须失败关闭,不得退回未经审计的真实签名 URL;行为日志中也禁止保存真实签名 URL、Token、Authorization 或存储密钥。
153
153
  - `Limit:false` 的公有文件允许通过 CDN/公有桶直接访问,不要求记录用户行为日志,也不要为了审计强制改走私有代理。
154
154
 
155
- <!-- /microi-progressive:chunk -->
156
- <!-- microi-progressive:chunk id=v8-file-upload-006 sha256=8d4fedab2418c7df7973b78d4b9d6c5fa77907929d401800689cd1b5b468f32f -->
155
+ <!-- /microi-progressive:chunk -->
156
+ <!-- microi-progressive:chunk id=v8-file-upload-006 sha256=8d4fedab2418c7df7973b78d4b9d6c5fa77907929d401800689cd1b5b468f32f -->
157
157
  ## 接口直接响应文件(下载/导出)
158
-
159
- 接口引擎需要在配置中开启【**响应文件**】选项,然后返回特殊结构:
160
-
161
- 平台后端会统一处理响应文件:图片和 PDF 自动 `inline` 在浏览器中打开,其它类型默认下载;PDF、PNG、JPEG、GIF、WebP、AVIF、BMP、TIFF、ICO、SVG 等常见可预览类型会自动校验文件头。V8 代码不要手写复杂的文件头判断,只需要保证 `ContentType` 与真实文件内容一致。
162
-
163
- ```javascript
164
- // 模板:导出 Excel
165
- var excelResult = V8.Office.ExportExcel({...});
166
- return {
167
- Code: 1,
168
- Data: {
169
- FileName: 'export_' + DateNow('yyyyMMdd_HHmmss') + '.xls',
170
- ContentType: 'application/vnd.ms-excel',
171
- FileByteBase64: System.Convert.ToBase64String(excelResult.Data)
172
- }
173
- };
174
-
175
- // 模板:返回图片
176
- var resp = V8.Http.GetResponse({ Url: 'https://example.com/img.png' });
177
- return {
178
- Code: 1,
179
- Data: {
180
- FileName: 'img.png',
181
- ContentType: 'image/png',
182
- FileByteBase64: System.Convert.ToBase64String(resp.RawBytes)
183
- }
184
- };
185
-
186
- // 模板:返回 PDF(浏览器直接预览;后端自动校验 %PDF- 文件头)
187
- var pdfResp = V8.Http.GetResponse({ Url: 'https://example.com/report.pdf' });
188
- return {
189
- Code: 1,
190
- Data: {
191
- FileName: 'report.pdf',
192
- ContentType: 'application/pdf',
193
- FileByteBase64: System.Convert.ToBase64String(pdfResp.RawBytes)
194
- }
195
- };
196
- ```
197
-
198
- 常用 ContentType:
199
- - `application/vnd.ms-excel` / `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
200
- - `application/pdf`
201
- - `image/png` / `image/jpeg`
202
- - `application/octet-stream`(通用二进制)
203
-
204
- 注意:如果远程系统返回的是错误页、登录页、业务容器格式(例如金蝶 PLM 电子仓 `KD_C_PLM`、或其它文件头不是 `%PDF-` 的伪 PDF),不要在 V8 里伪装成 PDF。后端会返回 JSON 错误,包含 `ExpectedFirstAscii`、`ActualFirstAscii`、`ActualFirstHex`、`Length`,按这些信息排查上游下载接口。
205
-
206
- <!-- /microi-progressive:chunk -->
207
- <!-- microi-progressive:chunk id=v8-file-upload-007 sha256=59ebcaa605d26def44cd027c0975a01fffee8670a890b4ed18b4138527be8a1b -->
208
- ## 通过 URL 列表批量下载并入库
209
-
210
- ```javascript
211
- var urls = V8.Param.urls; // ['https://...', 'https://...']
212
- var savedPaths = [];
213
- for (var i = 0; i < urls.length; i++) {
214
- try {
215
- var resp = V8.Http.GetResponse({ Url: urls[i], Timeout: 30 });
216
- if (resp.StatusCode !== 200) continue;
217
-
218
- var base64 = System.Convert.ToBase64String(resp.RawBytes);
219
- var fileName = V8.Method.NewGuid() + '.bin';
220
- var up = V8.Method.Upload({
221
- FilesByteBase64: { [fileName]: base64 },
222
- Limit: false,
223
- Path: '/batch-import/' + DateNow('yyyy-MM-dd'),
224
- OsClient: V8.OsClient
225
- });
226
- if (up.Code === 1) savedPaths.push(up.Data[0].Path);
227
- } catch (ex) {
228
- console.error('第' + (i + 1) + '个下载失败:' + ex.message);
229
- }
230
- }
231
- return { Code: 1, Data: savedPaths };
232
- ```
233
-
234
- <!-- /microi-progressive:chunk -->
158
+
159
+ 接口引擎需要在配置中开启【**响应文件**】选项,然后返回特殊结构:
160
+
161
+ 平台后端会统一处理响应文件:图片和 PDF 自动 `inline` 在浏览器中打开,其它类型默认下载;PDF、PNG、JPEG、GIF、WebP、AVIF、BMP、TIFF、ICO、SVG 等常见可预览类型会自动校验文件头。V8 代码不要手写复杂的文件头判断,只需要保证 `ContentType` 与真实文件内容一致。
162
+
163
+ ```javascript
164
+ // 模板:导出 Excel
165
+ var excelResult = V8.Office.ExportExcel({...});
166
+ return {
167
+ Code: 1,
168
+ Data: {
169
+ FileName: 'export_' + DateNow('yyyyMMdd_HHmmss') + '.xls',
170
+ ContentType: 'application/vnd.ms-excel',
171
+ FileByteBase64: System.Convert.ToBase64String(excelResult.Data)
172
+ }
173
+ };
174
+
175
+ // 模板:返回图片
176
+ var resp = V8.Http.GetResponse({ Url: 'https://example.com/img.png' });
177
+ return {
178
+ Code: 1,
179
+ Data: {
180
+ FileName: 'img.png',
181
+ ContentType: 'image/png',
182
+ FileByteBase64: System.Convert.ToBase64String(resp.RawBytes)
183
+ }
184
+ };
185
+
186
+ // 模板:返回 PDF(浏览器直接预览;后端自动校验 %PDF- 文件头)
187
+ var pdfResp = V8.Http.GetResponse({ Url: 'https://example.com/report.pdf' });
188
+ return {
189
+ Code: 1,
190
+ Data: {
191
+ FileName: 'report.pdf',
192
+ ContentType: 'application/pdf',
193
+ FileByteBase64: System.Convert.ToBase64String(pdfResp.RawBytes)
194
+ }
195
+ };
196
+ ```
197
+
198
+ 常用 ContentType:
199
+ - `application/vnd.ms-excel` / `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
200
+ - `application/pdf`
201
+ - `image/png` / `image/jpeg`
202
+ - `application/octet-stream`(通用二进制)
203
+
204
+ 注意:如果远程系统返回的是错误页、登录页、业务容器格式(例如金蝶 PLM 电子仓 `KD_C_PLM`、或其它文件头不是 `%PDF-` 的伪 PDF),不要在 V8 里伪装成 PDF。后端会返回 JSON 错误,包含 `ExpectedFirstAscii`、`ActualFirstAscii`、`ActualFirstHex`、`Length`,按这些信息排查上游下载接口。
205
+
206
+ <!-- /microi-progressive:chunk -->
207
+ <!-- microi-progressive:chunk id=v8-file-upload-007 sha256=59ebcaa605d26def44cd027c0975a01fffee8670a890b4ed18b4138527be8a1b -->
208
+ ## 通过 URL 列表批量下载并入库
209
+
210
+ ```javascript
211
+ var urls = V8.Param.urls; // ['https://...', 'https://...']
212
+ var savedPaths = [];
213
+ for (var i = 0; i < urls.length; i++) {
214
+ try {
215
+ var resp = V8.Http.GetResponse({ Url: urls[i], Timeout: 30 });
216
+ if (resp.StatusCode !== 200) continue;
217
+
218
+ var base64 = System.Convert.ToBase64String(resp.RawBytes);
219
+ var fileName = V8.Method.NewGuid() + '.bin';
220
+ var up = V8.Method.Upload({
221
+ FilesByteBase64: { [fileName]: base64 },
222
+ Limit: false,
223
+ Path: '/batch-import/' + DateNow('yyyy-MM-dd'),
224
+ OsClient: V8.OsClient
225
+ });
226
+ if (up.Code === 1) savedPaths.push(up.Data[0].Path);
227
+ } catch (ex) {
228
+ console.error('第' + (i + 1) + '个下载失败:' + ex.message);
229
+ }
230
+ }
231
+ return { Code: 1, Data: savedPaths };
232
+ ```
233
+
234
+ <!-- /microi-progressive:chunk -->
@@ -1,8 +1,8 @@
1
- # v8-file-upload 详细参考 2
2
-
3
- > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
-
5
- <!-- microi-progressive:chunk id=v8-file-upload-008 sha256=b7b809862a9d82f0270ae99486acb5fe23185b9d5efad073ae39b282724e2e32 -->
1
+ # v8-file-upload 详细参考 2
2
+
3
+ > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
+
5
+ <!-- microi-progressive:chunk id=v8-file-upload-008 sha256=b7b809862a9d82f0270ae99486acb5fe23185b9d5efad073ae39b282724e2e32 -->
6
6
  ## Office 文件在线编辑版本号规则
7
7
 
8
8
  ### 文件柜 Office 版本
@@ -14,148 +14,148 @@
14
14
  MCP 只读发现使用 `microi_list_file_cabinet_objects(path, limit)` 与 `microi_get_file_cabinet_office_meta(filePathName, sysMenuId, limit)`;后者必须使用权威文件柜菜单 Id,普通用户和访问密钥无权调用。它们不下载文件字节,也不代表已发布新版本。整文件夹移动/重命名和跨平台文件同步不能视为 Office 历史迁移;迁移存储时需连同私有桶历史前缀一起处理,并回读原对象和历史版本。
15
15
 
16
16
  OnlyOffice 前端组件 npm 包不能替代 Docs 文档服务。`DocsAPI.DocEditor` 的同一存储版本必须使用稳定文档 key,避免每位用户生成不同 key 后失去协同;版本更新后 key 才应变化。Univer 开源包并不包含与现有链路等价的 Office 文件转换、协同服务和历史版本,不能直接加一个系统设置选项就声称切换完成。
17
-
18
- 当文件上传控件开启【Office 在线预览】、【允许在线编辑】和【开启 Office 文件版本号】时,前后端必须遵循统一版本规则:
19
-
20
- - 新上传的 Office 文件(`pdf/doc/docx/xls/xlsx/ppt/pptx`)要立即写入初始版本 `v1.0.0`,字段 JSON 的 `Path` 指向该原始文件,`Version` 为 `v1.0.0`,`Versions[0]` 保存同一份原始文件路径。
21
- - 用户进入 OnlyOffice 在线编辑页后,每次手动点击【保存文件】才生成新版本;第一次保存生成 `v1.0.1`,之后依次生成 `v1.0.2`、`v1.0.3`。
22
- - 未开启版本号时,保存文件直接覆盖当前 `Path` 对应的 HDFS/OSS 源文件。
23
- - 开启版本号时,保存文件必须生成带版本号后缀的新文件,例如 `contract_v1.0.1.docx`,字段 JSON 的 `Path` 指向最新版本,`Versions` 保留历史版本路径。
24
- - 在线 Office 路由和文件上传字段都要能读取 `Versions`,用于右上角切换历史版本预览/编辑。
25
-
26
- ### OnlyOffice 服务端取文件与匿名预览规则
27
-
28
- - OnlyOffice 文档服务器会在服务端再次下载文件。浏览器可以下载但 OnlyOffice 提示“下载失败”时,优先检查生成地址是否为 `localhost/127.0.0.1/内网域名`。
29
- - OnlyOffice 可能先对文档地址发起 `HEAD` 探测。响应文件接口除了 `GET 200`,还必须让 `HEAD` 返回相同的 `Content-Type/Content-Length/Content-Disposition`,不能返回 `405`。
30
- - 私有文件在线预览调用 `/apiengine/platform-private-file-url` 时传 `ForOfficePreview:true` 和权威资源上下文。审计代理应优先使用租户系统配置的公网 `ApiBase`,但仍把真实对象存储签名地址保存在共享 Redis ticket 中,禁止直接返回真实签名地址;旧 Controller 路由只作兼容转发。
31
- - `/online-office` 可以匿名访问。公有存储模式只允许当前 `OsClient` 目录下的 `filePathName`;接口模式通过 `fileUrl` 接收当前平台正式 `ApiBase`,或由同端口本地后端读取的 loopback `/apiengine/...` 响应文件地址,并要求 URL 显式携带当前 `OsClient`。两种模式都拒绝跨租户、路径穿越和任意第三方 URL;`isPrivate=1` 必须登录。
32
- - 匿名接口引擎预览不要求 V8 代码先上传 HDFS:接口开启【响应文件】和【允许匿名】后,完整地址 URL 编码传给 `fileUrl`,同时传 `fileName/fileType`。页面通过匿名安全中转让当前后端限域读取源文件,并以确定性路径缓存到当前租户公有对象存储;OnlyOffice 只接收公网 `FileServer` 地址。loopback 仅允许同端口本地后端访问,禁止开放任意 URL 代理。
33
- - `canEdit` 只是前端请求参数,不是权限。最终编辑条件必须是“有效登录态 && canEdit=true”;匿名始终只读,不能因 URL 参数放开编辑。
34
- - 匿名预览页隐藏左侧菜单、顶部导航和页签;已登录用户保持原系统布局。
35
- - 匿名导出响应接口应配置频率限制或保证生成逻辑足够轻量,不能让单个公网 URL 无界消耗 CPU/内存;如业务仍需要落盘缓存,缓存事实必须进入共享 Redis/HDFS,不能用进程内变量。
36
-
37
- 字段 JSON 示例:
38
-
39
- ```json
40
- {
41
- "Name": "contract.docx",
42
- "Path": "/tenant_demo/file/20260622/contract_v1.0.1.docx",
43
- "Version": "v1.0.1",
44
- "Versions": [
45
- { "Version": "v1.0.0", "Name": "contract.docx", "Path": "/tenant_demo/file/20260622/contract.docx", "IsLatest": false },
46
- { "Version": "v1.0.1", "Name": "contract_v1.0.1.docx", "Path": "/tenant_demo/file/20260622/contract_v1.0.1.docx", "IsLatest": true }
47
- ]
48
- }
49
- ```
50
-
51
- <!-- /microi-progressive:chunk -->
52
- <!-- microi-progressive:chunk id=v8-file-upload-009 sha256=1cc3ff1b6a5c756d957819f198c13ea181143db484527d1931aefc8e0d52993c -->
53
- ## ImgUpload / FileUpload 字段值兼容规则
54
-
55
- `ImgUpload` 不能假设只是一种值结构。PC 表单、移动端、旧数据、单图/多图、公开/私有桶会混合出现以下格式:
56
-
57
- | 场景 | 可能的值 |
58
- |------|----------|
59
- | 空值/占位 | `''`、`null`、`undefined`、`'[]'`、`'null'`、`'正在上传中...'` |
60
- | 旧单图 | `'/upload/xxx/a.png'`、`'https://cdn/a.png'` |
61
- | 新单图 | `{ Path, Name, Size, Id, State }` 或 JSON 字符串 `'{"Path":"..."}'` |
62
- | 多图 | `[{ Path, Name, Id, State }]` 或 JSON 字符串 `'[{"Path":"..."}]'` |
63
- | 其它兼容字段 | `Path`、`FilePathName`、`FullPath`、`Url`、`url`、`src` |
64
-
65
- 任何端(PC、uni-app、H5、小程序)渲染图片前都必须先做“归一化 -> 取 Path -> 转最终 URL”,不要直接 `JSON.parse` 后只处理数组,也不要直接把字段值拼到 `FileServer`。
66
-
67
- 推荐归一化:
68
-
69
- ```javascript
70
- function normalizeUploadValue(value) {
71
- if (value == null || value === '' || value === 'undefined' || value === 'null') return [];
72
- if (value === '正在上传中...' || value === '[]' || value === '[ ]') return [];
73
-
74
- var raw = value;
75
- if (typeof raw === 'string') {
76
- var s = raw.trim();
77
- if ((s.indexOf('{') === 0 || s.indexOf('[') === 0)) {
78
- try { raw = JSON.parse(s); } catch (e) { raw = s; }
79
- } else {
80
- raw = s;
81
- }
82
- }
83
-
84
- if (Array.isArray(raw)) {
85
- return raw.map(normalizeUploadItem).filter(function (it) { return !!it.Path; });
86
- }
87
-
88
- var one = normalizeUploadItem(raw);
89
- return one.Path ? [one] : [];
90
- }
91
-
92
- function normalizeUploadItem(item) {
93
- if (!item) return {};
94
- if (typeof item === 'string') {
95
- return { Path: item, Name: item.split('/').pop() || item, State: 1 };
96
- }
97
- if (typeof item === 'object') {
98
- var path = item.Path || item.FilePathName || item.FullPath || item.Url || item.url || item.src || '';
99
- return {
100
- Id: item.Id || item.id || '',
101
- Name: item.Name || item.FileName || item.name || (path ? String(path).split('/').pop() : ''),
102
- Size: item.Size || item.size || '',
103
- CreateTime: item.CreateTime || item.createTime || '',
104
- State: item.State == null ? 1 : item.State,
105
- Path: path
106
- };
107
- }
108
- return {};
109
- }
110
- ```
111
-
112
- 公开图片 URL 解析原则与 `Microi.Client/src/utils/diy.common.js` 的 `GetServerPath` 一致:
113
-
114
- ```javascript
115
- function publicUploadUrl(path) {
116
- if (!path) return '';
117
- var s = String(path).trim();
118
- if (!s || s === '正在上传中...') return '';
119
- if (s.indexOf('.') === 0) return s; // ./static/img/loading.gif 等本地静态资源
120
- if (/^(https?:|data:|blob:)/i.test(s)) return s; // 已经是最终 URL
121
- if (s.indexOf('{') === 0 || s.indexOf('[') === 0) {
122
- var list = normalizeUploadValue(s);
123
- s = list.length ? list[0].Path : '';
124
- }
125
- if (!s) return '';
126
- return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + s.replace(/^\/+/, '');
127
- }
128
- ```
129
-
130
- 私有桶(`Limit === true`)不要拼 `FileServer`,必须把归一化后的 `Path` 传给 `V8.Method.GetPrivateFileUrl({ FilePathName: path })` 或后端签名接口换临时 URL。
131
-
132
- 跨端保存时保留上传响应的实际 `Limit`(兼容布尔值、`1/0` 与对应字符串),只删除临时签名地址和本地预览状态。历史对象缺少该标记时按字段配置取址;已有私有标记优先于公有字段缺省值。单图字段兼容历史单元素数组,不应在字段值预处理阶段把数组直接清空。
133
-
134
- <!-- /microi-progressive:chunk -->
135
- <!-- microi-progressive:chunk id=v8-file-upload-010 sha256=229294a07a8db953219c35292db5558430addb576505695b9ce88a275eb46232 -->
136
- ## 安全注意
137
-
138
- - ❌ 不要让前端任意指定 `Path`(路径穿越风险),只允许后端固定路径
139
- - ❌ 不要不校验文件类型 / 大小:根据 ContentType + 后缀双重校验
140
- - ❌ 不要把持有 Token 当成私有文件授权;普通用户必须证明菜单、记录和字段引用关系
141
- - ❌ 不要向普通角色开放文件列表、移动、重命名、删除、覆盖等管理接口;这些接口仅限 `Level >= 9999`
142
- - ❌ 不要开启 UEditor `catchimage` 远程抓图;默认关闭,确需采集时另建带域名白名单、DNS/IP 校验、禁止跳转、超时和响应上限的受控接口
143
- - ❌ 敏感文件(合同、身份证)必须用私有桶 `Limit: true`
144
- - ✅ 公有桶 URL 可缓存到前端,私有桶临时 URL 每次重新生成
145
- - ✅ 删除数据时同步清理 HDFS 文件(避免存储泄漏)
146
- - ✅ Excel/PDF 等导出文件通过【响应文件】配置返回,不要拼接到 JSON 数据里
147
-
148
- ### 安全升级兼容
149
-
150
- - 旧页面如果只传 `FilePathName` 获取私有地址,升级后普通帐号会失败;必须补齐 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`,不能改回匿名或放开管理权限。
151
- - 旧自定义上传若依赖普通用户设置 `Limit:false` 或任意多级 `Path`,应改成私有上传;公开资源改走受控发布流程。
152
- - 历史公有文件不会自动变成私有文件。迁移时先复制对象、验证私有读取,再停止旧公有访问;数据库继续保存租户内相对路径,不能持久化临时 URL。
153
- - 上线验收至少覆盖:普通角色跨菜单/跨记录/跨字段读取被拒绝、单文件/单请求/数量限制、帐号与租户配额、Redis 故障失败关闭、多节点并发、公有匿名 `200`、私有匿名 `403`、授权私有访问 `200`。
154
-
155
- ### 复盘:ZIP 发布端与安装端 SHA256 口径不一致
156
-
157
- - 触发场景:应用包已成功生成 ZIP 和摘要,但安装端下载 ZIP 后调用不存在的哈希辅助函数,或尝试通过当前 V8 环境不可用的 `.NET SHA256.Create()` 校验,导致安装中断。
158
- - 根因:发布端实际使用 `V8.EncryptHelper.Sha256Hex(FileByteBase64)`,安装端却按原始字节设计了另一套实现,函数名称、输入数据和运行时能力均未对齐。
159
- - 通用规则:文件摘要必须在包清单中记录算法和输入口径;当前应用 ZIP 统一使用 `SHA256-Base64Text`,发布端与安装端都对同一份 Base64 文本调用 `V8.EncryptHelper.Sha256Hex`。禁止仅凭函数名推测算法,也不要在未验证 V8 互操作能力时直接实例化 `.NET` 加密对象。
160
- - 自动化检查:生成同时包含源码 ZIP、编译 ZIP 的应用包,再从官方地址下载并安装;分别篡改 Base64 文本和摘要,正常包应安装成功,两个篡改包都必须在解压前被拒绝。
161
- <!-- /microi-progressive:chunk -->
17
+
18
+ 当文件上传控件开启【Office 在线预览】、【允许在线编辑】和【开启 Office 文件版本号】时,前后端必须遵循统一版本规则:
19
+
20
+ - 新上传的 Office 文件(`pdf/doc/docx/xls/xlsx/ppt/pptx`)要立即写入初始版本 `v1.0.0`,字段 JSON 的 `Path` 指向该原始文件,`Version` 为 `v1.0.0`,`Versions[0]` 保存同一份原始文件路径。
21
+ - 用户进入 OnlyOffice 在线编辑页后,每次手动点击【保存文件】才生成新版本;第一次保存生成 `v1.0.1`,之后依次生成 `v1.0.2`、`v1.0.3`。
22
+ - 未开启版本号时,保存文件直接覆盖当前 `Path` 对应的 HDFS/OSS 源文件。
23
+ - 开启版本号时,保存文件必须生成带版本号后缀的新文件,例如 `contract_v1.0.1.docx`,字段 JSON 的 `Path` 指向最新版本,`Versions` 保留历史版本路径。
24
+ - 在线 Office 路由和文件上传字段都要能读取 `Versions`,用于右上角切换历史版本预览/编辑。
25
+
26
+ ### OnlyOffice 服务端取文件与匿名预览规则
27
+
28
+ - OnlyOffice 文档服务器会在服务端再次下载文件。浏览器可以下载但 OnlyOffice 提示“下载失败”时,优先检查生成地址是否为 `localhost/127.0.0.1/内网域名`。
29
+ - OnlyOffice 可能先对文档地址发起 `HEAD` 探测。响应文件接口除了 `GET 200`,还必须让 `HEAD` 返回相同的 `Content-Type/Content-Length/Content-Disposition`,不能返回 `405`。
30
+ - 私有文件在线预览调用 `/apiengine/platform-private-file-url` 时传 `ForOfficePreview:true` 和权威资源上下文。审计代理应优先使用租户系统配置的公网 `ApiBase`,但仍把真实对象存储签名地址保存在共享 Redis ticket 中,禁止直接返回真实签名地址;旧 Controller 路由只作兼容转发。
31
+ - `/online-office` 可以匿名访问。公有存储模式只允许当前 `OsClient` 目录下的 `filePathName`;接口模式通过 `fileUrl` 接收当前平台正式 `ApiBase`,或由同端口本地后端读取的 loopback `/apiengine/...` 响应文件地址,并要求 URL 显式携带当前 `OsClient`。两种模式都拒绝跨租户、路径穿越和任意第三方 URL;`isPrivate=1` 必须登录。
32
+ - 匿名接口引擎预览不要求 V8 代码先上传 HDFS:接口开启【响应文件】和【允许匿名】后,完整地址 URL 编码传给 `fileUrl`,同时传 `fileName/fileType`。页面通过匿名安全中转让当前后端限域读取源文件,并以确定性路径缓存到当前租户公有对象存储;OnlyOffice 只接收公网 `FileServer` 地址。loopback 仅允许同端口本地后端访问,禁止开放任意 URL 代理。
33
+ - `canEdit` 只是前端请求参数,不是权限。最终编辑条件必须是“有效登录态 && canEdit=true”;匿名始终只读,不能因 URL 参数放开编辑。
34
+ - 匿名预览页隐藏左侧菜单、顶部导航和页签;已登录用户保持原系统布局。
35
+ - 匿名导出响应接口应配置频率限制或保证生成逻辑足够轻量,不能让单个公网 URL 无界消耗 CPU/内存;如业务仍需要落盘缓存,缓存事实必须进入共享 Redis/HDFS,不能用进程内变量。
36
+
37
+ 字段 JSON 示例:
38
+
39
+ ```json
40
+ {
41
+ "Name": "contract.docx",
42
+ "Path": "/tenant_demo/file/20260622/contract_v1.0.1.docx",
43
+ "Version": "v1.0.1",
44
+ "Versions": [
45
+ { "Version": "v1.0.0", "Name": "contract.docx", "Path": "/tenant_demo/file/20260622/contract.docx", "IsLatest": false },
46
+ { "Version": "v1.0.1", "Name": "contract_v1.0.1.docx", "Path": "/tenant_demo/file/20260622/contract_v1.0.1.docx", "IsLatest": true }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ <!-- /microi-progressive:chunk -->
52
+ <!-- microi-progressive:chunk id=v8-file-upload-009 sha256=1cc3ff1b6a5c756d957819f198c13ea181143db484527d1931aefc8e0d52993c -->
53
+ ## ImgUpload / FileUpload 字段值兼容规则
54
+
55
+ `ImgUpload` 不能假设只是一种值结构。PC 表单、移动端、旧数据、单图/多图、公开/私有桶会混合出现以下格式:
56
+
57
+ | 场景 | 可能的值 |
58
+ |------|----------|
59
+ | 空值/占位 | `''`、`null`、`undefined`、`'[]'`、`'null'`、`'正在上传中...'` |
60
+ | 旧单图 | `'/upload/xxx/a.png'`、`'https://cdn/a.png'` |
61
+ | 新单图 | `{ Path, Name, Size, Id, State }` 或 JSON 字符串 `'{"Path":"..."}'` |
62
+ | 多图 | `[{ Path, Name, Id, State }]` 或 JSON 字符串 `'[{"Path":"..."}]'` |
63
+ | 其它兼容字段 | `Path`、`FilePathName`、`FullPath`、`Url`、`url`、`src` |
64
+
65
+ 任何端(PC、uni-app、H5、小程序)渲染图片前都必须先做“归一化 -> 取 Path -> 转最终 URL”,不要直接 `JSON.parse` 后只处理数组,也不要直接把字段值拼到 `FileServer`。
66
+
67
+ 推荐归一化:
68
+
69
+ ```javascript
70
+ function normalizeUploadValue(value) {
71
+ if (value == null || value === '' || value === 'undefined' || value === 'null') return [];
72
+ if (value === '正在上传中...' || value === '[]' || value === '[ ]') return [];
73
+
74
+ var raw = value;
75
+ if (typeof raw === 'string') {
76
+ var s = raw.trim();
77
+ if ((s.indexOf('{') === 0 || s.indexOf('[') === 0)) {
78
+ try { raw = JSON.parse(s); } catch (e) { raw = s; }
79
+ } else {
80
+ raw = s;
81
+ }
82
+ }
83
+
84
+ if (Array.isArray(raw)) {
85
+ return raw.map(normalizeUploadItem).filter(function (it) { return !!it.Path; });
86
+ }
87
+
88
+ var one = normalizeUploadItem(raw);
89
+ return one.Path ? [one] : [];
90
+ }
91
+
92
+ function normalizeUploadItem(item) {
93
+ if (!item) return {};
94
+ if (typeof item === 'string') {
95
+ return { Path: item, Name: item.split('/').pop() || item, State: 1 };
96
+ }
97
+ if (typeof item === 'object') {
98
+ var path = item.Path || item.FilePathName || item.FullPath || item.Url || item.url || item.src || '';
99
+ return {
100
+ Id: item.Id || item.id || '',
101
+ Name: item.Name || item.FileName || item.name || (path ? String(path).split('/').pop() : ''),
102
+ Size: item.Size || item.size || '',
103
+ CreateTime: item.CreateTime || item.createTime || '',
104
+ State: item.State == null ? 1 : item.State,
105
+ Path: path
106
+ };
107
+ }
108
+ return {};
109
+ }
110
+ ```
111
+
112
+ 公开图片 URL 解析原则与 `Microi.Client/src/utils/diy.common.js` 的 `GetServerPath` 一致:
113
+
114
+ ```javascript
115
+ function publicUploadUrl(path) {
116
+ if (!path) return '';
117
+ var s = String(path).trim();
118
+ if (!s || s === '正在上传中...') return '';
119
+ if (s.indexOf('.') === 0) return s; // ./static/img/loading.gif 等本地静态资源
120
+ if (/^(https?:|data:|blob:)/i.test(s)) return s; // 已经是最终 URL
121
+ if (s.indexOf('{') === 0 || s.indexOf('[') === 0) {
122
+ var list = normalizeUploadValue(s);
123
+ s = list.length ? list[0].Path : '';
124
+ }
125
+ if (!s) return '';
126
+ return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + s.replace(/^\/+/, '');
127
+ }
128
+ ```
129
+
130
+ 私有桶(`Limit === true`)不要拼 `FileServer`,必须把归一化后的 `Path` 传给 `V8.Method.GetPrivateFileUrl({ FilePathName: path })` 或后端签名接口换临时 URL。
131
+
132
+ 跨端保存时保留上传响应的实际 `Limit`(兼容布尔值、`1/0` 与对应字符串),只删除临时签名地址和本地预览状态。历史对象缺少该标记时按字段配置取址;已有私有标记优先于公有字段缺省值。单图字段兼容历史单元素数组,不应在字段值预处理阶段把数组直接清空。
133
+
134
+ <!-- /microi-progressive:chunk -->
135
+ <!-- microi-progressive:chunk id=v8-file-upload-010 sha256=229294a07a8db953219c35292db5558430addb576505695b9ce88a275eb46232 -->
136
+ ## 安全注意
137
+
138
+ - ❌ 不要让前端任意指定 `Path`(路径穿越风险),只允许后端固定路径
139
+ - ❌ 不要不校验文件类型 / 大小:根据 ContentType + 后缀双重校验
140
+ - ❌ 不要把持有 Token 当成私有文件授权;普通用户必须证明菜单、记录和字段引用关系
141
+ - ❌ 不要向普通角色开放文件列表、移动、重命名、删除、覆盖等管理接口;这些接口仅限 `Level >= 9999`
142
+ - ❌ 不要开启 UEditor `catchimage` 远程抓图;默认关闭,确需采集时另建带域名白名单、DNS/IP 校验、禁止跳转、超时和响应上限的受控接口
143
+ - ❌ 敏感文件(合同、身份证)必须用私有桶 `Limit: true`
144
+ - ✅ 公有桶 URL 可缓存到前端,私有桶临时 URL 每次重新生成
145
+ - ✅ 删除数据时同步清理 HDFS 文件(避免存储泄漏)
146
+ - ✅ Excel/PDF 等导出文件通过【响应文件】配置返回,不要拼接到 JSON 数据里
147
+
148
+ ### 安全升级兼容
149
+
150
+ - 旧页面如果只传 `FilePathName` 获取私有地址,升级后普通帐号会失败;必须补齐 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`,不能改回匿名或放开管理权限。
151
+ - 旧自定义上传若依赖普通用户设置 `Limit:false` 或任意多级 `Path`,应改成私有上传;公开资源改走受控发布流程。
152
+ - 历史公有文件不会自动变成私有文件。迁移时先复制对象、验证私有读取,再停止旧公有访问;数据库继续保存租户内相对路径,不能持久化临时 URL。
153
+ - 上线验收至少覆盖:普通角色跨菜单/跨记录/跨字段读取被拒绝、单文件/单请求/数量限制、帐号与租户配额、Redis 故障失败关闭、多节点并发、公有匿名 `200`、私有匿名 `403`、授权私有访问 `200`。
154
+
155
+ ### 复盘:ZIP 发布端与安装端 SHA256 口径不一致
156
+
157
+ - 触发场景:应用包已成功生成 ZIP 和摘要,但安装端下载 ZIP 后调用不存在的哈希辅助函数,或尝试通过当前 V8 环境不可用的 `.NET SHA256.Create()` 校验,导致安装中断。
158
+ - 根因:发布端实际使用 `V8.EncryptHelper.Sha256Hex(FileByteBase64)`,安装端却按原始字节设计了另一套实现,函数名称、输入数据和运行时能力均未对齐。
159
+ - 通用规则:文件摘要必须在包清单中记录算法和输入口径;当前应用 ZIP 统一使用 `SHA256-Base64Text`,发布端与安装端都对同一份 Base64 文本调用 `V8.EncryptHelper.Sha256Hex`。禁止仅凭函数名推测算法,也不要在未验证 V8 互操作能力时直接实例化 `.NET` 加密对象。
160
+ - 自动化检查:生成同时包含源码 ZIP、编译 ZIP 的应用包,再从官方地址下载并安装;分别篡改 Base64 文本和摘要,正常包应安装成功,两个篡改包都必须在解压前被拒绝。
161
+ <!-- /microi-progressive:chunk -->