@fastcar/cli 0.1.2 → 0.1.4
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/bin/cli.js +239 -226
- package/package.json +1 -1
- package/skills/AGENTS.md +251 -0
- package/skills/fastcar-database/SKILL.md +436 -337
- package/skills/fastcar-framework/SKILL.md +577 -856
- package/skills/fastcar-rpc-microservices/SKILL.md +19 -69
- package/skills/fastcar-serverless/SKILL.md +48 -48
- package/skills/fastcar-toolkit/SKILL.md +22 -31
- package/skills/typescript-coding-style/SKILL.md +144 -0
- package/src/init.js +708 -700
- package/src/pack.js +7 -7
- package/src/skill.js +493 -364
- package/src/update.js +301 -0
- package/src/utils.js +2 -2
|
@@ -5,7 +5,7 @@ description: FastCar 数据库与缓存开发指南。Use when working with Fast
|
|
|
5
5
|
|
|
6
6
|
# FastCar Database
|
|
7
7
|
|
|
8
|
-
FastCar 数据库模块提供基于装饰器的 ORM 支持,涵盖 MySQL、PostgreSQL、MongoDB 和 Redis
|
|
8
|
+
FastCar 数据库模块提供基于装饰器的 ORM 支持,涵盖 MySQL、PostgreSQL、MongoDB 和 Redis。
|
|
9
9
|
|
|
10
10
|
## 模块速查
|
|
11
11
|
|
|
@@ -26,10 +26,17 @@ export default new APP();
|
|
|
26
26
|
#### 定义实体模型
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
import {
|
|
30
|
+
Table,
|
|
31
|
+
Field,
|
|
32
|
+
DBType,
|
|
33
|
+
PrimaryKey,
|
|
34
|
+
NotNull,
|
|
35
|
+
Size,
|
|
36
|
+
} from "@fastcar/core/annotation";
|
|
37
|
+
|
|
38
|
+
@Table("entities")
|
|
39
|
+
class Entity {
|
|
33
40
|
@Field("id")
|
|
34
41
|
@DBType("int")
|
|
35
42
|
@PrimaryKey
|
|
@@ -41,27 +48,16 @@ class User {
|
|
|
41
48
|
@Size({ maxSize: 50 })
|
|
42
49
|
name!: string;
|
|
43
50
|
|
|
44
|
-
@Field("
|
|
51
|
+
@Field("data")
|
|
45
52
|
@DBType("json")
|
|
46
|
-
|
|
53
|
+
data!: any;
|
|
47
54
|
|
|
48
55
|
@Field("created_at")
|
|
49
56
|
@DBType("datetime")
|
|
50
57
|
createdAt!: Date;
|
|
51
58
|
|
|
52
|
-
constructor(args?: Partial<
|
|
53
|
-
if (args)
|
|
54
|
-
Object.assign(this, args);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
toObject() {
|
|
59
|
-
return {
|
|
60
|
-
id: this.id,
|
|
61
|
-
name: this.name,
|
|
62
|
-
profile: this.profile,
|
|
63
|
-
createdAt: this.createdAt,
|
|
64
|
-
};
|
|
59
|
+
constructor(args?: Partial<Entity>) {
|
|
60
|
+
if (args) Object.assign(this, args);
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
```
|
|
@@ -71,300 +67,269 @@ class User {
|
|
|
71
67
|
```typescript
|
|
72
68
|
import { Entity, Repository } from "@fastcar/core/annotation";
|
|
73
69
|
import { MysqlMapper } from "@fastcar/mysql";
|
|
74
|
-
import
|
|
70
|
+
import Entity from "./Entity";
|
|
75
71
|
|
|
76
|
-
@Entity(
|
|
72
|
+
@Entity(Entity)
|
|
77
73
|
@Repository
|
|
78
|
-
class
|
|
79
|
-
|
|
80
|
-
export default UserMapper;
|
|
74
|
+
class EntityMapper extends MysqlMapper<Entity> {}
|
|
75
|
+
export default EntityMapper;
|
|
81
76
|
```
|
|
82
77
|
|
|
83
|
-
#### MysqlMapper
|
|
78
|
+
#### MysqlMapper 核心 API
|
|
84
79
|
|
|
85
80
|
```typescript
|
|
86
81
|
import { Service, Autowired } from "@fastcar/core/annotation";
|
|
87
82
|
import { OrderEnum, OperatorEnum } from "@fastcar/core/db";
|
|
88
|
-
import
|
|
89
|
-
import
|
|
83
|
+
import EntityMapper from "./EntityMapper";
|
|
84
|
+
import Entity from "./Entity";
|
|
90
85
|
|
|
91
86
|
@Service
|
|
92
|
-
class
|
|
87
|
+
class EntityService {
|
|
93
88
|
@Autowired
|
|
94
|
-
private
|
|
89
|
+
private mapper!: EntityMapper;
|
|
95
90
|
|
|
96
|
-
//
|
|
91
|
+
// ===== 查询方法 =====
|
|
97
92
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return this.userMapper.select({});
|
|
93
|
+
// 查询列表
|
|
94
|
+
async list() {
|
|
95
|
+
return this.mapper.select({});
|
|
102
96
|
}
|
|
103
97
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return this.userMapper.selectOne({
|
|
108
|
-
where: { id }
|
|
109
|
-
});
|
|
98
|
+
// 查询单个
|
|
99
|
+
async getOne(id: number) {
|
|
100
|
+
return this.mapper.selectOne({ where: { id } });
|
|
110
101
|
}
|
|
111
102
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
async getUserByPK(id: number) {
|
|
116
|
-
return this.userMapper.selectByPrimaryKey({ id } as User);
|
|
103
|
+
// 根据主键查询
|
|
104
|
+
async getByPK(id: number) {
|
|
105
|
+
return this.mapper.selectByPrimaryKey({ id } as Entity);
|
|
117
106
|
}
|
|
118
107
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return this.userMapper.count({ status: 1 });
|
|
108
|
+
// 统计记录数
|
|
109
|
+
async count() {
|
|
110
|
+
return this.mapper.count({ status: 1 });
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return this.userMapper.exist({ name });
|
|
113
|
+
// 判断是否存在
|
|
114
|
+
async exists(name: string) {
|
|
115
|
+
return this.mapper.exist({ name });
|
|
129
116
|
}
|
|
130
117
|
|
|
131
|
-
//
|
|
118
|
+
// ===== 条件查询 =====
|
|
132
119
|
|
|
133
120
|
// 简单等于条件
|
|
134
121
|
async queryByStatus(status: number) {
|
|
135
|
-
return this.
|
|
136
|
-
where: { status }
|
|
137
|
-
});
|
|
122
|
+
return this.mapper.select({ where: { status } });
|
|
138
123
|
}
|
|
139
124
|
|
|
140
125
|
// 多条件 AND
|
|
141
126
|
async queryByConditions(name: string, status: number) {
|
|
142
|
-
return this.
|
|
143
|
-
where: {
|
|
144
|
-
name,
|
|
145
|
-
status,
|
|
146
|
-
delStatus: false
|
|
147
|
-
}
|
|
127
|
+
return this.mapper.select({
|
|
128
|
+
where: { name, status, delStatus: false },
|
|
148
129
|
});
|
|
149
130
|
}
|
|
150
131
|
|
|
151
132
|
// 比较运算符
|
|
152
|
-
async
|
|
153
|
-
return this.
|
|
154
|
-
where: {
|
|
155
|
-
age: { $gte: minAge, $lte: maxAge }
|
|
156
|
-
}
|
|
133
|
+
async queryByRange(min: number, max: number) {
|
|
134
|
+
return this.mapper.select({
|
|
135
|
+
where: { value: { [OperatorEnum.gte]: min, [OperatorEnum.lte]: max } },
|
|
157
136
|
});
|
|
158
137
|
}
|
|
159
138
|
|
|
160
|
-
// 支持的运算符:
|
|
161
|
-
// $eq - 等于 (默认)
|
|
162
|
-
// $gt / $gte - 大于 / 大于等于
|
|
163
|
-
// $lt / $lte - 小于 / 小于等于
|
|
164
|
-
// $ne - 不等于
|
|
165
|
-
// $in - IN 查询 (数组)
|
|
166
|
-
// $nin - NOT IN 查询
|
|
167
|
-
// $isNull - IS NULL
|
|
168
|
-
// $isNotNull - IS NOT NULL
|
|
169
|
-
|
|
170
139
|
// IN 查询
|
|
171
140
|
async queryByIds(ids: number[]) {
|
|
172
|
-
return this.
|
|
173
|
-
where: { id: {
|
|
141
|
+
return this.mapper.select({
|
|
142
|
+
where: { id: { [OperatorEnum.in]: ids } },
|
|
174
143
|
});
|
|
175
144
|
}
|
|
176
145
|
|
|
177
146
|
// IS NULL 查询
|
|
178
|
-
async
|
|
179
|
-
return this.
|
|
180
|
-
where: { deletedAt: {
|
|
147
|
+
async queryDeleted() {
|
|
148
|
+
return this.mapper.select({
|
|
149
|
+
where: { deletedAt: { [OperatorEnum.isNUll]: true } },
|
|
181
150
|
});
|
|
182
151
|
}
|
|
183
152
|
|
|
184
|
-
//
|
|
153
|
+
// ===== AND 条件查询 =====
|
|
185
154
|
|
|
186
|
-
//
|
|
187
|
-
async
|
|
188
|
-
return this.
|
|
189
|
-
where: { status:
|
|
190
|
-
|
|
191
|
-
// 错误: orders: { createdAt: "DESC" }
|
|
155
|
+
// 方式1:默认多字段自动 AND
|
|
156
|
+
async queryByMultipleConditions(name: string, status: number) {
|
|
157
|
+
return this.mapper.select({
|
|
158
|
+
where: { name, status, delStatus: false },
|
|
159
|
+
// 生成 SQL: WHERE name = ? AND status = ? AND del_status = ?
|
|
192
160
|
});
|
|
193
161
|
}
|
|
194
162
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
orders: {
|
|
203
|
-
status: OrderEnum.asc,
|
|
204
|
-
createdAt: OrderEnum.desc
|
|
205
|
-
}
|
|
163
|
+
// 方式2:同一字段多条件 AND
|
|
164
|
+
async queryByAgeRange(min: number, max: number) {
|
|
165
|
+
return this.mapper.select({
|
|
166
|
+
where: {
|
|
167
|
+
age: { [OperatorEnum.gte]: min, [OperatorEnum.lte]: max },
|
|
168
|
+
// 生成 SQL: WHERE age >= ? AND age <= ?
|
|
169
|
+
},
|
|
206
170
|
});
|
|
207
171
|
}
|
|
208
172
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
173
|
+
// ===== OR 条件查询 =====
|
|
174
|
+
|
|
175
|
+
// 方式1:对象形式(推荐)- 不同字段 OR
|
|
176
|
+
async queryByNameOrEmail(keyword: string) {
|
|
177
|
+
return this.mapper.select({
|
|
178
|
+
where: {
|
|
179
|
+
[JoinEnum.or]: {
|
|
180
|
+
name: keyword,
|
|
181
|
+
email: keyword,
|
|
182
|
+
},
|
|
183
|
+
// 生成 SQL: WHERE name = ? OR email = ?
|
|
184
|
+
},
|
|
215
185
|
});
|
|
216
186
|
}
|
|
217
187
|
|
|
218
|
-
//
|
|
219
|
-
async
|
|
220
|
-
return this.
|
|
221
|
-
|
|
222
|
-
|
|
188
|
+
// 方式2:数组形式 - 复杂条件 OR
|
|
189
|
+
async queryComplexOr() {
|
|
190
|
+
return this.mapper.select({
|
|
191
|
+
where: {
|
|
192
|
+
[JoinEnum.or]: [
|
|
193
|
+
{ status: 1, type: "A" },
|
|
194
|
+
{ status: 2, type: "B" },
|
|
195
|
+
],
|
|
196
|
+
// 生成 SQL: WHERE (status = 1 AND type = 'A') OR (status = 2 AND type = 'B')
|
|
197
|
+
},
|
|
223
198
|
});
|
|
224
199
|
}
|
|
225
200
|
|
|
226
|
-
//
|
|
201
|
+
// ===== AND 与 OR 组合 =====
|
|
227
202
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
203
|
+
async queryComplex(department: string, status: number, keyword: string) {
|
|
204
|
+
return this.mapper.select({
|
|
205
|
+
where: {
|
|
206
|
+
department,
|
|
207
|
+
[JoinEnum.or]: {
|
|
208
|
+
name: { [OperatorEnum.like]: `%${keyword}%` },
|
|
209
|
+
email: { [OperatorEnum.like]: `%${keyword}%` },
|
|
210
|
+
},
|
|
211
|
+
status,
|
|
212
|
+
// 生成 SQL: WHERE department = ? AND (name LIKE ? OR email LIKE ?) AND status = ?
|
|
213
|
+
},
|
|
214
|
+
});
|
|
234
215
|
}
|
|
235
216
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
async
|
|
239
|
-
return this.
|
|
217
|
+
// ===== 排序和分页 =====
|
|
218
|
+
|
|
219
|
+
async getOrdered() {
|
|
220
|
+
return this.mapper.select({
|
|
221
|
+
where: { status: 1 },
|
|
222
|
+
orders: { createdAt: OrderEnum.desc },
|
|
223
|
+
});
|
|
240
224
|
}
|
|
241
225
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
226
|
+
async getPaged(page: number, pageSize: number) {
|
|
227
|
+
return this.mapper.select({
|
|
228
|
+
orders: { id: OrderEnum.desc },
|
|
229
|
+
offset: (page - 1) * pageSize,
|
|
230
|
+
limit: pageSize,
|
|
231
|
+
});
|
|
247
232
|
}
|
|
248
233
|
|
|
249
|
-
//
|
|
234
|
+
// ===== 插入方法 =====
|
|
250
235
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return this.
|
|
255
|
-
where: { id },
|
|
256
|
-
row: { name, updatedAt: new Date() }
|
|
257
|
-
});
|
|
236
|
+
// 插入单条
|
|
237
|
+
async create(name: string) {
|
|
238
|
+
const entity = new Entity({ name, createdAt: new Date() });
|
|
239
|
+
return this.mapper.saveOne(entity);
|
|
258
240
|
}
|
|
259
241
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return this.userMapper.updateOne({ where, row });
|
|
242
|
+
// 批量插入
|
|
243
|
+
async createBatch(list: Entity[]) {
|
|
244
|
+
return this.mapper.saveList(list);
|
|
264
245
|
}
|
|
265
246
|
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
async updateById(user: User) {
|
|
270
|
-
return this.userMapper.updateByPrimaryKey(user);
|
|
247
|
+
// 插入或更新
|
|
248
|
+
async saveOrUpdate(entity: Entity) {
|
|
249
|
+
return this.mapper.saveORUpdate(entity);
|
|
271
250
|
}
|
|
272
251
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
252
|
+
// ===== 更新方法 =====
|
|
253
|
+
|
|
254
|
+
// 条件更新
|
|
255
|
+
async updateName(id: number, name: string) {
|
|
256
|
+
return this.mapper.update({
|
|
276
257
|
where: { id },
|
|
277
|
-
row: {
|
|
258
|
+
row: { name, updatedAt: new Date() },
|
|
278
259
|
});
|
|
279
260
|
}
|
|
280
261
|
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
// 返回: boolean
|
|
285
|
-
async deleteUser(id: number) {
|
|
286
|
-
return this.userMapper.delete({
|
|
287
|
-
where: { id }
|
|
288
|
-
});
|
|
262
|
+
// 更新单条
|
|
263
|
+
async updateOne(where: any, row: any) {
|
|
264
|
+
return this.mapper.updateOne({ where, row });
|
|
289
265
|
}
|
|
290
266
|
|
|
291
|
-
//
|
|
292
|
-
async
|
|
293
|
-
return this.
|
|
267
|
+
// 根据主键更新
|
|
268
|
+
async updateById(entity: Entity) {
|
|
269
|
+
return this.mapper.updateByPrimaryKey(entity);
|
|
294
270
|
}
|
|
295
271
|
|
|
296
|
-
//
|
|
297
|
-
async
|
|
298
|
-
return this.
|
|
272
|
+
// 软删除
|
|
273
|
+
async softDelete(id: number) {
|
|
274
|
+
return this.mapper.update({
|
|
275
|
+
where: { id },
|
|
276
|
+
row: { delStatus: true, deletedAt: new Date() },
|
|
277
|
+
});
|
|
299
278
|
}
|
|
300
279
|
|
|
301
|
-
//
|
|
280
|
+
// ===== 删除方法 =====
|
|
302
281
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
async customQuery() {
|
|
306
|
-
return this.userMapper.query(
|
|
307
|
-
"SELECT * FROM users WHERE status = ? AND created_at > ?",
|
|
308
|
-
[1, "2024-01-01"]
|
|
309
|
-
);
|
|
282
|
+
async deleteById(id: number) {
|
|
283
|
+
return this.mapper.delete({ where: { id } });
|
|
310
284
|
}
|
|
311
285
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return this.userMapper.execute(
|
|
315
|
-
"UPDATE users SET login_count = login_count + 1 WHERE id = ?",
|
|
316
|
-
[1]
|
|
317
|
-
);
|
|
286
|
+
async deleteOne(where: any) {
|
|
287
|
+
return this.mapper.deleteOne(where);
|
|
318
288
|
}
|
|
319
289
|
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
290
|
+
// ===== 高级查询 =====
|
|
291
|
+
|
|
292
|
+
// selectByCustom 支持 JOIN、分组、聚合
|
|
293
|
+
async advancedQuery() {
|
|
294
|
+
// 指定字段 + 泛型类型
|
|
295
|
+
const results = await this.mapper.selectByCustom<{
|
|
296
|
+
id: number;
|
|
297
|
+
name: string;
|
|
298
|
+
relatedName: string;
|
|
299
|
+
}>({
|
|
300
|
+
tableAlias: "t",
|
|
301
|
+
fields: ["t.id", "t.name", "r.name as relatedName"],
|
|
323
302
|
join: [
|
|
324
303
|
{
|
|
325
304
|
type: "LEFT",
|
|
326
|
-
table: "
|
|
327
|
-
on: "
|
|
305
|
+
table: "related_table r",
|
|
306
|
+
on: "r.entity_id = t.id",
|
|
328
307
|
},
|
|
329
308
|
],
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// 强制索引
|
|
336
|
-
async forceIndexQuery() {
|
|
337
|
-
return this.userMapper.select({
|
|
338
|
-
forceIndex: ["idx_name"],
|
|
339
|
-
where: { status: 1 }
|
|
309
|
+
where: { "t.status": 1 },
|
|
310
|
+
camelcaseStyle: true,
|
|
340
311
|
});
|
|
341
|
-
}
|
|
342
312
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
313
|
+
// 聚合查询
|
|
314
|
+
const stats = await this.mapper.selectByCustom({
|
|
315
|
+
fields: [
|
|
316
|
+
"status",
|
|
317
|
+
"COUNT(*) as totalCount",
|
|
318
|
+
"MAX(created_at) as lastCreated",
|
|
319
|
+
],
|
|
320
|
+
groups: ["status"],
|
|
321
|
+
orders: { totalCount: OrderEnum.desc },
|
|
348
322
|
});
|
|
349
|
-
}
|
|
350
323
|
|
|
351
|
-
|
|
352
|
-
async groupByStatus() {
|
|
353
|
-
return this.userMapper.selectByCustom({
|
|
354
|
-
fields: ["status", "COUNT(*) as count"],
|
|
355
|
-
groups: ["status"]
|
|
356
|
-
});
|
|
324
|
+
return { results, stats };
|
|
357
325
|
}
|
|
358
326
|
|
|
359
|
-
//
|
|
360
|
-
async
|
|
361
|
-
return this.
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
'DATE_FORMAT(created_at, "%Y-%m-%d") as createdDate'
|
|
366
|
-
]
|
|
367
|
-
});
|
|
327
|
+
// 自定义 SQL
|
|
328
|
+
async customQuery() {
|
|
329
|
+
return this.mapper.query(
|
|
330
|
+
"SELECT * FROM entities WHERE status = ? AND created_at > ?",
|
|
331
|
+
[1, "2024-01-01"],
|
|
332
|
+
);
|
|
368
333
|
}
|
|
369
334
|
}
|
|
370
335
|
```
|
|
@@ -372,45 +337,43 @@ class UserService {
|
|
|
372
337
|
#### 常用查询条件速查表
|
|
373
338
|
|
|
374
339
|
```typescript
|
|
340
|
+
import { OperatorEnum, OrderEnum } from "@fastcar/core/db";
|
|
341
|
+
|
|
375
342
|
// 等于 (默认)
|
|
376
343
|
{ where: { status: 1 } }
|
|
377
344
|
|
|
378
345
|
// 不等于
|
|
379
|
-
{ where: { status: {
|
|
380
|
-
|
|
381
|
-
// 大于 / 大于等于
|
|
382
|
-
{ where: { age: { $gt: 18 } } }
|
|
383
|
-
{ where: { age: { $gte: 18 } } }
|
|
346
|
+
{ where: { status: { [OperatorEnum.neq]: 1 } } }
|
|
384
347
|
|
|
385
|
-
// 小于 / 小于等于
|
|
386
|
-
{ where: { age: {
|
|
387
|
-
{ where: { age: {
|
|
348
|
+
// 大于 / 大于等于 / 小于 / 小于等于
|
|
349
|
+
{ where: { age: { [OperatorEnum.gt]: 18 } } }
|
|
350
|
+
{ where: { age: { [OperatorEnum.gte]: 18, [OperatorEnum.lte]: 60 } } }
|
|
388
351
|
|
|
389
|
-
//
|
|
390
|
-
{ where: {
|
|
352
|
+
// IN / NOT IN
|
|
353
|
+
{ where: { id: { [OperatorEnum.in]: [1, 2, 3] } } }
|
|
354
|
+
{ where: { id: { [OperatorEnum.notin]: [1, 2, 3] } } }
|
|
391
355
|
|
|
392
|
-
//
|
|
393
|
-
{ where: {
|
|
356
|
+
// IS NULL / IS NOT NULL
|
|
357
|
+
{ where: { deletedAt: { [OperatorEnum.isNUll]: true } } }
|
|
358
|
+
{ where: { deletedAt: { [OperatorEnum.isNotNull]: true } } }
|
|
394
359
|
|
|
395
|
-
//
|
|
396
|
-
{ where: {
|
|
360
|
+
// AND(默认,多字段自动 AND)
|
|
361
|
+
{ where: { name: "A", status: 1 } }
|
|
397
362
|
|
|
398
|
-
//
|
|
399
|
-
{ where: {
|
|
363
|
+
// OR(对象形式 - 推荐,不同字段)
|
|
364
|
+
{ where: { [JoinEnum.or]: { name: "A", email: "A" } } }
|
|
400
365
|
|
|
401
|
-
//
|
|
402
|
-
{ where: {
|
|
366
|
+
// OR(数组形式,复杂条件)
|
|
367
|
+
{ where: { [JoinEnum.or]: [{ status: 1 }, { status: 2 }] } }
|
|
403
368
|
|
|
404
|
-
//
|
|
405
|
-
{ where: { status: 1,
|
|
369
|
+
// AND + OR 组合
|
|
370
|
+
{ where: { status: 1, [JoinEnum.or]: { type: 1, category: 2 } } }
|
|
406
371
|
|
|
407
|
-
// 排序
|
|
408
|
-
import { OrderEnum } from "@fastcar/core/db";
|
|
372
|
+
// 排序
|
|
409
373
|
{ orders: { createdAt: OrderEnum.desc } }
|
|
410
|
-
{ orders: { createdAt: OrderEnum.asc } }
|
|
411
374
|
|
|
412
375
|
// 分页
|
|
413
|
-
{
|
|
376
|
+
{ offset: 0, limit: 10 }
|
|
414
377
|
```
|
|
415
378
|
|
|
416
379
|
#### 事务处理
|
|
@@ -420,35 +383,33 @@ import { SqlSession } from "@fastcar/core/annotation";
|
|
|
420
383
|
import { MysqlDataSourceManager } from "@fastcar/mysql";
|
|
421
384
|
|
|
422
385
|
@Service
|
|
423
|
-
class
|
|
386
|
+
class BizService {
|
|
424
387
|
@Autowired
|
|
425
388
|
private dsm!: MysqlDataSourceManager;
|
|
426
389
|
|
|
427
390
|
@Autowired
|
|
428
|
-
private
|
|
391
|
+
private mapperA!: MapperA;
|
|
429
392
|
|
|
430
393
|
@Autowired
|
|
431
|
-
private
|
|
394
|
+
private mapperB!: MapperB;
|
|
432
395
|
|
|
433
|
-
async
|
|
396
|
+
async transactionExample(dataA: any, dataB: any) {
|
|
434
397
|
const sessionId = await this.dsm.beginTransaction();
|
|
435
|
-
|
|
398
|
+
|
|
436
399
|
try {
|
|
437
|
-
|
|
438
|
-
await this.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
400
|
+
await this.mapperA.saveOne(dataA, undefined, sessionId);
|
|
401
|
+
await this.mapperB.update(
|
|
402
|
+
{
|
|
403
|
+
where: { id: dataB.id },
|
|
404
|
+
row: { status: dataB.status },
|
|
405
|
+
},
|
|
406
|
+
undefined,
|
|
407
|
+
sessionId,
|
|
408
|
+
);
|
|
409
|
+
|
|
447
410
|
await this.dsm.commit(sessionId);
|
|
448
|
-
|
|
449
411
|
return true;
|
|
450
412
|
} catch (error) {
|
|
451
|
-
// 回滚事务
|
|
452
413
|
await this.dsm.rollback(sessionId);
|
|
453
414
|
throw error;
|
|
454
415
|
}
|
|
@@ -458,81 +419,45 @@ class OrderService {
|
|
|
458
419
|
|
|
459
420
|
### PostgreSQL (@fastcar/pgsql)
|
|
460
421
|
|
|
461
|
-
#### 开启 PostgreSQL
|
|
462
|
-
|
|
463
422
|
```typescript
|
|
464
|
-
import { Application } from "@fastcar/core/annotation";
|
|
465
423
|
import { EnablePgsql } from "@fastcar/pgsql/annotation";
|
|
424
|
+
import { PgsqlMapper } from "@fastcar/pgsql";
|
|
466
425
|
|
|
467
426
|
@Application
|
|
468
427
|
@EnablePgsql
|
|
469
428
|
class APP {}
|
|
470
|
-
export default new APP();
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
#### 定义 Mapper
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
import { Entity, Repository } from "@fastcar/core/annotation";
|
|
477
|
-
import { PgsqlMapper } from "@fastcar/pgsql";
|
|
478
|
-
import User from "./User";
|
|
479
429
|
|
|
480
|
-
@Entity(
|
|
430
|
+
@Entity(Entity)
|
|
481
431
|
@Repository
|
|
482
|
-
class
|
|
483
|
-
|
|
484
|
-
export default UserMapper;
|
|
432
|
+
class EntityMapper extends PgsqlMapper<Entity> {}
|
|
485
433
|
```
|
|
486
434
|
|
|
487
|
-
用法与 `MysqlMapper`
|
|
435
|
+
用法与 `MysqlMapper` 基本一致。
|
|
488
436
|
|
|
489
437
|
### MongoDB (@fastcar/mongo)
|
|
490
438
|
|
|
491
|
-
#### 开启 MongoDB
|
|
492
|
-
|
|
493
439
|
```typescript
|
|
494
|
-
import { Application } from "@fastcar/core/annotation";
|
|
495
440
|
import { EnableMongo } from "@fastcar/mongo/annotation";
|
|
441
|
+
import { MongoMapper } from "@fastcar/mongo";
|
|
496
442
|
|
|
497
443
|
@Application
|
|
498
444
|
@EnableMongo
|
|
499
445
|
class APP {}
|
|
500
|
-
export default new APP();
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
#### 定义 Mapper
|
|
504
446
|
|
|
505
|
-
|
|
506
|
-
import { Entity, Repository } from "@fastcar/core/annotation";
|
|
507
|
-
import { MongoMapper } from "@fastcar/mongo";
|
|
508
|
-
import User from "./User";
|
|
509
|
-
|
|
510
|
-
@Entity(User)
|
|
447
|
+
@Entity(Entity)
|
|
511
448
|
@Repository
|
|
512
|
-
class
|
|
513
|
-
|
|
514
|
-
export default UserMapper;
|
|
449
|
+
class EntityMapper extends MongoMapper<Entity> {}
|
|
515
450
|
```
|
|
516
451
|
|
|
517
452
|
### Redis (@fastcar/redis)
|
|
518
453
|
|
|
519
|
-
#### 开启 Redis
|
|
520
|
-
|
|
521
454
|
```typescript
|
|
522
|
-
import { Application } from "@fastcar/core/annotation";
|
|
523
455
|
import { EnableRedis } from "@fastcar/redis/annotation";
|
|
456
|
+
import { RedisClient } from "@fastcar/redis/annotation";
|
|
524
457
|
|
|
525
458
|
@Application
|
|
526
459
|
@EnableRedis
|
|
527
460
|
class APP {}
|
|
528
|
-
export default new APP();
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
#### 使用 RedisClient
|
|
532
|
-
|
|
533
|
-
```typescript
|
|
534
|
-
import { Service, Autowired } from "@fastcar/core/annotation";
|
|
535
|
-
import { RedisClient } from "@fastcar/redis/annotation";
|
|
536
461
|
|
|
537
462
|
@Service
|
|
538
463
|
class CacheService {
|
|
@@ -551,10 +476,6 @@ class CacheService {
|
|
|
551
476
|
await this.redis.del(key);
|
|
552
477
|
}
|
|
553
478
|
|
|
554
|
-
async expire(key: string, seconds: number) {
|
|
555
|
-
await this.redis.expire(key, seconds);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
479
|
// Hash 操作
|
|
559
480
|
async hset(key: string, field: string, value: string) {
|
|
560
481
|
await this.redis.hset(key, field, value);
|
|
@@ -577,17 +498,19 @@ class CacheService {
|
|
|
577
498
|
|
|
578
499
|
## 数据库逆向生成 (@fastcar/mysql-tool)
|
|
579
500
|
|
|
580
|
-
### 生成配置文件
|
|
581
|
-
|
|
582
501
|
```bash
|
|
502
|
+
# 生成配置文件
|
|
583
503
|
fastcar-cli reverse:init
|
|
504
|
+
|
|
505
|
+
# 执行逆向生成
|
|
506
|
+
fastcar-cli reverse
|
|
584
507
|
```
|
|
585
508
|
|
|
586
|
-
|
|
509
|
+
配置文件示例:
|
|
587
510
|
|
|
588
511
|
```json
|
|
589
512
|
{
|
|
590
|
-
"tables": ["
|
|
513
|
+
"tables": ["table1", "table2"],
|
|
591
514
|
"modelDir": "D:/project/src/model",
|
|
592
515
|
"mapperDir": "D:/project/src/mapper",
|
|
593
516
|
"dbConfig": {
|
|
@@ -596,25 +519,10 @@ fastcar-cli reverse:init
|
|
|
596
519
|
"user": "root",
|
|
597
520
|
"password": "password",
|
|
598
521
|
"database": "test_db"
|
|
599
|
-
}
|
|
600
|
-
"style": {
|
|
601
|
-
"tabWidth": 4,
|
|
602
|
-
"printWidth": 200,
|
|
603
|
-
"trailingComma": "es5",
|
|
604
|
-
"useTabs": true,
|
|
605
|
-
"parser": "typescript",
|
|
606
|
-
"endOfLine": "crlf"
|
|
607
|
-
},
|
|
608
|
-
"ignoreCamelcase": false
|
|
522
|
+
}
|
|
609
523
|
}
|
|
610
524
|
```
|
|
611
525
|
|
|
612
|
-
### 执行逆向生成
|
|
613
|
-
|
|
614
|
-
```bash
|
|
615
|
-
fastcar-cli reverse
|
|
616
|
-
```
|
|
617
|
-
|
|
618
526
|
## application.yml 数据库配置
|
|
619
527
|
|
|
620
528
|
```yaml
|
|
@@ -627,10 +535,7 @@ mysql:
|
|
|
627
535
|
database: mydb
|
|
628
536
|
username: root
|
|
629
537
|
password: password
|
|
630
|
-
# 连接池配置
|
|
631
538
|
connectionLimit: 10
|
|
632
|
-
# 是否使用预处理语句
|
|
633
|
-
useServerPrepStmts: true
|
|
634
539
|
|
|
635
540
|
pgsql:
|
|
636
541
|
host: localhost
|
|
@@ -653,13 +558,13 @@ redis:
|
|
|
653
558
|
|
|
654
559
|
## 完整模块列表
|
|
655
560
|
|
|
656
|
-
| 模块
|
|
657
|
-
|
|
658
|
-
| @fastcar/mysql
|
|
659
|
-
| @fastcar/pgsql
|
|
660
|
-
| @fastcar/mongo
|
|
661
|
-
| @fastcar/redis
|
|
662
|
-
| @fastcar/mysql-tool | `npm i @fastcar/mysql-tool` | 逆向生成工具
|
|
561
|
+
| 模块 | 安装命令 | 用途 |
|
|
562
|
+
| ------------------- | --------------------------- | -------------- |
|
|
563
|
+
| @fastcar/mysql | `npm i @fastcar/mysql` | MySQL ORM |
|
|
564
|
+
| @fastcar/pgsql | `npm i @fastcar/pgsql` | PostgreSQL ORM |
|
|
565
|
+
| @fastcar/mongo | `npm i @fastcar/mongo` | MongoDB |
|
|
566
|
+
| @fastcar/redis | `npm i @fastcar/redis` | Redis 缓存 |
|
|
567
|
+
| @fastcar/mysql-tool | `npm i @fastcar/mysql-tool` | 逆向生成工具 |
|
|
663
568
|
|
|
664
569
|
## 快速开始
|
|
665
570
|
|
|
@@ -679,6 +584,200 @@ npm run debug
|
|
|
679
584
|
|
|
680
585
|
1. **排序必须使用 OrderEnum**:`orders: { createdAt: OrderEnum.desc }`,不能使用字符串 `"DESC"`
|
|
681
586
|
2. **主键查询**:`selectByPrimaryKey` 和 `updateByPrimaryKey` 需要传入包含主键字段的对象
|
|
682
|
-
3.
|
|
683
|
-
4.
|
|
684
|
-
|
|
587
|
+
3. **批量插入**:`saveList` 会自动分批处理(每批1000条)
|
|
588
|
+
4. **软删除**:建议使用 `update` 方法更新 `delStatus` 字段,而不是物理删除
|
|
589
|
+
|
|
590
|
+
## 数据库查询最佳实践
|
|
591
|
+
|
|
592
|
+
### ⚠️ 分页查询 - 必须使用数据库层分页
|
|
593
|
+
|
|
594
|
+
**核心原则**:分页必须在数据库层完成,严禁全表查询后在内存中切片。
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// ✅ 正确:使用 SQL LIMIT/OFFSET 分页(数据库层完成分页)
|
|
598
|
+
const list = await this.mapper.select({
|
|
599
|
+
where: { status: 1 },
|
|
600
|
+
orders: { id: OrderEnum.desc },
|
|
601
|
+
offset: (page - 1) * pageSize,
|
|
602
|
+
limit: pageSize, // 只取需要的记录
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// ✅ 正确:使用游标/滚动分页(适合大数据量)
|
|
606
|
+
const list = await this.mapper.select({
|
|
607
|
+
where: {
|
|
608
|
+
id: { [OperatorEnum.lt]: lastId } // 基于上一页最后ID
|
|
609
|
+
},
|
|
610
|
+
orders: { id: OrderEnum.desc },
|
|
611
|
+
limit: pageSize,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// ❌ 错误:全表查询后在内存中切片(数据量大时会导致 OOM)
|
|
615
|
+
const allData = await this.mapper.select({ where: { status: 1 } }); // 可能百万级数据
|
|
616
|
+
const pageData = allData.slice((page - 1) * pageSize, page * pageSize); // 内存中切片
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**为什么重要**:
|
|
620
|
+
- 全表查询会将所有数据加载到 Node.js 内存,数据量大时会导致内存溢出(OOM)
|
|
621
|
+
- 网络传输大量无用数据,严重影响性能
|
|
622
|
+
- 数据库层分页只返回需要的记录,内存占用固定
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
### ⚠️ 分组聚合 - 必须使用数据库层 GROUP BY
|
|
627
|
+
|
|
628
|
+
**核心原则**:分组统计必须在数据库层完成,严禁全表查询后在 JS 中分组。
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// ✅ 正确:使用 SQL GROUP BY 分组(数据库层聚合)
|
|
632
|
+
const stats = await this.mapper.selectByCustom({
|
|
633
|
+
fields: [
|
|
634
|
+
"status",
|
|
635
|
+
"COUNT(*) as count",
|
|
636
|
+
"SUM(amount) as totalAmount",
|
|
637
|
+
"MAX(created_at) as latestTime",
|
|
638
|
+
],
|
|
639
|
+
groups: ["status"], // 数据库层分组
|
|
640
|
+
orders: { count: OrderEnum.desc },
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ✅ 正确:使用 JOIN + GROUP BY 关联聚合
|
|
644
|
+
const userOrderStats = await this.mapper.selectByCustom({
|
|
645
|
+
fields: [
|
|
646
|
+
"u.id",
|
|
647
|
+
"u.name",
|
|
648
|
+
"COUNT(o.id) as orderCount",
|
|
649
|
+
"SUM(o.amount) as totalAmount",
|
|
650
|
+
],
|
|
651
|
+
join: [{
|
|
652
|
+
type: "LEFT",
|
|
653
|
+
table: "orders o",
|
|
654
|
+
on: "o.user_id = u.id",
|
|
655
|
+
}],
|
|
656
|
+
groups: ["u.id", "u.name"], // 数据库层分组聚合
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// ❌ 错误:全表查询后在 JS 中分组(数据量大时会导致 OOM)
|
|
660
|
+
const allRecords = await this.mapper.select({}); // 加载所有数据
|
|
661
|
+
const grouped = allRecords.reduce((acc, item) => {
|
|
662
|
+
if (!acc[item.status]) acc[item.status] = { count: 0, total: 0 };
|
|
663
|
+
acc[item.status].count++;
|
|
664
|
+
acc[item.status].total += item.amount;
|
|
665
|
+
return acc;
|
|
666
|
+
}, {}); // 内存中分组,大数据量时性能极差
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
**为什么重要**:
|
|
670
|
+
- 数据库专为聚合计算优化,性能远高于 JS 遍历
|
|
671
|
+
- 减少网络传输(只返回聚合结果,而非原始数据)
|
|
672
|
+
- 避免 JS 单线程处理大量数据的性能瓶颈
|
|
673
|
+
- 防止内存溢出风险
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
## 编码规范
|
|
678
|
+
|
|
679
|
+
### 1. 实体对象创建规范
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
// ✅ 正确:使用 key-value 形式创建对象
|
|
683
|
+
const entity = new Entity({
|
|
684
|
+
name: "示例",
|
|
685
|
+
status: 1,
|
|
686
|
+
createTime: new Date(),
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// ❌ 错误:逐行赋值创建对象
|
|
690
|
+
const entity = new Entity();
|
|
691
|
+
entity.name = "示例";
|
|
692
|
+
entity.status = 1;
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
### 2. 分页查询规范
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
// ✅ 正确:使用 SQL limit/offset 分页
|
|
699
|
+
const total = await this.mapper.count(where);
|
|
700
|
+
const list = await this.mapper.select({
|
|
701
|
+
where: where,
|
|
702
|
+
orders: { createTime: OrderEnum.desc },
|
|
703
|
+
offset: (page - 1) * pageSize,
|
|
704
|
+
limit: pageSize,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// ❌ 错误:先全表查询再用 JS slice 分页
|
|
708
|
+
const list = await this.mapper.select({ where });
|
|
709
|
+
const pageData = list.slice((page - 1) * pageSize, page * pageSize);
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### 3. 查询条件规范
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
import { OperatorEnum } from "@fastcar/core/db";
|
|
716
|
+
|
|
717
|
+
// ✅ 正确:使用 OperatorEnum
|
|
718
|
+
const list = await this.mapper.select({
|
|
719
|
+
where: {
|
|
720
|
+
age: { [OperatorEnum.gte]: 18, [OperatorEnum.lte]: 60 },
|
|
721
|
+
status: { [OperatorEnum.in]: [1, 2, 3] },
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### 4. 接口返回规范
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
// ✅ 正确:返回空数据
|
|
730
|
+
if (records.length === 0) {
|
|
731
|
+
return Result.ok({ list: [], total: 0 });
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// ❌ 错误:返回模拟数据
|
|
735
|
+
if (records.length === 0) {
|
|
736
|
+
return Result.ok({ list: [{ name: "模拟数据1" }] });
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### 5. 更新操作规范
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// ✅ 正确:更新少于3个字段时使用 update/updateOne
|
|
744
|
+
await this.mapper.updateOne({
|
|
745
|
+
where: { id },
|
|
746
|
+
row: { lastLoginTime: new Date() },
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// ❌ 错误:为了更新1-2个字段而查询整个实体对象
|
|
750
|
+
const entity = await this.mapper.selectByPrimaryKey({ id });
|
|
751
|
+
entity.lastLoginTime = new Date();
|
|
752
|
+
await this.mapper.updateByPrimaryKey(entity);
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### 6. 复杂查询优化
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
// ✅ 正确:使用 selectByCustom + JOIN 一条 SQL 完成
|
|
759
|
+
interface QueryResult {
|
|
760
|
+
id: number;
|
|
761
|
+
name: string;
|
|
762
|
+
relatedName: string;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const results = await this.mapper.selectByCustom<QueryResult>({
|
|
766
|
+
tableAlias: "t",
|
|
767
|
+
fields: ["t.id", "t.name", "r.name as relatedName"],
|
|
768
|
+
join: [{
|
|
769
|
+
type: "INNER",
|
|
770
|
+
table: "related_table r",
|
|
771
|
+
on: "r.entity_id = t.id",
|
|
772
|
+
}],
|
|
773
|
+
where: { "t.status": 1 },
|
|
774
|
+
camelcaseStyle: true,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// ❌ 错误:多次查询 + 内存组装(N+1 问题)
|
|
778
|
+
const list = await this.mapper.select({});
|
|
779
|
+
for (const item of list) {
|
|
780
|
+
const related = await this.relatedMapper.selectOne({ ... });
|
|
781
|
+
// 内存组装...
|
|
782
|
+
}
|
|
783
|
+
```
|