@fastcar/cli 0.1.0 → 0.1.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/bin/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const init = require("../src/init");
4
4
  const setModules = require("../src/setModules");
@@ -26,7 +26,7 @@ Commands:
26
26
  clean node_modules 删除冗余的 node_modules 目录
27
27
  compress node_modules 压缩 node_modules 目录
28
28
  reverse 数据库表逆向生成
29
- reverse:init 生成 reverse.config.json 配置文件
29
+ reverse:init 生成 reverse.config.yml/json 配置文件
30
30
  pack [pm] 打包项目(排除 devDependencies)
31
31
  pm: 包管理器 (npm/yarn/pnpm),可选,默认自动检测
32
32
 
@@ -54,7 +54,7 @@ Examples:
54
54
  $ fastcar-cli init web my-project # 使用 web 模板创建 my-project
55
55
  $ fastcar-cli clean node_modules
56
56
  $ fastcar-cli reverse # 数据库表逆向生成
57
- $ fastcar-cli reverse:init # 生成默认配置文件
57
+ $ fastcar-cli reverse:init # 生成默认配置文件(默认 YAML 格式)
58
58
  $ fastcar-cli pack # 打包项目(自动检测包管理器)
59
59
  $ fastcar-cli pack yarn # 使用 yarn 安装依赖
60
60
  $ fastcar-cli pack pnpm # 使用 pnpm 安装依赖
@@ -67,7 +67,7 @@ Examples:
67
67
  $ fastcar-cli skill targets # 列出支持的 agents
68
68
 
69
69
  Reverse 命令参数说明:
70
- 通过配置文件传入参数,在项目根目录创建 reverse.config.json:
70
+ 通过配置文件传入参数,在项目根目录创建 reverse.config.yml 或 reverse.config.json:
71
71
 
72
72
  {
73
73
  "tables": ["test"], // 要逆向生成的表名数组(必填)
@@ -100,7 +100,7 @@ function showVersion() {
100
100
  console.log(`fastcar-cli version ${packageINFO.version}`);
101
101
  }
102
102
 
103
- function run(argv) {
103
+ async function run(argv) {
104
104
  // 命令入口
105
105
  if (!argv || argv.length === 0) {
106
106
  showHelp();
@@ -122,7 +122,7 @@ function run(argv) {
122
122
  break;
123
123
  }
124
124
  case "init": {
125
- init(body);
125
+ await init(body);
126
126
  break;
127
127
  }
128
128
  case "clean":
@@ -139,11 +139,11 @@ function run(argv) {
139
139
  break;
140
140
  }
141
141
  case "reverse": {
142
- reverseGenerate(body);
142
+ await reverseGenerate(body);
143
143
  break;
144
144
  }
145
145
  case "reverse:init": {
146
- initReverseConfig();
146
+ await initReverseConfig();
147
147
  break;
148
148
  }
149
149
  case "pack": {
@@ -220,4 +220,7 @@ function run(argv) {
220
220
  }
221
221
  }
222
222
 
223
- run(process.argv.slice(2));
223
+ run(process.argv.slice(2)).catch((err) => {
224
+ console.error(err);
225
+ process.exit(1);
226
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastcar/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "homepage": "https://william_zhong.coding.net/public/fast-car/fastcar-cli/git/files",
5
5
  "description": "fastcar-cli 脚手架快速搭建",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "commander": "^8.3.0",
25
25
  "compressing": "^1.5.1",
26
26
  "inquirer": "^8.2.0",
27
- "yaml": "^1.10.2"
27
+ "yaml": "^2.8.3"
28
28
  },
29
29
  "repository": {
30
30
  "type": "git",
@@ -26,7 +26,7 @@ export default new APP();
26
26
  #### 定义实体模型
27
27
 
28
28
  ```typescript
29
- import { Table, Field, DBType, PrimaryKey, NotNull, Size, CustomType } from "@fastcar/core/annotation";
29
+ import { Table, Field, DBType, PrimaryKey, NotNull, Size } from "@fastcar/core/annotation";
30
30
 
31
31
  @Table("users")
32
32
  class User {
@@ -43,11 +43,25 @@ class User {
43
43
 
44
44
  @Field("profile")
45
45
  @DBType("json")
46
- @CustomType("json")
47
- profile: any;
46
+ profile!: any;
48
47
 
49
- constructor(...args: any) {
50
- Object.assign(this, ...args);
48
+ @Field("created_at")
49
+ @DBType("datetime")
50
+ createdAt!: Date;
51
+
52
+ constructor(args?: Partial<User>) {
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
+ };
51
65
  }
52
66
  }
53
67
  ```
@@ -66,11 +80,11 @@ class UserMapper extends MysqlMapper<User> {}
66
80
  export default UserMapper;
67
81
  ```
68
82
 
69
- #### Service 中使用
83
+ #### MysqlMapper 完整 API 参考
70
84
 
71
85
  ```typescript
72
86
  import { Service, Autowired } from "@fastcar/core/annotation";
73
- import { OrderEnum } from "@fastcar/core/db";
87
+ import { OrderEnum, OperatorEnum } from "@fastcar/core/db";
74
88
  import UserMapper from "./UserMapper";
75
89
  import User from "./User";
76
90
 
@@ -79,73 +93,232 @@ class UserService {
79
93
  @Autowired
80
94
  private userMapper!: UserMapper;
81
95
 
82
- // 查询单条
83
- async getUser(id: number) {
84
- return this.userMapper.selectOne({ where: { id } });
96
+ // ==================== 查询方法 ====================
97
+
98
+ // 1. select(conditions) - 查询列表
99
+ // 返回: T[] - 实体对象数组
100
+ async getAllUsers() {
101
+ return this.userMapper.select({});
102
+ }
103
+
104
+ // 2. selectOne(conditions) - 查询单个
105
+ // 返回: T | null
106
+ async getUserById(id: number) {
107
+ return this.userMapper.selectOne({
108
+ where: { id }
109
+ });
110
+ }
111
+
112
+ // 3. selectByPrimaryKey(row) - 根据主键查询
113
+ // 参数: 包含主键字段的对象
114
+ // 返回: T | null
115
+ async getUserByPK(id: number) {
116
+ return this.userMapper.selectByPrimaryKey({ id } as User);
117
+ }
118
+
119
+ // 4. count(where) - 统计记录数
120
+ // 返回: number
121
+ async countUsers() {
122
+ return this.userMapper.count({ status: 1 });
123
+ }
124
+
125
+ // 5. exist(where) - 判断是否存在
126
+ // 返回: boolean
127
+ async checkUserExists(name: string) {
128
+ return this.userMapper.exist({ name });
129
+ }
130
+
131
+ // ==================== 条件查询详解 ====================
132
+
133
+ // 简单等于条件
134
+ async queryByStatus(status: number) {
135
+ return this.userMapper.select({
136
+ where: { status }
137
+ });
138
+ }
139
+
140
+ // 多条件 AND
141
+ async queryByConditions(name: string, status: number) {
142
+ return this.userMapper.select({
143
+ where: {
144
+ name,
145
+ status,
146
+ delStatus: false
147
+ }
148
+ });
85
149
  }
86
150
 
87
- // 条件查询
88
- async queryUsers(name: string) {
151
+ // 比较运算符
152
+ async queryByAgeRange(minAge: number, maxAge: number) {
89
153
  return this.userMapper.select({
90
154
  where: {
91
- name: { value: name },
92
- createdAt: { ">=": "2024-01-01", "<=": "2024-12-31" },
93
- },
94
- orders: { createdAt: OrderEnum.desc },
95
- limit: 10,
155
+ age: { $gte: minAge, $lte: maxAge }
156
+ }
96
157
  });
97
158
  }
98
159
 
99
- // 数组 IN 查询
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
+ // IN 查询
100
171
  async queryByIds(ids: number[]) {
101
172
  return this.userMapper.select({
102
- where: { id: { IN: ids } },
173
+ where: { id: { $in: ids } }
174
+ });
175
+ }
176
+
177
+ // IS NULL 查询
178
+ async queryDeletedUsers() {
179
+ return this.userMapper.select({
180
+ where: { deletedAt: { $isNull: true } }
181
+ });
182
+ }
183
+
184
+ // ==================== 排序和分页 ====================
185
+
186
+ // 排序 - 必须使用 OrderEnum
187
+ async getUsersOrdered() {
188
+ return this.userMapper.select({
189
+ where: { status: 1 },
190
+ orders: { createdAt: OrderEnum.desc } // 正确: 使用 OrderEnum.desc
191
+ // 错误: orders: { createdAt: "DESC" }
192
+ });
193
+ }
194
+
195
+ // OrderEnum 定义:
196
+ // OrderEnum.asc = "ASC"
197
+ // OrderEnum.desc = "DESC"
198
+
199
+ // 多字段排序
200
+ async getUsersMultiOrder() {
201
+ return this.userMapper.select({
202
+ orders: {
203
+ status: OrderEnum.asc,
204
+ createdAt: OrderEnum.desc
205
+ }
206
+ });
207
+ }
208
+
209
+ // 分页
210
+ async getUsersPaged(page: number, pageSize: number) {
211
+ return this.userMapper.select({
212
+ orders: { id: OrderEnum.desc },
213
+ offest: (page - 1) * pageSize,
214
+ limit: pageSize
103
215
  });
104
216
  }
105
217
 
106
- // 新增
218
+ // 只取前N条
219
+ async getTopUsers(limit: number) {
220
+ return this.userMapper.select({
221
+ orders: { score: OrderEnum.desc },
222
+ limit
223
+ });
224
+ }
225
+
226
+ // ==================== 插入方法 ====================
227
+
228
+ // 1. saveOne(row) - 插入单条
229
+ // 返回: number - 插入的主键ID
107
230
  async createUser(name: string) {
108
231
  const user = new User({ name, createdAt: new Date() });
109
- return this.userMapper.saveOne(user);
232
+ const insertId = await this.userMapper.saveOne(user);
233
+ return insertId;
110
234
  }
111
235
 
112
- // 批量新增
236
+ // 2. saveList(rows) - 批量插入
237
+ // 返回: boolean
113
238
  async createUsers(users: User[]) {
114
239
  return this.userMapper.saveList(users);
115
240
  }
116
241
 
117
- // 更新
118
- async updateName(id: number, name: string) {
119
- return this.userMapper.update({ where: { id }, row: { name } });
242
+ // 3. saveORUpdate(rows) - 插入或更新 (UPSERT)
243
+ // 主键冲突时更新,否则插入
244
+ // 返回: number - 主键ID
245
+ async saveOrUpdateUser(user: User) {
246
+ return this.userMapper.saveORUpdate(user);
247
+ }
248
+
249
+ // ==================== 更新方法 ====================
250
+
251
+ // 1. update({ row, where, limit }) - 条件更新
252
+ // 返回: boolean
253
+ async updateUserName(id: number, name: string) {
254
+ return this.userMapper.update({
255
+ where: { id },
256
+ row: { name, updatedAt: new Date() }
257
+ });
120
258
  }
121
259
 
122
- // 按主键更新
260
+ // 2. updateOne({ row, where }) - 更新单条
261
+ // 自动限制 limit: 1
262
+ async updateOneUser(where: any, row: any) {
263
+ return this.userMapper.updateOne({ where, row });
264
+ }
265
+
266
+ // 3. updateByPrimaryKey(row) - 根据主键更新
267
+ // 根据实体对象的主键字段更新
268
+ // 返回: boolean
123
269
  async updateById(user: User) {
124
270
  return this.userMapper.updateByPrimaryKey(user);
125
271
  }
126
272
 
127
- // 删除
273
+ // 更新示例:软删除
274
+ async softDeleteUser(id: number) {
275
+ return this.userMapper.update({
276
+ where: { id },
277
+ row: { delStatus: true, deletedAt: new Date() }
278
+ });
279
+ }
280
+
281
+ // ==================== 删除方法 ====================
282
+
283
+ // 1. delete({ where, limit }) - 条件删除
284
+ // 返回: boolean
128
285
  async deleteUser(id: number) {
129
- return this.userMapper.delete({ where: { id } });
286
+ return this.userMapper.delete({
287
+ where: { id }
288
+ });
130
289
  }
131
290
 
132
- // 判断存在
133
- async exists(name: string) {
134
- return this.userMapper.exist({ name });
291
+ // 2. deleteOne(where) - 删除单条
292
+ async deleteOneUser(where: any) {
293
+ return this.userMapper.deleteOne(where);
135
294
  }
136
295
 
137
- // 统计
138
- async count() {
139
- return this.userMapper.count({});
296
+ // 3. deleteByPrimaryKey(row) - 根据主键删除
297
+ async deleteById(id: number) {
298
+ return this.userMapper.deleteByPrimaryKey({ id } as User);
299
+ }
300
+
301
+ // ==================== 高级查询 ====================
302
+
303
+ // 自定义 SQL 查询
304
+ // query(sql, args) - 执行查询 SQL
305
+ async customQuery() {
306
+ return this.userMapper.query(
307
+ "SELECT * FROM users WHERE status = ? AND created_at > ?",
308
+ [1, "2024-01-01"]
309
+ );
140
310
  }
141
311
 
142
- // 执行原生 SQL
143
- async executeSql() {
144
- return this.userMapper.execute("SELECT * FROM users WHERE id = 1");
312
+ // execute(sql, args) - 执行任意 SQL
313
+ async customExecute() {
314
+ return this.userMapper.execute(
315
+ "UPDATE users SET login_count = login_count + 1 WHERE id = ?",
316
+ [1]
317
+ );
145
318
  }
146
319
 
147
- // 左连接查询
148
- async leftJoin() {
320
+ // 左连接查询 - 使用 selectByCustom
321
+ async leftJoinQuery() {
149
322
  return this.userMapper.selectByCustom({
150
323
  join: [
151
324
  {
@@ -155,34 +328,133 @@ class UserService {
155
328
  },
156
329
  ],
157
330
  tableAlias: "t",
331
+ where: { "t.status": 1 }
158
332
  });
159
333
  }
160
334
 
161
335
  // 强制索引
162
- async forceIndex() {
336
+ async forceIndexQuery() {
163
337
  return this.userMapper.select({
164
338
  forceIndex: ["idx_name"],
165
- orders: { name: OrderEnum.desc },
166
- limit: 1,
339
+ where: { status: 1 }
167
340
  });
168
341
  }
169
342
 
170
- // 使用函数
171
- async formatDate() {
343
+ // 指定查询字段
344
+ async selectFields() {
172
345
  return this.userMapper.select({
173
- fields: ['DATE_FORMAT(created_at, "%Y-%m-%d %H:%i:%s") as createdAt'],
346
+ fields: ["id", "name", "email"],
347
+ where: { status: 1 }
348
+ });
349
+ }
350
+
351
+ // 分组查询
352
+ async groupByStatus() {
353
+ return this.userMapper.selectByCustom({
354
+ fields: ["status", "COUNT(*) as count"],
355
+ groups: ["status"]
356
+ });
357
+ }
358
+
359
+ // 使用数据库函数
360
+ async useDbFunction() {
361
+ return this.userMapper.selectByCustom({
362
+ fields: [
363
+ 'id',
364
+ 'name',
365
+ 'DATE_FORMAT(created_at, "%Y-%m-%d") as createdDate'
366
+ ]
174
367
  });
175
368
  }
176
369
  }
177
370
  ```
178
371
 
179
- #### 多数据源
372
+ #### 常用查询条件速查表
373
+
374
+ ```typescript
375
+ // 等于 (默认)
376
+ { where: { status: 1 } }
377
+
378
+ // 不等于
379
+ { where: { status: { $ne: 1 } } }
380
+
381
+ // 大于 / 大于等于
382
+ { where: { age: { $gt: 18 } } }
383
+ { where: { age: { $gte: 18 } } }
384
+
385
+ // 小于 / 小于等于
386
+ { where: { age: { $lt: 60 } } }
387
+ { where: { age: { $lte: 60 } } }
388
+
389
+ // 范围查询
390
+ { where: { age: { $gte: 18, $lte: 60 } } }
391
+
392
+ // IN 查询
393
+ { where: { id: { $in: [1, 2, 3] } } }
394
+
395
+ // NOT IN 查询
396
+ { where: { id: { $nin: [1, 2, 3] } } }
397
+
398
+ // IS NULL
399
+ { where: { deletedAt: { $isNull: true } } }
400
+
401
+ // IS NOT NULL
402
+ { where: { deletedAt: { $isNotNull: true } } }
403
+
404
+ // 多条件 AND (默认)
405
+ { where: { status: 1, delStatus: false } }
406
+
407
+ // 排序 (必须使用 OrderEnum)
408
+ import { OrderEnum } from "@fastcar/core/db";
409
+ { orders: { createdAt: OrderEnum.desc } }
410
+ { orders: { createdAt: OrderEnum.asc } }
411
+
412
+ // 分页
413
+ { offest: 0, limit: 10 }
414
+ ```
415
+
416
+ #### 事务处理
180
417
 
181
- 在 `application.yml` 中配置多个数据源,Service 中通过指定数据源名称切换。
418
+ ```typescript
419
+ import { SqlSession } from "@fastcar/core/annotation";
420
+ import { MysqlDataSourceManager } from "@fastcar/mysql";
421
+
422
+ @Service
423
+ class OrderService {
424
+ @Autowired
425
+ private dsm!: MysqlDataSourceManager;
182
426
 
183
- #### 事务
427
+ @Autowired
428
+ private orderMapper!: OrderMapper;
184
429
 
185
- 使用 `@Transactional`(如果框架提供)或手动通过 `SqlSession` 控制事务边界。
430
+ @Autowired
431
+ private inventoryMapper!: InventoryMapper;
432
+
433
+ async createOrderWithTransaction(order: Order, inventoryUpdate: any) {
434
+ const sessionId = await this.dsm.beginTransaction();
435
+
436
+ try {
437
+ // 插入订单
438
+ await this.orderMapper.saveOne(order, undefined, sessionId);
439
+
440
+ // 更新库存
441
+ await this.inventoryMapper.update({
442
+ where: { id: inventoryUpdate.id },
443
+ row: { quantity: inventoryUpdate.quantity }
444
+ }, undefined, sessionId);
445
+
446
+ // 提交事务
447
+ await this.dsm.commit(sessionId);
448
+
449
+ return true;
450
+ } catch (error) {
451
+ // 回滚事务
452
+ await this.dsm.rollback(sessionId);
453
+ throw error;
454
+ }
455
+ }
456
+ }
457
+ ```
186
458
 
187
459
  ### PostgreSQL (@fastcar/pgsql)
188
460
 
@@ -208,6 +480,8 @@ import User from "./User";
208
480
  @Entity(User)
209
481
  @Repository
210
482
  class UserMapper extends PgsqlMapper<User> {}
483
+
484
+ export default UserMapper;
211
485
  ```
212
486
 
213
487
  用法与 `MysqlMapper` 基本一致,支持相同的查询条件和 CRUD 操作。
@@ -236,6 +510,8 @@ import User from "./User";
236
510
  @Entity(User)
237
511
  @Repository
238
512
  class UserMapper extends MongoMapper<User> {}
513
+
514
+ export default UserMapper;
239
515
  ```
240
516
 
241
517
  ### Redis (@fastcar/redis)
@@ -252,7 +528,7 @@ class APP {}
252
528
  export default new APP();
253
529
  ```
254
530
 
255
- #### 使用 RedisTemplate
531
+ #### 使用 RedisClient
256
532
 
257
533
  ```typescript
258
534
  import { Service, Autowired } from "@fastcar/core/annotation";
@@ -274,6 +550,28 @@ class CacheService {
274
550
  async del(key: string) {
275
551
  await this.redis.del(key);
276
552
  }
553
+
554
+ async expire(key: string, seconds: number) {
555
+ await this.redis.expire(key, seconds);
556
+ }
557
+
558
+ // Hash 操作
559
+ async hset(key: string, field: string, value: string) {
560
+ await this.redis.hset(key, field, value);
561
+ }
562
+
563
+ async hget(key: string, field: string) {
564
+ return this.redis.hget(key, field);
565
+ }
566
+
567
+ // List 操作
568
+ async lpush(key: string, value: string) {
569
+ await this.redis.lpush(key, value);
570
+ }
571
+
572
+ async rpop(key: string) {
573
+ return this.redis.rpop(key);
574
+ }
277
575
  }
278
576
  ```
279
577
 
@@ -329,6 +627,10 @@ mysql:
329
627
  database: mydb
330
628
  username: root
331
629
  password: password
630
+ # 连接池配置
631
+ connectionLimit: 10
632
+ # 是否使用预处理语句
633
+ useServerPrepStmts: true
332
634
 
333
635
  pgsql:
334
636
  host: localhost
@@ -372,3 +674,11 @@ npm i @fastcar/core @fastcar/mysql @fastcar/redis
372
674
  # 4. 启动应用
373
675
  npm run debug
374
676
  ```
677
+
678
+ ## 注意事项
679
+
680
+ 1. **排序必须使用 OrderEnum**:`orders: { createdAt: OrderEnum.desc }`,不能使用字符串 `"DESC"`
681
+ 2. **主键查询**:`selectByPrimaryKey` 和 `updateByPrimaryKey` 需要传入包含主键字段的对象
682
+ 3. **时间范围查询**:使用 `$gte` 和 `$lte` 运算符
683
+ 4. **批量插入**:`saveList` 会自动分批处理(每批1000条)
684
+ 5. **软删除**:建议使用 `update` 方法更新 `delStatus` 字段,而不是物理删除
@@ -64,58 +64,210 @@ app.start();
64
64
 
65
65
  ### Web 开发 (@fastcar/koa)
66
66
 
67
+ **正确的路由装饰器使用方式:**
68
+
67
69
  ```typescript
68
- import { GET, POST, REQUEST, Body, Param } from "@fastcar/koa/annotation";
70
+ import { GET, POST, REQUEST } from "@fastcar/koa/annotation";
69
71
 
70
72
  @Controller
71
73
  @REQUEST("/api/users")
72
74
  class UserController {
73
- @GET
75
+ // GET 请求 - 无路径参数时必须有括号
76
+ @GET()
74
77
  async list() {
75
78
  return { data: [] };
76
79
  }
77
80
 
81
+ // GET 请求 - 有路径参数
78
82
  @GET("/:id")
79
- async getById(@Param("id") id: string) {
83
+ async getById(id: string) {
80
84
  return { id };
81
85
  }
82
86
 
83
- @POST
84
- async create(@Body user: UserDTO) {
87
+ // POST 请求
88
+ @POST()
89
+ async create(body: UserDTO) {
85
90
  return { created: true };
86
91
  }
87
92
  }
88
93
  ```
89
94
 
95
+ **⚠️ 重要:FastCar 没有 `@Body`, `@Param`, `@Query` 装饰器**
96
+
97
+ - 请求参数直接作为方法参数传入
98
+ - GET 请求参数通过方法参数直接获取
99
+ - POST 请求体通过 `body` 参数获取
100
+ - 路径参数通过方法参数直接获取
101
+
90
102
  ### 数据库 (@fastcar/mysql)
91
103
 
104
+ **实体定义:**
105
+
92
106
  ```typescript
93
- import { Repository, Table, Field, PrimaryKey, SqlSession } from "@fastcar/mysql/annotation";
107
+ import { Table, Field, DBType, PrimaryKey, NotNull, Size } from "@fastcar/core/annotation";
94
108
 
95
109
  @Table("users")
96
110
  class User {
97
- @PrimaryKey
98
111
  @Field("id")
112
+ @DBType("int")
113
+ @PrimaryKey
99
114
  id!: number;
100
115
 
101
116
  @Field("name")
117
+ @DBType("varchar")
118
+ @NotNull
119
+ @Size({ maxSize: 50 })
102
120
  name!: string;
103
121
  }
122
+ ```
104
123
 
124
+ **Mapper 定义:**
125
+
126
+ ```typescript
127
+ import { Entity, Repository } from "@fastcar/core/annotation";
128
+ import { MysqlMapper } from "@fastcar/mysql";
129
+
130
+ @Entity(User)
105
131
  @Repository
106
- class UserRepository {
107
- @SqlSession
108
- private session!: SqlSession;
132
+ class UserMapper extends MysqlMapper<User> {}
133
+
134
+ export default UserMapper;
135
+ ```
136
+
137
+ **Service 中使用:**
138
+
139
+ ```typescript
140
+ import { Service, Autowired } from "@fastcar/core/annotation";
141
+ import { OrderEnum } from "@fastcar/core/db";
142
+ import UserMapper from "./UserMapper";
143
+
144
+ @Service
145
+ class UserService {
146
+ @Autowired
147
+ private userMapper!: UserMapper;
148
+
149
+ // 查询列表
150
+ async getUsers() {
151
+ return this.userMapper.select({
152
+ where: { status: 1 },
153
+ orders: { createTime: OrderEnum.desc },
154
+ limit: 10
155
+ });
156
+ }
157
+
158
+ // 查询单个
159
+ async getUser(id: number) {
160
+ return this.userMapper.selectOne({ where: { id } });
161
+ }
162
+
163
+ // 根据主键查询
164
+ async getUserById(id: number) {
165
+ return this.userMapper.selectByPrimaryKey({ id } as User);
166
+ }
167
+
168
+ // 插入
169
+ async createUser(user: User) {
170
+ return this.userMapper.saveOne(user);
171
+ }
172
+
173
+ // 更新
174
+ async updateUser(id: number, data: Partial<User>) {
175
+ return this.userMapper.update({ where: { id }, row: data });
176
+ }
177
+
178
+ // 根据主键更新
179
+ async updateById(user: User) {
180
+ return this.userMapper.updateByPrimaryKey(user);
181
+ }
182
+
183
+ // 删除
184
+ async deleteUser(id: number) {
185
+ return this.userMapper.delete({ where: { id } });
186
+ }
187
+
188
+ // 统计
189
+ async count() {
190
+ return this.userMapper.count({});
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### 表单验证 (@fastcar/core)
196
+
197
+ **正确的表单验证方式:**
198
+
199
+ ```typescript
200
+ import { ValidForm, NotNull, Size, Rule } from "@fastcar/core/annotation";
201
+
202
+ // DTO 类定义在单独的文件中,如 dto/UserDTO.ts
203
+ class UserDTO {
204
+ @NotNull
205
+ name!: string;
109
206
 
110
- async findById(id: number) {
111
- return this.session.findById(User, id);
207
+ @Size({ minSize: 1, maxSize: 150 })
208
+ age!: number;
209
+ }
210
+
211
+ @Controller
212
+ @REQUEST("/api/users")
213
+ class UserController {
214
+
215
+ // GET 请求 - 无需表单验证
216
+ @GET()
217
+ async list(page: number = 1, pageSize: number = 10) {
218
+ return { page, pageSize, data: [] };
219
+ }
220
+
221
+ // POST 请求 - 使用 @ValidForm + @Rule 进行表单验证
222
+ @ValidForm
223
+ @POST()
224
+ async create(@Rule() body: UserDTO) {
225
+ // 参数会自动校验,如果校验失败会抛出异常
226
+ const { name, age } = body;
227
+ return this.userService.create({ name, age });
112
228
  }
113
229
  }
114
230
  ```
115
231
 
232
+ **表单验证规则:**
233
+
234
+ | 装饰器 | 用途 | 示例 |
235
+ |--------|------|------|
236
+ | `@ValidForm` | 开启方法参数校验 | 放在方法上 |
237
+ | `@Rule()` | 标记校验对象 | 放在 DTO 参数前 |
238
+ | `@NotNull` | 参数不能为空 | 放在 DTO 字段上 |
239
+ | `@Size({min, max})` | 大小限制 | 放在 DTO 字段上 |
240
+
241
+ **⚠️ 常见错误:**
242
+
243
+ ❌ **错误** - 使用不存在的装饰器:
244
+ ```typescript
245
+ // 这些装饰器在 FastCar 中不存在!
246
+ import { Body, Param, Query } from "@fastcar/koa/annotation"; // ❌ 错误
247
+
248
+ @GET("/:id")
249
+ async getById(@Param("id") id: string) { ... } // ❌ 错误
250
+
251
+ @POST()
252
+ async create(@Body body: UserDTO) { ... } // ❌ 错误
253
+ ```
254
+
255
+ ✅ **正确** - 直接使用方法参数:
256
+ ```typescript
257
+ @GET("/:id")
258
+ async getById(id: string) { ... } // ✅ 正确
259
+
260
+ @POST()
261
+ async create(body: UserDTO) { ... } // ✅ 正确
262
+
263
+ @GET()
264
+ async list(page: number = 1) { ... } // ✅ 正确
265
+ ```
266
+
116
267
  ### Redis (@fastcar/redis)
117
268
 
118
269
  ```typescript
270
+ import { Service, Autowired } from "@fastcar/core/annotation";
119
271
  import { RedisClient } from "@fastcar/redis/annotation";
120
272
 
121
273
  @Service
@@ -262,6 +414,7 @@ export default new APP();
262
414
  template/
263
415
  ├── src/
264
416
  │ ├── controller/ # 控制器(web/cos)
417
+ │ ├── dto/ # DTO 类(表单验证)
265
418
  │ ├── middleware/ # 中间件(web/cos)
266
419
  │ ├── model/ # 数据模型
267
420
  │ └── app.ts # 应用入口
@@ -533,29 +686,6 @@ class LifecycleService {
533
686
  }
534
687
  ```
535
688
 
536
- ## 表单验证
537
-
538
- ```typescript
539
- import { ValidForm, NotNull, Size, Rule } from "@fastcar/core/annotation";
540
-
541
- class UserDTO {
542
- @NotNull
543
- name!: string;
544
-
545
- @Size({ minSize: 1, maxSize: 150 })
546
- age!: number;
547
- }
548
-
549
- @Controller
550
- class UserController {
551
- @ValidForm
552
- createUser(@Rule() @NotNull user: UserDTO) {
553
- // 参数自动校验
554
- return this.userService.create(user);
555
- }
556
- }
557
- ```
558
-
559
689
  ## 工具类
560
690
 
561
691
  ```typescript
@@ -655,6 +785,71 @@ npx tsc --init
655
785
  # 5. 创建入口文件和配置文件,开始开发
656
786
  ```
657
787
 
788
+ ## 常见错误与注意事项
789
+
790
+ ### 1. 路由装饰器必须有括号
791
+
792
+ ❌ **错误:**
793
+ ```typescript
794
+ @GET
795
+ async list() { }
796
+ ```
797
+
798
+ ✅ **正确:**
799
+ ```typescript
800
+ @GET()
801
+ async list() { }
802
+ ```
803
+
804
+ ### 2. 不要使用不存在的装饰器
805
+
806
+ ❌ **错误:**
807
+ ```typescript
808
+ import { Body, Param, Query } from "@fastcar/koa/annotation";
809
+
810
+ @GET("/:id")
811
+ async getById(@Param("id") id: string) { }
812
+
813
+ @POST()
814
+ async create(@Body body: UserDTO) { }
815
+ ```
816
+
817
+ ✅ **正确:**
818
+ ```typescript
819
+ @GET("/:id")
820
+ async getById(id: string) { }
821
+
822
+ @POST()
823
+ async create(body: UserDTO) { }
824
+ ```
825
+
826
+ ### 3. 表单验证使用 @ValidForm + @Rule
827
+
828
+ ❌ **错误:**
829
+ ```typescript
830
+ @POST()
831
+ async create(@Body body: UserDTO) { }
832
+ ```
833
+
834
+ ✅ **正确:**
835
+ ```typescript
836
+ @ValidForm
837
+ @POST()
838
+ async create(@Rule() body: UserDTO) { }
839
+ ```
840
+
841
+ ### 4. DTO 类放在单独文件夹
842
+
843
+ 推荐项目结构:
844
+ ```
845
+ src/
846
+ ├── controller/ # 控制器
847
+ ├── dto/ # DTO 类(表单验证)
848
+ ├── service/ # 服务层
849
+ ├── model/ # 数据模型
850
+ └── app.ts
851
+ ```
852
+
658
853
  ## 参考资源
659
854
 
660
855
  - 详细 API 文档:[references/api-reference.md](references/api-reference.md)
package/src/reverse.js CHANGED
@@ -1,6 +1,8 @@
1
1
  const path = require("path");
2
2
  const fs = require("fs");
3
3
  const process = require("process");
4
+ const yaml = require("yaml");
5
+ const inquirer = require("inquirer");
4
6
 
5
7
  // 默认配置文件模板
6
8
  const defaultConfig = {
@@ -25,18 +27,70 @@ const defaultConfig = {
25
27
  ignoreCamelcase: false,
26
28
  };
27
29
 
30
+ // 支持的配置文件名
31
+ const CONFIG_FILES = {
32
+ json: "reverse.config.json",
33
+ yml: "reverse.config.yml",
34
+ };
35
+
36
+ // 查找已存在的配置文件
37
+ function findExistingConfig(cwd) {
38
+ for (const ext of ["yml", "json"]) {
39
+ const configPath = path.join(cwd, CONFIG_FILES[ext]);
40
+ if (fs.existsSync(configPath)) {
41
+ return { ext, configPath };
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ // 读取配置文件
48
+ function readConfig(configPath, ext) {
49
+ const content = fs.readFileSync(configPath, "utf-8");
50
+ if (ext === "yml") {
51
+ return yaml.parse(content);
52
+ }
53
+ return JSON.parse(content);
54
+ }
55
+
56
+ // 写入配置文件
57
+ function writeConfig(configPath, ext, config) {
58
+ if (ext === "yml") {
59
+ fs.writeFileSync(configPath, yaml.stringify(config));
60
+ } else {
61
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
62
+ }
63
+ }
64
+
28
65
  // 生成默认配置文件
29
- function initReverseConfig() {
66
+ async function initReverseConfig() {
30
67
  const cwd = process.cwd();
31
- const configPath = path.join(cwd, "reverse.config.json");
32
68
 
33
- // 检查是否已存在
34
- if (fs.existsSync(configPath)) {
35
- console.log("⚠️ reverse.config.json 配置文件已存在");
69
+ // 检查是否已存在任何格式的配置文件
70
+ const existing = findExistingConfig(cwd);
71
+ if (existing) {
72
+ console.log(`⚠️ ${path.basename(existing.configPath)} 配置文件已存在`);
36
73
  console.log(" 如需重新生成,请先删除现有文件");
37
74
  return;
38
75
  }
39
76
 
77
+ // 交互式选择配置格式
78
+ const answer = await inquirer.prompt([
79
+ {
80
+ type: "list",
81
+ name: "format",
82
+ message: "选择配置文件格式:",
83
+ choices: [
84
+ { name: "YAML (reverse.config.yml)", value: "yml" },
85
+ { name: "JSON (reverse.config.json)", value: "json" },
86
+ ],
87
+ default: "yml",
88
+ },
89
+ ]);
90
+
91
+ const ext = answer.format;
92
+ const configPath = path.join(cwd, CONFIG_FILES[ext]);
93
+
40
94
  // 填充默认路径
41
95
  const config = {
42
96
  ...defaultConfig,
@@ -45,8 +99,8 @@ function initReverseConfig() {
45
99
  };
46
100
 
47
101
  try {
48
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
49
- console.log("✅ 已生成 reverse.config.json 配置文件");
102
+ writeConfig(configPath, ext, config);
103
+ console.log(`✅ 已生成 ${CONFIG_FILES[ext]} 配置文件`);
50
104
  console.log("📁 文件路径:", configPath);
51
105
  console.log("\n💡 请根据需要修改以下配置:");
52
106
  console.log(" • tables: 要逆向生成的表名数组");
@@ -72,11 +126,31 @@ async function reverseGenerate(args = []) {
72
126
  }
73
127
 
74
128
  // 读取配置文件
75
- const configPath = path.join(cwd, "reverse.config.json");
76
- if (!fs.existsSync(configPath)) {
77
- console.log("❌ 未找到 reverse.config.json 配置文件");
129
+ const existing = findExistingConfig(cwd);
130
+ if (!existing) {
131
+ console.log("❌ 未找到 reverse.config.yml 或 reverse.config.json 配置文件");
78
132
  console.log(" 请在项目根目录创建该文件,格式如下:");
79
133
  console.log(`
134
+ YAML 格式 (reverse.config.yml):
135
+ tables: [test]
136
+ modelDir: ${cwd.replace(/\\/g, "/")}/src/models
137
+ mapperDir: ${cwd.replace(/\\/g, "/")}/src/mappers
138
+ dbConfig:
139
+ host: localhost
140
+ port: 3306
141
+ user: root
142
+ password: password
143
+ database: test_db
144
+ style:
145
+ tabWidth: 4
146
+ printWidth: 200
147
+ trailingComma: es5
148
+ useTabs: true
149
+ parser: typescript
150
+ endOfLine: crlf
151
+ ignoreCamelcase: false
152
+
153
+ JSON 格式 (reverse.config.json):
80
154
  {
81
155
  "tables": ["test"],
82
156
  "modelDir": "${cwd.replace(/\\/g, "/")}/src/models",
@@ -104,9 +178,9 @@ async function reverseGenerate(args = []) {
104
178
 
105
179
  let config;
106
180
  try {
107
- config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
181
+ config = readConfig(existing.configPath, existing.ext);
108
182
  } catch (error) {
109
- console.log("❌ 配置文件解析失败:", error.message);
183
+ console.log(`❌ 配置文件 ${path.basename(existing.configPath)} 解析失败:`, error.message);
110
184
  return;
111
185
  }
112
186
 
@@ -9,9 +9,9 @@ const TARGETS = {
9
9
  name: 'Kimi Code CLI',
10
10
  description: 'Kimi Code extension for VS Code',
11
11
  globalPaths: {
12
- win32: path.join(os.homedir(), 'AppData/Roaming/Code/User/globalStorage/moonshot-ai.kimi-code/skills'),
13
- darwin: path.join(os.homedir(), '.config/agents/skills'),
14
- linux: path.join(os.homedir(), '.config/agents/skills')
12
+ win32: path.join(os.homedir(), '.kimi/skills'),
13
+ darwin: path.join(os.homedir(), '.kimi/skills'),
14
+ linux: path.join(os.homedir(), '.kimi/skills')
15
15
  },
16
16
  localPath: '.agents/skills'
17
17
  },
package/src/skill.js CHANGED
@@ -197,9 +197,11 @@ async function installSkill(skillName, options = {}) {
197
197
  console.log(`✅ 成功 ${modeText} 安装 ${skillName}`);
198
198
  console.log(` 位置: ${destPath}`);
199
199
  console.log();
200
- console.log('提示:');
201
- console.log(' 1. 重启你的 AI agent 以使用 skill');
202
- console.log(` 2. 在对话中询问关于 "${skillName}" 的内容`);
200
+ console.log('⚠️ 重要: 请重启你的 AI agent 以加载新安装的 skill!');
201
+ console.log();
202
+ console.log('重启后,你可以在对话中:');
203
+ console.log(` • 直接询问关于 "${skillName}" 的内容`);
204
+ console.log(` • 使用 /skill:${skillName} 强制加载该 skill`);
203
205
  } else {
204
206
  console.log('❌ 安装验证失败');
205
207
  }