@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.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/mcp-codex-stdio-adapter.js +189 -0
- package/dist/mcp-server.js +972 -0
- package/dist/mcp-trae-windows-launcher.cmd +21 -0
- package/dist/microi-cli-mcp.js +7 -0
- package/dist/microi-cli.js +1645 -0
- package/dist/microi-skills.meta.json +335 -0
- package/dist/microi.skills/.microi-skills-version.json +6 -0
- package/dist/microi.skills/README.md +276 -0
- package/dist/microi.skills/ai-engine/SKILL.md +140 -0
- package/dist/microi.skills/ai-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/app-store/SKILL.md +105 -0
- package/dist/microi.skills/app-store/agents/openai.yaml +4 -0
- package/dist/microi.skills/business-blueprint/SKILL.md +184 -0
- package/dist/microi.skills/datasource-engine/SKILL.md +89 -0
- package/dist/microi.skills/datasource-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/dos-orm/SKILL.md +76 -0
- package/dist/microi.skills/dos-orm/references/api-reference.md +229 -0
- package/dist/microi.skills/job-engine/SKILL.md +141 -0
- package/dist/microi.skills/job-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/message-notification/SKILL.md +113 -0
- package/dist/microi.skills/message-notification/agents/openai.yaml +6 -0
- package/dist/microi.skills/message-notification/references/contracts.md +99 -0
- package/dist/microi.skills/microi-ai-app-auth.js +651 -0
- package/dist/microi.skills/microi-ai-application/SKILL.md +80 -0
- package/dist/microi.skills/microi-ai-application/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-ai-application/references/frontend-baseline.md +164 -0
- package/dist/microi.skills/microi-client-frontend/SKILL.md +562 -0
- package/dist/microi.skills/microi-datasource-mapping/SKILL.md +108 -0
- package/dist/microi.skills/microi-db-schema/SKILL.md +170 -0
- package/dist/microi.skills/microi-db-schema/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-db-schema/references/core-tables.md +695 -0
- package/dist/microi.skills/microi-db-schema/references/form-component-options.md +256 -0
- package/dist/microi.skills/microi-db-schema/references/schema-overview.md +203 -0
- package/dist/microi.skills/microi-db-schema/references/schema.md +647 -0
- package/dist/microi.skills/microi-db-schema/references/table-catalog.md +1607 -0
- package/dist/microi.skills/microi-deployment/SKILL.md +117 -0
- package/dist/microi.skills/microi-deployment/references/deployment-matrix.md +94 -0
- package/dist/microi.skills/microi-docs-coverage/SKILL.md +91 -0
- package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +65 -0
- package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +887 -0
- package/dist/microi.skills/microi-form-engine/SKILL.md +159 -0
- package/dist/microi.skills/microi-form-engine/references/component-catalog.md +116 -0
- package/dist/microi.skills/microi-form-engine/references/data-source-events.md +117 -0
- package/dist/microi.skills/microi-form-layout/SKILL.md +373 -0
- package/dist/microi.skills/microi-frontend-sdk/SKILL.md +304 -0
- package/dist/microi.skills/microi-left-right-layout/SKILL.md +132 -0
- package/dist/microi.skills/microi-microservice/SKILL.md +115 -0
- package/dist/microi.skills/microi-microservice/references/runtime-delivery.md +145 -0
- package/dist/microi.skills/microi-mobile-app-quality/SKILL.md +436 -0
- package/dist/microi.skills/microi-solution-quotation/SKILL.md +76 -0
- package/dist/microi.skills/microi-solution-quotation/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -0
- package/dist/microi.skills/microi-system-delivery/SKILL.md +446 -0
- package/dist/microi.skills/microi-ui/SKILL.md +321 -0
- package/dist/microi.skills/microi-uniapp-frontend/SKILL.md +483 -0
- package/dist/microi.skills/microi.v8.js +1758 -0
- package/dist/microi.skills/module-engine/SKILL.md +131 -0
- package/dist/microi.skills/module-engine/references/module-config.md +174 -0
- package/dist/microi.skills/page-engine/SKILL.md +397 -0
- package/dist/microi.skills/performance-testing/SKILL.md +207 -0
- package/dist/microi.skills/playwright-e2e/SKILL.md +769 -0
- package/dist/microi.skills/print-engine/SKILL.md +237 -0
- package/dist/microi.skills/production-readonly-audit/SKILL.md +39 -0
- package/dist/microi.skills/report-engine/SKILL.md +69 -0
- package/dist/microi.skills/report-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/search-engine/SKILL.md +73 -0
- package/dist/microi.skills/search-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/spider-engine/SKILL.md +188 -0
- package/dist/microi.skills/translate-engine/SKILL.md +91 -0
- package/dist/microi.skills/translate-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/ui-design/SKILL.md +1575 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/app.js +54 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/index.html +163 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/styles.css +311 -0
- package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +98 -0
- package/dist/microi.skills/ui-design/references/design-pattern-library.md +171 -0
- package/dist/microi.skills/ui-design/references/mci-design-contract.md +84 -0
- package/dist/microi.skills/ui-design/references/motion-and-media.md +71 -0
- package/dist/microi.skills/ui-design/references/product-flow-recipes.md +94 -0
- package/dist/microi.skills/uniapp-mall-assets/SKILL.md +105 -0
- package/dist/microi.skills/v8-api-config/SKILL.md +272 -0
- package/dist/microi.skills/v8-cache-pattern/SKILL.md +286 -0
- package/dist/microi.skills/v8-crud-api/SKILL.md +398 -0
- package/dist/microi.skills/v8-debugging/SKILL.md +279 -0
- package/dist/microi.skills/v8-explorer-tree/SKILL.md +224 -0
- package/dist/microi.skills/v8-export-import/SKILL.md +590 -0
- package/dist/microi.skills/v8-file-upload/SKILL.md +497 -0
- package/dist/microi.skills/v8-formengine-http/SKILL.md +218 -0
- package/dist/microi.skills/v8-frontend-events/SKILL.md +349 -0
- package/dist/microi.skills/v8-frontend-events/references/bluetooth-print-api.md +107 -0
- package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +185 -0
- package/dist/microi.skills/v8-http-integration/SKILL.md +379 -0
- package/dist/microi.skills/v8-image-processing/SKILL.md +187 -0
- package/dist/microi.skills/v8-image-processing/agents/openai.yaml +4 -0
- package/dist/microi.skills/v8-image-processing/references/api-reference.md +620 -0
- package/dist/microi.skills/v8-menu-buttons/SKILL.md +661 -0
- package/dist/microi.skills/v8-mongodb/SKILL.md +149 -0
- package/dist/microi.skills/v8-mq-mqtt/SKILL.md +227 -0
- package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +193 -0
- package/dist/microi.skills/v8-security/SKILL.md +417 -0
- package/dist/microi.skills/v8-sql-query/SKILL.md +290 -0
- package/dist/microi.skills/v8-table-event/SKILL.md +385 -0
- package/dist/microi.skills/v8-template-engine/SKILL.md +165 -0
- package/dist/microi.skills/v8-utilities/SKILL.md +79 -0
- package/dist/microi.skills/v8-utilities/references/client-api-index.md +136 -0
- package/dist/microi.skills/v8-utilities/references/platform-http-routes.md +80 -0
- package/dist/microi.skills/v8-utilities/references/server-api-index.md +129 -0
- package/dist/microi.skills/v8-workflow/SKILL.md +322 -0
- package/dist/microi.skills/workspace-conventions/SKILL.md +479 -0
- package/package.json +40 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: v8-mongodb
|
|
3
|
+
description: Microi V8 MongoDB 指南。用于使用 V8.MongoDb AddFormData、UptFormData、DelFormData、GetFormData、GetTableData、对象过滤和文档 Id。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Microi V8 MongoDB 操作
|
|
7
|
+
|
|
8
|
+
你正在开发 Microi 吾码平台的 V8 引擎代码,需要使用 MongoDB 存储非结构化数据(如日志、IoT 数据、大文档等)。
|
|
9
|
+
|
|
10
|
+
## V8.MongoDb API
|
|
11
|
+
|
|
12
|
+
| 方法 | 说明 |
|
|
13
|
+
|------|------|
|
|
14
|
+
| `V8.MongoDb.AddFormData({...})` | 新增文档 |
|
|
15
|
+
| `V8.MongoDb.UptFormData({...})` | 修改文档(按 Id) |
|
|
16
|
+
| `V8.MongoDb.DelFormData({...})` | 删除文档(按 Id) |
|
|
17
|
+
| `V8.MongoDb.GetFormData({...})` | 查询单个文档(按 Id) |
|
|
18
|
+
| `V8.MongoDb.GetTableData({...})` | 查询文档列表 |
|
|
19
|
+
| `V8.MongoDb.NewId()` | 生成 MongoDB Id |
|
|
20
|
+
|
|
21
|
+
## 新增文档
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
var newId = V8.MongoDb.NewId();
|
|
25
|
+
V8.MongoDb.AddFormData({
|
|
26
|
+
DbName: 'sys_log_2024', // 数据库名
|
|
27
|
+
TableName: 'log_2024_12', // 集合(表)名
|
|
28
|
+
Id: newId, // 可选,不指定自动生成
|
|
29
|
+
_FormData: {
|
|
30
|
+
UserId: V8.CurrentUser.Id,
|
|
31
|
+
Action: '登录',
|
|
32
|
+
IP: '192.168.1.1',
|
|
33
|
+
CreateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 修改文档
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
V8.MongoDb.UptFormData({
|
|
42
|
+
DbName: 'sys_log_2024',
|
|
43
|
+
TableName: 'log_2024_12',
|
|
44
|
+
Id: V8.Param.id, // 必传
|
|
45
|
+
_FormData: {
|
|
46
|
+
Action: '更新操作',
|
|
47
|
+
UpdateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 删除文档
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
V8.MongoDb.DelFormData({
|
|
56
|
+
DbName: 'sys_log_2024',
|
|
57
|
+
TableName: 'log_2024_12',
|
|
58
|
+
Id: V8.Param.id // 必传
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 查询单个文档
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
var result = V8.MongoDb.GetFormData({
|
|
66
|
+
DbName: 'sys_log_2024',
|
|
67
|
+
TableName: 'log_2024_12',
|
|
68
|
+
Id: V8.Param.id // 必传
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 查询文档列表
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
var result = V8.MongoDb.GetTableData({
|
|
76
|
+
DbName: 'sys_log_2024',
|
|
77
|
+
TableName: 'log_2024_12',
|
|
78
|
+
_Where: [
|
|
79
|
+
['Type', '=', '访问菜单'],
|
|
80
|
+
['OR', 'Type', '=', '点击V8按钮']
|
|
81
|
+
]
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 实战模式
|
|
86
|
+
|
|
87
|
+
### IoT 设备日志存储
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// 接收 MQTT 消息后存入 MongoDB
|
|
91
|
+
var eventName = V8.EventName;
|
|
92
|
+
if (eventName === 'MessageReceived') {
|
|
93
|
+
V8.MongoDb.AddFormData({
|
|
94
|
+
DbName: 'iot_data',
|
|
95
|
+
TableName: 'device_log_' + DateNow('yyyy_MM'),
|
|
96
|
+
_FormData: {
|
|
97
|
+
DeviceId: V8.MQTT.ClientId,
|
|
98
|
+
Topic: V8.MQTT.Topic,
|
|
99
|
+
Payload: V8.MQTT.Payload,
|
|
100
|
+
Timestamp: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 操作审计日志
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// 在 SubmitAfterServerV8.js 中记录审计日志到 MongoDB
|
|
110
|
+
V8.MongoDb.AddFormData({
|
|
111
|
+
DbName: 'audit_log',
|
|
112
|
+
TableName: 'form_audit_' + DateNow('yyyy'),
|
|
113
|
+
_FormData: {
|
|
114
|
+
TableName: V8.TableModel.Name,
|
|
115
|
+
Action: V8.FormSubmitAction,
|
|
116
|
+
DataId: V8.Form.Id,
|
|
117
|
+
UserId: V8.CurrentUser.Id,
|
|
118
|
+
UserName: V8.CurrentUser.Name,
|
|
119
|
+
OldData: V8.FormSubmitAction === 'Update' ? JSON.stringify(V8.OldForm) : null,
|
|
120
|
+
NewData: JSON.stringify(V8.Form),
|
|
121
|
+
CreateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 按月分表查询
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// 查询指定月份的日志
|
|
130
|
+
var month = V8.Param.month || DateNow('yyyy_MM');
|
|
131
|
+
var result = V8.MongoDb.GetTableData({
|
|
132
|
+
DbName: 'sys_log_2024',
|
|
133
|
+
TableName: 'log_' + month,
|
|
134
|
+
_Where: [
|
|
135
|
+
['UserId', '=', V8.CurrentUser.Id]
|
|
136
|
+
]
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return { Code: 1, Data: result };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 注意事项
|
|
143
|
+
|
|
144
|
+
- MongoDB 参数统一使用**对象格式**:`{ DbName, TableName, Id, _FormData, _Where }`
|
|
145
|
+
- `DbName` 是 MongoDB 数据库名,`TableName` 是集合名
|
|
146
|
+
- `_Where` 条件语法与 `V8.FormEngine` 一致
|
|
147
|
+
- 适合存储日志、IoT 数据、大文档等非结构化 / 海量数据
|
|
148
|
+
- 建议按时间分库分表(如 `log_2024_01`),便于清理历史数据
|
|
149
|
+
- MongoDB 操作不参与 `V8.DbTrans` 事务
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: v8-mq-mqtt
|
|
3
|
+
description: Microi V8 消息队列与 MQTT 指南。用于使用 V8.MQ.SendMsg、RabbitMQ 队列、MQTT 事件处理、主题、载荷、客户端 Id 和异步集成。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Microi V8 消息队列与 MQTT
|
|
7
|
+
|
|
8
|
+
你正在开发 Microi 吾码平台的 V8 引擎代码,需要使用 RabbitMQ 消息队列或 MQTT 物联网协议。
|
|
9
|
+
|
|
10
|
+
## V8.MQ — RabbitMQ 消息队列
|
|
11
|
+
|
|
12
|
+
### 生产消息(后端)
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
// 在 async 接口引擎或 V8 事件中发送消息。
|
|
16
|
+
// 业务重试必须复用同一个 EventId,供消费者幂等去重。
|
|
17
|
+
var result = await V8.MQ.SendMsg({
|
|
18
|
+
QueueName: 'order_process',
|
|
19
|
+
EventId: V8.Param.eventId || V8.Method.NewUlid(),
|
|
20
|
+
Message: {
|
|
21
|
+
ProductId: '123',
|
|
22
|
+
Count: 2,
|
|
23
|
+
OrderId: V8.Param.orderId
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
if (result.Code !== 1) return result;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
逻辑队列名由服务端规范为 `microi.{lowerOsClient}.{queueName}`。V8 上下文、登录 Token 与后台显式 `OsClient` 是权威租户,body 不能切换到其它租户队列。
|
|
30
|
+
|
|
31
|
+
### 生产消息(前端)
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
V8.Post('/api/mq/sendmsg', {
|
|
35
|
+
QueueName: 'queue_name',
|
|
36
|
+
EventId: stableEventId,
|
|
37
|
+
Message: { ProductId: '123', Count: 2 }
|
|
38
|
+
}, function(result) {
|
|
39
|
+
if (result.Code === 1) V8.Tips('消息已发送', true);
|
|
40
|
+
}, null, {}, 'json');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 消费消息
|
|
44
|
+
|
|
45
|
+
消费者是一个接口引擎,在 `diy_queue_receive` 表中配置队列名和接口引擎 Key 后,消息到达时自动调用。
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// 消费者接口引擎
|
|
49
|
+
var message = V8.Param.Message; // object 类型
|
|
50
|
+
// message.EventId — 稳定业务幂等 Id
|
|
51
|
+
// message.Id — EventId 的兼容别名
|
|
52
|
+
// message.OsClient — 消息所属租户
|
|
53
|
+
// message.Message — 消息内容
|
|
54
|
+
// message.CurrentUserId — 生产消息的用户 Id
|
|
55
|
+
|
|
56
|
+
// 处理业务逻辑
|
|
57
|
+
var data = message.Message;
|
|
58
|
+
V8.FormEngine.UptFormData('Product', {
|
|
59
|
+
Id: data.ProductId,
|
|
60
|
+
Stock: data.Count
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 实战模式:异步处理耗时操作
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// 接口引擎:接收请求后发送到队列,快速返回
|
|
68
|
+
await V8.MQ.SendMsg({
|
|
69
|
+
QueueName: 'order_process',
|
|
70
|
+
EventId: V8.Param.eventId,
|
|
71
|
+
Message: {
|
|
72
|
+
orderId: V8.Param.orderId,
|
|
73
|
+
action: 'create',
|
|
74
|
+
userId: V8.CurrentUser.Id
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { Code: 1, Msg: '订单处理中,请稍候查看结果' };
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// 消费者接口引擎:异步处理订单
|
|
83
|
+
var msg = V8.Param.Message;
|
|
84
|
+
var data = msg.Message;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// 耗时操作:调用第三方 ERP 接口
|
|
88
|
+
var erpResult = V8.Http.Post({
|
|
89
|
+
Url: 'https://erp.company.com/api/order',
|
|
90
|
+
PostParamString: JSON.stringify({ orderId: data.orderId }),
|
|
91
|
+
ParamType: 'json',
|
|
92
|
+
Timeout: 30
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
V8.FormEngine.UptFormData('Order', {
|
|
96
|
+
Id: data.orderId,
|
|
97
|
+
SyncStatus: 'success',
|
|
98
|
+
SyncTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
99
|
+
});
|
|
100
|
+
} catch (ex) {
|
|
101
|
+
V8.FormEngine.UptFormData('Order', {
|
|
102
|
+
Id: data.orderId,
|
|
103
|
+
SyncStatus: 'failed',
|
|
104
|
+
SyncError: ex.message
|
|
105
|
+
});
|
|
106
|
+
console.error('订单同步失败: ' + ex.message);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### MQ 配置
|
|
111
|
+
|
|
112
|
+
主租户提供共享 Broker 地址 `MQHost/MQPort`。每个子租户必须在 RabbitMQ 中真实创建独立的 `MQUserName/MQPassword/MQVitrualHost`,并把权限限制在自己的 vhost 与 `microi.{osClient}.*` 队列;缺少凭据或与其它租户共用 user/password/vhost 时失败关闭,不回退主租户管理员账号。
|
|
113
|
+
|
|
114
|
+
在 `diy_queue_receive` 表新增记录后,平台启动时自动订阅:
|
|
115
|
+
|
|
116
|
+
| 字段 | 含义 |
|
|
117
|
+
|------|------|
|
|
118
|
+
| `Type` | `接口引擎`(固定) |
|
|
119
|
+
| `QueueName` | 逻辑队列名(与生产端 `SendMsg({ QueueName, ... })` 一致) |
|
|
120
|
+
| `ApiEngineKey` | 消费者接口引擎 Key |
|
|
121
|
+
| `IsEnable` | 是否启用 |
|
|
122
|
+
| `OsClient` | 所属租户(多租户隔离) |
|
|
123
|
+
|
|
124
|
+
> ⚠️ 修改 `diy_queue_receive` 后需重启平台才会生效订阅。
|
|
125
|
+
|
|
126
|
+
多节点会对同一租户队列使用 RabbitMQ competing consumer,但“只有一个节点收到”不等于业务只执行一次。消息 envelope 的 `EventId` 是稳定幂等键,消费者必须配合数据库唯一约束、inbox/条件更新保证副作用仅一次;连接凭据轮换后当前版本需要重启节点重建连接。
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## V8.MQTT — IoT 物联网
|
|
131
|
+
|
|
132
|
+
### MQTT 事件类型
|
|
133
|
+
|
|
134
|
+
MQTT 通过一个接口引擎处理所有事件,通过 `V8.EventName` 判断当前事件类型:
|
|
135
|
+
|
|
136
|
+
| V8.EventName | 说明 |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `StartServer` | MQTT 服务启动 |
|
|
139
|
+
| `Connected` | 客户端连接 |
|
|
140
|
+
| `Disconnected` | 客户端断开连接 |
|
|
141
|
+
| `MessageReceived` | 收到客户端消息 |
|
|
142
|
+
| `StopServer` | MQTT 服务停止 |
|
|
143
|
+
|
|
144
|
+
### V8.MQTT 上下文
|
|
145
|
+
|
|
146
|
+
| 属性 | 说明 |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `V8.MQTT.ClientId` | 客户端 Id |
|
|
149
|
+
| `V8.MQTT.Topic` | 消息主题 |
|
|
150
|
+
| `V8.MQTT.Payload` | 消息内容(在 MessageReceived 事件中) |
|
|
151
|
+
|
|
152
|
+
子租户必须 `MqttEnable=1` 并配置独立 `MqttAccount/MqttPwd`。子租户不能通过 `MqttAllowAnonymous=1` 或 `MqttTopicIsolation=0` 关闭边界;缺少完整凭据时拒绝连接。Topic 统一为 `tenant/{lowerOsClient}/{businessTopic}`,服务端 publish、subscribe、retained、ResponseTopic 都会校验并拒绝其它租户、系统 Topic 和共享订阅绕过。
|
|
153
|
+
|
|
154
|
+
### 完整示例
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
var eventName = V8.EventName;
|
|
158
|
+
|
|
159
|
+
if (eventName === 'StartServer') {
|
|
160
|
+
console.log('MQTT 服务已启动');
|
|
161
|
+
|
|
162
|
+
} else if (eventName === 'Connected') {
|
|
163
|
+
console.log('设备已连接: ' + V8.MQTT.ClientId);
|
|
164
|
+
// 记录设备在线状态
|
|
165
|
+
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
166
|
+
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
167
|
+
OnlineStatus: 1,
|
|
168
|
+
LastOnlineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
} else if (eventName === 'Disconnected') {
|
|
172
|
+
console.log('设备已断开: ' + V8.MQTT.ClientId);
|
|
173
|
+
V8.FormEngine.UptFormDataByWhere('Device', {
|
|
174
|
+
_Where: [['DeviceCode', '=', V8.MQTT.ClientId]],
|
|
175
|
+
OnlineStatus: 0,
|
|
176
|
+
LastOfflineTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
} else if (eventName === 'MessageReceived') {
|
|
180
|
+
// 处理设备上报的数据
|
|
181
|
+
var clientId = V8.MQTT.ClientId;
|
|
182
|
+
var topic = V8.MQTT.Topic;
|
|
183
|
+
var payload = V8.MQTT.Payload;
|
|
184
|
+
|
|
185
|
+
console.log('收到消息: ' + clientId + ' - ' + topic);
|
|
186
|
+
|
|
187
|
+
// 存储到 MongoDB(适合海量数据)
|
|
188
|
+
V8.MongoDb.AddFormData({
|
|
189
|
+
DbName: 'iot_data',
|
|
190
|
+
TableName: 'device_msg_' + DateNow('yyyy_MM'),
|
|
191
|
+
_FormData: {
|
|
192
|
+
DeviceId: clientId,
|
|
193
|
+
Topic: topic,
|
|
194
|
+
Payload: payload,
|
|
195
|
+
CreateTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// 解析特定主题的数据
|
|
200
|
+
var temperatureTopic = 'tenant/' + V8.OsClient.toLowerCase() + '/sensor/temperature';
|
|
201
|
+
if (topic === temperatureTopic) {
|
|
202
|
+
var temp = parseFloat(payload);
|
|
203
|
+
if (temp > 80) {
|
|
204
|
+
// 温度报警
|
|
205
|
+
V8.ApiEngine.Run('send-alarm', {
|
|
206
|
+
deviceId: clientId,
|
|
207
|
+
type: 'temperature',
|
|
208
|
+
value: temp
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
} else if (eventName === 'StopServer') {
|
|
214
|
+
console.log('MQTT 服务已停止');
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 注意事项
|
|
219
|
+
|
|
220
|
+
- MQ 消费者接口引擎通过 `V8.Param.Message` 获取消息,包含 `EventId`、兼容 `Id`、`OsClient`、`Message`、`CurrentUserId`
|
|
221
|
+
- MQ 适合异步解耦、削峰填谷、耗时操作异步化
|
|
222
|
+
- MQTT 所有事件在同一个接口引擎中处理,通过 `V8.EventName` 区分
|
|
223
|
+
- MQTT 适合 IoT 设备管理、实时数据采集
|
|
224
|
+
- 海量 MQTT 数据建议存入 MongoDB 而非 MySQL
|
|
225
|
+
- MQ 和 MQTT 的租户凭据在 SaaS 引擎中管理,但必须先在真实 Broker 创建对应资源,不能只写数据库字段
|
|
226
|
+
- `ConnectedClients` 只代表当前 MQTT 节点的诊断快照,不是集群全局在线事实
|
|
227
|
+
- 内嵌 MQTT Broker 不具备跨节点共享会话/订阅/retained 的集群一致性;多 API 节点生产部署应使用支持集群的外部 Broker,或把内嵌 Broker 固定到独立节点并由负载入口路由,不能让每个 API 节点各自充当一套独立 Broker
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: v8-saas-multi-tenant
|
|
3
|
+
description: Microi V8 SaaS 多租户指南。用于处理 OsClient、OsClientType、OsClientNetwork、租户配置、V8.OsClientModel、隔离和租户感知代码。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Microi V8 SaaS 多租户引擎
|
|
7
|
+
|
|
8
|
+
你正在为 Microi 吾码平台编写多租户(SaaS)相关代码。每个租户使用独立数据库账号;Redis、对象存储、RabbitMQ、MQTT 和搜索集群可以共享基础设施,但必须使用租户命名空间与独立服务凭据,**所有 V8 代码都在租户上下文中运行**。
|
|
9
|
+
|
|
10
|
+
## 核心概念
|
|
11
|
+
|
|
12
|
+
Microi 多租户 = **`OsClient` + `OsClientType` + `OsClientNetwork`** 三参数模型:
|
|
13
|
+
|
|
14
|
+
| 参数 | 说明 | 示例 |
|
|
15
|
+
|------|------|------|
|
|
16
|
+
| `OsClient` | 租户标识(系统Key) | `tenant_a`, `tenant_demo` |
|
|
17
|
+
| `OsClientType` | 租户类型 | `Normal` / `App` / `Wechat` |
|
|
18
|
+
| `OsClientNetwork` | 网络环境 | `Intranet`(内网)/`Outernet`(外网) |
|
|
19
|
+
|
|
20
|
+
主租户不是固定字符串 `master`,而是由当前部署的环境变量 `OsClient` 或 `AppSettings:OsClient` 决定。租户记录存放在受保护的 `sys_osclients` 表中;普通业务角色和普通 V8 不得直接查询、复制或修改该表。
|
|
21
|
+
|
|
22
|
+
## 上下文变量
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
V8.OsClient // 当前租户标识,如 'tenant_demo'
|
|
26
|
+
V8.OsClientType // 'Normal' / 'App' / 'Wechat'
|
|
27
|
+
V8.OsClientNetwork // 'Intranet' / 'Outernet'
|
|
28
|
+
V8.OsClientModel // 当前租户 SaaS 配置的脱敏副本
|
|
29
|
+
V8.ClientModel // OsClientModel 的兼容别名,同样是脱敏副本
|
|
30
|
+
V8.SysConfig // 当前租户 sys_config 的独立脱敏副本
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`V8.SysConfig` 与 `V8.FormEngine.GetSysConfig(...)` 不暴露 `ClientSecrets`、`PwdV8`、`GlobalServerV8Code` 或疑似凭据字段;子租户显式传其它 `OsClient` 也会被强制改回当前租户。
|
|
34
|
+
|
|
35
|
+
### V8.OsClientModel 常用字段
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
V8.OsClientModel.SysTitle // 租户系统标题
|
|
39
|
+
V8.OsClientModel.DbType // 非敏感数据库类型
|
|
40
|
+
V8.OsClientModel.HDFS // 'Aliyun' / 'MinIO' / 'S3'
|
|
41
|
+
V8.OsClientModel.AliOssPublicDomain // 可公开的文件域名
|
|
42
|
+
// 当前租户自行扩展的业务集成字段仍可读取
|
|
43
|
+
|
|
44
|
+
// 以下基础设施字段不会注入 V8:
|
|
45
|
+
// DbConn / DbReadConn / AuthSecret
|
|
46
|
+
// RedisHost / RedisPwd / MinIOSecretKey / AliOss*AccessKey*
|
|
47
|
+
// MQHost / MQUserName / MQPassword / MqttPwd / SearchEngineApiKey
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 平台级配置与主租户规则
|
|
51
|
+
|
|
52
|
+
`sys_osclients` 既保存每个租户自己的数据库、Redis、第三方密钥等配置,也可以承载影响整个 API 进程的平台级配置。平台级配置必须遵守“主租户为准、子租户只能降额隔离”的规则:
|
|
53
|
+
|
|
54
|
+
- 所有可变业务逻辑默认必须由接口引擎编排,包括但不限于租户开通、开库、初始化、归属修复、官网个人中心、付费额度等 SaaS 业务流程。C# 后端只暴露原子 V8 能力,例如建库、导入空库模板、复制 `sys_config`、刷新 SaaS 缓存、补偿回滚、字段兜底等;不要把可变业务分支写死到 Controller 或 `TenantProvisioningService` 这类后端定制代码里。接口引擎缺少能力时,优先扩展 `V8.Method`/V8 引擎原子函数,再由接口引擎调用。
|
|
55
|
+
- 主租户由运行环境决定:优先读取环境变量 `OsClient`,其次读取 `appsettings.json` 的 `AppSettings:OsClient`。只有这条主租户 `sys_osclients` 数据中的平台级字段会作为全局配置生效。
|
|
56
|
+
- 普通业务与运行参数统一从主租户 `sys_osclients` 或 `sys_config` 读取,未配置时使用代码安全默认值;不要再为同一参数增加 `MICROI_*` 或 `DOS_ORM_*` 环境变量。数据库、Redis 和必要密钥属于启动基础设施,继续使用现有专用安全配置;节点身份由平台自动生成。
|
|
57
|
+
- 文件上传的租户业务开关与额度按“当前租户 `sys_osclients` → 代码默认值”解析;平台固定灾难保护、HTTP/Multipart/Form 和反向代理上限不可由租户覆盖,也不要求安装者维护额外上传环境变量。
|
|
58
|
+
- 类似 MQTT 端口、PressureGuard、V8Limits、OrmLimits、StartupLimits、SecurityGuard 这类影响整进程资源的配置,不能让每个子租户各自抬高全局上限。子租户同名隔离字段只能降低自己的并发、等待时间或资源额度,用于隔离弱租户、试用租户或异常租户。
|
|
59
|
+
- 修改 `sys_osclients` 的表、字段、数据源或配置值后,必须刷新 SaaS 引擎运行缓存,并回读验证字段 `Component`、`Data`、`Config`、实际数据值和前端真实消费结果。不要只看 MCP 写入成功。
|
|
60
|
+
- SaaS 配置只在启动、管理员保存 `sys_osclients` 或显式租户刷新时发布到共享 Redis。初始化数据库会话、创建 `V8.Dbs` 运行态对象、普通 FormEngine 请求和表单设计器保存不得冒充配置变更反复发布。
|
|
61
|
+
- 扩展库缓存必须区分“尚未加载”和“已加载但为 0 条”;后者是有效结果。没有配置 `microi_database` 的租户不能在每次 V8 执行时重复查询、调用 `AddOrUptClient` 或打印“缓存 OsClient 配置到 Redis”。
|
|
62
|
+
- 多节点的缓存失效订阅只做本节点失效与数据库回源,禁止收到消息后再次发布形成回声。进程内初始化标记仅是可丢失优化,真正租户配置仍以共享数据库/Redis 为准。
|
|
63
|
+
- 新增平台级字段时,字段名建议保持英文稳定,例如 `PressureGlobalMaxConcurrentRequests`、`PressureV8MaxConcurrentExecutions`、`PressureOrmMaxConcurrentConnectionOpens`;字段标签和说明必须中文,说明中写清楚“主租户有效/子租户仅可降低”。
|
|
64
|
+
- 新租户记录不得复制主租户的数据库、鉴权、Redis/对象存储密钥或 MQ/MQTT/Search 凭据。共享基础设施地址与管理密钥只在服务端运行时解析,不持久化到子租户记录,也不进入 V8 投影。
|
|
65
|
+
- RabbitMQ 子租户必须使用独立 user/vhost/ACL,MQTT 必须使用独立账号密码,Search 必须使用限制到 `{osClient}_*` 的 API Key/用户。外部资源尚未真实创建时保持空凭据并失败关闭,禁止把主租户管理员凭据复制过去冒充完成。
|
|
66
|
+
- `V8.Cache`、`V8.HDFS`、RabbitMQ、MQTT、Search 分别强制 `Microi:{OsClient}:*`、`/{osClient}/...`、`microi.{osClient}.*`、`tenant/{osClient}/...`、`{osClient}_*` 命名空间。body/query 中的 `OsClient` 不能覆盖登录 Token 或 V8 上下文。
|
|
67
|
+
|
|
68
|
+
## 接口调用区分租户的三种方式
|
|
69
|
+
|
|
70
|
+
### 方式 1:Token 自动识别(最常用)
|
|
71
|
+
|
|
72
|
+
请求头携带 `Token`,平台自动识别用户所属租户。
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
GET /apiengine/get-products
|
|
76
|
+
Token: xxx-token-xxx
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 方式 2:URL 参数
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
GET /apiengine/get-products?OsClient=tenant_demo
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 方式 3:特殊 URL 格式(无 Token、无参数)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
GET /apiengine/get-products--OsClient--tenant_demo--
|
|
89
|
+
GET /apiengine/get-products--OsClient--tenant_demo--OsClientType--App--
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> 适用于第三方回调(无法添加 Header)、支付/微信回调等场景。
|
|
93
|
+
|
|
94
|
+
## 跨租户操作(仅可信控制面)
|
|
95
|
+
|
|
96
|
+
普通 V8 的 `FormEngine`、`DataSourceEngine`、`TranslateEngine`、`WFEngine`、`Cache`、`HDFS`、MQ/MQTT、Search 和 `Dbs` 都必须绑定当前 Token/V8 上下文中的 `OsClient`。请求 body/query 里传入其它租户不能切换连接,也不能读取其它租户凭据。
|
|
97
|
+
|
|
98
|
+
租户开通、迁移、备份或平台管理员代维只能走明确的控制面服务/接口引擎:
|
|
99
|
+
|
|
100
|
+
- 调用者必须是 `Level >= 9999`,并再次校验目标租户白名单和操作类型;
|
|
101
|
+
- 使用专用原子能力,不通过通用 FormEngine 读取完整 `sys_osclients` 记录;
|
|
102
|
+
- 不把数据库、认证、Redis、存储、MQ/MQTT、搜索等连接与密钥投影进 V8;
|
|
103
|
+
- 每次操作写安全审计、幂等键和补偿状态,并对目标租户回读验收;
|
|
104
|
+
- 多节点部署使用共享租约和业务幂等,不能依赖进程静态锁。
|
|
105
|
+
|
|
106
|
+
## 缓存按租户隔离
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// 推荐传逻辑 Key;服务端自动添加当前租户前缀
|
|
110
|
+
var key = 'Product:' + V8.Param.id;
|
|
111
|
+
V8.Cache.Set(key, value, 600);
|
|
112
|
+
|
|
113
|
+
// 完整当前租户 Key 继续兼容
|
|
114
|
+
var fullKey = 'Microi:' + V8.OsClient + ':Product:' + V8.Param.id;
|
|
115
|
+
|
|
116
|
+
// 其它租户的 Microi: 前缀会被服务端拒绝
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 接口引擎中针对不同租户走不同逻辑
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// 租户差异应来自当前租户的非敏感业务配置,不要硬编码某个“主租户”字符串
|
|
123
|
+
if (V8.OsClientModel.OrderApprovalMode === 'Direct') {
|
|
124
|
+
V8.FormEngine.UptFormData('Order', { Id: id, Status: 'Approved' });
|
|
125
|
+
} else {
|
|
126
|
+
await V8.WFEngine.StartWork({ FlowDesignId: 'order-flow', TableRowId: id });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// App 端 vs PC 端不同返回
|
|
130
|
+
if (V8.OsClientType === 'App') {
|
|
131
|
+
return { Code: 1, Data: simplifiedList };
|
|
132
|
+
}
|
|
133
|
+
return { Code: 1, Data: fullList };
|
|
134
|
+
|
|
135
|
+
// 内网外网走不同 ERP 网关
|
|
136
|
+
var erpUrl = (V8.OsClientNetwork === 'Intranet')
|
|
137
|
+
? 'http://192.168.1.10/erp/api'
|
|
138
|
+
: 'https://erp.public.com/api';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 第三方密钥放 OsClientModel(不要硬编码)
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
// ❌ 危险:密钥写在代码里,所有租户共用,无法独立轮换
|
|
145
|
+
var ak = 'AKIDxxxxxxxx';
|
|
146
|
+
|
|
147
|
+
// ✅ 正确:每个租户在 sys_osclients 表中配置自己的业务集成密钥
|
|
148
|
+
var ak = V8.OsClientModel.WxPayMchKey;
|
|
149
|
+
var secret = V8.OsClientModel.WxPaySecret;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> 在表单引擎中给 `sys_osclients` 添加租户自有业务字段(如 `WxPayMchKey`),可通过 `V8.OsClientModel.字段名` 访问。共享基础设施字段由服务端强制移除,不能用自定义同义字段绕过安全代理。
|
|
153
|
+
|
|
154
|
+
## 用户扩展字段访问(同理)
|
|
155
|
+
|
|
156
|
+
平台 `sys_user` 也由表单引擎驱动。如给 `sys_user` 添加 `Wife` 字段:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// V8 代码中可访问
|
|
160
|
+
V8.CurrentUser.Wife;
|
|
161
|
+
|
|
162
|
+
// SQL 数据源中可访问
|
|
163
|
+
SELECT * FROM Contact WHERE OwnerId = $CurrentUser.Id$ AND Spouse = $CurrentUser.Wife$
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 常见错误
|
|
167
|
+
|
|
168
|
+
❌ 绕开 `V8.Cache` 使用底层 Redis → 租户数据串号(V8 已不再暴露底层句柄)
|
|
169
|
+
❌ MongoDB DbName 不带 OsClient → 数据混淆
|
|
170
|
+
❌ 把 OsClientModel 字段直接返回给前端 → 密钥泄漏
|
|
171
|
+
❌ 在前端硬编码 OsClient → 一改全改,应通过 token/URL 自动识别
|
|
172
|
+
❌ 跨租户操作不验证当前用户权限 → 越权风险
|
|
173
|
+
❌ 子租户缺少 MQ/MQTT/Search 独立凭据时回退主账号 → 全平台越权
|
|
174
|
+
|
|
175
|
+
## 检查清单
|
|
176
|
+
|
|
177
|
+
- [ ] 缓存只通过 `V8.Cache` 使用逻辑 Key 或当前租户完整 Key
|
|
178
|
+
- [ ] 所有 MongoDB DbName 都包含 `V8.OsClient`
|
|
179
|
+
- [ ] 当前租户自有的业务集成密钥可放 `V8.OsClientModel`,共享 Redis/存储/MQ/MQTT/Search 凭据只能由服务端托管
|
|
180
|
+
- [ ] 跨租户操作前校验权限
|
|
181
|
+
- [ ] 不向前端返回 `V8.OsClientModel`
|
|
182
|
+
- [ ] 子租户数据库账号只授权本租户库,MQ/MQTT/Search 独立凭据已真实创建
|
|
183
|
+
- [ ] 文件、队列、Topic、索引均由服务端规范为当前租户命名空间
|
|
184
|
+
- [ ] 无扩展库租户重复执行 V8 时不会重复刷新 SaaS 配置;真实 `sys_osclients` 保存后各节点能按租户失效并回源
|
|
185
|
+
|
|
186
|
+
## Microi.AI 中转站租户凭据
|
|
187
|
+
|
|
188
|
+
- `mic_ai` 的 `Microi.AI中转站.ApiKey` 是租户调用吾码官方中转站的用户凭据。运行时不得因为该字段为空而静默创建、回退到当前用户字段或绕过校验,否则管理员无法判断真实配置来源。
|
|
189
|
+
- 老租户由用户在吾码官网个人中心复制 ApiKey 后填写到本租户的 `mic_ai` 中转站记录;空值应返回明确配置错误。
|
|
190
|
+
- 官网创建新 SaaS 租户时,必须先取得当前官网账号的 ApiKey,再由租户初始化服务写入新库 `mic_ai` 的 `Microi.AI中转站` 记录。该自动写入只发生在受控开库流程,不得散落到普通 AI 对话请求中。
|
|
191
|
+
- 开库入口把 ApiKey 传给后台 worker 后,worker 还必须继续显式传入最终的原子初始化方法;不能只在父接口或中间参数中“带过”。每个异步/后台边界都应对空值立即失败,避免租户创建成功但 `mic_ai.ApiKey` 静默为空。
|
|
192
|
+
- 自动化验收必须通过新租户 `admin` 登录回读 `mic_ai`,只输出“非空、长度、与官网账号 ApiKey 是否一致”等布尔结果,不得把真实 ApiKey 写入日志、截图或测试报告。
|
|
193
|
+
- 中转模型公开目录只返回模型 Id、显示名、厂商等非敏感字段,可匿名读取;严禁把官方上游模型的 ApiKey、Endpoint 私密配置随模型列表返回前端。
|