@microi.net/cli 5.8.5 → 5.8.7
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.
- package/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/LICENSE +21 -21
- package/README.md +71 -71
- package/assets/build-meta.json +7 -7
- package/assets/feature-matrix.json +138 -138
- package/assets/logo.svg +4 -4
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/mcp-codex-stdio-adapter.js +0 -0
- package/scripts/mcp-server.js +1 -1
- package/scripts/microi-cli.js +66 -65
- package/scripts/microi-codex-broker.js +450 -450
- package/scripts/microi-codex-router.js +618 -618
- package/scripts/microi-skills.meta.json +384 -384
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +21 -21
- package/skills/README.md +287 -287
- package/skills/ai-engine/SKILL.md +269 -265
- package/skills/ai-engine/agents/openai.yaml +4 -4
- package/skills/ai-engine/references/ai-employees.md +48 -48
- package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
- package/skills/ai-platform-governance/SKILL.md +177 -177
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
- package/skills/app-store/SKILL.md +525 -525
- package/skills/app-store/agents/openai.yaml +4 -4
- package/skills/business-blueprint/SKILL.md +193 -193
- package/skills/datasource-engine/SKILL.md +93 -93
- package/skills/datasource-engine/agents/openai.yaml +4 -4
- package/skills/dos-orm/SKILL.md +97 -95
- package/skills/dos-orm/references/api-reference.md +229 -229
- package/skills/email-engine/SKILL.md +81 -81
- package/skills/email-engine/references/v8-email.md +34 -34
- package/skills/job-engine/SKILL.md +176 -176
- package/skills/job-engine/agents/openai.yaml +4 -4
- package/skills/message-notification/SKILL.md +156 -156
- package/skills/message-notification/agents/openai.yaml +5 -5
- package/skills/message-notification/references/contracts.md +102 -102
- package/skills/microi/SKILL.md +14 -14
- package/skills/microi-ai-app-auth.js +652 -652
- package/skills/microi-ai-application/SKILL.md +115 -115
- package/skills/microi-ai-application/agents/openai.yaml +4 -4
- package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
- package/skills/microi-client-frontend/SKILL.md +246 -244
- 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
- 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
- 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
- package/skills/microi-codex/SKILL.md +102 -100
- package/skills/microi-codex-installer/SKILL.md +231 -231
- package/skills/microi-codex-installer/agents/openai.yaml +7 -7
- package/skills/microi-datasource-mapping/SKILL.md +122 -122
- package/skills/microi-db-schema/SKILL.md +175 -175
- package/skills/microi-db-schema/agents/openai.yaml +4 -4
- package/skills/microi-db-schema/references/core-tables.md +695 -695
- package/skills/microi-db-schema/references/form-component-options.md +256 -256
- package/skills/microi-db-schema/references/schema-overview.md +202 -202
- package/skills/microi-db-schema/references/schema.md +646 -646
- package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
- package/skills/microi-deployment/SKILL.md +222 -220
- package/skills/microi-deployment/references/deployment-matrix.md +109 -109
- package/skills/microi-docs-coverage/SKILL.md +133 -133
- package/skills/microi-docs-coverage/references/capability-map.md +91 -91
- package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
- package/skills/microi-form-engine/SKILL.md +333 -333
- package/skills/microi-form-engine/references/component-catalog.md +218 -218
- package/skills/microi-form-engine/references/data-source-events.md +124 -124
- package/skills/microi-form-layout/SKILL.md +205 -205
- 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
- package/skills/microi-frontend-sdk/SKILL.md +194 -194
- 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
- package/skills/microi-left-right-layout/SKILL.md +141 -141
- package/skills/microi-microservice/SKILL.md +328 -326
- package/skills/microi-microservice/references/runtime-delivery.md +278 -278
- package/skills/microi-mobile-app-quality/SKILL.md +185 -185
- 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
- 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
- package/skills/microi-solution-quotation/SKILL.md +78 -78
- package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
- package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
- package/skills/microi-sso/SKILL.md +96 -90
- package/skills/microi-sso/references/acceptance.md +49 -49
- package/skills/microi-sso/references/configuration-and-security.md +53 -53
- package/skills/microi-sso/references/inbound.md +53 -53
- package/skills/microi-sso/references/outbound.md +39 -39
- package/skills/microi-system-delivery/SKILL.md +137 -137
- 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
- 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
- package/skills/microi-ui/SKILL.md +192 -192
- 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
- package/skills/microi-uniapp-frontend/SKILL.md +193 -193
- 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
- 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
- package/skills/microi.v8.js +1921 -1921
- package/skills/module-engine/SKILL.md +255 -255
- package/skills/module-engine/references/module-config.md +204 -204
- package/skills/ocr-engine/SKILL.md +113 -113
- package/skills/ocr-engine/agents/openai.yaml +4 -4
- package/skills/page-engine/SKILL.md +206 -206
- package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
- 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
- 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
- package/skills/performance-testing/SKILL.md +221 -221
- package/skills/playwright-e2e/SKILL.md +196 -196
- 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
- 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
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
- package/skills/print-engine/SKILL.md +259 -259
- package/skills/production-readonly-audit/SKILL.md +41 -41
- package/skills/report-engine/SKILL.md +71 -71
- package/skills/report-engine/agents/openai.yaml +4 -4
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
- package/skills/scripts/sync-embedded-skills.mjs +46 -46
- package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
- package/skills/search-engine/SKILL.md +75 -75
- package/skills/search-engine/agents/openai.yaml +4 -4
- package/skills/spider-engine/SKILL.md +190 -190
- package/skills/system-observability/SKILL.md +249 -246
- package/skills/system-observability/references/memory-incident-triage.md +77 -77
- package/skills/translate-engine/SKILL.md +140 -140
- package/skills/translate-engine/agents/openai.yaml +4 -4
- package/skills/ui-design/SKILL.md +223 -223
- package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
- package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
- package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
- package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
- package/skills/ui-design/references/design-pattern-library.md +184 -184
- package/skills/ui-design/references/mci-design-contract.md +163 -163
- package/skills/ui-design/references/motion-and-media.md +78 -78
- package/skills/ui-design/references/product-flow-recipes.md +94 -94
- 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
- package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/uniapp-mall-assets/SKILL.md +176 -176
- package/skills/unity-integration/SKILL.md +171 -171
- package/skills/unity-integration/agents/openai.yaml +4 -4
- package/skills/unity-integration/references/ai-app-delivery.md +119 -119
- package/skills/unity-integration/references/sdk-api.md +82 -82
- package/skills/unity-integration/references/toolbox-migration.md +66 -66
- package/skills/unity-integration/references/webgl-hosting.md +57 -57
- package/skills/v8-api-config/SKILL.md +388 -388
- package/skills/v8-cache-pattern/SKILL.md +312 -312
- package/skills/v8-crud-api/SKILL.md +178 -178
- 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
- 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
- package/skills/v8-debugging/SKILL.md +284 -284
- package/skills/v8-explorer-tree/SKILL.md +228 -228
- package/skills/v8-export-import/SKILL.md +219 -219
- 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
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
- 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
- package/skills/v8-file-upload/SKILL.md +284 -284
- 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
- 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
- package/skills/v8-formengine-http/SKILL.md +238 -238
- package/skills/v8-frontend-events/SKILL.md +180 -180
- package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
- package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
- package/skills/v8-http-integration/SKILL.md +182 -182
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
- 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
- package/skills/v8-image-processing/SKILL.md +190 -190
- package/skills/v8-image-processing/agents/openai.yaml +4 -4
- package/skills/v8-image-processing/references/api-reference.md +623 -623
- package/skills/v8-menu-buttons/SKILL.md +186 -186
- 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
- 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
- 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
- package/skills/v8-mongodb/SKILL.md +200 -200
- package/skills/v8-mq-mqtt/SKILL.md +176 -176
- package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
- package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
- package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
- package/skills/v8-security/SKILL.md +210 -210
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
- package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
- package/skills/v8-sql-query/SKILL.md +302 -302
- package/skills/v8-table-event/SKILL.md +176 -176
- 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
- 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
- package/skills/v8-tcp-integration/SKILL.md +147 -147
- package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
- package/skills/v8-template-engine/SKILL.md +167 -167
- package/skills/v8-utilities/SKILL.md +104 -104
- package/skills/v8-utilities/references/client-api-index.md +143 -143
- package/skills/v8-utilities/references/platform-http-routes.md +83 -83
- package/skills/v8-utilities/references/server-api-index.md +188 -188
- package/skills/v8-workflow/SKILL.md +252 -252
- 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
- package/skills/v8-workflow/references/workflow-configuration.md +49 -49
- package/skills/vision-engine/SKILL.md +160 -160
- package/skills/vision-engine/agents/openai.yaml +4 -4
- package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
- package/skills/workspace-conventions/SKILL.md +273 -273
- 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
- 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
- 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,263 +1,263 @@
|
|
|
1
|
-
# v8-file-upload 详细参考 1
|
|
2
|
-
|
|
3
|
-
> 按需读取;本文件由 SKILL.md 的原章节无损拆分。
|
|
4
|
-
|
|
5
|
-
<!-- microi-progressive:chunk id=v8-file-upload-005 sha256=71cebd2f6946be49188c430c146b9ee923ceb0719fbda4bd3701b40152732cae -->
|
|
6
|
-
## 公有桶 vs 私有桶
|
|
7
|
-
|
|
8
|
-
### 应用商城 ZIP
|
|
9
|
-
|
|
10
|
-
应用商城的 AI 应用/微服务资产禁止逐文件 Base64 持久化到数据库。源码/安装包场景可以生成 ZIP;真实在线编译目录优先使用下节的 MCP 流式发布。数据库只保存路径和校验元数据。
|
|
11
|
-
|
|
12
|
-
在线商城安装的后台任务只传 `StoreId/StoreApiBase/StoreOsClient` 等定位信息,不能复制整行、`Form/Row/Btn` 或 `AppPakcet`。兼容旧 Base64 包的导入器必须按片限制真实上传数量/体积,片间靠已提交的 `AppId + FilePath + Hash` 复用;上传后统计大小优先使用包内 `Size/Sha256`,禁止再次 `FromBase64String` 构造完整字节数组。
|
|
13
|
-
|
|
14
|
-
```javascript
|
|
15
|
-
var zipResult = V8.Method.CreateZip({
|
|
16
|
-
Entries: [
|
|
17
|
-
{ Path: 'index.html', Content: '<html></html>' },
|
|
18
|
-
{ Path: 'assets/app.js', FileByteBase64: jsBase64 }
|
|
19
|
-
],
|
|
20
|
-
MaxFileCount: 20000,
|
|
21
|
-
MaxEntryBytes: 268435456,
|
|
22
|
-
MaxTotalBytes: 2147483648
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
var extractResult = V8.Method.ExtractZip({
|
|
26
|
-
FileByteBase64: zipBase64,
|
|
27
|
-
MaxFileCount: 20000,
|
|
28
|
-
MaxCompressionRatio: 200
|
|
29
|
-
});
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`System.IO` 在 Jint 沙箱中被禁止,不能在 V8 代码里直接构造 `MemoryStream/ZipArchive`;必须使用以上受控方法。
|
|
33
|
-
|
|
34
|
-
### 安装后的私有源码完整性
|
|
35
|
-
|
|
36
|
-
私有源码安装还须把旧日期/时间戳上传对象复制到完整清单摘要对应的活动目录,精确保存原始
|
|
37
|
-
`FilePath` 的大小写、中文与空格。复制后校验目标的 `Data.Sha256` 与 `Data.Size`,再写当前
|
|
38
|
-
文件元数据和最终应用指针;保留旧对象,不用 `MoveObject` 或删除旧源。复制 ACK 丢失时只
|
|
39
|
-
通过目标原始字节摘要回读确认,不盲目重传;目标已存在但摘要不同、权限不足或能力缺失均
|
|
40
|
-
停止。复用文件的验签和路径修正也计入资产分片预算,不能只对新上传文件计数。完成安装后
|
|
41
|
-
源码 ZIP 导出前后按完整原始文件路径、摘要和大小核对 `store-` 活动根的清单承诺,部分旧根
|
|
42
|
-
或漂移拒绝输出;ZIP 中兼容去掉 `source/` 包装目录不改变安装根的原始路径摘要。
|
|
43
|
-
仍须下载原生源码 ZIP 并逐文件验签,不用对象摘要代替 ZIP 实际返回字节的独立验证。
|
|
44
|
-
|
|
45
|
-
大型 UTF8 源码可复用既有 `V8.Method.GetPrivateFileText` 与
|
|
46
|
-
`V8.EncryptHelper.Sha256Hex/V8.Base64.StringToBase64`,避免 CLR byte[] 逐字节复制到 Jint。
|
|
47
|
-
仅在字符串按 UTF8 的摘要与权威原字节摘要一致时采用,保留 BOM、换行并核对编码长度;
|
|
48
|
-
二进制或有损解码仍使用原字节路径。文本原子存在却读取失败/异常时失败关闭,不换路径
|
|
49
|
-
掩盖存储故障。旧宿主缺少文本或编码能力才沿用旧路径,最终同一 ZIP 的实际字节验签保持。
|
|
50
|
-
|
|
51
|
-
### 已提交应用的 CDN 特殊字符别名修复(强制)
|
|
52
|
-
|
|
53
|
-
- 区分逻辑路径、对象键和 URL:原始 `@` 对象的 URL 可写 `%40`;字面量 `%40` 对象的 URL 需写 `%2540`。禁止在 HDFS 全局反解码对象键,禁止迁移或覆盖协议 v3 不可变编码对象、完整性标记及清单摘要。
|
|
54
|
-
- 此类修复使用现有受管接口引擎 `ai_app_cdn_backfill` 的 `Action=RepairCommittedCdnAliases`,不是任意文件复制接口。当前租户超级管理员仍须满足 `Level >= 9999`,精确匹配 `AppId/ConfirmAppId`;只补充已提交公有运行资产的原始 `@` 别名,不接收文件体、桶、URL 或自定义来源/目标路径。
|
|
55
|
-
- 先回读商城及完整版本记录,显式冻结 `ExpectedAppKey/ExpectedVersionId/ExpectedVersionNo`、`ExpectedRequestId/ExpectedRequestFingerprint/ExpectedRuntimeManifestHash`、`ExpectedCommittedPublishVersionId/ExpectedCommittedRuntimeManifestHash`、`ExpectedCurrentVersion/ExpectedPublishFence/ExpectedPublishRowVersion`。后三个数值使用规范十进制字符串。服务端须同时核对活动指针、协议、版本归属、版本 `FencingToken`、派生 `ReleasePrefix` 及完整清单;不得用调用方传入的清单替代权威记录。
|
|
56
|
-
- 商城与版本状态只接受 `ProjectionPending/RepairRequired/Completed`。完整运行清单按平台 ordinal 路径顺序,以 `Path<TAB>Sha256<TAB>Size`、行间 LF、末尾无 LF、UTF-8 重算 SHA-256;后续每次回读还须保持冻结清单字符串不变。指针、活动版本、栅栏、行版本或清单变化即停止,先核对部分副作用,再重新取得契约,不能盲目替换预期值续跑。
|
|
57
|
-
- 仅接受 `assets/<bundle>/import/<两位十六进制>/<文件名>@<子资源>.json` 的非入口、公有运行 JSON,每个不超过 1 MiB;拒绝源码、私有资源、百分号转义、绝对路径、目录穿越和不支持的 `@` 路径。来源为同版本目录中把原始 `@` 替换成字面量 `%40` 的既有对象;目标为版本目录及固定目录的原始 `@` 对象。全部使用当前租户内对象路径和 `Limit:false`,不能用 HTTPS 来源 URL 代替。
|
|
58
|
-
- `DryRun` 默认 `true`,显式 `false` 才复制。`AliasStart` 是冻结清单内符合条件的原始 `@` JSON 列表的零基游标,`AliasCount` 只允许 1–5;每资产最多复制版本和固定两个别名,一批最多 10 次复制。写入前预检整批来源和已有目标:`Sha256` 是原始物理字节摘要,与 `Size` 一同绑定清单;`WireSha256` 是字节转为 Base64 文本后的历史兼容摘要,只校验格式及来源、目标一致,禁止将它与清单原始摘要作相等判断。目标同字节跳过,任一不同即整批拒绝。每次复制前后重新核对摘要及最新绑定。
|
|
59
|
-
- `V8.Method.CopyObject` 没有原子的 create-if-absent/禁止覆盖保证,此动作必须如实返回 `AtomicCreateOnly:false`。预检查存在检查与复制之间的竞态,不能与另一发布者或任意写入者竞争同一固定路径;原后台投影可继续写同一冻结版本的相同字节。不得把“先查不存在再复制”描述为原子 create-only。
|
|
60
|
-
- 每批先 DryRun,再用同一绑定复制,保存 `Attempted/Copied/RequiresReadback/NextAliasStart`,从版本与固定 CDN 独立核对实际字节后再续批。超时、未知结果或 `Code=0` 先回读已尝试路径,不能盲重试;数据库事务无法回滚 OSS 副作用。禁止删除旧对象、重新上传字节、HTTP 回退或直接修改数据库状态。
|
|
61
|
-
- 修复不刷新 CDN、不推进指针、不修改清单或发布状态。原后台必须继续完成刷新、公开回读与 `Completed` 状态收敛;不能以对象复制成功代替公网资源及真实浏览器验收。操作说明见 [分布式存储](https://microi.net/doc/more/hdfs.html)。
|
|
62
|
-
|
|
63
|
-
### AI 应用编译目录流式发布(首选)
|
|
64
|
-
|
|
65
|
-
发布 Web、UniApp、MicroService 的 `dist` / H5 编译目录时,必须优先使用 `microi_publish_application_directory_stream`,不要把每个文件读成 Base64 后传给 `ai_app_build`、`microi_publish_microservice` 或普通 JSON 接口。旧工具仅为小文件兼容保留。
|
|
66
|
-
|
|
67
|
-
标准流程:
|
|
68
|
-
|
|
69
|
-
1. 先运行不带 `confirmExecution` 的预检。MCP 按流计算 SHA-256,拒绝符号链接、`.git`、`node_modules`、密钥/`.env`、路径穿越、超过 20000 个文件或超过 20 GB 的垃圾目录;默认不发布 `.map`。
|
|
70
|
-
2. 确认后把 `confirmExecution` 精确设为 `appIdOrKey`。每个文件通过 multipart 原始流进入 `/api/V8Engine/UploadApplicationAssetStream`,不构造整文件 `Buffer`、Base64 或 JSON 文件体。
|
|
71
|
-
3. 文件只写不可变版本目录。全部成功后,清单确认接口只接收 `Path/Sha256/Size`,由 HDFS Provider 的 `CopyObject` 在服务端复制到 root 与 `latest`;非入口先复制,入口最后复制。
|
|
72
|
-
4. 历史版本 URL 保留语义版本;分享/在线使用 URL 使用不含版本号的 root 稳定地址。重试必须复用同一版本与摘要,不能覆盖已有但缺少完整性证明的历史对象。
|
|
73
|
-
5. 该控制面只允许当前 Token 租户的 `Level >= 9999` 交互式管理员;访问密钥会话不得发布。单文件、HTTP/Multipart 和每日额度仍然生效,不能把“使用流”理解成无限上传。
|
|
74
|
-
|
|
75
|
-
几十 MB **不是** Jint 或 HDFS 的固定上限。旧链路失败的原因是二进制先膨胀为约 `4/3` 的 Base64,又在 JSON、Jint 字符串、.NET 字符串/字节数组之间产生多份累计分配;文件数量、并发和当前进程内存共同决定触发点。描述问题时必须明确“旧 Base64/Jint 发布链路的累计分配”,不得写成“几十 MB 就达到 Jint 硬上限”。HDFS 上传本身应走二进制流。
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"appIdOrKey": "flower-store",
|
|
80
|
-
"versionNo": "v1.2.0",
|
|
81
|
-
"directory": "D:/build/flower-store/dist",
|
|
82
|
-
"entryPath": "index.html",
|
|
83
|
-
"changeSummary": "修复移动端布局",
|
|
84
|
-
"confirmExecution": "flower-store"
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
底层断点式单文件工具是 `microi_upload_application_asset_stream`。除诊断或精确恢复单文件外,不要只调用它而遗漏最终清单确认,否则稳定入口不会切换。
|
|
89
|
-
|
|
90
|
-
| 类型 | `Limit` | 访问 URL | 用途 |
|
|
91
|
-
|------|---------|---------|------|
|
|
92
|
-
| 公有桶 | `false` | 直接拼接 `V8.SysConfig.FileServer + Path` | 产品图、Banner、公开文档 |
|
|
93
|
-
| 私有桶 | `true` | 必须用 `V8.Method.GetPrivateFileUrl` 获取临时 URL | 合同、身份证、敏感数据 |
|
|
94
|
-
|
|
95
|
-
### 表单字段存储策略
|
|
96
|
-
|
|
97
|
-
- 表单设计器的“禁止匿名访问”是服务端权威字段配置,不是管理员等级开关:未勾选即 `Limit=false`,勾选即 `Limit=true`。有当前表单新增/编辑权限的普通用户也可以按公有字段写入公有桶。
|
|
98
|
-
- PC 图片、文件、富文本上传提交 `FormEngineKey + FieldId + SysMenuId`;编辑记录附加 `FormDataId`,TableChild 附加父子授权链。后端校验动作权限并回读当前租户 `diy_field.Config` 后覆盖客户端 `Limit/Path`。客户端篡改 `Limit=false`、选择其它字段或提交任意目录都不能获得公有写入。
|
|
99
|
-
- 无字段上下文的普通上传默认保持私有;管理员可通过普通系统设置 `HdfsUploadRules` 按真实角色授权业务目录,并显式允许公有文件。后端 v8.2.9+ 支持 `*`、`**`、`?`、字符集合/范围/排除和花括号候选,实际路径仍先做保留目录与穿越校验。`ImgUpload/FileUpload` 老字段未保存 `Limit` 时兼容公有,`RichText` 缺失配置时默认私有。微信待审图片以及裁剪/压缩原图始终私有。
|
|
100
|
-
- 验收使用“普通用户 + 超级管理员 × 公有字段 + 私有字段 × 新增 + 编辑”矩阵,同时核对响应 `Limit`、实际对象桶、公有匿名读取与私有签名读取。单独用 HTTP 请求传 `Limit=false` 不能证明字段策略正确。
|
|
101
|
-
|
|
102
|
-
### 默认 MinIO 桶名与安装验收
|
|
103
|
-
|
|
104
|
-
- Microi 一键安装的默认私有桶固定为 `mci-private`,默认公有桶固定为 `mci-public`;禁止使用 `mci-publish` 等近似名称。
|
|
105
|
-
- `MinIOEndPointInternet` / `MinIOPrivateEndPoint` 同时兼容 `host:port` 与 `http(s)://host:port`。Provider 必须先归一化为 Host、Port、UseSsl,再调用 MinIO SDK 的 host/port 重载;不得把包含协议的整串 URL 直接作为 hostname,否则会出现 `Invalid URI: The hostname could not be parsed.`。显式 URL 的协议优先于历史 SSL 开关;端点禁止携带用户名密码、桶路径、查询或片段。
|
|
106
|
-
- 安装脚本创建 `mci-public` 后必须设置匿名下载权限,并把 `HDFS=MinIO`、内外网端点、AccessKey/SecretKey、`MinIOPrivateBucketName=mci-private`、`MinIOPublicBucketName=mci-public` 同步写入当前租户的 `sys_osclients`。
|
|
107
|
-
- 安装脚本还必须同步当前有效 `sys_config`:`ApiBase` 使用对外可访问的 API 端口,`FileServer` 使用 `http://<访问IP>:<MinIO API端口>/mci-public`。`ApiBase` 不能误用 Web 前端端口,因为 V8 代码会直接在其后拼接 `/api/...` 或 `/apiengine/...`。
|
|
108
|
-
- 安装验收必须使用真实登录 Token 和两个权威字段分别执行一次公有、私有上传:公有字段的对象匿名访问应返回 `200`,私有字段对象匿名访问应返回 `403`,私有文件通过授权签名 URL 访问应返回 `200`,并核对下载内容与上传内容一致。超级管理员可另用直接上传验证桶初始化;未命中管理员公有目录授权规则时,普通用户只传 `Limit=false` 的裸请求应继续落入私有桶。
|
|
109
|
-
|
|
110
|
-
### 复盘:签名 HEAD 被代理转换为 GET 导致上传后回读误报
|
|
111
|
-
|
|
112
|
-
- 触发场景:`PutObject` 已返回成功、对象可通过 GET 下载,但公有桶和私有桶的上传后 `StatObject` 均对桶根路径返回 `AccessDenied`;常见于启用严格回读校验后,MinIO Endpoint 前的 Nginx 同时启用了缓存与默认的 `proxy_cache_convert_head on`。
|
|
113
|
-
- 根因判断:S3 SigV4 的签名包含 HTTP 方法;代理把客户端签名的 HEAD 转为上游 GET 后会造成签名不一致。另一个可能原因是对象级凭据缺少桶级 `ListBucket` / `GetBucketLocation` 权限,因此不能只凭 `AccessDenied /bucket/` 推断对象未落盘,也不能把空 Region 当作唯一原因。
|
|
114
|
-
- 通用规则:优先在 MinIO 代理位置设置 `proxy_cache_convert_head off`;若仍使用缓存,缓存键需区分 `$request_method`。平台不得跳过上传后回读:当 HEAD/Stat 失败时,使用同一凭据生成签名 GET,并以 `Range: bytes=0-0` 回读;禁用重定向,非空对象必须同时验证期望总长度与首字节,空对象验证长度为零,`404` 判不存在,`403`、网络错误和证据不足继续失败关闭。禁止记录或返回带签名查询参数的 URL。
|
|
115
|
-
- 配置边界:只有实时回读证明 Endpoint、桶名、Region 或凭据确实错误时才修改 SaaS 配置;对象 GET 正常而仅 HEAD 失败时应修复代理或兼容回读路径,不能猜测内网地址、降低校验强度或轮换正常凭据。
|
|
116
|
-
- 自动化检查:覆盖签名 GET 的单字节 Range、期望总长度、首字节实际读取、空对象、长度不符、重定向、`403` 和 `404`;真实环境同时验证公有桶与私有桶的 `Put -> Range GET -> 内容一致`,并对比相同签名在 GET 与 HEAD 方法下的响应。
|
|
117
|
-
|
|
118
|
-
### 复盘:旧空库缺少可选字段导致 MinIO 初始化后中断
|
|
119
|
-
|
|
120
|
-
- 触发场景:MinIO 容器、私有桶和公有桶均已成功创建,但安装器更新 `sys_osclients` 时因旧库缺少 `NetworkIsInternet` 返回 `Unknown column`,整套安装停在 API 部署之前。
|
|
121
|
-
- 根因:安装器在 API/Upgrade 尚未启动时依赖了并非 MinIO 必需、且存量数据库不保证存在的旧可选字段;同时只按 `OsClient` 更新且没有写后回读。
|
|
122
|
-
- 通用规则:MinIO 安装前先校验真正必需的物理字段,并按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 唯一定位运行租户。内外网端点由 API 允许的启动项 `OsClientNetwork` 选择,安装器不得再写 `NetworkIsInternet`;配置更新后逐字段回读一致才继续。
|
|
123
|
-
- 恢复规则:桶初始化成功而配置写入失败不需要删除桶或数据卷。中断的新安装应先备份现有 Compose 并记录绑定数据目录,只对对应编排执行不带 `-v` 的 `docker compose down`,保留数据恢复点后再使用最新版脚本;禁止直接删除数据库或对象存储目录。
|
|
124
|
-
- 自动化检查:用一个不含 `NetworkIsInternet`、但包含 MinIO 必需字段的临时 MySQL 表执行 schema、唯一租户、UPDATE 和回读闭环;再插入重复三参数租户,断言安装器失败关闭且不批量覆盖。
|
|
125
|
-
|
|
126
|
-
### 复盘:MinIO 已可上传但系统设置仍指向官方地址
|
|
127
|
-
|
|
128
|
-
- 触发场景:一键安装和桶初始化均成功,用户手工上传也成功,但读取系统设置时发现 `ApiBase`、`FileServer` 仍是空库模板中的官方地址。
|
|
129
|
-
- 根因:安装流程只回写了 `sys_osclients` 的 MinIO 配置,没有同步前端和 V8 公共使用的 `sys_config` 地址字段。
|
|
130
|
-
- 通用规则:数据库还原并创建默认桶后,必须按安装模式选择的访问 IP 和动态端口同时回写有效 `sys_config.ApiBase/FileServer`;其中 `ApiBase` 指向 API 服务,`FileServer` 指向公有桶根地址。
|
|
131
|
-
- 自动化检查:安装完成后通过 `GetSysConfig` 回读两个字段,断言均使用本次访问 IP 和实际端口;再执行公有上传并使用 `FileServer + Path` 匿名下载,内容必须一致。
|
|
132
|
-
|
|
133
|
-
公开页面图片(首页 banner、商品主图、公开活动头像等)应返回公有 URL,例如 `V8.SysConfig.FileServer + Path`。不要把公有图片统一转成 `GetPrivateFileUrl` 的 `static-private` 签名地址;部分 H5/浏览器会因响应头或跨域策略触发 ORB/CORS 拦截,表现为 uni-app `<image>` 内层 `background-image: none`。
|
|
134
|
-
|
|
135
|
-
### `sys_user.Avatar` 固定使用私有桶
|
|
136
|
-
|
|
137
|
-
- `sys_user` 是内部系统用户表,`Avatar` 可能暴露员工身份信息,因此字段配置必须保持 `ImgUpload.Limit=true`,上传端也必须显式传 `Limit:true`;自定义用户管理页不能因为绕过表单引擎而回退到公有上传。
|
|
138
|
-
- 数据库继续只保存租户内相对路径,例如 `/tenant_demo/avatar/20240218/user.png`。历史公有头像迁移时,应把同一文件复制到私有桶的同一路径,确认私有对象可访问后再停用公有访问;不得为了迁移批量改写路径或制造重复日期目录。
|
|
139
|
-
- PC、移动端、聊天、工作流等任何页面渲染 `sys_user.Avatar` 时,禁止 `FileServer + Avatar`、`GetServerPath(Avatar)` 或把相对路径直接交给 `<img>`。前端应调用 `DiyCommon.GetUserAvatarUrl(avatar, userId)`,接口/V8 应调用 `V8.Method.GetPrivateFileUrl({ FilePathName: avatar })`,并为临时 URL 设置短期缓存与失败占位图。
|
|
140
|
-
- `ContactUserAvatar`、`FromUserAvatar`、`SenderAvatar` 等从 `sys_user.Avatar` 派生的快照字段同样按私有路径处理;消息数据只保存原始相对路径,不能把会过期的临时 URL 持久化到数据库。
|
|
141
|
-
|
|
142
|
-
#### 复盘:字段改私有后卡片仍访问公有桶
|
|
143
|
-
|
|
144
|
-
- 触发条件:把 `sys_user.Avatar` 改为 `Limit=true`,但模块卡片仍配置 `TableCardImgField=Avatar`。
|
|
145
|
-
- 根因:通用卡片渲染器只读取了字段值,没有读取图片字段的 `Config.ImgUpload.Limit`,仍统一调用 `GetServerPath/FileServer`。
|
|
146
|
-
- 修复规则:通用图片渲染器必须同时检查字段配置;私有字段先异步换取临时地址,`sys_user.Avatar` 还要有表名+字段名语义兜底,避免元数据缓存短暂陈旧时泄露到公有路径。
|
|
147
|
-
- 验收断言:筛选一条有头像的系统用户,页面中 `/tenant_demo/avatar/` 的直接公有请求数必须为 0,私有代理图片 `naturalWidth > 0`,并同时验证无头像占位图不报错。
|
|
148
|
-
|
|
149
|
-
定制移动端项目的 Hero、Banner、音频、视频、字体等大资源也应优先上传到目标租户公有 HDFS,再通过 FileServer/CDN 引用;小型导航图标和离线关键素材才保留在主包。上传前可适度压缩,但必须在多尺寸截图或试听/试播中确认质量,禁止以明显失真换取包体扫描通过。完整移动端规则见 `microi-uniapp-frontend/SKILL.md`。
|
|
150
|
-
|
|
151
|
-
后台 `ImgUpload` 字段通常保存相对路径或 JSON:接口返回给移动端前先解析出 `Path`,再按公私有场景转换 URL:
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
function publicFileUrl(path) {
|
|
155
|
-
if (!path) return '';
|
|
156
|
-
if (/^https?:/i.test(path)) return path;
|
|
157
|
-
return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '');
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### 私有桶临时 URL
|
|
162
|
-
|
|
163
|
-
```javascript
|
|
164
|
-
var url = V8.Method.GetPrivateFileUrl({
|
|
165
|
-
FilePathName: '/private/contract/2024/abc.pdf',
|
|
166
|
-
OsClient: V8.OsClient, // 可选,默认当前
|
|
167
|
-
Expires: 600 // 可选,过期秒数
|
|
168
|
-
});
|
|
169
|
-
// 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
- 普通客户端调用 `/apiengine/platform-private-file-url` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据;旧 Controller 路由仅作兼容转发,新代码禁止继续引用。
|
|
173
|
-
- `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
|
|
174
|
-
- 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
|
|
175
|
-
- 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
|
|
176
|
-
- 审计代理由平台后端回源对象存储,必须使用服务端内网端点生成上游地址,不能先生成公网 MinIO 签名地址再让后端绕公网回源。否则同一对象经内网上传成功后,可能在公网端点表现为 404。
|
|
177
|
-
- 备份包、安装包、导出包等重要大文件不能只信任 `PutObject` 成功响应;必须在写入后通过同一私有桶和内网端点执行 `ObjectExist` 回读(可行时再核对大小或 SHA-256),通过后才能把业务记录标记为完成。
|
|
178
|
-
- 平台任务若绕过 `V8.Method.Upload` 直接使用底层 `PutObject`,必须保证写入对象键与后续 `V8.Method.GetPrivateFileUrl` 的租户前缀规则一致。像 `/database-backups/` 这种服务端保留目录只能做严格白名单规范化,禁止对普通租户文件泛化去除 `/{OsClient}/` 隔离前缀。
|
|
179
|
-
- 下载验收至少对新签发地址执行一次 `Range: bytes=0-0`,断言返回 `200/206`、内容长度大于 0 且不是 JSON 错误;完整交付再核对下载字节数或 SHA-256。若产品要求保留当前业务页,应在新浏览上下文打开代理地址,并在浏览器拦截弹窗时给出品牌化反馈。
|
|
180
|
-
- 用户把私有链接转发给别人后,接收者没有有效登录身份时按“匿名访问”记录,禁止根据签发人猜测实际访问人;票据仍按原有效期失效。
|
|
181
|
-
- 代理创建或包装失败时必须失败关闭,不得退回未经审计的真实签名 URL;行为日志中也禁止保存真实签名 URL、Token、Authorization 或存储密钥。
|
|
182
|
-
- `Limit:false` 的公有文件允许通过 CDN/公有桶直接访问,不要求记录用户行为日志,也不要为了审计强制改走私有代理。
|
|
183
|
-
|
|
184
|
-
<!-- /microi-progressive:chunk -->
|
|
185
|
-
<!-- microi-progressive:chunk id=v8-file-upload-006 sha256=2dcf2dd7a571cc4757e9015cf213191be2dc3f041d91b0de51ed8ee00b0b86e7 -->
|
|
186
|
-
## 接口直接响应文件(下载/导出)
|
|
187
|
-
|
|
188
|
-
接口引擎需要在配置中开启【**响应文件**】选项,然后返回特殊结构:
|
|
189
|
-
|
|
190
|
-
平台后端会统一处理响应文件:图片和 PDF 自动 `inline` 在浏览器中打开,其它类型默认下载;PDF、PNG、JPEG、GIF、WebP、AVIF、BMP、TIFF、ICO、SVG 等常见可预览类型会自动校验文件头。V8 代码不要手写复杂的文件头判断,只需要保证 `ContentType` 与真实文件内容一致。
|
|
191
|
-
|
|
192
|
-
```javascript
|
|
193
|
-
// 模板:导出 Excel
|
|
194
|
-
var excelResult = V8.Office.ExportExcel({...});
|
|
195
|
-
return {
|
|
196
|
-
Code: 1,
|
|
197
|
-
Data: {
|
|
198
|
-
FileName: 'export_' + DateNow('yyyyMMdd_HHmmss') + '.xls',
|
|
199
|
-
ContentType: 'application/vnd.ms-excel',
|
|
200
|
-
FileByteBase64: System.Convert.ToBase64String(excelResult.Data)
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// 模板:返回图片
|
|
205
|
-
var resp = V8.Http.GetResponse({ Url: 'https://example.com/img.png' });
|
|
206
|
-
return {
|
|
207
|
-
Code: 1,
|
|
208
|
-
Data: {
|
|
209
|
-
FileName: 'img.png',
|
|
210
|
-
ContentType: 'image/png',
|
|
211
|
-
FileByteBase64: System.Convert.ToBase64String(resp.RawBytes)
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// 模板:返回 PDF(浏览器直接预览;后端自动校验 %PDF- 文件头)
|
|
216
|
-
var pdfResp = V8.Http.GetResponse({ Url: 'https://example.com/report.pdf' });
|
|
217
|
-
return {
|
|
218
|
-
Code: 1,
|
|
219
|
-
Data: {
|
|
220
|
-
FileName: 'report.pdf',
|
|
221
|
-
ContentType: 'application/pdf',
|
|
222
|
-
FileByteBase64: System.Convert.ToBase64String(pdfResp.RawBytes)
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
常用 ContentType:
|
|
228
|
-
- `application/vnd.ms-excel` / `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
|
|
229
|
-
- `application/pdf`
|
|
230
|
-
- `image/png` / `image/jpeg`
|
|
231
|
-
- `application/octet-stream`(通用二进制)
|
|
232
|
-
|
|
233
|
-
注意:如果远程系统返回的是错误页、登录页、业务容器格式(例如金蝶 PLM 电子仓 `KD_C_PLM`、或其它文件头不是 `%PDF-` 的伪 PDF),不要在 V8 里伪装成 PDF。后端会返回 JSON 错误,包含 `ExpectedFirstAscii`、`ActualFirstAscii`、`ActualFirstHex`、`Length`,按这些信息排查上游下载接口。
|
|
234
|
-
|
|
235
|
-
<!-- /microi-progressive:chunk -->
|
|
236
|
-
<!-- microi-progressive:chunk id=v8-file-upload-007 sha256=710eb73f616aff2ebce65a82568abe8208b701c1211a7e95ce500b1ce20b363f -->
|
|
237
|
-
## 通过 URL 列表批量下载并入库
|
|
238
|
-
|
|
239
|
-
```javascript
|
|
240
|
-
var urls = V8.Param.urls; // ['https://...', 'https://...']
|
|
241
|
-
var savedPaths = [];
|
|
242
|
-
for (var i = 0; i < urls.length; i++) {
|
|
243
|
-
try {
|
|
244
|
-
var resp = V8.Http.GetResponse({ Url: urls[i], Timeout: 30 });
|
|
245
|
-
if (resp.StatusCode !== 200) continue;
|
|
246
|
-
|
|
247
|
-
var base64 = System.Convert.ToBase64String(resp.RawBytes);
|
|
248
|
-
var fileName = V8.Method.NewGuid() + '.bin';
|
|
249
|
-
var up = V8.Method.Upload({
|
|
250
|
-
FilesByteBase64: { [fileName]: base64 },
|
|
251
|
-
Limit: false,
|
|
252
|
-
Path: '/batch-import/' + DateNow('yyyy-MM-dd'),
|
|
253
|
-
OsClient: V8.OsClient
|
|
254
|
-
});
|
|
255
|
-
if (up.Code === 1) savedPaths.push(up.Data[0].Path);
|
|
256
|
-
} catch (ex) {
|
|
257
|
-
console.error('第' + (i + 1) + '个下载失败:' + ex.message);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return { Code: 1, Data: savedPaths };
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
<!-- /microi-progressive:chunk -->
|
|
1
|
+
# v8-file-upload 详细参考 1
|
|
2
|
+
|
|
3
|
+
> 按需读取;本文件由 SKILL.md 的原章节无损拆分。
|
|
4
|
+
|
|
5
|
+
<!-- microi-progressive:chunk id=v8-file-upload-005 sha256=71cebd2f6946be49188c430c146b9ee923ceb0719fbda4bd3701b40152732cae -->
|
|
6
|
+
## 公有桶 vs 私有桶
|
|
7
|
+
|
|
8
|
+
### 应用商城 ZIP
|
|
9
|
+
|
|
10
|
+
应用商城的 AI 应用/微服务资产禁止逐文件 Base64 持久化到数据库。源码/安装包场景可以生成 ZIP;真实在线编译目录优先使用下节的 MCP 流式发布。数据库只保存路径和校验元数据。
|
|
11
|
+
|
|
12
|
+
在线商城安装的后台任务只传 `StoreId/StoreApiBase/StoreOsClient` 等定位信息,不能复制整行、`Form/Row/Btn` 或 `AppPakcet`。兼容旧 Base64 包的导入器必须按片限制真实上传数量/体积,片间靠已提交的 `AppId + FilePath + Hash` 复用;上传后统计大小优先使用包内 `Size/Sha256`,禁止再次 `FromBase64String` 构造完整字节数组。
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
var zipResult = V8.Method.CreateZip({
|
|
16
|
+
Entries: [
|
|
17
|
+
{ Path: 'index.html', Content: '<html></html>' },
|
|
18
|
+
{ Path: 'assets/app.js', FileByteBase64: jsBase64 }
|
|
19
|
+
],
|
|
20
|
+
MaxFileCount: 20000,
|
|
21
|
+
MaxEntryBytes: 268435456,
|
|
22
|
+
MaxTotalBytes: 2147483648
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
var extractResult = V8.Method.ExtractZip({
|
|
26
|
+
FileByteBase64: zipBase64,
|
|
27
|
+
MaxFileCount: 20000,
|
|
28
|
+
MaxCompressionRatio: 200
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`System.IO` 在 Jint 沙箱中被禁止,不能在 V8 代码里直接构造 `MemoryStream/ZipArchive`;必须使用以上受控方法。
|
|
33
|
+
|
|
34
|
+
### 安装后的私有源码完整性
|
|
35
|
+
|
|
36
|
+
私有源码安装还须把旧日期/时间戳上传对象复制到完整清单摘要对应的活动目录,精确保存原始
|
|
37
|
+
`FilePath` 的大小写、中文与空格。复制后校验目标的 `Data.Sha256` 与 `Data.Size`,再写当前
|
|
38
|
+
文件元数据和最终应用指针;保留旧对象,不用 `MoveObject` 或删除旧源。复制 ACK 丢失时只
|
|
39
|
+
通过目标原始字节摘要回读确认,不盲目重传;目标已存在但摘要不同、权限不足或能力缺失均
|
|
40
|
+
停止。复用文件的验签和路径修正也计入资产分片预算,不能只对新上传文件计数。完成安装后
|
|
41
|
+
源码 ZIP 导出前后按完整原始文件路径、摘要和大小核对 `store-` 活动根的清单承诺,部分旧根
|
|
42
|
+
或漂移拒绝输出;ZIP 中兼容去掉 `source/` 包装目录不改变安装根的原始路径摘要。
|
|
43
|
+
仍须下载原生源码 ZIP 并逐文件验签,不用对象摘要代替 ZIP 实际返回字节的独立验证。
|
|
44
|
+
|
|
45
|
+
大型 UTF8 源码可复用既有 `V8.Method.GetPrivateFileText` 与
|
|
46
|
+
`V8.EncryptHelper.Sha256Hex/V8.Base64.StringToBase64`,避免 CLR byte[] 逐字节复制到 Jint。
|
|
47
|
+
仅在字符串按 UTF8 的摘要与权威原字节摘要一致时采用,保留 BOM、换行并核对编码长度;
|
|
48
|
+
二进制或有损解码仍使用原字节路径。文本原子存在却读取失败/异常时失败关闭,不换路径
|
|
49
|
+
掩盖存储故障。旧宿主缺少文本或编码能力才沿用旧路径,最终同一 ZIP 的实际字节验签保持。
|
|
50
|
+
|
|
51
|
+
### 已提交应用的 CDN 特殊字符别名修复(强制)
|
|
52
|
+
|
|
53
|
+
- 区分逻辑路径、对象键和 URL:原始 `@` 对象的 URL 可写 `%40`;字面量 `%40` 对象的 URL 需写 `%2540`。禁止在 HDFS 全局反解码对象键,禁止迁移或覆盖协议 v3 不可变编码对象、完整性标记及清单摘要。
|
|
54
|
+
- 此类修复使用现有受管接口引擎 `ai_app_cdn_backfill` 的 `Action=RepairCommittedCdnAliases`,不是任意文件复制接口。当前租户超级管理员仍须满足 `Level >= 9999`,精确匹配 `AppId/ConfirmAppId`;只补充已提交公有运行资产的原始 `@` 别名,不接收文件体、桶、URL 或自定义来源/目标路径。
|
|
55
|
+
- 先回读商城及完整版本记录,显式冻结 `ExpectedAppKey/ExpectedVersionId/ExpectedVersionNo`、`ExpectedRequestId/ExpectedRequestFingerprint/ExpectedRuntimeManifestHash`、`ExpectedCommittedPublishVersionId/ExpectedCommittedRuntimeManifestHash`、`ExpectedCurrentVersion/ExpectedPublishFence/ExpectedPublishRowVersion`。后三个数值使用规范十进制字符串。服务端须同时核对活动指针、协议、版本归属、版本 `FencingToken`、派生 `ReleasePrefix` 及完整清单;不得用调用方传入的清单替代权威记录。
|
|
56
|
+
- 商城与版本状态只接受 `ProjectionPending/RepairRequired/Completed`。完整运行清单按平台 ordinal 路径顺序,以 `Path<TAB>Sha256<TAB>Size`、行间 LF、末尾无 LF、UTF-8 重算 SHA-256;后续每次回读还须保持冻结清单字符串不变。指针、活动版本、栅栏、行版本或清单变化即停止,先核对部分副作用,再重新取得契约,不能盲目替换预期值续跑。
|
|
57
|
+
- 仅接受 `assets/<bundle>/import/<两位十六进制>/<文件名>@<子资源>.json` 的非入口、公有运行 JSON,每个不超过 1 MiB;拒绝源码、私有资源、百分号转义、绝对路径、目录穿越和不支持的 `@` 路径。来源为同版本目录中把原始 `@` 替换成字面量 `%40` 的既有对象;目标为版本目录及固定目录的原始 `@` 对象。全部使用当前租户内对象路径和 `Limit:false`,不能用 HTTPS 来源 URL 代替。
|
|
58
|
+
- `DryRun` 默认 `true`,显式 `false` 才复制。`AliasStart` 是冻结清单内符合条件的原始 `@` JSON 列表的零基游标,`AliasCount` 只允许 1–5;每资产最多复制版本和固定两个别名,一批最多 10 次复制。写入前预检整批来源和已有目标:`Sha256` 是原始物理字节摘要,与 `Size` 一同绑定清单;`WireSha256` 是字节转为 Base64 文本后的历史兼容摘要,只校验格式及来源、目标一致,禁止将它与清单原始摘要作相等判断。目标同字节跳过,任一不同即整批拒绝。每次复制前后重新核对摘要及最新绑定。
|
|
59
|
+
- `V8.Method.CopyObject` 没有原子的 create-if-absent/禁止覆盖保证,此动作必须如实返回 `AtomicCreateOnly:false`。预检查存在检查与复制之间的竞态,不能与另一发布者或任意写入者竞争同一固定路径;原后台投影可继续写同一冻结版本的相同字节。不得把“先查不存在再复制”描述为原子 create-only。
|
|
60
|
+
- 每批先 DryRun,再用同一绑定复制,保存 `Attempted/Copied/RequiresReadback/NextAliasStart`,从版本与固定 CDN 独立核对实际字节后再续批。超时、未知结果或 `Code=0` 先回读已尝试路径,不能盲重试;数据库事务无法回滚 OSS 副作用。禁止删除旧对象、重新上传字节、HTTP 回退或直接修改数据库状态。
|
|
61
|
+
- 修复不刷新 CDN、不推进指针、不修改清单或发布状态。原后台必须继续完成刷新、公开回读与 `Completed` 状态收敛;不能以对象复制成功代替公网资源及真实浏览器验收。操作说明见 [分布式存储](https://microi.net/doc/more/hdfs.html)。
|
|
62
|
+
|
|
63
|
+
### AI 应用编译目录流式发布(首选)
|
|
64
|
+
|
|
65
|
+
发布 Web、UniApp、MicroService 的 `dist` / H5 编译目录时,必须优先使用 `microi_publish_application_directory_stream`,不要把每个文件读成 Base64 后传给 `ai_app_build`、`microi_publish_microservice` 或普通 JSON 接口。旧工具仅为小文件兼容保留。
|
|
66
|
+
|
|
67
|
+
标准流程:
|
|
68
|
+
|
|
69
|
+
1. 先运行不带 `confirmExecution` 的预检。MCP 按流计算 SHA-256,拒绝符号链接、`.git`、`node_modules`、密钥/`.env`、路径穿越、超过 20000 个文件或超过 20 GB 的垃圾目录;默认不发布 `.map`。
|
|
70
|
+
2. 确认后把 `confirmExecution` 精确设为 `appIdOrKey`。每个文件通过 multipart 原始流进入 `/api/V8Engine/UploadApplicationAssetStream`,不构造整文件 `Buffer`、Base64 或 JSON 文件体。
|
|
71
|
+
3. 文件只写不可变版本目录。全部成功后,清单确认接口只接收 `Path/Sha256/Size`,由 HDFS Provider 的 `CopyObject` 在服务端复制到 root 与 `latest`;非入口先复制,入口最后复制。
|
|
72
|
+
4. 历史版本 URL 保留语义版本;分享/在线使用 URL 使用不含版本号的 root 稳定地址。重试必须复用同一版本与摘要,不能覆盖已有但缺少完整性证明的历史对象。
|
|
73
|
+
5. 该控制面只允许当前 Token 租户的 `Level >= 9999` 交互式管理员;访问密钥会话不得发布。单文件、HTTP/Multipart 和每日额度仍然生效,不能把“使用流”理解成无限上传。
|
|
74
|
+
|
|
75
|
+
几十 MB **不是** Jint 或 HDFS 的固定上限。旧链路失败的原因是二进制先膨胀为约 `4/3` 的 Base64,又在 JSON、Jint 字符串、.NET 字符串/字节数组之间产生多份累计分配;文件数量、并发和当前进程内存共同决定触发点。描述问题时必须明确“旧 Base64/Jint 发布链路的累计分配”,不得写成“几十 MB 就达到 Jint 硬上限”。HDFS 上传本身应走二进制流。
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"appIdOrKey": "flower-store",
|
|
80
|
+
"versionNo": "v1.2.0",
|
|
81
|
+
"directory": "D:/build/flower-store/dist",
|
|
82
|
+
"entryPath": "index.html",
|
|
83
|
+
"changeSummary": "修复移动端布局",
|
|
84
|
+
"confirmExecution": "flower-store"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
底层断点式单文件工具是 `microi_upload_application_asset_stream`。除诊断或精确恢复单文件外,不要只调用它而遗漏最终清单确认,否则稳定入口不会切换。
|
|
89
|
+
|
|
90
|
+
| 类型 | `Limit` | 访问 URL | 用途 |
|
|
91
|
+
|------|---------|---------|------|
|
|
92
|
+
| 公有桶 | `false` | 直接拼接 `V8.SysConfig.FileServer + Path` | 产品图、Banner、公开文档 |
|
|
93
|
+
| 私有桶 | `true` | 必须用 `V8.Method.GetPrivateFileUrl` 获取临时 URL | 合同、身份证、敏感数据 |
|
|
94
|
+
|
|
95
|
+
### 表单字段存储策略
|
|
96
|
+
|
|
97
|
+
- 表单设计器的“禁止匿名访问”是服务端权威字段配置,不是管理员等级开关:未勾选即 `Limit=false`,勾选即 `Limit=true`。有当前表单新增/编辑权限的普通用户也可以按公有字段写入公有桶。
|
|
98
|
+
- PC 图片、文件、富文本上传提交 `FormEngineKey + FieldId + SysMenuId`;编辑记录附加 `FormDataId`,TableChild 附加父子授权链。后端校验动作权限并回读当前租户 `diy_field.Config` 后覆盖客户端 `Limit/Path`。客户端篡改 `Limit=false`、选择其它字段或提交任意目录都不能获得公有写入。
|
|
99
|
+
- 无字段上下文的普通上传默认保持私有;管理员可通过普通系统设置 `HdfsUploadRules` 按真实角色授权业务目录,并显式允许公有文件。后端 v8.2.9+ 支持 `*`、`**`、`?`、字符集合/范围/排除和花括号候选,实际路径仍先做保留目录与穿越校验。`ImgUpload/FileUpload` 老字段未保存 `Limit` 时兼容公有,`RichText` 缺失配置时默认私有。微信待审图片以及裁剪/压缩原图始终私有。
|
|
100
|
+
- 验收使用“普通用户 + 超级管理员 × 公有字段 + 私有字段 × 新增 + 编辑”矩阵,同时核对响应 `Limit`、实际对象桶、公有匿名读取与私有签名读取。单独用 HTTP 请求传 `Limit=false` 不能证明字段策略正确。
|
|
101
|
+
|
|
102
|
+
### 默认 MinIO 桶名与安装验收
|
|
103
|
+
|
|
104
|
+
- Microi 一键安装的默认私有桶固定为 `mci-private`,默认公有桶固定为 `mci-public`;禁止使用 `mci-publish` 等近似名称。
|
|
105
|
+
- `MinIOEndPointInternet` / `MinIOPrivateEndPoint` 同时兼容 `host:port` 与 `http(s)://host:port`。Provider 必须先归一化为 Host、Port、UseSsl,再调用 MinIO SDK 的 host/port 重载;不得把包含协议的整串 URL 直接作为 hostname,否则会出现 `Invalid URI: The hostname could not be parsed.`。显式 URL 的协议优先于历史 SSL 开关;端点禁止携带用户名密码、桶路径、查询或片段。
|
|
106
|
+
- 安装脚本创建 `mci-public` 后必须设置匿名下载权限,并把 `HDFS=MinIO`、内外网端点、AccessKey/SecretKey、`MinIOPrivateBucketName=mci-private`、`MinIOPublicBucketName=mci-public` 同步写入当前租户的 `sys_osclients`。
|
|
107
|
+
- 安装脚本还必须同步当前有效 `sys_config`:`ApiBase` 使用对外可访问的 API 端口,`FileServer` 使用 `http://<访问IP>:<MinIO API端口>/mci-public`。`ApiBase` 不能误用 Web 前端端口,因为 V8 代码会直接在其后拼接 `/api/...` 或 `/apiengine/...`。
|
|
108
|
+
- 安装验收必须使用真实登录 Token 和两个权威字段分别执行一次公有、私有上传:公有字段的对象匿名访问应返回 `200`,私有字段对象匿名访问应返回 `403`,私有文件通过授权签名 URL 访问应返回 `200`,并核对下载内容与上传内容一致。超级管理员可另用直接上传验证桶初始化;未命中管理员公有目录授权规则时,普通用户只传 `Limit=false` 的裸请求应继续落入私有桶。
|
|
109
|
+
|
|
110
|
+
### 复盘:签名 HEAD 被代理转换为 GET 导致上传后回读误报
|
|
111
|
+
|
|
112
|
+
- 触发场景:`PutObject` 已返回成功、对象可通过 GET 下载,但公有桶和私有桶的上传后 `StatObject` 均对桶根路径返回 `AccessDenied`;常见于启用严格回读校验后,MinIO Endpoint 前的 Nginx 同时启用了缓存与默认的 `proxy_cache_convert_head on`。
|
|
113
|
+
- 根因判断:S3 SigV4 的签名包含 HTTP 方法;代理把客户端签名的 HEAD 转为上游 GET 后会造成签名不一致。另一个可能原因是对象级凭据缺少桶级 `ListBucket` / `GetBucketLocation` 权限,因此不能只凭 `AccessDenied /bucket/` 推断对象未落盘,也不能把空 Region 当作唯一原因。
|
|
114
|
+
- 通用规则:优先在 MinIO 代理位置设置 `proxy_cache_convert_head off`;若仍使用缓存,缓存键需区分 `$request_method`。平台不得跳过上传后回读:当 HEAD/Stat 失败时,使用同一凭据生成签名 GET,并以 `Range: bytes=0-0` 回读;禁用重定向,非空对象必须同时验证期望总长度与首字节,空对象验证长度为零,`404` 判不存在,`403`、网络错误和证据不足继续失败关闭。禁止记录或返回带签名查询参数的 URL。
|
|
115
|
+
- 配置边界:只有实时回读证明 Endpoint、桶名、Region 或凭据确实错误时才修改 SaaS 配置;对象 GET 正常而仅 HEAD 失败时应修复代理或兼容回读路径,不能猜测内网地址、降低校验强度或轮换正常凭据。
|
|
116
|
+
- 自动化检查:覆盖签名 GET 的单字节 Range、期望总长度、首字节实际读取、空对象、长度不符、重定向、`403` 和 `404`;真实环境同时验证公有桶与私有桶的 `Put -> Range GET -> 内容一致`,并对比相同签名在 GET 与 HEAD 方法下的响应。
|
|
117
|
+
|
|
118
|
+
### 复盘:旧空库缺少可选字段导致 MinIO 初始化后中断
|
|
119
|
+
|
|
120
|
+
- 触发场景:MinIO 容器、私有桶和公有桶均已成功创建,但安装器更新 `sys_osclients` 时因旧库缺少 `NetworkIsInternet` 返回 `Unknown column`,整套安装停在 API 部署之前。
|
|
121
|
+
- 根因:安装器在 API/Upgrade 尚未启动时依赖了并非 MinIO 必需、且存量数据库不保证存在的旧可选字段;同时只按 `OsClient` 更新且没有写后回读。
|
|
122
|
+
- 通用规则:MinIO 安装前先校验真正必需的物理字段,并按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 唯一定位运行租户。内外网端点由 API 允许的启动项 `OsClientNetwork` 选择,安装器不得再写 `NetworkIsInternet`;配置更新后逐字段回读一致才继续。
|
|
123
|
+
- 恢复规则:桶初始化成功而配置写入失败不需要删除桶或数据卷。中断的新安装应先备份现有 Compose 并记录绑定数据目录,只对对应编排执行不带 `-v` 的 `docker compose down`,保留数据恢复点后再使用最新版脚本;禁止直接删除数据库或对象存储目录。
|
|
124
|
+
- 自动化检查:用一个不含 `NetworkIsInternet`、但包含 MinIO 必需字段的临时 MySQL 表执行 schema、唯一租户、UPDATE 和回读闭环;再插入重复三参数租户,断言安装器失败关闭且不批量覆盖。
|
|
125
|
+
|
|
126
|
+
### 复盘:MinIO 已可上传但系统设置仍指向官方地址
|
|
127
|
+
|
|
128
|
+
- 触发场景:一键安装和桶初始化均成功,用户手工上传也成功,但读取系统设置时发现 `ApiBase`、`FileServer` 仍是空库模板中的官方地址。
|
|
129
|
+
- 根因:安装流程只回写了 `sys_osclients` 的 MinIO 配置,没有同步前端和 V8 公共使用的 `sys_config` 地址字段。
|
|
130
|
+
- 通用规则:数据库还原并创建默认桶后,必须按安装模式选择的访问 IP 和动态端口同时回写有效 `sys_config.ApiBase/FileServer`;其中 `ApiBase` 指向 API 服务,`FileServer` 指向公有桶根地址。
|
|
131
|
+
- 自动化检查:安装完成后通过 `GetSysConfig` 回读两个字段,断言均使用本次访问 IP 和实际端口;再执行公有上传并使用 `FileServer + Path` 匿名下载,内容必须一致。
|
|
132
|
+
|
|
133
|
+
公开页面图片(首页 banner、商品主图、公开活动头像等)应返回公有 URL,例如 `V8.SysConfig.FileServer + Path`。不要把公有图片统一转成 `GetPrivateFileUrl` 的 `static-private` 签名地址;部分 H5/浏览器会因响应头或跨域策略触发 ORB/CORS 拦截,表现为 uni-app `<image>` 内层 `background-image: none`。
|
|
134
|
+
|
|
135
|
+
### `sys_user.Avatar` 固定使用私有桶
|
|
136
|
+
|
|
137
|
+
- `sys_user` 是内部系统用户表,`Avatar` 可能暴露员工身份信息,因此字段配置必须保持 `ImgUpload.Limit=true`,上传端也必须显式传 `Limit:true`;自定义用户管理页不能因为绕过表单引擎而回退到公有上传。
|
|
138
|
+
- 数据库继续只保存租户内相对路径,例如 `/tenant_demo/avatar/20240218/user.png`。历史公有头像迁移时,应把同一文件复制到私有桶的同一路径,确认私有对象可访问后再停用公有访问;不得为了迁移批量改写路径或制造重复日期目录。
|
|
139
|
+
- PC、移动端、聊天、工作流等任何页面渲染 `sys_user.Avatar` 时,禁止 `FileServer + Avatar`、`GetServerPath(Avatar)` 或把相对路径直接交给 `<img>`。前端应调用 `DiyCommon.GetUserAvatarUrl(avatar, userId)`,接口/V8 应调用 `V8.Method.GetPrivateFileUrl({ FilePathName: avatar })`,并为临时 URL 设置短期缓存与失败占位图。
|
|
140
|
+
- `ContactUserAvatar`、`FromUserAvatar`、`SenderAvatar` 等从 `sys_user.Avatar` 派生的快照字段同样按私有路径处理;消息数据只保存原始相对路径,不能把会过期的临时 URL 持久化到数据库。
|
|
141
|
+
|
|
142
|
+
#### 复盘:字段改私有后卡片仍访问公有桶
|
|
143
|
+
|
|
144
|
+
- 触发条件:把 `sys_user.Avatar` 改为 `Limit=true`,但模块卡片仍配置 `TableCardImgField=Avatar`。
|
|
145
|
+
- 根因:通用卡片渲染器只读取了字段值,没有读取图片字段的 `Config.ImgUpload.Limit`,仍统一调用 `GetServerPath/FileServer`。
|
|
146
|
+
- 修复规则:通用图片渲染器必须同时检查字段配置;私有字段先异步换取临时地址,`sys_user.Avatar` 还要有表名+字段名语义兜底,避免元数据缓存短暂陈旧时泄露到公有路径。
|
|
147
|
+
- 验收断言:筛选一条有头像的系统用户,页面中 `/tenant_demo/avatar/` 的直接公有请求数必须为 0,私有代理图片 `naturalWidth > 0`,并同时验证无头像占位图不报错。
|
|
148
|
+
|
|
149
|
+
定制移动端项目的 Hero、Banner、音频、视频、字体等大资源也应优先上传到目标租户公有 HDFS,再通过 FileServer/CDN 引用;小型导航图标和离线关键素材才保留在主包。上传前可适度压缩,但必须在多尺寸截图或试听/试播中确认质量,禁止以明显失真换取包体扫描通过。完整移动端规则见 `microi-uniapp-frontend/SKILL.md`。
|
|
150
|
+
|
|
151
|
+
后台 `ImgUpload` 字段通常保存相对路径或 JSON:接口返回给移动端前先解析出 `Path`,再按公私有场景转换 URL:
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
function publicFileUrl(path) {
|
|
155
|
+
if (!path) return '';
|
|
156
|
+
if (/^https?:/i.test(path)) return path;
|
|
157
|
+
return String(V8.SysConfig.FileServer || '').replace(/\/+$/, '') + '/' + String(path).replace(/^\/+/, '');
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 私有桶临时 URL
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
var url = V8.Method.GetPrivateFileUrl({
|
|
165
|
+
FilePathName: '/private/contract/2024/abc.pdf',
|
|
166
|
+
OsClient: V8.OsClient, // 可选,默认当前
|
|
167
|
+
Expires: 600 // 可选,过期秒数
|
|
168
|
+
});
|
|
169
|
+
// 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- 普通客户端调用 `/apiengine/platform-private-file-url` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据;旧 Controller 路由仅作兼容转发,新代码禁止继续引用。
|
|
173
|
+
- `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
|
|
174
|
+
- 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
|
|
175
|
+
- 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
|
|
176
|
+
- 审计代理由平台后端回源对象存储,必须使用服务端内网端点生成上游地址,不能先生成公网 MinIO 签名地址再让后端绕公网回源。否则同一对象经内网上传成功后,可能在公网端点表现为 404。
|
|
177
|
+
- 备份包、安装包、导出包等重要大文件不能只信任 `PutObject` 成功响应;必须在写入后通过同一私有桶和内网端点执行 `ObjectExist` 回读(可行时再核对大小或 SHA-256),通过后才能把业务记录标记为完成。
|
|
178
|
+
- 平台任务若绕过 `V8.Method.Upload` 直接使用底层 `PutObject`,必须保证写入对象键与后续 `V8.Method.GetPrivateFileUrl` 的租户前缀规则一致。像 `/database-backups/` 这种服务端保留目录只能做严格白名单规范化,禁止对普通租户文件泛化去除 `/{OsClient}/` 隔离前缀。
|
|
179
|
+
- 下载验收至少对新签发地址执行一次 `Range: bytes=0-0`,断言返回 `200/206`、内容长度大于 0 且不是 JSON 错误;完整交付再核对下载字节数或 SHA-256。若产品要求保留当前业务页,应在新浏览上下文打开代理地址,并在浏览器拦截弹窗时给出品牌化反馈。
|
|
180
|
+
- 用户把私有链接转发给别人后,接收者没有有效登录身份时按“匿名访问”记录,禁止根据签发人猜测实际访问人;票据仍按原有效期失效。
|
|
181
|
+
- 代理创建或包装失败时必须失败关闭,不得退回未经审计的真实签名 URL;行为日志中也禁止保存真实签名 URL、Token、Authorization 或存储密钥。
|
|
182
|
+
- `Limit:false` 的公有文件允许通过 CDN/公有桶直接访问,不要求记录用户行为日志,也不要为了审计强制改走私有代理。
|
|
183
|
+
|
|
184
|
+
<!-- /microi-progressive:chunk -->
|
|
185
|
+
<!-- microi-progressive:chunk id=v8-file-upload-006 sha256=2dcf2dd7a571cc4757e9015cf213191be2dc3f041d91b0de51ed8ee00b0b86e7 -->
|
|
186
|
+
## 接口直接响应文件(下载/导出)
|
|
187
|
+
|
|
188
|
+
接口引擎需要在配置中开启【**响应文件**】选项,然后返回特殊结构:
|
|
189
|
+
|
|
190
|
+
平台后端会统一处理响应文件:图片和 PDF 自动 `inline` 在浏览器中打开,其它类型默认下载;PDF、PNG、JPEG、GIF、WebP、AVIF、BMP、TIFF、ICO、SVG 等常见可预览类型会自动校验文件头。V8 代码不要手写复杂的文件头判断,只需要保证 `ContentType` 与真实文件内容一致。
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
// 模板:导出 Excel
|
|
194
|
+
var excelResult = V8.Office.ExportExcel({...});
|
|
195
|
+
return {
|
|
196
|
+
Code: 1,
|
|
197
|
+
Data: {
|
|
198
|
+
FileName: 'export_' + DateNow('yyyyMMdd_HHmmss') + '.xls',
|
|
199
|
+
ContentType: 'application/vnd.ms-excel',
|
|
200
|
+
FileByteBase64: System.Convert.ToBase64String(excelResult.Data)
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// 模板:返回图片
|
|
205
|
+
var resp = V8.Http.GetResponse({ Url: 'https://example.com/img.png' });
|
|
206
|
+
return {
|
|
207
|
+
Code: 1,
|
|
208
|
+
Data: {
|
|
209
|
+
FileName: 'img.png',
|
|
210
|
+
ContentType: 'image/png',
|
|
211
|
+
FileByteBase64: System.Convert.ToBase64String(resp.RawBytes)
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// 模板:返回 PDF(浏览器直接预览;后端自动校验 %PDF- 文件头)
|
|
216
|
+
var pdfResp = V8.Http.GetResponse({ Url: 'https://example.com/report.pdf' });
|
|
217
|
+
return {
|
|
218
|
+
Code: 1,
|
|
219
|
+
Data: {
|
|
220
|
+
FileName: 'report.pdf',
|
|
221
|
+
ContentType: 'application/pdf',
|
|
222
|
+
FileByteBase64: System.Convert.ToBase64String(pdfResp.RawBytes)
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
常用 ContentType:
|
|
228
|
+
- `application/vnd.ms-excel` / `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
|
|
229
|
+
- `application/pdf`
|
|
230
|
+
- `image/png` / `image/jpeg`
|
|
231
|
+
- `application/octet-stream`(通用二进制)
|
|
232
|
+
|
|
233
|
+
注意:如果远程系统返回的是错误页、登录页、业务容器格式(例如金蝶 PLM 电子仓 `KD_C_PLM`、或其它文件头不是 `%PDF-` 的伪 PDF),不要在 V8 里伪装成 PDF。后端会返回 JSON 错误,包含 `ExpectedFirstAscii`、`ActualFirstAscii`、`ActualFirstHex`、`Length`,按这些信息排查上游下载接口。
|
|
234
|
+
|
|
235
|
+
<!-- /microi-progressive:chunk -->
|
|
236
|
+
<!-- microi-progressive:chunk id=v8-file-upload-007 sha256=710eb73f616aff2ebce65a82568abe8208b701c1211a7e95ce500b1ce20b363f -->
|
|
237
|
+
## 通过 URL 列表批量下载并入库
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
var urls = V8.Param.urls; // ['https://...', 'https://...']
|
|
241
|
+
var savedPaths = [];
|
|
242
|
+
for (var i = 0; i < urls.length; i++) {
|
|
243
|
+
try {
|
|
244
|
+
var resp = V8.Http.GetResponse({ Url: urls[i], Timeout: 30 });
|
|
245
|
+
if (resp.StatusCode !== 200) continue;
|
|
246
|
+
|
|
247
|
+
var base64 = System.Convert.ToBase64String(resp.RawBytes);
|
|
248
|
+
var fileName = V8.Method.NewGuid() + '.bin';
|
|
249
|
+
var up = V8.Method.Upload({
|
|
250
|
+
FilesByteBase64: { [fileName]: base64 },
|
|
251
|
+
Limit: false,
|
|
252
|
+
Path: '/batch-import/' + DateNow('yyyy-MM-dd'),
|
|
253
|
+
OsClient: V8.OsClient
|
|
254
|
+
});
|
|
255
|
+
if (up.Code === 1) savedPaths.push(up.Data[0].Path);
|
|
256
|
+
} catch (ex) {
|
|
257
|
+
console.error('第' + (i + 1) + '个下载失败:' + ex.message);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { Code: 1, Data: savedPaths };
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
<!-- /microi-progressive:chunk -->
|