@microi.net/cli 5.6.0 → 5.6.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.
Files changed (54) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.mcp.json +1 -1
  5. package/.workbuddy-plugin/marketplace.json +2 -2
  6. package/.workbuddy-plugin/plugin.json +1 -1
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +7 -7
  9. package/assets/feature-matrix.json +129 -129
  10. package/cordis.patch.yml +1 -1
  11. package/package.json +1 -1
  12. package/scripts/codex-marketplace.json +20 -20
  13. package/scripts/mcp-codex-stdio-adapter.js +189 -189
  14. package/scripts/mcp-server.js +99 -99
  15. package/scripts/mcp-trae-windows-launcher.cmd +21 -21
  16. package/scripts/microi-cli-mcp.js +7 -7
  17. package/scripts/microi-cli.js +39 -36
  18. package/scripts/microi-codex-broker.js +447 -447
  19. package/scripts/microi-codex-router.js +582 -582
  20. package/scripts/microi-skills.meta.json +225 -222
  21. package/skills/.microi-skills-version.json +2 -2
  22. package/skills/.progressive-disclosure-manifest.json +37 -37
  23. package/skills/README.md +1 -1
  24. package/skills/app-store/SKILL.md +199 -182
  25. package/skills/dos-orm/SKILL.md +96 -81
  26. package/skills/message-notification/SKILL.md +152 -126
  27. package/skills/microi-client-frontend/SKILL.md +11 -5
  28. 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 +2 -2
  29. 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 +2 -2
  30. 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 +2 -2
  31. package/skills/microi-codex/SKILL.md +48 -46
  32. package/skills/microi-docs-coverage/SKILL.md +133 -123
  33. package/skills/microi-docs-coverage/references/capability-map.md +2 -2
  34. package/skills/microi-form-engine/SKILL.md +77 -53
  35. package/skills/microi-microservice/SKILL.md +17 -16
  36. package/skills/microi-sso/SKILL.md +91 -85
  37. package/skills/microi-system-delivery/SKILL.md +5 -4
  38. 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 +27 -22
  39. package/skills/module-engine/SKILL.md +17 -1
  40. package/skills/print-engine/SKILL.md +47 -39
  41. package/skills/scripts/sync-embedded-skills.mjs +46 -0
  42. package/skills/scripts/validate-progressive-disclosure.mjs +57 -52
  43. package/skills/system-observability/SKILL.md +16 -4
  44. package/skills/v8-api-config/SKILL.md +382 -378
  45. package/skills/v8-file-upload/SKILL.md +157 -154
  46. 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 +133 -133
  47. package/skills/v8-formengine-http/SKILL.md +19 -1
  48. package/skills/v8-security/SKILL.md +25 -25
  49. package/skills/v8-sql-query/SKILL.md +130 -128
  50. package/skills/v8-table-event/SKILL.md +29 -22
  51. 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 +19 -19
  52. package/skills/v8-workflow/SKILL.md +54 -45
  53. package/skills/workspace-conventions/SKILL.md +259 -236
  54. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +208 -208
@@ -3,121 +3,121 @@
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
5
  <!-- microi-progressive:chunk id=v8-file-upload-005 sha256=b09a96f25aef20eb8cbb204bd785cce370dcd2c9a28563c6f6d720fbdaa5218f -->
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
- ### AI 应用编译目录流式发布(首选)
35
-
36
- 发布 Web、UniApp、MicroService 的 `dist` / H5 编译目录时,必须优先使用 `microi_publish_application_directory_stream`,不要把每个文件读成 Base64 后传给 `ai_app_build`、`microi_publish_microservice` 或普通 JSON 接口。旧工具仅为小文件兼容保留。
37
-
38
- 标准流程:
39
-
40
- 1. 先运行不带 `confirmExecution` 的预检。MCP 按流计算 SHA-256,拒绝符号链接、`.git`、`node_modules`、密钥/`.env`、路径穿越、超过 20000 个文件或超过 20 GB 的垃圾目录;默认不发布 `.map`。
41
- 2. 确认后把 `confirmExecution` 精确设为 `appIdOrKey`。每个文件通过 multipart 原始流进入 `/api/V8Engine/UploadApplicationAssetStream`,不构造整文件 `Buffer`、Base64 或 JSON 文件体。
42
- 3. 文件只写不可变版本目录。全部成功后,清单确认接口只接收 `Path/Sha256/Size`,由 HDFS Provider 的 `CopyObject` 在服务端复制到 root 与 `latest`;非入口先复制,入口最后复制。
43
- 4. 历史版本 URL 保留语义版本;分享/在线使用 URL 使用不含版本号的 root 稳定地址。重试必须复用同一版本与摘要,不能覆盖已有但缺少完整性证明的历史对象。
44
- 5. 该控制面只允许当前 Token 租户的 `Level >= 9999` 交互式管理员;访问密钥会话不得发布。单文件、HTTP/Multipart 和每日额度仍然生效,不能把“使用流”理解成无限上传。
45
-
46
- 几十 MB **不是** Jint 或 HDFS 的固定上限。旧链路失败的原因是二进制先膨胀为约 `4/3` 的 Base64,又在 JSON、Jint 字符串、.NET 字符串/字节数组之间产生多份累计分配;文件数量、并发和当前进程内存共同决定触发点。描述问题时必须明确“旧 Base64/Jint 发布链路的累计分配”,不得写成“几十 MB 就达到 Jint 硬上限”。HDFS 上传本身应走二进制流。
47
-
48
- ```json
49
- {
50
- "appIdOrKey": "flower-store",
51
- "versionNo": "v1.2.0",
52
- "directory": "D:/build/flower-store/dist",
53
- "entryPath": "index.html",
54
- "changeSummary": "修复移动端布局",
55
- "confirmExecution": "flower-store"
56
- }
57
- ```
58
-
59
- 底层断点式单文件工具是 `microi_upload_application_asset_stream`。除诊断或精确恢复单文件外,不要只调用它而遗漏最终清单确认,否则稳定入口不会切换。
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
+ ### AI 应用编译目录流式发布(首选)
35
+
36
+ 发布 Web、UniApp、MicroService 的 `dist` / H5 编译目录时,必须优先使用 `microi_publish_application_directory_stream`,不要把每个文件读成 Base64 后传给 `ai_app_build`、`microi_publish_microservice` 或普通 JSON 接口。旧工具仅为小文件兼容保留。
37
+
38
+ 标准流程:
39
+
40
+ 1. 先运行不带 `confirmExecution` 的预检。MCP 按流计算 SHA-256,拒绝符号链接、`.git`、`node_modules`、密钥/`.env`、路径穿越、超过 20000 个文件或超过 20 GB 的垃圾目录;默认不发布 `.map`。
41
+ 2. 确认后把 `confirmExecution` 精确设为 `appIdOrKey`。每个文件通过 multipart 原始流进入 `/api/V8Engine/UploadApplicationAssetStream`,不构造整文件 `Buffer`、Base64 或 JSON 文件体。
42
+ 3. 文件只写不可变版本目录。全部成功后,清单确认接口只接收 `Path/Sha256/Size`,由 HDFS Provider 的 `CopyObject` 在服务端复制到 root 与 `latest`;非入口先复制,入口最后复制。
43
+ 4. 历史版本 URL 保留语义版本;分享/在线使用 URL 使用不含版本号的 root 稳定地址。重试必须复用同一版本与摘要,不能覆盖已有但缺少完整性证明的历史对象。
44
+ 5. 该控制面只允许当前 Token 租户的 `Level >= 9999` 交互式管理员;访问密钥会话不得发布。单文件、HTTP/Multipart 和每日额度仍然生效,不能把“使用流”理解成无限上传。
45
+
46
+ 几十 MB **不是** Jint 或 HDFS 的固定上限。旧链路失败的原因是二进制先膨胀为约 `4/3` 的 Base64,又在 JSON、Jint 字符串、.NET 字符串/字节数组之间产生多份累计分配;文件数量、并发和当前进程内存共同决定触发点。描述问题时必须明确“旧 Base64/Jint 发布链路的累计分配”,不得写成“几十 MB 就达到 Jint 硬上限”。HDFS 上传本身应走二进制流。
47
+
48
+ ```json
49
+ {
50
+ "appIdOrKey": "flower-store",
51
+ "versionNo": "v1.2.0",
52
+ "directory": "D:/build/flower-store/dist",
53
+ "entryPath": "index.html",
54
+ "changeSummary": "修复移动端布局",
55
+ "confirmExecution": "flower-store"
56
+ }
57
+ ```
58
+
59
+ 底层断点式单文件工具是 `microi_upload_application_asset_stream`。除诊断或精确恢复单文件外,不要只调用它而遗漏最终清单确认,否则稳定入口不会切换。
60
60
 
61
61
  | 类型 | `Limit` | 访问 URL | 用途 |
62
62
  |------|---------|---------|------|
63
- | 公有桶 | `false` | 直接拼接 `V8.SysConfig.FileServer + Path` | 产品图、Banner、公开文档 |
64
- | 私有桶 | `true` | 必须用 `V8.Method.GetPrivateFileUrl` 获取临时 URL | 合同、身份证、敏感数据 |
65
-
66
- ### 表单字段存储策略
67
-
68
- - 表单设计器的“禁止匿名访问”是服务端权威字段配置,不是管理员等级开关:未勾选即 `Limit=false`,勾选即 `Limit=true`。有当前表单新增/编辑权限的普通用户也可以按公有字段写入公有桶。
69
- - PC 图片、文件、富文本上传提交 `FormEngineKey + FieldId + SysMenuId`;编辑记录附加 `FormDataId`,TableChild 附加父子授权链。后端校验动作权限并回读当前租户 `diy_field.Config` 后覆盖客户端 `Limit/Path`。客户端篡改 `Limit=false`、选择其它字段或提交任意目录都不能获得公有写入。
70
- - 无字段上下文的普通上传保持私有;`ImgUpload/FileUpload` 老字段未保存 `Limit` 时兼容公有,`RichText` 缺失配置时默认私有。微信待审图片以及裁剪/压缩原图始终私有。
71
- - 验收使用“普通用户 + 超级管理员 × 公有字段 + 私有字段 × 新增 + 编辑”矩阵,同时核对响应 `Limit`、实际对象桶、公有匿名读取与私有签名读取。单独用 HTTP 请求传 `Limit=false` 不能证明字段策略正确。
72
-
73
- ### 默认 MinIO 桶名与安装验收
74
-
75
- - Microi 一键安装的默认私有桶固定为 `mci-private`,默认公有桶固定为 `mci-public`;禁止使用 `mci-publish` 等近似名称。
76
- - `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 开关;端点禁止携带用户名密码、桶路径、查询或片段。
77
- - 安装脚本创建 `mci-public` 后必须设置匿名下载权限,并把 `HDFS=MinIO`、内外网端点、AccessKey/SecretKey、`MinIOPrivateBucketName=mci-private`、`MinIOPublicBucketName=mci-public` 同步写入当前租户的 `sys_osclients`。
78
- - 安装脚本还必须同步当前有效 `sys_config`:`ApiBase` 使用对外可访问的 API 端口,`FileServer` 使用 `http://<访问IP>:<MinIO API端口>/mci-public`。`ApiBase` 不能误用 Web 前端端口,因为 V8 代码会直接在其后拼接 `/api/...` 或 `/apiengine/...`。
79
- - 安装验收必须使用真实登录 Token 和两个权威字段分别执行一次公有、私有上传:公有字段的对象匿名访问应返回 `200`,私有字段对象匿名访问应返回 `403`,私有文件通过授权签名 URL 访问应返回 `200`,并核对下载内容与上传内容一致。超级管理员可另用直接上传验证桶初始化;普通用户只传 `Limit=false` 的裸请求应继续落入私有桶。
80
-
81
- ### 复盘:签名 HEAD 被代理转换为 GET 导致上传后回读误报
82
-
83
- - 触发场景:`PutObject` 已返回成功、对象可通过 GET 下载,但公有桶和私有桶的上传后 `StatObject` 均对桶根路径返回 `AccessDenied`;常见于启用严格回读校验后,MinIO Endpoint 前的 Nginx 同时启用了缓存与默认的 `proxy_cache_convert_head on`。
84
- - 根因判断:S3 SigV4 的签名包含 HTTP 方法;代理把客户端签名的 HEAD 转为上游 GET 后会造成签名不一致。另一个可能原因是对象级凭据缺少桶级 `ListBucket` / `GetBucketLocation` 权限,因此不能只凭 `AccessDenied /bucket/` 推断对象未落盘,也不能把空 Region 当作唯一原因。
85
- - 通用规则:优先在 MinIO 代理位置设置 `proxy_cache_convert_head off`;若仍使用缓存,缓存键需区分 `$request_method`。平台不得跳过上传后回读:当 HEAD/Stat 失败时,使用同一凭据生成签名 GET,并以 `Range: bytes=0-0` 回读;禁用重定向,非空对象必须同时验证期望总长度与首字节,空对象验证长度为零,`404` 判不存在,`403`、网络错误和证据不足继续失败关闭。禁止记录或返回带签名查询参数的 URL。
86
- - 配置边界:只有实时回读证明 Endpoint、桶名、Region 或凭据确实错误时才修改 SaaS 配置;对象 GET 正常而仅 HEAD 失败时应修复代理或兼容回读路径,不能猜测内网地址、降低校验强度或轮换正常凭据。
87
- - 自动化检查:覆盖签名 GET 的单字节 Range、期望总长度、首字节实际读取、空对象、长度不符、重定向、`403` 和 `404`;真实环境同时验证公有桶与私有桶的 `Put -> Range GET -> 内容一致`,并对比相同签名在 GET 与 HEAD 方法下的响应。
88
-
89
- ### 复盘:旧空库缺少可选字段导致 MinIO 初始化后中断
90
-
91
- - 触发场景:MinIO 容器、私有桶和公有桶均已成功创建,但安装器更新 `sys_osclients` 时因旧库缺少 `NetworkIsInternet` 返回 `Unknown column`,整套安装停在 API 部署之前。
92
- - 根因:安装器在 API/Upgrade 尚未启动时依赖了并非 MinIO 必需、且存量数据库不保证存在的旧可选字段;同时只按 `OsClient` 更新且没有写后回读。
93
- - 通用规则:MinIO 安装前先校验真正必需的物理字段,并按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 唯一定位运行租户。内外网端点由 API 允许的启动项 `OsClientNetwork` 选择,安装器不得再写 `NetworkIsInternet`;配置更新后逐字段回读一致才继续。
94
- - 恢复规则:桶初始化成功而配置写入失败不需要删除桶或数据卷。中断的新安装应先备份现有 Compose 并记录绑定数据目录,只对对应编排执行不带 `-v` 的 `docker compose down`,保留数据恢复点后再使用最新版脚本;禁止直接删除数据库或对象存储目录。
95
- - 自动化检查:用一个不含 `NetworkIsInternet`、但包含 MinIO 必需字段的临时 MySQL 表执行 schema、唯一租户、UPDATE 和回读闭环;再插入重复三参数租户,断言安装器失败关闭且不批量覆盖。
96
-
97
- ### 复盘:MinIO 已可上传但系统设置仍指向官方地址
98
-
99
- - 触发场景:一键安装和桶初始化均成功,用户手工上传也成功,但读取系统设置时发现 `ApiBase`、`FileServer` 仍是空库模板中的官方地址。
100
- - 根因:安装流程只回写了 `sys_osclients` 的 MinIO 配置,没有同步前端和 V8 公共使用的 `sys_config` 地址字段。
101
- - 通用规则:数据库还原并创建默认桶后,必须按安装模式选择的访问 IP 和动态端口同时回写有效 `sys_config.ApiBase/FileServer`;其中 `ApiBase` 指向 API 服务,`FileServer` 指向公有桶根地址。
102
- - 自动化检查:安装完成后通过 `GetSysConfig` 回读两个字段,断言均使用本次访问 IP 和实际端口;再执行公有上传并使用 `FileServer + Path` 匿名下载,内容必须一致。
103
-
104
- 公开页面图片(首页 banner、商品主图、公开活动头像等)应返回公有 URL,例如 `V8.SysConfig.FileServer + Path`。不要把公有图片统一转成 `GetPrivateFileUrl` 的 `static-private` 签名地址;部分 H5/浏览器会因响应头或跨域策略触发 ORB/CORS 拦截,表现为 uni-app `<image>` 内层 `background-image: none`。
105
-
106
- ### `sys_user.Avatar` 固定使用私有桶
107
-
108
- - `sys_user` 是内部系统用户表,`Avatar` 可能暴露员工身份信息,因此字段配置必须保持 `ImgUpload.Limit=true`,上传端也必须显式传 `Limit:true`;自定义用户管理页不能因为绕过表单引擎而回退到公有上传。
109
- - 数据库继续只保存租户内相对路径,例如 `/tenant_demo/avatar/20240218/user.png`。历史公有头像迁移时,应把同一文件复制到私有桶的同一路径,确认私有对象可访问后再停用公有访问;不得为了迁移批量改写路径或制造重复日期目录。
110
- - PC、移动端、聊天、工作流等任何页面渲染 `sys_user.Avatar` 时,禁止 `FileServer + Avatar`、`GetServerPath(Avatar)` 或把相对路径直接交给 `<img>`。前端应调用 `DiyCommon.GetUserAvatarUrl(avatar, userId)`,接口/V8 应调用 `V8.Method.GetPrivateFileUrl({ FilePathName: avatar })`,并为临时 URL 设置短期缓存与失败占位图。
111
- - `ContactUserAvatar`、`FromUserAvatar`、`SenderAvatar` 等从 `sys_user.Avatar` 派生的快照字段同样按私有路径处理;消息数据只保存原始相对路径,不能把会过期的临时 URL 持久化到数据库。
112
-
113
- #### 复盘:字段改私有后卡片仍访问公有桶
114
-
115
- - 触发条件:把 `sys_user.Avatar` 改为 `Limit=true`,但模块卡片仍配置 `TableCardImgField=Avatar`。
116
- - 根因:通用卡片渲染器只读取了字段值,没有读取图片字段的 `Config.ImgUpload.Limit`,仍统一调用 `GetServerPath/FileServer`。
117
- - 修复规则:通用图片渲染器必须同时检查字段配置;私有字段先异步换取临时地址,`sys_user.Avatar` 还要有表名+字段名语义兜底,避免元数据缓存短暂陈旧时泄露到公有路径。
118
- - 验收断言:筛选一条有头像的系统用户,页面中 `/tenant_demo/avatar/` 的直接公有请求数必须为 0,私有代理图片 `naturalWidth > 0`,并同时验证无头像占位图不报错。
119
-
120
- 定制移动端项目的 Hero、Banner、音频、视频、字体等大资源也应优先上传到目标租户公有 HDFS,再通过 FileServer/CDN 引用;小型导航图标和离线关键素材才保留在主包。上传前可适度压缩,但必须在多尺寸截图或试听/试播中确认质量,禁止以明显失真换取包体扫描通过。完整移动端规则见 `microi-uniapp-frontend/SKILL.md`。
63
+ | 公有桶 | `false` | 直接拼接 `V8.SysConfig.FileServer + Path` | 产品图、Banner、公开文档 |
64
+ | 私有桶 | `true` | 必须用 `V8.Method.GetPrivateFileUrl` 获取临时 URL | 合同、身份证、敏感数据 |
65
+
66
+ ### 表单字段存储策略
67
+
68
+ - 表单设计器的“禁止匿名访问”是服务端权威字段配置,不是管理员等级开关:未勾选即 `Limit=false`,勾选即 `Limit=true`。有当前表单新增/编辑权限的普通用户也可以按公有字段写入公有桶。
69
+ - PC 图片、文件、富文本上传提交 `FormEngineKey + FieldId + SysMenuId`;编辑记录附加 `FormDataId`,TableChild 附加父子授权链。后端校验动作权限并回读当前租户 `diy_field.Config` 后覆盖客户端 `Limit/Path`。客户端篡改 `Limit=false`、选择其它字段或提交任意目录都不能获得公有写入。
70
+ - 无字段上下文的普通上传默认保持私有;管理员可通过普通系统设置 `HdfsUploadRules` 按真实角色授权业务目录,并显式允许公有文件。后端 v8.2.9+ 支持 `*`、`**`、`?`、字符集合/范围/排除和花括号候选,实际路径仍先做保留目录与穿越校验。`ImgUpload/FileUpload` 老字段未保存 `Limit` 时兼容公有,`RichText` 缺失配置时默认私有。微信待审图片以及裁剪/压缩原图始终私有。
71
+ - 验收使用“普通用户 + 超级管理员 × 公有字段 + 私有字段 × 新增 + 编辑”矩阵,同时核对响应 `Limit`、实际对象桶、公有匿名读取与私有签名读取。单独用 HTTP 请求传 `Limit=false` 不能证明字段策略正确。
72
+
73
+ ### 默认 MinIO 桶名与安装验收
74
+
75
+ - Microi 一键安装的默认私有桶固定为 `mci-private`,默认公有桶固定为 `mci-public`;禁止使用 `mci-publish` 等近似名称。
76
+ - `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 开关;端点禁止携带用户名密码、桶路径、查询或片段。
77
+ - 安装脚本创建 `mci-public` 后必须设置匿名下载权限,并把 `HDFS=MinIO`、内外网端点、AccessKey/SecretKey、`MinIOPrivateBucketName=mci-private`、`MinIOPublicBucketName=mci-public` 同步写入当前租户的 `sys_osclients`。
78
+ - 安装脚本还必须同步当前有效 `sys_config`:`ApiBase` 使用对外可访问的 API 端口,`FileServer` 使用 `http://<访问IP>:<MinIO API端口>/mci-public`。`ApiBase` 不能误用 Web 前端端口,因为 V8 代码会直接在其后拼接 `/api/...` 或 `/apiengine/...`。
79
+ - 安装验收必须使用真实登录 Token 和两个权威字段分别执行一次公有、私有上传:公有字段的对象匿名访问应返回 `200`,私有字段对象匿名访问应返回 `403`,私有文件通过授权签名 URL 访问应返回 `200`,并核对下载内容与上传内容一致。超级管理员可另用直接上传验证桶初始化;未命中管理员公有目录授权规则时,普通用户只传 `Limit=false` 的裸请求应继续落入私有桶。
80
+
81
+ ### 复盘:签名 HEAD 被代理转换为 GET 导致上传后回读误报
82
+
83
+ - 触发场景:`PutObject` 已返回成功、对象可通过 GET 下载,但公有桶和私有桶的上传后 `StatObject` 均对桶根路径返回 `AccessDenied`;常见于启用严格回读校验后,MinIO Endpoint 前的 Nginx 同时启用了缓存与默认的 `proxy_cache_convert_head on`。
84
+ - 根因判断:S3 SigV4 的签名包含 HTTP 方法;代理把客户端签名的 HEAD 转为上游 GET 后会造成签名不一致。另一个可能原因是对象级凭据缺少桶级 `ListBucket` / `GetBucketLocation` 权限,因此不能只凭 `AccessDenied /bucket/` 推断对象未落盘,也不能把空 Region 当作唯一原因。
85
+ - 通用规则:优先在 MinIO 代理位置设置 `proxy_cache_convert_head off`;若仍使用缓存,缓存键需区分 `$request_method`。平台不得跳过上传后回读:当 HEAD/Stat 失败时,使用同一凭据生成签名 GET,并以 `Range: bytes=0-0` 回读;禁用重定向,非空对象必须同时验证期望总长度与首字节,空对象验证长度为零,`404` 判不存在,`403`、网络错误和证据不足继续失败关闭。禁止记录或返回带签名查询参数的 URL。
86
+ - 配置边界:只有实时回读证明 Endpoint、桶名、Region 或凭据确实错误时才修改 SaaS 配置;对象 GET 正常而仅 HEAD 失败时应修复代理或兼容回读路径,不能猜测内网地址、降低校验强度或轮换正常凭据。
87
+ - 自动化检查:覆盖签名 GET 的单字节 Range、期望总长度、首字节实际读取、空对象、长度不符、重定向、`403` 和 `404`;真实环境同时验证公有桶与私有桶的 `Put -> Range GET -> 内容一致`,并对比相同签名在 GET 与 HEAD 方法下的响应。
88
+
89
+ ### 复盘:旧空库缺少可选字段导致 MinIO 初始化后中断
90
+
91
+ - 触发场景:MinIO 容器、私有桶和公有桶均已成功创建,但安装器更新 `sys_osclients` 时因旧库缺少 `NetworkIsInternet` 返回 `Unknown column`,整套安装停在 API 部署之前。
92
+ - 根因:安装器在 API/Upgrade 尚未启动时依赖了并非 MinIO 必需、且存量数据库不保证存在的旧可选字段;同时只按 `OsClient` 更新且没有写后回读。
93
+ - 通用规则:MinIO 安装前先校验真正必需的物理字段,并按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 唯一定位运行租户。内外网端点由 API 允许的启动项 `OsClientNetwork` 选择,安装器不得再写 `NetworkIsInternet`;配置更新后逐字段回读一致才继续。
94
+ - 恢复规则:桶初始化成功而配置写入失败不需要删除桶或数据卷。中断的新安装应先备份现有 Compose 并记录绑定数据目录,只对对应编排执行不带 `-v` 的 `docker compose down`,保留数据恢复点后再使用最新版脚本;禁止直接删除数据库或对象存储目录。
95
+ - 自动化检查:用一个不含 `NetworkIsInternet`、但包含 MinIO 必需字段的临时 MySQL 表执行 schema、唯一租户、UPDATE 和回读闭环;再插入重复三参数租户,断言安装器失败关闭且不批量覆盖。
96
+
97
+ ### 复盘:MinIO 已可上传但系统设置仍指向官方地址
98
+
99
+ - 触发场景:一键安装和桶初始化均成功,用户手工上传也成功,但读取系统设置时发现 `ApiBase`、`FileServer` 仍是空库模板中的官方地址。
100
+ - 根因:安装流程只回写了 `sys_osclients` 的 MinIO 配置,没有同步前端和 V8 公共使用的 `sys_config` 地址字段。
101
+ - 通用规则:数据库还原并创建默认桶后,必须按安装模式选择的访问 IP 和动态端口同时回写有效 `sys_config.ApiBase/FileServer`;其中 `ApiBase` 指向 API 服务,`FileServer` 指向公有桶根地址。
102
+ - 自动化检查:安装完成后通过 `GetSysConfig` 回读两个字段,断言均使用本次访问 IP 和实际端口;再执行公有上传并使用 `FileServer + Path` 匿名下载,内容必须一致。
103
+
104
+ 公开页面图片(首页 banner、商品主图、公开活动头像等)应返回公有 URL,例如 `V8.SysConfig.FileServer + Path`。不要把公有图片统一转成 `GetPrivateFileUrl` 的 `static-private` 签名地址;部分 H5/浏览器会因响应头或跨域策略触发 ORB/CORS 拦截,表现为 uni-app `<image>` 内层 `background-image: none`。
105
+
106
+ ### `sys_user.Avatar` 固定使用私有桶
107
+
108
+ - `sys_user` 是内部系统用户表,`Avatar` 可能暴露员工身份信息,因此字段配置必须保持 `ImgUpload.Limit=true`,上传端也必须显式传 `Limit:true`;自定义用户管理页不能因为绕过表单引擎而回退到公有上传。
109
+ - 数据库继续只保存租户内相对路径,例如 `/tenant_demo/avatar/20240218/user.png`。历史公有头像迁移时,应把同一文件复制到私有桶的同一路径,确认私有对象可访问后再停用公有访问;不得为了迁移批量改写路径或制造重复日期目录。
110
+ - PC、移动端、聊天、工作流等任何页面渲染 `sys_user.Avatar` 时,禁止 `FileServer + Avatar`、`GetServerPath(Avatar)` 或把相对路径直接交给 `<img>`。前端应调用 `DiyCommon.GetUserAvatarUrl(avatar, userId)`,接口/V8 应调用 `V8.Method.GetPrivateFileUrl({ FilePathName: avatar })`,并为临时 URL 设置短期缓存与失败占位图。
111
+ - `ContactUserAvatar`、`FromUserAvatar`、`SenderAvatar` 等从 `sys_user.Avatar` 派生的快照字段同样按私有路径处理;消息数据只保存原始相对路径,不能把会过期的临时 URL 持久化到数据库。
112
+
113
+ #### 复盘:字段改私有后卡片仍访问公有桶
114
+
115
+ - 触发条件:把 `sys_user.Avatar` 改为 `Limit=true`,但模块卡片仍配置 `TableCardImgField=Avatar`。
116
+ - 根因:通用卡片渲染器只读取了字段值,没有读取图片字段的 `Config.ImgUpload.Limit`,仍统一调用 `GetServerPath/FileServer`。
117
+ - 修复规则:通用图片渲染器必须同时检查字段配置;私有字段先异步换取临时地址,`sys_user.Avatar` 还要有表名+字段名语义兜底,避免元数据缓存短暂陈旧时泄露到公有路径。
118
+ - 验收断言:筛选一条有头像的系统用户,页面中 `/tenant_demo/avatar/` 的直接公有请求数必须为 0,私有代理图片 `naturalWidth > 0`,并同时验证无头像占位图不报错。
119
+
120
+ 定制移动端项目的 Hero、Banner、音频、视频、字体等大资源也应优先上传到目标租户公有 HDFS,再通过 FileServer/CDN 引用;小型导航图标和离线关键素材才保留在主包。上传前可适度压缩,但必须在多尺寸截图或试听/试播中确认质量,禁止以明显失真换取包体扫描通过。完整移动端规则见 `microi-uniapp-frontend/SKILL.md`。
121
121
 
122
122
  后台 `ImgUpload` 字段通常保存相对路径或 JSON:接口返回给移动端前先解析出 `Path`,再按公私有场景转换 URL:
123
123
 
@@ -129,32 +129,32 @@ function publicFileUrl(path) {
129
129
  }
130
130
  ```
131
131
 
132
- ### 私有桶临时 URL
133
-
134
- ```javascript
135
- var url = V8.Method.GetPrivateFileUrl({
132
+ ### 私有桶临时 URL
133
+
134
+ ```javascript
135
+ var url = V8.Method.GetPrivateFileUrl({
136
136
  FilePathName: '/private/contract/2024/abc.pdf',
137
137
  OsClient: V8.OsClient, // 可选,默认当前
138
138
  Expires: 600 // 可选,过期秒数
139
- });
140
- // 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
141
- ```
142
-
143
- - 普通客户端调用 `/apiengine/platform-private-file-url` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据;旧 Controller 路由仅作兼容转发,新代码禁止继续引用。
144
- - `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
145
- - 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
146
- - 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
147
- - 审计代理由平台后端回源对象存储,必须使用服务端内网端点生成上游地址,不能先生成公网 MinIO 签名地址再让后端绕公网回源。否则同一对象经内网上传成功后,可能在公网端点表现为 404。
148
- - 备份包、安装包、导出包等重要大文件不能只信任 `PutObject` 成功响应;必须在写入后通过同一私有桶和内网端点执行 `ObjectExist` 回读(可行时再核对大小或 SHA-256),通过后才能把业务记录标记为完成。
149
- - 平台任务若绕过 `V8.Method.Upload` 直接使用底层 `PutObject`,必须保证写入对象键与后续 `V8.Method.GetPrivateFileUrl` 的租户前缀规则一致。像 `/database-backups/` 这种服务端保留目录只能做严格白名单规范化,禁止对普通租户文件泛化去除 `/{OsClient}/` 隔离前缀。
150
- - 下载验收至少对新签发地址执行一次 `Range: bytes=0-0`,断言返回 `200/206`、内容长度大于 0 且不是 JSON 错误;完整交付再核对下载字节数或 SHA-256。若产品要求保留当前业务页,应在新浏览上下文打开代理地址,并在浏览器拦截弹窗时给出品牌化反馈。
151
- - 用户把私有链接转发给别人后,接收者没有有效登录身份时按“匿名访问”记录,禁止根据签发人猜测实际访问人;票据仍按原有效期失效。
152
- - 代理创建或包装失败时必须失败关闭,不得退回未经审计的真实签名 URL;行为日志中也禁止保存真实签名 URL、Token、Authorization 或存储密钥。
153
- - `Limit:false` 的公有文件允许通过 CDN/公有桶直接访问,不要求记录用户行为日志,也不要为了审计强制改走私有代理。
154
-
139
+ });
140
+ // 后端审计代理 URL,过期不可访问;真实对象存储签名 URL 不会返回前端
141
+ ```
142
+
143
+ - 普通客户端调用 `/apiengine/platform-private-file-url` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据;旧 Controller 路由仅作兼容转发,新代码禁止继续引用。
144
+ - `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
145
+ - 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
146
+ - 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
147
+ - 审计代理由平台后端回源对象存储,必须使用服务端内网端点生成上游地址,不能先生成公网 MinIO 签名地址再让后端绕公网回源。否则同一对象经内网上传成功后,可能在公网端点表现为 404。
148
+ - 备份包、安装包、导出包等重要大文件不能只信任 `PutObject` 成功响应;必须在写入后通过同一私有桶和内网端点执行 `ObjectExist` 回读(可行时再核对大小或 SHA-256),通过后才能把业务记录标记为完成。
149
+ - 平台任务若绕过 `V8.Method.Upload` 直接使用底层 `PutObject`,必须保证写入对象键与后续 `V8.Method.GetPrivateFileUrl` 的租户前缀规则一致。像 `/database-backups/` 这种服务端保留目录只能做严格白名单规范化,禁止对普通租户文件泛化去除 `/{OsClient}/` 隔离前缀。
150
+ - 下载验收至少对新签发地址执行一次 `Range: bytes=0-0`,断言返回 `200/206`、内容长度大于 0 且不是 JSON 错误;完整交付再核对下载字节数或 SHA-256。若产品要求保留当前业务页,应在新浏览上下文打开代理地址,并在浏览器拦截弹窗时给出品牌化反馈。
151
+ - 用户把私有链接转发给别人后,接收者没有有效登录身份时按“匿名访问”记录,禁止根据签发人猜测实际访问人;票据仍按原有效期失效。
152
+ - 代理创建或包装失败时必须失败关闭,不得退回未经审计的真实签名 URL;行为日志中也禁止保存真实签名 URL、Token、Authorization 或存储密钥。
153
+ - `Limit:false` 的公有文件允许通过 CDN/公有桶直接访问,不要求记录用户行为日志,也不要为了审计强制改走私有代理。
154
+
155
155
  <!-- /microi-progressive:chunk -->
156
156
  <!-- microi-progressive:chunk id=v8-file-upload-006 sha256=8d4fedab2418c7df7973b78d4b9d6c5fa77907929d401800689cd1b5b468f32f -->
157
- ## 接口直接响应文件(下载/导出)
157
+ ## 接口直接响应文件(下载/导出)
158
158
 
159
159
  接口引擎需要在配置中开启【**响应文件**】选项,然后返回特殊结构:
160
160
 
@@ -90,7 +90,25 @@ URL 中的表名小写最稳,平台不区分大小写。匿名版本目前**
90
90
  - 客户端新增、修改、删除分别校验真实菜单的 `Add`、`Edit`、`Del` 权限。`SqlWhere` / `SqlJoin` 是模块查询过滤,不是行级写权限:不得把它们追加到写入 SQL,也不得因查询包含 Join 拒绝已获授权的主表写入。
91
91
  - 进入 `SubmitBeforeServerV8` / `SubmitAfterServerV8` 后,事件内 FormEngine/数据库调用与接口引擎一样属于可信服务器执行,可实现当前租户内的跨表事务。需要“只能修改本人数据”等业务约束以及归属字段写入时,应在这里或专用接口引擎中完成。
92
92
 
93
- ## Body 结构(POST JSON)
93
+ ## 原生详情关联数据
94
+
95
+ - `POST /api/FormEngine/GetFormRelatedData` 接收父表 `ParentFormEngineKey`、父记录
96
+ `ParentTableRowId`、真实 `_SysMenuId`,以及固定 `RelatedType`:
97
+ `Counts/DataLog/DataComment/DataVersion`。身份、租户来自 DiyToken,不能透传请求的可信标记。
98
+ - `AuthorizeClientTableOperationAsync(..., 'Read')` 仅完成表/菜单授权,不证明父行可读。
99
+ 必须再执行 Client `GetFormDataAsync`,让父记录不存在或 `ServerDataV8` 拒绝阻断关联查询。
100
+ - 固定辅助表查询使用服务端新构造的 CLR 参数与可信来源;`JObject` 中写
101
+ `_InvokeType:'Server'` 仍是非可信参数。不得通过给普通角色开放辅助表权限来修复此类拒绝。
102
+ - 日志和版本返回固定元信息,`DataAppend.HistoryContentMode='MetadataOnly'`;
103
+ 当前行脱敏不授予历史 `Content/Data` 原文。客户端不能据元信息开放预览、对比、加载。
104
+ - 评论必须同时按真实 `TableId + TableRowId` 过滤。缺少 `TableId` 时,Counts 返回
105
+ `DataComment=null`,以 `DataAppend.DataCommentUnavailableReason/Message` 显式说明;
106
+ 评论详情失败。不能按单键兼容、猜旧数据归属、假报零条或承诺不存在的可用升级包。
107
+ - 回归须区分实际 HTTP 与替身组件测试:普通角色父行可读、同菜单另一归属被 DataFilter
108
+ 拒绝、父 Id 不存在、同 Id 不同表、伪造租户/类型/可信标记、历史敏感原文不返回都需验收。
109
+ 安全评论绑定写入及旧数据处理须独立交付后才能宣称评论完整可用。
110
+
111
+ ## Body 结构(POST JSON)
94
112
 
95
113
  ```jsonc
96
114
  {
@@ -9,17 +9,17 @@ description: Microi V8 安全指南。用于审查 DiyToken 与权限、可逆
9
9
 
10
10
  你正在开发 Microi 吾码平台的 V8 引擎代码,必须遵守以下安全规范。
11
11
 
12
- 访问密钥由 `microi_list_my_access_keys`、`microi_create_my_access_key`、`microi_revoke_my_access_key` 管理,只允许当前用户、限期、最小 scope,明文仅创建时返回一次。外部身份回调固定为 `/api/ExternalLogin/Callback`,服务端校验租户、Provider、state、redirect 和回调域名,验证成功后仍签发 DiyToken。
13
-
14
- 官方升级资源属于独立控制面。`get-microi-upgrade-resource` 可以匿名读取固定白名单,但 `Publish/PublishBatch` 必须调用仅绑定该 Managed ApiEngineKey 的 `V8.Method.AuthorizeOfficialResourcePublish()`:固定 `iTdos` 官方租户、拒绝访问密钥会话,并从主库复核当前用户、状态和平台管理员角色。禁止只相信 `V8.CurrentUser.Level`,也禁止把这个可信原子复用于普通接口、租户 Hook 或表单事件;资源校验、SHA 乐观锁、事务行锁、写入及回读仍由 Managed V8 编排。
12
+ 访问密钥由 `microi_list_my_access_keys`、`microi_create_my_access_key`、`microi_revoke_my_access_key` 管理,只允许当前用户、限期、最小 scope,明文仅创建时返回一次。外部身份回调固定为 `/api/ExternalLogin/Callback`,服务端校验租户、Provider、state、redirect 和回调域名,验证成功后仍签发 DiyToken。
13
+
14
+ 官方升级资源属于独立控制面。`get-microi-upgrade-resource` 可以匿名读取固定白名单,但 `Publish/PublishBatch` 必须调用仅绑定该 Managed ApiEngineKey 的 `V8.Method.AuthorizeOfficialResourcePublish()`:固定 `iTdos` 官方租户、拒绝访问密钥会话,并从主库复核当前用户、状态和平台管理员角色。禁止只相信 `V8.CurrentUser.Level`,也禁止把这个可信原子复用于普通接口、租户 Hook 或表单事件;资源校验、SHA 乐观锁、事务行锁、写入及回读仍由 Managed V8 编排。
15
15
 
16
16
  <!-- microi-progressive:begin -->
17
17
  <!-- microi-progressive:chunk id=v8-security-000 sha256=d03ee34e72925db55f9022de26dfc251c0d3d69fac52ae6a910d42ddfc142ae7 -->
18
18
  ## 0. 租户动态系统设置与密钥边界
19
19
 
20
- 第三方密钥(微信、支付宝、OpenAI、阿里云、ERP、SMTP)**禁止**硬编码在 V8 代码或前端。公开的租户配置必须建成当前租户 `sys_config` 的实体字段;敏感或仅供后端使用的租户业务配置保存到 `mci_system_setting`。数据库、Redis、MongoDB、MinIO、MQ 等部署控制面仍由主库 `sys_osclients` 托管,子租户不能修改。
21
-
22
- 能力是否启用、入口是否显示、公开交互模式等 Bool/Enum 配置即使属于登录或第三方集成,也必须放在 `sys_config`;API Key、ClientSecret、RP ID、Origin、Issuer、Scope、供应商地址等后端参数才放在 `mci_system_setting`。禁止给同一个新配置双写两张表。迁移旧开关时采用“新 `sys_config` 显式值 → 旧私密 Key → 存量安全默认”的只读回退,并从私密设置的列表、保存和删除入口移除旧 Key。
20
+ 第三方密钥(微信、支付宝、OpenAI、阿里云、ERP、SMTP)**禁止**硬编码在 V8 代码或前端。公开的租户配置必须建成当前租户 `sys_config` 的实体字段;敏感或仅供后端使用的租户业务配置保存到 `mci_system_setting`。数据库、Redis、MongoDB、MinIO、MQ 等部署控制面仍由主库 `sys_osclients` 托管,子租户不能修改。
21
+
22
+ 能力是否启用、入口是否显示、公开交互模式等 Bool/Enum 配置即使属于登录或第三方集成,也必须放在 `sys_config`;API Key、ClientSecret、RP ID、Origin、Issuer、Scope、供应商地址等后端参数才放在 `mci_system_setting`。禁止给同一个新配置双写两张表。迁移旧开关时采用“新 `sys_config` 显式值 → 旧私密 Key → 存量安全默认”的只读回退,并从私密设置的列表、保存和删除入口移除旧 Key。
23
23
 
24
24
  ```javascript
25
25
  // ✅ 浏览器/前端 V8 只读取 sys_config 的浏览器安全投影
@@ -35,9 +35,9 @@ var clientSecret = privateSettings['Login.Gitee.ClientSecret'];
35
35
  var openaiKey = 'sk-xxxxxxxxxx';
36
36
  ```
37
37
 
38
- `V8.OsClientModel` 与兼容别名 `V8.ClientModel` 均为独立脱敏副本:数据库连接、AuthSecret、Redis、对象存储、MQ、MQTT、Search 的地址与凭据不会注入脚本。存量租户业务字段只作兼容,新增 Secret 不得继续依赖 `V8.OsClientModel`。
39
-
40
- 接口引擎需要保存可逆的接口私有密码或 Token 时,使用 `V8.Method.ProtectApiEngineSecret(value)` 写入密文,读取时使用 `V8.Method.UnprotectApiEngineSecret(cipher)`。宿主固定绑定当前 `OsClient + ApiEngineKey`,V8 不能指定租户、Purpose 或密钥;同租户其它接口引擎也不能解密。禁止继续用 `V8.OsClientModel.AuthSecret/DbConn` 自行派生 AES 密钥。列表仍只返回脱敏元数据,解密前仍要校验当前用户、行归属和业务权限,原文不得进入日志、审计或匿名响应。
38
+ `V8.OsClientModel` 与兼容别名 `V8.ClientModel` 均为独立脱敏副本:数据库连接、AuthSecret、Redis、对象存储、MQ、MQTT、Search 的地址与凭据不会注入脚本。存量租户业务字段只作兼容,新增 Secret 不得继续依赖 `V8.OsClientModel`。
39
+
40
+ 接口引擎需要保存可逆的接口私有密码或 Token 时,使用 `V8.Method.ProtectApiEngineSecret(value)` 写入密文,读取时使用 `V8.Method.UnprotectApiEngineSecret(cipher)`。宿主固定绑定当前 `OsClient + ApiEngineKey`,V8 不能指定租户、Purpose 或密钥;同租户其它接口引擎也不能解密。禁止继续用 `V8.OsClientModel.AuthSecret/DbConn` 自行派生 AES 密钥。列表仍只返回脱敏元数据,解密前仍要校验当前用户、行归属和业务权限,原文不得进入日志、审计或匿名响应。
41
41
 
42
42
  `V8.SysConfig` 按运行端采用不同权限投影,且任何运行端都不存在 `PublicSettings` 属性。浏览器/前端 V8 只得到匿名 `GetSysConfig` 的独立脱敏 `sys_config` 投影;`mci_system_setting` 的任何记录都不会进入浏览器。后端接口引擎和后端 V8 事件得到当前租户完整、独立的 `sys_config`,全部启用的 `mci_system_setting` 则放在 `V8.SysConfig.ServerPrivateSettings`,Secret 由可信后端按租户解密。独立节点避免动态 Key 覆盖 `sys_config` 实体字段,也让前后端边界可审计。子租户调用 `V8.FormEngine.GetSysConfig(...)` 时仍强制使用当前 `OsClient`,不能借缓存命中读取其它租户配置。
43
43
 
@@ -65,7 +65,7 @@ Secret 只通过租户管理员专用端点写入租户绑定的认证密文。
65
65
  - 多节点保存连接使用按 `OsClient + DbKey` 隔离的分布式锁,并由数据库唯一索引兜底;同步数据和附件仍必须使用业务幂等键,锁不能替代唯一约束、状态机或 inbox/outbox。
66
66
 
67
67
  <!-- /microi-progressive:chunk -->
68
- <!-- microi-progressive:chunk id=v8-security-001 sha256=bcb95a6c33b3abb6bfce13589e21ce56ff2aea8578d05180f2f2538cc5117fb8 -->
68
+ <!-- microi-progressive:chunk id=v8-security-001 sha256=bcb95a6c33b3abb6bfce13589e21ce56ff2aea8578d05180f2f2538cc5117fb8 -->
69
69
  ## 0.5 接口引擎配置安全
70
70
 
71
71
  代码以外,接口本身的配置项也是安全防线(详见 `v8-api-config/SKILL.md`):
@@ -75,21 +75,21 @@ Secret 只通过租户管理员专用端点写入租户绑定的认证密文。
75
75
  | `IsAnonymous = false` | 非公开接口默认关闭,防止匿名调用越权 |
76
76
  | `StopHttp = true` | 内部接口(核心扣款、内部计算)防止外部直接 HTTP 调用 |
77
77
  | `LockKey = ...` | 写操作类接口(对账、补单)防止并发执行 |
78
- | `RateLimit = 60/m` | 公开接口(验证码、登录)防爬虫 |
79
- | `LogParam = true` | 支付/审计类接口记录请求 |
80
- | `ResponseType = HTTP` | 标准协议需要状态码、重定向、XML/纯文本或特定响应头 |
81
-
82
- `ResponseType=HTTP` 不是任意响应头旁路。普通 V8 只能返回安全白名单头,`Location` 经过站内/HTTPS 校验;Host、Content-Length、逐跳头始终禁止。`Set-Cookie` 等高风险头只接受平台可信原子的进程内签名结果,V8 不能获得或伪造签名密钥。需要协议编解码、签名验签或密钥隔离时,公开地址仍由 Managed 接口引擎拥有,C# 只实现精确 Key 可调用的最小 `V8.Method` 原子,禁止恢复业务 Controller。
83
-
84
- `ApiAddress` 使用 `{OsClient}`、`{ConnectionKey}` 路径模板时,模板值是权威值并覆盖同名 Query/Form/JSON 参数;多模板歧义必须失败关闭,防止跨租户或跨连接参数混淆。
85
-
86
- ### 表单上传的防篡改边界
87
-
88
- - `ImgUpload/FileUpload/RichText` 的“禁止匿名访问”属于权威业务配置,不是用户等级策略。普通用户通过当前菜单、表和新增/编辑动作授权后,后端应重新读取 `diy_field.Config` 决定公有桶或私有桶;禁止把所有非超级管理员上传一刀切为私有桶。
89
- - 恢复公有字段语义不能退回信任客户端 `Limit=false`。请求必须携带表、字段、菜单、记录及必要的 TableChild 上下文,后端校验字段归属、组件类型和操作权限,并固定控件对应的安全一级目录。
90
- - 没有可验证字段上下文的普通上传保持私有;微信待审图片、裁剪/压缩原图等更强规则继续优先。测试必须同时证明“公有字段可用”和“私有字段/伪造上下文不可绕过”。
91
-
92
- <!-- /microi-progressive:chunk -->
78
+ | `RateLimit = 60/m` | 公开接口(验证码、登录)防爬虫 |
79
+ | `LogParam = true` | 支付/审计类接口记录请求 |
80
+ | `ResponseType = HTTP` | 标准协议需要状态码、重定向、XML/纯文本或特定响应头 |
81
+
82
+ `ResponseType=HTTP` 不是任意响应头旁路。普通 V8 只能返回安全白名单头,`Location` 经过站内/HTTPS 校验;Host、Content-Length、逐跳头始终禁止。`Set-Cookie` 等高风险头只接受平台可信原子的进程内签名结果,V8 不能获得或伪造签名密钥。需要协议编解码、签名验签或密钥隔离时,公开地址仍由 Managed 接口引擎拥有,C# 只实现精确 Key 可调用的最小 `V8.Method` 原子,禁止恢复业务 Controller。
83
+
84
+ `ApiAddress` 使用 `{OsClient}`、`{ConnectionKey}` 路径模板时,模板值是权威值并覆盖同名 Query/Form/JSON 参数;多模板歧义必须失败关闭,防止跨租户或跨连接参数混淆。
85
+
86
+ ### 表单上传的防篡改边界
87
+
88
+ - `ImgUpload/FileUpload/RichText` 的“禁止匿名访问”属于权威业务配置,不是用户等级策略。普通用户通过当前菜单、表和新增/编辑动作授权后,后端应重新读取 `diy_field.Config` 决定公有桶或私有桶;禁止把所有非超级管理员上传一刀切为私有桶。
89
+ - 恢复公有字段语义不能退回信任客户端 `Limit=false`。请求必须携带表、字段、菜单、记录及必要的 TableChild 上下文,后端校验字段归属、组件类型和操作权限,并固定控件对应的安全一级目录。
90
+ - 没有字段上下文的普通上传默认保持私有安全目录;管理员可在普通 `sys_config.HdfsUploadRules` 按可信角色授予业务目录(含有界 glob)与明确公有权限。必须先拒绝实际路径中的通配符、穿越和平台保留目录,再做模式匹配;通配符不是匿名/跨租户/发布授权。微信待审图片、头像、裁剪/压缩原图等更强规则继续优先。测试同时证明公有字段/目录可用、私有请求/伪造上下文/保留目录不可绕过。
91
+
92
+ <!-- /microi-progressive:chunk -->
93
93
  <!-- microi-progressive:chunk id=v8-security-002 sha256=7229e5f8d200c0dfbab08752405f5e88d7edc80e9e92b6802880c022b09ca893 -->
94
94
  ## 1. 防 SQL 注入
95
95