@fastcar/cli 0.1.1 → 0.1.3
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 +21 -9
- package/package.json +2 -2
- package/skills/fastcar-database/SKILL.md +380 -147
- package/skills/fastcar-framework/SKILL.md +278 -186
- 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/pack.js +7 -7
- package/src/reverse.js +86 -12
- 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,10 @@ export default new APP();
|
|
|
26
26
|
#### 定义实体模型
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
import { Table, Field, DBType, PrimaryKey, NotNull, Size
|
|
29
|
+
import { Table, Field, DBType, PrimaryKey, NotNull, Size } from "@fastcar/core/annotation";
|
|
30
30
|
|
|
31
|
-
@Table("
|
|
32
|
-
class
|
|
31
|
+
@Table("entities")
|
|
32
|
+
class Entity {
|
|
33
33
|
@Field("id")
|
|
34
34
|
@DBType("int")
|
|
35
35
|
@PrimaryKey
|
|
@@ -41,13 +41,16 @@ class User {
|
|
|
41
41
|
@Size({ maxSize: 50 })
|
|
42
42
|
name!: string;
|
|
43
43
|
|
|
44
|
-
@Field("
|
|
44
|
+
@Field("data")
|
|
45
45
|
@DBType("json")
|
|
46
|
-
|
|
47
|
-
profile: any;
|
|
46
|
+
data!: any;
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
@Field("created_at")
|
|
49
|
+
@DBType("datetime")
|
|
50
|
+
createdAt!: Date;
|
|
51
|
+
|
|
52
|
+
constructor(args?: Partial<Entity>) {
|
|
53
|
+
if (args) Object.assign(this, args);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
```
|
|
@@ -57,206 +60,315 @@ class User {
|
|
|
57
60
|
```typescript
|
|
58
61
|
import { Entity, Repository } from "@fastcar/core/annotation";
|
|
59
62
|
import { MysqlMapper } from "@fastcar/mysql";
|
|
60
|
-
import
|
|
63
|
+
import Entity from "./Entity";
|
|
61
64
|
|
|
62
|
-
@Entity(
|
|
65
|
+
@Entity(Entity)
|
|
63
66
|
@Repository
|
|
64
|
-
class
|
|
65
|
-
|
|
66
|
-
export default UserMapper;
|
|
67
|
+
class EntityMapper extends MysqlMapper<Entity> {}
|
|
68
|
+
export default EntityMapper;
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
####
|
|
71
|
+
#### MysqlMapper 核心 API
|
|
70
72
|
|
|
71
73
|
```typescript
|
|
72
74
|
import { Service, Autowired } from "@fastcar/core/annotation";
|
|
73
|
-
import { OrderEnum } from "@fastcar/core/db";
|
|
74
|
-
import
|
|
75
|
-
import
|
|
75
|
+
import { OrderEnum, OperatorEnum } from "@fastcar/core/db";
|
|
76
|
+
import EntityMapper from "./EntityMapper";
|
|
77
|
+
import Entity from "./Entity";
|
|
76
78
|
|
|
77
79
|
@Service
|
|
78
|
-
class
|
|
80
|
+
class EntityService {
|
|
79
81
|
@Autowired
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
private mapper!: EntityMapper;
|
|
83
|
+
|
|
84
|
+
// ===== 查询方法 =====
|
|
85
|
+
|
|
86
|
+
// 查询列表
|
|
87
|
+
async list() {
|
|
88
|
+
return this.mapper.select({});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 查询单个
|
|
92
|
+
async getOne(id: number) {
|
|
93
|
+
return this.mapper.selectOne({ where: { id } });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 根据主键查询
|
|
97
|
+
async getByPK(id: number) {
|
|
98
|
+
return this.mapper.selectByPrimaryKey({ id } as Entity);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 统计记录数
|
|
102
|
+
async count() {
|
|
103
|
+
return this.mapper.count({ status: 1 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 判断是否存在
|
|
107
|
+
async exists(name: string) {
|
|
108
|
+
return this.mapper.exist({ name });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ===== 条件查询 =====
|
|
112
|
+
|
|
113
|
+
// 简单等于条件
|
|
114
|
+
async queryByStatus(status: number) {
|
|
115
|
+
return this.mapper.select({ where: { status } });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 多条件 AND
|
|
119
|
+
async queryByConditions(name: string, status: number) {
|
|
120
|
+
return this.mapper.select({
|
|
121
|
+
where: { name, status, delStatus: false }
|
|
96
122
|
});
|
|
97
123
|
}
|
|
98
124
|
|
|
99
|
-
//
|
|
125
|
+
// 比较运算符
|
|
126
|
+
async queryByRange(min: number, max: number) {
|
|
127
|
+
return this.mapper.select({
|
|
128
|
+
where: { value: { [OperatorEnum.gte]: min, [OperatorEnum.lte]: max } }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// IN 查询
|
|
100
133
|
async queryByIds(ids: number[]) {
|
|
101
|
-
return this.
|
|
102
|
-
where: { id: {
|
|
134
|
+
return this.mapper.select({
|
|
135
|
+
where: { id: { [OperatorEnum.in]: ids } }
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// IS NULL 查询
|
|
140
|
+
async queryDeleted() {
|
|
141
|
+
return this.mapper.select({
|
|
142
|
+
where: { deletedAt: { [OperatorEnum.isNUll]: true } }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ===== 排序和分页 =====
|
|
147
|
+
|
|
148
|
+
async getOrdered() {
|
|
149
|
+
return this.mapper.select({
|
|
150
|
+
where: { status: 1 },
|
|
151
|
+
orders: { createdAt: OrderEnum.desc }
|
|
103
152
|
});
|
|
104
153
|
}
|
|
105
154
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
155
|
+
async getPaged(page: number, pageSize: number) {
|
|
156
|
+
return this.mapper.select({
|
|
157
|
+
orders: { id: OrderEnum.desc },
|
|
158
|
+
offset: (page - 1) * pageSize,
|
|
159
|
+
limit: pageSize
|
|
160
|
+
});
|
|
110
161
|
}
|
|
111
162
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
163
|
+
// ===== 插入方法 =====
|
|
164
|
+
|
|
165
|
+
// 插入单条
|
|
166
|
+
async create(name: string) {
|
|
167
|
+
const entity = new Entity({ name, createdAt: new Date() });
|
|
168
|
+
return this.mapper.saveOne(entity);
|
|
115
169
|
}
|
|
116
170
|
|
|
117
|
-
//
|
|
171
|
+
// 批量插入
|
|
172
|
+
async createBatch(list: Entity[]) {
|
|
173
|
+
return this.mapper.saveList(list);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 插入或更新
|
|
177
|
+
async saveOrUpdate(entity: Entity) {
|
|
178
|
+
return this.mapper.saveORUpdate(entity);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ===== 更新方法 =====
|
|
182
|
+
|
|
183
|
+
// 条件更新
|
|
118
184
|
async updateName(id: number, name: string) {
|
|
119
|
-
return this.
|
|
185
|
+
return this.mapper.update({
|
|
186
|
+
where: { id },
|
|
187
|
+
row: { name, updatedAt: new Date() }
|
|
188
|
+
});
|
|
120
189
|
}
|
|
121
190
|
|
|
122
|
-
//
|
|
123
|
-
async
|
|
124
|
-
return this.
|
|
191
|
+
// 更新单条
|
|
192
|
+
async updateOne(where: any, row: any) {
|
|
193
|
+
return this.mapper.updateOne({ where, row });
|
|
125
194
|
}
|
|
126
195
|
|
|
127
|
-
//
|
|
128
|
-
async
|
|
129
|
-
return this.
|
|
196
|
+
// 根据主键更新
|
|
197
|
+
async updateById(entity: Entity) {
|
|
198
|
+
return this.mapper.updateByPrimaryKey(entity);
|
|
130
199
|
}
|
|
131
200
|
|
|
132
|
-
//
|
|
133
|
-
async
|
|
134
|
-
return this.
|
|
201
|
+
// 软删除
|
|
202
|
+
async softDelete(id: number) {
|
|
203
|
+
return this.mapper.update({
|
|
204
|
+
where: { id },
|
|
205
|
+
row: { delStatus: true, deletedAt: new Date() }
|
|
206
|
+
});
|
|
135
207
|
}
|
|
136
208
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
209
|
+
// ===== 删除方法 =====
|
|
210
|
+
|
|
211
|
+
async deleteById(id: number) {
|
|
212
|
+
return this.mapper.delete({ where: { id } });
|
|
140
213
|
}
|
|
141
214
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return this.userMapper.execute("SELECT * FROM users WHERE id = 1");
|
|
215
|
+
async deleteOne(where: any) {
|
|
216
|
+
return this.mapper.deleteOne(where);
|
|
145
217
|
}
|
|
146
218
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
219
|
+
// ===== 高级查询 =====
|
|
220
|
+
|
|
221
|
+
// selectByCustom 支持 JOIN、分组、聚合
|
|
222
|
+
async advancedQuery() {
|
|
223
|
+
// 指定字段 + 泛型类型
|
|
224
|
+
const results = await this.mapper.selectByCustom<{
|
|
225
|
+
id: number;
|
|
226
|
+
name: string;
|
|
227
|
+
relatedName: string;
|
|
228
|
+
}>({
|
|
157
229
|
tableAlias: "t",
|
|
230
|
+
fields: ["t.id", "t.name", "r.name as relatedName"],
|
|
231
|
+
join: [{
|
|
232
|
+
type: "LEFT",
|
|
233
|
+
table: "related_table r",
|
|
234
|
+
on: "r.entity_id = t.id"
|
|
235
|
+
}],
|
|
236
|
+
where: { "t.status": 1 },
|
|
237
|
+
camelcaseStyle: true,
|
|
158
238
|
});
|
|
159
|
-
}
|
|
160
239
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
240
|
+
// 聚合查询
|
|
241
|
+
const stats = await this.mapper.selectByCustom({
|
|
242
|
+
fields: [
|
|
243
|
+
"status",
|
|
244
|
+
"COUNT(*) as totalCount",
|
|
245
|
+
"MAX(created_at) as lastCreated"
|
|
246
|
+
],
|
|
247
|
+
groups: ["status"],
|
|
248
|
+
orders: { totalCount: OrderEnum.desc }
|
|
167
249
|
});
|
|
250
|
+
|
|
251
|
+
return { results, stats };
|
|
168
252
|
}
|
|
169
253
|
|
|
170
|
-
//
|
|
171
|
-
async
|
|
172
|
-
return this.
|
|
173
|
-
|
|
174
|
-
|
|
254
|
+
// 自定义 SQL
|
|
255
|
+
async customQuery() {
|
|
256
|
+
return this.mapper.query(
|
|
257
|
+
"SELECT * FROM entities WHERE status = ? AND created_at > ?",
|
|
258
|
+
[1, "2024-01-01"]
|
|
259
|
+
);
|
|
175
260
|
}
|
|
176
261
|
}
|
|
177
262
|
```
|
|
178
263
|
|
|
179
|
-
####
|
|
264
|
+
#### 常用查询条件速查表
|
|
180
265
|
|
|
181
|
-
|
|
266
|
+
```typescript
|
|
267
|
+
import { OperatorEnum, OrderEnum } from "@fastcar/core/db";
|
|
182
268
|
|
|
183
|
-
|
|
269
|
+
// 等于 (默认)
|
|
270
|
+
{ where: { status: 1 } }
|
|
184
271
|
|
|
185
|
-
|
|
272
|
+
// 不等于
|
|
273
|
+
{ where: { status: { [OperatorEnum.neq]: 1 } } }
|
|
186
274
|
|
|
187
|
-
|
|
275
|
+
// 大于 / 大于等于 / 小于 / 小于等于
|
|
276
|
+
{ where: { age: { [OperatorEnum.gt]: 18 } } }
|
|
277
|
+
{ where: { age: { [OperatorEnum.gte]: 18, [OperatorEnum.lte]: 60 } } }
|
|
278
|
+
|
|
279
|
+
// IN / NOT IN
|
|
280
|
+
{ where: { id: { [OperatorEnum.in]: [1, 2, 3] } } }
|
|
281
|
+
{ where: { id: { [OperatorEnum.notin]: [1, 2, 3] } } }
|
|
188
282
|
|
|
189
|
-
|
|
283
|
+
// IS NULL / IS NOT NULL
|
|
284
|
+
{ where: { deletedAt: { [OperatorEnum.isNUll]: true } } }
|
|
285
|
+
{ where: { deletedAt: { [OperatorEnum.isNotNull]: true } } }
|
|
286
|
+
|
|
287
|
+
// 排序
|
|
288
|
+
{ orders: { createdAt: OrderEnum.desc } }
|
|
289
|
+
|
|
290
|
+
// 分页
|
|
291
|
+
{ offset: 0, limit: 10 }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### 事务处理
|
|
190
295
|
|
|
191
296
|
```typescript
|
|
192
|
-
import {
|
|
193
|
-
import {
|
|
297
|
+
import { SqlSession } from "@fastcar/core/annotation";
|
|
298
|
+
import { MysqlDataSourceManager } from "@fastcar/mysql";
|
|
194
299
|
|
|
195
|
-
@
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
300
|
+
@Service
|
|
301
|
+
class BizService {
|
|
302
|
+
@Autowired
|
|
303
|
+
private dsm!: MysqlDataSourceManager;
|
|
304
|
+
|
|
305
|
+
@Autowired
|
|
306
|
+
private mapperA!: MapperA;
|
|
307
|
+
|
|
308
|
+
@Autowired
|
|
309
|
+
private mapperB!: MapperB;
|
|
310
|
+
|
|
311
|
+
async transactionExample(dataA: any, dataB: any) {
|
|
312
|
+
const sessionId = await this.dsm.beginTransaction();
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
await this.mapperA.saveOne(dataA, undefined, sessionId);
|
|
316
|
+
await this.mapperB.update({
|
|
317
|
+
where: { id: dataB.id },
|
|
318
|
+
row: { status: dataB.status }
|
|
319
|
+
}, undefined, sessionId);
|
|
320
|
+
|
|
321
|
+
await this.dsm.commit(sessionId);
|
|
322
|
+
return true;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
await this.dsm.rollback(sessionId);
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
199
329
|
```
|
|
200
330
|
|
|
201
|
-
|
|
331
|
+
### PostgreSQL (@fastcar/pgsql)
|
|
202
332
|
|
|
203
333
|
```typescript
|
|
204
|
-
import {
|
|
334
|
+
import { EnablePgsql } from "@fastcar/pgsql/annotation";
|
|
205
335
|
import { PgsqlMapper } from "@fastcar/pgsql";
|
|
206
|
-
import User from "./User";
|
|
207
336
|
|
|
208
|
-
@
|
|
337
|
+
@Application
|
|
338
|
+
@EnablePgsql
|
|
339
|
+
class APP {}
|
|
340
|
+
|
|
341
|
+
@Entity(Entity)
|
|
209
342
|
@Repository
|
|
210
|
-
class
|
|
343
|
+
class EntityMapper extends PgsqlMapper<Entity> {}
|
|
211
344
|
```
|
|
212
345
|
|
|
213
|
-
用法与 `MysqlMapper`
|
|
346
|
+
用法与 `MysqlMapper` 基本一致。
|
|
214
347
|
|
|
215
348
|
### MongoDB (@fastcar/mongo)
|
|
216
349
|
|
|
217
|
-
#### 开启 MongoDB
|
|
218
|
-
|
|
219
350
|
```typescript
|
|
220
|
-
import { Application } from "@fastcar/core/annotation";
|
|
221
351
|
import { EnableMongo } from "@fastcar/mongo/annotation";
|
|
352
|
+
import { MongoMapper } from "@fastcar/mongo";
|
|
222
353
|
|
|
223
354
|
@Application
|
|
224
355
|
@EnableMongo
|
|
225
356
|
class APP {}
|
|
226
|
-
export default new APP();
|
|
227
|
-
```
|
|
228
357
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { Entity, Repository } from "@fastcar/core/annotation";
|
|
233
|
-
import { MongoMapper } from "@fastcar/mongo";
|
|
234
|
-
import User from "./User";
|
|
235
|
-
|
|
236
|
-
@Entity(User)
|
|
358
|
+
@Entity(Entity)
|
|
237
359
|
@Repository
|
|
238
|
-
class
|
|
360
|
+
class EntityMapper extends MongoMapper<Entity> {}
|
|
239
361
|
```
|
|
240
362
|
|
|
241
363
|
### Redis (@fastcar/redis)
|
|
242
364
|
|
|
243
|
-
#### 开启 Redis
|
|
244
|
-
|
|
245
365
|
```typescript
|
|
246
|
-
import { Application } from "@fastcar/core/annotation";
|
|
247
366
|
import { EnableRedis } from "@fastcar/redis/annotation";
|
|
367
|
+
import { RedisClient } from "@fastcar/redis/annotation";
|
|
248
368
|
|
|
249
369
|
@Application
|
|
250
370
|
@EnableRedis
|
|
251
371
|
class APP {}
|
|
252
|
-
export default new APP();
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
#### 使用 RedisTemplate
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
import { Service, Autowired } from "@fastcar/core/annotation";
|
|
259
|
-
import { RedisClient } from "@fastcar/redis/annotation";
|
|
260
372
|
|
|
261
373
|
@Service
|
|
262
374
|
class CacheService {
|
|
@@ -274,22 +386,42 @@ class CacheService {
|
|
|
274
386
|
async del(key: string) {
|
|
275
387
|
await this.redis.del(key);
|
|
276
388
|
}
|
|
389
|
+
|
|
390
|
+
// Hash 操作
|
|
391
|
+
async hset(key: string, field: string, value: string) {
|
|
392
|
+
await this.redis.hset(key, field, value);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async hget(key: string, field: string) {
|
|
396
|
+
return this.redis.hget(key, field);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// List 操作
|
|
400
|
+
async lpush(key: string, value: string) {
|
|
401
|
+
await this.redis.lpush(key, value);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async rpop(key: string) {
|
|
405
|
+
return this.redis.rpop(key);
|
|
406
|
+
}
|
|
277
407
|
}
|
|
278
408
|
```
|
|
279
409
|
|
|
280
410
|
## 数据库逆向生成 (@fastcar/mysql-tool)
|
|
281
411
|
|
|
282
|
-
### 生成配置文件
|
|
283
|
-
|
|
284
412
|
```bash
|
|
413
|
+
# 生成配置文件
|
|
285
414
|
fastcar-cli reverse:init
|
|
415
|
+
|
|
416
|
+
# 执行逆向生成
|
|
417
|
+
fastcar-cli reverse
|
|
286
418
|
```
|
|
287
419
|
|
|
288
|
-
|
|
420
|
+
配置文件示例:
|
|
289
421
|
|
|
290
422
|
```json
|
|
291
423
|
{
|
|
292
|
-
"tables": ["
|
|
424
|
+
"tables": ["table1", "table2"],
|
|
293
425
|
"modelDir": "D:/project/src/model",
|
|
294
426
|
"mapperDir": "D:/project/src/mapper",
|
|
295
427
|
"dbConfig": {
|
|
@@ -298,25 +430,10 @@ fastcar-cli reverse:init
|
|
|
298
430
|
"user": "root",
|
|
299
431
|
"password": "password",
|
|
300
432
|
"database": "test_db"
|
|
301
|
-
}
|
|
302
|
-
"style": {
|
|
303
|
-
"tabWidth": 4,
|
|
304
|
-
"printWidth": 200,
|
|
305
|
-
"trailingComma": "es5",
|
|
306
|
-
"useTabs": true,
|
|
307
|
-
"parser": "typescript",
|
|
308
|
-
"endOfLine": "crlf"
|
|
309
|
-
},
|
|
310
|
-
"ignoreCamelcase": false
|
|
433
|
+
}
|
|
311
434
|
}
|
|
312
435
|
```
|
|
313
436
|
|
|
314
|
-
### 执行逆向生成
|
|
315
|
-
|
|
316
|
-
```bash
|
|
317
|
-
fastcar-cli reverse
|
|
318
|
-
```
|
|
319
|
-
|
|
320
437
|
## application.yml 数据库配置
|
|
321
438
|
|
|
322
439
|
```yaml
|
|
@@ -329,6 +446,7 @@ mysql:
|
|
|
329
446
|
database: mydb
|
|
330
447
|
username: root
|
|
331
448
|
password: password
|
|
449
|
+
connectionLimit: 10
|
|
332
450
|
|
|
333
451
|
pgsql:
|
|
334
452
|
host: localhost
|
|
@@ -372,3 +490,118 @@ npm i @fastcar/core @fastcar/mysql @fastcar/redis
|
|
|
372
490
|
# 4. 启动应用
|
|
373
491
|
npm run debug
|
|
374
492
|
```
|
|
493
|
+
|
|
494
|
+
## 注意事项
|
|
495
|
+
|
|
496
|
+
1. **排序必须使用 OrderEnum**:`orders: { createdAt: OrderEnum.desc }`,不能使用字符串 `"DESC"`
|
|
497
|
+
2. **主键查询**:`selectByPrimaryKey` 和 `updateByPrimaryKey` 需要传入包含主键字段的对象
|
|
498
|
+
3. **批量插入**:`saveList` 会自动分批处理(每批1000条)
|
|
499
|
+
4. **软删除**:建议使用 `update` 方法更新 `delStatus` 字段,而不是物理删除
|
|
500
|
+
|
|
501
|
+
## 编码规范
|
|
502
|
+
|
|
503
|
+
### 1. 实体对象创建规范
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// ✅ 正确:使用 key-value 形式创建对象
|
|
507
|
+
const entity = new Entity({
|
|
508
|
+
name: "示例",
|
|
509
|
+
status: 1,
|
|
510
|
+
createTime: new Date(),
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// ❌ 错误:逐行赋值创建对象
|
|
514
|
+
const entity = new Entity();
|
|
515
|
+
entity.name = "示例";
|
|
516
|
+
entity.status = 1;
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### 2. 分页查询规范
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// ✅ 正确:使用 SQL limit/offset 分页
|
|
523
|
+
const total = await this.mapper.count(where);
|
|
524
|
+
const list = await this.mapper.select({
|
|
525
|
+
where: where,
|
|
526
|
+
orders: { createTime: OrderEnum.desc },
|
|
527
|
+
offset: (page - 1) * pageSize,
|
|
528
|
+
limit: pageSize,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// ❌ 错误:先全表查询再用 JS slice 分页
|
|
532
|
+
const list = await this.mapper.select({ where });
|
|
533
|
+
const pageData = list.slice((page - 1) * pageSize, page * pageSize);
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### 3. 查询条件规范
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import { OperatorEnum } from "@fastcar/core/db";
|
|
540
|
+
|
|
541
|
+
// ✅ 正确:使用 OperatorEnum
|
|
542
|
+
const list = await this.mapper.select({
|
|
543
|
+
where: {
|
|
544
|
+
age: { [OperatorEnum.gte]: 18, [OperatorEnum.lte]: 60 },
|
|
545
|
+
status: { [OperatorEnum.in]: [1, 2, 3] },
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### 4. 接口返回规范
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// ✅ 正确:返回空数据
|
|
554
|
+
if (records.length === 0) {
|
|
555
|
+
return Result.ok({ list: [], total: 0 });
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ❌ 错误:返回模拟数据
|
|
559
|
+
if (records.length === 0) {
|
|
560
|
+
return Result.ok({ list: [{ name: "模拟数据1" }] });
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 5. 更新操作规范
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
// ✅ 正确:更新少于3个字段时使用 update/updateOne
|
|
568
|
+
await this.mapper.updateOne({
|
|
569
|
+
where: { id },
|
|
570
|
+
row: { lastLoginTime: new Date() },
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// ❌ 错误:为了更新1-2个字段而查询整个实体对象
|
|
574
|
+
const entity = await this.mapper.selectByPrimaryKey({ id });
|
|
575
|
+
entity.lastLoginTime = new Date();
|
|
576
|
+
await this.mapper.updateByPrimaryKey(entity);
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 6. 复杂查询优化
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// ✅ 正确:使用 selectByCustom + JOIN 一条 SQL 完成
|
|
583
|
+
interface QueryResult {
|
|
584
|
+
id: number;
|
|
585
|
+
name: string;
|
|
586
|
+
relatedName: string;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const results = await this.mapper.selectByCustom<QueryResult>({
|
|
590
|
+
tableAlias: "t",
|
|
591
|
+
fields: ["t.id", "t.name", "r.name as relatedName"],
|
|
592
|
+
join: [{
|
|
593
|
+
type: "INNER",
|
|
594
|
+
table: "related_table r",
|
|
595
|
+
on: "r.entity_id = t.id",
|
|
596
|
+
}],
|
|
597
|
+
where: { "t.status": 1 },
|
|
598
|
+
camelcaseStyle: true,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// ❌ 错误:多次查询 + 内存组装(N+1 问题)
|
|
602
|
+
const list = await this.mapper.select({});
|
|
603
|
+
for (const item of list) {
|
|
604
|
+
const related = await this.relatedMapper.selectOne({ ... });
|
|
605
|
+
// 内存组装...
|
|
606
|
+
}
|
|
607
|
+
```
|