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 +63 -63
- package/docs/guide/examples.md +117 -83
- package/docs/guide/quickstart.md +8 -6
- package/docs/plugins/database.md +143 -82
- package/docs/plugins/plugin.md +8 -4
- package/docs/reference/addon.md +5 -3
- package/docs/reference/table.md +1 -1
- package/docs/reference/validator.md +7 -5
- package/package.json +4 -4
- package/docs/quickstart.md +0 -8
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
|
-
|
|
521
|
+
fields: ["id", "title", "summary", "createdAt"],
|
|
522
522
|
where: where,
|
|
523
523
|
page: page || 1,
|
|
524
524
|
limit: limit || 10,
|
|
525
|
-
orderBy:
|
|
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
|
|
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
|
-
####
|
|
1341
|
+
#### fieldClear - 清理数据字段
|
|
1340
1342
|
|
|
1341
|
-
|
|
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
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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 =
|
|
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
|
-
//
|
|
1377
|
-
const cleanData =
|
|
1378
|
-
|
|
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
|
-
>
|
|
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.
|
|
1564
|
+
const result = await befly.db.trans(async (trx) => {
|
|
1566
1565
|
// 1. 查询商品信息(带锁)
|
|
1567
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
1735
|
+
const orderRes = await befly.db.getOne({
|
|
1732
1736
|
table: 'order',
|
|
1733
1737
|
where: { id: ctx.body.id }
|
|
1734
1738
|
});
|
|
1735
1739
|
|
|
1736
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/docs/guide/examples.md
CHANGED
|
@@ -54,21 +54,21 @@ export default {
|
|
|
54
54
|
required: ["email", "password"],
|
|
55
55
|
handler: async (befly, ctx) => {
|
|
56
56
|
// 检查邮箱是否已存在
|
|
57
|
-
const
|
|
57
|
+
const existsRes = await befly.db.getOne({
|
|
58
58
|
table: "user",
|
|
59
|
-
|
|
59
|
+
fields: ["id"],
|
|
60
60
|
where: { email: ctx.body.email }
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
if (
|
|
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
|
|
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:
|
|
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
|
|
103
|
+
const userRes = await befly.db.getOne({
|
|
104
104
|
table: "user",
|
|
105
|
-
|
|
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
|
-
|
|
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
|
|
167
|
+
const userRes = await befly.db.getOne({
|
|
166
168
|
table: "user",
|
|
167
|
-
|
|
168
|
-
where: { id: ctx.user
|
|
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
|
|
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
|
|
247
|
+
const userRes = await befly.db.getOne({
|
|
244
248
|
table: "user",
|
|
245
|
-
|
|
246
|
-
where: { id: ctx.user
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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
|
|
451
|
+
const articleRes = await befly.db.getOne({
|
|
446
452
|
table: "article",
|
|
447
|
-
|
|
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
|
|
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
|
|
525
|
+
const articleRes = await befly.db.getOne({
|
|
518
526
|
table: "article",
|
|
519
|
-
|
|
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
|
|
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
|
|
589
|
-
if (orderBy === "views")
|
|
590
|
-
if (orderBy === "likes")
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
651
|
+
const authorRes = await befly.db.getOne({
|
|
640
652
|
table: "user",
|
|
641
|
-
|
|
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
|
-
|
|
662
|
+
const categoryRes = await befly.db.getOne({
|
|
649
663
|
table: "category",
|
|
650
|
-
|
|
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
|
-
|
|
657
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
825
|
+
where: { id: ctx.user?.id }
|
|
798
826
|
});
|
|
799
827
|
```
|
|
800
828
|
|
|
801
829
|
#### 优化写法(简洁)
|
|
802
830
|
|
|
803
831
|
```typescript
|
|
804
|
-
|
|
805
|
-
const { nickname, avatar, phone, gender, birthday, bio } = ctx.body;
|
|
832
|
+
import { fieldClear } from "befly/utils/fieldClear";
|
|
806
833
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
|
853
|
+
where: { id: ctx.user?.id }
|
|
819
854
|
});
|
|
820
855
|
```
|
|
821
856
|
|
|
822
|
-
### 使用
|
|
857
|
+
### 使用 fieldClear 进行精细控制
|
|
823
858
|
|
|
824
|
-
当需要保留特定值(如 0、空字符串)时,使用 `
|
|
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 =
|
|
865
|
+
const data = fieldClear(
|
|
831
866
|
{ nickname: nickname, sort: sort, state: state, remark: remark },
|
|
832
|
-
[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
|
-
|
|
887
|
+
fields: ["id", "title", "createdAt"],
|
|
854
888
|
where: {
|
|
855
889
|
state: state, // undefined 时忽略
|
|
856
890
|
categoryId: categoryId, // undefined 时忽略
|
package/docs/guide/quickstart.md
CHANGED
|
@@ -87,26 +87,28 @@ export default {
|
|
|
87
87
|
required: ["email", "password"],
|
|
88
88
|
handler: async (befly, ctx) => {
|
|
89
89
|
// 查询用户
|
|
90
|
-
const
|
|
90
|
+
const userRes = await befly.db.getOne({
|
|
91
91
|
table: "user",
|
|
92
|
-
|
|
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({
|
|
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
|
```
|
package/docs/plugins/database.md
CHANGED
|
@@ -1,128 +1,189 @@
|
|
|
1
|
-
# Befly
|
|
1
|
+
# Befly 数据库(DbHelper)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
30
|
+
> 注意:不要把整个 `DbResult` 直接返回给客户端;应只返回 `result.data`。
|
|
31
|
+
|
|
32
|
+
## 常用查询
|
|
33
|
+
|
|
34
|
+
### getOne(单条)
|
|
27
35
|
|
|
28
36
|
```typescript
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
128
|
+
### fields(字段选择)
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
- 不传或 `[]`:查询所有字段
|
|
131
|
+
- 仅包含字段:`["id", "email"]`
|
|
132
|
+
- 仅排除字段:`["!password"]`
|
|
93
133
|
|
|
94
|
-
|
|
134
|
+
> 禁止混用包含与排除。
|
|
95
135
|
|
|
96
|
-
|
|
136
|
+
### orderBy(排序)
|
|
97
137
|
|
|
98
|
-
|
|
138
|
+
格式:`"字段#ASC" | "字段#DESC"`,例如:`["id#DESC"]`。
|
|
99
139
|
|
|
100
|
-
##
|
|
140
|
+
## null/undefined 自动过滤
|
|
101
141
|
|
|
102
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
`unsafe` 用于内部脚本/同步逻辑(行为与 `query` 相同,返回 `DbResult`)。
|
|
123
176
|
|
|
124
|
-
|
|
177
|
+
## 错误定位:error.sqlInfo
|
|
125
178
|
|
|
126
|
-
|
|
179
|
+
当 SQL 执行失败,抛出的错误对象会带 `sqlInfo`:
|
|
127
180
|
|
|
128
|
-
|
|
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
|
+
```
|
package/docs/plugins/plugin.md
CHANGED
|
@@ -263,16 +263,20 @@ const dbPlugin: Plugin = {
|
|
|
263
263
|
|
|
264
264
|
```typescript
|
|
265
265
|
// 查询
|
|
266
|
-
const
|
|
266
|
+
const userRes = await befly.db.getOne({ table: "user", where: { id: 1 } });
|
|
267
|
+
const user = userRes.data;
|
|
267
268
|
|
|
268
269
|
// 插入
|
|
269
|
-
const
|
|
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)
|
package/docs/reference/addon.md
CHANGED
|
@@ -280,12 +280,14 @@ export default {
|
|
|
280
280
|
required: ["email", "password"],
|
|
281
281
|
handler: async (befly, ctx) => {
|
|
282
282
|
// 使用 addon 表时需要完整表名
|
|
283
|
-
const
|
|
283
|
+
const adminRes = await befly.db.getOne({
|
|
284
284
|
table: "addon_admin_admin",
|
|
285
|
-
|
|
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
|
-
|
|
330
|
+
fields: ["id", "name", "code"]
|
|
329
331
|
});
|
|
330
332
|
```
|
|
331
333
|
|
package/docs/reference/table.md
CHANGED
|
@@ -589,30 +589,32 @@ A: 数组类型会验证:
|
|
|
589
589
|
|
|
590
590
|
### Q: 如何在验证前清理数据中的 null/undefined 值?
|
|
591
591
|
|
|
592
|
-
A: 使用 `
|
|
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 =
|
|
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
|
|
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 值,通常不需要手动调用
|
|
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.
|
|
4
|
-
"gitHead": "
|
|
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": "
|
|
62
|
+
"typecheck": "tsc --noEmit"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"befly-shared": "^1.3.
|
|
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",
|
package/docs/quickstart.md
DELETED