@microi.net/cli 4.9.5 → 4.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/README.md +7 -12
- package/assets/build-meta.json +6 -5
- package/package.json +1 -1
- package/scripts/mcp-server.js +83 -83
- package/scripts/microi-cli.js +80 -92
- package/scripts/microi-codex-broker.js +418 -0
- package/scripts/microi-codex-router.js +159 -65
- package/scripts/microi-skills.meta.json +349 -190
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +3566 -0
- package/skills/ai-engine/SKILL.md +1 -1
- package/skills/ai-platform-governance/SKILL.md +22 -167
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -0
- package/skills/app-store/SKILL.md +1 -1
- package/skills/business-blueprint/SKILL.md +1 -1
- package/skills/datasource-engine/SKILL.md +1 -1
- package/skills/dos-orm/SKILL.md +1 -1
- package/skills/job-engine/SKILL.md +1 -1
- package/skills/message-notification/SKILL.md +1 -1
- package/skills/microi-ai-application/SKILL.md +1 -1
- package/skills/microi-client-frontend/SKILL.md +18 -435
- package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +144 -0
- 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 +178 -0
- 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 +144 -0
- package/skills/microi-codex/SKILL.md +4 -4
- package/skills/microi-codex-installer/SKILL.md +25 -36
- package/skills/microi-datasource-mapping/SKILL.md +1 -1
- package/skills/microi-db-schema/SKILL.md +4 -4
- package/skills/microi-db-schema/references/schema-overview.md +1 -1
- package/skills/microi-db-schema/references/schema.md +1 -1
- package/skills/microi-db-schema/references/table-catalog.md +1 -1
- package/skills/microi-deployment/SKILL.md +1 -1
- package/skills/microi-docs-coverage/SKILL.md +1 -1
- package/skills/microi-form-engine/SKILL.md +2 -2
- package/skills/microi-form-layout/SKILL.md +20 -226
- package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -0
- package/skills/microi-frontend-sdk/SKILL.md +18 -152
- 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 +171 -0
- package/skills/microi-left-right-layout/SKILL.md +1 -1
- package/skills/microi-microservice/SKILL.md +8 -1
- package/skills/microi-microservice/references/runtime-delivery.md +4 -0
- package/skills/microi-mobile-app-quality/SKILL.md +23 -289
- 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 +209 -0
- package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +117 -0
- package/skills/microi-solution-quotation/SKILL.md +1 -1
- package/skills/microi-system-delivery/SKILL.md +17 -381
- package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +186 -0
- 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 +210 -0
- package/skills/microi-ui/SKILL.md +20 -170
- package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -0
- package/skills/microi-uniapp-frontend/SKILL.md +27 -336
- package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +225 -0
- package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +154 -0
- package/skills/module-engine/SKILL.md +1 -1
- package/skills/ocr-engine/SKILL.md +1 -1
- package/skills/page-engine/SKILL.md +24 -272
- package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +234 -0
- package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +60 -0
- package/skills/performance-testing/SKILL.md +1 -1
- package/skills/playwright-e2e/SKILL.md +25 -591
- package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +173 -0
- package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +183 -0
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -0
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -0
- package/skills/print-engine/SKILL.md +1 -1
- package/skills/production-readonly-audit/SKILL.md +1 -1
- package/skills/report-engine/SKILL.md +1 -1
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -0
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -0
- package/skills/scripts/validate-progressive-disclosure.mjs +52 -0
- package/skills/search-engine/SKILL.md +1 -1
- package/skills/spider-engine/SKILL.md +1 -1
- package/skills/translate-engine/SKILL.md +1 -1
- package/skills/ui-design/SKILL.md +27 -1462
- package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -0
- package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +155 -0
- package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -0
- package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -0
- package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -0
- package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -0
- package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -0
- package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -0
- package/skills/uniapp-mall-assets/SKILL.md +1 -1
- package/skills/unity-integration/SKILL.md +1 -1
- package/skills/v8-api-config/SKILL.md +1 -1
- package/skills/v8-cache-pattern/SKILL.md +1 -1
- package/skills/v8-crud-api/SKILL.md +21 -246
- package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +226 -0
- package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -0
- package/skills/v8-debugging/SKILL.md +1 -1
- package/skills/v8-explorer-tree/SKILL.md +1 -1
- package/skills/v8-export-import/SKILL.md +16 -426
- package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +211 -0
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -0
- package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +42 -0
- package/skills/v8-file-upload/SKILL.md +17 -355
- 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 +227 -0
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +149 -0
- package/skills/v8-formengine-http/SKILL.md +1 -1
- package/skills/v8-frontend-events/SKILL.md +20 -206
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -0
- package/skills/v8-http-integration/SKILL.md +15 -237
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -0
- package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +44 -0
- package/skills/v8-image-processing/SKILL.md +1 -1
- package/skills/v8-menu-buttons/SKILL.md +16 -512
- package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +221 -0
- package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +224 -0
- package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +104 -0
- package/skills/v8-mongodb/SKILL.md +1 -1
- package/skills/v8-mq-mqtt/SKILL.md +12 -176
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -0
- package/skills/v8-saas-multi-tenant/SKILL.md +1 -1
- package/skills/v8-security/SKILL.md +17 -330
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +199 -0
- package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +158 -0
- package/skills/v8-sql-query/SKILL.md +1 -1
- package/skills/v8-table-event/SKILL.md +17 -237
- package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +216 -0
- package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +46 -0
- package/skills/v8-template-engine/SKILL.md +1 -1
- package/skills/v8-utilities/SKILL.md +1 -1
- package/skills/v8-workflow/SKILL.md +20 -161
- package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -0
- package/skills/workspace-conventions/SKILL.md +36 -369
- 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 -0
- package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +196 -0
- package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +27 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# v8-security 详细参考 2
|
|
2
|
+
|
|
3
|
+
> 按需读取;本文件由 SKILL.md 的原章节无损拆分。
|
|
4
|
+
|
|
5
|
+
<!-- microi-progressive:chunk id=v8-security-008 sha256=dfd8dabfc7d1ecdd99283c0dd5e1d5499d76f8337eff0d9cd9b0cfcac0a9dddc -->
|
|
6
|
+
## 7. 日志记录
|
|
7
|
+
|
|
8
|
+
关键操作必须记录审计日志:
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
// 记录敏感操作
|
|
12
|
+
V8.Method.AddSysLog({
|
|
13
|
+
Title: '删除用户',
|
|
14
|
+
Content: JSON.stringify({
|
|
15
|
+
OperatorId: V8.CurrentUser.Id,
|
|
16
|
+
OperatorName: V8.CurrentUser.Name,
|
|
17
|
+
TargetId: V8.Param.userId,
|
|
18
|
+
Time: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
19
|
+
}),
|
|
20
|
+
Type: '安全审计',
|
|
21
|
+
Level: 2
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 管理员吊销用户全部登录态
|
|
26
|
+
|
|
27
|
+
需要让某个用户所有终端立即退出时,接口引擎必须调用平台统一能力,不要只删除前端 Token,也不要只修改用户状态:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
if (!V8.CurrentUser || Number(V8.CurrentUser.Level || 0) < 9999) {
|
|
31
|
+
return { Code: 0, Msg: '仅系统管理员可执行此操作' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var clearResult = V8.Method.ClearUserLoginInfo(V8.Param.UserId, V8.OsClient);
|
|
35
|
+
if (clearResult.Code != 1) {
|
|
36
|
+
return clearResult;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- `ClearUserLoginInfo` 会删除该用户 Redis 中的全部终端 Token,旧 Token 随即失效且不能继续以旧换新。
|
|
41
|
+
- 已建立实时连接的终端会收到 `ReceiveForceLogout` 并立即退出;没有实时连接的终端在下一次请求时收到 Token 失效。
|
|
42
|
+
- “禁用用户”必须先吊销全部登录态,再把 `sys_user.State` 更新为 `0`,并记录安全审计日志。
|
|
43
|
+
- 接口引擎和底层方法都必须校验管理员权限、目标用户 Id 和租户边界,禁止跨租户吊销。
|
|
44
|
+
|
|
45
|
+
<!-- /microi-progressive:chunk -->
|
|
46
|
+
<!-- microi-progressive:chunk id=v8-security-009 sha256=c3618f2fae953f2951195d0669f51963f5dfbc96f43b4262e4234f556935e1f7 -->
|
|
47
|
+
## 8. 错误处理
|
|
48
|
+
|
|
49
|
+
不要把内部错误信息暴露给前端:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
try {
|
|
53
|
+
var result = V8.Db.FromSql('SELECT * FROM t WHERE Id = @p0')
|
|
54
|
+
.AddInParameter("@p0", V8.Param.id)
|
|
55
|
+
.ToArray();
|
|
56
|
+
return { Code: 1, Data: result };
|
|
57
|
+
} catch (ex) {
|
|
58
|
+
// 内部日志使用追踪号和必要的非敏感字段;不要记录完整V8.Param
|
|
59
|
+
var traceId = V8.Method.NewUlid();
|
|
60
|
+
console.error('查询失败 traceId=' + traceId + ' error=' + ex.message);
|
|
61
|
+
// 返回给前端的信息不含内部细节
|
|
62
|
+
return { Code: 0, Msg: '查询失败,请稍后重试', DataAppend: { TraceId: traceId } };
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
<!-- /microi-progressive:chunk -->
|
|
67
|
+
<!-- microi-progressive:chunk id=v8-security-010 sha256=9d8e9186a7fa3a6c85e2df8cf0d6776ca05c3eff1c97dd36424722b5880c2812 -->
|
|
68
|
+
## 9. Token、终端会话与租户隔离
|
|
69
|
+
|
|
70
|
+
开发 Web、H5、UniApp、小程序、App、VS Code 或 MCP 客户端时,必须同时遵守 `microi-frontend-sdk/SKILL.md` 的 Token 协议:登录传 `_ClientType`,请求头传稳定 `did`,每次响应接收新的 `authorization`,并在浏览器/应用恢复前台时检查续签。
|
|
71
|
+
|
|
72
|
+
- PC 默认使用 `SessionAuthTimeout` 分钟策略;移动端、VS Code、MCP 默认使用 `AccessTokenLifetime` 天策略。不要在前端自行扩大服务端有效期。
|
|
73
|
+
- Token 的 `OsClient` 必须与当前请求租户一致。收到 `TenantMismatch` 时立即停止请求,不能自动把 Token 复制到另一个租户。
|
|
74
|
+
- 收到 `JwtExpired`、`SessionExpired`、`SessionMissing`、`AuthVersionChanged` 时清理当前连接的 Token;多服务器或多租户客户端只能清理受影响的连接,不能全局退出其它连接。
|
|
75
|
+
- 收到 `TokenReplaced` 时先检查同一终端是否已有新 Token,避免并发旧响应误清新登录态。
|
|
76
|
+
- 用户提示可以显示过期时长、终端类型和租户 Key,但日志、Toast、URL、截图禁止输出完整 Token。
|
|
77
|
+
|
|
78
|
+
### IP 高频拦截、受信 VS Code 独立阈值与解除
|
|
79
|
+
|
|
80
|
+
- `DataAppend.SecurityBlocked=true` 是服务端已可达但当前 IP 被临时拦截,不是网络断开。前端必须展示原始 `Msg`、`Reason`、`ExpiresAtUtc`、IP 和解除建议;不能把它转换成“后端 API 服务暂时不可用”。直接返回该结果的安全中间件必须在 CORS 之后。
|
|
81
|
+
- 普通访问默认阈值为 10 秒 600 请求/120 异常。VS Code 多服务器源码拉取不能通过减少并发或请求量规避;符合条件的只读 V8Debug `Get/List` 使用独立桶,默认 10 秒 6000 请求/1200 异常。
|
|
82
|
+
- 受信判断必须同时满足:服务端共享登录态确认 `Level >= 9999`、请求 Token 是活动 `ClientType=VSCode` Token、请求 `did` 与该 Token 保存的 Did 完全一致、路由为只读 `/api/V8Debug/Get*` 或 `/api/V8Debug/List*`。自报 `ClientType`、User-Agent、`X-User-Level`、单独伪造 did 都不可信;Update/Create/Execute/Upload/Finalize 和 FormEngine 写入不放宽。
|
|
83
|
+
- 普通请求的计数 scope 固定为当前 API 主运行实例,不能使用请求方可控的 `OsClient` Query/Header 选择 Redis 桶;只有已通过上述联合校验的 VS Code profile 才能使用活动 Token 服务端绑定的租户 scope。测试必须覆盖轮换两个真实租户 Key 仍落入同一普通桶。
|
|
84
|
+
- SecurityGuard 只读取 `Connection.RemoteIpAddress`,不得直接解析 `X-Forwarded-For`/`X-Real-IP`。容器运行时可自动信任当前容器路由表实际发现的 RFC1918/ULA 私有默认网关**精确 IP**,用于宿主 Nginx 经 Docker 发布端口转发的最后一跳;不得自动信任公网网关或任意私有网段。其它代理只信任主租户 SaaS 引擎【后端运行配置】的 `BackendForwardedKnownProxies` 精确 IP 和 `BackendForwardedKnownNetworks` 受控 CIDR;禁止使用环境变量、自定义 `appsettings` 节点、`0.0.0.0/0` 或 `::/0`。历史 `SecurityRespectForwardedHeaders` 字段不能建立 Header 信任。该宿主配置变更需滚动重启节点,测试必须覆盖容器网关把 XFF 投影为真实 IP,以及公网 Remote IP 携带伪造 `X-Forwarded-For: 127.0.0.1` 时仍识别公网 IP。
|
|
85
|
+
- `UseForwardedHeaders` 后若 Remote IP 仍是自动发现的精确容器网关,表示代理未提供可验证的客户端地址;该请求不得进入按 IP 自动封禁,否则任一用户可导致全站误封。此降级不跳过后续全局请求压力、内存和业务鉴权,公网或普通私网地址也不适用;运维仍应补齐最后一跳转发头。
|
|
86
|
+
- IP 错误窗口只把未匹配端点的 404/405 作为扫描计数;400/401/403/413/429、5xx、已匹配 Controller 以及 `/api`、`/apiengine` 动态路由的应用级 404/405 只能审计,不能触发自动封禁。请求总量窗口继续作为独立的高频攻击门禁,禁止用服务端故障或正常业务拒绝反向惩罚客户端。
|
|
87
|
+
- 旧版 `HighError`/`TrustedVsCodeHighError` 宽泛错误策略产生的自动封禁属于可识别的失效状态;升级节点应从共享 Redis 和本机缓存幂等退休,不能要求所有租户等待旧 TTL,也不能解除手动、`HighFrequency` 或新版 `RouteScan` 封禁。
|
|
88
|
+
- 自动封禁按 `ExpiresAtUtc` 到期解除。立即解除只能从未被封禁的管理网络,以平台超级管理员进入【系统日志 → 安全防护】操作 `/api/SecurityGuard/UnblockIp`;同一被封出口不能给自己解封。
|
|
89
|
+
- 固定可信出口才可精确加入当前服务器匹配 `sys_osclients.SecurityWhitelistIps`。禁止全网段、动态用户 IP 或请求参数自动入白名单,也不要关闭安全防护或全局放大普通阈值。
|
|
90
|
+
- 多节点封禁、解封和到期状态必须进入共享 Redis/数据库;本机静态字典只能做缓存。Redis 可用且共享 block 不存在就是权威已解封,节点必须删除本机旧 block,禁止把旧状态回写复活;Redis 不可用时才允许本机降级。验收要让同一出口分别命中至少两个 API 节点,覆盖普通阈值、受信读取、伪造 Header、手动解除和自动到期。
|
|
91
|
+
|
|
92
|
+
<!-- /microi-progressive:chunk -->
|
|
93
|
+
<!-- microi-progressive:chunk id=v8-security-011 sha256=aaf4ca37e6a03ddd3520d32559e322d0d7bbba60be8aceef03d200138df77a98 -->
|
|
94
|
+
## 10. Jint 运行时升级边界
|
|
95
|
+
|
|
96
|
+
升级 Jint 时必须逐版阅读官方 release notes,并至少验证以下兼容面,不能只以编译通过作为验收:
|
|
97
|
+
|
|
98
|
+
- `Engine` 非线程安全;每次执行使用独立 Engine。`setTimeout` 等回调必须在当前请求内由同一 Engine 串行排空后再释放,禁止 `Task.Run` 捕获 Engine 跨线程或在响应后继续执行。需要可靠后台执行时改用 MQ、Job 或 outbox,不把进程内定时器当作持久任务。
|
|
99
|
+
- 重复脚本使用有上限的 `Engine.PrepareScript` 缓存;`Prepared<Script>` 可跨 Engine 复用。Promise 使用 `EvaluateAsync` / `UnwrapIfPromiseAsync` 和请求取消令牌,禁止在 ASP.NET 请求线程上使用阻塞的 `UnwrapIfPromise`。
|
|
100
|
+
- 内存 MB 转字节前先提升为 `long`,例如 `checked((long)mb * 1024L * 1024L)`;2GB 用 `int` 相乘会溢出并让限制失真。
|
|
101
|
+
- Jint 4.14 默认把 CLR 数组改为 `LiveView`,并默认缓存最近对象包装器。Microi 为兼容历史脚本显式使用 `ArrayConversionMode.Copy + CacheRecentObjectWrappers=false`;若以后切到 LiveView,必须覆盖宿主数组被 JS 修改、固定长度 push/pop 报错、`Array.isArray=false` 和重复读取身份缓存测试。
|
|
102
|
+
- 引擎约束必须在平台宿主对象注入完成、用户脚本执行前 `Constraints.Reset()`;同时覆盖超时、语句数、递归、内存、Promise 取消及 CLR 宿主边界返回后的再次检查。
|
|
103
|
+
|
|
104
|
+
<!-- /microi-progressive:chunk -->
|
|
105
|
+
<!-- microi-progressive:chunk id=v8-security-012 sha256=782777ed84d4030038999d74be896385365f7f3a005332558b7f5e46b17b351b -->
|
|
106
|
+
## 安全检查清单
|
|
107
|
+
|
|
108
|
+
- [ ] 所有数据库查询使用参数化(`_Where` 或 `@p0`)
|
|
109
|
+
- [ ] 把 Token 认证与表/菜单/操作授权分开;列表/写入显式菜单严格精确校验,历史唯一详情只校验同表已授权菜单访问权
|
|
110
|
+
- [ ] 当前表前端 facade 自动注入真实菜单,跨表不借用主菜单;可信后端 V8 由服务端标记且不要求菜单
|
|
111
|
+
- [ ] 平台表按管理员专用、只读委托、按角色管理三级执行;全部拒绝匿名,Import/Export 必须携带真实菜单并有专项权限
|
|
112
|
+
- [ ] 菜单 `SqlWhere` / `SqlJoin` 覆盖真实列表、计数和导出查询;详情只校验同表菜单访问;主表新增/修改/删除/导入只按专项操作权限,不把查询范围带入写 SQL;行级写业务限制由后端 V8/接口引擎校验
|
|
113
|
+
- [ ] 授权缓存按租户使用 Redis `epoch` 和用户级快照;冷加载读主库,权限变更递增 `epoch`
|
|
114
|
+
- [ ] 关键操作校验 `V8.CurrentUser` 权限
|
|
115
|
+
- [ ] 涉及数据修改的接口校验请求参数合法性
|
|
116
|
+
- [ ] 敏感数据(手机号、身份证等)脱敏返回
|
|
117
|
+
- [ ] 密码只走平台认证/重置流程;禁止MD5/SHA直接存储,后端使用带盐自适应密码哈希
|
|
118
|
+
- [ ] 可逆业务秘密只在后端加解密;列表掩码、独立授权显示、no-store 且审计不含明文
|
|
119
|
+
- [ ] 新登录/SSO/Passkey/人脸入口最终签发 DiyToken,不并行创建第二套权限体系
|
|
120
|
+
- [ ] 敏感操作票据由后端重算 ActionHash 后原子消费,不能相信前端验证成功布尔值
|
|
121
|
+
- [ ] 写操作有防重复提交机制
|
|
122
|
+
- [ ] 关键操作写审计日志
|
|
123
|
+
- [ ] catch 块不暴露内部错误给前端
|
|
124
|
+
- [ ] 外部数据库连接串不来自普通请求、不回显,DbKey 无重复且不占用 V8.Dbs 保留名称
|
|
125
|
+
- [ ] 外部附件管理入口硬校验 `Level >= 9999`、显式确认、来源脱敏,流式迁移并按源附件 Id 幂等回读
|
|
126
|
+
- [ ] Jint 升级覆盖 Prepared 缓存、非阻塞 Promise、long 内存换算、数组互操作兼容和同线程定时器生命周期
|
|
127
|
+
|
|
128
|
+
### 复盘:管理员设计器和升级任务的嵌套 FormEngine 写入被误判
|
|
129
|
+
|
|
130
|
+
- 触发场景:`Level=9999` 管理员在表单设计器保存时,外层 `UptFormData`、内部 `UptDiyFieldList` 或 `AddDiyField` 返回 `NoAuth`;无 HTTP 用户的升级任务写 `sys_apiengine/sys_menu/diy_field` 也被拒绝。
|
|
131
|
+
- 根因:Redis 保留了旧结构的授权快照,新字段反序列化为 `0/false`;同时更新前旧记录读取或动态新增字段把已校验上下文降成裸 `JObject` / 匿名参数,丢失管理员或可信服务端来源。批量字段保存若逐字段调用完整 CRUD,还会把授权、V8 和缓存工作放大 N 倍。
|
|
132
|
+
- 通用规则:授权快照使用独立契约版本;内部嵌套调用必须显式传递原始客户端管理员上下文,或由真正的服务端任务构造不可伪造的强类型可信参数,不能依赖 `_InvokeType` 或 CLR/JObject 猜测。
|
|
133
|
+
- 自动化检查:预置缺少新字段的旧 Redis 快照后验证新版本 Key 不命中;分别覆盖管理员设计器批量字段保存/新增字段、普通用户直接写管理员专用表被拒绝、只读委托表写入被拒绝、`mic_print` 有权读取成功、升级程序可信写入成功,以及 HTTP JSON 伪造可信字段仍失败。
|
|
134
|
+
|
|
135
|
+
<!-- /microi-progressive:chunk -->
|
|
136
|
+
<!-- microi-progressive:chunk id=v8-security-013 sha256=d87a3b8c82d4eb9c88ee5214711672f9e0684895dc3f4eca32b0278587f68426 -->
|
|
137
|
+
## 浏览器访问密钥
|
|
138
|
+
|
|
139
|
+
固定看板、电视和信息屏免输入帐号密码时,使用平台 `mci_user_access_key`,禁止自行在接口引擎中保存明文 Secret,也禁止把长期登录 Token 拼进 URL。
|
|
140
|
+
|
|
141
|
+
- 一个帐号可有多个密钥;每个密钥独立名称、到期时间(90天/自定义/永久)、范围、使用审计和吊销状态。永久密钥必须可单独吊销并建议定期人工轮换。
|
|
142
|
+
- 密钥格式固定为 `microi_ak_<48-bit公开前缀>.<128-bit随机秘密>`,当前总长度 41 个字符;只保存完整密钥的 SHA-256 哈希,明文只在创建时返回一次。日志、MongoDB、Redis、异常和回答中不得出现完整密钥。
|
|
143
|
+
- 浏览器启动链接使用 `{Microi.Client前端WebBase}/?OsClient={租户Key}#/access-login?access_key={密钥}&redirect={encodeURIComponent后的站内Hash路由}`。例如目标路由 `/mic/data-dashboard/preview/01KK988A0YPHKAM8SF216917HX` 编码后是 `redirect=%2Fmic%2Fdata-dashboard%2Fpreview%2F01KK988A0YPHKAM8SF216917HX`。前端域名不能误用 API Server;目标路由必须以 `/` 开头并位于密钥允许页面范围内。
|
|
144
|
+
- 固定电视/看板应保存完整 `/access-login` 链接作为浏览器开机主页或受控书签,不能只保存兑换后的预览页。`access_key` 位于 Hash 中,前端解析后立即 `history.replaceState` 清除;地址栏随后不含密钥是正常安全行为,禁止给目标页重复追加密钥或引入 `permanent=1/keep_login=1` 等客户端寿命参数。永久只描述密钥记录不计划到期;短期受限 Token 正常轮换,会话丢失时重新打开启动链接再次兑换。后端兑换只接受 JSON Body。
|
|
145
|
+
- 兑换得到短期 `_ClientType=AccessKey` Token。JWT 只保存 `MicroiAccessKeyId`,权限范围从共享数据库/Redis实时加载,不能把范围写进共享 `CurrentToken.CurrentUser`。
|
|
146
|
+
- 密钥权限只能收窄:帐号实时角色/菜单/行范围与 `Scopes + AllowedRoutes + AllowedTableNames + AllowedApiEngineKeys + AllowedDataSourceKeys` 取交集。检查必须位于管理员快捷放行之前。
|
|
147
|
+
- 默认只允许 `page:open + form:read`;`form:write/form:export/file:read/api-engine:run/data-source:run` 必须显式启用。`AllowedRoutes` 和 `AllowedTableNames` 可以使用单独值 `*` 表示“全部目标帐号已授权资源”,但检查仍必须位于管理员快捷放行之前并继续执行帐号菜单、表单、部门和行权限;旧 UI 误存的路由值 `/*` 只作为该通配值的兼容别名。`AllowedApiEngineKeys` 和 `AllowedDataSourceKeys` 必须是准确白名单,禁止 `*`。
|
|
148
|
+
- API 放行必须使用按 capability 分类的运行时矩阵,不能靠零散补一个报错路径:页面范围为 `*` 且具有 `page:open` 时才允许 `SysMenu/GetSysMenuStep`;指定页面密钥不得读取完整菜单树。只允许会话启动、页面元数据、表单 CRUD/导出、本人后台任务和本人终端信息等明确运行面;显示密码、密钥管理、菜单/表/字段设计、索引、缓存、服务器、其它终端管理等控制面保持拒绝。
|
|
149
|
+
- FormEngine 通过 action filter 对模型绑定后的 `FormEngineKey/TableName/TableId/TableIds` 逐项校验,并单独识别 `ModuleEngineKey/_ModuleEngineKey/_SysMenuId/SysMenuId`。`AllowedTableNames` 在共享数据库回源时派生为 `AllowedTableIds + AllowedFieldIds + AllowedMenuReferences`;菜单引用只收集 `DiyTableId` 位于允许范围内的 `sys_menu.Id/ModuleEngineKey`,全部放进带契约版本的短 TTL Redis 运行时缓存。解析失败必须 fail closed。这样只传表 Id 的元数据接口、只传 `_FieldId/FieldIds` 的字段 SQL/批量下拉数据接口,以及只传菜单 Id 的标准列表请求,都不能绕过表名白名单或被误拒绝。
|
|
150
|
+
- FormEngine 的动态友好路由(如 `GetTableData-{table-key}`、`Get-TableData-{table-key}`、`GetFormData-{table-key}` 及写入别名)必须在 API capability 鉴权前归一化。动态路由转换器与访问密钥鉴权必须复用同一个别名解析器,禁止各维护一份前缀清单;归一化只决定所需 scope,URL 后缀还必须与模型绑定后的 `FormEngineKey/TableId/ModuleEngineKey/_SysMenuId` 之一规范化一致,并再次校验准确表或菜单引用。空 Key、前后缀不一致、额外路径段和相似前缀必须拒绝。
|
|
151
|
+
- `ApiEngineController` 的 Run 系列即使标记了 `[AllowAnonymous]`,检测到访问密钥会话后也必须解析实际命中的引擎模型并校验准确 `ApiEngineKey`;数据源运行和后台接口任务同样校验准确 Key。禁止只在 MVC 授权过滤器中检查粗粒度路径,因为匿名兼容入口会跳过该过滤器。
|
|
152
|
+
- 自动登录 URL 必须携带当前 `OsClient`;前端进入 `/access-login` 时先清除 Hash 中的密钥,再用 JSON Body 兑换,并设置有限超时。禁止等待与兑换无关的 SSO 初始化导致无限加载。
|
|
153
|
+
- 管理操作只允许普通登录会话的本人或管理员;访问密钥会话不能创建或吊销密钥。系统账号中的管理入口必须由 `sys_menu.MoreBtns` 动态配置,并通过通用 `V8.OpenDialog` 打开预注册的 `UserAccessKeyPanel`;不得新增业务专用 `V8.OpenUserAccessKeys`,也不得在通用表格/卡片模板中按表名硬编码。按钮显隐不能代替后端逐次鉴权。
|
|
154
|
+
- 多节点共享 Redis 只作为短 TTL 缓存和限流;数据库是事实源,吊销主动清除缓存。不得使用 `static` 字典、本机文件或本地定时器保存密钥状态。
|
|
155
|
+
- 对外仍要求 HTTPS。固定终端使用独立只读帐号,不能用超级管理员帐号创建看板密钥。
|
|
156
|
+
|
|
157
|
+
验收至少覆盖:明文只返回一次、错误密钥固定时间比较、过期/吊销/停用帐号失败、指定页面成功而其它路由失败、全部页面可加载 `GetSysMenuStep`、标准与动态 `GetTableData/GetFormData` 路由均可读取允许表、仅传绑定菜单 Id 的 `ModuleEngineKey` 动态列表成功、动态写路由仍要求 `form:write`、空 Key/前后缀不一致/相似前缀/额外路径段失败、允许表名及对应表 Id/菜单 Id 成功而其它资源失败、FormEngine 设计接口仍拒绝、接口/数据源 Key 精确限制且动态/后台入口不能绕过、普通帐号权限变化即时收窄、两个 API 节点吊销一致生效。
|
|
158
|
+
<!-- /microi-progressive:chunk -->
|
|
@@ -3,7 +3,7 @@ name: v8-sql-query
|
|
|
3
3
|
description: Microi V8 安全 SQL 查询指南。用于选择 V8.FormEngine _Where 或 V8.Db.FromSql,处理参数化查询、联表、聚合并避免 SQL 注入。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **Codex
|
|
6
|
+
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
7
|
|
|
8
8
|
# Microi V8 安全 SQL 查询
|
|
9
9
|
|
|
@@ -3,12 +3,14 @@ name: v8-table-event
|
|
|
3
3
|
description: Microi V8 表单事件开发。用于编写 InFormV8、SubmitFormV8、SubmitBeforeServerV8、SubmitAfterServerV8、OutFormV8、DataFilterV8 和事务感知事件。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> **Codex
|
|
6
|
+
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
7
|
|
|
8
8
|
# Microi V8 表单事件开发
|
|
9
9
|
|
|
10
10
|
你正在开发 Microi 吾码平台的 V8 表单事件。事件绑定在表单引擎的表上,在数据操作的不同阶段自动触发。
|
|
11
11
|
|
|
12
|
+
<!-- microi-progressive:begin -->
|
|
13
|
+
<!-- microi-progressive:chunk id=v8-table-event-000 sha256=dd86294c8cf42f580b2eb806d981c239c0efc2934505c0716e804de2f8a9235c -->
|
|
12
14
|
## 本地优先与版本头(必做)
|
|
13
15
|
|
|
14
16
|
AI 本地开发表单 V8 事件时,优先修改 `microi-v8-engine/<租户>/<项目>/表单引擎/.../<事件Label>(<EventType>).js` 本地文件,再通过 MCP 或 VS Code 插件同步到数据库。文件中文名必须取 `diy_table` 对应 `diy_field.Label`,其中 `SubmitFormV8` 为 `前端表单提交前V8事件(SubmitFormV8).js`,`OutFormV8` 为 `前端表单提交后V8事件(OutFormV8).js`。若插件显示“本地和远端不一致”,先读取本地与远端并合并有效差异,不能直接覆盖。
|
|
@@ -35,6 +37,8 @@ AI 本地开发表单 V8 事件时,优先修改 `microi-v8-engine/<租户>/<
|
|
|
35
37
|
|
|
36
38
|
生成 V8 事件代码时,代码内容本身(文件头、普通注释、`console.log`、返回 `Msg` 等)不要包含 `Microi`、`吾码` 等平台品牌文字,除非业务数据或字段值本身必须如此。生成代码要有可维护注释:每个 `function` 前写清用途、关键参数和返回值;提交前校验、提交后联动、字段显隐、数据脱敏、跨表写入、复杂条件判断等代码段前写短注释说明业务原因;避免“给变量赋值”这类无信息量注释。
|
|
37
39
|
|
|
40
|
+
<!-- /microi-progressive:chunk -->
|
|
41
|
+
<!-- microi-progressive:chunk id=v8-table-event-001 sha256=7bad55ca6781e6ca458a0333a47c71a3bad30334d146e940496c45c4ffa7ddf5 -->
|
|
38
42
|
## 事件类型
|
|
39
43
|
|
|
40
44
|
| 事件 | 运行端 | V8.EventName | 触发时机 | 用途 |
|
|
@@ -46,6 +50,8 @@ AI 本地开发表单 V8 事件时,优先修改 `microi-v8-engine/<租户>/<
|
|
|
46
50
|
| `SubmitAfterServerV8.js` | **后端** | `FormSubmitAfter` | 数据写入 DB 之后 | 触发通知、同步其它表、日志 |
|
|
47
51
|
| `DataFilterV8.js` | **后端** | `DataFilter` | 获取列表/表单数据后 | 每行数据加工、脱敏、补充字段 |
|
|
48
52
|
|
|
53
|
+
<!-- /microi-progressive:chunk -->
|
|
54
|
+
<!-- microi-progressive:chunk id=v8-table-event-002 sha256=1956a46955c57434582c8dd5a41ffe9d445a74f42b26706c7d2033dfb822ffc0 -->
|
|
49
55
|
## 事件触发规则
|
|
50
56
|
|
|
51
57
|
- 后端 V8 事件 / 接口引擎中调用 `V8.FormEngine` 增删改 → **不触发**表单 V8 事件
|
|
@@ -54,6 +60,8 @@ AI 本地开发表单 V8 事件时,优先修改 `microi-v8-engine/<租户>/<
|
|
|
54
60
|
- 服务器端提交前/后 V8 事件在**同一事务**中执行
|
|
55
61
|
- `diy_table.V8Unlimited` 只控制该表的后端提交前、提交后和数据处理 V8;仅当事件链必须保持一个事务且无法安全分片时开启。它解除当前 Jint Engine 的超时、语句、函数递归和累计分配限制,但不解除进程常驻内存、取消、并发、接口嵌套深度、权限和数据库保护。
|
|
56
62
|
|
|
63
|
+
<!-- /microi-progressive:chunk -->
|
|
64
|
+
<!-- microi-progressive:chunk id=v8-table-event-003 sha256=65a1e728982d4e534d06dc2440eac111fe53a08f0a3012c6a9f8b1de45b4b81f -->
|
|
57
65
|
## ⚠️ 关键陷阱(必读)
|
|
58
66
|
|
|
59
67
|
### 1. 设计模式保护(前端事件必加)
|
|
@@ -95,6 +103,8 @@ V8.ApiEngine.Run('other-engine', { Form: V8.Form }, V8.DbTrans);
|
|
|
95
103
|
|
|
96
104
|
---
|
|
97
105
|
|
|
106
|
+
<!-- /microi-progressive:chunk -->
|
|
107
|
+
<!-- microi-progressive:chunk id=v8-table-event-004 sha256=d3852ea4ed2f399ca19b537372d7f53a7d8f667d39ebd215210957fb8b6d9c0f -->
|
|
98
108
|
## 前端事件特有 API
|
|
99
109
|
|
|
100
110
|
```javascript
|
|
@@ -147,241 +157,11 @@ V8.ShowFormTab('tabName'); // 显示Tab
|
|
|
147
157
|
V8.ClickFormTab('tabName'); // 选中Tab
|
|
148
158
|
```
|
|
149
159
|
|
|
150
|
-
|
|
160
|
+
<!-- /microi-progressive:chunk -->
|
|
161
|
+
## 详细参考路由(渐进披露)
|
|
151
162
|
|
|
152
|
-
|
|
153
|
-
// 新增时设置默认值
|
|
154
|
-
if (V8.FormMode === 'Add') {
|
|
155
|
-
V8.FormSet('Status', 1);
|
|
156
|
-
V8.FormSet('CreateTime', DateNow('yyyy-MM-dd HH:mm:ss'));
|
|
157
|
-
V8.FormSet('CreatorId', V8.CurrentUser.Id);
|
|
158
|
-
V8.FormSet('CreatorName', V8.CurrentUser.Name);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 编辑时禁用某些字段
|
|
162
|
-
if (V8.FormMode === 'Edit') {
|
|
163
|
-
V8.FieldSet('Account', 'Readonly', true); // 账号不可修改
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 查看模式隐藏操作按钮
|
|
167
|
-
if (V8.FormMode === 'View') {
|
|
168
|
-
V8.HideFormBtn('Save');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 根据角色控制字段可见性
|
|
172
|
-
if (V8.CurrentUser.RoleName.indexOf('管理员') === -1) {
|
|
173
|
-
V8.FieldSet('AuditField', 'Visible', false);
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## SubmitFormV8.js — 前端提交校验
|
|
178
|
-
|
|
179
|
-
```javascript
|
|
180
|
-
// 自定义校验
|
|
181
|
-
var phone = V8.Form.Phone;
|
|
182
|
-
if (phone && !/^1[3-9]\d{9}$/.test(phone)) {
|
|
183
|
-
V8.Tips('手机号格式不正确', false);
|
|
184
|
-
return false; // 返回 false 阻止提交
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 业务逻辑校验
|
|
188
|
-
if (V8.Form.StartDate > V8.Form.EndDate) {
|
|
189
|
-
V8.Tips('开始日期不能大于结束日期', false);
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## SubmitBeforeServerV8.js — 服务端提交前
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
// V8.Form 是即将写入数据库的数据
|
|
198
|
-
// V8.OldForm 是修改前的旧数据(仅更新时有值)
|
|
199
|
-
// V8.FormSubmitAction:'Insert' / 'Update' / 'Delete'
|
|
200
|
-
|
|
201
|
-
// 新增时:自动填充审计字段
|
|
202
|
-
if (V8.FormSubmitAction === 'Insert') {
|
|
203
|
-
V8.Form.CreateTime = DateNow('yyyy-MM-dd HH:mm:ss');
|
|
204
|
-
V8.Form.CreateUserId = V8.CurrentUser.Id;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 更新时:记录修改人
|
|
208
|
-
if (V8.FormSubmitAction === 'Update') {
|
|
209
|
-
V8.Form.UpdateTime = DateNow('yyyy-MM-dd HH:mm:ss');
|
|
210
|
-
V8.Form.UpdateUserId = V8.CurrentUser.Id;
|
|
211
|
-
}
|
|
163
|
+
仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。
|
|
212
164
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
_Where: [['OrderId', '=', V8.Form.Id]]
|
|
217
|
-
});
|
|
218
|
-
if (related.DataCount > 0) {
|
|
219
|
-
return { Code: 0, Msg: '该订单下有明细数据,不允许删除' };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 唯一性校验
|
|
224
|
-
if (V8.FormSubmitAction === 'Insert' || V8.FormSubmitAction === 'Update') {
|
|
225
|
-
var where = [['Code', '=', V8.Form.Code]];
|
|
226
|
-
if (V8.FormSubmitAction === 'Update') {
|
|
227
|
-
where.push(['AND', 'Id', '<>', V8.Form.Id]);
|
|
228
|
-
}
|
|
229
|
-
var exist = V8.FormEngine.GetFormData('Product', { _Where: where });
|
|
230
|
-
if (exist.Code === 1 && exist.Data) {
|
|
231
|
-
return { Code: 0, Msg: '编码已存在' };
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// 返回 { Code: 0, Msg: '...' } 阻止提交并自动回滚事务
|
|
236
|
-
// 无需手动调用 V8.DbTrans.Rollback()
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## SubmitAfterServerV8.js — 服务端提交后
|
|
240
|
-
|
|
241
|
-
```javascript
|
|
242
|
-
// 此时数据已成功写入数据库(仍在事务中)
|
|
243
|
-
// 返回 { Code: 0 } 仍可回滚事务
|
|
244
|
-
|
|
245
|
-
// 新增后:自动创建关联数据。保持默认 Server 调用,不触发目标表事件
|
|
246
|
-
if (V8.FormSubmitAction === 'Insert') {
|
|
247
|
-
V8.FormEngine.AddFormData('UserProfile', {
|
|
248
|
-
UserId: V8.Form.Id,
|
|
249
|
-
NickName: V8.Form.Name
|
|
250
|
-
}, V8.DbTrans);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 更新后:同步更新其它表的冗余字段
|
|
254
|
-
if (V8.FormSubmitAction === 'Update') {
|
|
255
|
-
if (V8.OldForm.Name !== V8.Form.Name) {
|
|
256
|
-
V8.FormEngine.UptFormDataByWhere('OrderHeader', {
|
|
257
|
-
_Where: [['CustomerId', '=', V8.Form.Id]],
|
|
258
|
-
CustomerName: V8.Form.Name
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 通知(调用其他接口引擎,可共享事务)
|
|
264
|
-
V8.ApiEngine.Run('send-notification', {
|
|
265
|
-
userId: V8.Form.Id,
|
|
266
|
-
type: V8.FormSubmitAction
|
|
267
|
-
}, V8.DbTrans);
|
|
268
|
-
|
|
269
|
-
// 记录操作日志
|
|
270
|
-
V8.Method.AddSysLog({
|
|
271
|
-
Title: V8.FormSubmitAction + ' ' + V8.TableModel.Name,
|
|
272
|
-
Content: JSON.stringify({ Id: V8.Form.Id }),
|
|
273
|
-
Type: '业务日志'
|
|
274
|
-
});
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
`SubmitAfterServerV8` 的“After”仍是“写入后、提交前”。需要在事务真正提交后才发布的缓存版本、跨节点通知等副作用,不能直接在事件中执行。平台为 `microi_database` 提供专用 `V8.Method.RefreshExtensionDatabases()`:事件调用时只登记提交后回调,提交成功才递增当前租户共享 Redis 版本,回滚时自动丢弃。普通业务的外部消息仍优先使用同事务 outbox,不能把任意不可撤销副作用都塞进内存回调。
|
|
278
|
-
|
|
279
|
-
## DataFilterV8.js — 服务端数据处理事件
|
|
280
|
-
|
|
281
|
-
获取列表/表单数据后,每行数据都会执行一次此事件。
|
|
282
|
-
|
|
283
|
-
```javascript
|
|
284
|
-
// V8.RowIndex — 当前行索引(从 0 开始)
|
|
285
|
-
// V8.Form — 当前行数据
|
|
286
|
-
// V8.NotSaveField — 指定哪些字段编辑时不保存(数组)
|
|
287
|
-
// V8.CacheData — 用于缓存数据,避免每行重复查询
|
|
288
|
-
|
|
289
|
-
// 补充计算字段
|
|
290
|
-
V8.Form.TotalPrice = V8.Form.Price * V8.Form.Quantity;
|
|
291
|
-
|
|
292
|
-
// 指定某些字段不保存(仅在编辑表单时有效)
|
|
293
|
-
V8.NotSaveField = ['TotalPrice', 'CompanyName'];
|
|
294
|
-
|
|
295
|
-
// 使用 CacheData 避免 N+1 查询
|
|
296
|
-
if (!V8.CacheData.deptMap) {
|
|
297
|
-
var depts = V8.FormEngine.GetTableData('Department', {});
|
|
298
|
-
var map = {};
|
|
299
|
-
for (var i = 0; i < depts.Data.length; i++) {
|
|
300
|
-
map[depts.Data[i].Id] = depts.Data[i].Name;
|
|
301
|
-
}
|
|
302
|
-
V8.CacheData.deptMap = map;
|
|
303
|
-
}
|
|
304
|
-
V8.Form.DeptName = V8.CacheData.deptMap[V8.Form.DeptId] || '';
|
|
305
|
-
|
|
306
|
-
// 数据脱敏
|
|
307
|
-
if (V8.Form.Phone) {
|
|
308
|
-
V8.Form.Phone = V8.Form.Phone.substring(0, 3) + '****' + V8.Form.Phone.substring(7);
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
## 事件上下文变量
|
|
313
|
-
|
|
314
|
-
### 前端事件
|
|
315
|
-
|
|
316
|
-
| 变量 | 说明 | 可用事件 |
|
|
317
|
-
|------|------|---------|
|
|
318
|
-
| `V8.Form` | 当前表单数据(新增时也有 Id) | 全部 |
|
|
319
|
-
| `V8.OldForm` | 已加载的修改前旧数据 | 普通表单;提交前/后 |
|
|
320
|
-
| `V8.FormMode` | `'Add'` / `'Edit'` / `'View'` | 全部 |
|
|
321
|
-
| `V8.FormOutAction` | `'Insert'`/`'Update'`/`'Close'`/`'Delete'` | FormOut |
|
|
322
|
-
| `V8.FormSubmitAction` | `'Insert'` / `'Update'` / `'Delete'` | SubmitBefore |
|
|
323
|
-
| `V8.EventName` | 当前事件名 | 全部 |
|
|
324
|
-
| `V8.CurrentUser` | 当前用户 | 全部 |
|
|
325
|
-
| `V8.TableId` / `V8.TableName` | 当前表 Id / Name | 全部 |
|
|
326
|
-
| `V8.SelectedData` / `V8.TableRowSelected` | 选中的行数组 | 列表/批量按钮 |
|
|
327
|
-
| `V8.CurrentTableData` | 当前表当页数据 | 全部 |
|
|
328
|
-
| `V8.ClientType` | `'PC'`/`'IOS'`/`'Android'`/`'H5'`/`'WeChat'` | 全部 |
|
|
329
|
-
| `V8.ThisValue` | 当前字段新值:可能是对象、原始值或行内 `{New,Old}` | FieldValueChange |
|
|
330
|
-
| `V8.OldValue` | 当前字段旧值,仅行内编辑可靠 | 表格行内 FieldValueChange |
|
|
331
|
-
| `V8.KeyCode` | 键盘事件的键码 | FieldOnKeyup |
|
|
332
|
-
| `V8.Event` | 原生事件;键盘事件目前不提供 | FieldSlotButtonClick 等显式事件 |
|
|
333
|
-
| `V8.ParentV8` | 子表中访问父表 V8 对象 | 子表事件 |
|
|
334
|
-
|
|
335
|
-
### 后端事件
|
|
336
|
-
|
|
337
|
-
| 变量 | 说明 | 可用事件 |
|
|
338
|
-
|------|------|---------|
|
|
339
|
-
| `V8.Form` | 当前表单/行数据 | 全部 |
|
|
340
|
-
| `V8.OldForm` | 提交前旧数据 | SubmitBefore/After |
|
|
341
|
-
| `V8.FormSubmitAction` | `'Insert'`/`'Update'`/`'Delete'` | SubmitBefore/After |
|
|
342
|
-
| `V8.TableModel` | 表模型(Id, Name 等) | SubmitBefore/After |
|
|
343
|
-
| `V8.EventName` | 当前事件名 | 全部 |
|
|
344
|
-
| `V8.InvokeType` | `'Server'`/`'Client'` | 全部 |
|
|
345
|
-
| `V8.CurrentUser` | 当前用户 | 全部 |
|
|
346
|
-
| `V8.RowIndex` | 行索引(从 0) | DataFilter |
|
|
347
|
-
| `V8.NotSaveField` | 不保存的字段(数组,可写) | DataFilter |
|
|
348
|
-
| `V8.CacheData` | 缓存数据(避免 N+1,可写) | DataFilter |
|
|
349
|
-
|
|
350
|
-
## 前端事件名(V8.EventName 可能的值)
|
|
351
|
-
|
|
352
|
-
| 值 | 说明 |
|
|
353
|
-
|---|---|
|
|
354
|
-
| `FormIn` | 进入表单事件 |
|
|
355
|
-
| `FormSubmitBefore` | 提交前事件 |
|
|
356
|
-
| `FormOut` | 离开表单事件 |
|
|
357
|
-
| `FieldValueChange` | 字段值变更事件 |
|
|
358
|
-
| `FieldOnKeyup` | 文本框键盘事件 |
|
|
359
|
-
| `TableFieldOnKeyup` | 表格行内文本框键盘事件 |
|
|
360
|
-
| `FieldSlotButtonClick` | 单行文本插槽按钮点击事件 |
|
|
361
|
-
| `V8BtnRun` | V8 按钮执行事件 |
|
|
362
|
-
| `V8BtnLimit` | V8 按钮是否显示事件 |
|
|
363
|
-
| `BtnFormDetailRun` | 详情按钮 V8 按钮 |
|
|
364
|
-
| `TableRowClick` | 表格行点击 V8 事件 |
|
|
365
|
-
| `OpenTableBefore` | 弹出表格前事件 |
|
|
366
|
-
| `OpenTableSubmit` | 弹出表格提交事件 |
|
|
367
|
-
| `PageTab` | 多 Tab 页签 V8 事件 |
|
|
368
|
-
| `WFNodeStart` | 流程节点开始 V8 事件 |
|
|
369
|
-
| `WFNodeEnd` | 流程节点结束 V8 事件 |
|
|
370
|
-
|
|
371
|
-
## 注意事项
|
|
372
|
-
|
|
373
|
-
- 前端事件可使用 `window` 对象和 `async/await`,后端事件不可以
|
|
374
|
-
- 后端提交前/后事件返回 `{ Code: 0, Msg: '...' }` 可阻止数据写入并回滚事务
|
|
375
|
-
- 直接修改 `V8.Form` 的字段值即可改变最终写入的数据
|
|
376
|
-
- 后端事件中使用 `V8.FormEngine` 默认是 Server 调用,不触发目标表事件;`_InvokeType:'Client'` 恰好会触发目标表事件,不能用于“避免递归”
|
|
377
|
-
- `_InvokeType:'Server'` 只表达事件调用语义,不是客户端授权开关;浏览器伪造它不会获得受信任权限
|
|
378
|
-
- `V8.FormSubmitAction` 的值是 `'Insert'`/`'Update'`/`'Delete'`(非 Add/Upt/Del)
|
|
379
|
-
- 在 DataFilterV8 中使用 `V8.CacheData` 缓存查询结果,避免每行执行 N+1 查询
|
|
380
|
-
- 表事件调用下游接口时,`diy_table.V8Unlimited` 不会自动放开下游接口;下游 `sys_apiengine.V8Unlimited` 必须独立配置,避免一次开关无边界扩散到整条调用链
|
|
381
|
-
|
|
382
|
-
### 复盘:提交后事件误把增量表单当作完整记录
|
|
383
|
-
|
|
384
|
-
- 触发场景:插件、MCP 或后端只更新代码等少数字段时,`SubmitAfterServerV8` 直接对 `V8.Form.Id`、业务 Key 调用 `toLowerCase()`,保存动作因字段未出现在增量参数中而异常。
|
|
385
|
-
- 根因:事件假设 `V8.Form` 始终包含整行数据,没有兼容稀疏更新(sparse patch)。
|
|
386
|
-
- 通用规则:提交后事件使用非本次必传字段前,必须从 `V8.OldForm` 合并兜底,或凭已有 `Id` 回查当前记录;调用字符串方法前先显式 `String(...)` 并校验空值。写缓存时应缓存合并后的完整模型,不能用稀疏对象覆盖整行缓存。
|
|
387
|
-
- 自动化检查:分别只更新一个普通字段、只更新大文本代码字段并执行一次完整表单保存;事件均不得报错,按 Id、唯一 Key 和可选地址回读缓存都应得到完整记录。
|
|
165
|
+
- [references/progressive-01-informv8-js-表单打开事件.md](references/progressive-01-informv8-js-表单打开事件.md):InFormV8.js — 表单打开事件;SubmitFormV8.js — 前端提交校验;SubmitBeforeServerV8.js — 服务端提交前;SubmitAfterServerV8.js — 服务端提交后;DataFilterV8.js — 服务端数据处理事件;事件上下文变量
|
|
166
|
+
- [references/progressive-02-前端事件名-v8-eventname-可能的值.md](references/progressive-02-前端事件名-v8-eventname-可能的值.md):前端事件名(V8.EventName 可能的值);注意事项
|
|
167
|
+
<!-- microi-progressive:end -->
|