@microi.net/cli 5.5.9 → 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.
- 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/assets/build-meta.json +5 -5
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/mcp-server.js +99 -99
- package/scripts/microi-cli.js +39 -36
- package/scripts/microi-skills.meta.json +341 -338
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +3566 -3566
- package/skills/README.md +4 -4
- package/skills/ai-engine/SKILL.md +45 -45
- package/skills/ai-platform-governance/SKILL.md +177 -177
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
- package/skills/app-store/SKILL.md +21 -4
- package/skills/business-blueprint/SKILL.md +85 -85
- package/skills/datasource-engine/SKILL.md +91 -91
- package/skills/dos-orm/SKILL.md +15 -0
- package/skills/dos-orm/references/api-reference.md +229 -229
- package/skills/email-engine/SKILL.md +80 -80
- package/skills/email-engine/references/v8-email.md +34 -34
- package/skills/job-engine/SKILL.md +154 -154
- package/skills/message-notification/SKILL.md +27 -1
- package/skills/message-notification/agents/openai.yaml +5 -5
- package/skills/message-notification/references/contracts.md +102 -102
- package/skills/microi-ai-app-auth.js +652 -652
- package/skills/microi-ai-application/SKILL.md +102 -102
- package/skills/microi-ai-application/agents/openai.yaml +4 -4
- package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
- package/skills/microi-client-frontend/SKILL.md +47 -41
- 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 +4 -4
- 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 +30 -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 +58 -58
- package/skills/microi-codex/SKILL.md +2 -0
- package/skills/microi-codex-installer/SKILL.md +216 -216
- package/skills/microi-codex-installer/agents/openai.yaml +7 -7
- package/skills/microi-datasource-mapping/SKILL.md +67 -67
- package/skills/microi-db-schema/SKILL.md +6 -6
- package/skills/microi-db-schema/references/core-tables.md +18 -18
- package/skills/microi-db-schema/references/form-component-options.md +256 -256
- package/skills/microi-db-schema/references/schema-overview.md +11 -11
- package/skills/microi-db-schema/references/schema.md +46 -46
- package/skills/microi-db-schema/references/table-catalog.md +57 -57
- package/skills/microi-deployment/SKILL.md +18 -1
- package/skills/microi-deployment/references/deployment-matrix.md +101 -101
- package/skills/microi-docs-coverage/SKILL.md +10 -0
- package/skills/microi-docs-coverage/references/capability-map.md +3 -2
- package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
- package/skills/microi-form-engine/SKILL.md +27 -3
- package/skills/microi-form-engine/references/component-catalog.md +218 -218
- package/skills/microi-form-engine/references/data-source-events.md +124 -124
- package/skills/microi-form-layout/SKILL.md +13 -13
- package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
- package/skills/microi-frontend-sdk/SKILL.md +4 -4
- 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 +175 -175
- package/skills/microi-left-right-layout/SKILL.md +141 -141
- package/skills/microi-microservice/SKILL.md +2 -1
- package/skills/microi-microservice/references/runtime-delivery.md +278 -278
- package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
- package/skills/microi-solution-quotation/SKILL.md +78 -78
- package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
- package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
- package/skills/microi-sso/SKILL.md +6 -0
- package/skills/microi-sso/references/acceptance.md +49 -49
- package/skills/microi-sso/references/configuration-and-security.md +53 -53
- package/skills/microi-sso/references/inbound.md +53 -53
- package/skills/microi-sso/references/outbound.md +39 -39
- package/skills/microi-system-delivery/SKILL.md +5 -4
- 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 +7 -2
- package/skills/microi-ui/SKILL.md +7 -2
- package/skills/microi-uniapp-frontend/SKILL.md +134 -134
- 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 +136 -136
- 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 +124 -124
- package/skills/module-engine/SKILL.md +30 -14
- package/skills/ocr-engine/SKILL.md +2 -2
- package/skills/ocr-engine/agents/openai.yaml +1 -1
- package/skills/page-engine/SKILL.md +28 -28
- 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 +126 -126
- 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 +42 -42
- package/skills/performance-testing/SKILL.md +120 -120
- package/skills/playwright-e2e/SKILL.md +84 -84
- 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 +68 -68
- 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 +64 -64
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +20 -20
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -69
- package/skills/print-engine/SKILL.md +11 -3
- package/skills/production-readonly-audit/SKILL.md +41 -41
- package/skills/report-engine/SKILL.md +71 -71
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
- package/skills/scripts/sync-embedded-skills.mjs +46 -0
- package/skills/scripts/validate-progressive-disclosure.mjs +8 -3
- package/skills/search-engine/SKILL.md +75 -75
- package/skills/spider-engine/SKILL.md +190 -190
- package/skills/system-observability/SKILL.md +169 -157
- package/skills/system-observability/references/memory-incident-triage.md +73 -73
- package/skills/translate-engine/SKILL.md +1 -1
- package/skills/ui-design/SKILL.md +3 -1
- package/skills/uniapp-mall-assets/SKILL.md +170 -170
- package/skills/unity-integration/SKILL.md +155 -155
- package/skills/unity-integration/agents/openai.yaml +4 -4
- package/skills/unity-integration/references/ai-app-delivery.md +103 -103
- package/skills/unity-integration/references/sdk-api.md +82 -82
- package/skills/unity-integration/references/toolbox-migration.md +66 -66
- package/skills/unity-integration/references/webgl-hosting.md +57 -57
- package/skills/v8-api-config/SKILL.md +4 -0
- package/skills/v8-cache-pattern/SKILL.md +306 -306
- package/skills/v8-crud-api/SKILL.md +29 -29
- 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 +56 -56
- package/skills/v8-debugging/SKILL.md +143 -143
- package/skills/v8-explorer-tree/SKILL.md +64 -64
- 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 +201 -201
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +118 -118
- 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 +32 -32
- package/skills/v8-file-upload/SKILL.md +5 -2
- 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 +2 -2
- 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 +138 -138
- package/skills/v8-formengine-http/SKILL.md +126 -108
- package/skills/v8-frontend-events/SKILL.md +22 -22
- package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
- package/skills/v8-frontend-events/references/bluetooth-print.md +247 -247
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +50 -50
- package/skills/v8-http-integration/SKILL.md +106 -106
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +31 -31
- 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 +5 -5
- package/skills/v8-image-processing/SKILL.md +190 -190
- package/skills/v8-image-processing/agents/openai.yaml +4 -4
- package/skills/v8-image-processing/references/api-reference.md +623 -623
- package/skills/v8-menu-buttons/SKILL.md +103 -103
- package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +145 -145
- package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
- package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +43 -43
- package/skills/v8-mongodb/SKILL.md +65 -65
- package/skills/v8-mq-mqtt/SKILL.md +175 -175
- package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +137 -137
- package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
- package/skills/v8-saas-multi-tenant/SKILL.md +223 -223
- package/skills/v8-security/SKILL.md +1 -1
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +122 -122
- package/skills/v8-sql-query/SKILL.md +7 -5
- package/skills/v8-table-event/SKILL.md +9 -2
- 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 +1 -1
- 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 +15 -15
- package/skills/v8-tcp-integration/SKILL.md +1 -1
- package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
- package/skills/v8-template-engine/SKILL.md +59 -59
- package/skills/v8-utilities/SKILL.md +92 -92
- package/skills/v8-utilities/references/client-api-index.md +143 -143
- package/skills/v8-utilities/references/platform-http-routes.md +83 -83
- package/skills/v8-utilities/references/server-api-index.md +74 -74
- package/skills/v8-workflow/SKILL.md +13 -4
- package/skills/vision-engine/SKILL.md +19 -19
- package/skills/vision-engine/references/architecture-and-acceptance.md +34 -34
- package/skills/workspace-conventions/SKILL.md +35 -12
- 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 +2 -2
- 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 +179 -179
- 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 +21 -21
|
@@ -3,61 +3,61 @@
|
|
|
3
3
|
> 按需读取;本文件由 SKILL.md 的原章节无损拆分。
|
|
4
4
|
|
|
5
5
|
<!-- microi-progressive:chunk id=v8-security-005 sha256=c2bc7b1ec3ad6240b05537e1ffada3ef2e07e7a10e15102d9e3c8f777ae7ee31 -->
|
|
6
|
-
## 2. 权限校验
|
|
7
|
-
|
|
8
|
-
### DiyToken 是平台会话与权限入口,不替换为 ASP.NET Identity
|
|
9
|
-
|
|
10
|
-
DiyToken 与 `sys_user`、`OsClient`、终端 `did`、共享 Redis 登录态、角色/部门、菜单动作、表权限、数据范围和接口引擎共同构成吾码认证授权体系。它不是“只有一个 Token 字符串”的简化登录:
|
|
11
|
-
|
|
12
|
-
- 登录、SSO、OAuth、Passkey、人脸或访问密钥验证成功后,统一签发/兑换 DiyToken。
|
|
13
|
-
- Token 认证只证明用户和租户;服务端仍按资源、动作和数据范围授权,前端按钮不可代替。
|
|
14
|
-
- 用户停用、角色变化、单终端/全部终端吊销和 Token 轮换都依赖共享会话事实,不依赖单机内存。
|
|
15
|
-
- ASP.NET Identity 可以作为外部身份源的适配参考,但不能整体替换吾码的租户、低代码权限、V8 和在线终端协议。
|
|
16
|
-
|
|
17
|
-
开发新登录入口时只实现“验证身份 -> 获取仍启用的 `sys_user` -> 签发 DiyToken”,禁止并行创建第二套用户、角色、权限 Token 或会话有效期规则。
|
|
18
|
-
|
|
19
|
-
### 平台 FormEngine 授权边界
|
|
20
|
-
|
|
21
|
-
**Token 只完成身份认证,不是任意表的访问凭证。** 来自浏览器、UniApp、SDK 或其它外部客户端的通用 FormEngine 请求,必须由服务端完成以下授权,V8 或前端代码不得自行模拟、放宽:
|
|
22
|
-
|
|
23
|
-
- HTTP 客户端显式传入 `_SysMenuId`(或兼容的 `ModuleEngineKey`)时,服务端默认进入**严格精确菜单授权**:校验该菜单真实存在、绑定的 `DiyTableId` 与目标表一致、当前用户有效角色拥有该菜单,以及当前操作的 `Read`、`Add`、`Edit`、`Del` 等权限。列表、写入、导入、导出显式传错、伪造或借用其它菜单 Id 必须失败关闭。
|
|
24
|
-
- 为兼容已发布项目中大量未传或保留过期 `_SysMenuId` 的旧版 PC/UniApp,普通业务表的唯一详情只要当前角色真实拥有至少一个直接绑定同表的菜单(或精确表级 `Read` 权限)即可读取,不应用菜单 `SqlWhere` / `SqlJoin`。该规则不能用于放宽列表、导入、导出或写入的菜单动作权限。
|
|
25
|
-
- 历史前端 V8 的无菜单 FormEngine 集合请求由后端根据“当前用户有效角色可访问的 `sys_menu`”推断目标表权限;多个菜单范围只有 Join 上下文一致时才可合并,否则失败关闭。推断必须使用后端授权快照,不能相信前端提交的角色、菜单列表或权限 JSON。确实没有菜单入口的 SDK/定制页面仍可按最小权限使用高级表权限;`diy_table.BindRole` 只做候选角色过滤,不能单独代替操作权限。
|
|
26
|
-
- 标准 PC 表单引擎会通过前端 FormEngine facade 给“当前表”的 V8 调用自动注入真实 `_SysMenuId`;V8 跨表调用故意保持无菜单,让后端按目标表的已授权菜单推断,禁止把当前主表菜单错误传播给其它表。
|
|
27
|
-
- 历史 PC/UniApp 的单表字段元数据可在菜单缺失/过期时回退到当前角色另一个引用同表的已授权菜单;`GetDiyFieldByDiyTables` 还可能批量提交“主表 + 关联表”,后端必须保留调用顺序,以第一张主表作为强制授权锚点。主表通过后,后续表逐张授权并过滤未授权表/保护表,不能让一张被拒绝的关联表拖垮已授权主表,也不能把被过滤表的字段、SQL 数据源或 V8 配置返回客户端。元数据兼容不授予数据行权限。
|
|
28
|
-
- `TableChild` 隐藏子菜单不要求存量角色逐个补授权。子表访问只能由后端在同一次请求中验证“已授权父菜单、父表 TableChild 字段配置、子菜单绑定、父记录可见范围、父键唯一性和子表外键”后委托,并把外键条件强制写入真实查询/写入;伪造 `_TableChildAuth`、跨父记录借用外键或脱离父表直接访问都必须失败。
|
|
29
|
-
- 菜单 `SqlWhere`、`SqlJoin` / `JoinTables` 数据范围必须在服务端形成的**真实列表、计数和导出查询**中执行,不能只用于界面展示或查询后过滤。单行详情只校验同表菜单访问权,不应用这些模块列表过滤;它们也不是行级写权限。
|
|
30
|
-
- 主表新增、修改、删除分别由当前角色的 `Add`、`Edit`、`Del` 权限控制;不得把查询 SqlWhere 追加到写入 SQL,也不得因为查询包含跨表 Join 拒绝已获授权的写入。需要“仅可修改本人数据”等业务限制时,在 `SubmitBeforeServerV8` 或专用接口引擎中以可信服务器代码校验,并统一写入 `TenantId`、负责人、创建人等归属字段。
|
|
31
|
-
- 导入、导出必须携带真实菜单上下文,并分别拥有 `Import`、`Export`;不能用 Table 级直接授权绕过。
|
|
32
|
-
- 平台表必须按后端 `PlatformResourceSecurity` 分级:账号/角色/权限、SaaS 配置、接口引擎、表字段元数据、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,写入仍硬拒绝;`mic_page/mic_print` 按真实菜单或 Table 的 `Read/Add/Edit/Del` 权限管理。
|
|
33
|
-
- 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;上述三类平台表必须先于匿名开关拒绝。
|
|
34
|
-
- 角色增删改接口不能只相信 Token 缓存或前端禁用状态,必须覆盖请求中的 `_CurrentUser/OsClient`,并从租户主库复核活动用户、数据库 Level 与有效角色 Level。角色降级要先同步受影响用户 Level,再提升共享授权 `epoch`,避免旧令牌窗口;Postman 伪造 `_IsAdmin/Level/RoleIds` 必须失败。
|
|
35
|
-
- 权限 JSON、角色 Id 或菜单上下文解析失败时必须失败关闭;角色 Id 使用精确集合匹配,禁止 `Contains` 子串判断。
|
|
36
|
-
|
|
37
|
-
标准菜单模块继承菜单权限,不需要维护“角色 × 全部业务表”的巨大矩阵,也不能要求所有历史前端 V8 立即补传菜单 Id。新前端在当前表上下文应由平台 facade 自动携带真实菜单;历史无菜单请求继续由后端安全推断。【高级表权限】只用于确实没有任何菜单入口的定制页面/SDK,并按最小权限授予。
|
|
38
|
-
|
|
39
|
-
跨租户公共发现接口可以匿名,但返回值必须采用严格字段白名单,只返回公开标识和展示信息,禁止返回 `ApiKey`、上游 Endpoint、连接串或内部配置。例如官方 AI 中转模型清单应保持 `IsEnable=1`、`StopHttp=0`、`AllowAnonymous=1`,且只返回模型 Id、展示名等公开字段;将这类被其它租户服务器调用的发现接口误改为非匿名,会让所有消费者得到空模型列表或 `NoAuth`,属于破坏性配置变更。发布和升级后必须用无 Token 的真实 HTTP 请求验证返回字段与匿名状态。
|
|
40
|
-
|
|
41
|
-
> 后端接口引擎、后端表单 V8 和平台内部调用由服务端在构造参数时设置不可由 HTTP JSON 注入的 `_TrustedServerInvocation`,因此调用 `V8.FormEngine` 不要求 `_SysMenuId`。`_InvokeType:'Server'` 只是事件调用语义,不是外部客户端可用的授权开关;普通用户即使在请求中伪造这两个字段也不能成为可信调用。任何能让普通用户写 V8、接口引擎、任务或数据源配置的管理入口本身都必须限制为 `Level >= 9999`,否则会重新获得任意数据执行能力。
|
|
42
|
-
|
|
43
|
-
> 外层 `AddFormData` 的客户端菜单/`Add` 权限校验与事件内部执行权是两件事:前者防止无新增权限的用户进入事件,后者允许已经进入的 `SubmitBeforeServerV8` / `SubmitAfterServerV8` 像接口引擎一样在当前租户内执行复杂 SQL、跨表事务和其它表 CRUD。不要把客户端菜单范围再次套到服务器 V8 的嵌套调用。
|
|
44
|
-
|
|
45
|
-
### FormEngine 授权缓存与性能
|
|
46
|
-
|
|
47
|
-
授权不能在每次 FormEngine 请求中重复全表查询,也不能用只在单节点有效的永久静态字典。平台使用按 `OsClient` 隔离的 Redis 授权版本 `epoch`、用户级授权快照、短 TTL 的进程内 L1 与共享 Redis L2:
|
|
48
|
-
|
|
49
|
-
- 请求先读取当前租户 `epoch`,再按“租户 + epoch + 用户”读取授权快照;同一版本命中时复用有效用户、有效角色、菜单、表与操作权限。
|
|
50
|
-
- 正确菜单、无菜单候选范围和详情同表菜单访问都只读取一次缓存快照;详情不逐菜单执行数据范围探测。不得把详情的同表菜单兼容扩展为无范围列表或批量查询。
|
|
51
|
-
- 冷加载必须从主库读取 `sys_user`、`sys_role`、`sys_rolelimit`、`sys_menu` 等授权事实,避免只读副本延迟把已撤销权限重新缓存。
|
|
52
|
-
- 用户状态/级别/角色、角色状态、菜单绑定/数据范围、角色菜单或高级表权限发生变化时,必须在事务成功后递增共享 Redis `epoch`。所有 API 节点看到新版本后自然放弃旧快照,不依赖粘性会话或逐节点重启。
|
|
53
|
-
- L1 只做短时性能优化,允许丢失;L2 和 `epoch` 才负责多节点共享版本。缓存失效异常时应缩短使用窗口并失败关闭敏感操作,不能无限沿用旧权限。
|
|
54
|
-
- 快照 Key 必须包含独立的“序列化契约版本”。新增 `UserLevel`、`IsActiveUser` 等安全字段或改变字段语义时提升版本,让 Redis 中跨重启、滚动升级遗留的旧 JSON 立即失效,禁止缺失字段按 `0/false` 反序列化后误拒绝有效管理员或误放普通用户。
|
|
55
|
-
- 已通过 `Level >= 9999` 校验的表单设计器批量写 `diy_field` 时,应在外层只授权一次,并校验所有字段都属于同一 `TableId`;随后在同一事务内更新元数据,批次结束后只清一次缓存/版本。不要让每个字段重新进入通用 FormEngine 授权、V8、日志和缓存管线。`AddDiyField/AddField` 等单字段能力仍须传递经服务端确认的管理员上下文;升级程序等无 HTTP 用户的可信任务必须构造强类型参数并设置不可由 JSON 绑定的 `_TrustedServerInvocation`。
|
|
56
|
-
|
|
57
|
-
### 接口引擎中校验当前用户
|
|
58
|
-
|
|
59
|
-
```javascript
|
|
60
|
-
// 只允许自己查看自己的数据
|
|
6
|
+
## 2. 权限校验
|
|
7
|
+
|
|
8
|
+
### DiyToken 是平台会话与权限入口,不替换为 ASP.NET Identity
|
|
9
|
+
|
|
10
|
+
DiyToken 与 `sys_user`、`OsClient`、终端 `did`、共享 Redis 登录态、角色/部门、菜单动作、表权限、数据范围和接口引擎共同构成吾码认证授权体系。它不是“只有一个 Token 字符串”的简化登录:
|
|
11
|
+
|
|
12
|
+
- 登录、SSO、OAuth、Passkey、人脸或访问密钥验证成功后,统一签发/兑换 DiyToken。
|
|
13
|
+
- Token 认证只证明用户和租户;服务端仍按资源、动作和数据范围授权,前端按钮不可代替。
|
|
14
|
+
- 用户停用、角色变化、单终端/全部终端吊销和 Token 轮换都依赖共享会话事实,不依赖单机内存。
|
|
15
|
+
- ASP.NET Identity 可以作为外部身份源的适配参考,但不能整体替换吾码的租户、低代码权限、V8 和在线终端协议。
|
|
16
|
+
|
|
17
|
+
开发新登录入口时只实现“验证身份 -> 获取仍启用的 `sys_user` -> 签发 DiyToken”,禁止并行创建第二套用户、角色、权限 Token 或会话有效期规则。
|
|
18
|
+
|
|
19
|
+
### 平台 FormEngine 授权边界
|
|
20
|
+
|
|
21
|
+
**Token 只完成身份认证,不是任意表的访问凭证。** 来自浏览器、UniApp、SDK 或其它外部客户端的通用 FormEngine 请求,必须由服务端完成以下授权,V8 或前端代码不得自行模拟、放宽:
|
|
22
|
+
|
|
23
|
+
- HTTP 客户端显式传入 `_SysMenuId`(或兼容的 `ModuleEngineKey`)时,服务端默认进入**严格精确菜单授权**:校验该菜单真实存在、绑定的 `DiyTableId` 与目标表一致、当前用户有效角色拥有该菜单,以及当前操作的 `Read`、`Add`、`Edit`、`Del` 等权限。列表、写入、导入、导出显式传错、伪造或借用其它菜单 Id 必须失败关闭。
|
|
24
|
+
- 为兼容已发布项目中大量未传或保留过期 `_SysMenuId` 的旧版 PC/UniApp,普通业务表的唯一详情只要当前角色真实拥有至少一个直接绑定同表的菜单(或精确表级 `Read` 权限)即可读取,不应用菜单 `SqlWhere` / `SqlJoin`。该规则不能用于放宽列表、导入、导出或写入的菜单动作权限。
|
|
25
|
+
- 历史前端 V8 的无菜单 FormEngine 集合请求由后端根据“当前用户有效角色可访问的 `sys_menu`”推断目标表权限;多个菜单范围只有 Join 上下文一致时才可合并,否则失败关闭。推断必须使用后端授权快照,不能相信前端提交的角色、菜单列表或权限 JSON。确实没有菜单入口的 SDK/定制页面仍可按最小权限使用高级表权限;`diy_table.BindRole` 只做候选角色过滤,不能单独代替操作权限。
|
|
26
|
+
- 标准 PC 表单引擎会通过前端 FormEngine facade 给“当前表”的 V8 调用自动注入真实 `_SysMenuId`;V8 跨表调用故意保持无菜单,让后端按目标表的已授权菜单推断,禁止把当前主表菜单错误传播给其它表。
|
|
27
|
+
- 历史 PC/UniApp 的单表字段元数据可在菜单缺失/过期时回退到当前角色另一个引用同表的已授权菜单;`GetDiyFieldByDiyTables` 还可能批量提交“主表 + 关联表”,后端必须保留调用顺序,以第一张主表作为强制授权锚点。主表通过后,后续表逐张授权并过滤未授权表/保护表,不能让一张被拒绝的关联表拖垮已授权主表,也不能把被过滤表的字段、SQL 数据源或 V8 配置返回客户端。元数据兼容不授予数据行权限。
|
|
28
|
+
- `TableChild` 隐藏子菜单不要求存量角色逐个补授权。子表访问只能由后端在同一次请求中验证“已授权父菜单、父表 TableChild 字段配置、子菜单绑定、父记录可见范围、父键唯一性和子表外键”后委托,并把外键条件强制写入真实查询/写入;伪造 `_TableChildAuth`、跨父记录借用外键或脱离父表直接访问都必须失败。
|
|
29
|
+
- 菜单 `SqlWhere`、`SqlJoin` / `JoinTables` 数据范围必须在服务端形成的**真实列表、计数和导出查询**中执行,不能只用于界面展示或查询后过滤。单行详情只校验同表菜单访问权,不应用这些模块列表过滤;它们也不是行级写权限。
|
|
30
|
+
- 主表新增、修改、删除分别由当前角色的 `Add`、`Edit`、`Del` 权限控制;不得把查询 SqlWhere 追加到写入 SQL,也不得因为查询包含跨表 Join 拒绝已获授权的写入。需要“仅可修改本人数据”等业务限制时,在 `SubmitBeforeServerV8` 或专用接口引擎中以可信服务器代码校验,并统一写入 `TenantId`、负责人、创建人等归属字段。
|
|
31
|
+
- 导入、导出必须携带真实菜单上下文,并分别拥有 `Import`、`Export`;不能用 Table 级直接授权绕过。
|
|
32
|
+
- 平台表必须按后端 `PlatformResourceSecurity` 分级:账号/角色/权限、SaaS 配置、接口引擎、表字段元数据、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,写入仍硬拒绝;`mic_page/mic_print` 按真实菜单或 Table 的 `Read/Add/Edit/Del` 权限管理。
|
|
33
|
+
- 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;上述三类平台表必须先于匿名开关拒绝。
|
|
34
|
+
- 角色增删改接口不能只相信 Token 缓存或前端禁用状态,必须覆盖请求中的 `_CurrentUser/OsClient`,并从租户主库复核活动用户、数据库 Level 与有效角色 Level。角色降级要先同步受影响用户 Level,再提升共享授权 `epoch`,避免旧令牌窗口;Postman 伪造 `_IsAdmin/Level/RoleIds` 必须失败。
|
|
35
|
+
- 权限 JSON、角色 Id 或菜单上下文解析失败时必须失败关闭;角色 Id 使用精确集合匹配,禁止 `Contains` 子串判断。
|
|
36
|
+
|
|
37
|
+
标准菜单模块继承菜单权限,不需要维护“角色 × 全部业务表”的巨大矩阵,也不能要求所有历史前端 V8 立即补传菜单 Id。新前端在当前表上下文应由平台 facade 自动携带真实菜单;历史无菜单请求继续由后端安全推断。【高级表权限】只用于确实没有任何菜单入口的定制页面/SDK,并按最小权限授予。
|
|
38
|
+
|
|
39
|
+
跨租户公共发现接口可以匿名,但返回值必须采用严格字段白名单,只返回公开标识和展示信息,禁止返回 `ApiKey`、上游 Endpoint、连接串或内部配置。例如官方 AI 中转模型清单应保持 `IsEnable=1`、`StopHttp=0`、`AllowAnonymous=1`,且只返回模型 Id、展示名等公开字段;将这类被其它租户服务器调用的发现接口误改为非匿名,会让所有消费者得到空模型列表或 `NoAuth`,属于破坏性配置变更。发布和升级后必须用无 Token 的真实 HTTP 请求验证返回字段与匿名状态。
|
|
40
|
+
|
|
41
|
+
> 后端接口引擎、后端表单 V8 和平台内部调用由服务端在构造参数时设置不可由 HTTP JSON 注入的 `_TrustedServerInvocation`,因此调用 `V8.FormEngine` 不要求 `_SysMenuId`。`_InvokeType:'Server'` 只是事件调用语义,不是外部客户端可用的授权开关;普通用户即使在请求中伪造这两个字段也不能成为可信调用。任何能让普通用户写 V8、接口引擎、任务或数据源配置的管理入口本身都必须限制为 `Level >= 9999`,否则会重新获得任意数据执行能力。
|
|
42
|
+
|
|
43
|
+
> 外层 `AddFormData` 的客户端菜单/`Add` 权限校验与事件内部执行权是两件事:前者防止无新增权限的用户进入事件,后者允许已经进入的 `SubmitBeforeServerV8` / `SubmitAfterServerV8` 像接口引擎一样在当前租户内执行复杂 SQL、跨表事务和其它表 CRUD。不要把客户端菜单范围再次套到服务器 V8 的嵌套调用。
|
|
44
|
+
|
|
45
|
+
### FormEngine 授权缓存与性能
|
|
46
|
+
|
|
47
|
+
授权不能在每次 FormEngine 请求中重复全表查询,也不能用只在单节点有效的永久静态字典。平台使用按 `OsClient` 隔离的 Redis 授权版本 `epoch`、用户级授权快照、短 TTL 的进程内 L1 与共享 Redis L2:
|
|
48
|
+
|
|
49
|
+
- 请求先读取当前租户 `epoch`,再按“租户 + epoch + 用户”读取授权快照;同一版本命中时复用有效用户、有效角色、菜单、表与操作权限。
|
|
50
|
+
- 正确菜单、无菜单候选范围和详情同表菜单访问都只读取一次缓存快照;详情不逐菜单执行数据范围探测。不得把详情的同表菜单兼容扩展为无范围列表或批量查询。
|
|
51
|
+
- 冷加载必须从主库读取 `sys_user`、`sys_role`、`sys_rolelimit`、`sys_menu` 等授权事实,避免只读副本延迟把已撤销权限重新缓存。
|
|
52
|
+
- 用户状态/级别/角色、角色状态、菜单绑定/数据范围、角色菜单或高级表权限发生变化时,必须在事务成功后递增共享 Redis `epoch`。所有 API 节点看到新版本后自然放弃旧快照,不依赖粘性会话或逐节点重启。
|
|
53
|
+
- L1 只做短时性能优化,允许丢失;L2 和 `epoch` 才负责多节点共享版本。缓存失效异常时应缩短使用窗口并失败关闭敏感操作,不能无限沿用旧权限。
|
|
54
|
+
- 快照 Key 必须包含独立的“序列化契约版本”。新增 `UserLevel`、`IsActiveUser` 等安全字段或改变字段语义时提升版本,让 Redis 中跨重启、滚动升级遗留的旧 JSON 立即失效,禁止缺失字段按 `0/false` 反序列化后误拒绝有效管理员或误放普通用户。
|
|
55
|
+
- 已通过 `Level >= 9999` 校验的表单设计器批量写 `diy_field` 时,应在外层只授权一次,并校验所有字段都属于同一 `TableId`;随后在同一事务内更新元数据,批次结束后只清一次缓存/版本。不要让每个字段重新进入通用 FormEngine 授权、V8、日志和缓存管线。`AddDiyField/AddField` 等单字段能力仍须传递经服务端确认的管理员上下文;升级程序等无 HTTP 用户的可信任务必须构造强类型参数并设置不可由 JSON 绑定的 `_TrustedServerInvocation`。
|
|
56
|
+
|
|
57
|
+
### 接口引擎中校验当前用户
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// 只允许自己查看自己的数据
|
|
61
61
|
if (!V8.CurrentUser || !V8.CurrentUser.Id) {
|
|
62
62
|
return { Code: -1, Msg: '未登录' };
|
|
63
63
|
}
|
|
@@ -104,78 +104,78 @@ var result = V8.FormEngine.GetTableData('Order', {
|
|
|
104
104
|
|
|
105
105
|
<!-- /microi-progressive:chunk -->
|
|
106
106
|
<!-- microi-progressive:chunk id=v8-security-006 sha256=ba49a1e5ea27c6cca4c78c6ee3e95a5e41032bb08f6813753bc8aa855ddfc832 -->
|
|
107
|
-
## 5. 防重复提交
|
|
108
|
-
|
|
109
|
-
前端禁用按钮或普通 Cache 的 `Exists → Set` 只能改善体验,不能保证业务只执行一次。写操作必须接收稳定幂等键,并通过数据库唯一约束、条件更新或状态机原子落库;接口引擎可再配置 `LockKey` 降低并发,但锁不能代替业务幂等。
|
|
110
|
-
|
|
111
|
-
```javascript
|
|
112
|
-
var requestId = String(V8.Param.RequestId || '');
|
|
113
|
-
if (!/^[A-Za-z0-9_-]{8,100}$/.test(requestId)) {
|
|
114
|
-
return { Code: 0, Msg: '缺少合法幂等键' };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// IdempotencyKey 在数据库中建立唯一索引;重复请求读取并返回已有结果
|
|
118
|
-
var old = V8.FormEngine.GetFormData('Order', {
|
|
119
|
-
_Where: [['IdempotencyKey', '=', requestId]]
|
|
120
|
-
});
|
|
121
|
-
if (old.Code === 1 && old.Data) {
|
|
122
|
-
return { Code: 1, Data: old.Data, Msg: '重复请求已复用原结果' };
|
|
123
|
-
}
|
|
124
|
-
// 最终仍以唯一约束处理并发竞态,不以这次预查询作为安全保证
|
|
125
|
-
```
|
|
107
|
+
## 5. 防重复提交
|
|
108
|
+
|
|
109
|
+
前端禁用按钮或普通 Cache 的 `Exists → Set` 只能改善体验,不能保证业务只执行一次。写操作必须接收稳定幂等键,并通过数据库唯一约束、条件更新或状态机原子落库;接口引擎可再配置 `LockKey` 降低并发,但锁不能代替业务幂等。
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
var requestId = String(V8.Param.RequestId || '');
|
|
113
|
+
if (!/^[A-Za-z0-9_-]{8,100}$/.test(requestId)) {
|
|
114
|
+
return { Code: 0, Msg: '缺少合法幂等键' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// IdempotencyKey 在数据库中建立唯一索引;重复请求读取并返回已有结果
|
|
118
|
+
var old = V8.FormEngine.GetFormData('Order', {
|
|
119
|
+
_Where: [['IdempotencyKey', '=', requestId]]
|
|
120
|
+
});
|
|
121
|
+
if (old.Code === 1 && old.Data) {
|
|
122
|
+
return { Code: 1, Data: old.Data, Msg: '重复请求已复用原结果' };
|
|
123
|
+
}
|
|
124
|
+
// 最终仍以唯一约束处理并发竞态,不以这次预查询作为安全保证
|
|
125
|
+
```
|
|
126
126
|
|
|
127
127
|
<!-- /microi-progressive:chunk -->
|
|
128
128
|
<!-- microi-progressive:chunk id=v8-security-007 sha256=60297c515c7c7b6e35cd871ab2fd7c772f8b227dfcefa0419868943213556c06 -->
|
|
129
129
|
## 6. 敏感数据
|
|
130
130
|
|
|
131
|
-
### 密码与认证
|
|
132
|
-
|
|
133
|
-
业务 V8 禁止自行查询 `sys_user`、保存密码或用 MD5/SHA1/SHA256 直接哈希密码。统一使用平台登录、重置密码和管理员用户管理接口;新密码存储必须由后端使用带盐、可调成本的专用密码哈希,并支持版本与轮换。登录 RSA 只隐藏传输报文中的明文外观,不能替代 HTTPS,也不能用于密码存储。
|
|
134
|
-
|
|
135
|
-
存量 `PwdEncode=DES` 的兼容例外只能位于 `[PlatformAdminOnly]` 的 `GetSysUserPassword`:还要拒绝访问密钥会话、按当前 `OsClient` 和准确用户 Id 查询、用“解密后重新加密等于原密文”验证结果、返回 `no-store` 并写不含明文的安全审计。`PwdEncode=V8` 不假定可逆。不得把该能力暴露给 FormEngine、V8、普通角色或匿名端点。
|
|
136
|
-
|
|
137
|
-
### 可逆业务秘密:允许加密存储和授权显示
|
|
138
|
-
|
|
139
|
-
设备口令、第三方业务账号密码、客户明确要求再次显示的字段可以使用 `V8.EncryptHelper.DESEncode/DESDecode` 兼容机制,但必须与登录密码、支付私钥、`AuthSecret` 和基础设施密钥分开:
|
|
140
|
-
|
|
141
|
-
```javascript
|
|
142
|
-
// 保存:只在可信后端执行
|
|
143
|
-
V8.Form.SecretCipher = V8.EncryptHelper.DESEncode(String(V8.Form.SecretPlain || ''));
|
|
144
|
-
V8.NotSaveField.push('SecretPlain');
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
显示明文必须设计为独立后端动作,并同时做到:
|
|
148
|
-
|
|
149
|
-
- 校验当前 DiyToken、准确 `OsClient`、当前用户真实角色/菜单/业务权限;高风险场景再要求下面的一次性强身份票据。
|
|
150
|
-
- 只解密一条明确记录,不向列表、批量导出、通用 FormEngine、匿名或访问密钥会话提供解密器。
|
|
151
|
-
- 只记录操作者、目标 Id、用途、结果和时间,禁止日志/审计/通知/缓存保存明文;HTTP 响应设置 `no-store`。
|
|
152
|
-
- 页面默认掩码,点击显示需二次确认,失焦/超时/路由离开后清除;禁止复制到 URL、LocalStorage 或前端日志。
|
|
153
|
-
- DES 是存量兼容格式。新高价值秘密优先由可信 C# 原子能力使用带版本的现代认证加密和集中密钥管理,V8 仍只编排授权显示,不能获取主密钥。
|
|
154
|
-
|
|
155
|
-
### Passkey、Authenticator、人脸与敏感操作票据
|
|
156
|
-
|
|
157
|
-
前端 V8 使用 `V8.Identity.Verify({Purpose,ActionHash,Method,Code})` 完成人机交互;`Method=Totp` 时 `Code` 是用户当前看到的 6 位动态口令。后端必须从数据库重读权威业务字段、按稳定版本/顺序重算 `ActionHash`,再消费票据:
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
var actionHash = V8.EncryptHelper.Sha256Hex(canonicalCommand);
|
|
161
|
-
var verified = V8.Method.ConsumeIdentityVerificationTicket({
|
|
162
|
-
Ticket: V8.Param.IdentityVerificationTicket,
|
|
163
|
-
Purpose: 'RevealBusinessSecret',
|
|
164
|
-
ActionHash: actionHash
|
|
165
|
-
});
|
|
166
|
-
if (verified.Code !== 1) return verified;
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
- Ticket 绑定 `OsClient + UserId + Purpose + ActionHash`,共享 Redis 保存两分钟并使用原子 `GETDEL`,只能消费一次。
|
|
170
|
-
- 前端提交的摘要、`Verified=true`、认证器名称或方法不能作为授权事实;访问密钥会话不能使用。
|
|
171
|
-
- 票据只证明近期强身份,不代替菜单/表/行权限、业务状态机、事务、幂等或审计。
|
|
172
|
-
- 设备指纹/Face ID/Windows Hello 优先走 WebAuthn/Passkey,不需要模型 Docker;只有严格服务端人脸/活体检测才通过 `Microi Face Gateway v1` 接入独立服务。
|
|
173
|
-
- 标准 TOTP Authenticator 不需要 Docker,但 6 位码不能单独标识账号:登录时仍需账号;密钥只以租户绑定的认证密文保存,并用共享限流和已接受计数器阻止暴力尝试与重放。
|
|
174
|
-
- 每个 Passkey/TOTP 分别保存 `AllowPasswordlessLogin` 与 `AllowStepUp`;登录和票据签发都必须服务端重新读取策略,不能只依赖个人中心开关的前端状态。
|
|
175
|
-
- Gitee、微信、GitHub 等外部身份只允许登录已绑定的吾码用户;禁止按邮箱/昵称自动合并账号。OAuth state 与登录票据保存在共享 Redis、单次消费并绑定 `OsClient`/Provider/Origin;固定端点白名单,ClientSecret 只由可信后端读取,最终仍签发 DiyToken。
|
|
176
|
-
- 官方升级通过 `app.microi.saas-engine` 应用包幂等安装身份表、`mci_system_setting`、`mci_user_external_identity`、默认设置、个人中心和系统设置微服务。默认设置按 `ConfigKey + InsertIfMissing` 补齐,不覆盖 `ValueSource=Tenant` 的租户值;Passkey、TOTP、总开关和改密步进验证默认开启,严格人脸及各外部 Provider 默认关闭。
|
|
177
|
-
- 完整表、SaaS 字段、API 和启用顺序见 `microi.doc/docs/doc/more/identity-verification.md`。
|
|
178
|
-
- 身份验证 HTTP 控制面位于 `/api/identityverification/`,外部登录位于 `/api/externallogin/`,动态设置位于 `/api/tenantsystemsettings/`;业务页面优先使用 `V8.Identity` 或官方个人中心,不要自行复制 WebAuthn/OAuth 协议代码。
|
|
131
|
+
### 密码与认证
|
|
132
|
+
|
|
133
|
+
业务 V8 禁止自行查询 `sys_user`、保存密码或用 MD5/SHA1/SHA256 直接哈希密码。统一使用平台登录、重置密码和管理员用户管理接口;新密码存储必须由后端使用带盐、可调成本的专用密码哈希,并支持版本与轮换。登录 RSA 只隐藏传输报文中的明文外观,不能替代 HTTPS,也不能用于密码存储。
|
|
134
|
+
|
|
135
|
+
存量 `PwdEncode=DES` 的兼容例外只能位于 `[PlatformAdminOnly]` 的 `GetSysUserPassword`:还要拒绝访问密钥会话、按当前 `OsClient` 和准确用户 Id 查询、用“解密后重新加密等于原密文”验证结果、返回 `no-store` 并写不含明文的安全审计。`PwdEncode=V8` 不假定可逆。不得把该能力暴露给 FormEngine、V8、普通角色或匿名端点。
|
|
136
|
+
|
|
137
|
+
### 可逆业务秘密:允许加密存储和授权显示
|
|
138
|
+
|
|
139
|
+
设备口令、第三方业务账号密码、客户明确要求再次显示的字段可以使用 `V8.EncryptHelper.DESEncode/DESDecode` 兼容机制,但必须与登录密码、支付私钥、`AuthSecret` 和基础设施密钥分开:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// 保存:只在可信后端执行
|
|
143
|
+
V8.Form.SecretCipher = V8.EncryptHelper.DESEncode(String(V8.Form.SecretPlain || ''));
|
|
144
|
+
V8.NotSaveField.push('SecretPlain');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
显示明文必须设计为独立后端动作,并同时做到:
|
|
148
|
+
|
|
149
|
+
- 校验当前 DiyToken、准确 `OsClient`、当前用户真实角色/菜单/业务权限;高风险场景再要求下面的一次性强身份票据。
|
|
150
|
+
- 只解密一条明确记录,不向列表、批量导出、通用 FormEngine、匿名或访问密钥会话提供解密器。
|
|
151
|
+
- 只记录操作者、目标 Id、用途、结果和时间,禁止日志/审计/通知/缓存保存明文;HTTP 响应设置 `no-store`。
|
|
152
|
+
- 页面默认掩码,点击显示需二次确认,失焦/超时/路由离开后清除;禁止复制到 URL、LocalStorage 或前端日志。
|
|
153
|
+
- DES 是存量兼容格式。新高价值秘密优先由可信 C# 原子能力使用带版本的现代认证加密和集中密钥管理,V8 仍只编排授权显示,不能获取主密钥。
|
|
154
|
+
|
|
155
|
+
### Passkey、Authenticator、人脸与敏感操作票据
|
|
156
|
+
|
|
157
|
+
前端 V8 使用 `V8.Identity.Verify({Purpose,ActionHash,Method,Code})` 完成人机交互;`Method=Totp` 时 `Code` 是用户当前看到的 6 位动态口令。后端必须从数据库重读权威业务字段、按稳定版本/顺序重算 `ActionHash`,再消费票据:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
var actionHash = V8.EncryptHelper.Sha256Hex(canonicalCommand);
|
|
161
|
+
var verified = V8.Method.ConsumeIdentityVerificationTicket({
|
|
162
|
+
Ticket: V8.Param.IdentityVerificationTicket,
|
|
163
|
+
Purpose: 'RevealBusinessSecret',
|
|
164
|
+
ActionHash: actionHash
|
|
165
|
+
});
|
|
166
|
+
if (verified.Code !== 1) return verified;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Ticket 绑定 `OsClient + UserId + Purpose + ActionHash`,共享 Redis 保存两分钟并使用原子 `GETDEL`,只能消费一次。
|
|
170
|
+
- 前端提交的摘要、`Verified=true`、认证器名称或方法不能作为授权事实;访问密钥会话不能使用。
|
|
171
|
+
- 票据只证明近期强身份,不代替菜单/表/行权限、业务状态机、事务、幂等或审计。
|
|
172
|
+
- 设备指纹/Face ID/Windows Hello 优先走 WebAuthn/Passkey,不需要模型 Docker;只有严格服务端人脸/活体检测才通过 `Microi Face Gateway v1` 接入独立服务。
|
|
173
|
+
- 标准 TOTP Authenticator 不需要 Docker,但 6 位码不能单独标识账号:登录时仍需账号;密钥只以租户绑定的认证密文保存,并用共享限流和已接受计数器阻止暴力尝试与重放。
|
|
174
|
+
- 每个 Passkey/TOTP 分别保存 `AllowPasswordlessLogin` 与 `AllowStepUp`;登录和票据签发都必须服务端重新读取策略,不能只依赖个人中心开关的前端状态。
|
|
175
|
+
- Gitee、微信、GitHub 等外部身份只允许登录已绑定的吾码用户;禁止按邮箱/昵称自动合并账号。OAuth state 与登录票据保存在共享 Redis、单次消费并绑定 `OsClient`/Provider/Origin;固定端点白名单,ClientSecret 只由可信后端读取,最终仍签发 DiyToken。
|
|
176
|
+
- 官方升级通过 `app.microi.saas-engine` 应用包幂等安装身份表、`mci_system_setting`、`mci_user_external_identity`、默认设置、个人中心和系统设置微服务。默认设置按 `ConfigKey + InsertIfMissing` 补齐,不覆盖 `ValueSource=Tenant` 的租户值;Passkey、TOTP、总开关和改密步进验证默认开启,严格人脸及各外部 Provider 默认关闭。
|
|
177
|
+
- 完整表、SaaS 字段、API 和启用顺序见 `microi.doc/docs/doc/more/identity-verification.md`。
|
|
178
|
+
- 身份验证 HTTP 控制面位于 `/api/identityverification/`,外部登录位于 `/api/externallogin/`,动态设置位于 `/api/tenantsystemsettings/`;业务页面优先使用 `V8.Identity` 或官方个人中心,不要自行复制 WebAuthn/OAuth 协议代码。
|
|
179
179
|
|
|
180
180
|
### 脱敏返回
|
|
181
181
|
|
|
@@ -81,6 +81,8 @@ V8.FormEngine.GetTableData('Table', { _Where: newWhere });
|
|
|
81
81
|
|
|
82
82
|
> **⚠️ FromSql 调用规则:** `V8.Db.FromSql` 在 V8 中只传 SQL 字符串,不要把动态值作为第二个或后续参数传给 `FromSql`。动态值必须用链式 `.AddInParameter('@p0', value)` 绑定;否则会生成平台不支持的调用签名。
|
|
83
83
|
|
|
84
|
+
> **共享事务:** `V8.Db` 是主库会话,`V8.Db.FromSql` 不会自动加入接口引擎事务。需要与表单、其它 SQL 或流程共同提交/回滚的操作,必须使用 `V8.DbTrans.FromSql`;依赖本事务尚未提交结果的查询也一样。`V8.FormEngine`、`V8.ApiEngine.Run` 继续显式传第三参数 `V8.DbTrans`。禁止从安全代理取内部事务并自行提交。
|
|
85
|
+
|
|
84
86
|
```javascript
|
|
85
87
|
// ❌ 错误形态:不要把动态值作为 FromSql 的第二个参数传入
|
|
86
88
|
|
|
@@ -88,7 +90,7 @@ V8.FormEngine.GetTableData('Table', { _Where: newWhere });
|
|
|
88
90
|
V8.FormEngine.UptFormData('t', { Id: id, A: val1, B: val2 });
|
|
89
91
|
|
|
90
92
|
// ✅ 必须用原生 SQL 时:FromSql 只传 SQL,参数用 AddInParameter
|
|
91
|
-
V8.
|
|
93
|
+
V8.DbTrans.FromSql("UPDATE t SET A=@p0, B=@p1 WHERE Id=@p2")
|
|
92
94
|
.AddInParameter("@p0", val1)
|
|
93
95
|
.AddInParameter("@p1", val2)
|
|
94
96
|
.AddInParameter("@p2", id)
|
|
@@ -118,7 +120,7 @@ var count = V8.Db.FromSql(
|
|
|
118
120
|
.ToScalar();
|
|
119
121
|
|
|
120
122
|
// 非查询(UPDATE / INSERT / DELETE)
|
|
121
|
-
V8.
|
|
123
|
+
V8.DbTrans.FromSql(
|
|
122
124
|
'UPDATE SysUser SET LastLoginTime = @p0 WHERE Id = @p1'
|
|
123
125
|
).AddInParameter("@p0", DateNow('yyyy-MM-dd HH:mm:ss'))
|
|
124
126
|
.AddInParameter("@p1", V8.CurrentUser.Id)
|
|
@@ -174,15 +176,15 @@ MCP 结构发现使用 `microi_inspect_external_database`,安全抽样默认
|
|
|
174
176
|
### 接口引擎事务(自动管理)
|
|
175
177
|
|
|
176
178
|
```javascript
|
|
177
|
-
//
|
|
179
|
+
// 接口引擎创建 V8.DbTrans;V8.Db 是独立主库会话,不自动加入它:
|
|
178
180
|
// 返回 Code=1 → 自动提交事务
|
|
179
181
|
// 返回 Code≠1 → 自动回滚事务
|
|
180
182
|
// 手动调用 V8.DbTrans.Commit() 或 V8.DbTrans.Rollback() 均无效
|
|
181
|
-
V8.
|
|
183
|
+
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance - @p0 WHERE Id = @p1')
|
|
182
184
|
.AddInParameter("@p0", 100)
|
|
183
185
|
.AddInParameter("@p1", fromId)
|
|
184
186
|
.ExecuteNonQuery();
|
|
185
|
-
V8.
|
|
187
|
+
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance + @p0 WHERE Id = @p1')
|
|
186
188
|
.AddInParameter("@p0", 100)
|
|
187
189
|
.AddInParameter("@p1", toId)
|
|
188
190
|
.ExecuteNonQuery();
|
|
@@ -96,10 +96,17 @@ V8.ApiEngine.Run('other-engine', { Form: V8.Form }, V8.DbTrans);
|
|
|
96
96
|
// 不传 V8.DbTrans 会导致并行事务、可能死锁或脏读
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
### 5. 模板引擎与 DataFilterV8 的区别
|
|
99
|
+
### 5. 模板引擎与 DataFilterV8 的区别
|
|
100
100
|
|
|
101
101
|
- 数据**加工**(计算字段、脱敏、查关联表名)→ 用 `DataFilterV8`(后端,每行执行,可用 `V8.CacheData` 防 N+1)
|
|
102
|
-
- 数据**渲染**(颜色徽章、HTML、图片)→ 用【表格 V8 模板引擎】,详见 `v8-template-engine/SKILL.md`
|
|
102
|
+
- 数据**渲染**(颜色徽章、HTML、图片)→ 用【表格 V8 模板引擎】,详见 `v8-template-engine/SKILL.md`
|
|
103
|
+
|
|
104
|
+
### 6. 新增主键与跨表引用
|
|
105
|
+
|
|
106
|
+
- 原生新增管线先独立确定真实主键;`SubmitBeforeServerV8` 中的 `V8.Form.Id` 可能尚未提供。不得自行给 `V8.Form.Id` 补一个 GUID,并把它当作最终插入主键去创建条码、明细、关系或审计。
|
|
107
|
+
- 依赖最终主键的附属记录放在 `SubmitAfterServerV8`,核验 `V8.Form.Id` 后在同一 `V8.DbTrans` 中回读主记录,再创建附属记录。After 仍在提交前,任一附属写入失败必须返回失败并回滚主表;外部消息仍走 outbox。
|
|
108
|
+
- 原生删除事件的权威删除前记录由 `V8.Form` 提供,`V8.OldForm` 可能为空;删除审计、关系清理应按 `FormSubmitAction` 选择上下文。原生删除的 `Form.Version` 来自数据库快照,不能把它当作客户端最后看到的版本。需要拒绝过期删除时,通过受控接口接收期望版本、锁定主库记录并验证,再在同一事务执行删除。
|
|
109
|
+
- 回归必须比较“新增接口返回 Id、主表实际 Id、附属外键、审计 RowId”四者一致,并覆盖唯一冲突时主表及附属记录同时回滚,不能只断言新增接口 `Code=1`。
|
|
103
110
|
|
|
104
111
|
---
|
|
105
112
|
|
|
@@ -181,7 +181,7 @@ if (V8.Form.Phone) {
|
|
|
181
181
|
|
|
182
182
|
| 变量 | 说明 | 可用事件 |
|
|
183
183
|
|------|------|---------|
|
|
184
|
-
| `V8.Form` |
|
|
184
|
+
| `V8.Form` | 当前表单数据;原生新增提交前可能没有 Id,依赖真实主键的跨表写入在提交后事件执行 | 全部 |
|
|
185
185
|
| `V8.OldForm` | 已加载的修改前旧数据 | 普通表单;提交前/后 |
|
|
186
186
|
| `V8.FormMode` | `'Add'` / `'Edit'` / `'View'` | 全部 |
|
|
187
187
|
| `V8.FormOutAction` | `'Insert'`/`'Update'`/`'Close'`/`'Delete'` | FormOut |
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
| `FormSubmitBefore` | 提交前事件 |
|
|
12
12
|
| `FormOut` | 离开表单事件 |
|
|
13
13
|
| `FieldValueChange` | 字段值变更事件 |
|
|
14
|
-
| `FieldOnKeyup` | 文本框键盘事件 |
|
|
15
|
-
| `TableFieldOnKeyup` | 表格行内文本框键盘事件 |
|
|
16
|
-
| `FieldSlotButtonClick` | 单行文本插槽按钮点击事件 |
|
|
14
|
+
| `FieldOnKeyup` | 文本框键盘事件 |
|
|
15
|
+
| `TableFieldOnKeyup` | 表格行内文本框键盘事件 |
|
|
16
|
+
| `FieldSlotButtonClick` | 单行文本插槽按钮点击事件 |
|
|
17
17
|
| `V8BtnRun` | V8 按钮执行事件 |
|
|
18
18
|
| `V8BtnLimit` | V8 按钮是否显示事件 |
|
|
19
19
|
| `BtnFormDetailRun` | 详情按钮 V8 按钮 |
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
|
|
27
27
|
<!-- /microi-progressive:chunk -->
|
|
28
28
|
<!-- microi-progressive:chunk id=v8-table-event-012 sha256=db5bf410bc6babf14d3d91cc61b9875c81bc1be24b2c1b7c62334bc6819e4e55 -->
|
|
29
|
-
## 注意事项
|
|
29
|
+
## 注意事项
|
|
30
30
|
|
|
31
31
|
- 前端事件可使用 `window` 对象和 `async/await`,后端事件不可以
|
|
32
32
|
- 后端提交前/后事件返回 `{ Code: 0, Msg: '...' }` 可阻止数据写入并回滚事务
|
|
33
33
|
- 直接修改 `V8.Form` 的字段值即可改变最终写入的数据
|
|
34
|
-
- 后端事件中使用 `V8.FormEngine` 默认是 Server 调用,不触发目标表事件;`_InvokeType:'Client'` 恰好会触发目标表事件,不能用于“避免递归”
|
|
35
|
-
- `_InvokeType:'Server'` 只表达事件调用语义,不是客户端授权开关;浏览器伪造它不会获得受信任权限
|
|
34
|
+
- 后端事件中使用 `V8.FormEngine` 默认是 Server 调用,不触发目标表事件;`_InvokeType:'Client'` 恰好会触发目标表事件,不能用于“避免递归”
|
|
35
|
+
- `_InvokeType:'Server'` 只表达事件调用语义,不是客户端授权开关;浏览器伪造它不会获得受信任权限
|
|
36
36
|
- `V8.FormSubmitAction` 的值是 `'Insert'`/`'Update'`/`'Delete'`(非 Add/Upt/Del)
|
|
37
|
-
- 在 DataFilterV8 中使用 `V8.CacheData` 缓存查询结果,避免每行执行 N+1 查询
|
|
38
|
-
- 表事件调用下游接口时,表级正向 `diy_table.V8Limit` 不会改变下游接口;下游接口独立读取正向 `sys_apiengine.V8Limit`(两者均默认 0 表示不限 Jint 单次预算,1 表示启用限制)
|
|
39
|
-
|
|
40
|
-
### 复盘:提交后事件误把增量表单当作完整记录
|
|
41
|
-
|
|
42
|
-
- 触发场景:插件、MCP 或后端只更新代码等少数字段时,`SubmitAfterServerV8` 直接对 `V8.Form.Id`、业务 Key 调用 `toLowerCase()`,保存动作因字段未出现在增量参数中而异常。
|
|
43
|
-
- 根因:事件假设 `V8.Form` 始终包含整行数据,没有兼容稀疏更新(sparse patch)。
|
|
44
|
-
- 通用规则:提交后事件使用非本次必传字段前,必须从 `V8.OldForm` 合并兜底,或凭已有 `Id` 回查当前记录;调用字符串方法前先显式 `String(...)` 并校验空值。写缓存时应缓存合并后的完整模型,不能用稀疏对象覆盖整行缓存。
|
|
45
|
-
- 自动化检查:分别只更新一个普通字段、只更新大文本代码字段并执行一次完整表单保存;事件均不得报错,按 Id、唯一 Key 和可选地址回读缓存都应得到完整记录。
|
|
37
|
+
- 在 DataFilterV8 中使用 `V8.CacheData` 缓存查询结果,避免每行执行 N+1 查询
|
|
38
|
+
- 表事件调用下游接口时,表级正向 `diy_table.V8Limit` 不会改变下游接口;下游接口独立读取正向 `sys_apiengine.V8Limit`(两者均默认 0 表示不限 Jint 单次预算,1 表示启用限制)
|
|
39
|
+
|
|
40
|
+
### 复盘:提交后事件误把增量表单当作完整记录
|
|
41
|
+
|
|
42
|
+
- 触发场景:插件、MCP 或后端只更新代码等少数字段时,`SubmitAfterServerV8` 直接对 `V8.Form.Id`、业务 Key 调用 `toLowerCase()`,保存动作因字段未出现在增量参数中而异常。
|
|
43
|
+
- 根因:事件假设 `V8.Form` 始终包含整行数据,没有兼容稀疏更新(sparse patch)。
|
|
44
|
+
- 通用规则:提交后事件使用非本次必传字段前,必须从 `V8.OldForm` 合并兜底,或凭已有 `Id` 回查当前记录;调用字符串方法前先显式 `String(...)` 并校验空值。写缓存时应缓存合并后的完整模型,不能用稀疏对象覆盖整行缓存。
|
|
45
|
+
- 自动化检查:分别只更新一个普通字段、只更新大文本代码字段并执行一次完整表单保存;事件均不得报错,按 Id、唯一 Key 和可选地址回读缓存都应得到完整记录。
|
|
46
46
|
<!-- /microi-progressive:chunk -->
|
|
@@ -144,4 +144,4 @@ var hex = result.Data.Hex;
|
|
|
144
144
|
- 测试:真实 Jint 注入、Byte/Base64/Hex/Text、回环收发、超时/上限/非法参数。
|
|
145
145
|
- 编辑器:`Microi.Client/.../v8-api-server-definitions.js` 与 `Microi.VSCode/src/editor/typingsManager.ts`。
|
|
146
146
|
- 文档与知识:官网中文后端 V8 页面、`microi.skills/README.md`、`v8-utilities` 索引、`Microi.AI/Resource` 镜像和向量资源列表。
|
|
147
|
-
- 验收:目标测试、后端构建、精确重启与健康检查、独立 TCP 回环;真实打印机出纸必须单独报告,不能用回环测试替代。
|
|
147
|
+
- 验收:目标测试、后端构建、精确重启与健康检查、独立 TCP 回环;真实打印机出纸必须单独报告,不能用回环测试替代。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "V8 TCP 原始字节"
|
|
3
|
-
short_description: "通过后端 V8 安全发送和接收 TCP 原始字节数据"
|
|
4
|
-
default_prompt: "使用 $v8-tcp-integration 编写一个安全、可验证的 Microi 后端 V8 TCP 设备集成。"
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "V8 TCP 原始字节"
|
|
3
|
+
short_description: "通过后端 V8 安全发送和接收 TCP 原始字节数据"
|
|
4
|
+
default_prompt: "使用 $v8-tcp-integration 编写一个安全、可验证的 Microi 后端 V8 TCP 设备集成。"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: v8-template-engine
|
|
3
|
-
description: Microi V8 模板引擎指南。用于编写表格/表单模板渲染、V8.Result HTML/text 输出、行格式化、徽章、图片和自定义显示逻辑。
|
|
3
|
+
description: Microi V8 模板引擎指南。用于编写表格/表单模板渲染、V8.Result HTML/text 输出、行格式化、徽章、图片和自定义显示逻辑。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
@@ -12,27 +12,27 @@ description: Microi V8 模板引擎指南。用于编写表格/表单模板渲
|
|
|
12
12
|
## 核心规则
|
|
13
13
|
|
|
14
14
|
- 模板引擎绑定在【表单属性】的【表格 V8 模板引擎】或【表单 V8 模板引擎】
|
|
15
|
-
- 通过给 `V8.Result` 赋值字符串(HTML 或纯文本)来控制最终渲染结果
|
|
16
|
-
- 在 **表格模板** 中:每行数据都会执行一次,`V8.Form` 是当前行数据
|
|
17
|
-
- 注意:`V8.Form` 此时只能访问到【模块引擎】配置的【查询列】字段;若查询列为空则可访问全部字段
|
|
18
|
-
- 支持 `bootstrap`、`element-ui` 样式类名
|
|
19
|
-
- `V8.EventName` 可能为 `TableTemplateEngine` 或 `FormTemplateEngine`
|
|
20
|
-
- 表格模板同步执行,输出优先级是 `V8.Result` → JavaScript `return` → 原字段值;表单模板异步执行并以 `V8.Result` 为输出
|
|
21
|
-
- 模板结果通过 `v-safe-html`/DOMPurify 净化。`script`、`iframe`、`on*` 事件属性、`javascript:` 等危险内容会被剥离,禁止依赖内联点击事件
|
|
22
|
-
- 业务文本必须 HTML 转义后再拼接;权限、校验、状态写入等业务逻辑必须放后端
|
|
23
|
-
|
|
24
|
-
模板中需要拼 HTML 时,先定义安全转义函数:
|
|
25
|
-
|
|
26
|
-
```javascript
|
|
27
|
-
var escapeHtml = function (value) {
|
|
28
|
-
return String(value == null ? '' : value)
|
|
29
|
-
.replace(/&/g, '&')
|
|
30
|
-
.replace(/</g, '<')
|
|
31
|
-
.replace(/>/g, '>')
|
|
32
|
-
.replace(/"/g, '"')
|
|
33
|
-
.replace(/'/g, ''');
|
|
34
|
-
};
|
|
35
|
-
```
|
|
15
|
+
- 通过给 `V8.Result` 赋值字符串(HTML 或纯文本)来控制最终渲染结果
|
|
16
|
+
- 在 **表格模板** 中:每行数据都会执行一次,`V8.Form` 是当前行数据
|
|
17
|
+
- 注意:`V8.Form` 此时只能访问到【模块引擎】配置的【查询列】字段;若查询列为空则可访问全部字段
|
|
18
|
+
- 支持 `bootstrap`、`element-ui` 样式类名
|
|
19
|
+
- `V8.EventName` 可能为 `TableTemplateEngine` 或 `FormTemplateEngine`
|
|
20
|
+
- 表格模板同步执行,输出优先级是 `V8.Result` → JavaScript `return` → 原字段值;表单模板异步执行并以 `V8.Result` 为输出
|
|
21
|
+
- 模板结果通过 `v-safe-html`/DOMPurify 净化。`script`、`iframe`、`on*` 事件属性、`javascript:` 等危险内容会被剥离,禁止依赖内联点击事件
|
|
22
|
+
- 业务文本必须 HTML 转义后再拼接;权限、校验、状态写入等业务逻辑必须放后端
|
|
23
|
+
|
|
24
|
+
模板中需要拼 HTML 时,先定义安全转义函数:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
var escapeHtml = function (value) {
|
|
28
|
+
return String(value == null ? '' : value)
|
|
29
|
+
.replace(/&/g, '&')
|
|
30
|
+
.replace(/</g, '<')
|
|
31
|
+
.replace(/>/g, '>')
|
|
32
|
+
.replace(/"/g, '"')
|
|
33
|
+
.replace(/'/g, ''');
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
36
|
|
|
37
37
|
## 状态徽章(Bootstrap 样式)
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ if (V8.IsNull(value)) {
|
|
|
46
46
|
else if (value === '未通过') classStr = 'badge-warning';
|
|
47
47
|
else if (value === '待审核') classStr = 'badge-info';
|
|
48
48
|
else if (value === '通过') classStr = 'badge-success';
|
|
49
|
-
V8.Result = '<span class="badge badge-pill ' + classStr + '">' + escapeHtml(value) + '</span>';
|
|
49
|
+
V8.Result = '<span class="badge badge-pill ' + classStr + '">' + escapeHtml(value) + '</span>';
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
@@ -57,12 +57,12 @@ if (V8.IsNull(value)) {
|
|
|
57
57
|
```javascript
|
|
58
58
|
// 不同业务状态显示不同字体颜色
|
|
59
59
|
if (V8.Form.XuqiuLX === '合并') {
|
|
60
|
-
V8.Result = '<span style="color:blue;">' + escapeHtml(V8.Form.XuqiuDDH) + '</span>';
|
|
61
|
-
} else if (V8.Form.HebingID) {
|
|
62
|
-
V8.Result = '<span style="color:#999;">' + escapeHtml(V8.Form.XuqiuDDH) + '</span>';
|
|
63
|
-
} else {
|
|
64
|
-
V8.Result = escapeHtml(V8.Form.XuqiuDDH);
|
|
65
|
-
}
|
|
60
|
+
V8.Result = '<span style="color:blue;">' + escapeHtml(V8.Form.XuqiuDDH) + '</span>';
|
|
61
|
+
} else if (V8.Form.HebingID) {
|
|
62
|
+
V8.Result = '<span style="color:#999;">' + escapeHtml(V8.Form.XuqiuDDH) + '</span>';
|
|
63
|
+
} else {
|
|
64
|
+
V8.Result = escapeHtml(V8.Form.XuqiuDDH);
|
|
65
|
+
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## 单图列渲染
|
|
@@ -70,11 +70,11 @@ if (V8.Form.XuqiuLX === '合并') {
|
|
|
70
70
|
```javascript
|
|
71
71
|
var html = '';
|
|
72
72
|
var fileServer = V8.SysConfig.FileServer;
|
|
73
|
-
if (!V8.IsNull(V8.Form.GongsiLOGO)) {
|
|
74
|
-
html = '<img src="' + escapeHtml(fileServer + V8.Form.GongsiLOGO) + '" '
|
|
75
|
-
+ 'alt="公司Logo" '
|
|
76
|
-
+ 'style="height:40px;width:40px;object-fit:cover;margin:5px 0;" />';
|
|
77
|
-
}
|
|
73
|
+
if (!V8.IsNull(V8.Form.GongsiLOGO)) {
|
|
74
|
+
html = '<img src="' + escapeHtml(fileServer + V8.Form.GongsiLOGO) + '" '
|
|
75
|
+
+ 'alt="公司Logo" '
|
|
76
|
+
+ 'style="height:40px;width:40px;object-fit:cover;margin:5px 0;" />';
|
|
77
|
+
}
|
|
78
78
|
V8.Result = html;
|
|
79
79
|
```
|
|
80
80
|
|
|
@@ -84,15 +84,15 @@ V8.Result = html;
|
|
|
84
84
|
var html = '';
|
|
85
85
|
var fileServer = V8.SysConfig.FileServer;
|
|
86
86
|
if (!V8.IsNull(V8.Form.TupianMS) && V8.Form.TupianMS.indexOf('[') !== -1) {
|
|
87
|
-
var imgs = JSON.parse(V8.Form.TupianMS);
|
|
88
|
-
imgs.forEach(function(item) {
|
|
89
|
-
var url = escapeHtml(fileServer + item.Path);
|
|
90
|
-
html += '<a href="' + url + '" target="_blank" rel="noopener noreferrer">'
|
|
91
|
-
+ '<img src="' + url + '" alt="图片" '
|
|
92
|
-
+ 'style="width:40px;height:40px;object-fit:cover;margin:5px 5px 5px 0;" />'
|
|
93
|
-
+ '</a>';
|
|
94
|
-
});
|
|
95
|
-
}
|
|
87
|
+
var imgs = JSON.parse(V8.Form.TupianMS);
|
|
88
|
+
imgs.forEach(function(item) {
|
|
89
|
+
var url = escapeHtml(fileServer + item.Path);
|
|
90
|
+
html += '<a href="' + url + '" target="_blank" rel="noopener noreferrer">'
|
|
91
|
+
+ '<img src="' + url + '" alt="图片" '
|
|
92
|
+
+ 'style="width:40px;height:40px;object-fit:cover;margin:5px 5px 5px 0;" />'
|
|
93
|
+
+ '</a>';
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
96
|
V8.Result = html;
|
|
97
97
|
```
|
|
98
98
|
|
|
@@ -111,27 +111,27 @@ V8.Result =
|
|
|
111
111
|
+ '</div>';
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
## 多字段合并显示
|
|
114
|
+
## 多字段合并显示
|
|
115
115
|
|
|
116
116
|
```javascript
|
|
117
117
|
// 联系人姓名 + 电话脱敏
|
|
118
118
|
var name = V8.Form.LianxiR || '';
|
|
119
119
|
var phone = V8.Form.LianxiPhone || '';
|
|
120
120
|
if (phone.length === 11) phone = phone.substring(0, 3) + '****' + phone.substring(7);
|
|
121
|
-
V8.Result = '<div><b>' + escapeHtml(name) + '</b><br/><small style="color:#999;">'
|
|
122
|
-
+ escapeHtml(phone) + '</small></div>';
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
模块引擎已有更轻量的声明式方案:`ViewSchema.Layout.List.Columns` 可用 `Field + Lines +
|
|
126
|
-
TrailingFields` 组成多行列,`Layout.Card` 可配置移动端顶部、右侧、正文、元信息和底部字段。
|
|
127
|
-
仅需要多字段排版、图标、`Tone/Color/Prefix/Suffix` 时优先使用声明式配置;需要复杂条件
|
|
128
|
-
HTML 时再给被引用的 `diy_field` 配置 `V8TmpEngineTable`。两种方式都会复用净化后的模板
|
|
129
|
-
结果,且所有引用字段仍必须在模块查询列或 `_SelectFields` 中。
|
|
121
|
+
V8.Result = '<div><b>' + escapeHtml(name) + '</b><br/><small style="color:#999;">'
|
|
122
|
+
+ escapeHtml(phone) + '</small></div>';
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
模块引擎已有更轻量的声明式方案:`ViewSchema.Layout.List.Columns` 可用 `Field + Lines +
|
|
126
|
+
TrailingFields` 组成多行列,`Layout.Card` 可配置移动端顶部、右侧、正文、元信息和底部字段。
|
|
127
|
+
仅需要多字段排版、图标、`Tone/Color/Prefix/Suffix` 时优先使用声明式配置;需要复杂条件
|
|
128
|
+
HTML 时再给被引用的 `diy_field` 配置 `V8TmpEngineTable`。两种方式都会复用净化后的模板
|
|
129
|
+
结果,且所有引用字段仍必须在模块查询列或 `_SelectFields` 中。
|
|
130
130
|
|
|
131
131
|
## 条件性图标
|
|
132
132
|
|
|
133
133
|
```javascript
|
|
134
|
-
var html = escapeHtml(V8.Form.Title || '');
|
|
134
|
+
var html = escapeHtml(V8.Form.Title || '');
|
|
135
135
|
if (V8.Form.IsHot === 1) html += ' <i class="fas fa-fire" style="color:#F56C6C;"></i>';
|
|
136
136
|
if (V8.Form.IsNew === 1) html += ' <span class="badge badge-danger">NEW</span>';
|
|
137
137
|
V8.Result = html;
|
|
@@ -150,11 +150,11 @@ V8.Result = '<span style="color:' + color + ';font-weight:bold;">¥' + total + '
|
|
|
150
150
|
|
|
151
151
|
## 常见错误
|
|
152
152
|
|
|
153
|
-
❌ 不要在模板中调用 `V8.FormEngine`(每行都查 → N+1 性能问题);需要关联数据时在查询列、接口引擎或后端 `DataFilterV8` 预取,只有 `DataFilterV8` 才提供 `V8.CacheData`
|
|
154
|
-
❌ 不要依赖对象/数组的隐式字符串化;`V8.Result` 建议显式输出字符串
|
|
155
|
-
❌ 模板中 `V8.Form` 默认只有【查询列】字段;缺字段就要去模块引擎补查询列
|
|
156
|
-
❌ 不要在模板里写复杂业务逻辑(应放到接口引擎或 DataFilterV8)
|
|
157
|
-
❌ 不要输出 `onclick/onerror`、`script/iframe` 或 `javascript:` URL;DOMPurify 会剥离这些内容
|
|
153
|
+
❌ 不要在模板中调用 `V8.FormEngine`(每行都查 → N+1 性能问题);需要关联数据时在查询列、接口引擎或后端 `DataFilterV8` 预取,只有 `DataFilterV8` 才提供 `V8.CacheData`
|
|
154
|
+
❌ 不要依赖对象/数组的隐式字符串化;`V8.Result` 建议显式输出字符串
|
|
155
|
+
❌ 模板中 `V8.Form` 默认只有【查询列】字段;缺字段就要去模块引擎补查询列
|
|
156
|
+
❌ 不要在模板里写复杂业务逻辑(应放到接口引擎或 DataFilterV8)
|
|
157
|
+
❌ 不要输出 `onclick/onerror`、`script/iframe` 或 `javascript:` URL;DOMPurify 会剥离这些内容
|
|
158
158
|
|
|
159
159
|
## 与 DataFilterV8 的区别
|
|
160
160
|
|