@microi.net/cli 6.1.4 → 6.1.6
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/.mcp.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +4 -4
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/codex-marketplace.json +20 -20
- package/scripts/mcp-codex-stdio-adapter.js +189 -189
- package/scripts/mcp-server.js +79 -79
- package/scripts/mcp-trae-windows-launcher.cmd +21 -21
- package/scripts/microi-cli-mcp.js +7 -7
- package/scripts/microi-skills.meta.json +290 -290
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +13 -13
- package/skills/ai-engine/references/ai-employees.md +48 -48
- package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
- package/skills/business-blueprint/SKILL.md +193 -193
- package/skills/datasource-engine/SKILL.md +93 -93
- package/skills/dos-orm/SKILL.md +105 -105
- package/skills/dos-orm/references/api-reference.md +229 -229
- package/skills/email-engine/SKILL.md +81 -81
- package/skills/email-engine/references/v8-email.md +34 -34
- package/skills/job-engine/SKILL.md +179 -179
- package/skills/message-notification/SKILL.md +160 -156
- package/skills/message-notification/references/contracts.md +119 -102
- package/skills/microi/SKILL.md +14 -14
- package/skills/microi-ai-application/SKILL.md +115 -115
- package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
- 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 +10 -10
- 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 +10 -10
- 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 +10 -10
- package/skills/microi-codex/SKILL.md +133 -130
- package/skills/microi-datasource-mapping/SKILL.md +131 -131
- package/skills/microi-db-schema/SKILL.md +182 -182
- package/skills/microi-db-schema/references/core-tables.md +695 -695
- package/skills/microi-db-schema/references/form-component-options.md +256 -256
- package/skills/microi-db-schema/references/schema-overview.md +202 -202
- package/skills/microi-db-schema/references/schema.md +646 -646
- package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
- package/skills/microi-deployment/SKILL.md +262 -262
- package/skills/microi-deployment/references/deployment-matrix.md +112 -112
- package/skills/microi-docs-coverage/SKILL.md +133 -133
- package/skills/microi-docs-coverage/references/capability-map.md +91 -91
- package/skills/microi-form-engine/SKILL.md +338 -338
- 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-left-right-layout/SKILL.md +141 -141
- package/skills/microi-microservice/references/runtime-delivery.md +282 -282
- package/skills/microi-solution-quotation/SKILL.md +79 -79
- package/skills/microi-sso/SKILL.md +102 -102
- 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 +43 -43
- package/skills/microi-system-delivery/references/context-quality.md +3 -3
- package/skills/microi-system-delivery/references/progressive-01-/346/240/207/345/207/206/345/267/245/344/275/234/346/265/201.md +11 -11
- 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 +12 -12
- package/skills/microi-system-delivery/references/progressive-03-/345/256/211/345/205/250/344/270/216/350/267/250/347/253/257/351/252/214/346/224/266.md +8 -8
- package/skills/module-engine/SKILL.md +297 -297
- package/skills/module-engine/references/module-config.md +204 -204
- package/skills/ocr-engine/SKILL.md +113 -113
- 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 +10 -10
- 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 +12 -12
- package/skills/performance-testing/SKILL.md +227 -227
- package/skills/print-engine/SKILL.md +259 -259
- package/skills/production-readonly-audit/SKILL.md +41 -41
- package/skills/report-engine/SKILL.md +83 -83
- package/skills/search-engine/SKILL.md +75 -75
- package/skills/spider-engine/SKILL.md +190 -190
- package/skills/system-observability/SKILL.md +264 -264
- package/skills/system-observability/references/memory-incident-triage.md +77 -77
- package/skills/translate-engine/SKILL.md +140 -140
- package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
- package/skills/ui-design/references/design-pattern-library.md +184 -184
- package/skills/ui-design/references/mci-design-contract.md +163 -163
- package/skills/ui-design/references/motion-and-media.md +78 -78
- package/skills/ui-design/references/product-flow-recipes.md +94 -94
- package/skills/uniapp-mall-assets/SKILL.md +176 -176
- package/skills/unity-integration/SKILL.md +178 -178
- 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-cache-pattern/SKILL.md +320 -320
- package/skills/v8-debugging/SKILL.md +284 -284
- package/skills/v8-explorer-tree/SKILL.md +228 -228
- package/skills/v8-file-upload/SKILL.md +20 -1
- package/skills/v8-file-upload/references/cdn-alias-repair.md +13 -13
- package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
- package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
- package/skills/v8-image-processing/SKILL.md +190 -190
- package/skills/v8-image-processing/references/api-reference.md +623 -623
- package/skills/v8-mongodb/SKILL.md +257 -241
- package/skills/v8-mq-mqtt/references/mqtt-production.md +352 -352
- package/skills/v8-security/SKILL.md +8 -1
- package/skills/v8-sql-query/SKILL.md +302 -302
- package/skills/v8-template-engine/SKILL.md +167 -167
- package/skills/v8-utilities/references/platform-http-routes.md +83 -83
- package/skills/v8-workflow/references/workflow-configuration.md +49 -49
- package/skills/vision-engine/SKILL.md +160 -160
- package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
- 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 +34 -34
- 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 +6 -6
|
@@ -53,13 +53,20 @@ Preview 和确认均重验当前客户端、回调及 Scope;账号变化通过
|
|
|
53
53
|
同一 Managed 会话接口包含本人初始化与敏感管理动作时,精确 `ApiRole` 不能代替动作级鉴权。SaaS v8.4.26 / `platform-sys-user-session` v1.0.5 对已归一化的 `GetSysUserPassword`,先拒绝匿名和访问密钥,再以可信 `V8.CurrentUser`、`V8.Db` 主库的本人活动状态/等级及有效管理员角色核验后调用原可信原子;不读取密码字段做授权,不使用 `DbRead`、客户端身份字段或角色名判断。标准角色授权可用于只读账号的本人会话初始化,不能把整引擎授权解释成敏感动作许可。负例使用预先确认不存在的固定用户 Id:普通账号必须先被管理员门拒绝,管理员仅验证合法路径返回不存在,禁止为验收读取任何真实密码。
|
|
54
54
|
|
|
55
55
|
<!-- microi-progressive:begin -->
|
|
56
|
-
<!-- microi-progressive:chunk id=v8-security-000 sha256=
|
|
56
|
+
<!-- microi-progressive:chunk id=v8-security-000 sha256=0dfa03a03298161e6aa29dff18f68fdb00e4f0865cb7562fec090dcadd745c43 -->
|
|
57
57
|
## 0. 租户动态系统设置与密钥边界
|
|
58
58
|
|
|
59
59
|
**AI 默认处理路径:** 用户交付第三方 Secret 并授权配置时,优先调用 `microi_manage_server_private_secret`:List 只确认 Key/HasSecret;Save 使用 `confirmExecution=SAVE:<ConfigKey>`,固定 `IsSecret=true / IsPublic=false` 并自动脱敏回读。例如畅捷通使用 `Integration.Changjet.AppSecret`,后端从 `V8.SysConfig.ServerPrivateSettings` 使用。不要仅拒绝硬编码后停止,也不要因为 `OsClientModel` 不暴露密钥而误判服务端 V8 无法调用第三方。工具不提供原文揭示;更换已有 Key 前回读现状,超时只回读、不盲目重写。
|
|
60
60
|
|
|
61
61
|
第三方密钥(微信、支付宝、OpenAI、阿里云、ERP、SMTP)**禁止**硬编码在 V8 代码或前端。公开的租户配置必须建成当前租户 `sys_config` 的实体字段;敏感或仅供后端使用的租户业务配置保存到 `mci_system_setting`。数据库、Redis、MongoDB、MinIO、MQ 等部署控制面仍由主库 `sys_osclients` 托管,子租户不能修改。
|
|
62
62
|
|
|
63
|
+
MinIO 部署凭据使用专用 `microi_manage_saas_minio`,不能误存到普通第三方业务 Secret,
|
|
64
|
+
也不能为此放宽 `microi_admin_table_data` 的秘密字段限制。先 Inspect 当前主租户及运行分区,
|
|
65
|
+
再以其 Revision 和 `SAVE_MINIO:<tenant>:<revision>` 确认保存;省略凭据保留,候选先探测、
|
|
66
|
+
事务行锁 CAS、提交后缓存刷新及运行回读。只返回 HasAccessKey/HasSecretKey,拒绝子租户和
|
|
67
|
+
访问密钥会话,不修改 HDFS/OSS 或桶权限。超时和 Persisted=true 的失败先 Inspect/Probe,
|
|
68
|
+
不能盲重放;旧后端不支持时升级正式后端,禁止用 SQL、普通表单或临时 V8 绕过。
|
|
69
|
+
|
|
63
70
|
能力是否启用、入口是否显示、公开交互模式等 Bool/Enum 配置即使属于登录或第三方集成,也必须放在 `sys_config`;API Key、ClientSecret、RP ID、Origin、Issuer、Scope、供应商地址等后端参数才放在 `mci_system_setting`。禁止给同一个新配置双写两张表。迁移旧开关时采用“新 `sys_config` 显式值 → 旧私密 Key → 存量安全默认”的只读回退,并从私密设置的列表、保存和删除入口移除旧 Key。
|
|
64
71
|
|
|
65
72
|
```javascript
|
|
@@ -1,302 +1,302 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: v8-sql-query
|
|
3
|
-
description: Microi V8 安全 SQL 查询指南。用于选择 V8.FormEngine _Where 或 V8.Db.FromSql,处理参数化查询、联表、聚合并避免 SQL 注入。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
> **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
|
|
7
|
-
|
|
8
|
-
# Microi V8 安全 SQL 查询
|
|
9
|
-
|
|
10
|
-
你正在开发 Microi 吾码平台的 V8 引擎代码。数据库查询有两种方式:`V8.FormEngine`(推荐)和 `V8.Db`(原始 SQL)。必须遵守安全规范。
|
|
11
|
-
|
|
12
|
-
## 性能门禁(必须执行)
|
|
13
|
-
|
|
14
|
-
写接口引擎前必须先做数据访问计划,避免“循环套循环查数据库”:
|
|
15
|
-
|
|
16
|
-
- 禁止在 `for` / `while` / `forEach` / `map` 循环内调用 `V8.FormEngine.GetFormData`、`GetTableData`、`V8.Db.FromSql`、`V8.ApiEngine.Run` 或 `V8.Http.*`。确实无法避免时,必须先说明原因,并加分页、缓存或限流。
|
|
17
|
-
- 多条 Id、编码、外键查询必须一次性用 `_Where: [['Id','In', ids]]`、SQL `IN`、JOIN 或聚合查询取回,再用内存字典映射。
|
|
18
|
-
- 需要父子、主从、用户/部门/角色名称映射时,先批量取关联表,只保留 `_SelectFields` 必要列,不要逐行查名称。
|
|
19
|
-
- 统计、计数、汇总优先让数据库一次 `GROUP BY` / `COUNT` / `SUM` 完成,不要把大表全部拉到 V8 里循环统计。
|
|
20
|
-
- 列表接口必须限制 `_PageSize`,管理端默认不要超过 100,导出或批处理必须显式分批。
|
|
21
|
-
- 每次查询都要写 `_SelectFields` 或明确 SQL 字段列表,禁止 `SELECT *` 用在大表、接口列表、循环前置查询中。
|
|
22
|
-
- 外部 HTTP、翻译、短信、AI 等慢调用不能放在数据库事务和大循环中;要么异步队列,要么批量预处理并设置超时。
|
|
23
|
-
- 返回前自检一次:数据库访问次数应与数据量无关或近似常数级,不能随着行数线性增长为 N 次查询。
|
|
24
|
-
|
|
25
|
-
## 首选:V8.FormEngine + _Where(自动防注入)
|
|
26
|
-
|
|
27
|
-
`_Where` 是参数化查询语法,自动防 SQL 注入,**永远优先使用**。
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
// ✅ 安全:_Where 自动参数化
|
|
31
|
-
var result = V8.FormEngine.GetTableData('SysUser', {
|
|
32
|
-
_Where: [
|
|
33
|
-
['Account', '=', V8.Param.account],
|
|
34
|
-
['AND', 'Status', '=', 1]
|
|
35
|
-
],
|
|
36
|
-
_PageIndex: 1,
|
|
37
|
-
_PageSize: 20
|
|
38
|
-
});
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### _Where 完整语法
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
// 基本条件
|
|
45
|
-
[['Field', '操作符', value]]
|
|
46
|
-
|
|
47
|
-
// 操作符:=, ==, <>, !=, >, >=, <, <=, Like, NotLike, StartLike, EndLike, In, NotIn
|
|
48
|
-
|
|
49
|
-
// 多条件 AND
|
|
50
|
-
[['A', '=', 1], ['AND', 'B', '>', 10]]
|
|
51
|
-
|
|
52
|
-
// 多条件 OR
|
|
53
|
-
[['A', '=', 1], ['OR', 'B', '=', 2]]
|
|
54
|
-
|
|
55
|
-
// IN 查询
|
|
56
|
-
[['Id', 'In', ['id1', 'id2', 'id3']]]
|
|
57
|
-
|
|
58
|
-
// NULL 判断
|
|
59
|
-
[['Field', '=', null]] // IS NULL
|
|
60
|
-
[['Field', '<>', null]] // IS NOT NULL
|
|
61
|
-
|
|
62
|
-
// 分组(括号):(Age > 18 OR Status = 1)
|
|
63
|
-
[['Name', 'Like', '张'], ['AND', '(', 'Age', '>', 18], ['OR', 'Status', '=', 1, ')']]
|
|
64
|
-
|
|
65
|
-
// 日期范围
|
|
66
|
-
[['CreateTime', '>=', '2024-01-01'], ['AND', 'CreateTime', '<', '2024-02-01']]
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 旧版 _Where 兼容(V8.Method.ParseWhere)
|
|
70
|
-
|
|
71
|
-
老版本前端可能传对象格式(`{Name, Value, Type, AndOr, GroupStart, GroupEnd}`)。
|
|
72
|
-
`V8.Method.ParseWhere` 返回统一的 `DiyWhere` 对象集合;它不是“对象转数组”函数。
|
|
73
|
-
|
|
74
|
-
```javascript
|
|
75
|
-
var objectWhere = V8.Method.ParseWhere(V8.Param._Where);
|
|
76
|
-
V8.FormEngine.GetTableData('Table', { _Where: objectWhere });
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
合并客户端筛选与接口固定条件时,必须先统一格式,禁止直接把数组条件追加到对象条件集合。
|
|
80
|
-
部分部署按首项类型解析整个集合,混合格式会触发转换失败并退化为空条件,导致统计返回未筛选总数。
|
|
81
|
-
需要兼容混合输入时,逐条调用 `ParseWhere(JSON.stringify([condition]))`,确认每条恰好解析出一个有效条件后,
|
|
82
|
-
再合并为同一种格式;非空输入解析为空、非法操作符或括号不平衡必须返回失败,不能继续执行统计。
|
|
83
|
-
存在顶层 OR 时,还须保证语义为“整个客户端筛选 AND 固定条件”,不能仅在原表达式末尾追加 AND。
|
|
84
|
-
回归至少比较纯数组、纯对象、混合条件、OR/分组、零结果和非法输入。
|
|
85
|
-
|
|
86
|
-
## 次选:V8.Db.FromSql(仅 SQL 字符串 + AddInParameter)
|
|
87
|
-
|
|
88
|
-
> **⚠️ FormEngine 优先原则:** 增删改操作(INSERT / UPDATE / DELETE)必须**优先**使用 `V8.FormEngine.AddFormData` / `UptFormData` / `UptFormDataByWhere` / `DelFormData` 等方法。**只有**多表 JOIN、复杂子查询、GROUP BY 聚合等 FormEngine 无法表达的场景才使用 `V8.Db.FromSql`。
|
|
89
|
-
|
|
90
|
-
> **⚠️ FromSql 调用规则:** `V8.Db.FromSql` 在 V8 中只传 SQL 字符串,不要把动态值作为第二个或后续参数传给 `FromSql`。动态值必须用链式 `.AddInParameter('@p0', value)` 绑定;否则会生成平台不支持的调用签名。
|
|
91
|
-
|
|
92
|
-
> **共享事务:** `V8.Db` 是主库会话,`V8.Db.FromSql` 不会自动加入接口引擎事务。需要与表单、其它 SQL 或流程共同提交/回滚的操作,必须使用 `V8.DbTrans.FromSql`;依赖本事务尚未提交结果的查询也一样。`V8.FormEngine`、`V8.ApiEngine.Run` 继续显式传第三参数 `V8.DbTrans`。禁止从安全代理取内部事务并自行提交。
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
// ❌ 错误形态:不要把动态值作为 FromSql 的第二个参数传入
|
|
96
|
-
|
|
97
|
-
// ✅ 优先:改用 FormEngine(单表增删改查都优先这样写)
|
|
98
|
-
V8.FormEngine.UptFormData('t', { Id: id, A: val1, B: val2 });
|
|
99
|
-
|
|
100
|
-
// ✅ 必须用原生 SQL 时:FromSql 只传 SQL,参数用 AddInParameter
|
|
101
|
-
V8.DbTrans.FromSql("UPDATE t SET A=@p0, B=@p1 WHERE Id=@p2")
|
|
102
|
-
.AddInParameter("@p0", val1)
|
|
103
|
-
.AddInParameter("@p1", val2)
|
|
104
|
-
.AddInParameter("@p2", id)
|
|
105
|
-
.ExecuteNonQuery();
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
当 `_Where` 无法满足复杂查询(多表 JOIN、子查询、聚合统计)时使用 `V8.Db`:
|
|
109
|
-
|
|
110
|
-
```javascript
|
|
111
|
-
// ✅ 安全:使用 @p0, @p1 占位符,并用 AddInParameter 绑定
|
|
112
|
-
var list = V8.Db.FromSql(
|
|
113
|
-
'SELECT a.Id, a.Name, b.OrderCount FROM Customer a LEFT JOIN (SELECT CustomerId, COUNT(*) OrderCount FROM OrderHeader GROUP BY CustomerId) b ON a.Id = b.CustomerId WHERE a.Status = @p0'
|
|
114
|
-
).AddInParameter("@p0", 1).ToArray();
|
|
115
|
-
|
|
116
|
-
// ✅ 安全:多个参数
|
|
117
|
-
var row = V8.Db.FromSql(
|
|
118
|
-
'SELECT * FROM SysUser WHERE Account = @p0 AND DeptId = @p1'
|
|
119
|
-
).AddInParameter("@p0", V8.Param.account)
|
|
120
|
-
.AddInParameter("@p1", V8.Param.deptId)
|
|
121
|
-
.First();
|
|
122
|
-
|
|
123
|
-
// 统计
|
|
124
|
-
var count = V8.Db.FromSql(
|
|
125
|
-
'SELECT COUNT(*) FROM OrderHeader WHERE Status = @p0 AND CreateTime >= @p1'
|
|
126
|
-
).AddInParameter("@p0", 1)
|
|
127
|
-
.AddInParameter("@p1", V8.Param.startDate)
|
|
128
|
-
.ToScalar();
|
|
129
|
-
|
|
130
|
-
// 非查询(UPDATE / INSERT / DELETE)
|
|
131
|
-
V8.DbTrans.FromSql(
|
|
132
|
-
'UPDATE SysUser SET LastLoginTime = @p0 WHERE Id = @p1'
|
|
133
|
-
).AddInParameter("@p0", DateNow('yyyy-MM-dd HH:mm:ss'))
|
|
134
|
-
.AddInParameter("@p1", V8.CurrentUser.Id)
|
|
135
|
-
.ExecuteNonQuery();
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
> 老版本兼容:要把维护接口复制到旧部署时,不要假定 `DateNow` 或 `System.DateTime.Now.ToString(...)` 一定可用。若数据库类型固定,可直接使用该数据库的当前时间表达式(例如 MySQL 的 `NOW()`);跨数据库代码则应按数据库类型选择表达式或传入平台已确认支持的时间值。
|
|
139
|
-
|
|
140
|
-
### V8.Db 方法速查
|
|
141
|
-
|
|
142
|
-
| 方法 | 返回 | 用途 |
|
|
143
|
-
|------|------|------|
|
|
144
|
-
| `.ToArray()` | 数组 | 查询多条 |
|
|
145
|
-
| `.First()` | 对象 \| null | 查询单条 |
|
|
146
|
-
| `.ToScalar()` | 单值 | COUNT / MAX / SUM 等 |
|
|
147
|
-
| `.ExecuteNonQuery()` | 影响行数 | UPDATE / DELETE / INSERT |
|
|
148
|
-
|
|
149
|
-
> 别名:`.ToList()` = `.ToArray()`,`.ToModel()` = `.First()`,`.ExecuteScalar()` = `.ToScalar()`
|
|
150
|
-
|
|
151
|
-
### 读写分离
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
V8.Db.FromSql(...) // 主库(读写)
|
|
155
|
-
V8.DbRead.FromSql(...) // 从库(只读,适合报表和大量查询)
|
|
156
|
-
// 未部署读写分离时 V8.DbRead 与 V8.Db 一致
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 跨应用查询(扩展数据库)
|
|
160
|
-
|
|
161
|
-
```javascript
|
|
162
|
-
var list = V8.Dbs.OracleDB1.FromSql('SELECT * FROM Table WHERE Id = @p0')
|
|
163
|
-
.AddInParameter("@p0", id)
|
|
164
|
-
.ToArray();
|
|
165
|
-
|
|
166
|
-
// 不写入 microi_database:创建仅当前请求使用的临时会话
|
|
167
|
-
var tempDb = V8.Dbs.Open(
|
|
168
|
-
'SqlServer',
|
|
169
|
-
'Server=127.0.0.1,1433;Database=app;User Id=user;Password=***;TrustServerCertificate=True;'
|
|
170
|
-
);
|
|
171
|
-
var tempList = tempDb.FromSql('SELECT Id, Name FROM Customer WHERE Status = @p0')
|
|
172
|
-
.AddInParameter('@p0', 1)
|
|
173
|
-
.ToArray();
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`V8.Dbs.Open` 与保存连接均只支持 Dos.ORM 已认证的 `MySql`、`SqlServer`、`Oracle`、`PostgreSql`、`DaMeng`、`KingBase`。动态连接串只能来自可信服务端密钥或管理员代码,禁止使用 `V8.Param.ConnectionString`,禁止记录或返回。外部 SQL 的表名、列名、排序字段必须来自已校验元数据白名单;动态值继续使用 `AddInParameter`。
|
|
177
|
-
|
|
178
|
-
MCP 结构发现使用 `microi_inspect_external_database`,安全抽样默认使用只读的 `microi_query_external_database`。当用户明确要求数据库管理级能力时,使用独立的 `microi_execute_external_database`:它只允许后端确认的 `Level >= 9999` 当前用户调用,显式确认后可执行目标数据库账号有权执行的任意 DML、DDL、存储过程、数据库原生命令或多语句。输出行数限制只保护 MCP 传输,不限制数据库副作用;审计只记录 SQL 哈希、长度、模式和结果,不得记录 SQL 正文、连接串或密码。
|
|
179
|
-
|
|
180
|
-
不要用 `microi_get_db_schema` 读取第三方库,也不要在对话中搬运全库数据;持续同步应创建参数化、分页、幂等的服务端任务。最高权限入口不等于跨租户,也不能超越目标数据库账号自身权限。
|
|
181
|
-
|
|
182
|
-
## 数据库事务
|
|
183
|
-
|
|
184
|
-
### 接口引擎事务(自动管理)
|
|
185
|
-
|
|
186
|
-
```javascript
|
|
187
|
-
// 接口引擎创建 V8.DbTrans;V8.Db 是独立主库会话,不自动加入它:
|
|
188
|
-
// 返回 Code=1 → 自动提交事务
|
|
189
|
-
// 返回 Code≠1 → 自动回滚事务
|
|
190
|
-
// 手动调用 V8.DbTrans.Commit() 或 V8.DbTrans.Rollback() 均无效
|
|
191
|
-
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance - @p0 WHERE Id = @p1')
|
|
192
|
-
.AddInParameter("@p0", 100)
|
|
193
|
-
.AddInParameter("@p1", fromId)
|
|
194
|
-
.ExecuteNonQuery();
|
|
195
|
-
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance + @p0 WHERE Id = @p1')
|
|
196
|
-
.AddInParameter("@p0", 100)
|
|
197
|
-
.AddInParameter("@p1", toId)
|
|
198
|
-
.ExecuteNonQuery();
|
|
199
|
-
|
|
200
|
-
// V8.DbTrans 可传给 FormEngine 和 ApiEngine.Run 共享事务
|
|
201
|
-
V8.FormEngine.UptFormData('Table1', { Id: 'x', Status: 1 }, V8.DbTrans);
|
|
202
|
-
V8.ApiEngine.Run('other-engine', { Id: 'x' }, V8.DbTrans);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 扩展数据库事务(手动管理)
|
|
206
|
-
|
|
207
|
-
```javascript
|
|
208
|
-
// 扩展数据库需要手动管理事务
|
|
209
|
-
var exTrans = V8.Dbs.OracleDB1.BeginTransaction();
|
|
210
|
-
try {
|
|
211
|
-
exTrans.FromSql('UPDATE t1 SET a = @p0 WHERE Id = @p1')
|
|
212
|
-
.AddInParameter("@p0", 1)
|
|
213
|
-
.AddInParameter("@p1", id1)
|
|
214
|
-
.ExecuteNonQuery();
|
|
215
|
-
exTrans.FromSql('UPDATE t2 SET b = @p0 WHERE Id = @p1')
|
|
216
|
-
.AddInParameter("@p0", 2)
|
|
217
|
-
.AddInParameter("@p1", id2)
|
|
218
|
-
.ExecuteNonQuery();
|
|
219
|
-
exTrans.Commit();
|
|
220
|
-
} catch (ex) {
|
|
221
|
-
exTrans.Rollback();
|
|
222
|
-
} finally {
|
|
223
|
-
exTrans.Close(); // 必须释放事务对象
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## 绝对禁止
|
|
228
|
-
|
|
229
|
-
```javascript
|
|
230
|
-
// ❌ 绝对禁止:拼接 SQL 字符串
|
|
231
|
-
var sql = "SELECT * FROM SysUser WHERE Account = '" + V8.Param.account + "'";
|
|
232
|
-
V8.Db.FromSql(sql).ToArray(); // SQL 注入漏洞!
|
|
233
|
-
|
|
234
|
-
// ❌ 禁止:动态拼接表名
|
|
235
|
-
var sql = "SELECT * FROM " + V8.Param.tableName + " WHERE Id = @p0";
|
|
236
|
-
|
|
237
|
-
// ✅ 正确做法:单表查询优先使用 FormEngine + _Where
|
|
238
|
-
var result = V8.FormEngine.GetTableData('SysUser', {
|
|
239
|
-
_Where: [['Account', '=', V8.Param.account]],
|
|
240
|
-
_PageSize: 20
|
|
241
|
-
});
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## 常见查询模式
|
|
245
|
-
|
|
246
|
-
### 分页查询
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
var pageIndex = parseInt(V8.Param.pageIndex) || 1;
|
|
250
|
-
var pageSize = Math.min(parseInt(V8.Param.pageSize) || 20, 100); // 限制最大100
|
|
251
|
-
|
|
252
|
-
var result = V8.FormEngine.GetTableData('TableName', {
|
|
253
|
-
_Where: [['Status', '=', 1]],
|
|
254
|
-
_OrderBy: 'CreateTime',
|
|
255
|
-
_OrderByType: 'DESC',
|
|
256
|
-
_PageIndex: pageIndex,
|
|
257
|
-
_PageSize: pageSize
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
return { Code: 1, Data: result.Data, Total: result.DataCount };
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### 模糊搜索(多字段)
|
|
264
|
-
|
|
265
|
-
```javascript
|
|
266
|
-
var keyword = V8.Param.keyword;
|
|
267
|
-
var where = [['Status', '=', 1]];
|
|
268
|
-
if (keyword) {
|
|
269
|
-
where.push(['AND', '(', 'Name', 'Like', keyword]);
|
|
270
|
-
where.push(['OR', 'Code', 'Like', keyword]);
|
|
271
|
-
where.push(['OR', 'Phone', 'Like', keyword, ')']);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
var result = V8.FormEngine.GetTableData('Customer', {
|
|
275
|
-
_Where: where,
|
|
276
|
-
_PageIndex: 1,
|
|
277
|
-
_PageSize: 20
|
|
278
|
-
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### 关联查询(SQL JOIN)
|
|
282
|
-
|
|
283
|
-
```javascript
|
|
284
|
-
var list = V8.Db.FromSql(`
|
|
285
|
-
SELECT o.Id, o.OrderNo, o.TotalAmount, c.Name AS CustomerName
|
|
286
|
-
FROM OrderHeader o
|
|
287
|
-
INNER JOIN Customer c ON o.CustomerId = c.Id
|
|
288
|
-
WHERE o.Status = @p0 AND o.CreateTime >= @p1
|
|
289
|
-
ORDER BY o.CreateTime DESC
|
|
290
|
-
`).AddInParameter("@p0", 1)
|
|
291
|
-
.AddInParameter("@p1", V8.Param.startDate)
|
|
292
|
-
.ToArray();
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## 注意事项
|
|
296
|
-
|
|
297
|
-
- `V8.Db.FromSql` 只传 SQL 字符串,参数占位符从 `@p0` 开始递增,动态值用 `.AddInParameter("@p0", value)` 绑定
|
|
298
|
-
- 服务端 `V8.FormEngine` 操作默认不触发表单 V8 事件;确需触发时在参数中加 `_InvokeType: 'Client'`
|
|
299
|
-
- 查询结果数量较大时务必分页,`_PageSize` 默认最大 1000
|
|
300
|
-
- `V8.DbRead` 适用于不需要实时性的报表查询
|
|
301
|
-
- 接口引擎的事务由平台自动管理,**不要手动调用** `V8.DbTrans.Commit/Rollback`
|
|
302
|
-
- 扩展数据库事务必须手动调用 `BeginTransaction/Commit/Rollback/Close`
|
|
1
|
+
---
|
|
2
|
+
name: v8-sql-query
|
|
3
|
+
description: Microi V8 安全 SQL 查询指南。用于选择 V8.FormEngine _Where 或 V8.Db.FromSql,处理参数化查询、联表、聚合并避免 SQL 注入。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
|
|
7
|
+
|
|
8
|
+
# Microi V8 安全 SQL 查询
|
|
9
|
+
|
|
10
|
+
你正在开发 Microi 吾码平台的 V8 引擎代码。数据库查询有两种方式:`V8.FormEngine`(推荐)和 `V8.Db`(原始 SQL)。必须遵守安全规范。
|
|
11
|
+
|
|
12
|
+
## 性能门禁(必须执行)
|
|
13
|
+
|
|
14
|
+
写接口引擎前必须先做数据访问计划,避免“循环套循环查数据库”:
|
|
15
|
+
|
|
16
|
+
- 禁止在 `for` / `while` / `forEach` / `map` 循环内调用 `V8.FormEngine.GetFormData`、`GetTableData`、`V8.Db.FromSql`、`V8.ApiEngine.Run` 或 `V8.Http.*`。确实无法避免时,必须先说明原因,并加分页、缓存或限流。
|
|
17
|
+
- 多条 Id、编码、外键查询必须一次性用 `_Where: [['Id','In', ids]]`、SQL `IN`、JOIN 或聚合查询取回,再用内存字典映射。
|
|
18
|
+
- 需要父子、主从、用户/部门/角色名称映射时,先批量取关联表,只保留 `_SelectFields` 必要列,不要逐行查名称。
|
|
19
|
+
- 统计、计数、汇总优先让数据库一次 `GROUP BY` / `COUNT` / `SUM` 完成,不要把大表全部拉到 V8 里循环统计。
|
|
20
|
+
- 列表接口必须限制 `_PageSize`,管理端默认不要超过 100,导出或批处理必须显式分批。
|
|
21
|
+
- 每次查询都要写 `_SelectFields` 或明确 SQL 字段列表,禁止 `SELECT *` 用在大表、接口列表、循环前置查询中。
|
|
22
|
+
- 外部 HTTP、翻译、短信、AI 等慢调用不能放在数据库事务和大循环中;要么异步队列,要么批量预处理并设置超时。
|
|
23
|
+
- 返回前自检一次:数据库访问次数应与数据量无关或近似常数级,不能随着行数线性增长为 N 次查询。
|
|
24
|
+
|
|
25
|
+
## 首选:V8.FormEngine + _Where(自动防注入)
|
|
26
|
+
|
|
27
|
+
`_Where` 是参数化查询语法,自动防 SQL 注入,**永远优先使用**。
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// ✅ 安全:_Where 自动参数化
|
|
31
|
+
var result = V8.FormEngine.GetTableData('SysUser', {
|
|
32
|
+
_Where: [
|
|
33
|
+
['Account', '=', V8.Param.account],
|
|
34
|
+
['AND', 'Status', '=', 1]
|
|
35
|
+
],
|
|
36
|
+
_PageIndex: 1,
|
|
37
|
+
_PageSize: 20
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### _Where 完整语法
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// 基本条件
|
|
45
|
+
[['Field', '操作符', value]]
|
|
46
|
+
|
|
47
|
+
// 操作符:=, ==, <>, !=, >, >=, <, <=, Like, NotLike, StartLike, EndLike, In, NotIn
|
|
48
|
+
|
|
49
|
+
// 多条件 AND
|
|
50
|
+
[['A', '=', 1], ['AND', 'B', '>', 10]]
|
|
51
|
+
|
|
52
|
+
// 多条件 OR
|
|
53
|
+
[['A', '=', 1], ['OR', 'B', '=', 2]]
|
|
54
|
+
|
|
55
|
+
// IN 查询
|
|
56
|
+
[['Id', 'In', ['id1', 'id2', 'id3']]]
|
|
57
|
+
|
|
58
|
+
// NULL 判断
|
|
59
|
+
[['Field', '=', null]] // IS NULL
|
|
60
|
+
[['Field', '<>', null]] // IS NOT NULL
|
|
61
|
+
|
|
62
|
+
// 分组(括号):(Age > 18 OR Status = 1)
|
|
63
|
+
[['Name', 'Like', '张'], ['AND', '(', 'Age', '>', 18], ['OR', 'Status', '=', 1, ')']]
|
|
64
|
+
|
|
65
|
+
// 日期范围
|
|
66
|
+
[['CreateTime', '>=', '2024-01-01'], ['AND', 'CreateTime', '<', '2024-02-01']]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 旧版 _Where 兼容(V8.Method.ParseWhere)
|
|
70
|
+
|
|
71
|
+
老版本前端可能传对象格式(`{Name, Value, Type, AndOr, GroupStart, GroupEnd}`)。
|
|
72
|
+
`V8.Method.ParseWhere` 返回统一的 `DiyWhere` 对象集合;它不是“对象转数组”函数。
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
var objectWhere = V8.Method.ParseWhere(V8.Param._Where);
|
|
76
|
+
V8.FormEngine.GetTableData('Table', { _Where: objectWhere });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
合并客户端筛选与接口固定条件时,必须先统一格式,禁止直接把数组条件追加到对象条件集合。
|
|
80
|
+
部分部署按首项类型解析整个集合,混合格式会触发转换失败并退化为空条件,导致统计返回未筛选总数。
|
|
81
|
+
需要兼容混合输入时,逐条调用 `ParseWhere(JSON.stringify([condition]))`,确认每条恰好解析出一个有效条件后,
|
|
82
|
+
再合并为同一种格式;非空输入解析为空、非法操作符或括号不平衡必须返回失败,不能继续执行统计。
|
|
83
|
+
存在顶层 OR 时,还须保证语义为“整个客户端筛选 AND 固定条件”,不能仅在原表达式末尾追加 AND。
|
|
84
|
+
回归至少比较纯数组、纯对象、混合条件、OR/分组、零结果和非法输入。
|
|
85
|
+
|
|
86
|
+
## 次选:V8.Db.FromSql(仅 SQL 字符串 + AddInParameter)
|
|
87
|
+
|
|
88
|
+
> **⚠️ FormEngine 优先原则:** 增删改操作(INSERT / UPDATE / DELETE)必须**优先**使用 `V8.FormEngine.AddFormData` / `UptFormData` / `UptFormDataByWhere` / `DelFormData` 等方法。**只有**多表 JOIN、复杂子查询、GROUP BY 聚合等 FormEngine 无法表达的场景才使用 `V8.Db.FromSql`。
|
|
89
|
+
|
|
90
|
+
> **⚠️ FromSql 调用规则:** `V8.Db.FromSql` 在 V8 中只传 SQL 字符串,不要把动态值作为第二个或后续参数传给 `FromSql`。动态值必须用链式 `.AddInParameter('@p0', value)` 绑定;否则会生成平台不支持的调用签名。
|
|
91
|
+
|
|
92
|
+
> **共享事务:** `V8.Db` 是主库会话,`V8.Db.FromSql` 不会自动加入接口引擎事务。需要与表单、其它 SQL 或流程共同提交/回滚的操作,必须使用 `V8.DbTrans.FromSql`;依赖本事务尚未提交结果的查询也一样。`V8.FormEngine`、`V8.ApiEngine.Run` 继续显式传第三参数 `V8.DbTrans`。禁止从安全代理取内部事务并自行提交。
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// ❌ 错误形态:不要把动态值作为 FromSql 的第二个参数传入
|
|
96
|
+
|
|
97
|
+
// ✅ 优先:改用 FormEngine(单表增删改查都优先这样写)
|
|
98
|
+
V8.FormEngine.UptFormData('t', { Id: id, A: val1, B: val2 });
|
|
99
|
+
|
|
100
|
+
// ✅ 必须用原生 SQL 时:FromSql 只传 SQL,参数用 AddInParameter
|
|
101
|
+
V8.DbTrans.FromSql("UPDATE t SET A=@p0, B=@p1 WHERE Id=@p2")
|
|
102
|
+
.AddInParameter("@p0", val1)
|
|
103
|
+
.AddInParameter("@p1", val2)
|
|
104
|
+
.AddInParameter("@p2", id)
|
|
105
|
+
.ExecuteNonQuery();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
当 `_Where` 无法满足复杂查询(多表 JOIN、子查询、聚合统计)时使用 `V8.Db`:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// ✅ 安全:使用 @p0, @p1 占位符,并用 AddInParameter 绑定
|
|
112
|
+
var list = V8.Db.FromSql(
|
|
113
|
+
'SELECT a.Id, a.Name, b.OrderCount FROM Customer a LEFT JOIN (SELECT CustomerId, COUNT(*) OrderCount FROM OrderHeader GROUP BY CustomerId) b ON a.Id = b.CustomerId WHERE a.Status = @p0'
|
|
114
|
+
).AddInParameter("@p0", 1).ToArray();
|
|
115
|
+
|
|
116
|
+
// ✅ 安全:多个参数
|
|
117
|
+
var row = V8.Db.FromSql(
|
|
118
|
+
'SELECT * FROM SysUser WHERE Account = @p0 AND DeptId = @p1'
|
|
119
|
+
).AddInParameter("@p0", V8.Param.account)
|
|
120
|
+
.AddInParameter("@p1", V8.Param.deptId)
|
|
121
|
+
.First();
|
|
122
|
+
|
|
123
|
+
// 统计
|
|
124
|
+
var count = V8.Db.FromSql(
|
|
125
|
+
'SELECT COUNT(*) FROM OrderHeader WHERE Status = @p0 AND CreateTime >= @p1'
|
|
126
|
+
).AddInParameter("@p0", 1)
|
|
127
|
+
.AddInParameter("@p1", V8.Param.startDate)
|
|
128
|
+
.ToScalar();
|
|
129
|
+
|
|
130
|
+
// 非查询(UPDATE / INSERT / DELETE)
|
|
131
|
+
V8.DbTrans.FromSql(
|
|
132
|
+
'UPDATE SysUser SET LastLoginTime = @p0 WHERE Id = @p1'
|
|
133
|
+
).AddInParameter("@p0", DateNow('yyyy-MM-dd HH:mm:ss'))
|
|
134
|
+
.AddInParameter("@p1", V8.CurrentUser.Id)
|
|
135
|
+
.ExecuteNonQuery();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> 老版本兼容:要把维护接口复制到旧部署时,不要假定 `DateNow` 或 `System.DateTime.Now.ToString(...)` 一定可用。若数据库类型固定,可直接使用该数据库的当前时间表达式(例如 MySQL 的 `NOW()`);跨数据库代码则应按数据库类型选择表达式或传入平台已确认支持的时间值。
|
|
139
|
+
|
|
140
|
+
### V8.Db 方法速查
|
|
141
|
+
|
|
142
|
+
| 方法 | 返回 | 用途 |
|
|
143
|
+
|------|------|------|
|
|
144
|
+
| `.ToArray()` | 数组 | 查询多条 |
|
|
145
|
+
| `.First()` | 对象 \| null | 查询单条 |
|
|
146
|
+
| `.ToScalar()` | 单值 | COUNT / MAX / SUM 等 |
|
|
147
|
+
| `.ExecuteNonQuery()` | 影响行数 | UPDATE / DELETE / INSERT |
|
|
148
|
+
|
|
149
|
+
> 别名:`.ToList()` = `.ToArray()`,`.ToModel()` = `.First()`,`.ExecuteScalar()` = `.ToScalar()`
|
|
150
|
+
|
|
151
|
+
### 读写分离
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
V8.Db.FromSql(...) // 主库(读写)
|
|
155
|
+
V8.DbRead.FromSql(...) // 从库(只读,适合报表和大量查询)
|
|
156
|
+
// 未部署读写分离时 V8.DbRead 与 V8.Db 一致
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 跨应用查询(扩展数据库)
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
var list = V8.Dbs.OracleDB1.FromSql('SELECT * FROM Table WHERE Id = @p0')
|
|
163
|
+
.AddInParameter("@p0", id)
|
|
164
|
+
.ToArray();
|
|
165
|
+
|
|
166
|
+
// 不写入 microi_database:创建仅当前请求使用的临时会话
|
|
167
|
+
var tempDb = V8.Dbs.Open(
|
|
168
|
+
'SqlServer',
|
|
169
|
+
'Server=127.0.0.1,1433;Database=app;User Id=user;Password=***;TrustServerCertificate=True;'
|
|
170
|
+
);
|
|
171
|
+
var tempList = tempDb.FromSql('SELECT Id, Name FROM Customer WHERE Status = @p0')
|
|
172
|
+
.AddInParameter('@p0', 1)
|
|
173
|
+
.ToArray();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`V8.Dbs.Open` 与保存连接均只支持 Dos.ORM 已认证的 `MySql`、`SqlServer`、`Oracle`、`PostgreSql`、`DaMeng`、`KingBase`。动态连接串只能来自可信服务端密钥或管理员代码,禁止使用 `V8.Param.ConnectionString`,禁止记录或返回。外部 SQL 的表名、列名、排序字段必须来自已校验元数据白名单;动态值继续使用 `AddInParameter`。
|
|
177
|
+
|
|
178
|
+
MCP 结构发现使用 `microi_inspect_external_database`,安全抽样默认使用只读的 `microi_query_external_database`。当用户明确要求数据库管理级能力时,使用独立的 `microi_execute_external_database`:它只允许后端确认的 `Level >= 9999` 当前用户调用,显式确认后可执行目标数据库账号有权执行的任意 DML、DDL、存储过程、数据库原生命令或多语句。输出行数限制只保护 MCP 传输,不限制数据库副作用;审计只记录 SQL 哈希、长度、模式和结果,不得记录 SQL 正文、连接串或密码。
|
|
179
|
+
|
|
180
|
+
不要用 `microi_get_db_schema` 读取第三方库,也不要在对话中搬运全库数据;持续同步应创建参数化、分页、幂等的服务端任务。最高权限入口不等于跨租户,也不能超越目标数据库账号自身权限。
|
|
181
|
+
|
|
182
|
+
## 数据库事务
|
|
183
|
+
|
|
184
|
+
### 接口引擎事务(自动管理)
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// 接口引擎创建 V8.DbTrans;V8.Db 是独立主库会话,不自动加入它:
|
|
188
|
+
// 返回 Code=1 → 自动提交事务
|
|
189
|
+
// 返回 Code≠1 → 自动回滚事务
|
|
190
|
+
// 手动调用 V8.DbTrans.Commit() 或 V8.DbTrans.Rollback() 均无效
|
|
191
|
+
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance - @p0 WHERE Id = @p1')
|
|
192
|
+
.AddInParameter("@p0", 100)
|
|
193
|
+
.AddInParameter("@p1", fromId)
|
|
194
|
+
.ExecuteNonQuery();
|
|
195
|
+
V8.DbTrans.FromSql('UPDATE Account SET Balance = Balance + @p0 WHERE Id = @p1')
|
|
196
|
+
.AddInParameter("@p0", 100)
|
|
197
|
+
.AddInParameter("@p1", toId)
|
|
198
|
+
.ExecuteNonQuery();
|
|
199
|
+
|
|
200
|
+
// V8.DbTrans 可传给 FormEngine 和 ApiEngine.Run 共享事务
|
|
201
|
+
V8.FormEngine.UptFormData('Table1', { Id: 'x', Status: 1 }, V8.DbTrans);
|
|
202
|
+
V8.ApiEngine.Run('other-engine', { Id: 'x' }, V8.DbTrans);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 扩展数据库事务(手动管理)
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// 扩展数据库需要手动管理事务
|
|
209
|
+
var exTrans = V8.Dbs.OracleDB1.BeginTransaction();
|
|
210
|
+
try {
|
|
211
|
+
exTrans.FromSql('UPDATE t1 SET a = @p0 WHERE Id = @p1')
|
|
212
|
+
.AddInParameter("@p0", 1)
|
|
213
|
+
.AddInParameter("@p1", id1)
|
|
214
|
+
.ExecuteNonQuery();
|
|
215
|
+
exTrans.FromSql('UPDATE t2 SET b = @p0 WHERE Id = @p1')
|
|
216
|
+
.AddInParameter("@p0", 2)
|
|
217
|
+
.AddInParameter("@p1", id2)
|
|
218
|
+
.ExecuteNonQuery();
|
|
219
|
+
exTrans.Commit();
|
|
220
|
+
} catch (ex) {
|
|
221
|
+
exTrans.Rollback();
|
|
222
|
+
} finally {
|
|
223
|
+
exTrans.Close(); // 必须释放事务对象
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 绝对禁止
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
// ❌ 绝对禁止:拼接 SQL 字符串
|
|
231
|
+
var sql = "SELECT * FROM SysUser WHERE Account = '" + V8.Param.account + "'";
|
|
232
|
+
V8.Db.FromSql(sql).ToArray(); // SQL 注入漏洞!
|
|
233
|
+
|
|
234
|
+
// ❌ 禁止:动态拼接表名
|
|
235
|
+
var sql = "SELECT * FROM " + V8.Param.tableName + " WHERE Id = @p0";
|
|
236
|
+
|
|
237
|
+
// ✅ 正确做法:单表查询优先使用 FormEngine + _Where
|
|
238
|
+
var result = V8.FormEngine.GetTableData('SysUser', {
|
|
239
|
+
_Where: [['Account', '=', V8.Param.account]],
|
|
240
|
+
_PageSize: 20
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 常见查询模式
|
|
245
|
+
|
|
246
|
+
### 分页查询
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
var pageIndex = parseInt(V8.Param.pageIndex) || 1;
|
|
250
|
+
var pageSize = Math.min(parseInt(V8.Param.pageSize) || 20, 100); // 限制最大100
|
|
251
|
+
|
|
252
|
+
var result = V8.FormEngine.GetTableData('TableName', {
|
|
253
|
+
_Where: [['Status', '=', 1]],
|
|
254
|
+
_OrderBy: 'CreateTime',
|
|
255
|
+
_OrderByType: 'DESC',
|
|
256
|
+
_PageIndex: pageIndex,
|
|
257
|
+
_PageSize: pageSize
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return { Code: 1, Data: result.Data, Total: result.DataCount };
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 模糊搜索(多字段)
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
var keyword = V8.Param.keyword;
|
|
267
|
+
var where = [['Status', '=', 1]];
|
|
268
|
+
if (keyword) {
|
|
269
|
+
where.push(['AND', '(', 'Name', 'Like', keyword]);
|
|
270
|
+
where.push(['OR', 'Code', 'Like', keyword]);
|
|
271
|
+
where.push(['OR', 'Phone', 'Like', keyword, ')']);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
var result = V8.FormEngine.GetTableData('Customer', {
|
|
275
|
+
_Where: where,
|
|
276
|
+
_PageIndex: 1,
|
|
277
|
+
_PageSize: 20
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 关联查询(SQL JOIN)
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
var list = V8.Db.FromSql(`
|
|
285
|
+
SELECT o.Id, o.OrderNo, o.TotalAmount, c.Name AS CustomerName
|
|
286
|
+
FROM OrderHeader o
|
|
287
|
+
INNER JOIN Customer c ON o.CustomerId = c.Id
|
|
288
|
+
WHERE o.Status = @p0 AND o.CreateTime >= @p1
|
|
289
|
+
ORDER BY o.CreateTime DESC
|
|
290
|
+
`).AddInParameter("@p0", 1)
|
|
291
|
+
.AddInParameter("@p1", V8.Param.startDate)
|
|
292
|
+
.ToArray();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## 注意事项
|
|
296
|
+
|
|
297
|
+
- `V8.Db.FromSql` 只传 SQL 字符串,参数占位符从 `@p0` 开始递增,动态值用 `.AddInParameter("@p0", value)` 绑定
|
|
298
|
+
- 服务端 `V8.FormEngine` 操作默认不触发表单 V8 事件;确需触发时在参数中加 `_InvokeType: 'Client'`
|
|
299
|
+
- 查询结果数量较大时务必分页,`_PageSize` 默认最大 1000
|
|
300
|
+
- `V8.DbRead` 适用于不需要实时性的报表查询
|
|
301
|
+
- 接口引擎的事务由平台自动管理,**不要手动调用** `V8.DbTrans.Commit/Rollback`
|
|
302
|
+
- 扩展数据库事务必须手动调用 `BeginTransaction/Commit/Rollback/Close`
|