befly 3.10.14 → 3.10.16

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/docs/api/api.md CHANGED
@@ -518,14 +518,14 @@ export default {
518
518
 
519
519
  const result = await befly.db.getList({
520
520
  table: "article",
521
- columns: ["id", "title", "summary", "createdAt"],
521
+ fields: ["id", "title", "summary", "createdAt"],
522
522
  where: where,
523
523
  page: page || 1,
524
524
  limit: limit || 10,
525
- orderBy: { id: "desc" }
525
+ orderBy: ["id#DESC"]
526
526
  });
527
527
 
528
- return befly.tool.Yes("获取成功", result);
528
+ return befly.tool.Yes("获取成功", result.data);
529
529
  }
530
530
  } as ApiRoute;
531
531
  ```
@@ -542,11 +542,13 @@ export default {
542
542
  },
543
543
  required: ["id"],
544
544
  handler: async (befly, ctx) => {
545
- const article = await befly.db.getDetail({
545
+ const articleRes = await befly.db.getOne({
546
546
  table: "article",
547
547
  where: { id: ctx.body.id, state: 1 }
548
548
  });
549
549
 
550
+ const article = articleRes.data;
551
+
550
552
  if (!article?.id) {
551
553
  return befly.tool.No("文章不存在");
552
554
  }
@@ -1336,29 +1338,23 @@ interface BeflyContext {
1336
1338
 
1337
1339
  ### 常用工具方法
1338
1340
 
1339
- #### befly.db.cleanFields - 清理数据字段
1341
+ #### fieldClear - 清理数据字段
1340
1342
 
1341
- 清理对象中的 `null` 和 `undefined` 值,适用于处理可选参数:
1343
+ 清理对象中的指定值(常用:`null/undefined`),适用于处理可选参数。
1342
1344
 
1343
1345
  ```typescript
1344
- // 方法签名
1345
- befly.db.cleanFields<T>(
1346
- data: T, // 要清理的数据对象
1347
- excludeValues?: any[], // 要排除的值,默认 [null, undefined]
1348
- keepValues?: Record<string, any> // 强制保留的键值对
1349
- ): Partial<T>
1350
- ```
1346
+ import { fieldClear } from "befly/utils/fieldClear";
1351
1347
 
1352
- **基本用法:**
1353
-
1354
- ```typescript
1355
- // 默认排除 null 和 undefined
1356
- const cleanData = befly.db.cleanFields({
1357
- name: "John",
1358
- age: null,
1359
- email: undefined,
1360
- phone: ""
1361
- });
1348
+ // 常用:排除 null 和 undefined
1349
+ const cleanData = fieldClear(
1350
+ {
1351
+ name: "John",
1352
+ age: null,
1353
+ email: undefined,
1354
+ phone: ""
1355
+ },
1356
+ { excludeValues: [null, undefined] }
1357
+ );
1362
1358
  // 结果: { name: 'John', phone: '' }
1363
1359
  ```
1364
1360
 
@@ -1366,19 +1362,22 @@ const cleanData = befly.db.cleanFields({
1366
1362
 
1367
1363
  ```typescript
1368
1364
  // 同时排除 null、undefined 和空字符串
1369
- const cleanData = befly.db.cleanFields({ name: "John", phone: "", age: null }, [null, undefined, ""]);
1365
+ const cleanData = fieldClear({ name: "John", phone: "", age: null }, { excludeValues: [null, undefined, ""] });
1370
1366
  // 结果: { name: 'John' }
1371
1367
  ```
1372
1368
 
1373
1369
  **保留特定字段的特定值:**
1374
1370
 
1375
1371
  ```typescript
1376
- // 即使值在排除列表中,也保留 status 字段的 null
1377
- const cleanData = befly.db.cleanFields({ name: "John", status: null, count: 0 }, [null, undefined], { status: null });
1378
- // 结果: { name: 'John', status: null, count: 0 }
1372
+ // 保留 count 0
1373
+ const cleanData = fieldClear(
1374
+ { name: "John", status: null, count: 0 },
1375
+ { excludeValues: [null, undefined], keepMap: { count: 0 } }
1376
+ );
1377
+ // 结果: { name: 'John', count: 0 }
1379
1378
  ```
1380
1379
 
1381
- > **注意**:`insData`、`updData` 和 `where` 条件会自动调用 `cleanFields`,通常无需手动调用。
1380
+ > **注意**:DbHelper 的写入(`insData/insBatch/updData`)与查询条件(`where`)会自动过滤 `null/undefined`,通常无需手动调用。
1382
1381
 
1383
1382
  ---
1384
1383
 
@@ -1562,15 +1561,16 @@ export default {
1562
1561
  required: ["productId", "quantity"],
1563
1562
  handler: async (befly, ctx) => {
1564
1563
  // 使用事务确保库存扣减和订单创建的原子性
1565
- const result = await befly.db.transaction(async (trx) => {
1564
+ const result = await befly.db.trans(async (trx) => {
1566
1565
  // 1. 查询商品信息(带锁)
1567
- const product = await trx.getOne({
1566
+ const productRes = await trx.getOne({
1568
1567
  table: "product",
1569
- where: { id: ctx.body.productId },
1570
- forUpdate: true // 行锁
1568
+ where: { id: ctx.body.productId }
1571
1569
  });
1572
1570
 
1573
- if (!product) {
1571
+ const product = productRes.data;
1572
+
1573
+ if (!product?.id) {
1574
1574
  throw new Error("商品不存在");
1575
1575
  }
1576
1576
 
@@ -1588,7 +1588,7 @@ export default {
1588
1588
  });
1589
1589
 
1590
1590
  // 3. 创建订单
1591
- const orderId = await trx.insData({
1591
+ const orderIdRes = await trx.insData({
1592
1592
  table: "order",
1593
1593
  data: {
1594
1594
  userId: ctx.user.id,
@@ -1599,6 +1599,8 @@ export default {
1599
1599
  }
1600
1600
  });
1601
1601
 
1602
+ const orderId = orderIdRes.data;
1603
+
1602
1604
  // 4. 创建订单明细
1603
1605
  await trx.insData({
1604
1606
  table: "order_item",
@@ -1640,19 +1642,21 @@ export default {
1640
1642
  }
1641
1643
 
1642
1644
  // 批量插入
1643
- const result = await befly.db.batchInsert({
1644
- table: "user",
1645
- data: users.map((user: any) => ({
1646
- username: user.username,
1647
- email: user.email,
1648
- nickname: user.nickname || user.username,
1649
- state: 1
1650
- }))
1651
- });
1645
+ const idsRes = await befly.db.insBatch(
1646
+ "user",
1647
+ users.map((user: any) => {
1648
+ return {
1649
+ username: user.username,
1650
+ email: user.email,
1651
+ nickname: user.nickname || user.username,
1652
+ state: 1
1653
+ };
1654
+ })
1655
+ );
1652
1656
 
1653
1657
  return befly.tool.Yes("导入成功", {
1654
1658
  total: users.length,
1655
- inserted: result.affectedRows
1659
+ ids: idsRes.data
1656
1660
  });
1657
1661
  }
1658
1662
  };
@@ -1682,7 +1686,7 @@ export default {
1682
1686
  });
1683
1687
 
1684
1688
  return befly.tool.Yes("更新成功", {
1685
- updated: result.affectedRows
1689
+ updated: result.data
1686
1690
  });
1687
1691
  }
1688
1692
  };
@@ -1711,7 +1715,7 @@ export default {
1711
1715
  });
1712
1716
 
1713
1717
  return befly.tool.Yes("删除成功", {
1714
- deleted: result.affectedRows
1718
+ deleted: result.data
1715
1719
  });
1716
1720
  }
1717
1721
  };
@@ -1728,12 +1732,14 @@ export default {
1728
1732
  required: ['id'],
1729
1733
  handler: async (befly, ctx) => {
1730
1734
  // 查询订单基本信息
1731
- const order = await befly.db.getOne({
1735
+ const orderRes = await befly.db.getOne({
1732
1736
  table: 'order',
1733
1737
  where: { id: ctx.body.id }
1734
1738
  });
1735
1739
 
1736
- if (!order) {
1740
+ const order = orderRes.data;
1741
+
1742
+ if (!order?.id) {
1737
1743
  return befly.tool.No('订单不存在');
1738
1744
  }
1739
1745
 
@@ -1744,23 +1750,17 @@ export default {
1744
1750
  });
1745
1751
 
1746
1752
  // 查询用户信息
1747
- const user = await befly.db.getOne({
1753
+ const userRes = await befly.db.getOne({
1748
1754
  table: 'user',
1755
+ fields: ['id', 'username', 'nickname', 'phone'],
1749
1756
  where: { id: order.userId }
1750
1757
  });
1751
1758
 
1759
+ const user = userRes.data;
1760
+
1752
1761
  return befly.tool.Yes('查询成功', {
1753
1762
  order: order,
1754
- items: itemsResult.lists, // 订单明细列表
1755
- user: user
1756
- });
1757
- where: { id: order.userId },
1758
- columns: ['id', 'username', 'nickname', 'phone']
1759
- });
1760
-
1761
- return befly.tool.Yes('获取成功', {
1762
- ...order,
1763
- items: items,
1763
+ items: itemsResult.data.lists, // 订单明细列表
1764
1764
  user: user
1765
1765
  });
1766
1766
  }
@@ -1784,13 +1784,13 @@ export default {
1784
1784
  on: { "article.authorId": "author.id" }
1785
1785
  }
1786
1786
  ],
1787
- columns: ["article.id", "article.title", "article.createdAt", "author.nickname AS authorName"],
1787
+ fields: ["article.id", "article.title", "article.createdAt", "author.nickname AS authorName"],
1788
1788
  page: ctx.body.page || 1,
1789
1789
  limit: ctx.body.limit || 10,
1790
1790
  orderBy: ["article.createdAt#DESC"]
1791
1791
  });
1792
1792
 
1793
- return befly.tool.Yes("获取成功", result);
1793
+ return befly.tool.Yes("获取成功", result.data);
1794
1794
  }
1795
1795
  };
1796
1796
  ```
@@ -1866,14 +1866,14 @@ export default {
1866
1866
  // 查询所有用户(不分页,注意上限 10000 条)
1867
1867
  const result = await befly.db.getAll({
1868
1868
  table: "user",
1869
- columns: ["id", "username", "nickname", "email", "phone", "createdAt"],
1869
+ fields: ["id", "username", "nickname", "email", "phone", "createdAt"],
1870
1870
  where: { state: 1 },
1871
1871
  orderBy: ["createdAt#DESC"]
1872
1872
  });
1873
1873
 
1874
1874
  // 转换为 CSV 格式
1875
1875
  const headers = ["ID", "用户名", "昵称", "邮箱", "手机", "注册时间"];
1876
- const rows = result.lists.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
1876
+ const rows = result.data.lists.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
1877
1877
 
1878
1878
  const csv = [headers.join(","), ...rows.map((r: any[]) => r.join(","))].join("\n");
1879
1879
 
@@ -54,21 +54,21 @@ export default {
54
54
  required: ["email", "password"],
55
55
  handler: async (befly, ctx) => {
56
56
  // 检查邮箱是否已存在
57
- const exists = await befly.db.getDetail({
57
+ const existsRes = await befly.db.getOne({
58
58
  table: "user",
59
- columns: ["id"],
59
+ fields: ["id"],
60
60
  where: { email: ctx.body.email }
61
61
  });
62
62
 
63
- if (exists?.id) {
64
- return No("该邮箱已被注册");
63
+ if (existsRes.data?.id) {
64
+ return befly.tool.No("该邮箱已被注册");
65
65
  }
66
66
 
67
67
  // 加密密码
68
68
  const hashedPassword = await befly.cipher.hashPassword(ctx.body.password);
69
69
 
70
70
  // 创建用户
71
- const result = await befly.db.insData({
71
+ const idRes = await befly.db.insData({
72
72
  table: "user",
73
73
  data: {
74
74
  email: ctx.body.email,
@@ -77,7 +77,7 @@ export default {
77
77
  }
78
78
  });
79
79
 
80
- return Yes("注册成功", { id: result.insertId });
80
+ return befly.tool.Yes("注册成功", { id: idRes.data });
81
81
  }
82
82
  } as ApiRoute;
83
83
  ```
@@ -100,31 +100,33 @@ export default {
100
100
  required: ["email", "password"],
101
101
  handler: async (befly, ctx) => {
102
102
  // 查询用户
103
- const user = await befly.db.getDetail({
103
+ const userRes = await befly.db.getOne({
104
104
  table: "user",
105
- columns: ["id", "email", "password", "nickname", "avatar", "role", "state"],
105
+ fields: ["id", "email", "password", "nickname", "avatar", "role", "state", "loginCount"],
106
106
  where: { email: ctx.body.email }
107
107
  });
108
108
 
109
+ const user = userRes.data;
110
+
109
111
  if (!user?.id) {
110
- return No("用户不存在");
112
+ return befly.tool.No("用户不存在");
111
113
  }
112
114
 
113
115
  if (user.state !== 1) {
114
- return No("账户已被禁用");
116
+ return befly.tool.No("账户已被禁用");
115
117
  }
116
118
 
117
119
  // 验证密码
118
120
  const isValid = await befly.cipher.verifyPassword(ctx.body.password, user.password);
119
121
  if (!isValid) {
120
- return No("密码错误");
122
+ return befly.tool.No("密码错误");
121
123
  }
122
124
 
123
125
  // 更新登录信息
124
126
  await befly.db.updData({
125
127
  table: "user",
126
128
  data: {
127
- loginCount: user.loginCount + 1,
129
+ loginCount: (user.loginCount || 0) + 1,
128
130
  lastLoginAt: Date.now()
129
131
  },
130
132
  where: { id: user.id }
@@ -132,11 +134,11 @@ export default {
132
134
 
133
135
  // 签发令牌
134
136
  const token = befly.jwt.sign({
135
- userId: user.id,
137
+ id: user.id,
136
138
  role: user.role
137
139
  });
138
140
 
139
- return Yes("登录成功", {
141
+ return befly.tool.Yes("登录成功", {
140
142
  token: token,
141
143
  user: {
142
144
  id: user.id,
@@ -162,17 +164,19 @@ export default {
162
164
  method: "GET",
163
165
  auth: true,
164
166
  handler: async (befly, ctx) => {
165
- const user = await befly.db.getDetail({
167
+ const userRes = await befly.db.getOne({
166
168
  table: "user",
167
- columns: ["id", "email", "nickname", "avatar", "phone", "gender", "birthday", "bio", "role", "createdAt"],
168
- where: { id: ctx.user.userId }
169
+ fields: ["id", "email", "nickname", "avatar", "phone", "gender", "birthday", "bio", "role", "createdAt"],
170
+ where: { id: ctx.user?.id }
169
171
  });
170
172
 
173
+ const user = userRes.data;
174
+
171
175
  if (!user?.id) {
172
- return No("用户不存在");
176
+ return befly.tool.No("用户不存在");
173
177
  }
174
178
 
175
- return Yes("获取成功", user);
179
+ return befly.tool.Yes("获取成功", user);
176
180
  }
177
181
  } as ApiRoute;
178
182
  ```
@@ -208,16 +212,16 @@ export default {
208
212
  if (ctx.body.bio !== undefined) updateData.bio = ctx.body.bio;
209
213
 
210
214
  if (Object.keys(updateData).length === 0) {
211
- return No("没有需要更新的字段");
215
+ return befly.tool.No("没有需要更新的字段");
212
216
  }
213
217
 
214
218
  await befly.db.updData({
215
219
  table: "user",
216
220
  data: updateData,
217
- where: { id: ctx.user.userId }
221
+ where: { id: ctx.user?.id }
218
222
  });
219
223
 
220
- return Yes("更新成功");
224
+ return befly.tool.Yes("更新成功");
221
225
  }
222
226
  } as ApiRoute;
223
227
  ```
@@ -240,20 +244,22 @@ export default {
240
244
  required: ["oldPassword", "newPassword"],
241
245
  handler: async (befly, ctx) => {
242
246
  // 获取用户密码
243
- const user = await befly.db.getDetail({
247
+ const userRes = await befly.db.getOne({
244
248
  table: "user",
245
- columns: ["id", "password"],
246
- where: { id: ctx.user.userId }
249
+ fields: ["id", "password"],
250
+ where: { id: ctx.user?.id }
247
251
  });
248
252
 
253
+ const user = userRes.data;
254
+
249
255
  if (!user?.id) {
250
- return No("用户不存在");
256
+ return befly.tool.No("用户不存在");
251
257
  }
252
258
 
253
259
  // 验证原密码
254
260
  const isValid = await befly.cipher.verifyPassword(ctx.body.oldPassword, user.password);
255
261
  if (!isValid) {
256
- return No("原密码错误");
262
+ return befly.tool.No("原密码错误");
257
263
  }
258
264
 
259
265
  // 加密新密码
@@ -263,10 +269,10 @@ export default {
263
269
  await befly.db.updData({
264
270
  table: "user",
265
271
  data: { password: hashedPassword },
266
- where: { id: ctx.user.userId }
272
+ where: { id: ctx.user?.id }
267
273
  });
268
274
 
269
- return Yes("密码修改成功");
275
+ return befly.tool.Yes("密码修改成功");
270
276
  }
271
277
  } as ApiRoute;
272
278
  ```
@@ -310,14 +316,14 @@ export default {
310
316
 
311
317
  const result = await befly.db.getList({
312
318
  table: "user",
313
- columns: ["id", "email", "nickname", "avatar", "phone", "role", "state", "loginCount", "lastLoginAt", "createdAt"],
319
+ fields: ["id", "email", "nickname", "avatar", "phone", "role", "state", "loginCount", "lastLoginAt", "createdAt"],
314
320
  where: where,
315
321
  page: page || 1,
316
322
  limit: limit || 20,
317
- orderBy: { id: "desc" }
323
+ orderBy: ["id#DESC"]
318
324
  });
319
325
 
320
- return Yes("获取成功", result);
326
+ return befly.tool.Yes("获取成功", result.data);
321
327
  }
322
328
  } as ApiRoute;
323
329
  ```
@@ -398,7 +404,7 @@ export default {
398
404
  cover: cover || "",
399
405
  categoryId: categoryId || 0,
400
406
  tags: tags || [],
401
- authorId: ctx.user.userId,
407
+ authorId: ctx.user?.id,
402
408
  publishedAt: Date.now()
403
409
  }
404
410
  });
@@ -412,7 +418,7 @@ export default {
412
418
  });
413
419
  }
414
420
 
415
- return Yes("发布成功", { id: result.insertId });
421
+ return befly.tool.Yes("发布成功", { id: result.data });
416
422
  }
417
423
  } as ApiRoute;
418
424
  ```
@@ -442,19 +448,21 @@ export default {
442
448
  const { id, title, content, summary, cover, categoryId, tags } = ctx.body;
443
449
 
444
450
  // 检查文章是否存在
445
- const article = await befly.db.getDetail({
451
+ const articleRes = await befly.db.getOne({
446
452
  table: "article",
447
- columns: ["id", "authorId", "categoryId"],
453
+ fields: ["id", "authorId", "categoryId"],
448
454
  where: { id: id }
449
455
  });
450
456
 
457
+ const article = articleRes.data;
458
+
451
459
  if (!article?.id) {
452
- return No("文章不存在");
460
+ return befly.tool.No("文章不存在");
453
461
  }
454
462
 
455
463
  // 检查权限(只能编辑自己的文章,管理员除外)
456
- if (article.authorId !== ctx.user.userId && ctx.user.role !== "admin") {
457
- return No("没有权限编辑此文章");
464
+ if (article.authorId !== ctx.user?.id && ctx.user?.role !== "admin") {
465
+ return befly.tool.No("没有权限编辑此文章");
458
466
  }
459
467
 
460
468
  const updateData: Record<string, any> = {};
@@ -466,7 +474,7 @@ export default {
466
474
  if (tags !== undefined) updateData.tags = tags;
467
475
 
468
476
  if (Object.keys(updateData).length === 0) {
469
- return No("没有需要更新的字段");
477
+ return befly.tool.No("没有需要更新的字段");
470
478
  }
471
479
 
472
480
  await befly.db.updData({
@@ -493,7 +501,7 @@ export default {
493
501
  }
494
502
  }
495
503
 
496
- return Yes("更新成功");
504
+ return befly.tool.Yes("更新成功");
497
505
  }
498
506
  } as ApiRoute;
499
507
  ```
@@ -514,19 +522,21 @@ export default {
514
522
  },
515
523
  required: ["id"],
516
524
  handler: async (befly, ctx) => {
517
- const article = await befly.db.getDetail({
525
+ const articleRes = await befly.db.getOne({
518
526
  table: "article",
519
- columns: ["id", "authorId", "categoryId"],
527
+ fields: ["id", "authorId", "categoryId"],
520
528
  where: { id: ctx.body.id }
521
529
  });
522
530
 
531
+ const article = articleRes.data;
532
+
523
533
  if (!article?.id) {
524
- return No("文章不存在");
534
+ return befly.tool.No("文章不存在");
525
535
  }
526
536
 
527
537
  // 检查权限
528
- if (article.authorId !== ctx.user.userId && ctx.user.role !== "admin") {
529
- return No("没有权限删除此文章");
538
+ if (article.authorId !== ctx.user?.id && ctx.user?.role !== "admin") {
539
+ return befly.tool.No("没有权限删除此文章");
530
540
  }
531
541
 
532
542
  // 软删除
@@ -544,7 +554,7 @@ export default {
544
554
  });
545
555
  }
546
556
 
547
- return Yes("删除成功");
557
+ return befly.tool.Yes("删除成功");
548
558
  }
549
559
  } as ApiRoute;
550
560
  ```
@@ -585,20 +595,20 @@ export default {
585
595
  }
586
596
 
587
597
  // 排序
588
- let order: Record<string, "asc" | "desc"> = { isTop: "desc", publishedAt: "desc" };
589
- if (orderBy === "views") order = { viewCount: "desc" };
590
- if (orderBy === "likes") order = { likeCount: "desc" };
598
+ let orderByList: string[] = ["isTop#DESC", "publishedAt#DESC"];
599
+ if (orderBy === "views") orderByList = ["viewCount#DESC"];
600
+ if (orderBy === "likes") orderByList = ["likeCount#DESC"];
591
601
 
592
602
  const result = await befly.db.getList({
593
603
  table: "article",
594
- columns: ["id", "title", "summary", "cover", "categoryId", "tags", "authorId", "viewCount", "likeCount", "commentCount", "isTop", "isRecommend", "publishedAt"],
604
+ fields: ["id", "title", "summary", "cover", "categoryId", "tags", "authorId", "viewCount", "likeCount", "commentCount", "isTop", "isRecommend", "publishedAt"],
595
605
  where: where,
596
606
  page: page || 1,
597
607
  limit: limit || 10,
598
- orderBy: order
608
+ orderBy: orderByList
599
609
  });
600
610
 
601
- return Yes("获取成功", result);
611
+ return befly.tool.Yes("获取成功", result.data);
602
612
  }
603
613
  } as ApiRoute;
604
614
  ```
@@ -619,13 +629,15 @@ export default {
619
629
  },
620
630
  required: ["id"],
621
631
  handler: async (befly, ctx) => {
622
- const article = await befly.db.getDetail({
632
+ const articleRes = await befly.db.getOne({
623
633
  table: "article",
624
634
  where: { id: ctx.body.id, state: 1 }
625
635
  });
626
636
 
637
+ const article = articleRes.data;
638
+
627
639
  if (!article?.id) {
628
- return No("文章不存在");
640
+ return befly.tool.No("文章不存在");
629
641
  }
630
642
 
631
643
  // 增加浏览量
@@ -636,25 +648,41 @@ export default {
636
648
  });
637
649
 
638
650
  // 获取作者信息
639
- const author = await befly.db.getDetail({
651
+ const authorRes = await befly.db.getOne({
640
652
  table: "user",
641
- columns: ["id", "nickname", "avatar"],
653
+ fields: ["id", "nickname", "avatar"],
642
654
  where: { id: article.authorId }
643
655
  });
644
656
 
657
+ const author = authorRes.data;
658
+
645
659
  // 获取分类信息
646
660
  let category = null;
647
661
  if (article.categoryId) {
648
- category = await befly.db.getDetail({
662
+ const categoryRes = await befly.db.getOne({
649
663
  table: "category",
650
- columns: ["id", "name", "slug"],
664
+ fields: ["id", "name", "slug"],
651
665
  where: { id: article.categoryId }
652
666
  });
667
+
668
+ category = categoryRes.data;
653
669
  }
654
670
 
655
- return Yes("获取成功", {
656
- ...article,
657
- viewCount: article.viewCount + 1,
671
+ return befly.tool.Yes("获取成功", {
672
+ id: article.id,
673
+ title: article.title,
674
+ content: article.content,
675
+ summary: article.summary,
676
+ cover: article.cover,
677
+ categoryId: article.categoryId,
678
+ tags: article.tags,
679
+ authorId: article.authorId,
680
+ viewCount: (article.viewCount || 0) + 1,
681
+ likeCount: article.likeCount,
682
+ commentCount: article.commentCount,
683
+ isTop: article.isTop,
684
+ isRecommend: article.isRecommend,
685
+ publishedAt: article.publishedAt,
658
686
  author: author,
659
687
  category: category
660
688
  });
@@ -684,18 +712,18 @@ export default {
684
712
  const file = formData.get("file") as File | null;
685
713
 
686
714
  if (!file) {
687
- return No("请选择文件");
715
+ return befly.tool.No("请选择文件");
688
716
  }
689
717
 
690
718
  // 检查文件大小(10MB)
691
719
  if (file.size > 10 * 1024 * 1024) {
692
- return No("文件大小不能超过 10MB");
720
+ return befly.tool.No("文件大小不能超过 10MB");
693
721
  }
694
722
 
695
723
  // 检查文件类型
696
724
  const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
697
725
  if (!allowedTypes.includes(file.type)) {
698
- return No("只支持 jpg/png/gif/webp 格式");
726
+ return befly.tool.No("只支持 jpg/png/gif/webp 格式");
699
727
  }
700
728
 
701
729
  // 生成文件名
@@ -716,7 +744,7 @@ export default {
716
744
  // 返回 URL
717
745
  const url = `/uploads/${new Date().toISOString().slice(0, 7)}/${fileName}`;
718
746
 
719
- return Yes("上传成功", {
747
+ return befly.tool.Yes("上传成功", {
720
748
  url: url,
721
749
  name: file.name,
722
750
  size: file.size,
@@ -746,7 +774,7 @@ export default {
746
774
  // 获取所有用户
747
775
  const result = await befly.db.getList({
748
776
  table: "user",
749
- columns: ["id", "email", "nickname", "phone", "role", "state", "createdAt"],
777
+ fields: ["id", "email", "nickname", "phone", "role", "state", "createdAt"],
750
778
  where: { state: { $gte: 0 } },
751
779
  page: 1,
752
780
  limit: 10000
@@ -754,7 +782,7 @@ export default {
754
782
 
755
783
  // 生成 CSV
756
784
  const headers = ["ID", "邮箱", "昵称", "手机号", "角色", "状态", "注册时间"];
757
- const rows = result.list.map((user: any) => [user.id, user.email, user.nickname, user.phone || "", user.role, user.state === 1 ? "正常" : "禁用", new Date(user.createdAt).toLocaleString()]);
785
+ const rows = result.data.lists.map((user: any) => [user.id, user.email, user.nickname, user.phone || "", user.role, user.state === 1 ? "正常" : "禁用", new Date(user.createdAt).toLocaleString()]);
758
786
 
759
787
  const csv = [headers.join(","), ...rows.map((row: any[]) => row.map((cell) => `"${cell}"`).join(","))].join("\n");
760
788
 
@@ -794,43 +822,49 @@ if (Object.keys(updateData).length === 0) {
794
822
  await befly.db.updData({
795
823
  table: "user",
796
824
  data: updateData,
797
- where: { id: ctx.user.userId }
825
+ where: { id: ctx.user?.id }
798
826
  });
799
827
  ```
800
828
 
801
829
  #### 优化写法(简洁)
802
830
 
803
831
  ```typescript
804
- // 直接传入,undefined 值自动过滤
805
- const { nickname, avatar, phone, gender, birthday, bio } = ctx.body;
832
+ import { fieldClear } from "befly/utils/fieldClear";
806
833
 
807
- const data = { nickname: nickname, avatar: avatar, phone: phone, gender: gender, birthday: birthday, bio: bio };
808
-
809
- // 使用 cleanFields 检查是否有有效数据
810
- const cleanData = befly.tool.cleanFields(data);
834
+ // 直接传入,undefined 值自动过滤
835
+ const data = {
836
+ nickname: ctx.body.nickname,
837
+ avatar: ctx.body.avatar,
838
+ phone: ctx.body.phone,
839
+ gender: ctx.body.gender,
840
+ birthday: ctx.body.birthday,
841
+ bio: ctx.body.bio
842
+ };
843
+
844
+ // 使用 fieldClear 检查是否有有效数据
845
+ const cleanData = fieldClear(data, { excludeValues: [null, undefined] });
811
846
  if (Object.keys(cleanData).length === 0) {
812
- return No("没有需要更新的字段");
847
+ return befly.tool.No("没有需要更新的字段");
813
848
  }
814
849
 
815
850
  await befly.db.updData({
816
851
  table: "user",
817
852
  data: cleanData,
818
- where: { id: ctx.user.userId }
853
+ where: { id: ctx.user?.id }
819
854
  });
820
855
  ```
821
856
 
822
- ### 使用 cleanFields 进行精细控制
857
+ ### 使用 fieldClear 进行精细控制
823
858
 
824
- 当需要保留特定值(如 0、空字符串)时,使用 `cleanFields` 的高级参数:
859
+ 当需要保留特定值(如 0、空字符串)时,使用 `fieldClear` 的高级参数:
825
860
 
826
861
  ```typescript
827
862
  const { nickname, sort, state, remark } = ctx.body;
828
863
 
829
864
  // 保留 0 值(sort 和 state 允许为 0)
830
- const data = befly.tool.cleanFields(
865
+ const data = fieldClear(
831
866
  { nickname: nickname, sort: sort, state: state, remark: remark },
832
- [null, undefined], // 排除 null undefined
833
- { sort: true, state: true } // 保留这些字段的 0 值
867
+ { excludeValues: [null, undefined], keepMap: { sort: 0, state: 0 } }
834
868
  );
835
869
 
836
870
  await befly.db.updData({
@@ -850,7 +884,7 @@ const { keyword, state, categoryId, startDate, endDate } = ctx.body;
850
884
 
851
885
  const result = await befly.db.getList({
852
886
  table: "article",
853
- columns: ["id", "title", "createdAt"],
887
+ fields: ["id", "title", "createdAt"],
854
888
  where: {
855
889
  state: state, // undefined 时忽略
856
890
  categoryId: categoryId, // undefined 时忽略
@@ -87,26 +87,28 @@ export default {
87
87
  required: ["email", "password"],
88
88
  handler: async (befly, ctx) => {
89
89
  // 查询用户
90
- const user = await befly.db.getDetail({
90
+ const userRes = await befly.db.getOne({
91
91
  table: "user",
92
- columns: ["id", "email", "password", "nickname"],
92
+ fields: ["id", "email", "password", "nickname"],
93
93
  where: { email: ctx.body.email }
94
94
  });
95
95
 
96
+ const user = userRes.data;
97
+
96
98
  if (!user?.id) {
97
- return No("用户不存在");
99
+ return befly.tool.No("用户不存在");
98
100
  }
99
101
 
100
102
  // 验证密码
101
103
  const isValid = await befly.cipher.verifyPassword(ctx.body.password, user.password);
102
104
  if (!isValid) {
103
- return No("密码错误");
105
+ return befly.tool.No("密码错误");
104
106
  }
105
107
 
106
108
  // 签发令牌
107
- const token = befly.jwt.sign({ userId: user.id });
109
+ const token = befly.jwt.sign({ id: user.id });
108
110
 
109
- return Yes("登录成功", { token: token, user: { id: user.id, nickname: user.nickname } });
111
+ return befly.tool.Yes("登录成功", { token: token, user: { id: user.id, nickname: user.nickname } });
110
112
  }
111
113
  } as ApiRoute;
112
114
  ```
@@ -1,128 +1,189 @@
1
- # Befly 数据库操作指南
1
+ # Befly 数据库(DbHelper)
2
2
 
3
- > 本文档详细介绍 Befly 框架的数据库操作 API,包括 CRUD 操作、事务、条件查询等。
3
+ 本文档只描述 **当前实现**:所有数据库操作统一返回 `DbResult`(`{ data, sql }`),调用方必须读取 `.data`。
4
4
 
5
- ## 目录
5
+ ## 核心约定:DbResult(必须读 data)
6
6
 
7
- - [核心概念](#核心概念)
8
- - [字段命名规范](#字段命名规范)
9
- - [查询方法](#查询方法)
10
- - [写入方法](#写入方法)
11
- - [数值操作](#数值操作)
12
- - [事务操作](#事务操作)
13
- - [多表联查](#多表联查)
14
- - [Where 条件语法](#where-条件语法)
15
- - [字段选择语法](#字段选择语法)
16
- - [排序语法](#排序语法)
17
- - [系统字段说明](#系统字段说明)
18
- - [完整示例](#完整示例)
7
+ `DbHelper` 的所有方法都会返回:
19
8
 
20
- ---
9
+ - `data`:你真正要用的结果
10
+ - `sql`:本次执行的 SQL 信息(用于调试/排错)
21
11
 
22
- ## 核心概念
12
+ ```js
13
+ type DbResult<TData = any, TSql = SqlInfo> = {
14
+ data: TData;
15
+ sql: TSql;
16
+ };
17
+
18
+ ```
19
+
20
+ ### SQL 信息(SqlInfo / ListSql)
23
21
 
24
- ### DbHelper
22
+ - 单条/写入类操作的 `sql` 为 `SqlInfo`:
23
+ - `sql`:SQL 字符串
24
+ - `params`:参数数组
25
+ - `duration`:耗时(毫秒)
26
+ - 列表/全量查询的 `sql` 为 `ListSql`:
27
+ - `count`:统计 SQL(用于 total)
28
+ - `data?`:数据 SQL(当 total=0 时可能不会执行第二次查询)
25
29
 
26
- `DbHelper` Befly 的数据库操作核心类,提供了完整的 CRUD 封装。通过 `befly.db` 访问。
30
+ > 注意:不要把整个 `DbResult` 直接返回给客户端;应只返回 `result.data`。
31
+
32
+ ## 常用查询
33
+
34
+ ### getOne(单条)
27
35
 
28
36
  ```typescript
29
- // API handler 中使用
30
- handler: async (befly, ctx) => {
31
- const user = await befly.db.getOne({
32
- table: "user",
33
- where: { id: 1 }
34
- });
35
- };
37
+ const userRes = await befly.db.getOne({
38
+ table: "user",
39
+ fields: ["id", "email", "nickname"],
40
+ where: { id: ctx.user?.id }
41
+ });
42
+
43
+ const user = userRes.data;
44
+ if (!user?.id) {
45
+ return befly.tool.No("用户不存在");
46
+ }
47
+
48
+ return befly.tool.Yes("获取成功", user);
36
49
  ```
37
50
 
38
- ### 自动转换
39
-
40
- - **表名**:小驼峰 `userProfile` 自动转换为下划线 `user_profile`
41
- - **字段名**:写入时小驼峰转下划线,查询时下划线转小驼峰
42
- - **BIGINT 字段**:`id`、`*Id`、`*_id`、`*At`、`*_at` 自动转为 number
51
+ ### getList(分页)
43
52
 
44
- ### 自动过滤 null 和 undefined
53
+ ```typescript
54
+ const listRes = await befly.db.getList({
55
+ table: "user",
56
+ fields: ["id", "email", "nickname"],
57
+ where: { state: 1 },
58
+ page: 1,
59
+ limit: 20,
60
+ orderBy: ["id#DESC"]
61
+ });
62
+
63
+ return befly.tool.Yes("获取成功", listRes.data);
64
+ // listRes.data: { lists, total, page, limit, pages }
65
+ ```
45
66
 
46
- 所有写入方法(`insData`、`insBatch`、`updData`)和条件查询(`where`)都会**自动过滤值为 `null` 或 `undefined` 的字段**。
67
+ ### getAll(不分页,有上限保护)
47
68
 
48
- ---
69
+ ```typescript
70
+ const allRes = await befly.db.getAll({
71
+ table: "user",
72
+ fields: ["id", "email"],
73
+ where: { state: 1 },
74
+ orderBy: ["createdAt#DESC"]
75
+ });
76
+
77
+ return befly.tool.Yes("获取成功", allRes.data);
78
+ // allRes.data: { lists, total }
79
+ ```
49
80
 
50
- ## 字段命名规范
81
+ ## 写入与返回值
51
82
 
52
- | 位置 | 格式 | 示例 |
53
- | --------------------- | ------ | ----------------------- |
54
- | 代码中(参数/返回值) | 小驼峰 | `userId`, `createdAt` |
55
- | 数据库中 | 下划线 | `user_id`, `created_at` |
83
+ ### insData(插入单条)
56
84
 
57
- ---
85
+ `insData` 返回插入的 ID(数值):
58
86
 
59
- ## 查询方法
87
+ ```typescript
88
+ const idRes = await befly.db.insData({
89
+ table: "user",
90
+ data: {
91
+ email: ctx.body.email,
92
+ nickname: ctx.body.nickname
93
+ }
94
+ });
95
+
96
+ return befly.tool.Yes("创建成功", { id: idRes.data });
97
+ ```
60
98
 
61
- - `getOne`:查询单条
62
- - `getList`:分页查询
63
- - `getAll`:查询全部(有上限保护)
64
- - `getCount`:查询数量
65
- - `exists`:检查存在
66
- - `getFieldValue`:查询单字段
99
+ ### updData / delData
67
100
 
68
- ---
101
+ `updData` / `delData` 返回影响行数(数值):
69
102
 
70
- ## 写入方法
103
+ ```typescript
104
+ const updRes = await befly.db.updData({
105
+ table: "user",
106
+ data: { nickname: ctx.body.nickname },
107
+ where: { id: ctx.user?.id }
108
+ });
71
109
 
72
- - `insData`:插入单条(自动生成系统字段)
73
- - `insBatch`:批量插入
74
- - `updData`:更新(自动更新 `updated_at`)
75
- - `delData`:软删除
76
- - `delForce`:硬删除
77
- - `disableData` / `enableData`:禁用/启用
110
+ return befly.tool.Yes("更新成功", { changed: updRes.data });
111
+ ```
78
112
 
79
- ---
113
+ ### insBatch(批量插入)
80
114
 
81
- ## 数值操作
115
+ `insBatch` 返回插入的 ID 列表:
82
116
 
83
- - `increment`:自增
84
- - `decrement`:自减
117
+ ```typescript
118
+ const idsRes = await befly.db.insBatch("user", [
119
+ { email: "a@qq.com", nickname: "A" },
120
+ { email: "b@qq.com", nickname: "B" }
121
+ ]);
85
122
 
86
- ---
123
+ return befly.tool.Yes("导入成功", { ids: idsRes.data });
124
+ ```
87
125
 
88
- ## 事务操作
126
+ ## 字段/排序
89
127
 
90
- 使用 `trans` 方法执行事务,自动处理 commit/rollback。
128
+ ### fields(字段选择)
91
129
 
92
- ---
130
+ - 不传或 `[]`:查询所有字段
131
+ - 仅包含字段:`["id", "email"]`
132
+ - 仅排除字段:`["!password"]`
93
133
 
94
- ## 多表联查
134
+ > 禁止混用包含与排除。
95
135
 
96
- 查询方法支持通过 `joins` 参数进行多表联查。
136
+ ### orderBy(排序)
97
137
 
98
- ---
138
+ 格式:`"字段#ASC" | "字段#DESC"`,例如:`["id#DESC"]`。
99
139
 
100
- ## Where 条件语法
140
+ ## null/undefined 自动过滤
101
141
 
102
- 支持 `$or/$and` 与 `$gt/$gte/$lt/$lte/$in/$between/$null/$like` 等。
142
+ DbHelper 在写入(`insData/insBatch/updData`)以及查询条件(`where`)中,会自动过滤 `null/undefined`。
103
143
 
104
- ---
144
+ 如果你希望在业务侧更精细控制(例如:保留 `0` / 空字符串),请使用 `fieldClear`:
105
145
 
106
- ## 字段选择语法
146
+ ```typescript
147
+ import { fieldClear } from "befly/utils/fieldClear";
107
148
 
108
- - `fields: []` / 不传:查询所有字段
109
- - `fields: ["id", "name"]`:指定字段
110
- - `fields: ["!password"]`:排除字段(不能混用包含与排除)
149
+ const data = fieldClear(
150
+ { nickname: ctx.body.nickname, sort: ctx.body.sort, state: ctx.body.state },
151
+ { excludeValues: [null, undefined], keepMap: { sort: 0, state: 0 } }
152
+ );
153
+ ```
111
154
 
112
- ---
155
+ ## 事务(trans)
113
156
 
114
- ## 排序语法
157
+ ````typescript
158
+ const out = await befly.db.trans(async (trx) => {
159
+ const idRes = await trx.insData({
160
+ table: "order",
161
+ data: { userId: ctx.user?.id, status: "pending" }
162
+ });
163
+ return { orderId: idRes.data };
164
+ });
115
165
 
116
- 使用 `字段#方向` 格式:`ASC` / `DESC`。
166
+ return befly.tool.Yes("创建成功", out);
117
167
 
118
- ---
168
+ ## 原生 SQL(query / unsafe)
119
169
 
120
- ## 系统字段说明
170
+ ```typescript
171
+ const r = await befly.db.query("SELECT 1 AS cnt");
172
+ return befly.tool.Yes("ok", r.data);
173
+ ````
121
174
 
122
- 每条记录自动包含:`id`、`created_at`、`updated_at`、`deleted_at`、`state`。
175
+ `unsafe` 用于内部脚本/同步逻辑(行为与 `query` 相同,返回 `DbResult`)。
123
176
 
124
- ---
177
+ ## 错误定位:error.sqlInfo
125
178
 
126
- ## 完整示例
179
+ SQL 执行失败,抛出的错误对象会带 `sqlInfo`:
127
180
 
128
- 更完整示例与细节说明请参考仓库中的 `DbHelper` 相关测试用例与源码实现。
181
+ ```typescript
182
+ try {
183
+ await befly.db.query("SELECT * FROM not_exists");
184
+ } catch (error: any) {
185
+ // error.sqlInfo: { sql, params, duration }
186
+ befly.logger.error({ sqlInfo: error?.sqlInfo }, "SQL 执行失败");
187
+ throw error;
188
+ }
189
+ ```
@@ -263,16 +263,20 @@ const dbPlugin: Plugin = {
263
263
 
264
264
  ```typescript
265
265
  // 查询
266
- const user = await befly.db.getOne({ table: "user", where: { id: 1 } });
266
+ const userRes = await befly.db.getOne({ table: "user", where: { id: 1 } });
267
+ const user = userRes.data;
267
268
 
268
269
  // 插入
269
- const id = await befly.db.insData({ table: "user", data: { name: "张三" } });
270
+ const idRes = await befly.db.insData({ table: "user", data: { name: "张三" } });
271
+ const id = idRes.data;
270
272
 
271
273
  // 更新
272
- await befly.db.updData({ table: "user", data: { name: "李四" }, where: { id: 1 } });
274
+ const updRes = await befly.db.updData({ table: "user", data: { name: "李四" }, where: { id: 1 } });
275
+ const changed = updRes.data;
273
276
 
274
277
  // 删除
275
- await befly.db.delData({ table: "user", where: { id: 1 } });
278
+ const delRes = await befly.db.delData({ table: "user", where: { id: 1 } });
279
+ const deleted = delRes.data;
276
280
  ```
277
281
 
278
282
  > 详细用法请参考 [database.md](./database.md)
@@ -280,12 +280,14 @@ export default {
280
280
  required: ["email", "password"],
281
281
  handler: async (befly, ctx) => {
282
282
  // 使用 addon 表时需要完整表名
283
- const admin = await befly.db.getDetail({
283
+ const adminRes = await befly.db.getOne({
284
284
  table: "addon_admin_admin",
285
- columns: ["id", "email", "password", "nickname"],
285
+ fields: ["id", "email", "password", "nickname"],
286
286
  where: { email: ctx.body.email }
287
287
  });
288
288
 
289
+ const admin = adminRes.data;
290
+
289
291
  // ... 业务逻辑
290
292
 
291
293
  return Yes("登录成功", { token: token });
@@ -325,7 +327,7 @@ export default {
325
327
  // 使用完整表名
326
328
  await befly.db.getList({
327
329
  table: "addon_admin_role", // addon_{name}_{table}
328
- columns: ["id", "name", "code"]
330
+ fields: ["id", "name", "code"]
329
331
  });
330
332
  ```
331
333
 
@@ -535,7 +535,7 @@ await syncTable(ctx, sources.tables);
535
535
  └─ 保留字段检查
536
536
 
537
537
  2. 建立数据库连接
538
- └─ 检查数据库版本(MySQL 8+, PostgreSQL 17+)
538
+ └─ 检查数据库版本(MySQL 8+, PostgreSQL 17+, SQLite 3.50+)
539
539
 
540
540
  3. 扫描表定义文件
541
541
  ├─ 项目 tables 目录
@@ -589,30 +589,32 @@ A: 数组类型会验证:
589
589
 
590
590
  ### Q: 如何在验证前清理数据中的 null/undefined 值?
591
591
 
592
- A: 使用 `befly.tool.cleanFields` 方法:
592
+ A: 使用 `fieldClear` 工具函数:
593
593
 
594
594
  ```typescript
595
+ import { fieldClear } from "befly/utils/fieldClear";
596
+
595
597
  // 在 API handler 中使用
596
598
  handler: async (befly, ctx) => {
597
599
  const { nickname, phone, address } = ctx.body;
598
600
 
599
601
  // 清理 null 和 undefined 值
600
- const cleanData = befly.tool.cleanFields({
602
+ const cleanData = fieldClear({
601
603
  nickname: nickname,
602
604
  phone: phone,
603
605
  address: address
604
- });
606
+ }, { excludeValues: [null, undefined] });
605
607
 
606
608
  // cleanData 只包含有效值
607
609
  await befly.db.updData({
608
610
  table: "user",
609
611
  data: cleanData,
610
- where: { id: ctx.user.userId }
612
+ where: { id: ctx.user?.id }
611
613
  });
612
614
 
613
615
  return Yes("更新成功");
614
616
  };
615
617
  ```
616
618
 
617
- > **注意**:数据库操作(insData、updData 等)会自动过滤 null/undefined 值,通常不需要手动调用 cleanFields
619
+ > **注意**:数据库操作(insData、updData 等)会自动过滤 null/undefined 值,通常不需要手动调用 fieldClear
618
620
  > 详见 [database.md](../plugins/database.md#nullundefined-值自动过滤)。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.10.14",
4
- "gitHead": "c758a3e8a78966ac35043989fd80cf377ad96006",
3
+ "version": "3.10.16",
4
+ "gitHead": "f5d17c019a58e82cfacec5ce3a1f04e3225ba838",
5
5
  "private": false,
6
6
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
7
7
  "keywords": [
@@ -59,10 +59,10 @@
59
59
  },
60
60
  "scripts": {
61
61
  "test": "bun test",
62
- "typecheck": "bunx tsgo --noEmit"
62
+ "typecheck": "tsc --noEmit"
63
63
  },
64
64
  "dependencies": {
65
- "befly-shared": "^1.3.8",
65
+ "befly-shared": "^1.3.9",
66
66
  "chalk": "^5.6.2",
67
67
  "es-toolkit": "^1.43.0",
68
68
  "fast-jwt": "^6.1.0",
@@ -1,8 +0,0 @@
1
- # Quickstart 快速入门(已迁移)
2
-
3
- 为避免重复内容长期漂移,本 Quickstart 已迁移到权威入口:
4
-
5
- - `packages/core/docs/guide/quickstart.md`
6
- - 在线阅读:[`./guide/quickstart.md`](./guide/quickstart.md)
7
-
8
- 如你从旧链接跳转到这里,请以 `guide/quickstart.md` 的内容为准(配置字段、同步流程、权限 pathname 规则等都以当前实现为准)。