@microi.net/cli 5.2.1 → 5.2.3

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 (29) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/assets/build-meta.json +5 -5
  7. package/cordis.patch.yml +1 -1
  8. package/package.json +1 -1
  9. package/scripts/mcp-server.js +1 -1
  10. package/scripts/microi-codex-broker.js +12 -2
  11. package/scripts/microi-skills.meta.json +199 -199
  12. package/skills/.microi-skills-version.json +2 -2
  13. package/skills/app-store/SKILL.md +63 -11
  14. package/skills/microi-client-frontend/SKILL.md +15 -1
  15. package/skills/microi-form-engine/SKILL.md +106 -84
  16. package/skills/microi-form-engine/references/component-catalog.md +74 -3
  17. package/skills/microi-form-layout/SKILL.md +16 -7
  18. 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 +4 -0
  19. 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 +4 -0
  20. package/skills/microi-system-delivery/SKILL.md +13 -13
  21. package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +53 -53
  22. package/skills/microi-uniapp-frontend/SKILL.md +9 -1
  23. package/skills/module-engine/SKILL.md +8 -8
  24. package/skills/module-engine/references/module-config.md +204 -204
  25. package/skills/v8-api-config/SKILL.md +167 -154
  26. package/skills/v8-cache-pattern/SKILL.md +114 -114
  27. package/skills/v8-file-upload/SKILL.md +28 -3
  28. 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 +1 -1
  29. package/skills/workspace-conventions/SKILL.md +11 -0
@@ -1,69 +1,69 @@
1
1
  ---
2
2
  name: v8-cache-pattern
3
- description: Microi V8 Redis 缓存与管理模式。用于读写 V8.Cache、租户缓存命名、TTL 策略、防陈旧数据,以及使用 Redis 管理器页面或 MCP 检索、统计、查看和维护 String、Hash、List、Set、Sorted Set、Stream。
3
+ description: Microi V8 Redis 缓存与管理模式。用于读写 V8.Cache、租户缓存命名、TTL 策略、防陈旧数据,以及使用 Redis 管理器页面或 MCP 检索、统计、查看和维护 String、Hash、List、Set、Sorted Set、Stream。
4
4
  ---
5
5
 
6
6
  > **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
7
7
 
8
8
  # Microi V8 Redis 缓存模式
9
9
 
10
- 你正在开发 Microi 吾码平台的 V8 引擎代码,需要使用 Redis 缓存提升性能。V8 只获得当前租户的安全缓存代理,不会获得 Redis `IDatabase`、连接管理或服务器扫描能力。
10
+ 你正在开发 Microi 吾码平台的 V8 引擎代码,需要使用 Redis 缓存提升性能。V8 只获得当前租户的安全缓存代理,不会获得 Redis `IDatabase`、连接管理或服务器扫描能力。
11
11
 
12
- ## V8.Cache API
12
+ ## V8.Cache API
13
13
 
14
14
  | 方法 | 说明 | 返回值 |
15
15
  |------|------|--------|
16
16
  | `V8.Cache.Set(key, value, expire)` | 设置缓存 | `boolean` |
17
- | `V8.Cache.Get(key)` | 获取缓存 | `string \| null` |
18
- | `V8.Cache.Remove(key)` | 删除缓存 | `boolean` |
19
- | `V8.Cache.KeyExist(key)` | 是否存在(兼容旧版运行时的真实方法名) | `boolean` |
20
- | `V8.Cache.HashSet(key, field, value)` | 写入 Hash 字段 | `boolean` |
21
- | `V8.Cache.HashGet(key, field)` | 读取 Hash 字段 | `string \| null` |
22
- | `V8.Cache.HashGetAll(key)` | 读取全部 Hash 字段 | Hash 条目数组 |
23
- | `V8.Cache.HashDelete(key, field)` | 删除 Hash 字段 | `boolean` |
24
- | `V8.Cache.HashIncrement(key, field, amount)` | 原子增减数值字段 | `number` |
25
-
26
- > 需要把接口引擎复制到不同版本的 Microi 环境时,统一使用 `V8.Cache.KeyExist(key)`。部分新版本可能提供 `Exists` 别名,但旧版运行时没有该方法。
27
-
28
- > 新运行时把逻辑 Key 自动规范为 `Microi:${V8.OsClient}:{逻辑Key}`;已带当前租户完整前缀的历史 Key 不会重复添加。任何其它租户的 `Microi:` 前缀都会被拒绝,而不是改写后继续执行。
29
-
30
- Hash 适合保存同一对象的多个独立字段或原子计数:
31
-
32
- ```javascript
33
- var hashKey = 'ProductStock:' + V8.Param.productId;
34
- V8.Cache.HashSet(hashKey, 'Available', '120');
35
- V8.Cache.HashSet(hashKey, 'Reserved', '8');
36
-
37
- var available = V8.Cache.HashGet(hashKey, 'Available');
38
- var allFields = V8.Cache.HashGetAll(hashKey);
39
- var reserved = V8.Cache.HashIncrement(hashKey, 'Reserved', 1);
40
-
41
- V8.Cache.HashDelete(hashKey, 'Reserved');
42
- ```
43
-
44
- `HashIncrement` 的 `amount` 可以为负数。当前 V8 Hash API 不提供独立 TTL 设置;需要自动过期时,优先把对象序列化为 String 后用 `Set(key, value, expire)`,或由受控 Redis 管理流程设置整 Key 的 TTL。
45
-
46
- ## Redis 管理器与 MCP
47
-
48
- 平台 Redis 管理器固定路由为 `#/mci-redis-manager`:
49
-
50
- - 已登录平台管理员可使用当前租户默认 Redis,并可管理保存于主租户 `mci_redis_connection` 表的额外连接;记录必须按 `TenantOsClient` 隔离,密码只在后端加密保存且永不回传前端。
51
- - 未登录时只允许创建当前页面内存中的临时连接;不得加载当前租户 Redis、已保存连接或缓存中的旧用户信息,刷新页面后必须清空临时凭据。
52
- - Key 列表必须使用 `SCAN` 游标分页,禁止在生产 Redis 上使用阻塞式 `KEYS *`。内容查看支持 String、Hash、List、Set、Sorted Set、Stream;集合内容要分页并限制单次条数。
53
- - 写入 Hash/List/Set/Sorted Set 时先完整解析 JSON,再覆盖旧 Key;删除、覆盖、重命名和 TTL 变更属于破坏性操作,必须先展示目标连接、数据库与 Key 并要求明确确认。
54
- - 临时匿名接口只开放白名单操作,不开放任意 Redis 命令、Lua、`FLUSHALL` 或 `FLUSHDB`;设置短连接超时、访问频率限制、单次 Key 数量和内容大小上限。
55
-
56
- MCP 默认操作当前 MCP `OsClient` 的租户 Redis;额外连接只传管理页保存后的 `connectionId`,禁止在 MCP 参数、日志或回答中传递 Redis 密码。
57
-
58
- | MCP 工具 | 用途 | 确认规则 |
59
- |------|------|------|
60
- | `microi_redis_statistics` | 服务器、内存、客户端、命中率与 Key 类型统计 | 只读 |
61
- | `microi_redis_list_keys` | SCAN 分页检索 Key、类型、TTL、内存估算 | 只读 |
62
- | `microi_redis_get_key` | 分页查看单个 Key 内容 | 只读 |
63
- | `microi_redis_delete_keys` | 单个或批量删除,最多 500 个 | `confirmExecution="DELETE"` |
64
- | `microi_redis_replace_value` | 新建或覆盖 String/Hash/List/Set/Sorted Set | `confirmExecution` 等于完整 Key 或 `EXECUTE` |
65
- | `microi_redis_rename_key` | 不覆盖目标的 Key 重命名 | `confirmExecution` 等于新 Key 或 `EXECUTE` |
66
- | `microi_redis_set_ttl` | `-1` 永久、`0` 删除、正数为秒 | `confirmExecution` 等于完整 Key 或 `EXECUTE` |
17
+ | `V8.Cache.Get(key)` | 获取缓存 | `string \| null` |
18
+ | `V8.Cache.Remove(key)` | 删除缓存 | `boolean` |
19
+ | `V8.Cache.KeyExist(key)` | 是否存在(兼容旧版运行时的真实方法名) | `boolean` |
20
+ | `V8.Cache.HashSet(key, field, value)` | 写入 Hash 字段 | `boolean` |
21
+ | `V8.Cache.HashGet(key, field)` | 读取 Hash 字段 | `string \| null` |
22
+ | `V8.Cache.HashGetAll(key)` | 读取全部 Hash 字段 | Hash 条目数组 |
23
+ | `V8.Cache.HashDelete(key, field)` | 删除 Hash 字段 | `boolean` |
24
+ | `V8.Cache.HashIncrement(key, field, amount)` | 原子增减数值字段 | `number` |
25
+
26
+ > 需要把接口引擎复制到不同版本的 Microi 环境时,统一使用 `V8.Cache.KeyExist(key)`。部分新版本可能提供 `Exists` 别名,但旧版运行时没有该方法。
27
+
28
+ > 新运行时把逻辑 Key 自动规范为 `Microi:${V8.OsClient}:{逻辑Key}`;已带当前租户完整前缀的历史 Key 不会重复添加。任何其它租户的 `Microi:` 前缀都会被拒绝,而不是改写后继续执行。
29
+
30
+ Hash 适合保存同一对象的多个独立字段或原子计数:
31
+
32
+ ```javascript
33
+ var hashKey = 'ProductStock:' + V8.Param.productId;
34
+ V8.Cache.HashSet(hashKey, 'Available', '120');
35
+ V8.Cache.HashSet(hashKey, 'Reserved', '8');
36
+
37
+ var available = V8.Cache.HashGet(hashKey, 'Available');
38
+ var allFields = V8.Cache.HashGetAll(hashKey);
39
+ var reserved = V8.Cache.HashIncrement(hashKey, 'Reserved', 1);
40
+
41
+ V8.Cache.HashDelete(hashKey, 'Reserved');
42
+ ```
43
+
44
+ `HashIncrement` 的 `amount` 可以为负数。当前 V8 Hash API 不提供独立 TTL 设置;需要自动过期时,优先把对象序列化为 String 后用 `Set(key, value, expire)`,或由受控 Redis 管理流程设置整 Key 的 TTL。
45
+
46
+ ## Redis 管理器与 MCP
47
+
48
+ 平台 Redis 管理器固定路由为 `#/mci-redis-manager`:
49
+
50
+ - 已登录平台管理员可使用当前租户默认 Redis,并可管理保存于主租户 `mci_redis_connection` 表的额外连接;记录必须按 `TenantOsClient` 隔离,密码只在后端加密保存且永不回传前端。
51
+ - 未登录时只允许创建当前页面内存中的临时连接;不得加载当前租户 Redis、已保存连接或缓存中的旧用户信息,刷新页面后必须清空临时凭据。
52
+ - Key 列表必须使用 `SCAN` 游标分页,禁止在生产 Redis 上使用阻塞式 `KEYS *`。内容查看支持 String、Hash、List、Set、Sorted Set、Stream;集合内容要分页并限制单次条数。
53
+ - 写入 Hash/List/Set/Sorted Set 时先完整解析 JSON,再覆盖旧 Key;删除、覆盖、重命名和 TTL 变更属于破坏性操作,必须先展示目标连接、数据库与 Key 并要求明确确认。
54
+ - 临时匿名接口只开放白名单操作,不开放任意 Redis 命令、Lua、`FLUSHALL` 或 `FLUSHDB`;设置短连接超时、访问频率限制、单次 Key 数量和内容大小上限。
55
+
56
+ MCP 默认操作当前 MCP `OsClient` 的租户 Redis;额外连接只传管理页保存后的 `connectionId`,禁止在 MCP 参数、日志或回答中传递 Redis 密码。
57
+
58
+ | MCP 工具 | 用途 | 确认规则 |
59
+ |------|------|------|
60
+ | `microi_redis_statistics` | 服务器、内存、客户端、命中率与 Key 类型统计 | 只读 |
61
+ | `microi_redis_list_keys` | SCAN 分页检索 Key、类型、TTL、内存估算 | 只读 |
62
+ | `microi_redis_get_key` | 分页查看单个 Key 内容 | 只读 |
63
+ | `microi_redis_delete_keys` | 单个或批量删除,最多 500 个 | `confirmExecution="DELETE"` |
64
+ | `microi_redis_replace_value` | 新建或覆盖 String/Hash/List/Set/Sorted Set | `confirmExecution` 等于完整 Key 或 `EXECUTE` |
65
+ | `microi_redis_rename_key` | 不覆盖目标的 Key 重命名 | `confirmExecution` 等于新 Key 或 `EXECUTE` |
66
+ | `microi_redis_set_ttl` | `-1` 永久、`0` 删除、正数为秒 | `confirmExecution` 等于完整 Key 或 `EXECUTE` |
67
67
 
68
68
  **过期时间格式:** 支持两种写法
69
69
  - 整数(秒):`V8.Cache.Set(key, value, 3600)` = 1 小时
@@ -75,21 +75,21 @@ MCP 默认操作当前 MCP `OsClient` 的租户 Redis;额外连接只传管理
75
75
  - `'7.00:00:00'` = 7 天
76
76
  - 不传则**永久缓存**(直到手动 Remove 或 Redis 重启)
77
77
 
78
- ## 🔑 Key 命名规范(必须遵守)
78
+ ## 🔑 Key 命名规范(必须遵守)
79
79
 
80
- Redis 中统一保存 4 段式 Key:`Microi:${OsClient}:{Category}:{Key}`。V8 代码推荐只传 `{Category}:{Key}`,服务端自动添加当前 `OsClient`;传完整当前租户 Key 用于兼容旧脚本。
80
+ Redis 中统一保存 4 段式 Key:`Microi:${OsClient}:{Category}:{Key}`。V8 代码推荐只传 `{Category}:{Key}`,服务端自动添加当前 `OsClient`;传完整当前租户 Key 用于兼容旧脚本。
81
81
 
82
82
  ```javascript
83
- // ✅ 推荐:逻辑 Key,运行时自动绑定当前租户
84
- var k1 = 'User:' + userId;
85
- var k2 = 'SmsCode:' + phone;
86
- var k3 = 'Lock:OrderPay:' + orderId;
87
-
88
- // ✅ 兼容:完整当前租户 Key
89
- var fullKey = 'Microi:' + V8.OsClient + ':User:' + userId;
90
-
91
- // ❌ 拒绝:不能访问其它租户
92
- var foreignKey = 'Microi:other-tenant:User:' + userId;
83
+ // ✅ 推荐:逻辑 Key,运行时自动绑定当前租户
84
+ var k1 = 'User:' + userId;
85
+ var k2 = 'SmsCode:' + phone;
86
+ var k3 = 'Lock:OrderPay:' + orderId;
87
+
88
+ // ✅ 兼容:完整当前租户 Key
89
+ var fullKey = 'Microi:' + V8.OsClient + ':User:' + userId;
90
+
91
+ // ❌ 拒绝:不能访问其它租户
92
+ var foreignKey = 'Microi:other-tenant:User:' + userId;
93
93
  ```
94
94
 
95
95
  | 段 | 说明 |
@@ -108,23 +108,23 @@ var foreignKey = 'Microi:other-tenant:User:' + userId;
108
108
  - L1:.NET 进程内 `IMemoryCache`(每个容器独立)
109
109
  - L2:Redis(全集群共享)
110
110
 
111
- 读取顺序:L1 命中 → L2 命中 → 数据库
111
+ 读取顺序:L1 命中 → L2 命中 → 数据库
112
112
  写入顺序:DB → L2 → L1
113
113
 
114
- > ⚠️ 直接修改数据库未走平台保存流程时,可能绕过缓存失效。优先调用受支持的保存/刷新接口并回读验证;不要把重启容器或清空整个 Redis 当作日常缓存刷新方案。
115
-
116
- ### FormEngine 授权缓存(Redis epoch + 用户级快照)
117
-
118
- FormEngine 授权是平台内部安全缓存,不能由业务 V8 直接读写。它既要兼容历史前端 V8 的无 `_SysMenuId` 调用,也要避免每个请求重复查询 `sys_user`、`sys_role`、`sys_rolelimit` 和 `sys_menu`:
119
-
120
- 1. 每个 `OsClient` 在共享 Redis 中维护单调递增的授权版本 `epoch`。
121
- 2. 用户授权快照 Key 至少包含 `OsClient + epoch + UserId`,内容包含当前有效用户状态/级别、有效角色、可访问菜单、菜单绑定表、操作权限和数据范围元数据。
122
- 3. 每个 API 节点可用短 TTL 的进程内 L1 加速;Redis L2 在所有节点间共享。读取顺序为“当前 epoch → L1 用户快照 → L2 用户快照 → 主库冷加载”。
123
- 4. 冷加载必须查询主库而不是只读副本,防止复制延迟把刚禁用的用户、撤销的角色或旧菜单范围重新写回缓存。并发冷加载可在单节点合并,但正确性仍以 Redis `epoch` 和主库事实为准。
124
- 5. 用户状态/级别/角色、角色状态、角色菜单/高级表权限、菜单绑定表、菜单权限 JSON、`SqlWhere`、`SqlJoin` / `JoinTables` 等授权事实变更后,必须在写入成功后递增 Redis `epoch`。新旧节点滚动发布期间都通过版本切换自然淘汰旧快照。
125
- 6. L1 丢失、节点重启或发布不影响正确性;禁止把永久 `static` 字典、单机文件或粘性会话当作授权事实源。短 TTL 只是兜底,不能代替变更时递增 `epoch`。
126
-
127
- 无菜单客户端请求只使用该快照推断当前用户对目标表的权限;显式 `_SysMenuId` 仍按对应菜单严格精确校验。两种路径都必须在实际 SQL 中应用菜单 `SqlWhere` / `SqlJoin` 数据范围,不能只缓存一个“允许/拒绝”结果后绕过行级范围。
114
+ > ⚠️ 直接修改数据库未走平台保存流程时,可能绕过缓存失效。优先调用受支持的保存/刷新接口并回读验证;不要把重启容器或清空整个 Redis 当作日常缓存刷新方案。
115
+
116
+ ### FormEngine 授权缓存(Redis epoch + 用户级快照)
117
+
118
+ FormEngine 授权是平台内部安全缓存,不能由业务 V8 直接读写。它既要兼容历史前端 V8 的无 `_SysMenuId` 调用,也要避免每个请求重复查询 `sys_user`、`sys_role`、`sys_rolelimit` 和 `sys_menu`:
119
+
120
+ 1. 每个 `OsClient` 在共享 Redis 中维护单调递增的授权版本 `epoch`。
121
+ 2. 用户授权快照 Key 至少包含 `OsClient + epoch + UserId`,内容包含当前有效用户状态/级别、有效角色、可访问菜单、菜单绑定表、操作权限和数据范围元数据。
122
+ 3. 每个 API 节点可用短 TTL 的进程内 L1 加速;Redis L2 在所有节点间共享。读取顺序为“当前 epoch → L1 用户快照 → L2 用户快照 → 主库冷加载”。
123
+ 4. 冷加载必须查询主库而不是只读副本,防止复制延迟把刚禁用的用户、撤销的角色或旧菜单范围重新写回缓存。并发冷加载可在单节点合并,但正确性仍以 Redis `epoch` 和主库事实为准。
124
+ 5. 用户状态/级别/角色、角色状态、角色菜单/高级表权限、菜单绑定表、菜单权限 JSON、`SqlWhere`、`SqlJoin` / `JoinTables` 等授权事实变更后,必须在写入成功后递增 Redis `epoch`。新旧节点滚动发布期间都通过版本切换自然淘汰旧快照。
125
+ 6. L1 丢失、节点重启或发布不影响正确性;禁止把永久 `static` 字典、单机文件或粘性会话当作授权事实源。短 TTL 只是兜底,不能代替变更时递增 `epoch`。
126
+
127
+ 无菜单客户端请求只使用该快照推断当前用户对目标表的权限;显式 `_SysMenuId` 仍按对应菜单严格精确校验。两种路径都必须在实际 SQL 中应用菜单 `SqlWhere` / `SqlJoin` 数据范围,不能只缓存一个“允许/拒绝”结果后绕过行级范围。
128
128
 
129
129
  ## 基本读写
130
130
 
@@ -199,7 +199,7 @@ var result = V8.FormEngine.GetTableData('Product', {
199
199
  _PageSize: pageSize
200
200
  });
201
201
 
202
- var response = { Code: 1, Data: result.Data, DataCount: result.DataCount };
202
+ var response = { Code: 1, Data: result.Data, DataCount: result.DataCount };
203
203
 
204
204
  // 列表缓存时间短一些(5 分钟)
205
205
  V8.Cache.Set(cacheKey, JSON.stringify(response), '0.00:05:00');
@@ -235,22 +235,22 @@ if (result.Code === 1 && result.Data) {
235
235
  }
236
236
  ```
237
237
 
238
- ## 分布式锁:不要用普通 Cache 拼装
239
-
240
- `KeyExist → Set → Remove` 不是分布式锁:检查与写入不原子、没有唯一持有者令牌、锁过期后旧持有者会删除新持有者的锁,也无法处理节点暂停、网络分区和滚动发布。
241
-
242
- V8 业务脚本需要互斥时:
243
-
244
- 1. 接口引擎使用平台 `LockKey/LockTimeout` 配置;
245
- 2. Job/Worker 使用带租约、唯一持有者令牌、续租、超时自动释放和“仅持有者可释放”语义的共享锁;
246
- 3. Key 至少包含 `OsClient + 任务/业务唯一标识`;
247
- 4. 分布式锁只能减少并发,业务副作用仍必须用幂等键、唯一约束/条件更新、状态机或 outbox/inbox 保证只执行一次。
248
-
249
- `V8.Cache` 没有公开安全的 compare-and-set/带令牌释放原语时,禁止自行实现锁。
250
-
251
- ## 原子计数与限流
252
-
253
- `Get → parseInt → Set` 在并发下会丢计数。普通 Hash 计数可使用 `V8.Cache.HashIncrement`;需要“计数 + 首次设置 TTL + 超限拒绝”的安全限流、日上传配额或金额额度时,应使用平台 `RateLimit` / SecurityGuard 或后端 Redis Lua 原子脚本,并在 Redis 不可用时按风险选择失败关闭。不要在 V8 中用多个普通 Cache 调用模拟原子配额。
238
+ ## 分布式锁:不要用普通 Cache 拼装
239
+
240
+ `KeyExist → Set → Remove` 不是分布式锁:检查与写入不原子、没有唯一持有者令牌、锁过期后旧持有者会删除新持有者的锁,也无法处理节点暂停、网络分区和滚动发布。
241
+
242
+ V8 业务脚本需要互斥时:
243
+
244
+ 1. 接口引擎使用平台 `LockKey/LockTimeout` 配置;
245
+ 2. Job/Worker 使用带租约、唯一持有者令牌、续租、超时自动释放和“仅持有者可释放”语义的共享锁;
246
+ 3. Key 至少包含 `OsClient + 任务/业务唯一标识`;
247
+ 4. 分布式锁只能减少并发,业务副作用仍必须用幂等键、唯一约束/条件更新、状态机或 outbox/inbox 保证只执行一次。
248
+
249
+ `V8.Cache` 没有公开安全的 compare-and-set/带令牌释放原语时,禁止自行实现锁。
250
+
251
+ ## 原子计数与限流
252
+
253
+ `Get → parseInt → Set` 在并发下会丢计数。普通 Hash 计数可使用 `V8.Cache.HashIncrement`;需要“计数 + 首次设置 TTL + 超限拒绝”的安全限流、日上传配额或金额额度时,应使用平台 `RateLimit` / SecurityGuard 或后端 Redis Lua 原子脚本,并在 Redis 不可用时按风险选择失败关闭。不要在 V8 中用多个普通 Cache 调用模拟原子配额。
254
254
 
255
255
  ## 缓存 Key 命名规范
256
256
 
@@ -265,25 +265,25 @@ Microi:myapp:lock:order:xxx-id 订单锁
265
265
  Microi:myapp:api:count:userId:date API 调用计数
266
266
  ```
267
267
 
268
- ## 注意事项
268
+ ## 注意事项
269
269
 
270
270
  - `V8.Cache.Get()` 返回 `null` 表示 key 不存在,返回空字符串 `''` 是合法值
271
271
  - `V8.Cache.Set()` 的 value 必须是字符串,对象需要 `JSON.stringify()`
272
272
  - **过期时间格式为 `d.HH:mm:ss` 字符串**(非秒数),不传则永久缓存
273
273
  - Key 命名建议:`Microi:{V8.OsClient}:{分类}:{Key}`,避免跨应用冲突
274
- - 写操作后即时清除相关缓存,避免脏数据
275
- - 不要缓存频繁变化的数据(如实时库存),不如每次查库
276
-
277
- ## 后端批量写入与 Redis Pub/Sub 回压
278
-
279
- 平台源码中的缓存写入、删除和按模式删除不仅操作 Redis 数据,还会发布跨节点 L1
280
- 失效通知。批量导入、自动升级和迁移代码必须 `await` 这些异步调用,禁止
281
- fire-and-forget;否则数千个 `SCAN/DEL/PUBLISH` 会同时进入同一个
282
- `ConnectionMultiplexer`,表现为 `outstanding` 持续升高、`SocketClosed`,并可能让
283
- 其它节点继续使用旧缓存。
284
-
285
- - 同一租户的失效广播要有界并发,短暂连接异常可做有限次数重试;
286
- - 持续故障的日志应按时间窗口汇总,但不得静默吞掉一致性告警;
287
- - 每个租户可能使用不同 Redis,订阅初始化状态不得用一个全局 `static bool` 共享;
288
- - 缓存实例必须保存创建时的准确 `OsClient`,按模式 `SCAN/DEL` 时直接使用该租户连接;禁止根据 Redis DB 编号反推连接,因为不同租户可能在不同服务器上使用相同 DB 编号;
289
- - 等待发布只解决回压,业务写入和缓存失效仍需保持 `OsClient` 隔离及可重试幂等。
274
+ - 写操作后即时清除相关缓存,避免脏数据
275
+ - 不要缓存频繁变化的数据(如实时库存),不如每次查库
276
+
277
+ ## 后端批量写入与 Redis Pub/Sub 回压
278
+
279
+ 平台源码中的缓存写入、删除和按模式删除不仅操作 Redis 数据,还会发布跨节点 L1
280
+ 失效通知。批量导入、自动升级和迁移代码必须 `await` 这些异步调用,禁止
281
+ fire-and-forget;否则数千个 `SCAN/DEL/PUBLISH` 会同时进入同一个
282
+ `ConnectionMultiplexer`,表现为 `outstanding` 持续升高、`SocketClosed`,并可能让
283
+ 其它节点继续使用旧缓存。
284
+
285
+ - 同一租户的失效广播要有界并发,短暂连接异常可做有限次数重试;
286
+ - 持续故障的日志应按时间窗口汇总,但不得静默吞掉一致性告警;
287
+ - 每个租户可能使用不同 Redis,订阅初始化状态不得用一个全局 `static bool` 共享;
288
+ - 缓存实例必须保存创建时的准确 `OsClient`,按模式 `SCAN/DEL` 时直接使用该租户连接;禁止根据 Redis DB 编号反推连接,因为不同租户可能在不同服务器上使用相同 DB 编号;
289
+ - 等待发布只解决回压,业务写入和缓存失效仍需保持 `OsClient` 隔离及可重试幂等。
@@ -10,8 +10,33 @@ description: Microi V8 与 MCP 文件上传下载指南。用于处理流式 AI
10
10
  你正在为 Microi 吾码平台编写文件上传/下载/返回相关代码。平台分布式存储(HDFS)支持阿里云OSS、MinIO、亚马逊S3,存储方案由 SaaS 引擎按租户配置。
11
11
 
12
12
  公开入口覆盖 `V8.uploadFile`、多文件 `V8.uploadFiles` 与 MCP `microi_upload_file_base64`。多文件上传必须限制并发、逐文件返回结果;Base64 工具只接受明确文件名、大小和租户内目标范围,写后回读路径、大小与哈希。
13
-
14
- <!-- microi-progressive:begin -->
13
+
14
+ ## 交互式图片默认压缩(强制)
15
+
16
+ - `ImgUpload`、PC、UniApp/H5/小程序以及 MCP 普通图片上传在未显式传 `Preview` 时,必须按 `Preview=true` 处理;字段设计器新配置默认开启。只有业务明确要求公开原始画质时才允许显式关闭,不能把“客户端漏传”解释为关闭。
17
+ - 默认展示图目标为不超过约 `500 KB`、最长边不超过 `1920 px`;可按真实用途进一步收紧。压缩必须使用 Windows/Linux/容器一致的跨平台实现,并限制输入字节、像素总量与解码并发,避免超大图触发 OOM。
18
+ - 压缩流程固定为“先将原始字节写入当前租户 HDFS 私有桶的 `_origin` 对象 → 生成压缩展示图 → 写入目标公有/私有位置 → 回读大小与格式”。即使展示图是公有资源,原图也只能保存在私有桶。
19
+ - 压缩、解码或编码失败时必须失败关闭,返回可诊断错误;禁止静默把几十 MB 的未压缩原图发布到公有桶。返回字段中的 `Size` 应表示展示图实际字节数,`OriginalSize` 可用于管理员审计,但业务字段不得保存私有桶真实地址或签名 URL。
20
+ - 历史治理只迁移超过目标体积的图片:先为旧对象补齐私有原图副本,再上传压缩展示图、原子更新全部业务引用并逐条回读。确认没有引用后才删除旧公有对象;批量任务要有清单、检查点、幂等映射和失败重试,不能边扫描边不可逆覆盖。
21
+
22
+ ## 表单引擎图片裁剪(强制)
23
+
24
+ - `ImgUpload.Crop` 支持 `Enabled`、`Mode=free/fixed/select`、`Ratio`、`CustomWidth/CustomHeight`以及 `AllowZoom/AllowRotate/AllowFlip`。`Enabled` 只表示表单用户的默认状态,不得当作裁剪能力总开关;新增/编辑表单把裁剪开关合并到紧凑上传面板,旁边同时显示公有/私有桶、单/多图与最大数量、压缩状态和最大体积。旧字段未配置时该开关默认关闭,但用户仍可主动开启。
25
+ - 裁剪弹层必须同时提供“取消本次上传”“不裁剪直接上传”“应用裁剪并上传”三个不同动作。多图选择按队列逐张处理;直接上传只跳过当前图片的裁剪,不能取消或阻塞后续图片。
26
+ - 多图模式下后端可能为每个单文件请求返回 `Data: [{...}]`,单图模式返回 `Data: {...}`。PC/移动端必须先归一化对象/数组,再按客户端 `uid` 替换上传占位项并生成预览 URL,禁止把接口成功误显示成空列表。
27
+ - 开启裁剪时,前端必须上传最终裁剪图,并在同一 multipart 请求中以 `MicroiOriginalFile` 附带同名、未改动的原图及 `CropEnabled=true`。不得仅传坐标后由后端重演,否则 EXIF 方向、旋转或镜像可能导致前后端结果不一致。
28
+ - 后端必须校验裁剪图与原图一一同名,两者字节数都计入单次限制和每日配额,但原图不计为第二个业务文件。未宣告裁剪却传原图、宣告裁剪却漏传/错配原图都要失败关闭。
29
+ - HDFS 写入顺序固定为“未改动原图写入私有 `_origin` 对象 → 可选压缩裁剪图 → 写入业务展示图”。裁剪原图写入失败时不得发布展示图;即使 `Preview=false`,裁剪原图仍必须私有保留。不得返回或持久化原图真实路径。
30
+
31
+ ## 富文本图片、视频与附件(强制)
32
+
33
+ - `RichText.Limit=false` 只用于需匿名长期访问的官网公告、商品详情等公开正文;内部内容用 `true`。普通交互式帐号即使传 `false`,后端仍可按安全策略强制私有,客户端必须以上传响应的实际 `Limit` 为准。
34
+ - RichText 分别配置 `Image`、`Video`、`File` 的 `Enabled/MaxSize/MaxCount`;图片另传 `Preview/CompressMaxSize/CompressMaxWidth`,并继续遵循“原图私有、展示图公有或私有”的压缩链路。附件类型白名单用 `File.Accept` 进一步收紧,不能放宽服务端白名单。
35
+ - 私有正文持久化 `/__microi_richtext_private__/...` 稳定对象标识,严禁保存对象存储签名 URL、`OpenPrivateFile` Ticket、DiyToken 或其它会过期的凭据。每次打开记录时携带 `FormEngineKey/FormDataId/FieldId/SysMenuId` 批量换取短效审计代理地址。
36
+ - 私有文件后端授权必须重新校验当前租户、菜单、表、行和 RichText 字段,并确认所请求路径精确存在于 `img/video/source.src` 或 `a.href`;正文文字、`data-src/data-href`、脚本标签和前缀相似路径都必须失败关闭。
37
+ - 外部匿名页面没有后台记录权限上下文,不能解析私有标识。公开文章必须由有权发布公有资产的超级管理员显式使用公有桶,不得通过延长私有 URL 有效期模拟公开资源。
38
+
39
+ <!-- microi-progressive:begin -->
15
40
  <!-- microi-progressive:chunk id=v8-file-upload-000 sha256=b48ce09f93a43efd30e700af637db3881355e8d2baaf45165ea2bd0bdfda25cf -->
16
41
  ## 核心 API
17
42
 
@@ -87,7 +112,7 @@ Unity `Data`、WASM、Windows 安装包、视频模型等发布资产不得进
87
112
  1. **租户业务配置**:有效正数/布尔值按 `sys_osclients` 当前租户 → 代码默认值解析。租户可以按业务需要提高或降低默认值,不要求安装者维护额外环境变量或修改 `appsettings`。
88
113
  2. **平台绝对上限**:最终业务值再与代码内固定灾难保护上限取较小值,租户和安装参数都不能放大。
89
114
  3. **HTTP 解析上限**:Kestrel 请求正文与 Multipart 固定为 2048 MB,普通表单单值固定为 128 MB,是所有租户共享的请求解析硬顶。
90
- 4. **字段级限制**:前端 `FileUpload` / `ImgUpload` 的 `MaxSize`、`MaxCount` 等只能与当前租户有效值取更小值,不能提高后端上限,也不能替代服务端校验。
115
+ 4. **字段级限制**:前端 `FileUpload` / `ImgUpload` / `RichText` 的 `MaxSize`、`MaxCount` 等只能与当前租户有效值取更小值,不能提高后端上限,也不能替代服务端校验。
91
116
 
92
117
  业务配置与固定边界:
93
118
 
@@ -134,7 +134,7 @@ var url = V8.Method.GetPrivateFileUrl({
134
134
  ```
135
135
 
136
136
  - 普通客户端调用 `/api/HDFS/GetPrivateFileUrl` 时,不能只提交 `FilePathName`,必须同时提交 `FormEngineKey`、`FormDataId`、`FieldId`、`SysMenuId`。服务端校验菜单、菜单绑定表、记录数据范围、字段归属以及字段值确实引用该路径后,才签发临时票据。
137
- - `FieldId` 必须属于目标表,且组件为 `FileUpload` 或 `ImgUpload`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。
137
+ - `FieldId` 必须属于目标表,且组件为 `FileUpload`、`ImgUpload` 或 `RichText`;`SysMenuId` 必须是当前用户真实拥有、并绑定目标表的菜单。RichText 还必须在真实 `img/video/source.src` 或 `a.href` 中精确引用请求路径,普通文字、`data-*` 和脚本标签不算授权依据。
138
138
  - 普通用户禁止通过该入口直接取得私有文件 `Byte` / `Stream`。签发失败时不能回退裸路径、真实对象存储签名地址或公有 URL。
139
139
  - 私有文件访问必须经过后端短期票据代理:签发链接时记录当前登录用户,实际 `GET/HEAD` 打开或下载时再记录一次访问行为;支持 `Range` 流式响应,并对同一次分片请求做短时去重,不能把文件完整读入内存。
140
140
  - 审计代理由平台后端回源对象存储,必须使用服务端内网端点生成上游地址,不能先生成公网 MinIO 签名地址再让后端绕公网回源。否则同一对象经内网上传成功后,可能在公网端点表现为 404。
@@ -119,6 +119,10 @@ AI 在工作区任意任务中生成的**一次性临时脚本、诊断文件、
119
119
  | 吾码后台系统前端移动端自适应源码 | `Microi.Client/src/views/mobile/` |
120
120
  | 吾码低代码后端源码 | `Microi.Server/` |
121
121
  | 吾码表单引擎源码 | `Microi.Client/src/views/form-engine/` |
122
+ | 吾码表单设计器源码 | `Microi.Client/src/views/form-engine/diy-design.vue` |
123
+ | 吾码数据表格渲染源码 | `Microi.Client/src/views/form-engine/diy-table.vue` |
124
+ | 吾码表单渲染源码 | `Microi.Client/src/views/form-engine/diy-form.vue` |
125
+ | 吾码地图控件源码 | `Microi.Client/src/views/form-engine/diy-field-component/diy-map.vue` |
122
126
  | 吾码界面引擎源码 | `Microi.Client/src/views/page-engine/` |
123
127
  | 吾码打印引擎源码 | `Microi.Client/src/views/print-engine/` |
124
128
  | 吾码 App 源码 | `microi.app/` |
@@ -128,6 +132,13 @@ AI 在工作区任意任务中生成的**一次性临时脚本、诊断文件、
128
132
 
129
133
  以上路径只作为通用工作区相对路径规范,不写入具体本机盘符。跨仓库、空工作区或普通用户项目中,如果路径不存在,以插件生成的 `AGENTS.md`、MCP 配置和实际文件树为准。
130
134
 
135
+ ### 吾码创建人身份识别(强制)
136
+
137
+ - 每次开始处理吾码任务时,都先检查工作区根的 `Microi.Server/Microi.net/`:目录必须存在,并且 `rg --files Microi.Server/Microi.net` 至少返回一个真实源码文件,空目录不成立。满足条件即确认当前是“吾码创建人在官方完整源码工作区开发”;这是工作区级开发者身份标记,不要求本次代码恰好修改在该目录内。目录缺失或为空时按普通吾码用户工作区处理,不得假设拥有官方主库权限。
138
+ - 确认创建人身份后,处理平台基础能力的修改、构建或交付任务时,必须同时读取 `app-store` Skill,并把官方母版资源、对应平台应用数据包、应用商城发布及发布后回读纳入完成边界;不得只改源码、本地租户或本地 JSON 后结束。
139
+ - 触发表现包括但不限于:新增或修改系统设置、表、字段、Tab、菜单、权限、接口引擎、表单事件、数据源、页面、打印、工作流、任务、平台内置微服务及可幂等种子数据。先判断资源归属的一个或多个官方平台应用,基础空库包与存量租户增量应用都涉及时必须同时更新。
140
+ - 纯审查、解释和诊断任务保持只读;目录识别只决定交付链路和事实源,不授权与当前任务无关的线上写入。当前任务属于平台功能修改/交付且官方 `microi_itdos` 权限可用时,官方应用包发布与版本、状态、内容哈希回读属于正常收尾步骤,不得再次降级成可选建议。
141
+
131
142
  每个 `AI应用/{appKey}` 必须只有一个可编辑事实源,统一承载界面、微服务、Manifest、接口引擎、资源策略、测试、构建脚本与商城上传素材。普通应用默认使用当前连接下的 `Microi-V8-Engine` 目录;受审计的官方内置应用若由独立 Git 仓库维护,必须由版本管理的发布契约唯一指向该源码根,并让构建、跨工程测试和发行包共同读取契约。此时同名 `Microi-V8-Engine` 目录只是远端同步镜像,不得回退为构建源。禁止靠目录探测在多份副本之间自动择新,也禁止另建无契约的 `microi.apps/` 平行发行根。
132
143
 
133
144
  <!-- /microi-progressive:chunk -->