@microi.net/cli 6.1.9 → 6.2.1

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.
@@ -1,279 +1,284 @@
1
- ---
2
- name: v8-file-upload
3
- description: Microi V8 与 MCP 文件上传下载指南。用于流式发布、私有文件、HDFS/OSS/MinIO/S3,以及 OSS/CDN 费用治理、FileServerBak 热点自动复制与持续 NAS 分流、实际消费验收。
4
- ---
5
-
6
- > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
-
8
- # Microi V8 文件上传下载
9
-
10
- ## OSS / CDN 费用与 NAS 分流入口
11
-
12
- 用户要求降低 OSS/CDN 流量费用、把高下载模型/JS/图片/视频转到 NAS,或询问
13
- “1 GiB / 24 小时配置在哪里”时,使用已有热点分流能力。先读
14
- [热点分流操作流程](references/hotspot-offload-operations.md),再核对本文的原生权限和依赖边界。
15
- 系统设置 `/system-config` 的 `FileServerBakThresholdGiB`、`FileServerBakWindowHours`
16
- 控制完整日流量资格;`FileServerBakSizeMiB` 控制独立单文件大小资格,需原生能力回读。
17
- `FileServerBak` 是备用 HTTPS origin。可信达标后低流量不撤回 NAS 偏好。
18
- 只复制、保存配置或发布页面都不等于实际入口已走 NAS,也不等于完整日费用已下降。
19
-
20
- ## 源码仓库资源隔离(默认强制)
21
-
22
- 截图、配图、PDF/PPT、音视频、压缩包、模型、安装包和其它大体积交付资源统一通过 MCP 上传 CDN,禁止提交到 Gitee/GitHub 源码历史,即使压缩到小于 1 MB 也不能把截图或附件改放源码仓库。官方资源使用已校验的 `microi_itdos`(`https://api.microi.net + OsClient=iTdos`)公有桶与 `https://file.microi.net` 前缀;私有资料按租户实际权限保持私有。源码只存 URL、字节数、SHA-256、MIME 等轻量清单。上传后独立回读实际字节和类型,PDF 验证 Range;超过 Base64 协议容量使用流式/分片工具。暂存区及每个待推送提交都须排除资源本体,详细门禁遵循 [工作区默认 CDN 规范](../workspace-conventions/SKILL.md#截图与大体积资源必须走-cdn默认强制)。
23
-
24
- 你正在为 Microi 吾码平台编写文件上传/下载/返回相关代码。平台分布式存储(HDFS)支持阿里云OSS、MinIO、亚马逊S3,存储方案由 SaaS 引擎按租户配置。上传对象的 `Content-Type` 由框架按实际对象路径统一确定,覆盖 HTML、JS/MJS、CSS、JSON/MAP、SVG、字体与 WASM,未知后缀保留 `application/octet-stream`。MinIO、S3、OSS 普通与分片上传使用同一映射;该映射不替代上传权限、内容和配额校验。旧对象不会因后端升级自动改变元数据:先核对字节数和 SHA-256,再修正类型或按正式应用发布流程产生新版本。不要把源码与公有编译产物混在一起,也不要关闭 `nosniff` 掩盖类型错误。
25
-
26
- 文件与发布测试默认使用当前 MCP 连接下、当前租户实际生效的 HDFS Provider;先只读核实提供商与对象范围,沿已授权隔离前缀执行公私权限、原始字节、哈希、MIME 与写后回读断言。已确定当前环境后,不再为其它 OSS/MinIO/S3 反复询问或新建存储;仅明确的提供商缺陷、迁移或兼容任务才扩展矩阵,并逐项报告真实执行与未覆盖范围。提供商不可用或断言失败必须保留真实错误,不能换提供商或省略回读来冒充通过。
27
-
28
- 公开入口覆盖 `V8.uploadFile`、多文件 `V8.uploadFiles` 与 MCP `microi_upload_file_base64`。多文件上传必须限制并发、逐文件返回结果;Base64 工具只接受明确文件名、大小和租户内目标范围,写后回读路径、大小与哈希。
29
-
30
- 公开运行资源的备用地址只通过 `V8.Method.GetPublicResourceBackupUrl({FilePathName:'tenant/micro-app/app/index.html'})` 只读选择,返回 `{Code:1,Data:{UseBackup,Url}}`。只允许 `FilePathName`,不能传 URL、Ready、SHA、流量、凭据或用户身份;当前公开应用、完整版本清单、签名副本与 NAS 健康必须同时匹配。`UseBackup=false`、异常或旧后端缺少该能力时继续原公开地址,不把 `FileServerBak` 非空或目录存在当作可用副本;该方法不复制对象、不读取私有文件,也不授予管理权限。管理和迁移仍走已有受信任应用接口,并分别回读副本完整性与真实消费结果。
31
-
32
- ## MCP stdio 与完整包的传输边界
33
-
34
- ### 普通本机文件上传与备用公私目标
35
-
36
- - 先回读 `microi_get_status.PrivateFileUploadProtocol` 或 `microi_manage_file_stream_upload action=Capabilities`;支持值为 `PrivateFileUpload/1`。工具已安装不等于 API 已升级,旧 API 不支持时失败关闭,不能改走公有应用发布。
37
- - `microi_upload_file_stream.localFilePath` 是 MCP 客户端电脑上的普通文件,由客户端读取原始字节、固定 16 MiB 分片;32 GiB 只是协议硬上限,实际仍受 SaaS FileUploadMaxFileMB/MaxRequestMB、DisableFileUpload 与共享日用户/租户额度约束。首次写入分片走现有 UploadQuota 原子额度,确认分片重放不重复预留,失败不退款。不要与旧 `microi_import_external_attachment.sourcePath` 混淆:后者是 API 服务账号能访问的服务器绝对路径/UNC,目标仍是主 HDFS。
38
- - `target` 默认 `Primary`,`Primary` / `BackupPrivate` 均为私有;显式 `BackupPublic` 只把最终文件写入当前租户 SaaS MinIO 公桶。两种备用目标都核对 `FileServerBak` 公网绑定,暂存私桶不同于公桶且只读策略检查通过;不改 HDFS/桶策略,不允许自由 Endpoint/Bucket/凭据。只接受交互式平台管理员,禁止访问密钥会话。
39
- - 只有用户明确授权选定文件可由所有人访问时才使用 `BackupPublic`,必须 `publishPublic=true`、`confirmExecution="PUBLIC:" + requestId`;私有目标确认仍是原 requestId。没有公开意图的旧调用保持私有;图库/人脸/其它私密资料不随示例公开。公开会话分片、签名清单和回执始终私有,目标和公开意图绑定签名,不能把既有私有会话换目标公开。
40
- - 该入口对 MinIO 检查桶策略、OSS 检查私有 ACL 与桶策略;S3 尚缺完整私有 ACL 证明,`StorageProviderSupported=false` 时不上传。Capabilities 仅展示协议/配置,不代替实际桶权限验证。32 GiB 协议格式不放宽普通上传绝对 2 GiB 单文件/总请求保护。
41
- - 稳定 `requestId` 固定原始 SHA-256/大小/每片哈希、操作者、租户及存储绑定;不确定结果先 Status 后同键续传。MCP 本机检查点不含凭据;跨客户端带原回执 `expectedStorageBindingHash`。不能取新 binding 伪装旧会话恢复。
42
- - 最终返回 Completed 前服务端合并并回读原始字节 SHA-256;独立验收调用 Verify,不拿 ETag、200、元数据或上传结束代替原片完整性。会话清单/块/回执始终私有并 HMAC 绑定精确对象键,底层使用既有 HDFS 流、原生鉴权和 renewable lease,不经过 V8 大对象。
43
- - 未完成会话有效期 7 天,Abort 只清理拥有的 staging;完成自动清理失败显示 CleanupComplete=false,重复 Complete/Abort 可补清。不得删除用户最终原片。签名清单/终态回执保留审计;暂存磁盘有界内存流式,最终合并需要与原片等量的临时空间。
44
- - BackupPrivate 回执的 FilePathName 不能当主 HDFS 路径交给旧 GetPrivateFileUrl。没有备用私有下载票据能力时仅交付后端 Verify 证据,不能拼匿名 URL 或改公桶。当前公开热点分流仍只处理公开资源,与本协议隔离。
45
- - `BackupPublic` 完成后客户端执行 `ProbePublic`:全量存储 Verify + 可信 FileServerBak 对象匿名 HEAD/首段 Range 校验,成功才返回 PublicUrl;独立 Complete 仅完成存储,没有匿名链接承诺。匿名范围最多16MiB,不冒称全文件已匿名下载;服务端全文件 SHA 与匿名可用性证据分别记录。无 Cookie/凭据/重定向,大文件错误200只读头拒绝,不下载全片。探测失败 Code0 仍保留 Completed/字节证据,沿原 requestId 或原 sessionId/binding 执行 ProbePublic 重试,不换请求号重复上传。管理 Complete 的公开确认是 `PUBLIC:` + sessionId,Abort 仍原 sessionId且只清理自有暂存。
46
-
47
- - 标准 MCP 服务的 stdio 使用固定 `128 MiB`(`134217728` 字节)未解析 JSON-RPC 缓冲,超过边界立即拒绝并关闭连接;不读取动态环境变量放大。SDK 调用端若仍使用默认 `10 MiB` 缓冲,大响应仍可能关闭客户端,必须核对调用端支持的真实边界。
48
- - 该限制计算 UTF-8 wire 字节,包含完整 JSON 信封、Base64 和重复 `Data/Result` 正文。单个 Base64 字段的理论原始字节上限约 `96 MiB` 减信封,不能据此保证任意 `64 MiB` 包响应都通过,也不能把它当成 HDFS `256 MiB` 应用包能力的新上限。
49
- - 已存在的正规完整包协议仍可使用 `PreparedPersistPackageByteBase64`;先按精确正文计算编码和整个请求/响应字节数,保留版本、CAS、哈希、HDFS 回读及快照 finalize。连接关闭属于结果未知,先回读同版本、同请求身份,不能换请求键盲重传。
50
- - 超过 wire 能力的源码、运行资产优先使用已有目录/文件 stream 或分片协议及其
51
- 小型指针/回执,不把私有源码、超大资产常态化塞入 JSON/Jint;支持更大正文的其它
52
- 连接也须有明确边界。不能用增大stdio缓冲绕过能力鉴权、业务配额或包完整性检查。
53
-
54
- ## 表单字段的公有桶与私有桶(强制)
55
-
56
- - `Config.FileUpload.EnableRolePermission=true` 的字段强制私有上传,优先级高于字段和请求 `Limit`。
57
- 每个附件 `VisibleRoleIds` 多选真实角色,三个默认关闭的开关为 `HideUnauthorizedFiles`、
58
- `ShowUnauthorizedFileName`、`DisableRoleInheritance`。无权限时后端仅返回脱敏占位,不含 Path、
59
- Versions 或签名地址;普通保存必须保留无权附件,不能改角色、改名、删除或通过复制路径重新授权。
60
- 上传响应的 `_UploadProof` 绑定当前租户、用户、字段、记录、私有路径及有效期,只用于首次保存校验,
61
- 持久化时移除;新文件须携带当前记录/字段上下文上传。历史公有附件需重新私有上传后才能设置角色。
62
- 完整配置、继承与 MCP 流程见 `../microi-form-engine/SKILL.md`。
63
-
64
- ### 旧上传返回与接口引擎扩展
65
-
66
- - 系统设置 `CompatiblePlatformOldVersion` 默认关闭,空值/缺字段也关闭;开启时旧 HTTP 上传
67
- 保留 `Code/Data`,单文件也统一返回数组(包括 `Multiple=false`),补 `url/type/size/duration/uploading/progress/path/name/id`。
68
- 图片 `type=image`,名称去掉最后一个扩展名;`url` 必须沿用实际 `Url`,不能把私有文件改拼公有地址。
69
- - `/api/HDFS/upload`、`/api/Upload`、`/apiengine/platform-hdfs-upload` 使用当前租户上传引擎。
70
- 只有主库确认完整地址、别名及固定 Key 缺失才执行编译兜底;禁用、StopHttp、拒绝或异常不兜底。
71
- - `V8.Method.UploadCurrentRequestAsync()` 复用已验证的当前请求文件流,重复调用只上传一次;
72
- `V8.Method.IsLegacyUploadCompatibilityEnabled()` 读取宿主取得的开关,二者均不接受参数。
73
- 它们绑定当前租户与选定引擎,普通脚本或嵌套其它引擎不能借用;无请求时使用原有 `V8.Method.Upload`。
74
- - 返回编排由系统设置应用拥有的 Managed `platform-hdfs-upload` 实现;持久定制使用
75
- CreateIfMissing `platform-hdfs-upload-hook`,返回 `{Code:1, UploadResult:完整结果}`。
76
- 先用 `JSON.parse(JSON.stringify(V8.Param.Result))` 转为普通 JS 对象,再判断 `Data` 数组并遍历追加字段;不能直接对 CLR 包装对象使用 `Array.isArray` 或 `length` 分支。
77
- 系统设置及基础空库包都交付可空字段,但上传引擎只由系统设置包拥有;不携带租户开关值。
78
- - MCP 复用 `microi_add_field`、`microi_update_field`、`microi_create_engine`、
79
- `microi_save_engine_code` 和管理员回读;验收分别覆盖真实上传、配置缓存、引擎优先、缺失及错误分支。
80
-
81
- - `ImgUpload`、`FileUpload`、`RichText` 的“禁止匿名访问”是字段权威策略:`Limit=false` 写公有桶,`Limit=true` 写私有桶。普通用户只要通过当前菜单/表的新增或编辑动作授权,也必须按该字段配置执行;不得按用户等级把全部非超级管理员上传统一改成私有桶。
82
- - 浏览器上传必须携带 `FormEngineKey + FieldId + SysMenuId`,编辑已有记录再带 `FormDataId`,TableChild 再带父子授权上下文。后端先用 FormEngine 校验动作权限,再从当前租户回读 `diy_field.Component/Config`,用权威 `Limit` 覆盖请求值,并把目录固定为 `ImgUpload→img`、`FileUpload→file`、`RichText→editor`。
83
- - 客户端 `Limit`、`Path`、字段 Id 和菜单 Id 都只是待验证线索。没有可验证字段上下文的普通交互式上传默认私有并限制到安全一级目录;不能为了恢复公有字段语义而重新信任裸 `Limit=false`。
84
- - 兼容旧移动端/定制页面时,在普通系统设置 `sys_config.HdfsUploadRules` 配置目录与角色规则,无需逐个改客户端。`Path` 使用区分大小写的租户内相对路径;`RoleIds` 是真实角色 Id 数组,或显式启用 `AllAuthenticated`;`IncludeSubdirectories` 和 `AllowPublic` 默认关闭。只有后端有效角色命中且显式允许公有,才尊重请求 `Limit=false`。规则不是秘密,不必放入后端私有设置,修改权限与服务端判定必须严格受控。
85
- - 排查目录授权失败时先检查 multipart 是否同时提交 `Path` 和 `path`。客户端只应提交一个 `Path`;后端对相同值归一,对不同值拒绝,不能因重复字段误判而扩大 `HdfsUploadRules`。
86
- - 后端 v8.2.9+ 的配置 `Path` 支持 `*`(单层任意字符)、`**`(零到多层目录,须独占层级)、`?`、`[abc]`、`[a-z]`、`[!0-9]`/`[^0-9]`、`{a,b}` 候选及组合;优先 `files/{inspection,quality}/**` 这类最小业务前缀,不能为省事直接向全部用户配置 `** + AllowPublic`。只匹配请求目录,不匹配文件名或服务端追加的年月。实际上传路径不接受通配符;保留目录先于 glob 校验,不能用宽泛规则绕过。最多512字符/规则、4层花括号、32候选、4096令牌;有界动态规划和有界语法缓存不得缓存租户授权结果。先更新全部后端节点再配置新语法,精确旧规则仍兼容。
87
- - 目录规则只扩展无字段上下文上传,不绕过表/菜单/行权限、租户隔离、保留路径、文件类型、内容检测及配额;不能授予访问密钥会话、私有文件读取或应用发布权限。规则通过系统设置共享缓存读取,保存/删除后失效;应用包只发布字段和可空 DDL,禁止附带会覆盖租户规则的配置数据。验收覆盖授权/未授权角色、路径边界、公私桶、禁用用户、跨租户、规则撤销及缓存生效。
88
- - 老 `ImgUpload/FileUpload` 缺失 `Limit` 时兼容为公有;老 `RichText` 缺失上传配置时默认私有。微信待审图片与裁剪/压缩 `_origin` 原图始终私有,字段公有配置不能放宽这些特殊边界。客户端保存与预览必须以上传响应的实际 `Limit` 为准。
89
-
90
- ## 交互式图片默认压缩(强制)
91
-
92
- - `ImgUpload`、PC、UniApp/H5/小程序以及 MCP 普通图片上传在未显式传 `Preview` 时,必须按 `Preview=true` 处理;字段设计器新配置默认开启。只有业务明确要求公开原始画质时才允许显式关闭,不能把“客户端漏传”解释为关闭。
93
- - 默认展示图目标为不超过约 `500 KB`、最长边不超过 `1920 px`;可按真实用途进一步收紧。压缩必须使用 Windows/Linux/容器一致的跨平台实现,并限制输入字节、像素总量与解码并发,避免超大图触发 OOM。
94
- - 压缩流程固定为“先将原始字节写入当前租户 HDFS 私有桶的 `_origin` 对象 → 生成压缩展示图 → 写入目标公有/私有位置 → 回读大小与格式”。即使展示图是公有资源,原图也只能保存在私有桶。
95
- - 压缩、解码或编码失败时必须失败关闭,返回可诊断错误;禁止静默把几十 MB 的未压缩原图发布到公有桶。返回字段中的 `Size` 应表示展示图实际字节数,`OriginalSize` 可用于管理员审计,但业务字段不得保存私有桶真实地址或签名 URL。
96
- - 历史治理只迁移超过目标体积的图片:先为旧对象补齐私有原图副本,再上传压缩展示图、原子更新全部业务引用并逐条回读。确认没有引用后才删除旧公有对象;批量任务要有清单、检查点、幂等映射和失败重试,不能边扫描边不可逆覆盖。
97
-
98
- ## 表单引擎图片裁剪(强制)
99
-
100
- - `ImgUpload.Crop` 支持 `Enabled`、`Mode=free/fixed/select`、`Ratio`、`CustomWidth/CustomHeight`以及 `AllowZoom/AllowRotate/AllowFlip`。`Enabled` 只表示表单用户的默认状态,不得当作裁剪能力总开关;新增/编辑表单把裁剪开关合并到紧凑上传面板,旁边同时显示公有/私有桶、单/多图与最大数量、压缩状态和最大体积。旧字段未配置时该开关默认关闭,但用户仍可主动开启。
101
- - 裁剪弹层必须同时提供“取消本次上传”“不裁剪直接上传”“应用裁剪并上传”三个不同动作。多图选择按队列逐张处理;直接上传只跳过当前图片的裁剪,不能取消或阻塞后续图片。
102
- - 多图模式下后端可能为每个单文件请求返回 `Data: [{...}]`,单图模式返回 `Data: {...}`。PC/移动端必须先归一化对象/数组,再按客户端 `uid` 替换上传占位项并生成预览 URL,禁止把接口成功误显示成空列表。
103
- - 开启裁剪时,前端必须上传最终裁剪图,并在同一 multipart 请求中以 `MicroiOriginalFile` 附带同名、未改动的原图及 `CropEnabled=true`。不得仅传坐标后由后端重演,否则 EXIF 方向、旋转或镜像可能导致前后端结果不一致。
104
- - 后端必须校验裁剪图与原图一一同名,两者字节数都计入单次限制和每日配额,但原图不计为第二个业务文件。未宣告裁剪却传原图、宣告裁剪却漏传/错配原图都要失败关闭。
105
- - HDFS 写入顺序固定为“未改动原图写入私有 `_origin` 对象 → 可选压缩裁剪图 → 写入业务展示图”。裁剪原图写入失败时不得发布展示图;即使 `Preview=false`,裁剪原图仍必须私有保留。不得返回或持久化原图真实路径。
106
-
107
- ## 富文本图片、视频与附件(强制)
108
-
109
- - `RichText.Limit=false` 只用于需匿名长期访问的官网公告、商品详情等公开正文;内部内容用 `true`。普通用户经表单新增/编辑授权后也按后端回读到的该字段配置选择桶;仅修改请求中的 `Limit` 不能改变策略,客户端必须以上传响应的实际 `Limit` 为准。
110
- - RichText 分别配置 `Image`、`Video`、`File` 的 `Enabled/MaxSize/MaxCount`;图片另传 `Preview/CompressMaxSize/CompressMaxWidth`,并继续遵循“原图私有、展示图公有或私有”的压缩链路。附件类型白名单用 `File.Accept` 进一步收紧,不能放宽服务端白名单。
111
- - 私有正文持久化 `/__microi_richtext_private__/...` 稳定对象标识,严禁保存对象存储签名 URL、`OpenPrivateFile` Ticket、DiyToken 或其它会过期的凭据。每次打开记录时携带 `FormEngineKey/FormDataId/FieldId/SysMenuId` 批量换取短效审计代理地址。
112
- - 私有文件后端授权必须重新校验当前租户、菜单、表、行和 RichText 字段,并确认所请求路径精确存在于 `img/video/source.src` 或 `a.href`;普通上传字段的对象/数组只认 `Path/FilePath/FilePathName`,不得递归把 `Name/Size/Metadata` 等任意标量当作路径。未经当前租户 FileServer 主机权威校验的绝对 HTTP(S) URL 不得等价为本地对象 Key;正文文字、`data-src/data-href`、脚本标签和前缀相似路径都必须失败关闭。
113
- - 外部匿名页面没有后台记录权限上下文,不能解析私有标识。公开文章应由设计者把 RichText 字段配置为 `Limit=false`,有该表单新增/编辑权限的用户即可按权威配置发布;不得通过延长私有 URL 有效期模拟公开资源。
114
- - 若业务明确要求撤稿后旧媒体地址立即停止返回字节,使用私有对象和专用批准引用代理:只接受固定业务标识,权威校验当前租户、不可变批准版本与精确媒体引用,读取前后复核发布/许可/撤回状态,限制 MIME/体积并返回 `no-store`;不开放裸路径,不返回签名 URL 或重定向。普通文件短链接口不能代替这种业务撤权能力。
115
- - 撤回授权与稳定请求键待办先在共享数据库事务提交,再执行 HDFS 删除。未知结果沿用原请求键探查重放,原上传请求摘要保持。删除源对象不等于删除 CDN 缓存;历史公开链接必须有官方失效任务和独立缓存字节读回证据,否则保留未确认状态,不把 `Offline`、隐藏前台或源站 `404` 记作完整撤回。
116
-
117
- 官网客户端读取私有文件统一调用 `/apiengine/platform-private-file-url`,提交 `FilePathName` 或有界 `FilePathNames`,并按资源类型提供权威定位参数:普通表单字段使用 `FormEngineKey + FormDataId + FieldId + SysMenuId`;用户头像使用 `ResourceKind=UserAvatar + ResourceId=用户Id`;菜单/部门导入模板分别使用 `MenuImportTemplate`、`DeptImportTemplate` 与对应记录 Id。CAD 私有派生预览使用 `ResourceKind=FormFieldDerivedPreview`,除表单四元组外必须同时提交字段中保存的 `OriginalFilePathName` 和单个派生 `FilePathName`;后端只接受同目录同 basename 的 DWG→`_preview.dxf`、STEP/STP→`_preview.stl` 唯一映射,并在对象存在后签名。文件柜对象使用 `ResourceKind=FileManagerObject`,`ResourceId` 必须与单个 `FilePathName` 大小写精确相同,并提交能力探针返回的当前租户权威 `SysMenuId`;此类签名只允许平台超级管理员 DiyToken 会话,访问密钥和普通菜单用户一律拒绝。后端会从权威字段或对象存储重新读取并精确匹配路径;管理员也不能只传裸路径绕过对象引用,普通客户端禁止换取私有文件原始 Byte/Stream。旧 `/api/HDFS/GetPrivateFileUrl` 与 `/api/HDFS/MallFileUrl` 只保留令牌格式兼容并转发同一 Managed 接口,新代码不得继续引用。
118
-
119
- ## 已提交应用的 CDN 特殊字符别名修复(强制)
120
-
121
- 使用 i_app_cdn_backfill 的 RepairCommittedCdnAliases 前,必须读取[完整绑定、范围与并发边界](references/cdn-alias-repair.md)。保持默认 DryRun、原始字节哈希与 Base64 摘要分离,并独立回读实际 CDN 字节。
122
-
123
- ### 历史安装应用的持久别名检查点
124
-
125
- - `LegacyUnverified`、协议 2 的已安装微服务可能由后台补齐稳定入口。旧版每 30 秒扫描并反复全读版本与别名对象,会产生与浏览器/CDN 访问无关的 SDK 存储出流量;先按访问日志核对调用方、对象路径及周期,不能仅用 CDN 用量解释 OSS 公网账单。
126
- - 后端使用受保护 `mci_ai_app_version.BuildLog` 的独立两键对象 `{Contract:'InstalledAliasReconciliation/1',Checkpoint:{...}}`;检查点字段为 `AuthorityHash/Status/Attempt/StablePasses/UpdatedAtUtc/NextCheckAtUtc`,状态仅 `Verifying/Retrying/Verified`。未知非空日志或非法结构拒绝接管,禁止人工清空日志或伪造 `Verified` 来节流。
127
- - `AuthorityHash` 绑定规范化租户及完整权威应用、版本、服务、公开文件清单;只排除检查点自身,保留版本 `UpdateTime` 发现同版本重装。元数据变更使检查点立即失效,下一轮恢复全量校验。写入只条件更新所属版本的 `BuildLog`,不改 `UpdateTime`、`LegacyUnverified`、协议号、active/committed 指针或 V3 成功状态。
128
- - 初次成功后等待 5 分钟,第二次稳定成功后等待 30 分钟,第三次及以后每日复核;发生修复复制重新累计稳定次数。失败按 5/10/20/40/60 分钟退避并封顶。先持久化 `Verifying` 再读对象,硬重启仍遵守等待;保存失败不开始昂贵读取。全程持有共享发布租约,结果按主库完整权威和日志独立回读确认,响应未知不能盲重做。
129
- - 只读验收结合 `VerifiedCheckpointSkipped/RetryBackoffSkipped/NextCheckAtUtc` 与对象存储访问日志,覆盖等待内零重复读取、元数据变更、失败恢复和多个节点。需确认实际 API 节点已部署修复;文档、单测、镜像上传不等于生产节流生效,也不把检查点成功当成正式 V3 发布回执。
130
-
131
- <!-- microi-progressive:begin -->
132
- <!-- microi-progressive:chunk id=v8-file-upload-000 sha256=9402f7173710e8d110392f184119154a50187138ab7d698fce0b73b5f12123e2 -->
133
- ## 核心 API
134
-
135
- | API | 说明 |
136
- |-----|------|
137
- | `V8.FilesByteBase64` | 接收上传时携带的文件字典 `{ FileName: base64 }` |
138
- | `V8.Method.Upload({...})` | 服务端上传文件到 HDFS(推荐) |
139
- | `V8.Method.GetPrivateFileUrl({FilePathName})` | 生成私有桶临时访问 URL |
140
- | `V8.Method.CopyObject({FilePathName,Path,Limit})` | 当前租户同一桶内服务端复制,保留原对象;用于固定入口与版本快照 |
141
- | `V8.Method.ObjectExist({FilePathName,Limit})` | 检查当前租户公有/私有桶对象是否存在 |
142
- | `V8.Method.GetObjectSha256({FilePathName,Limit})` | 在服务端流式计算对象原始字节与旧版 Base64 文本的 SHA-256 和字节数;不把对象内容送入 V8 |
143
- | `V8.Method.ListObjects({Path,Limit,Recursive,Marker,MaxKeys})` | 分页列举当前租户前缀,单页最多 1000 个 |
144
- | `/apiengine/platform-private-file-url` | 官网 PC/UniApp 按菜单、记录、字段和对象引用换取私有文件短链 |
145
- | `V8.Http.GetResponse({Url}).RawBytes` | 下载远程文件为字节数组 |
146
- | 接口返回 `{ FileName, ContentType, FileByteBase64 }` | 接口直接响应文件 |
147
-
148
- 固定 CDN 应用回填优先使用服务端 `CopyObject`,公有桶复制编译资产、私有桶复制源码;`Limit` 在源与目标间保持一致,`Path` 和 `FilePathName` 均由后端收敛到当前租户。大对象用 `GetObjectSha256` 流式核对原对象和复制目标,公有体验路径仍须从 CDN 独立回读。历史版本目标已存在时须核对字节哈希,发现不同内容立即停止;固定根可在新版本验证后覆盖。`ListObjects` 必须分页并限制到单个应用前缀,不得把这些存储管理原子直接开放为匿名业务接口。大型私有文本的 `GetPrivateFileText` 字符串边界及原字节/最终ZIP验签规则见[已有详细参考](references/progressive-01-公有桶-vs-私有桶.md)。
149
-
150
- MinIO SDK 7 的复制签名不匹配还可能来自带参数 MIME:SDK 对源 `Content-Type` 签名,却在 HTTP 请求中额外保留 `StringContent` 的默认 MIME。先在独立夹具核对源 MIME、重复头和真实存储返回,再升级后端复制传输修复;不能去掉 `charset`、关闭签名校验或下载后重传来伪造通过。修复后必须同时验证公私桶原始字节、精确 MIME、源元数据及源对象保留,固定入口恢复另覆盖主库权威、真实共享租约、双节点竞争和新进程重跑。取消/超时后保持原请求键,先回读目标再决定是否继续。
151
-
152
- <!-- /microi-progressive:chunk -->
153
- <!-- microi-progressive:chunk id=v8-file-upload-001 sha256=d535a333639a9005f5d20f25e36e2753a11835380713c1bb063ae618e6cea4af -->
154
- ## 第三方数据库附件迁移
155
-
156
- 当第三方表只保存附件路径时,先用 `microi_inspect_external_database` / `microi_query_external_database` 或 `V8.Dbs.<DbKey>` 查询记录。`microi_import_external_attachment` 允许后端已确认的 `Level >= 9999` 当前用户直接提供 HTTP/HTTPS URL、API 节点可读的本机绝对路径或 UNC 路径。
157
-
158
- - 导入工具必须显式确认;HTTP、私网、重定向、本机和 UNC 均可访问,但最终能力受 API 服务进程账号、网络、磁盘及对象存储权限约束。
159
- - 下载与上传使用临时文件和文件流,不经过 Base64;不设固定 20/100 MB 上限,`MaxBytes=0` 或省略表示不设置 MCP 上限,可处理 200/500 MB 或更大文件。
160
- - 带签名参数或用户凭据的源 URL、鉴权 Header 和本机/UNC 路径不得出现在结果、日志或目标表;脱敏审计只记录来源 SHA-256、类型和字节数,目标字段只保存吾码租户内相对路径。
161
- - 使用第三方附件 Id/版本作为幂等键,回读目标记录后才标记成功;多节点重投不能重复产生业务附件。
162
- - 写入目标 `FileUpload` 字段前必须回读其权威 `diy_field.Config.FileUpload.Limit`;目标字段为 `Limit=true` 时,迁移上传也必须使用 `Limit=true` 写入私有桶,不得上传到公有桶后仅靠字段路径伪装为私有文件。
163
- - 源附件为空、大小为 `0` 或源对象不可读取时,不得创建目标业务记录并把上传字段保存为 `[]`、`'[]'` 或其它空占位值;应仅在迁移账本中记录为跳过或失败,保留来源 Id、原因和可重试状态。
164
- - 私有附件只有在目标记录回读成功,并使用该记录的权威资源上下文调用 `/apiengine/platform-private-file-url` 取得代理地址,再对该地址执行 `Range: bytes=0-0` 且确认返回 `200/206`、实际读到字节并且不是 JSON 错误后,才允许标记迁移成功;完整验收再核对总字节数或 SHA-256。
165
- - 批量迁移应落任务状态表并分页处理,失败可重试;不要让 MCP 一次加载整库路径或大文件集合。
166
-
167
- 可信后端 V8 可用 `V8.Http.GetResponse({ Url: url }).RawBytes` 下载,再用 `System.Convert.ToBase64String` 和 `V8.Method.Upload` 上传。该路径同样必须校验域名、大小、Content-Type、后缀和最终重定向目标。
168
-
169
- <!-- /microi-progressive:chunk -->
170
- <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=587102654ab519a0a97ffe204a0b69c06d868865f4193bfa0546365aa1fa0344 -->
171
- ## 接收前端上传的文件
172
-
173
- 前端发起文件上传时,平台自动把文件以 base64 形式注入到 `V8.FilesByteBase64`:
174
-
175
- ```javascript
176
- // V8.FilesByteBase64 = { '文件名1.png': 'base64...', '文件名2.pdf': 'base64...' }
177
- if (!V8.FilesByteBase64) {
178
- return { Code: 0, Msg: '请上传文件' };
179
- }
180
-
181
- var fileNames = Object.keys(V8.FilesByteBase64);
182
- var firstFile = fileNames[0];
183
- var firstBase64 = V8.FilesByteBase64[firstFile];
184
-
185
- // 上传到 HDFS
186
- var upResult = V8.Method.Upload({
187
- FilesByteBase64: V8.FilesByteBase64,
188
- Limit: true, // 业务上传默认私有桶(需临时 URL 访问)
189
- Preview: false, // true=自动生成预览图
190
- Path: '/business/orders', // 存储路径前缀
191
- OsClient: V8.OsClient
192
- });
193
-
194
- if (upResult.Code !== 1) return upResult;
195
-
196
- // upResult.Data = [{ FileName, Path, FullPath, Size, ... }, ...]
197
- var filePath = upResult.Data[0].Path; // 相对路径,存数据库
198
- var fullUrl = upResult.Data[0].FullPath; // 完整 URL(公有桶)
199
- ```
200
-
201
- ### AI 应用超大资产断点续传
202
-
203
- Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进入 Base64、JSON 或 Jint。`microi_publish_application_directory_stream` 在协议 v3 下与 `@microi.net/cli` 共用同一套传输客户端:单文件大于 128 MiB 时自动切换为原始字节断点续传,小文件继续兼容旧版单请求链路。
204
-
205
- - 默认分片 16 MiB;5 GiB 文件为 320 片。分片通过 `application/octet-stream` 发送,必须携带精确 `Content-Length` 与 SHA-256。
206
- - 服务端逐片写入 HDFS 后重新流式回读校验;完成时按顺序合并、再次核对整文件 SHA-256,再生成不可变版本完整性标记。
207
- - 会话 Id 由租户、应用、版本、路径和文件摘要确定。网络中断或进程重启后先读取远端状态,只续传缺失分片;已成功的相同摘要请求直接幂等返回。
208
- - 吾码不为协议 v3 设置业务文件/目录字节上限,状态中的 `ApplicationAssetResumableProductSizeLimitBytes=0` 表示没有产品配置上限。每个对象仍受协议技术边界(最多 10000 片、单片最多 1 GiB)、JavaScript 安全整数、对象存储、磁盘、网关和网络条件约束。
209
- - 该链路只允许通过能力鉴权的当前租户超级管理员,并继续受 `DisableFileUpload` 负向总开关控制;它不是普通用户上传或任意 HDFS 路径写入接口。
210
- - 每个会话都在 `mci_ai_app_file` 保留审计记录,`StorageScope=ApplicationAssetMultipartSession`。管理员在 **系统引擎 → 超大文件上传记录** 查看状态、阶段、已传字节/分片、进度、心跳、错误和恢复建议;成功、失败和取消记录都不静默删除。
211
-
212
- ### 普通业务上传的分层限制
213
-
214
- 以下限制适用于 HTTP、表单、V8、移动端和旧版应用资产单请求,不适用于上面的受信任 v3 断点续传。Token 不是无限上传授权;普通入口必须在解码 Base64、解析图片或调用对象存储前执行服务端校验:
215
-
216
- 1. **租户业务配置**:有效正数/布尔值按 `sys_osclients` 当前租户 → 代码默认值解析。租户可以按业务需要提高或降低默认值,不要求安装者维护额外环境变量或修改 `appsettings`。
217
- 2. **平台绝对上限**:最终业务值再与代码内固定灾难保护上限取较小值,租户和安装参数都不能放大。
218
- 3. **HTTP 解析上限**:Kestrel 请求正文与 Multipart 固定为 2048 MB,普通表单单值固定为 128 MB,是所有租户共享的请求解析硬顶。
219
- 4. **字段级限制**:前端 `FileUpload` / `ImgUpload` / `RichText` 的 `MaxSize`、`MaxCount` 等只能与当前租户有效值取更小值,不能提高后端上限,也不能替代服务端校验。
220
-
221
- 业务配置与固定边界:
222
-
223
- | `sys_osclients` 字段 | 代码默认值 | 平台固定边界 |
224
- |---|---:|---:|
225
- | `DisableFileUpload` | `false`(关闭,即允许上传) | — |
226
- | `FileUploadMaxFileMB` | 100 MB | 1024 MB |
227
- | `FileUploadMaxRequestMB` | 200 MB | 2048 MB |
228
- | `FileUploadMaxCount` | 10 | 100 |
229
- | `FileUploadDailyUserQuotaMB` | 2048 MB | 10 TB |
230
- | `FileUploadDailyTenantQuotaMB` | 20480 MB | 10 TB |
231
-
232
- - `sys_osclients` 六个现行字段全部可空;`DisableFileUpload` 空值、无效值或 `0/false` 均表示允许上传,只有 `1/true` 才禁止。旧数据库尚未创建新物理字段时才回退读取 `FileUploadEnabled`,升级后旧正向字段即使为 `0` 也不得覆盖新字段的默认允许语义。`FileUploadMaxRequestMB` 指一次上传所有文件的业务合计大小,不等于 Kestrel HTTP 请求正文上限。
233
- - 固定灾难保护和 HTTP/Multipart/Form 解析上限不属于安装配置;租户值即使更大也会被这些边界截断。最终单次总量还不能超过帐号或租户的有效日额度,单文件不能超过最终单次总量。
234
- - `DisableFileUpload=1` 表示禁用当前租户上传,也会阻止 AI 应用资产断点续传;不能把内部发布协议当成绕过开关的后门。普通单请求继续受全局大小硬上限,v3 只移除产品级字节上限;租户配置刷新应走现有 SaaS 引擎重载和共享 Redis 发布订阅,不能依赖单节点内存。
235
- - 帐号与租户每日额度在共享 Redis 中用单次原子脚本预留,支持多节点;Redis 不可用时失败关闭,不能降级成无限上传。
236
- - 额度按 UTC 日期统计。为防并发重试绕过限制,后续对象存储失败也不退还已经预留的额度。
237
- - 每日额度只阻断短期滥用;对象存储必须另外配置租户/桶总容量、账单告警、生命周期与实际用量对账。Redis 计数不能作为长期容量事实源。
238
- - 标准表单字段上传按后端回读的 `diy_field.Config` 决定桶,不能按帐号等级覆盖字段策略。无字段上下文的普通上传默认只允许平台预定义私有一级目录;管理员可通过 `sys_config.HdfsUploadRules` 按真实角色明确扩展业务目录和公有权限,不能信任客户端自报角色或裸 `Limit:false`。源码、应用产物等仍使用独立受控发布流程。
239
- - 反向代理、Ingress/IIS 的请求体上限应与进程级 HTTP 解析硬顶协调。字段自身的类型、后缀、大小和数量配置只能进一步收紧当前租户有效值,不能替代平台硬顶。
240
-
241
- ### 复盘:“当前租户已停用文件上传”
242
-
243
- - 该提示只表示当前运行环境命中的 `sys_osclients.DisableFileUpload` 被明确设置为 `1/true`(或仍在旧库兼容期且 `FileUploadEnabled=0`)。新字段缺列以外的空值、无效值和新租户都按默认允许,不能先把问题归因于 MinIO/HDFS。
244
- - 新版响应同时返回 `DataAppend.ErrorType=TenantFileUploadDisabled`、`OsClient`、`ConfigField=DisableFileUpload`、`ExpectedValue=0` 和文档地址;客户端必须保留后端 `Msg/DataAppend`,不能只显示“上传失败”。
245
- - 处理时先读取当前 API 进程的 `OsClient + OsClientType + OsClientNetwork`,再精确回读同三元组的启用记录并把 `DisableFileUpload` 关闭为 `0`。不能仅按租户名批量覆盖其它网络或环境记录。
246
- - 保存后等待 SaaS 共享配置重载,再分别验证一个小公有图片和一个小私有文件。只有错误转为 endpoint、bucket、签名或 `Invalid URI` 后,才进入对象存储配置排查。
247
- - 不要通过删除 Redis 日额度 Key、扩大文件大小上限或改成公有桶来解除租户停用;这些动作与开关无关,还会扩大安全风险。
248
-
249
- ### AI / MCP 调整租户上传配额
250
-
251
- 用户明确授权修改某个租户的上传额度时,AI 可以直接使用标准 MCP 完成,不要把应用层提示误判成阿里云 OSS、MinIO 或 S3 的存储配额,也不要先清 Redis:
252
-
253
- 1. `microi_admin_table_data(operation: "query", tableName: "sys_osclients")` 按当前 `OsClient + OsClientType + OsClientNetwork` 查询,仅选择 `Id/OsClientType/OsClientNetwork`、`DisableFileUpload` 和五个现行 `FileUpload*` 配额字段;`FileUploadEnabled` 只用于诊断未升级旧节点,不读取秘密。
254
- 2. 只更新上一步唯一实际生效记录,只有用户明确要求多个环境保持一致时才扩展。调用 `microi_admin_table_data(operation: "update")`,`id/row.Id` 固定已回读 Id,并传 `confirmExecution: "UPDATE:sys_osclients:<Id>"`,其余字段省略。
255
- 3. MB 是存储单位:`20 GB = 20480 MB`。可修改字段为 `DisableFileUpload`、`FileUploadMaxFileMB`、`FileUploadMaxRequestMB`、`FileUploadMaxCount`、`FileUploadDailyUserQuotaMB`、`FileUploadDailyTenantQuotaMB`;不要再写旧 `FileUploadEnabled`。
256
- 4. 保存后逐条远程回读;FormEngine 会排队重载 SaaS 运行配置,再用真实小文件上传做生效冒烟。只看到 MCP 返回“更新成功”不算验收。
257
- 5. 提高每日配额保留当天已用计数,剩余额度为新上限减已用量。计数按 UTC 日期,失败上传不退款;除非用户明确授权事故处置,不得删除 Redis 配额 Key。
258
-
259
- 租户 MCP 只能调整普通业务上传配置;平台固定灾难保护、HTTP/Multipart/Form 解析上限和反向代理上限不能通过 `sys_osclients` 绕过。协议 v3 的原始分片不读取普通单请求大小字段,但仍要求能力鉴权、总开关、版本快照、逐片/整文件哈希和审计。写入 `sys_osclients` 属于控制面操作,只允许当前租户的 `Level >= 9999` 管理身份,并且必须保留 MCP 审计与写后回读。
260
-
261
- ### UniApp / H5 客户端直传路径规则
262
-
263
- 移动端通过 `/api/HDFS/UniappUpload` 上传时,前端必须走 `microi.v8.js` 的 `V8.uploadFile`,不要在页面里手写 `uni.uploadFile`。客户端上传的 `Path` 与服务端 `V8.Method.Upload` 示例不同,必须是安全相对路径:
264
-
265
- - 正确:`mall/pay-proof`、`mall/member/avatar`、`order/proof`
266
- - 错误:`/mall/pay-proof`、`https://...`、`C:\...`、`../x`、`mall//x`、`~x`
267
- - multipart 请求不能带 `Content-Type: application/json`,否则后端可能读不到 `Path` 表单字段并返回“移动端文件上传路径不合法!”
268
- - `OsClient` 只能保留一个规范字段,避免同时提交 `OsClient`、`osclient` 或 query/header/formData 多处互相冲突。
269
- - 生产 H5 不能只依赖 `uni.uploadFile`。页面从 `uni.chooseImage` 得到的 `tempFiles[0].file`、`tempFiles[0]`、`blob:` / `data:` 临时路径都要传给 `V8.uploadFile`,并设置 `preferFetch:true`;SDK 必须能用 `fetch + FormData` 兜底,否则线上可能报 `未找到 MicroiV8 上传适配器。`。
270
-
271
- <!-- /microi-progressive:chunk -->
272
- 热点分流与跨平台会话的完整规则见
273
- [操作与原生边界](references/hotspot-offload-operations.md)。
274
- ## 详细参考路由(渐进披露)
275
- 仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。
276
- - [references/progressive-01-公有桶-vs-私有桶.md](references/progressive-01-公有桶-vs-私有桶.md):公有桶 vs 私有桶;接口直接响应文件(下载/导出);通过 URL 列表批量下载并入库
277
- - [references/progressive-02-office-文件在线编辑版本号规则.md](references/progressive-02-office-文件在线编辑版本号规则.md):Office 文件在线编辑版本号规则;ImgUpload / FileUpload 字段值兼容规则;安全注意
278
- - 处理空目录标记或远程下载前,读取[完整存储边界](references/progressive-03-空目录标记与远程下载.md)。
279
- <!-- microi-progressive:end -->
1
+ ---
2
+ name: v8-file-upload
3
+ description: Microi V8 与 MCP 文件上传下载指南。用于流式发布、私有文件、HDFS/OSS/MinIO/S3,以及 OSS/CDN 费用治理、FileServerBak 热点自动复制与持续 NAS 分流、实际消费验收。
4
+ ---
5
+
6
+ > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
+
8
+ # Microi V8 文件上传下载
9
+
10
+ ## OSS / CDN 费用与 NAS 分流入口
11
+
12
+ 用户要求降低 OSS/CDN 流量费用、把高下载模型/JS/图片/视频转到 NAS,或询问
13
+ “1 GiB / 24 小时配置在哪里”时,使用已有热点分流能力。先读
14
+ [热点分流操作流程](references/hotspot-offload-operations.md),再核对本文的原生权限和依赖边界。
15
+ 系统设置 `/system-config` 的 `FileServerBakThresholdGiB`、`FileServerBakWindowHours`
16
+ 控制完整日流量资格;`FileServerBakSizeMiB` 控制独立单文件大小资格,需原生能力回读。
17
+ `FileServerBak` 是备用 HTTPS origin。可信达标后低流量不撤回 NAS 偏好。
18
+ 只复制、保存配置或发布页面都不等于实际入口已走 NAS,也不等于完整日费用已下降。
19
+
20
+ ## 源码仓库资源隔离(默认强制)
21
+
22
+ 截图、配图、PDF/PPT、音视频、压缩包、模型、安装包和其它大体积交付资源统一通过 MCP 上传 CDN,禁止提交到 Gitee/GitHub 源码历史,即使压缩到小于 1 MB 也不能把截图或附件改放源码仓库。官方资源使用已校验的 `microi_itdos`(`https://api.microi.net + OsClient=iTdos`)公有桶与 `https://file.microi.net` 前缀;私有资料按租户实际权限保持私有。源码只存 URL、字节数、SHA-256、MIME 等轻量清单。上传后独立回读实际字节和类型,PDF 验证 Range;超过 Base64 协议容量使用流式/分片工具。暂存区及每个待推送提交都须排除资源本体,详细门禁遵循 [工作区默认 CDN 规范](../workspace-conventions/SKILL.md#截图与大体积资源必须走-cdn默认强制)。
23
+
24
+ 你正在为 Microi 吾码平台编写文件上传/下载/返回相关代码。平台分布式存储(HDFS)支持阿里云OSS、MinIO、亚马逊S3,存储方案由 SaaS 引擎按租户配置。上传对象的 `Content-Type` 由框架按实际对象路径统一确定,覆盖 HTML、JS/MJS、CSS、JSON/MAP、SVG、字体与 WASM,未知后缀保留 `application/octet-stream`。MinIO、S3、OSS 普通与分片上传使用同一映射;该映射不替代上传权限、内容和配额校验。旧对象不会因后端升级自动改变元数据:先核对字节数和 SHA-256,再修正类型或按正式应用发布流程产生新版本。不要把源码与公有编译产物混在一起,也不要关闭 `nosniff` 掩盖类型错误。
25
+
26
+ 文件与发布测试默认使用当前 MCP 连接下、当前租户实际生效的 HDFS Provider;先只读核实提供商与对象范围,沿已授权隔离前缀执行公私权限、原始字节、哈希、MIME 与写后回读断言。已确定当前环境后,不再为其它 OSS/MinIO/S3 反复询问或新建存储;仅明确的提供商缺陷、迁移或兼容任务才扩展矩阵,并逐项报告真实执行与未覆盖范围。提供商不可用或断言失败必须保留真实错误,不能换提供商或省略回读来冒充通过。
27
+
28
+ 公开入口覆盖 `V8.uploadFile`、多文件 `V8.uploadFiles` 与 MCP `microi_upload_file_base64`。多文件上传必须限制并发、逐文件返回结果;Base64 工具只接受明确文件名、大小和租户内目标范围,写后回读路径、大小与哈希。
29
+
30
+ 公开运行资源的备用地址只通过 `V8.Method.GetPublicResourceBackupUrl({FilePathName:'tenant/micro-app/app/index.html'})` 只读选择,返回 `{Code:1,Data:{UseBackup,Url}}`。只允许 `FilePathName`,不能传 URL、Ready、SHA、流量、凭据或用户身份;当前公开应用、完整版本清单、签名副本与 NAS 健康必须同时匹配。`UseBackup=false`、异常或旧后端缺少该能力时继续原公开地址,不把 `FileServerBak` 非空或目录存在当作可用副本;该方法不复制对象、不读取私有文件,也不授予管理权限。管理和迁移仍走已有受信任应用接口,并分别回读副本完整性与真实消费结果。
31
+
32
+ 专用公开导入桶与 `V8.Method.GetCompletedPublicUploadProof` 的配置、授权、提升和失败边界,完整读取[专用公开导入协议](references/hotspot-offload-operations.md#专用公开导入桶与可信媒体证明)。
33
+ ## MCP stdio 与完整包的传输边界
34
+
35
+ ### 普通本机文件上传与备用公私目标
36
+
37
+ - 先回读 `microi_get_status.PrivateFileUploadProtocol` 或 `microi_manage_file_stream_upload action=Capabilities`;支持值为 `PrivateFileUpload/1`。工具已安装不等于 API 已升级,旧 API 不支持时失败关闭,不能改走公有应用发布。
38
+ - `microi_upload_file_stream.localFilePath` 是 MCP 客户端电脑上的普通文件,由客户端读取原始字节、固定 16 MiB 分片;32 GiB 只是协议硬上限,实际仍受 SaaS FileUploadMaxFileMB/MaxRequestMB、DisableFileUpload 与共享日用户/租户额度约束。首次写入分片走现有 UploadQuota 原子额度,确认分片重放不重复预留,失败不退款。不要与旧 `microi_import_external_attachment.sourcePath` 混淆:后者是 API 服务账号能访问的服务器绝对路径/UNC,目标仍是主 HDFS。
39
+ - `target` 默认 `Primary`,`Primary` / `BackupPrivate` 均为私有;显式 `BackupPublic` 只把最终文件写入当前租户 SaaS MinIO 公桶。两种备用目标都核对 `FileServerBak` 公网绑定,暂存私桶不同于公桶且只读策略检查通过;不切换 HDFS、不允许自由 Endpoint/Bucket/凭据。未配置专用公开导入桶时,公开授权只对签名 Completed 的单个最终对象追加匿名 GetObject,保留其它 Statement,不开放整桶、前缀、ListBucket/PutObject 或私桶;专用桶例外仅限下述独立受保护命名空间。只接受交互式平台管理员,禁止访问密钥会话。
40
+ - 只有用户明确授权选定文件可由所有人访问时才使用 `BackupPublic`,必须 `publishPublic=true`、`confirmExecution="PUBLIC:" + requestId`;私有目标确认仍是原 requestId。没有公开意图的旧调用保持私有;图库/人脸/其它私密资料不随示例公开。公开会话分片、签名清单和回执始终私有,目标和公开意图绑定签名,不能把既有私有会话换目标公开。
41
+ - 该入口对 MinIO 检查桶策略、OSS 检查私有 ACL 与桶策略;S3 尚缺完整私有 ACL 证明,`StorageProviderSupported=false` 时不上传。Capabilities 仅展示协议/配置,不代替实际桶权限验证。32 GiB 协议格式不放宽普通上传绝对 2 GiB 单文件/总请求保护。
42
+ - 稳定 `requestId` 固定原始 SHA-256/大小/每片哈希、操作者、租户及存储绑定;不确定结果先 Status 后同键续传。MCP 本机检查点不含凭据;跨客户端带原回执 `expectedStorageBindingHash`。不能取新 binding 伪装旧会话恢复。
43
+ - 最终返回 Completed 前服务端合并并回读原始字节 SHA-256;独立验收调用 Verify,不拿 ETag、200、元数据或上传结束代替原片完整性。会话清单/块/回执始终私有并 HMAC 绑定精确对象键,底层使用既有 HDFS 流、原生鉴权和 renewable lease,不经过 V8 大对象。
44
+ - 未完成会话有效期 7 天,Abort 只清理拥有的 staging;完成自动清理失败显示 CleanupComplete=false,重复 Complete/Abort 可补清。不得删除用户最终原片。签名清单/终态回执保留审计;暂存磁盘有界内存流式,最终合并需要与原片等量的临时空间。
45
+ - BackupPrivate 回执的 FilePathName 不能当主 HDFS 路径交给旧 GetPrivateFileUrl。没有备用私有下载票据能力时仅交付后端 Verify 证据,不能拼匿名 URL 或改公桶。当前公开热点分流仍只处理公开资源,与本协议隔离。
46
+ - `BackupPublic` 自动公开要求后端 8.8.6 与支持该动作的 MCP,Capabilities 必须回读 `PublicObjectPublicationProtocol=ExplicitPublicObjectRead/1`;8.8.5 只完成存储,不能补新对象匿名授权。未配置专用桶时,客户端完成后执行写动作 `PublishPublic`:签名 Completed + 全量 SHA 校验在前,与热点分流共享同一桶策略租约,精确追加该对象 GetObject 后重新校验管理员、绑定及全量 SHA,再匿名 HEAD/首段 Range,成功才返回 PublicUrl。原 Statement 保持;同名 Sid 异常/条件/策略变量/通配符拒绝,不静默修复。写入结果未知保留 MayHaveAddedGrant 事实,失败以新租约只撤本次可能新增授权,回报 PublicPolicyRollbackConfirmed;不能撤此前已核实的既有授权。共享租约+写前比较仅协调平台写入者,不宣称 SDK 对外部管理员有原子 If-Match。
47
+ - `ProbePublic` 始终只读,仍执行全量存储 Verify + 可信 FileServerBak 匿名校验;匿名范围最多16MiB,不冒称全文件已匿名下载。无 Cookie/凭据/重定向,大文件错误200只读头拒绝,不下载全片。独立 Complete 仅完成存储;失败 Code0 保留 Completed/字节证据,原 requestId 可恢复,或显式 `PublishPublic` 携带原 sessionId/binding、BackupPublic、publishPublic=true 与 `confirmExecution="PUBLIC:"+sessionId` 重新授权,不换请求号重复上传。已 Completed 会话可超过7天恢复,未完成会话寿命不变。管理 Complete 的公开确认也是 `PUBLIC:` + sessionId,Abort 仍原 sessionId且只清理自有暂存。
48
+
49
+ - 标准 MCP 服务的 stdio 使用固定 `128 MiB`(`134217728` 字节)未解析 JSON-RPC 缓冲,超过边界立即拒绝并关闭连接;不读取动态环境变量放大。SDK 调用端若仍使用默认 `10 MiB` 缓冲,大响应仍可能关闭客户端,必须核对调用端支持的真实边界。
50
+ - 该限制计算 UTF-8 wire 字节,包含完整 JSON 信封、Base64 和重复 `Data/Result` 正文。单个 Base64 字段的理论原始字节上限约 `96 MiB` 减信封,不能据此保证任意 `64 MiB` 包响应都通过,也不能把它当成 HDFS `256 MiB` 应用包能力的新上限。
51
+ - 已存在的正规完整包协议仍可使用 `PreparedPersistPackageByteBase64`;先按精确正文计算编码和整个请求/响应字节数,保留版本、CAS、哈希、HDFS 回读及快照 finalize。连接关闭属于结果未知,先回读同版本、同请求身份,不能换请求键盲重传。
52
+ - 超过 wire 能力的源码、运行资产优先使用已有目录/文件 stream 或分片协议及其
53
+ 小型指针/回执,不把私有源码、超大资产常态化塞入 JSON/Jint;支持更大正文的其它
54
+ 连接也须有明确边界。不能用增大stdio缓冲绕过能力鉴权、业务配额或包完整性检查。
55
+
56
+ `ProbePublic` 的可选 `PublicPolicyDiagnostics` 仅输出固定 `FailureStage/ReasonCode/SdkCode`、有界字节数/语句数及真实布尔状态;合法字段名中的任意 URL、异常文本、策略正文、资源 ARN 和秘密仍被拒绝,未知保留 `Unknown`,不从错误文本推断 Redis 故障。原当前管理员、签名会话 owner/tenant/binding/Completed 和完整 SHA 验证保持,之后仅 GetPolicy + 纯规则检查,不调用 Set/Remove。
57
+ 旧适配器缺诊断能力返回 `UnsupportedDiagnostics`,仍继续原独立匿名 HEAD/Range;策略失败不覆盖匿名结果,策略就绪不等于公开可读。`PublishPublic` 沿用同样白名单诊断,保持未知 SET 后回读、共享租约和仅撤销本次新增授权的规则;诊断观察器不能改变写入顺序。
58
+
59
+ ## 表单字段的公有桶与私有桶(强制)
60
+
61
+ - `Config.FileUpload.EnableRolePermission=true` 的字段强制私有上传,优先级高于字段和请求 `Limit`。
62
+ 每个附件 `VisibleRoleIds` 多选真实角色,三个默认关闭的开关为 `HideUnauthorizedFiles`、
63
+ `ShowUnauthorizedFileName`、`DisableRoleInheritance`。无权限时后端仅返回脱敏占位,不含 Path、
64
+ Versions 或签名地址;普通保存必须保留无权附件,不能改角色、改名、删除或通过复制路径重新授权。
65
+ 上传响应的 `_UploadProof` 绑定当前租户、用户、字段、记录、私有路径及有效期,只用于首次保存校验,
66
+ 持久化时移除;新文件须携带当前记录/字段上下文上传。历史公有附件需重新私有上传后才能设置角色。
67
+ 完整配置、继承与 MCP 流程见 `../microi-form-engine/SKILL.md`。
68
+
69
+ ### 旧上传返回与接口引擎扩展
70
+
71
+ - 系统设置 `CompatiblePlatformOldVersion` 默认关闭,空值/缺字段也关闭;开启时旧 HTTP 上传
72
+ 保留 `Code/Data`,单文件也统一返回数组(包括 `Multiple=false`),补 `url/type/size/duration/uploading/progress/path/name/id`。
73
+ 图片 `type=image`,名称去掉最后一个扩展名;`url` 必须沿用实际 `Url`,不能把私有文件改拼公有地址。
74
+ - `/api/HDFS/upload`、`/api/Upload`、`/apiengine/platform-hdfs-upload` 使用当前租户上传引擎。
75
+ 只有主库确认完整地址、别名及固定 Key 缺失才执行编译兜底;禁用、StopHttp、拒绝或异常不兜底。
76
+ - `V8.Method.UploadCurrentRequestAsync()` 复用已验证的当前请求文件流,重复调用只上传一次;
77
+ `V8.Method.IsLegacyUploadCompatibilityEnabled()` 读取宿主取得的开关,二者均不接受参数。
78
+ 它们绑定当前租户与选定引擎,普通脚本或嵌套其它引擎不能借用;无请求时使用原有 `V8.Method.Upload`。
79
+ - 返回编排由系统设置应用拥有的 Managed `platform-hdfs-upload` 实现;持久定制使用
80
+ CreateIfMissing `platform-hdfs-upload-hook`,返回 `{Code:1, UploadResult:完整结果}`。
81
+ 先用 `JSON.parse(JSON.stringify(V8.Param.Result))` 转为普通 JS 对象,再判断 `Data` 数组并遍历追加字段;不能直接对 CLR 包装对象使用 `Array.isArray` 或 `length` 分支。
82
+ 系统设置及基础空库包都交付可空字段,但上传引擎只由系统设置包拥有;不携带租户开关值。
83
+ - MCP 复用 `microi_add_field`、`microi_update_field`、`microi_create_engine`、
84
+ `microi_save_engine_code` 和管理员回读;验收分别覆盖真实上传、配置缓存、引擎优先、缺失及错误分支。
85
+
86
+ - `ImgUpload`、`FileUpload`、`RichText` 的“禁止匿名访问”是字段权威策略:`Limit=false` 写公有桶,`Limit=true` 写私有桶。普通用户只要通过当前菜单/表的新增或编辑动作授权,也必须按该字段配置执行;不得按用户等级把全部非超级管理员上传统一改成私有桶。
87
+ - 浏览器上传必须携带 `FormEngineKey + FieldId + SysMenuId`,编辑已有记录再带 `FormDataId`,TableChild 再带父子授权上下文。后端先用 FormEngine 校验动作权限,再从当前租户回读 `diy_field.Component/Config`,用权威 `Limit` 覆盖请求值,并把目录固定为 `ImgUpload→img`、`FileUpload→file`、`RichText→editor`。
88
+ - 客户端 `Limit`、`Path`、字段 Id 和菜单 Id 都只是待验证线索。没有可验证字段上下文的普通交互式上传默认私有并限制到安全一级目录;不能为了恢复公有字段语义而重新信任裸 `Limit=false`。
89
+ - 兼容旧移动端/定制页面时,在普通系统设置 `sys_config.HdfsUploadRules` 配置目录与角色规则,无需逐个改客户端。`Path` 使用区分大小写的租户内相对路径;`RoleIds` 是真实角色 Id 数组,或显式启用 `AllAuthenticated`;`IncludeSubdirectories` 和 `AllowPublic` 默认关闭。只有后端有效角色命中且显式允许公有,才尊重请求 `Limit=false`。规则不是秘密,不必放入后端私有设置,修改权限与服务端判定必须严格受控。
90
+ - 排查目录授权失败时先检查 multipart 是否同时提交 `Path` 和 `path`。客户端只应提交一个 `Path`;后端对相同值归一,对不同值拒绝,不能因重复字段误判而扩大 `HdfsUploadRules`。
91
+ - 后端 v8.2.9+ 的配置 `Path` 支持 `*`(单层任意字符)、`**`(零到多层目录,须独占层级)、`?`、`[abc]`、`[a-z]`、`[!0-9]`/`[^0-9]`、`{a,b}` 候选及组合;优先 `files/{inspection,quality}/**` 这类最小业务前缀,不能为省事直接向全部用户配置 `** + AllowPublic`。只匹配请求目录,不匹配文件名或服务端追加的年月。实际上传路径不接受通配符;保留目录先于 glob 校验,不能用宽泛规则绕过。最多512字符/规则、4层花括号、32候选、4096令牌;有界动态规划和有界语法缓存不得缓存租户授权结果。先更新全部后端节点再配置新语法,精确旧规则仍兼容。
92
+ - 目录规则只扩展无字段上下文上传,不绕过表/菜单/行权限、租户隔离、保留路径、文件类型、内容检测及配额;不能授予访问密钥会话、私有文件读取或应用发布权限。规则通过系统设置共享缓存读取,保存/删除后失效;应用包只发布字段和可空 DDL,禁止附带会覆盖租户规则的配置数据。验收覆盖授权/未授权角色、路径边界、公私桶、禁用用户、跨租户、规则撤销及缓存生效。
93
+ - 老 `ImgUpload/FileUpload` 缺失 `Limit` 时兼容为公有;老 `RichText` 缺失上传配置时默认私有。微信待审图片与裁剪/压缩 `_origin` 原图始终私有,字段公有配置不能放宽这些特殊边界。客户端保存与预览必须以上传响应的实际 `Limit` 为准。
94
+
95
+ ## 交互式图片默认压缩(强制)
96
+
97
+ - `ImgUpload`、PC、UniApp/H5/小程序以及 MCP 普通图片上传在未显式传 `Preview` 时,必须按 `Preview=true` 处理;字段设计器新配置默认开启。只有业务明确要求公开原始画质时才允许显式关闭,不能把“客户端漏传”解释为关闭。
98
+ - 默认展示图目标为不超过约 `500 KB`、最长边不超过 `1920 px`;可按真实用途进一步收紧。压缩必须使用 Windows/Linux/容器一致的跨平台实现,并限制输入字节、像素总量与解码并发,避免超大图触发 OOM。
99
+ - 压缩流程固定为“先将原始字节写入当前租户 HDFS 私有桶的 `_origin` 对象 → 生成压缩展示图 → 写入目标公有/私有位置 → 回读大小与格式”。即使展示图是公有资源,原图也只能保存在私有桶。
100
+ - 压缩、解码或编码失败时必须失败关闭,返回可诊断错误;禁止静默把几十 MB 的未压缩原图发布到公有桶。返回字段中的 `Size` 应表示展示图实际字节数,`OriginalSize` 可用于管理员审计,但业务字段不得保存私有桶真实地址或签名 URL。
101
+ - 历史治理只迁移超过目标体积的图片:先为旧对象补齐私有原图副本,再上传压缩展示图、原子更新全部业务引用并逐条回读。确认没有引用后才删除旧公有对象;批量任务要有清单、检查点、幂等映射和失败重试,不能边扫描边不可逆覆盖。
102
+
103
+ ## 表单引擎图片裁剪(强制)
104
+
105
+ - `ImgUpload.Crop` 支持 `Enabled`、`Mode=free/fixed/select`、`Ratio`、`CustomWidth/CustomHeight`以及 `AllowZoom/AllowRotate/AllowFlip`。`Enabled` 只表示表单用户的默认状态,不得当作裁剪能力总开关;新增/编辑表单把裁剪开关合并到紧凑上传面板,旁边同时显示公有/私有桶、单/多图与最大数量、压缩状态和最大体积。旧字段未配置时该开关默认关闭,但用户仍可主动开启。
106
+ - 裁剪弹层必须同时提供“取消本次上传”“不裁剪直接上传”“应用裁剪并上传”三个不同动作。多图选择按队列逐张处理;直接上传只跳过当前图片的裁剪,不能取消或阻塞后续图片。
107
+ - 多图模式下后端可能为每个单文件请求返回 `Data: [{...}]`,单图模式返回 `Data: {...}`。PC/移动端必须先归一化对象/数组,再按客户端 `uid` 替换上传占位项并生成预览 URL,禁止把接口成功误显示成空列表。
108
+ - 开启裁剪时,前端必须上传最终裁剪图,并在同一 multipart 请求中以 `MicroiOriginalFile` 附带同名、未改动的原图及 `CropEnabled=true`。不得仅传坐标后由后端重演,否则 EXIF 方向、旋转或镜像可能导致前后端结果不一致。
109
+ - 后端必须校验裁剪图与原图一一同名,两者字节数都计入单次限制和每日配额,但原图不计为第二个业务文件。未宣告裁剪却传原图、宣告裁剪却漏传/错配原图都要失败关闭。
110
+ - HDFS 写入顺序固定为“未改动原图写入私有 `_origin` 对象 → 可选压缩裁剪图 → 写入业务展示图”。裁剪原图写入失败时不得发布展示图;即使 `Preview=false`,裁剪原图仍必须私有保留。不得返回或持久化原图真实路径。
111
+
112
+ ## 富文本图片、视频与附件(强制)
113
+
114
+ - `RichText.Limit=false` 只用于需匿名长期访问的官网公告、商品详情等公开正文;内部内容用 `true`。普通用户经表单新增/编辑授权后也按后端回读到的该字段配置选择桶;仅修改请求中的 `Limit` 不能改变策略,客户端必须以上传响应的实际 `Limit` 为准。
115
+ - RichText 分别配置 `Image`、`Video`、`File` 的 `Enabled/MaxSize/MaxCount`;图片另传 `Preview/CompressMaxSize/CompressMaxWidth`,并继续遵循“原图私有、展示图公有或私有”的压缩链路。附件类型白名单用 `File.Accept` 进一步收紧,不能放宽服务端白名单。
116
+ - 私有正文持久化 `/__microi_richtext_private__/...` 稳定对象标识,严禁保存对象存储签名 URL、`OpenPrivateFile` Ticket、DiyToken 或其它会过期的凭据。每次打开记录时携带 `FormEngineKey/FormDataId/FieldId/SysMenuId` 批量换取短效审计代理地址。
117
+ - 私有文件后端授权必须重新校验当前租户、菜单、表、行和 RichText 字段,并确认所请求路径精确存在于 `img/video/source.src` 或 `a.href`;普通上传字段的对象/数组只认 `Path/FilePath/FilePathName`,不得递归把 `Name/Size/Metadata` 等任意标量当作路径。未经当前租户 FileServer 主机权威校验的绝对 HTTP(S) URL 不得等价为本地对象 Key;正文文字、`data-src/data-href`、脚本标签和前缀相似路径都必须失败关闭。
118
+ - 外部匿名页面没有后台记录权限上下文,不能解析私有标识。公开文章应由设计者把 RichText 字段配置为 `Limit=false`,有该表单新增/编辑权限的用户即可按权威配置发布;不得通过延长私有 URL 有效期模拟公开资源。
119
+ - 若业务明确要求撤稿后旧媒体地址立即停止返回字节,使用私有对象和专用批准引用代理:只接受固定业务标识,权威校验当前租户、不可变批准版本与精确媒体引用,读取前后复核发布/许可/撤回状态,限制 MIME/体积并返回 `no-store`;不开放裸路径,不返回签名 URL 或重定向。普通文件短链接口不能代替这种业务撤权能力。
120
+ - 撤回授权与稳定请求键待办先在共享数据库事务提交,再执行 HDFS 删除。未知结果沿用原请求键探查重放,原上传请求摘要保持。删除源对象不等于删除 CDN 缓存;历史公开链接必须有官方失效任务和独立缓存字节读回证据,否则保留未确认状态,不把 `Offline`、隐藏前台或源站 `404` 记作完整撤回。
121
+
122
+ 官网客户端读取私有文件统一调用 `/apiengine/platform-private-file-url`,提交 `FilePathName` 或有界 `FilePathNames`,并按资源类型提供权威定位参数:普通表单字段使用 `FormEngineKey + FormDataId + FieldId + SysMenuId`;用户头像使用 `ResourceKind=UserAvatar + ResourceId=用户Id`;菜单/部门导入模板分别使用 `MenuImportTemplate`、`DeptImportTemplate` 与对应记录 Id。CAD 私有派生预览使用 `ResourceKind=FormFieldDerivedPreview`,除表单四元组外必须同时提交字段中保存的 `OriginalFilePathName` 和单个派生 `FilePathName`;后端只接受同目录同 basename 的 DWG→`_preview.dxf`、STEP/STP→`_preview.stl` 唯一映射,并在对象存在后签名。文件柜对象使用 `ResourceKind=FileManagerObject`,`ResourceId` 必须与单个 `FilePathName` 大小写精确相同,并提交能力探针返回的当前租户权威 `SysMenuId`;此类签名只允许平台超级管理员 DiyToken 会话,访问密钥和普通菜单用户一律拒绝。后端会从权威字段或对象存储重新读取并精确匹配路径;管理员也不能只传裸路径绕过对象引用,普通客户端禁止换取私有文件原始 Byte/Stream。旧 `/api/HDFS/GetPrivateFileUrl` 与 `/api/HDFS/MallFileUrl` 只保留令牌格式兼容并转发同一 Managed 接口,新代码不得继续引用。
123
+
124
+ ## 已提交应用的 CDN 特殊字符别名修复(强制)
125
+
126
+ 使用 i_app_cdn_backfill 的 RepairCommittedCdnAliases 前,必须读取[完整绑定、范围与并发边界](references/cdn-alias-repair.md)。保持默认 DryRun、原始字节哈希与 Base64 摘要分离,并独立回读实际 CDN 字节。
127
+
128
+ ### 历史安装应用的持久别名检查点
129
+
130
+ - `LegacyUnverified`、协议 2 的已安装微服务可能由后台补齐稳定入口。旧版每 30 秒扫描并反复全读版本与别名对象,会产生与浏览器/CDN 访问无关的 SDK 存储出流量;先按访问日志核对调用方、对象路径及周期,不能仅用 CDN 用量解释 OSS 公网账单。
131
+ - 后端使用受保护 `mci_ai_app_version.BuildLog` 的独立两键对象 `{Contract:'InstalledAliasReconciliation/1',Checkpoint:{...}}`;检查点字段为 `AuthorityHash/Status/Attempt/StablePasses/UpdatedAtUtc/NextCheckAtUtc`,状态仅 `Verifying/Retrying/Verified`。未知非空日志或非法结构拒绝接管,禁止人工清空日志或伪造 `Verified` 来节流。
132
+ - `AuthorityHash` 绑定规范化租户及完整权威应用、版本、服务、公开文件清单;只排除检查点自身,保留版本 `UpdateTime` 发现同版本重装。元数据变更使检查点立即失效,下一轮恢复全量校验。写入只条件更新所属版本的 `BuildLog`,不改 `UpdateTime`、`LegacyUnverified`、协议号、active/committed 指针或 V3 成功状态。
133
+ - 初次成功后等待 5 分钟,第二次稳定成功后等待 30 分钟,第三次及以后每日复核;发生修复复制重新累计稳定次数。失败按 5/10/20/40/60 分钟退避并封顶。先持久化 `Verifying` 再读对象,硬重启仍遵守等待;保存失败不开始昂贵读取。全程持有共享发布租约,结果按主库完整权威和日志独立回读确认,响应未知不能盲重做。
134
+ - 只读验收结合 `VerifiedCheckpointSkipped/RetryBackoffSkipped/NextCheckAtUtc` 与对象存储访问日志,覆盖等待内零重复读取、元数据变更、失败恢复和多个节点。需确认实际 API 节点已部署修复;文档、单测、镜像上传不等于生产节流生效,也不把检查点成功当成正式 V3 发布回执。
135
+
136
+ <!-- microi-progressive:begin -->
137
+ <!-- microi-progressive:chunk id=v8-file-upload-000 sha256=103f9fba40afae432102e8538ef5889d027b69adcde018cf28ea4521d836adb5 -->
138
+ ## 核心 API
139
+
140
+ | API | 说明 |
141
+ |-----|------|
142
+ | `V8.FilesByteBase64` | 接收上传时携带的文件字典 `{ FileName: base64 }` |
143
+ | `V8.Method.Upload({...})` | 服务端上传文件到 HDFS(推荐) |
144
+ | `V8.Method.GetPrivateFileUrl({FilePathName})` | 生成私有桶临时访问 URL |
145
+ | `V8.Method.CopyObject({FilePathName,Path,Limit})` | 当前租户同一桶内服务端复制,保留原对象;用于固定入口与版本快照 |
146
+ | `V8.Method.ObjectExist({FilePathName,Limit})` | 检查当前租户公有/私有桶对象是否存在 |
147
+ | `V8.Method.GetObjectSha256({FilePathName,Limit})` | 在服务端流式计算对象原始字节与旧版 Base64 文本的 SHA-256 和字节数;不把对象内容送入 V8 |
148
+ | `V8.Method.ListObjects({Path,Limit,Recursive,Marker,MaxKeys})` | 分页列举当前租户前缀,单页最多 1000 个 |
149
+ | `/apiengine/platform-private-file-url` | 官网 PC/UniApp 按菜单、记录、字段和对象引用换取私有文件短链 |
150
+ | `V8.Http.GetResponse({Url}).RawBytes` | 下载远程文件为字节数组 |
151
+ | 接口返回 `{ FileName, ContentType, FileByteBase64 }` | 接口直接响应文件 |
152
+
153
+ 固定 CDN 应用回填优先使用服务端 `CopyObject`,公有桶复制编译资产、私有桶复制源码;`Limit` 在源与目标间保持一致,`Path` 和 `FilePathName` 均由后端收敛到当前租户。大对象用 `GetObjectSha256` 流式核对原对象和复制目标,公有体验路径仍须从 CDN 独立回读。历史版本目标已存在时须核对字节哈希,发现不同内容立即停止;固定根可在新版本验证后覆盖。`ListObjects` 必须分页并限制到单个应用前缀,不得把这些存储管理原子直接开放为匿名业务接口。大型私有文本的 `GetPrivateFileText` 字符串边界及原字节/最终ZIP验签规则见[已有详细参考](references/progressive-01-公有桶-vs-私有桶.md)。
154
+
155
+ MinIO SDK 7 的复制签名不匹配还可能来自带参数 MIME:SDK 对源 `Content-Type` 签名,却在 HTTP 请求中额外保留 `StringContent` 的默认 MIME。先在独立夹具核对源 MIME、重复头和真实存储返回,再升级后端复制传输修复;不能去掉 `charset`、关闭签名校验或下载后重传来伪造通过。修复后必须同时验证公私桶原始字节、精确 MIME、源元数据及源对象保留,固定入口恢复另覆盖主库权威、真实共享租约、双节点竞争和新进程重跑。取消/超时后保持原请求键,先回读目标再决定是否继续。
156
+
157
+ <!-- /microi-progressive:chunk -->
158
+ <!-- microi-progressive:chunk id=v8-file-upload-001 sha256=75f69f74c9e9c6799fdddb39d14fa2e831886aa1f9e41a1388e37b7fb620b91e -->
159
+ ## 第三方数据库附件迁移
160
+
161
+ 当第三方表只保存附件路径时,先用 `microi_inspect_external_database` / `microi_query_external_database` 或 `V8.Dbs.<DbKey>` 查询记录。`microi_import_external_attachment` 允许后端已确认的 `Level >= 9999` 当前用户直接提供 HTTP/HTTPS URL、API 节点可读的本机绝对路径或 UNC 路径。
162
+
163
+ - 导入工具必须显式确认;HTTP、私网、重定向、本机和 UNC 均可访问,但最终能力受 API 服务进程账号、网络、磁盘及对象存储权限约束。
164
+ - 下载与上传使用临时文件和文件流,不经过 Base64;不设固定 20/100 MB 上限,`MaxBytes=0` 或省略表示不设置 MCP 上限,可处理 200/500 MB 或更大文件。
165
+ - 带签名参数或用户凭据的源 URL、鉴权 Header 和本机/UNC 路径不得出现在结果、日志或目标表;脱敏审计只记录来源 SHA-256、类型和字节数,目标字段只保存吾码租户内相对路径。
166
+ - 使用第三方附件 Id/版本作为幂等键,回读目标记录后才标记成功;多节点重投不能重复产生业务附件。
167
+ - 写入目标 `FileUpload` 字段前必须回读其权威 `diy_field.Config.FileUpload.Limit`;目标字段为 `Limit=true` 时,迁移上传也必须使用 `Limit=true` 写入私有桶,不得上传到公有桶后仅靠字段路径伪装为私有文件。
168
+ - 源附件为空、大小为 `0` 或源对象不可读取时,不得创建目标业务记录并把上传字段保存为 `[]`、`'[]'` 或其它空占位值;应仅在迁移账本中记录为跳过或失败,保留来源 Id、原因和可重试状态。
169
+ - 私有附件只有在目标记录回读成功,并使用该记录的权威资源上下文调用 `/apiengine/platform-private-file-url` 取得代理地址,再对该地址执行 `Range: bytes=0-0` 且确认返回 `200/206`、实际读到字节并且不是 JSON 错误后,才允许标记迁移成功;完整验收再核对总字节数或 SHA-256。
170
+ - 批量迁移应落任务状态表并分页处理,失败可重试;不要让 MCP 一次加载整库路径或大文件集合。
171
+
172
+ 可信后端 V8 可用 `V8.Http.GetResponse({ Url: url }).RawBytes` 下载,再用 `System.Convert.ToBase64String` 和 `V8.Method.Upload` 上传。该路径同样必须校验域名、大小、Content-Type、后缀和最终重定向目标。
173
+
174
+ <!-- /microi-progressive:chunk -->
175
+ <!-- microi-progressive:chunk id=v8-file-upload-002 sha256=105e24aa6af03c0b7393ff892f7dfbd603f7f2abc0527f84c8fbba73eee12043 -->
176
+ ## 接收前端上传的文件
177
+
178
+ 前端发起文件上传时,平台自动把文件以 base64 形式注入到 `V8.FilesByteBase64`:
179
+
180
+ ```javascript
181
+ // V8.FilesByteBase64 = { '文件名1.png': 'base64...', '文件名2.pdf': 'base64...' }
182
+ if (!V8.FilesByteBase64) {
183
+ return { Code: 0, Msg: '请上传文件' };
184
+ }
185
+
186
+ var fileNames = Object.keys(V8.FilesByteBase64);
187
+ var firstFile = fileNames[0];
188
+ var firstBase64 = V8.FilesByteBase64[firstFile];
189
+
190
+ // 上传到 HDFS
191
+ var upResult = V8.Method.Upload({
192
+ FilesByteBase64: V8.FilesByteBase64,
193
+ Limit: true, // 业务上传默认私有桶(需临时 URL 访问)
194
+ Preview: false, // true=自动生成预览图
195
+ Path: '/business/orders', // 存储路径前缀
196
+ OsClient: V8.OsClient
197
+ });
198
+
199
+ if (upResult.Code !== 1) return upResult;
200
+
201
+ // upResult.Data = [{ FileName, Path, FullPath, Size, ... }, ...]
202
+ var filePath = upResult.Data[0].Path; // 相对路径,存数据库
203
+ var fullUrl = upResult.Data[0].FullPath; // 完整 URL(公有桶)
204
+ ```
205
+
206
+ ### AI 应用超大资产断点续传
207
+
208
+ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进入 Base64、JSON 或 Jint。`microi_publish_application_directory_stream` 在协议 v3 下与 `@microi.net/cli` 共用同一套传输客户端:单文件大于 128 MiB 时自动切换为原始字节断点续传,小文件继续兼容旧版单请求链路。
209
+
210
+ - 默认分片 16 MiB;5 GiB 文件为 320 片。分片通过 `application/octet-stream` 发送,必须携带精确 `Content-Length` 与 SHA-256。
211
+ - 服务端逐片写入 HDFS 后重新流式回读校验;完成时按顺序合并、再次核对整文件 SHA-256,再生成不可变版本完整性标记。
212
+ - 会话 Id 由租户、应用、版本、路径和文件摘要确定。网络中断或进程重启后先读取远端状态,只续传缺失分片;已成功的相同摘要请求直接幂等返回。
213
+ - 吾码不为协议 v3 设置业务文件/目录字节上限,状态中的 `ApplicationAssetResumableProductSizeLimitBytes=0` 表示没有产品配置上限。每个对象仍受协议技术边界(最多 10000 片、单片最多 1 GiB)、JavaScript 安全整数、对象存储、磁盘、网关和网络条件约束。
214
+ - 该链路只允许通过能力鉴权的当前租户超级管理员,并继续受 `DisableFileUpload` 负向总开关控制;它不是普通用户上传或任意 HDFS 路径写入接口。
215
+ - 每个会话都在 `mci_ai_app_file` 保留审计记录,`StorageScope=ApplicationAssetMultipartSession`。管理员在 **系统引擎 → 超大文件上传记录** 查看状态、阶段、已传字节/分片、进度、心跳、错误和恢复建议;成功、失败和取消记录都不静默删除。
216
+
217
+ ### 普通业务上传的分层限制
218
+
219
+ 以下限制适用于 HTTP、表单、V8、移动端和旧版应用资产单请求,不适用于上面的受信任 v3 断点续传。Token 不是无限上传授权;普通入口必须在解码 Base64、解析图片或调用对象存储前执行服务端校验:
220
+
221
+ 1. **租户业务配置**:有效正数/布尔值按 `sys_osclients` 当前租户 → 代码默认值解析。租户可以按业务需要提高或降低默认值,不要求安装者维护额外环境变量或修改 `appsettings`。
222
+ 2. **平台绝对上限**:最终业务值再与代码内固定灾难保护上限取较小值,租户和安装参数都不能放大。
223
+ 3. **HTTP 解析上限**:Kestrel 请求正文与 Multipart 固定为 2048 MB,普通表单单值固定为 128 MB,是所有租户共享的请求解析硬顶。
224
+ 4. **字段级限制**:前端 `FileUpload` / `ImgUpload` / `RichText` 的 `MaxSize`、`MaxCount` 等只能与当前租户有效值取更小值,不能提高后端上限,也不能替代服务端校验。
225
+
226
+ 业务配置与固定边界:
227
+
228
+ | `sys_osclients` 字段 | 代码默认值 | 平台固定边界 |
229
+ |---|---:|---:|
230
+ | `DisableFileUpload` | `false`(关闭,即允许上传) | — |
231
+ | `FileUploadMaxFileMB` | 100 MB | 1024 MB |
232
+ | `FileUploadMaxRequestMB` | 200 MB | 2048 MB |
233
+ | `FileUploadMaxCount` | 10 | 100 |
234
+ | `FileUploadDailyUserQuotaMB` | 2048 MB | 10 TB |
235
+ | `FileUploadDailyTenantQuotaMB` | 20480 MB | 10 TB |
236
+
237
+ - `sys_osclients` 六个现行字段全部可空;`DisableFileUpload` 空值、无效值或 `0/false` 均表示允许上传,只有 `1/true` 才禁止。旧数据库尚未创建新物理字段时才回退读取 `FileUploadEnabled`,升级后旧正向字段即使为 `0` 也不得覆盖新字段的默认允许语义。`FileUploadMaxRequestMB` 指一次上传所有文件的业务合计大小,不等于 Kestrel HTTP 请求正文上限。
238
+ - 固定灾难保护和 HTTP/Multipart/Form 解析上限不属于安装配置;租户值即使更大也会被这些边界截断。最终单次总量还不能超过帐号或租户的有效日额度,单文件不能超过最终单次总量。
239
+ - `DisableFileUpload=1` 表示禁用当前租户上传,也会阻止 AI 应用资产断点续传;不能把内部发布协议当成绕过开关的后门。普通单请求继续受全局大小硬上限,v3 只移除产品级字节上限;租户配置刷新应走现有 SaaS 引擎重载和共享 Redis 发布订阅,不能依赖单节点内存。
240
+ - 帐号与租户每日额度在共享 Redis 中用单次原子脚本预留,支持多节点;Redis 不可用时失败关闭,不能降级成无限上传。
241
+ - 额度按 UTC 日期统计。为防并发重试绕过限制,后续对象存储失败也不退还已经预留的额度。
242
+ - 每日额度只阻断短期滥用;对象存储必须另外配置租户/桶总容量、账单告警、生命周期与实际用量对账。Redis 计数不能作为长期容量事实源。
243
+ - 标准表单字段上传按后端回读的 `diy_field.Config` 决定桶,不能按帐号等级覆盖字段策略。无字段上下文的普通上传默认只允许平台预定义私有一级目录;管理员可通过 `sys_config.HdfsUploadRules` 按真实角色明确扩展业务目录和公有权限,不能信任客户端自报角色或裸 `Limit:false`。源码、应用产物等仍使用独立受控发布流程。
244
+ - 反向代理、Ingress/IIS 的请求体上限应与进程级 HTTP 解析硬顶协调。字段自身的类型、后缀、大小和数量配置只能进一步收紧当前租户有效值,不能替代平台硬顶。
245
+
246
+ ### 复盘:“当前租户已停用文件上传”
247
+
248
+ - 该提示只表示当前运行环境命中的 `sys_osclients.DisableFileUpload` 被明确设置为 `1/true`(或仍在旧库兼容期且 `FileUploadEnabled=0`)。新字段缺列以外的空值、无效值和新租户都按默认允许,不能先把问题归因于 MinIO/HDFS。
249
+ - 新版响应同时返回 `DataAppend.ErrorType=TenantFileUploadDisabled`、`OsClient`、`ConfigField=DisableFileUpload`、`ExpectedValue=0` 和文档地址;客户端必须保留后端 `Msg/DataAppend`,不能只显示“上传失败”。
250
+ - 处理时先读取当前 API 进程的 `OsClient + OsClientType + OsClientNetwork`,再精确回读同三元组的启用记录并把 `DisableFileUpload` 关闭为 `0`。不能仅按租户名批量覆盖其它网络或环境记录。
251
+ - 保存后等待 SaaS 共享配置重载,再分别验证一个小公有图片和一个小私有文件。只有错误转为 endpoint、bucket、签名或 `Invalid URI` 后,才进入对象存储配置排查。
252
+ - 不要通过删除 Redis 日额度 Key、扩大文件大小上限或改成公有桶来解除租户停用;这些动作与开关无关,还会扩大安全风险。
253
+
254
+ ### AI / MCP 调整租户上传配额
255
+
256
+ 用户明确授权修改某个租户的上传额度时,AI 可以直接使用标准 MCP 完成,不要把应用层提示误判成阿里云 OSS、MinIO 或 S3 的存储配额,也不要先清 Redis:
257
+
258
+ 1. `microi_admin_table_data(operation: "query", tableName: "sys_osclients")` 按当前 `OsClient + OsClientType + OsClientNetwork` 查询,仅选择 `Id/OsClientType/OsClientNetwork`、`DisableFileUpload` 和五个现行 `FileUpload*` 配额字段;`FileUploadEnabled` 只用于诊断未升级旧节点,不读取秘密。
259
+ 2. 只更新上一步唯一实际生效记录,只有用户明确要求多个环境保持一致时才扩展。调用 `microi_admin_table_data(operation: "update")`,`id/row.Id` 固定已回读 Id,并传 `confirmExecution: "UPDATE:sys_osclients:<Id>"`,其余字段省略。
260
+ 3. MB 是存储单位:`20 GB = 20480 MB`。可修改字段为 `DisableFileUpload`、`FileUploadMaxFileMB`、`FileUploadMaxRequestMB`、`FileUploadMaxCount`、`FileUploadDailyUserQuotaMB`、`FileUploadDailyTenantQuotaMB`;不要再写旧 `FileUploadEnabled`。
261
+ 4. 保存后逐条远程回读;FormEngine 会排队重载 SaaS 运行配置,再用真实小文件上传做生效冒烟。只看到 MCP 返回“更新成功”不算验收。
262
+ 5. 提高每日配额保留当天已用计数,剩余额度为新上限减已用量。计数按 UTC 日期,失败上传不退款;除非用户明确授权事故处置,不得删除 Redis 配额 Key。
263
+
264
+ 租户 MCP 只能调整普通业务上传配置;平台固定灾难保护、HTTP/Multipart/Form 解析上限和反向代理上限不能通过 `sys_osclients` 绕过。协议 v3 的原始分片不读取普通单请求大小字段,但仍要求能力鉴权、总开关、版本快照、逐片/整文件哈希和审计。写入 `sys_osclients` 属于控制面操作,只允许当前租户的 `Level >= 9999` 管理身份,并且必须保留 MCP 审计与写后回读。
265
+
266
+ ### UniApp / H5 客户端直传路径规则
267
+
268
+ 移动端通过 `/api/HDFS/UniappUpload` 上传时,前端必须走 `microi.v8.js` 的 `V8.uploadFile`,不要在页面里手写 `uni.uploadFile`。客户端上传的 `Path` 与服务端 `V8.Method.Upload` 示例不同,必须是安全相对路径:
269
+
270
+ - 正确:`mall/pay-proof`、`mall/member/avatar`、`order/proof`
271
+ - 错误:`/mall/pay-proof`、`https://...`、`C:\...`、`../x`、`mall//x`、`~x`
272
+ - multipart 请求不能带 `Content-Type: application/json`,否则后端可能读不到 `Path` 表单字段并返回“移动端文件上传路径不合法!”
273
+ - `OsClient` 只能保留一个规范字段,避免同时提交 `OsClient`、`osclient` 或 query/header/formData 多处互相冲突。
274
+ - 生产 H5 不能只依赖 `uni.uploadFile`。页面从 `uni.chooseImage` 得到的 `tempFiles[0].file`、`tempFiles[0]`、`blob:` / `data:` 临时路径都要传给 `V8.uploadFile`,并设置 `preferFetch:true`;SDK 必须能用 `fetch + FormData` 兜底,否则线上可能报 `未找到 MicroiV8 上传适配器。`。
275
+
276
+ <!-- /microi-progressive:chunk -->
277
+ 热点分流与跨平台会话的完整规则见
278
+ [操作与原生边界](references/hotspot-offload-operations.md)。
279
+ ## 详细参考路由(渐进披露)
280
+ 仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。
281
+ - [references/progressive-01-公有桶-vs-私有桶.md](references/progressive-01-公有桶-vs-私有桶.md):公有桶 vs 私有桶;接口直接响应文件(下载/导出);通过 URL 列表批量下载并入库
282
+ - [references/progressive-02-office-文件在线编辑版本号规则.md](references/progressive-02-office-文件在线编辑版本号规则.md):Office 文件在线编辑版本号规则;ImgUpload / FileUpload 字段值兼容规则;安全注意
283
+ - 处理空目录标记或远程下载前,读取[完整存储边界](references/progressive-03-空目录标记与远程下载.md)。
284
+ <!-- microi-progressive:end -->