@lovrabet/cli 1.2.4 → 1.2.5-beta.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/lib/add-page/input-page-router.js +1 -1
- package/lib/add-page/main.js +1 -1
- package/lib/add-page/select-page-template.js +1 -1
- package/lib/api/api-doc-ui.js +1 -1
- package/lib/api/api-doc.js +1 -1
- package/lib/api/api-pull-ui.js +1 -1
- package/lib/api/fetch-model-list.js +1 -1
- package/lib/api/generate-api-file.js +1 -1
- package/lib/api/main.js +1 -1
- package/lib/api/pull-silent.js +1 -1
- package/lib/app-menu/app-menu-sync-ui.js +1 -1
- package/lib/app-menu/create-menu.js +1 -1
- package/lib/app-menu/get-local-pages.js +1 -1
- package/lib/app-menu/get-online-menu-list.js +1 -1
- package/lib/app-menu/use-get-online-menu-list.js +1 -1
- package/lib/app-menu/utils.js +1 -1
- package/lib/app-menu/valid-url.js +1 -1
- package/lib/app-menu-update-cdn/current-content.js +1 -1
- package/lib/app-menu-update-cdn/input-cdn-asset.js +1 -1
- package/lib/app-menu-update-cdn/main.js +1 -1
- package/lib/app-menu-update-cdn/update-menu-cdn-url.js +1 -1
- package/lib/auth/auth-server-ui.js +1 -1
- package/lib/auth/auth-server.js +1 -1
- package/lib/auth/constant.js +1 -1
- package/lib/auth/get-cookie.js +1 -1
- package/lib/auth/is-session-valid.js +1 -1
- package/lib/auth/logout.js +1 -1
- package/lib/cli.js +1 -1
- package/lib/cmd/build-watch.js +1 -1
- package/lib/cmd/build.js +1 -1
- package/lib/cmd/logs.js +1 -1
- package/lib/cmd/preview.js +1 -1
- package/lib/cmd/start.js +1 -1
- package/lib/config/config-help.js +1 -1
- package/lib/config/main.js +1 -1
- package/lib/constant/domain.js +1 -1
- package/lib/constant/env.js +1 -1
- package/lib/create-app/enhanced-guided-create.js +1 -1
- package/lib/create-app/format-elapsed.js +1 -1
- package/lib/create-app/main.js +1 -1
- package/lib/create-app/task-finished.js +1 -1
- package/lib/create-app/task-loading.js +1 -1
- package/lib/create-app/task-running.js +1 -1
- package/lib/create-app/task-time.js +1 -1
- package/lib/create-app/use-copy-project-template.js +1 -1
- package/lib/create-app/use-format-code.js +1 -1
- package/lib/create-app/use-install-dependencies.js +1 -1
- package/lib/help.js +1 -1
- package/lib/init/main.js +1 -1
- package/lib/mcp/claude.js +1 -0
- package/lib/mcp/cursor.js +1 -1
- package/lib/mcp/main.js +1 -1
- package/lib/skills/main.js +1 -0
- package/lib/utils/check-sdk-version.js +1 -1
- package/lib/utils/config.js +1 -1
- package/lib/utils/copy-directory.js +1 -1
- package/lib/utils/http-client.js +1 -1
- package/lib/utils/logger.js +1 -1
- package/lib/utils/router-updater.js +1 -1
- package/lib/utils/sleep.js +1 -1
- package/lib/utils/template-replacer.js +1 -1
- package/package.json +1 -1
- package/templates/projects/sub-app-react-demo/index.html +22 -34
- package/templates/projects/sub-app-react-demo/public/logo.svg +1 -0
- package/templates/projects/sub-app-react-demo/src/api/api.ts +1 -1
- package/templates/projects/sub-app-react-demo/src/api/client.ts +1 -1
- package/templates/projects/sub-app-react-demo/src/layouts/MainLayout.tsx +44 -71
- package/templates/projects/sub-app-react-demo/src/pages/index.tsx +387 -927
- package/templates/projects/sub-app-react-demo/src/pages/sdk-demo/index.tsx +1 -1
- package/templates/projects/sub-app-react-demo/src/pages/workbench/index.module.css +293 -0
- package/templates/projects/sub-app-react-demo/src/pages/workbench/index.tsx +100 -414
- package/templates/projects/sub-app-react-demo/src/style.css +21 -15
- package/templates/projects/sub-app-react-demo/vite.config.ts +18 -13
- package/templates/rules/lovrabet_rules.mdc.tpl +636 -43
- package/templates/skills/.claude/skills/lovrabet/SKILL.md +258 -0
- package/templates/skills/.cursor/commands/lovrabet.md +248 -0
- package/templates/skills/.cursorrules +109 -0
- package/templates/skills/.shared/README.md +45 -0
- package/templates/skills/.shared/guides/01-filter-query/guide.md +300 -0
- package/templates/skills/.shared/guides/02-mcp-sql-workflow/guide.md +272 -0
- package/templates/skills/.shared/guides/03-antd-style/guide.md +227 -0
- package/templates/skills/.shared/guides/04-troubleshooting/guide.md +426 -0
- package/templates/skills/.shared/guides/05-api-integration/guide.md +327 -0
- package/templates/skills/.shared/guides/06-menu-management/guide.md +305 -0
- package/templates/skills/.shared/guides/07-backend-function/guide.md +679 -0
- package/templates/skills/.windsurf/workflows/lovrabet.md +257 -0
- package/templates/projects/sub-app-react-demo/.vscode/extensions.json +0 -3
- package/templates/projects/sub-app-react-demo/.vscode/settings.json +0 -57
- package/templates/projects/sub-app-react-demo/src/pages/intro/index.tsx +0 -560
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Filter 查询构建规范
|
|
2
|
+
|
|
3
|
+
> **目标**:学会正确使用 SDK `filter()` 方法,避免常见错误
|
|
4
|
+
|
|
5
|
+
## 🎯 为什么这个技能重要?
|
|
6
|
+
|
|
7
|
+
`filter()` 是 Lovrabet SDK 中最常用的查询方法,但也是最容易出错的地方。根据统计,**80% 的 API 错误都与 filter 使用不当有关**。
|
|
8
|
+
|
|
9
|
+
### 常见错误现象
|
|
10
|
+
- 查询没有结果,但数据明明存在
|
|
11
|
+
- 报错 "参数格式不正确"
|
|
12
|
+
- 分页不生效
|
|
13
|
+
- 排序没有效果
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📖 基本结构
|
|
18
|
+
|
|
19
|
+
### 最简单的 filter 调用
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// 数据集: 用户信息 | 数据表: users
|
|
23
|
+
const result = await client.models.users.filter({
|
|
24
|
+
currentPage: 1,
|
|
25
|
+
pageSize: 20,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(result.tableData); // 数据列表
|
|
29
|
+
console.log(result.total); // 总数
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 完整的 filter 调用
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const result = await client.models.users.filter({
|
|
36
|
+
where: { /* 查询条件 */ },
|
|
37
|
+
select: ['id', 'name', 'email'], // 返回哪些字段
|
|
38
|
+
orderBy: [{ createTime: 'desc' }], // 排序
|
|
39
|
+
currentPage: 1,
|
|
40
|
+
pageSize: 20,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 🔑 关键操作符
|
|
47
|
+
|
|
48
|
+
### 比较操作符
|
|
49
|
+
|
|
50
|
+
| 操作符 | 说明 | 示例 |
|
|
51
|
+
|--------|------|------|
|
|
52
|
+
| `$eq` | 等于 | `{ status: { $eq: 'active' } }` |
|
|
53
|
+
| `$ne` | 不等于 | `{ status: { $ne: 'deleted' } }` |
|
|
54
|
+
| `$gte` | 大于等于 | `{ age: { $gte: 18 } }` |
|
|
55
|
+
| `$lte` | 小于等于 | `{ age: { $lte: 65 } }` |
|
|
56
|
+
| `$gt` | 大于 | `{ price: { $gt: 100 } }` |
|
|
57
|
+
| `$lt` | 小于 | `{ price: { $lt: 1000 } }` |
|
|
58
|
+
|
|
59
|
+
### 字符串操作符
|
|
60
|
+
|
|
61
|
+
| 操作符 | 说明 | 示例 |
|
|
62
|
+
|--------|------|------|
|
|
63
|
+
| `$contain` | 包含 | `{ name: { $contain: 'keyword' } }` |
|
|
64
|
+
| `$startWith` | 开头匹配 | `{ email: { $startWith: 'admin' } }` |
|
|
65
|
+
| `$endWith` | 结尾匹配 | `{ domain: { $endWith: '.com' } }` |
|
|
66
|
+
|
|
67
|
+
### 集合操作符
|
|
68
|
+
|
|
69
|
+
| 操作符 | 说明 | 示例 |
|
|
70
|
+
|--------|------|------|
|
|
71
|
+
| `$in` | 包含于 | `{ type: { $in: ['A', 'B', 'C'] } }` |
|
|
72
|
+
|
|
73
|
+
### 逻辑操作符
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// $and - 所有条件都要满足
|
|
77
|
+
where: {
|
|
78
|
+
$and: [
|
|
79
|
+
{ age: { $gte: 18 } },
|
|
80
|
+
{ status: { $eq: 'active' } }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// $or - 任一条件满足即可
|
|
85
|
+
where: {
|
|
86
|
+
$or: [
|
|
87
|
+
{ status: { $eq: 'pending' } },
|
|
88
|
+
{ status: { $eq: 'processing' } }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 嵌套组合
|
|
93
|
+
where: {
|
|
94
|
+
$and: [
|
|
95
|
+
{ age: { $gte: 18 } },
|
|
96
|
+
{
|
|
97
|
+
$or: [
|
|
98
|
+
{ country: { $eq: '中国' } },
|
|
99
|
+
{ country: { $eq: '美国' } }
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## ⚠️ 10 个常见错误
|
|
109
|
+
|
|
110
|
+
### 错误 1:忘记使用操作符
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// ❌ 错误:直接写值
|
|
114
|
+
where: { status: 'active' }
|
|
115
|
+
|
|
116
|
+
// ✅ 正确:使用 $eq 操作符
|
|
117
|
+
where: { status: { $eq: 'active' } }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 错误 2:参数名错误
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// ❌ 错误:使用 fields
|
|
124
|
+
fields: ['id', 'name']
|
|
125
|
+
|
|
126
|
+
// ✅ 正确:使用 select
|
|
127
|
+
select: ['id', 'name']
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 错误 3:排序参数名错误
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// ❌ 错误:使用 sort 或 sortList
|
|
134
|
+
sort: [{ createTime: 'desc' }]
|
|
135
|
+
|
|
136
|
+
// ✅ 正确:使用 orderBy
|
|
137
|
+
orderBy: [{ createTime: 'desc' }]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 错误 4:使用 SQL 语法
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// ❌ 错误:使用 LIKE
|
|
144
|
+
where: { name: { LIKE: '%keyword%' } }
|
|
145
|
+
|
|
146
|
+
// ✅ 正确:使用 $contain
|
|
147
|
+
where: { name: { $contain: 'keyword' } }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 错误 5:使用字符串比较
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// ❌ 错误:字符串形式的比较
|
|
154
|
+
where: { age: '>= 18' }
|
|
155
|
+
|
|
156
|
+
// ✅ 正确:使用操作符
|
|
157
|
+
where: { age: { $gte: 18 } }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 错误 6:区间查询操作符错误
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// ❌ 错误:使用 $gt 和 $lt
|
|
164
|
+
where: { age: { $gt: 18, $lt: 65 } }
|
|
165
|
+
|
|
166
|
+
// ✅ 正确:使用 $gte 和 $lte
|
|
167
|
+
where: { age: { $gte: 18, $lte: 65 } }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 错误 7:orderBy 不是数组
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// ❌ 错误:字符串格式
|
|
174
|
+
orderBy: 'createTime desc'
|
|
175
|
+
|
|
176
|
+
// ✅ 正确:数组格式
|
|
177
|
+
orderBy: [{ createTime: 'desc' }]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 错误 8:select 不是数组
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// ❌ 错误:字符串格式
|
|
184
|
+
select: 'id,name,status'
|
|
185
|
+
|
|
186
|
+
// ✅ 正确:数组格式
|
|
187
|
+
select: ['id', 'name', 'status']
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 错误 9:$or 值不是数组
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// ❌ 错误:对象格式
|
|
194
|
+
where: { $or: { status: 'A', type: 'X' } }
|
|
195
|
+
|
|
196
|
+
// ✅ 正确:数组格式
|
|
197
|
+
where: { $or: [
|
|
198
|
+
{ status: { $eq: 'A' } },
|
|
199
|
+
{ type: { $eq: 'X' } }
|
|
200
|
+
]}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 错误 10:分页参数名错误
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// ❌ 错误:使用 page/limit
|
|
207
|
+
page: 1,
|
|
208
|
+
limit: 20
|
|
209
|
+
|
|
210
|
+
// ✅ 正确:使用 currentPage/pageSize
|
|
211
|
+
currentPage: 1,
|
|
212
|
+
pageSize: 20
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 📝 实战示例
|
|
218
|
+
|
|
219
|
+
### 场景 1:带搜索的用户列表
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// 数据集: 用户信息 | 数据表: users
|
|
223
|
+
async function searchUsers(keyword: string, page: number = 1) {
|
|
224
|
+
const result = await client.models.users.filter({
|
|
225
|
+
where: keyword ? {
|
|
226
|
+
$or: [
|
|
227
|
+
{ name: { $contain: keyword } },
|
|
228
|
+
{ email: { $contain: keyword } },
|
|
229
|
+
{ phone: { $contain: keyword } },
|
|
230
|
+
]
|
|
231
|
+
} : undefined,
|
|
232
|
+
select: ['id', 'name', 'email', 'phone', 'status'],
|
|
233
|
+
orderBy: [{ createTime: 'desc' }],
|
|
234
|
+
currentPage: page,
|
|
235
|
+
pageSize: 20,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
list: result.tableData,
|
|
240
|
+
total: result.total,
|
|
241
|
+
hasMore: page * 20 < result.total,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 场景 2:筛选订单列表
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// 数据集: 订单 | 数据表: orders
|
|
250
|
+
async function getOrders(filters: {
|
|
251
|
+
status?: string;
|
|
252
|
+
dateStart?: string;
|
|
253
|
+
dateEnd?: string;
|
|
254
|
+
minAmount?: number;
|
|
255
|
+
}) {
|
|
256
|
+
const conditions = [];
|
|
257
|
+
|
|
258
|
+
if (filters.status) {
|
|
259
|
+
conditions.push({ status: { $eq: filters.status } });
|
|
260
|
+
}
|
|
261
|
+
if (filters.dateStart) {
|
|
262
|
+
conditions.push({ createTime: { $gte: filters.dateStart } });
|
|
263
|
+
}
|
|
264
|
+
if (filters.dateEnd) {
|
|
265
|
+
conditions.push({ createTime: { $lte: filters.dateEnd } });
|
|
266
|
+
}
|
|
267
|
+
if (filters.minAmount) {
|
|
268
|
+
conditions.push({ amount: { $gte: filters.minAmount } });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const result = await client.models.orders.filter({
|
|
272
|
+
where: conditions.length > 0 ? { $and: conditions } : undefined,
|
|
273
|
+
orderBy: [{ createTime: 'desc' }],
|
|
274
|
+
currentPage: 1,
|
|
275
|
+
pageSize: 50,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return result.tableData;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## ✅ 自检清单
|
|
285
|
+
|
|
286
|
+
使用 filter 时,检查:
|
|
287
|
+
|
|
288
|
+
- [ ] where 条件使用了操作符(`$eq`、`$gte` 等)
|
|
289
|
+
- [ ] 参数名正确(`select` 不是 `fields`)
|
|
290
|
+
- [ ] `orderBy` 和 `select` 是数组格式
|
|
291
|
+
- [ ] 分页参数名是 `currentPage`/`pageSize`
|
|
292
|
+
- [ ] 添加了数据集和数据表注释
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 🔗 相关资源
|
|
297
|
+
|
|
298
|
+
- [常见场景示例](./examples.md) - 更多实际开发中的示例
|
|
299
|
+
- [错误对照](./errors.md) - 完整的错误 vs 正确写法对照表
|
|
300
|
+
- [自检清单](./checklist.md) - 详细的代码检查清单
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# MCP SQL 创建工作流
|
|
2
|
+
|
|
3
|
+
> **目标**:掌握创建自定义 SQL 的正确流程,避免因 SQL 错误导致的问题
|
|
4
|
+
|
|
5
|
+
## 🎯 为什么这个技能重要?
|
|
6
|
+
|
|
7
|
+
很多开发者在创建自定义 SQL 时跳过验证步骤,导致:
|
|
8
|
+
- SQL 中的表名或字段名错误
|
|
9
|
+
- 保存后才发现语法错误
|
|
10
|
+
- 不确定 SQL 是否正确执行
|
|
11
|
+
|
|
12
|
+
**按照正确的流程操作,可以避免 90% 的 SQL 相关问题。**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 📋 5 步工作流(强制顺序)
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
第1步 → 第2步 → 第3步 → 第4步 → 第5步
|
|
20
|
+
↓ ↓ ↓ ↓ ↓
|
|
21
|
+
查询 生成 验证 保存 测试
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 步骤详解
|
|
27
|
+
|
|
28
|
+
### 第 1 步:查询现有 SQL
|
|
29
|
+
|
|
30
|
+
**目标**:确认 SQL 是否已存在,避免重复创建。
|
|
31
|
+
|
|
32
|
+
在 IDE 中使用 MCP 工具:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
请使用 list_sql_queries 查询所有 SQL,查找包含 "用户统计" 的 SQL
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
或者在代码中:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// 使用 MCP 工具查询
|
|
42
|
+
const sqlList = await list_sql_queries({
|
|
43
|
+
keyword: '用户统计',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 检查是否已存在
|
|
47
|
+
const existingSql = sqlList.sqls.find(s => s.sqlName.includes('用户统计'));
|
|
48
|
+
if (existingSql) {
|
|
49
|
+
console.log('SQL 已存在:', existingSql.sqlCode);
|
|
50
|
+
// 可以直接使用,或进入第3步验证
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 第 2 步:获取表结构
|
|
55
|
+
|
|
56
|
+
**目标**:先获取表结构,确保 SQL 中的字段名正确。
|
|
57
|
+
|
|
58
|
+
在 IDE 中使用 MCP 工具:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
请使用 get_dataset_detail 获取数据集 "customer" 的详情
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
或者在代码中:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// 获取数据集详情
|
|
68
|
+
const detail = await get_dataset_detail({
|
|
69
|
+
datasetCode: 'customer',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 确认字段存在
|
|
73
|
+
const fieldCodes = detail.fields.map(f => f.code);
|
|
74
|
+
console.log('可用字段:', fieldCodes);
|
|
75
|
+
|
|
76
|
+
// 检查字段
|
|
77
|
+
if (!fieldCodes.includes('customerName')) {
|
|
78
|
+
throw new Error('字段 customerName 不存在');
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**重要**:获取表结构后,查看实际的字段名,不要凭记忆编写 SQL。
|
|
83
|
+
|
|
84
|
+
### 第 3 步:验证 SQL 内容
|
|
85
|
+
|
|
86
|
+
**目标**:在保存前验证 SQL 语法正确性。
|
|
87
|
+
|
|
88
|
+
在 IDE 中使用 MCP 工具:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
请使用 validate_sql_content 验证以下 SQL:
|
|
92
|
+
SELECT id, customer_name, create_time
|
|
93
|
+
FROM customer
|
|
94
|
+
WHERE status = 1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
或者在代码中:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const validation = await validate_sql_content({
|
|
101
|
+
sqlContent: `
|
|
102
|
+
SELECT id, customer_name, create_time
|
|
103
|
+
FROM customer
|
|
104
|
+
WHERE status = 1
|
|
105
|
+
`,
|
|
106
|
+
dbId: detail.basic.database.dbId,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!validation.validation.valid) {
|
|
110
|
+
console.error('SQL 验证失败:', validation.validation.errors);
|
|
111
|
+
// 根据错误信息修改 SQL
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 第 4 步:保存 SQL
|
|
117
|
+
|
|
118
|
+
**目标**:将验证通过的 SQL 保存到平台。
|
|
119
|
+
|
|
120
|
+
在 IDE 中使用 MCP 工具:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
请使用 save_or_update_custom_sql 保存以下 SQL:
|
|
124
|
+
- SQL 名称: 客户统计查询
|
|
125
|
+
- SQL 代码: customer_stats
|
|
126
|
+
- SQL 内容: SELECT id, customer_name, create_time FROM customer WHERE status = 1
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
或者在代码中:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const result = await save_or_update_custom_sql({
|
|
133
|
+
sqlName: '客户统计查询',
|
|
134
|
+
sqlCode: 'customer_stats',
|
|
135
|
+
sqlContent: `
|
|
136
|
+
SELECT id, customer_name, create_time
|
|
137
|
+
FROM customer
|
|
138
|
+
WHERE status = 1
|
|
139
|
+
`,
|
|
140
|
+
dbId: detail.basic.database.dbId,
|
|
141
|
+
verifyAfterSave: true, // 保存后自动验证
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
console.log('SQL 保存成功:', result.sqlId);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 第 5 步:测试执行
|
|
148
|
+
|
|
149
|
+
**目标**:验证 SQL 能正确执行并返回预期数据。
|
|
150
|
+
|
|
151
|
+
在 IDE 中使用 MCP 工具:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
请使用 execute_custom_sql 执行 SQL "customer_stats",测试是否正确
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
或者在代码中:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const execResult = await execute_custom_sql({
|
|
161
|
+
sqlCode: 'customer_stats',
|
|
162
|
+
params: {}, // 如果有参数,在这里传入
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (!execResult.execSuccess) {
|
|
166
|
+
console.error('SQL 执行失败:', execResult.execError);
|
|
167
|
+
} else {
|
|
168
|
+
console.log('SQL 执行成功,返回', execResult.rowCount, '行');
|
|
169
|
+
console.log('数据预览:', execResult.execResult?.slice(0, 3));
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## ⚠️ 禁止行为
|
|
176
|
+
|
|
177
|
+
| 禁止行为 | 后果 | 正确做法 |
|
|
178
|
+
|---------|------|---------|
|
|
179
|
+
| 跳过第1步,直接创建 | 可能创建重复的 SQL | 先查询现有 SQL |
|
|
180
|
+
| 跳过第2步,直接写 SQL | 字段名可能错误 | 先获取表结构 |
|
|
181
|
+
| 跳过第3步,直接保存 | 语法错误导致保存失败 | 先验证 SQL |
|
|
182
|
+
| 保存后不测试 | 运行时才发现错误 | 必须测试执行 |
|
|
183
|
+
| 假设 SQL code 存在 = SQL 正确 | SQL 内容可能有错误 | 必须验证 SQL 内容 |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 📝 完整示例
|
|
188
|
+
|
|
189
|
+
### 场景:创建客户月度统计 SQL
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// 第1步:查询现有 SQL
|
|
193
|
+
const sqlList = await list_sql_queries({
|
|
194
|
+
keyword: '客户月度统计',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (sqlList.sqls.length > 0) {
|
|
198
|
+
console.log('找到相关 SQL:', sqlList.sqls.map(s => s.sqlName));
|
|
199
|
+
// 如果已有合适的 SQL,直接使用
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 第2步:获取表结构
|
|
203
|
+
const detail = await get_dataset_detail({
|
|
204
|
+
datasetCode: 'customer',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const tableName = detail.basic.tableName; // 实际表名
|
|
208
|
+
const fields = detail.fields.map(f => f.code); // 实际字段名
|
|
209
|
+
|
|
210
|
+
// 第3步:编写并验证 SQL
|
|
211
|
+
const sqlContent = `
|
|
212
|
+
SELECT
|
|
213
|
+
DATE_FORMAT(create_time, '%Y-%m') as month,
|
|
214
|
+
COUNT(*) as customer_count,
|
|
215
|
+
COUNT(DISTINCT city) as city_count
|
|
216
|
+
FROM ${tableName}
|
|
217
|
+
WHERE status = 1
|
|
218
|
+
GROUP BY DATE_FORMAT(create_time, '%Y-%m')
|
|
219
|
+
ORDER BY month DESC
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
const validation = await validate_sql_content({
|
|
223
|
+
sqlContent,
|
|
224
|
+
dbId: detail.basic.database.dbId,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!validation.validation.valid) {
|
|
228
|
+
console.error('SQL 验证失败:', validation.validation.errors);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 第4步:保存 SQL
|
|
233
|
+
const saveResult = await save_or_update_custom_sql({
|
|
234
|
+
sqlName: '客户月度统计',
|
|
235
|
+
sqlCode: 'customer_monthly_stats',
|
|
236
|
+
sqlContent,
|
|
237
|
+
dbId: detail.basic.database.dbId,
|
|
238
|
+
verifyAfterSave: true,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// 第5步:测试执行
|
|
242
|
+
const testResult = await execute_custom_sql({
|
|
243
|
+
sqlCode: 'customer_monthly_stats',
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (testResult.execSuccess) {
|
|
247
|
+
console.log('✓ SQL 创建成功!');
|
|
248
|
+
console.log('返回行数:', testResult.rowCount);
|
|
249
|
+
} else {
|
|
250
|
+
console.error('✗ SQL 执行失败:', testResult.execError);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## ✅ 自检清单
|
|
257
|
+
|
|
258
|
+
创建自定义 SQL 时,确认:
|
|
259
|
+
|
|
260
|
+
- [ ] 第1步:查询了现有 SQL,避免重复
|
|
261
|
+
- [ ] 第2步:获取了表结构,确认字段名
|
|
262
|
+
- [ ] 第3步:验证了 SQL 语法
|
|
263
|
+
- [ ] 第4步:保存成功
|
|
264
|
+
- [ ] 第5步:测试执行成功
|
|
265
|
+
- [ ] SQL 只包含 SELECT 查询(没有 INSERT/UPDATE/DELETE)
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 🔗 相关资源
|
|
270
|
+
|
|
271
|
+
- [步骤详解](./step-by-step.md) - 每一步的详细说明和注意事项
|
|
272
|
+
- [自检清单](./checklist.md) - 详细的检查清单
|