@microi.net/cli 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/mcp-codex-stdio-adapter.js +189 -0
  4. package/dist/mcp-server.js +972 -0
  5. package/dist/mcp-trae-windows-launcher.cmd +21 -0
  6. package/dist/microi-cli-mcp.js +7 -0
  7. package/dist/microi-cli.js +1645 -0
  8. package/dist/microi-skills.meta.json +335 -0
  9. package/dist/microi.skills/.microi-skills-version.json +6 -0
  10. package/dist/microi.skills/README.md +276 -0
  11. package/dist/microi.skills/ai-engine/SKILL.md +140 -0
  12. package/dist/microi.skills/ai-engine/agents/openai.yaml +4 -0
  13. package/dist/microi.skills/app-store/SKILL.md +105 -0
  14. package/dist/microi.skills/app-store/agents/openai.yaml +4 -0
  15. package/dist/microi.skills/business-blueprint/SKILL.md +184 -0
  16. package/dist/microi.skills/datasource-engine/SKILL.md +89 -0
  17. package/dist/microi.skills/datasource-engine/agents/openai.yaml +4 -0
  18. package/dist/microi.skills/dos-orm/SKILL.md +76 -0
  19. package/dist/microi.skills/dos-orm/references/api-reference.md +229 -0
  20. package/dist/microi.skills/job-engine/SKILL.md +141 -0
  21. package/dist/microi.skills/job-engine/agents/openai.yaml +4 -0
  22. package/dist/microi.skills/message-notification/SKILL.md +113 -0
  23. package/dist/microi.skills/message-notification/agents/openai.yaml +6 -0
  24. package/dist/microi.skills/message-notification/references/contracts.md +99 -0
  25. package/dist/microi.skills/microi-ai-app-auth.js +651 -0
  26. package/dist/microi.skills/microi-ai-application/SKILL.md +80 -0
  27. package/dist/microi.skills/microi-ai-application/agents/openai.yaml +4 -0
  28. package/dist/microi.skills/microi-ai-application/references/frontend-baseline.md +164 -0
  29. package/dist/microi.skills/microi-client-frontend/SKILL.md +562 -0
  30. package/dist/microi.skills/microi-datasource-mapping/SKILL.md +108 -0
  31. package/dist/microi.skills/microi-db-schema/SKILL.md +170 -0
  32. package/dist/microi.skills/microi-db-schema/agents/openai.yaml +4 -0
  33. package/dist/microi.skills/microi-db-schema/references/core-tables.md +695 -0
  34. package/dist/microi.skills/microi-db-schema/references/form-component-options.md +256 -0
  35. package/dist/microi.skills/microi-db-schema/references/schema-overview.md +203 -0
  36. package/dist/microi.skills/microi-db-schema/references/schema.md +647 -0
  37. package/dist/microi.skills/microi-db-schema/references/table-catalog.md +1607 -0
  38. package/dist/microi.skills/microi-deployment/SKILL.md +117 -0
  39. package/dist/microi.skills/microi-deployment/references/deployment-matrix.md +94 -0
  40. package/dist/microi.skills/microi-docs-coverage/SKILL.md +91 -0
  41. package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +65 -0
  42. package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +887 -0
  43. package/dist/microi.skills/microi-form-engine/SKILL.md +159 -0
  44. package/dist/microi.skills/microi-form-engine/references/component-catalog.md +116 -0
  45. package/dist/microi.skills/microi-form-engine/references/data-source-events.md +117 -0
  46. package/dist/microi.skills/microi-form-layout/SKILL.md +373 -0
  47. package/dist/microi.skills/microi-frontend-sdk/SKILL.md +304 -0
  48. package/dist/microi.skills/microi-left-right-layout/SKILL.md +132 -0
  49. package/dist/microi.skills/microi-microservice/SKILL.md +115 -0
  50. package/dist/microi.skills/microi-microservice/references/runtime-delivery.md +145 -0
  51. package/dist/microi.skills/microi-mobile-app-quality/SKILL.md +436 -0
  52. package/dist/microi.skills/microi-solution-quotation/SKILL.md +76 -0
  53. package/dist/microi.skills/microi-solution-quotation/agents/openai.yaml +4 -0
  54. package/dist/microi.skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -0
  55. package/dist/microi.skills/microi-system-delivery/SKILL.md +446 -0
  56. package/dist/microi.skills/microi-ui/SKILL.md +321 -0
  57. package/dist/microi.skills/microi-uniapp-frontend/SKILL.md +483 -0
  58. package/dist/microi.skills/microi.v8.js +1758 -0
  59. package/dist/microi.skills/module-engine/SKILL.md +131 -0
  60. package/dist/microi.skills/module-engine/references/module-config.md +174 -0
  61. package/dist/microi.skills/page-engine/SKILL.md +397 -0
  62. package/dist/microi.skills/performance-testing/SKILL.md +207 -0
  63. package/dist/microi.skills/playwright-e2e/SKILL.md +769 -0
  64. package/dist/microi.skills/print-engine/SKILL.md +237 -0
  65. package/dist/microi.skills/production-readonly-audit/SKILL.md +39 -0
  66. package/dist/microi.skills/report-engine/SKILL.md +69 -0
  67. package/dist/microi.skills/report-engine/agents/openai.yaml +4 -0
  68. package/dist/microi.skills/search-engine/SKILL.md +73 -0
  69. package/dist/microi.skills/search-engine/agents/openai.yaml +4 -0
  70. package/dist/microi.skills/spider-engine/SKILL.md +188 -0
  71. package/dist/microi.skills/translate-engine/SKILL.md +91 -0
  72. package/dist/microi.skills/translate-engine/agents/openai.yaml +4 -0
  73. package/dist/microi.skills/ui-design/SKILL.md +1575 -0
  74. package/dist/microi.skills/ui-design/assets/pattern-showcase/app.js +54 -0
  75. package/dist/microi.skills/ui-design/assets/pattern-showcase/index.html +163 -0
  76. package/dist/microi.skills/ui-design/assets/pattern-showcase/styles.css +311 -0
  77. package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +98 -0
  78. package/dist/microi.skills/ui-design/references/design-pattern-library.md +171 -0
  79. package/dist/microi.skills/ui-design/references/mci-design-contract.md +84 -0
  80. package/dist/microi.skills/ui-design/references/motion-and-media.md +71 -0
  81. package/dist/microi.skills/ui-design/references/product-flow-recipes.md +94 -0
  82. package/dist/microi.skills/uniapp-mall-assets/SKILL.md +105 -0
  83. package/dist/microi.skills/v8-api-config/SKILL.md +272 -0
  84. package/dist/microi.skills/v8-cache-pattern/SKILL.md +286 -0
  85. package/dist/microi.skills/v8-crud-api/SKILL.md +398 -0
  86. package/dist/microi.skills/v8-debugging/SKILL.md +279 -0
  87. package/dist/microi.skills/v8-explorer-tree/SKILL.md +224 -0
  88. package/dist/microi.skills/v8-export-import/SKILL.md +590 -0
  89. package/dist/microi.skills/v8-file-upload/SKILL.md +497 -0
  90. package/dist/microi.skills/v8-formengine-http/SKILL.md +218 -0
  91. package/dist/microi.skills/v8-frontend-events/SKILL.md +349 -0
  92. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print-api.md +107 -0
  93. package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +185 -0
  94. package/dist/microi.skills/v8-http-integration/SKILL.md +379 -0
  95. package/dist/microi.skills/v8-image-processing/SKILL.md +187 -0
  96. package/dist/microi.skills/v8-image-processing/agents/openai.yaml +4 -0
  97. package/dist/microi.skills/v8-image-processing/references/api-reference.md +620 -0
  98. package/dist/microi.skills/v8-menu-buttons/SKILL.md +661 -0
  99. package/dist/microi.skills/v8-mongodb/SKILL.md +149 -0
  100. package/dist/microi.skills/v8-mq-mqtt/SKILL.md +227 -0
  101. package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +193 -0
  102. package/dist/microi.skills/v8-security/SKILL.md +417 -0
  103. package/dist/microi.skills/v8-sql-query/SKILL.md +290 -0
  104. package/dist/microi.skills/v8-table-event/SKILL.md +385 -0
  105. package/dist/microi.skills/v8-template-engine/SKILL.md +165 -0
  106. package/dist/microi.skills/v8-utilities/SKILL.md +79 -0
  107. package/dist/microi.skills/v8-utilities/references/client-api-index.md +136 -0
  108. package/dist/microi.skills/v8-utilities/references/platform-http-routes.md +80 -0
  109. package/dist/microi.skills/v8-utilities/references/server-api-index.md +129 -0
  110. package/dist/microi.skills/v8-workflow/SKILL.md +322 -0
  111. package/dist/microi.skills/workspace-conventions/SKILL.md +479 -0
  112. package/package.json +40 -0
@@ -0,0 +1,398 @@
1
+ ---
2
+ name: v8-crud-api
3
+ description: Microi V8 CRUD 接口引擎开发。用于编写服务端 JavaScript,涉及 V8.FormEngine Add/Upt/Del/GetTableData、DosResult 返回、事务和校验。
4
+ ---
5
+
6
+ # Microi V8 CRUD API 接口引擎开发
7
+
8
+ 你正在开发 Microi 吾码平台的 V8 接口引擎。接口引擎是运行在服务端的 JavaScript 函数,通过 `V8.FormEngine` 操作数据库,通过 `V8.Result` 或 `return` 返回结果。
9
+
10
+ ## 本地优先与版本头(必做)
11
+
12
+ AI 本地开发接口引擎时,优先修改 `microi-v8-engine/<租户>/<项目>/接口引擎/.../*.js` 本地文件,再通过 MCP 或 VS Code 插件同步到数据库。插件提示“本地和远端不一致”时,必须先读取本地与远端代码并合并有效差异,不能盲目用任一侧覆盖另一侧。
13
+
14
+ 每一次修改、上传、推送接口引擎代码,都必须维护文件顶部版本区域。版本号从 `v1.0.0` 开始;每次上传/推送/修改递增 1;补丁位和次版本位最大为 9 并向前进位(`v1.0.9 -> v1.1.0`、`v1.9.9 -> v2.0.0`、`v9.9.9 -> v10.0.0`)。代码头只写完整功能说明,不写修改历史、时间戳或 ChangeLog。推荐格式:
15
+
16
+ ```javascript
17
+ /*
18
+ * V8 ApiEngine
19
+ * ApiEngineKey: 示例接口引擎Key
20
+ * Version: v1.0.0
21
+ * 功能说明:
22
+ * - 完整说明该接口引擎负责的业务功能、输入参数、关键返回字段和重要副作用。
23
+ */
24
+ ```
25
+
26
+ 同步流程:`确认后端可达(不可达则自动启动 Microi.Server/Microi.net.Api/Microi.net.Api.csproj) -> 读取远端 -> 修改本地并递增语义版本头 -> JS 语法检查 -> 保存远端 -> 回读远端确认代码头 Version 与 sys_apiengine.Version 一致 -> 用 HTTP /apiengine/{key} + osclient Header 复测`。只用 MCP 保存成功不算完成,必须至少做回读或 HTTP 验证。普通 POST/PUT/PATCH/DELETE 禁止无脑追加 `--OsClient--...--`;该特殊路径仅保留给确实无法传 Header/Form/Query 的 GET/HEAD 或第三方回调场景。
27
+
28
+ 如果保存或回读时出现 `fetch failed`、`ECONNREFUSED`、`000 Failed to connect`、端口无人监听等服务不可达问题,不能提前中止。需要启动或重启本地 API 时,必须在 `Microi.Server/Microi.net.Api` 目录通过 VS Code 可见终端执行 `dotnet run --launch-profile Microi.net.Api`,让开发者能肉眼看到并手动停止;不要用隐藏后台 shell 启动长期占用端口的 API 进程。若工具当前无法打开可见终端,应先说明限制并让用户启动/重启后继续验证。涉及 PC 页面联调或 Playwright 时,`Microi.Client` 也必须使用可见终端执行:`npm run dev -- --host 0.0.0.0 --port 1988`。只有启动失败、依赖缺失、数据库连接失败或端口冲突无法处理时才报告阻塞。
29
+
30
+ Microi.net.Api 普通本地启动不要额外设置 `ASPNETCORE_ENVIRONMENT` / `DOTNET_ENVIRONMENT`,让 `Program.cs` 读取 `Microi.Server/Microi.net.Api/.microi-local` 并加载对应的 `appsettings.{Env}.json`。
31
+
32
+ 版本与历史同步规则:通过 MCP 或 VS Code 插件保存接口引擎时,必须同步写入 `sys_apiengine.Version`;修改记录只写入 `sys_apiengine.ChangeHistory`,不得写进代码头。`ChangeHistory` 是“修改历史说明”,每次更新都必须把最新说明拼接到最前面,并保留原有全部历史文字,禁止覆盖、清空或只保留最新一条。旧数据库可能没有 `Version`、`ChangeHistory` 字段,工具必须检测字段或失败回退,保证旧库仍可只更新 `ApiV8Code` 与 `UpdateTime`。
33
+
34
+ 生成接口引擎代码时,代码内容本身(文件头、普通注释、`console.log`、返回 `Msg` 等)不要包含 `Microi`、`吾码` 等平台品牌文字,除非业务数据或字段值本身必须如此。生成代码要有可维护注释:每个 `function` 前写清用途、关键参数和返回值;跨表事务、权限校验、状态机、金额/库存计算、复杂 `_Where` 条件等代码段前写短注释说明业务原因;避免“给变量赋值”这类无信息量注释。
35
+
36
+ ## 核心规则
37
+
38
+ - 接口引擎文件是纯 JavaScript(Jint 引擎,非 Node.js)
39
+ - 全局对象 `V8` 是所有后端能力的入口
40
+ - 通过 `V8.Param` 获取前端传入的参数(URL参数 / form-data / payload-json)
41
+ - 通过 `V8.CurrentUser` 获取当前登录用户信息
42
+ - 返回结果统一格式:`{ Code: 1, Data: any, Msg: '成功' }`
43
+ - 所有 FormEngine 方法在服务器端支持第三个参数传入 `V8.DbTrans`(事务对象)
44
+ - 服务端调用 FormEngine 默认**不触发**表单 V8 事件,加 `_InvokeType: 'Client'` 才触发
45
+ - 接口内 `return Code=1` 自动提交事务、`Code≠1` 自动回滚事务,**禁止**手动 Commit/Rollback
46
+
47
+ ## 性能底线(必须自检)
48
+
49
+ - 写接口引擎前必须先做数据访问计划:需要哪些表、哪些字段、预计数据量、是否分页、是否需要缓存。
50
+ - 禁止在 `for` / `while` / `forEach` / 嵌套循环中反复调用 `GetFormData`、`GetTableData`、`FromSql`、`ApiEngine.Run` 或外部 HTTP。先收集 Id/Key,用一次 `In` 查询或一条 JOIN/聚合 SQL 批量取回,再用对象字典映射。
51
+ - 禁止用双重循环匹配两组列表。先把小表或关联表整理成 `{ id: row }`、`{ parentId: [] }` 这类 Map,再单循环组装结果。
52
+ - 所有列表查询必须设置 `_SelectFields`,只取业务需要的字段;面向前端的列表必须分页或显式限制 `_PageSize`,不能一次拉全表。
53
+ - 报表、统计、跨表汇总优先使用数据库聚合(`COUNT/SUM/GROUP BY/JOIN`)或一次批量查询后内存聚合,不能逐行查明细再累加。
54
+ - 高频读取的系统配置、字典、菜单、字段、角色权限等静态数据要优先使用 `V8.Cache` 或平台已有缓存;写入后必须清理相关缓存。
55
+ - 外部 HTTP、短信、翻译、文件处理等慢操作不要放在数据库事务内循环执行;能批量就批量,不能批量就拆成异步任务/队列。
56
+ - 返回前做一次性能复核:数据库访问次数是否与数据量无关、是否避免 N+1 查询、是否有索引友好的 `_Where` 条件、是否不会因空参数导致全表扫描。
57
+
58
+ ### 计量流水与分页审计
59
+
60
+ - Token、积分、余额等计量必须由服务端在真实成功结果后记录,失败请求不得扣减;能够取得供应商 `usage` 时禁止用完整 JSON 字符数代替真实输入/输出 Token。
61
+ - 余额扣减与流水新增必须在同一事务内完成,并锁定当前账户行,避免并发请求透支或出现“余额已扣但流水未写”。
62
+ - 面向个人中心的流水接口必须按 `V8.CurrentUser.Id` 强制隔离,并返回 `PageIndex`、`PageSize`、`TotalCount`;列表只取页面需要的审计字段。
63
+ - 为便于用户核对,可以保存经过空白归一化的用户输入短摘要;摘要按 Unicode 文本元素截取,不能截断 emoji 或代理对,也不要把完整请求 JSON 当摘要。
64
+
65
+ ## DosResult 状态码
66
+
67
+ | Code | 含义 |
68
+ |------|------|
69
+ | `1` | 成功 |
70
+ | `0` | 业务失败(自动回滚) |
71
+ | `2` | `GetFormData` 数据不存在(特殊值,仍属正常查询)|
72
+ | `1001` | Token 失效 |
73
+ | `1002` | 身份验证失败 |
74
+
75
+ ```javascript
76
+ // GetFormData Code=2 的处理
77
+ var r = V8.FormEngine.GetFormData('Order', { Id: V8.Param.id });
78
+ if (r.Code === 2) return { Code: 0, Msg: '订单不存在' };
79
+ if (r.Code !== 1) return r;
80
+ // r.Data 才是真实数据
81
+ ```
82
+
83
+ ## 全局日期函数
84
+
85
+ ```javascript
86
+ DateNow('yyyy-MM-dd HH:mm:ss') // 当前时间字符串
87
+ DateFormat(new Date(), 'yyyy-MM-dd') // 格式化
88
+ DateAdd(new Date(), 'd', 7, 'yyyy-MM-dd') // 加减(s/m/h/d/w/q/M/y)
89
+ ```
90
+
91
+ ## 查询列表(分页)
92
+
93
+ ```javascript
94
+ var result = V8.FormEngine.GetTableData('SysUser', {
95
+ _Where: [
96
+ ['Status', '=', 1],
97
+ ['AND', 'Name', 'Like', V8.Param.keyword || '']
98
+ ],
99
+ _SelectFields: ['Id', 'Account', 'Name', 'Phone', 'CreateTime'],
100
+ _OrderBy: 'CreateTime',
101
+ _OrderByType: 'DESC',
102
+ _PageIndex: V8.Param.pageIndex || 1,
103
+ _PageSize: V8.Param.pageSize || 20
104
+ });
105
+
106
+ return { Code: 1, Data: result.Data, DataCount: result.DataCount, Msg: '成功' };
107
+ ```
108
+
109
+ ### 请求内异步查询
110
+
111
+ 后端接口引擎可在本次请求内使用真实异步查询;前端 V8 不使用这个方法名:
112
+
113
+ ```javascript
114
+ var result = await V8.FormEngine.GetTableDataAsync('SysUser', {
115
+ _Where: [['Status', '=', 1]],
116
+ _SelectFields: ['Id', 'Account', 'Name'],
117
+ _PageIndex: 1,
118
+ _PageSize: 20
119
+ });
120
+
121
+ return { Code: 1, Data: result.Data, DataCount: result.DataCount };
122
+ ```
123
+
124
+ 必须 `await` 结果。需要接口先返回、后续再批量处理时,应改用平台后台任务、Job、MQ 或 outbox,而不是丢弃 Promise。
125
+
126
+ ### 多字段排序
127
+
128
+ ```javascript
129
+ var result = V8.FormEngine.GetTableData('SysUser', {
130
+ _Where: [['Status', '=', 1]],
131
+ _OrderBys: { 'CreateTime': 'desc', 'Name': 'asc' }
132
+ });
133
+ ```
134
+
135
+ ### 匿名查询(无需登录)
136
+
137
+ ```javascript
138
+ var result = V8.FormEngine.GetTableDataAnonymous('Article', {
139
+ _Where: [['IsPublished', '=', 1]],
140
+ _PageSize: 10
141
+ });
142
+ ```
143
+
144
+ 匿名新增的公开入口是
145
+ `POST /api/formengine/AddFormDataAnonymous`,且目标表必须显式允许匿名新增。
146
+ 当前后端 `V8.FormEngine` 接口不公开
147
+ `V8.FormEngine.AddFormDataAnonymous`;接口引擎内部仍使用
148
+ `V8.FormEngine.AddFormData(...)` 并遵守可信执行身份。不要仅因为历史文档出现
149
+ 该名称就为匿名业务接口关闭服务端校验。
150
+
151
+ ### 获取树形数据
152
+
153
+ ```javascript
154
+ // 表单属性需开启【树形结构】
155
+ var result = V8.FormEngine.GetTableDataTree('Department', {});
156
+ ```
157
+
158
+ ### 仅获取数据条数
159
+
160
+ ```javascript
161
+ var result = V8.FormEngine.GetTableDataCount('SysUser', {
162
+ _Where: [['Status', '=', 1]]
163
+ });
164
+ // result.DataCount 为总数
165
+ ```
166
+
167
+ ## 查询单条
168
+
169
+ ```javascript
170
+ if (!V8.Param.id) {
171
+ return { Code: 0, Msg: 'id 不能为空' };
172
+ }
173
+
174
+ var result = V8.FormEngine.GetFormData('SysUser', {
175
+ _Where: [['Id', '=', V8.Param.id]],
176
+ _SelectFields: ['Id', 'Account', 'Name', 'Phone']
177
+ });
178
+ // 也可以用 Id 直接查:{ Id: 'xxx' }
179
+
180
+ if (result.Code !== 1 || !result.Data) {
181
+ return { Code: 0, Msg: '数据不存在' };
182
+ }
183
+
184
+ return { Code: 1, Data: result.Data };
185
+ ```
186
+
187
+ ## 新增
188
+
189
+ ```javascript
190
+ if (!V8.Param.Account || !V8.Param.Name) {
191
+ return { Code: 0, Msg: '账号和姓名不能为空' };
192
+ }
193
+
194
+ // 检查唯一性
195
+ var exist = V8.FormEngine.GetFormData('SysUser', {
196
+ _Where: [['Account', '=', V8.Param.Account]]
197
+ });
198
+ if (exist.Code === 1 && exist.Data) {
199
+ return { Code: 0, Msg: '账号已存在' };
200
+ }
201
+
202
+ var result = V8.FormEngine.AddFormData('SysUser', {
203
+ // Id 不传会自动生成 GUID
204
+ Account: V8.Param.Account,
205
+ Name: V8.Param.Name,
206
+ Phone: V8.Param.Phone || '',
207
+ Status: 1
208
+ });
209
+ // result.Data 包含 Id, CreateTime, UserId 等自动生成字段
210
+
211
+ return { Code: result.Code, Data: result.Data, Msg: result.Code === 1 ? '新增成功' : result.Msg };
212
+ ```
213
+
214
+ ### 批量新增
215
+
216
+ ```javascript
217
+ var addList = [];
218
+ for (var i = 0; i < V8.Param.items.length; i++) {
219
+ addList.push({
220
+ FormEngineKey: 'SysUser', // 支持不同表混合批量
221
+ Account: V8.Param.items[i].Account,
222
+ Name: V8.Param.items[i].Name
223
+ });
224
+ }
225
+ var result = V8.FormEngine.AddTableData(addList);
226
+ ```
227
+
228
+ ## 更新
229
+
230
+ ```javascript
231
+ if (!V8.Param.Id) {
232
+ return { Code: 0, Msg: 'Id 不能为空' };
233
+ }
234
+
235
+ var result = V8.FormEngine.UptFormData('SysUser', {
236
+ Id: V8.Param.Id, // 必传
237
+ Name: V8.Param.Name,
238
+ Phone: V8.Param.Phone,
239
+ _NotSaveField: ['Account'], // 可选:忽略这些字段不更新
240
+ _NoLineForAdd: true, // 可选:数据不存在时自动插入
241
+ _ForceUpt: true // 可选:强制修改自动编号字段
242
+ });
243
+
244
+ return { Code: result.Code, Msg: result.Code === 1 ? '更新成功' : result.Msg };
245
+ ```
246
+
247
+ ### 批量更新
248
+
249
+ ```javascript
250
+ var uptList = [];
251
+ for (var i = 0; i < V8.Param.items.length; i++) {
252
+ uptList.push({
253
+ FormEngineKey: 'SysUser',
254
+ Id: V8.Param.items[i].Id,
255
+ Status: V8.Param.items[i].Status
256
+ });
257
+ }
258
+ V8.FormEngine.UptTableData(uptList);
259
+ ```
260
+
261
+ ## 删除
262
+
263
+ ```javascript
264
+ // 删除单条
265
+ var result = V8.FormEngine.DelFormData('SysUser', { Id: V8.Param.Id });
266
+
267
+ // 批量删除(传 Ids 数组)
268
+ var result = V8.FormEngine.DelFormData('SysUser', { Ids: V8.Param.Ids });
269
+
270
+ return { Code: result.Code, Msg: result.Code === 1 ? '删除成功' : result.Msg };
271
+ ```
272
+
273
+ ### 批量删除(跨表)
274
+
275
+ ```javascript
276
+ var delList = [];
277
+ delList.push({ FormEngineKey: 'OrderDetail', Id: V8.Param.detailId });
278
+ delList.push({ FormEngineKey: 'OrderHeader', Id: V8.Param.orderId });
279
+ V8.FormEngine.DelTableData(delList);
280
+ ```
281
+
282
+ ## 按条件批量操作
283
+
284
+ ```javascript
285
+ // 按条件更新
286
+ V8.FormEngine.UptFormDataByWhere('SysUser', {
287
+ _Where: [['DeptId', '=', V8.Param.deptId]],
288
+ Status: 0,
289
+ _NoLineForAdd: true // 可选:不存在时插入
290
+ });
291
+
292
+ // 按条件删除(不支持 _Where 以外的删除方式)
293
+ V8.FormEngine.DelFormDataByWhere('SysUser', {
294
+ _Where: [['Status', '=', 0], ['AND', 'CreateTime', '<', '2024-01-01']]
295
+ });
296
+ ```
297
+
298
+ ## 事务处理
299
+
300
+ ```javascript
301
+ // 接口引擎中 V8.Db 自动开启事务:
302
+ // 返回DosResult/带Code对象:仅Code=1提交,其他值回滚
303
+ // 返回对象但没有Code:回滚
304
+ // 返回字符串/数字/数组/布尔/null且未异常:提交
305
+ // 手动调用 V8.DbTrans.Commit() 或 Rollback() 无效,由平台统一管理
306
+
307
+ // FormEngine 可传入事务对象(第三个参数)
308
+ V8.FormEngine.AddFormData('Table1', { Name: '测试' }, V8.DbTrans);
309
+ V8.FormEngine.UptFormData('Table2', { Id: 'xxx', Status: 1 }, V8.DbTrans);
310
+
311
+ // 调用其他接口引擎也可共享事务
312
+ V8.ApiEngine.Run('other-engine-key', { Id: 'xxx' }, V8.DbTrans);
313
+ ```
314
+
315
+ ## 请求内异步与后台处理
316
+
317
+ ```javascript
318
+ // 本次请求必须拿到结果时,使用真实Async API并await
319
+ var resp = await V8.Http.PostResponseAsync({
320
+ Url: 'https://other.com/notify',
321
+ PostParam: { Id: V8.Param.id },
322
+ Timeout: 10
323
+ });
324
+ return resp.StatusCode >= 200 && resp.StatusCode < 300
325
+ ? { Code: 1 }
326
+ : { Code: 0, Msg: '通知失败' };
327
+ ```
328
+
329
+ 禁止用 `setTimeout` / `Task.Run` 实现“立即返回、后台继续”:接口返回后 Jint Engine、租户上下文、事务和执行租约会释放。脱离请求的任务使用后台任务、Job、MQ 或 outbox,并按 `EventId` 幂等处理与恢复。
330
+
331
+ ## 动态加字段(运行时改表结构)
332
+
333
+ ```javascript
334
+ V8.FormEngine.AddField({
335
+ TableName: 'diy_test',
336
+ Name: 'Age',
337
+ Type: 'int', // 仅使用平台允许的varchar(N)/mediumtext/longtext/int/bigint/decimal(18,N)
338
+ Label: '年龄',
339
+ Component: 'NumberText',
340
+ TableWidth: '100',
341
+ Visible: 1
342
+ });
343
+ ```
344
+
345
+ > 风险:会执行 DDL(ALTER TABLE)。仅在低代码自定义配置场景使用,业务运行时**不要**频繁调用。
346
+
347
+ 日期时间字段统一使用 `varchar(25)` 保存 `yyyy-MM-dd HH:mm:ss`,组件使用 `DateTime`。禁止 `datetime/date/timestamp/float/double/boolean/string/text/nvarchar` 等平台不允许的物理类型。动态表/字段属于控制面能力,只允许 `Level >= 9999` 的可信管理脚本使用。
348
+
349
+ ## 旧版 _Where 兼容
350
+
351
+ ```javascript
352
+ // 老版本前端 / 老接口可能传旧格式 _Where:[{ Name, Value, Type, AndOr, Group }, ...]
353
+ // 转换成新格式:
354
+ var newWhere = V8.Method.ParseWhere(V8.Param._Where);
355
+ V8.FormEngine.GetTableData('Table', { _Where: newWhere });
356
+ ```
357
+
358
+ ## _Where 条件语法速查
359
+
360
+ ```javascript
361
+ // 等于
362
+ [['Field', '=', value]]
363
+
364
+ // 模糊查询
365
+ [['Name', 'Like', '张']] // %张%
366
+ [['Name', 'StartLike', '张']] // 张%
367
+ [['Name', 'EndLike', '三']] // %三
368
+
369
+ // AND / OR
370
+ [['A', '=', 1], ['AND', 'B', '>', 10]]
371
+ [['A', '=', 1], ['OR', 'B', '=', 2]]
372
+
373
+ // IN / NotIn
374
+ [['Id', 'In', ['id1', 'id2', 'id3']]]
375
+ [['Status', 'NotIn', [0, -1]]]
376
+
377
+ // NULL
378
+ [['Field', '=', null]] // IS NULL
379
+ [['Field', '<>', null]] // IS NOT NULL
380
+
381
+ // 分组(括号)
382
+ [['Name', 'Like', '张'], ['AND', '(', 'Age', '>', 18], ['OR', 'Status', '=', 1, ')']]
383
+
384
+ // 日期范围
385
+ [['CreateTime', '>=', '2024-01-01'], ['AND', 'CreateTime', '<', '2024-02-01']]
386
+ ```
387
+
388
+ **支持的操作符:** `=`, `==`, `<>`, `!=`, `>`, `>=`, `<`, `<=`, `Like`, `NotLike`, `StartLike`, `EndLike`, `In`, `NotIn`
389
+
390
+ ## 注意事项
391
+
392
+ - `_Where` 是参数化查询,自动防 SQL 注入,**不要拼接 SQL 字符串**
393
+ - `AddFormData` 不需要传 `Id`,后端自动生成 GUID
394
+ - `UptFormData` 必须包含 `Id` 字段
395
+ - 如需触发表单 V8 事件,在参数中加 `_InvokeType: 'Client'`
396
+ - 返回值中 `Code: 1` 表示成功,`Code: 0` 表示失败,`Code: 2` 表示数据不存在
397
+ - 分页参数使用 `_PageIndex` 和 `_PageSize`(带下划线前缀)
398
+ - 列表返回总数字段为 `result.DataCount`(非 Total)
@@ -0,0 +1,279 @@
1
+ ---
2
+ name: v8-debugging
3
+ description: Microi V8 调试与日志指南。用于排查接口引擎、V8 事件、console.log、DataAppend.DebugLog、sys_log、异常处理和远程执行问题。
4
+ ---
5
+
6
+ # Microi V8 调试与日志
7
+
8
+ 你正在为 Microi 吾码平台编写 V8 引擎代码,需要在开发/测试/生产环境进行排错。本指南提供调试模式、异常捕获、系统日志、调试输出的标准做法。
9
+
10
+ ## 三种输出通道
11
+
12
+ | 通道 | 何处看 | 用途 |
13
+ |------|--------|------|
14
+ | 后端 V8 的 `console.log` / `console.error` | 租户系统日志(MongoDB);MCP/调试执行同时返回当次 `ConsoleOutput` | 临时诊断、性能日志 |
15
+ | `DataAppend.DebugLog`(返回给前端) | 浏览器开发者工具 / Postman 响应 | 当次请求的关键节点、变量值 |
16
+ | `V8.Method.AddSysLog({...})` | 系统日志页 / `sys_log` 表 | 业务操作审计、第三方回调记录 |
17
+
18
+ ## 调试模式 isDebugLog
19
+
20
+ 调试开关必须同时满足“服务端允许调试 + 当前用户 `Level >= 9999`”。不能只相信 URL/query 的 `isDebugLog=1`,匿名或普通用户否则可获得内部数据与堆栈。
21
+
22
+ ```javascript
23
+ var isAdmin = V8.CurrentUser && Number(V8.CurrentUser.Level || 0) >= 9999;
24
+ var isDebugLog = isAdmin &&
25
+ V8.SysConfig &&
26
+ V8.SysConfig.EnableV8Debug === 1 &&
27
+ (V8.Param.isDebugLog === '1' || V8.Param.isDebugLog === true);
28
+ var debugLog = [];
29
+ function dbg(msg, data) {
30
+ if (!isDebugLog) return;
31
+ debugLog.push({
32
+ time: DateNow('HH:mm:ss.fff'),
33
+ msg: msg,
34
+ data: data
35
+ });
36
+ }
37
+
38
+ dbg('开始查询参数', {
39
+ Keyword: String(V8.Param.Keyword || '').substring(0, 100),
40
+ PageIndex: V8.Param.PageIndex
41
+ });
42
+
43
+ var products = V8.FormEngine.GetTableData('Product', {
44
+ _Where: [['Status', '=', 1]]
45
+ });
46
+ dbg('查询结果', { Code: products.Code, Count: products.Data && products.Data.length });
47
+
48
+ // ... 业务处理 ...
49
+
50
+ return {
51
+ Code: 1,
52
+ Data: result,
53
+ DataAppend: isDebugLog ? { DebugLog: debugLog } : null
54
+ };
55
+ ```
56
+
57
+ 管理员在服务端显式开启调试后,才可用 `?isDebugLog=1` 查看当次请求的脱敏节点日志。生产环境默认关闭,调试完成后立即关闭。
58
+
59
+ ## Jint 内存上限错误
60
+
61
+ 出现 `Script has allocated ... but is limited to ...` 时,不要把它解释为
62
+ 数据库返回数据的实际大小,也不要直接关闭内存限制。
63
+
64
+ - Jint `LimitMemory` 使用当前执行线程的**累计托管分配字节数**,不是当前
65
+ 存活堆或进程工作集;中间对象即使已经被 GC 回收,累计值也不会减少。
66
+ - 平台必须在宿主对象、扩展和全局 V8 准备完成后调用
67
+ `engine.Constraints.Reset()`,让全局 V8 和当前接口 V8 各自获得完整预算;
68
+ 用户脚本以及脚本调用的 CLR/FormEngine 逻辑仍受同一阶段预算限制。
69
+ - 默认单次执行预算为 2048 MB,默认节点硬上限为 8192 MB;根调用树默认
70
+ 8192 MB、节点硬上限 32768 MB。存量接口仅把仍等于
71
+ 历史默认值 1024 MB 的记录迁移到 2048 MB;客户明确设置的其它值不覆盖。
72
+ - 旧版嵌套接口会让子引擎的初始化、查询、JSON 和业务分配被每一层父引擎
73
+ 重复计数,因此四层、少量数据也可能提前触发 2GB。新版默认启用父子单层
74
+ 预算隔离,每层独立计数,同时由根调用树默认 8192MB 的总预算兜底。
75
+ - `LimitRecursion` 只限制当前 JavaScript 函数递归,不限制
76
+ `V8.ApiEngine.Run` 层数;接口嵌套默认 32、节点硬上限默认 64。
77
+ - 排查时记录 `ApiEngineKey`、实际 `LimitMemory`、返回行数、选择字段、
78
+ 分页大小、`V8.Limits`、`DataAppend.V8Limit` 调用路径和重现脚本。若
79
+ 2048 MB 仍触发,继续减少一次性对象图、流式或
80
+ 分批处理;不得把上限无限提高或取消。
81
+ - `V8_MEMORY_LIMIT` 表示单层预算,`V8_CALL_TREE_MEMORY_LIMIT` 表示整棵
82
+ 调用树,`V8_STATEMENTS_LIMIT`、`V8_RECURSION_LIMIT`、`V8_TIMEOUT`、
83
+ `V8_NESTED_DEPTH_LIMIT` 和 `V8_EXECUTION_QUEUE_TIMEOUT` 必须分别处理。
84
+ - 后台任务总时长可以超过 10/30 分钟,但单片仍受上述预算;使用
85
+ `HasMore + Checkpoint` 续跑,不要只提高单次超时或内存。
86
+ - 若业务链必须在一个共享数据库事务中全部提交或全部回滚,分片会改变业务
87
+ 语义,可由管理员为对应 `sys_apiengine` 或 `diy_table` 开启
88
+ `V8Unlimited`。此时 `V8.Limits.UnlimitedRuntime=true`,只解除当前 Jint
89
+ 的超时、语句、函数递归、累计分配和 Promise 等待限制;常驻内存保护、
90
+ 外部取消、并发、接口嵌套深度、权限沙箱与数据库限制仍在。先排查数据库
91
+ 长事务锁、日志和回滚风险,下游接口/表事件需分别开启,不能从请求参数启用。
92
+ - 服务端另有一个内置受信任特例:主租户中由持久队列恢复的
93
+ `import-microi-store-package`:必须同时具备可信用户快照、`Level >= 9999`
94
+ 和 TaskId,才允许 `V8.Limits.MemoryAccounting=ResidentMemoryGuardOnly`。
95
+ 此模式不是“无限内存”,而是改用容器优先的进程 RSS 防线:95% 拒绝新工作、
96
+ 98% 有界停机;导入器仍必须按资产分片并可从 checkpoint 幂等恢复。前台调用、
97
+ 子租户或其它 Key 看到该模式都属于安全缺陷。
98
+ - 回归至少验证 Jint 包版本、约束重置、默认/硬上限,以及 400 行左右普通
99
+ FormEngine 查询与数据加工不会被平台准备阶段的累计分配误伤。
100
+
101
+ ## try/catch 异常捕获 + 详情上报
102
+
103
+ ```javascript
104
+ try {
105
+ var r = V8.Http.Post({
106
+ Url: 'https://api.partner.com/sync',
107
+ PostParam: {
108
+ Id: V8.Param.Id,
109
+ Action: V8.Param.Action
110
+ },
111
+ ParamType: 'json',
112
+ Timeout: 30
113
+ });
114
+ if (!r || r.indexOf('"code":0') === 0) {
115
+ throw new Error('合作方接口失败:' + r);
116
+ }
117
+ return { Code: 1, Data: JSON.parse(r) };
118
+ } catch (ex) {
119
+ // 完整异常信息
120
+ var traceId = V8.Method.NewUlid();
121
+ var errorDetails = {
122
+ traceId: traceId,
123
+ message: ex.message,
124
+ stack: ex.stack,
125
+ line: ex.lineNumber,
126
+ column: ex.columnNumber,
127
+ fileName: ex.fileName,
128
+ when: DateNow('yyyy-MM-dd HH:mm:ss')
129
+ };
130
+ console.error('SyncError', JSON.stringify(errorDetails));
131
+
132
+ V8.Method.AddSysLog({
133
+ Type: 'IntegrationError',
134
+ Title: '合作方同步失败',
135
+ Content: JSON.stringify(errorDetails),
136
+ Level: 3 // 1=Info / 2=Warn / 3=Error
137
+ });
138
+
139
+ return {
140
+ Code: 0,
141
+ Msg: '同步失败,请按追踪号查询日志',
142
+ DataAppend: isDebugLog ? { TraceId: traceId } : null
143
+ };
144
+ }
145
+ ```
146
+
147
+ ## V8.Method.AddSysLog — 业务审计日志
148
+
149
+ ```javascript
150
+ V8.Method.AddSysLog({
151
+ Type: 'OrderCreate', // 自定义类型,便于过滤
152
+ Title: '客户【' + V8.Form.CustomerName + '】下单',
153
+ Content: JSON.stringify({
154
+ OrderId: V8.Form.Id,
155
+ Amount: V8.Form.TotalAmount,
156
+ UserId: V8.CurrentUser.Id
157
+ }),
158
+ Level: 1 // 1=Info, 2=Warn, 3=Error
159
+ });
160
+ ```
161
+
162
+ 日志保存在按租户、月份拆分的 MongoDB 系统日志集合中,可在系统日志菜单查看并按 Type 过滤。所有 `AddSysLog` 调用统一进入后端异步队列,由后台批量写 MongoDB;批次先写本地 spool,MongoDB 故障和正常重启后自动幂等重放,因此业务请求不得自行启动线程或直接并发写 MongoDB。
163
+
164
+ - 容器环境把固定目录 `logs/syslog-spool` 挂载到持久卷,不为路径增加环境变量。
165
+ - 分布式部署的节点标识由平台自动生成;所有节点写同一 MongoDB 时仍按全局 `EventId` upsert。详情状态和私有文件票据存共享 Redis,本机内存只作故障兜底,不能依赖粘性会话。
166
+ - 多节点可能同时观察到的菜单、详情关闭、附件分片和登录生命周期事件要使用确定性 `EventId`,不能只靠本机字典去重;服务正常停止会落盘排空,重启会自动重放。
167
+ - 平台用户行为统一使用结构化字段 `Category`、`Action`、`Source`、`TargetType`、`TargetId`、`SessionId`、`DurationSeconds`、`Success`、`OccurredAt`。
168
+ - 用户显示采用 `Name(Account)`;禁止记录密码、原始 Token、Authorization、Secret、ApiKey、连接字符串,内容还必须限长。
169
+ - 前端只能上报纯 UI 行为信号;菜单访问、CRUD、导入导出、登录生命周期、私有文件实际访问等必须以后端真实执行点为事实源。
170
+
171
+ ### 匿名登录/第三方授权接口的追踪日志
172
+
173
+ 微信手机号、OAuth、短信快捷登录等匿名接口必须生成追踪号并按阶段记录失败。开发者工具成功不能替代体验版/真机验证。
174
+
175
+ ```javascript
176
+ var traceId = V8.Method.NewGuid().replace(/-/g, '').substring(0, 16);
177
+
178
+ function fail(stage, message, detail) {
179
+ V8.Method.AddSysLog({
180
+ Type: 'ThirdPartyLogin',
181
+ Title: '授权登录失败[' + stage + '][' + traceId + ']',
182
+ Content: JSON.stringify({
183
+ TraceId: traceId,
184
+ Stage: stage,
185
+ Message: message,
186
+ Detail: detail || {},
187
+ OsClient: V8.OsClient
188
+ }),
189
+ Remark: traceId,
190
+ Level: 3
191
+ });
192
+ return { Code: 0, Msg: '授权登录失败(' + stage + '):' + message + ';追踪号:' + traceId };
193
+ }
194
+ ```
195
+
196
+ - 在身份交换、AccessToken、手机号/用户资料交换、账号匹配、注册/更新、Token 签发前维护明确阶段名,所有显式失败和顶层 `catch` 都走同一个 `fail`。
197
+ - 日志保留第三方 `errcode/errmsg`,但先脱敏;禁止记录 Secret、AccessToken、授权 code、LoginCode、OpenId、完整手机号和用户 Token。
198
+ - `V8.Method.AddSysLog` 写 MongoDB 系统日志,适合 `Code=0` 仍需保留的结构化失败记录;`console.error` 也按当前 `OsClient` 进入 MongoDB,但不替代带追踪号的业务审计日志。
199
+ - 前端响应显示同一个追踪号,排查时先按追踪号查系统日志,再结合第三方错误码定位。
200
+
201
+ ## 后端 V8 console 的去向
202
+
203
+ - 普通接口引擎和表单后端 V8 的 `console.log/info/warn/error` 按租户写入 MongoDB 系统日志,`Source/TargetType` 为 `V8`,可按接口 Key 或事件名定位。
204
+ - MCP 远程执行和 V8 调试会通过请求级上下文捕获当次输出并返回 `ConsoleOutput`;不得用进程级 `Console.SetOut`,否则并发请求会互相截取日志。
205
+ - Docker/服务器控制台只保留影响平台启动、主租户、日志管道或进程存活的关键日志,不再作为普通 V8 调试日志入口。
206
+ - 前端 V8 的 `console.log` 仍在浏览器开发者工具中查看,与后端日志通道无关。
207
+
208
+ ## 性能跟踪(毫秒级耗时)
209
+
210
+ ```javascript
211
+ var t0 = Date.now();
212
+
213
+ var step1 = V8.FormEngine.GetTableData('Big', { _PageSize: 5000 });
214
+ var t1 = Date.now();
215
+
216
+ var step2 = V8.Db.FromSql('SELECT COUNT(*) FROM Order').ToScalar();
217
+ var t2 = Date.now();
218
+
219
+ dbg('耗时', { step1Ms: t1 - t0, step2Ms: t2 - t1, totalMs: t2 - t0 });
220
+ ```
221
+
222
+ ## 前端调试
223
+
224
+ ```javascript
225
+ // 前端 V8 事件中
226
+ console.log('当前表单', V8.Form);
227
+ console.warn('弃用字段被使用');
228
+ console.error('校验失败', V8.Form);
229
+
230
+ // 在浏览器开发者工具控制台查看
231
+ // 或显式弹层:
232
+ V8.Tips('调试: ' + JSON.stringify(V8.Form), true);
233
+ ```
234
+
235
+ ## VS Code 插件输出约定
236
+
237
+ Microi VS Code 插件的右下角信息、警告和错误通知必须同步写入【输出 → Microi 吾码】,让构建、推送、拉取、登录、同步和调试结果在通知消失后仍可追溯。输出时间使用运行 VS Code 电脑的本地时区,格式统一为 `yyyy-MM-dd HH:mm:ss`,禁止直接使用 `toISOString()` 造成 UTC 时间偏差。
238
+
239
+ - 后台 Token/会话维护等静默健康探测设置 `silent=true` 后,预期的网络不可达不得反复写成红色错误。
240
+ - 用户主动执行拉取、推送或状态检查时,网络错误必须保留错误码、系统调用、地址、端口及聚合错误明细;禁止出现只有 `Error:`、没有原因的空日志。
241
+ - 所有右下角通知统一经过通知封装写入输出;带操作按钮的通知还应记录用户选择,禁止业务模块直接调用 `vscode.window.showInformationMessage/showWarningMessage/showErrorMessage` 绕过持久日志。
242
+
243
+ ## 不要在生产泄漏敏感信息
244
+
245
+ ```javascript
246
+ // ❌ 危险:返回给前端
247
+ return { Code: 0, Msg: ex.message, DataAppend: { Stack: ex.stack } };
248
+
249
+ // ✅ 只返回关联ID;完整堆栈仅写内部日志
250
+ var traceId = V8.Method.NewUlid();
251
+ console.error('traceId=' + traceId + ' error=' + ex.message);
252
+ return {
253
+ Code: 0,
254
+ Msg: '系统繁忙',
255
+ DataAppend: { TraceId: traceId }
256
+ };
257
+ ```
258
+
259
+ ## DosResult 状态码速查
260
+
261
+ | Code | 含义 |
262
+ |------|------|
263
+ | `1` | 成功 |
264
+ | `0` | 业务失败(接口引擎中 return 此 Code 自动回滚事务)|
265
+ | `2` | `GetFormData` 数据不存在(特殊:仍是查询正常,只是无数据)|
266
+ | `1001` | Token 已失效 |
267
+ | `1002` | 身份验证失败 |
268
+ | 其它非 1 | 视为失败,自动回滚 |
269
+
270
+ 调试时遇到 `Code != 1` 优先看 `Msg`、再开 `isDebugLog=1` 排查。
271
+
272
+ ## 检查清单
273
+
274
+ - [ ] 接口引擎是否支持 `isDebugLog` 参数?
275
+ - [ ] 关键节点是否调用 `dbg()`?
276
+ - [ ] 第三方调用是否 `try/catch` + `AddSysLog`?
277
+ - [ ] 异常 stack 仅在 debug 模式下返回前端?
278
+ - [ ] 业务审计是否 `AddSysLog`(Level 1/2/3)?
279
+ - [ ] 性能瓶颈是否打 `TickCount` 耗时?