befly 3.10.0 → 3.10.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.
Files changed (79) hide show
  1. package/.gitignore +0 -0
  2. package/README.md +10 -13
  3. package/configs/presetFields.ts +10 -0
  4. package/configs/presetRegexp.ts +225 -0
  5. package/docs/README.md +17 -11
  6. package/docs/api/api.md +15 -1
  7. package/docs/guide/quickstart.md +19 -5
  8. package/docs/infra/redis.md +23 -11
  9. package/docs/quickstart.md +5 -335
  10. package/docs/reference/addon.md +0 -15
  11. package/docs/reference/config.md +1 -1
  12. package/docs/reference/logger.md +3 -3
  13. package/docs/reference/sync.md +99 -73
  14. package/docs/reference/table.md +1 -1
  15. package/package.json +15 -16
  16. package/docs/cipher.md +0 -582
  17. package/docs/database.md +0 -1176
  18. package/tests/_mocks/mockSqliteDb.ts +0 -204
  19. package/tests/addonHelper-cache.test.ts +0 -32
  20. package/tests/api-integration-array-number.test.ts +0 -282
  21. package/tests/apiHandler-routePath-only.test.ts +0 -32
  22. package/tests/befly-config-env.test.ts +0 -78
  23. package/tests/cacheHelper.test.ts +0 -323
  24. package/tests/cacheKeys.test.ts +0 -41
  25. package/tests/checkApi-routePath-strict.test.ts +0 -166
  26. package/tests/checkMenu.test.ts +0 -346
  27. package/tests/checkTable-smoke.test.ts +0 -157
  28. package/tests/cipher.test.ts +0 -249
  29. package/tests/dbDialect-cache.test.ts +0 -23
  30. package/tests/dbDialect.test.ts +0 -46
  31. package/tests/dbHelper-advanced.test.ts +0 -723
  32. package/tests/dbHelper-all-array-types.test.ts +0 -316
  33. package/tests/dbHelper-array-serialization.test.ts +0 -258
  34. package/tests/dbHelper-batch-write.test.ts +0 -90
  35. package/tests/dbHelper-columns.test.ts +0 -234
  36. package/tests/dbHelper-execute.test.ts +0 -187
  37. package/tests/dbHelper-joins.test.ts +0 -221
  38. package/tests/fields-redis-cache.test.ts +0 -127
  39. package/tests/fields-validate.test.ts +0 -99
  40. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  41. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  42. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  43. package/tests/getClientIp.test.ts +0 -54
  44. package/tests/integration.test.ts +0 -189
  45. package/tests/jwt.test.ts +0 -65
  46. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  47. package/tests/logger.test.ts +0 -325
  48. package/tests/redisHelper.test.ts +0 -495
  49. package/tests/redisKeys.test.ts +0 -9
  50. package/tests/scanConfig.test.ts +0 -144
  51. package/tests/scanFiles-routePath.test.ts +0 -46
  52. package/tests/smoke-sql.test.ts +0 -24
  53. package/tests/sqlBuilder-advanced.test.ts +0 -608
  54. package/tests/sqlBuilder.test.ts +0 -209
  55. package/tests/sync-connection.test.ts +0 -183
  56. package/tests/sync-init-guard.test.ts +0 -105
  57. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  58. package/tests/syncApi-obsolete-records.test.ts +0 -69
  59. package/tests/syncApi-type-compat.test.ts +0 -72
  60. package/tests/syncDev-permissions.test.ts +0 -81
  61. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  62. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  63. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  64. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  65. package/tests/syncMenu-paths.test.ts +0 -59
  66. package/tests/syncTable-apply.test.ts +0 -279
  67. package/tests/syncTable-array-number.test.ts +0 -160
  68. package/tests/syncTable-constants.test.ts +0 -101
  69. package/tests/syncTable-db-integration.test.ts +0 -237
  70. package/tests/syncTable-ddl.test.ts +0 -245
  71. package/tests/syncTable-helpers.test.ts +0 -99
  72. package/tests/syncTable-schema.test.ts +0 -99
  73. package/tests/syncTable-testkit.test.ts +0 -25
  74. package/tests/syncTable-types.test.ts +0 -122
  75. package/tests/tableRef-and-deserialize.test.ts +0 -67
  76. package/tests/util.test.ts +0 -100
  77. package/tests/validator-array-number.test.ts +0 -310
  78. package/tests/validator-default.test.ts +0 -373
  79. package/tests/validator.test.ts +0 -679
package/docs/database.md DELETED
@@ -1,1176 +0,0 @@
1
- # Befly 数据库操作指南
2
-
3
- > 本文档详细介绍 Befly 框架的数据库操作 API,包括 CRUD 操作、事务、条件查询等。
4
-
5
- > 本文档已迁移到:[`./plugins/database.md`](./plugins/database.md)
6
-
7
- ## 目录
8
-
9
- - [Befly 数据库操作指南](#befly-数据库操作指南)
10
- - [目录](#目录)
11
- - [核心概念](#核心概念)
12
- - [DbHelper](#dbhelper)
13
- - [自动转换](#自动转换)
14
- - [自动过滤 null 和 undefined](#自动过滤-null-和-undefined)
15
- - [写入时自动过滤](#写入时自动过滤)
16
- - [更新时自动过滤](#更新时自动过滤)
17
- - [Where 条件自动过滤](#where-条件自动过滤)
18
- - [实际应用示例](#实际应用示例)
19
- - [手动清理字段](#手动清理字段)
20
- - [字段命名规范](#字段命名规范)
21
- - [查询方法](#查询方法)
22
- - [getOne - 查询单条](#getone---查询单条)
23
- - [getList - 分页查询](#getlist---分页查询)
24
- - [getAll - 查询全部](#getall---查询全部)
25
- - [getCount - 查询数量](#getcount---查询数量)
26
- - [exists - 检查存在](#exists---检查存在)
27
- - [getFieldValue - 查询单字段](#getfieldvalue---查询单字段)
28
- - [写入方法](#写入方法)
29
- - [insData - 插入数据](#insdata---插入数据)
30
- - [insBatch - 批量插入](#insbatch---批量插入)
31
- - [updData - 更新数据](#upddata---更新数据)
32
- - [delData - 软删除](#deldata---软删除)
33
- - [delForce - 硬删除](#delforce---硬删除)
34
- - [disableData - 禁用](#disabledata---禁用)
35
- - [enableData - 启用](#enabledata---启用)
36
- - [数值操作](#数值操作)
37
- - [increment - 自增](#increment---自增)
38
- - [decrement - 自减](#decrement---自减)
39
- - [事务操作](#事务操作)
40
- - [多表联查](#多表联查)
41
- - [简洁写法(推荐)](#简洁写法推荐)
42
- - [JoinOption 参数说明](#joinoption-参数说明)
43
- - [联查注意事项](#联查注意事项)
44
- - [完整示例:订单列表 API](#完整示例订单列表-api)
45
- - [使用 query 原始 SQL](#使用-query-原始-sql)
46
- - [Where 条件语法](#where-条件语法)
47
- - [基础条件](#基础条件)
48
- - [比较操作符](#比较操作符)
49
- - [逻辑操作符](#逻辑操作符)
50
- - [范围操作符](#范围操作符)
51
- - [空值操作符](#空值操作符)
52
- - [模糊匹配](#模糊匹配)
53
- - [字段选择语法](#字段选择语法)
54
- - [查询所有字段](#查询所有字段)
55
- - [指定字段](#指定字段)
56
- - [排除字段](#排除字段)
57
- - [排序语法](#排序语法)
58
- - [系统字段说明](#系统字段说明)
59
- - [State 状态值](#state-状态值)
60
- - [默认 State 过滤](#默认-state-过滤)
61
- - [完整示例](#完整示例)
62
- - [用户管理 API](#用户管理-api)
63
- - [订单创建(事务)](#订单创建事务)
64
- - [复杂查询](#复杂查询)
65
-
66
- ---
67
-
68
- ## 核心概念
69
-
70
- ### DbHelper
71
-
72
- `DbHelper` 是 Befly 的数据库操作核心类,提供了完整的 CRUD 封装。通过 `befly.db` 访问。
73
-
74
- ```typescript
75
- // 在 API handler 中使用
76
- handler: async (befly, ctx) => {
77
- const user = await befly.db.getOne({
78
- table: "user",
79
- where: { id: 1 }
80
- });
81
- };
82
- ```
83
-
84
- ### 自动转换
85
-
86
- - **表名**:小驼峰 `userProfile` 自动转换为下划线 `user_profile`
87
- - **字段名**:写入时小驼峰转下划线,查询时下划线转小驼峰
88
- - **BIGINT 字段**:`id`、`*Id`、`*_id`、`*At`、`*_at` 自动转为 number
89
-
90
- ### 自动过滤 null 和 undefined
91
-
92
- 所有写入方法(`insData`、`insBatch`、`updData`)和条件查询(`where`)都会**自动过滤值为 `null` 或 `undefined` 的字段**。
93
-
94
- 这意味着:
95
-
96
- - 传入 `undefined` 或 `null` 的字段会被忽略,不会写入数据库
97
- - `where` 条件中值为 `undefined` 或 `null` 的条件会被忽略
98
- - 这使得处理可选参数变得非常简单,无需手动判断
99
-
100
- #### 写入时自动过滤
101
-
102
- ```typescript
103
- // 用户提交的数据可能部分字段为空
104
- await befly.db.insData({
105
- table: "user",
106
- data: {
107
- username: "john",
108
- email: "john@example.com",
109
- phone: undefined, // ❌ 自动忽略,不会写入
110
- avatar: null, // ❌ 自动忽略,不会写入
111
- nickname: "" // ✅ 空字符串会写入(不是 null/undefined)
112
- }
113
- });
114
- // 实际 SQL: INSERT INTO user (username, email, nickname, ...) VALUES ('john', 'john@example.com', '', ...)
115
- ```
116
-
117
- #### 更新时自动过滤
118
-
119
- ```typescript
120
- // 只更新用户提交的字段
121
- await befly.db.updData({
122
- table: "user",
123
- data: {
124
- nickname: ctx.body.nickname, // 如果用户传了值,会更新
125
- avatar: ctx.body.avatar, // 如果为 undefined,自动忽略
126
- bio: ctx.body.bio // 如果为 null,自动忽略
127
- },
128
- where: { id: ctx.user.id }
129
- });
130
- // 只有非 null/undefined 的字段会被更新
131
- ```
132
-
133
- #### Where 条件自动过滤
134
-
135
- ```typescript
136
- // 条件筛选:用户可能只传部分筛选条件
137
- const result = await befly.db.getList({
138
- table: "article",
139
- where: {
140
- categoryId: ctx.body.categoryId, // 如果未传,值为 undefined,自动忽略
141
- status: ctx.body.status, // 如果未传,值为 undefined,自动忽略
142
- authorId: ctx.body.authorId // 如果未传,值为 undefined,自动忽略
143
- },
144
- page: 1,
145
- limit: 10
146
- });
147
- // 只有非 null/undefined 的条件会参与 WHERE 构建
148
- ```
149
-
150
- #### 实际应用示例
151
-
152
- ```typescript
153
- // API: 用户列表(带可选筛选条件)
154
- export default {
155
- name: "用户列表",
156
- fields: {
157
- keyword: { name: "关键词", type: "string", max: 50 },
158
- status: { name: "状态", type: "number" },
159
- state: { name: "状态", type: "number" }
160
- },
161
- handler: async (befly, ctx) => {
162
- // 直接使用请求参数,无需判断是否存在
163
- // null/undefined 的条件会被自动过滤
164
- const result = await befly.db.getList({
165
- table: "user",
166
- where: {
167
- status: ctx.body.status, // 未传时为 undefined,自动忽略
168
- state: ctx.body.state, // 未传时为 undefined,自动忽略
169
- username$like: ctx.body.keyword ? `%${ctx.body.keyword}%` : undefined // 无关键词时忽略
170
- },
171
- page: ctx.body.page || 1,
172
- limit: ctx.body.limit || 10
173
- });
174
-
175
- return befly.tool.Yes("查询成功", result);
176
- }
177
- };
178
- ```
179
-
180
- #### 手动清理字段
181
-
182
- 如需手动清理数据,可以使用 `cleanFields` 方法:
183
-
184
- ```typescript
185
- // 默认排除 null 和 undefined
186
- const cleanData = befly.db.cleanFields({
187
- name: "John",
188
- age: null,
189
- email: undefined,
190
- phone: ""
191
- });
192
- // 结果: { name: 'John', phone: '' }
193
-
194
- // 自定义排除值(如同时排除空字符串)
195
- const cleanData2 = befly.db.cleanFields(
196
- { name: "John", phone: "", age: null },
197
- [null, undefined, ""] // 排除这些值
198
- );
199
- // 结果: { name: 'John' }
200
-
201
- // 保留特定字段的特定值(即使在排除列表中)
202
- const cleanData3 = befly.db.cleanFields(
203
- { name: "John", status: null, count: 0 },
204
- [null, undefined], // 排除 null 和 undefined
205
- { status: null } // 但保留 status 字段的 null 值
206
- );
207
- // 结果: { name: 'John', status: null, count: 0 }
208
- ```
209
-
210
- ---
211
-
212
- ## 字段命名规范
213
-
214
- | 位置 | 格式 | 示例 |
215
- | --------------------- | ------ | ----------------------- |
216
- | 代码中(参数/返回值) | 小驼峰 | `userId`, `createdAt` |
217
- | 数据库中 | 下划线 | `user_id`, `created_at` |
218
-
219
- ```typescript
220
- // 写入时使用小驼峰
221
- await befly.db.insData({
222
- table: "user",
223
- data: {
224
- userName: "John", // → user_name
225
- createdBy: 1 // → created_by
226
- }
227
- });
228
-
229
- // 查询返回小驼峰
230
- const user = await befly.db.getOne({
231
- table: "user",
232
- where: { userId: 1 } // → WHERE user_id = 1
233
- });
234
- // 返回: { userId: 1, userName: 'John', createdBy: 1 }
235
- ```
236
-
237
- ---
238
-
239
- ## 查询方法
240
-
241
- ### getOne - 查询单条
242
-
243
- 查询满足条件的第一条记录。
244
-
245
- ```typescript
246
- interface QueryOptions {
247
- table: string; // 表名
248
- fields?: string[]; // 字段列表(可选)
249
- where?: WhereConditions; // 查询条件(可选)
250
- }
251
- ```
252
-
253
- **示例:**
254
-
255
- ```typescript
256
- // 基础查询
257
- const user = await befly.db.getOne({
258
- table: "user",
259
- where: { id: 1 }
260
- });
261
-
262
- // 指定字段
263
- const user = await befly.db.getOne({
264
- table: "user",
265
- fields: ["id", "username", "email"],
266
- where: { id: 1 }
267
- });
268
-
269
- // 排除字段(使用 ! 前缀)
270
- const user = await befly.db.getOne({
271
- table: "user",
272
- fields: ["!password", "!token"],
273
- where: { id: 1 }
274
- });
275
- ```
276
-
277
- ### getList - 分页查询
278
-
279
- 分页查询,返回列表和分页信息。
280
-
281
- ```typescript
282
- interface QueryOptions {
283
- table: string;
284
- fields?: string[];
285
- where?: WhereConditions;
286
- orderBy?: string[]; // 排序,格式: ["field#ASC", "field#DESC"]
287
- page?: number; // 页码,默认 1
288
- limit?: number; // 每页数量,默认 10
289
- }
290
-
291
- interface ListResult<T> {
292
- list: T[]; // 数据列表
293
- total: number; // 总记录数
294
- page: number; // 当前页码
295
- limit: number; // 每页数量
296
- pages: number; // 总页数
297
- }
298
- ```
299
-
300
- **示例:**
301
-
302
- ```typescript
303
- // 基础分页
304
- const result = await befly.db.getList({
305
- table: "user",
306
- page: 1,
307
- limit: 10
308
- });
309
- // 返回: { list: [...], total: 100, page: 1, limit: 10, pages: 10 }
310
-
311
- // 带条件和排序
312
- const result = await befly.db.getList({
313
- table: "user",
314
- fields: ["id", "username", "createdAt"],
315
- where: { state: 1 },
316
- orderBy: ["createdAt#DESC", "id#ASC"],
317
- page: 2,
318
- limit: 20
319
- });
320
- ```
321
-
322
- ### getAll - 查询全部
323
-
324
- 查询所有满足条件的记录(有上限保护,最多 10000 条)。
325
-
326
- **返回值**:`{ lists: T[], total: number }`
327
-
328
- - `lists`:数据数组(受 MAX_LIMIT 保护,最多 10000 条)
329
- - `total`:真实总记录数(不受 MAX_LIMIT 限制)
330
-
331
- ```typescript
332
- // 查询所有
333
- const result = await befly.db.getAll({
334
- table: "user"
335
- });
336
- // result: { lists: [...], total: 实际总数 }
337
-
338
- // 带条件
339
- const activeResult = await befly.db.getAll({
340
- table: "user",
341
- fields: ["id", "username"],
342
- where: { state: 1 },
343
- orderBy: ["sort#ASC"]
344
- });
345
-
346
- // 访问数据
347
- console.log(activeResult.lists); // 数据数组(最多 10000 条)
348
- console.log(activeResult.total); // 真实总数(如 100000)
349
- ```
350
-
351
- **重要说明**:
352
-
353
- - `total` 是查询满足条件的真实总记录数
354
- - `lists` 受 `MAX_LIMIT = 10000` 保护,最多返回 10000 条
355
- - 如果总数超过 10000,`lists.length < total`
356
- - 建议使用 `getList` 进行分页查询
357
-
358
- > ⚠️ **警告**:此方法可能返回大量数据。超过 1000 条会输出警告日志,达到 10000 条会提示只返回部分数据。
359
-
360
- ### getCount - 查询数量
361
-
362
- 仅查询满足条件的记录数量。
363
-
364
- ```typescript
365
- // 查询总数
366
- const count = await befly.db.getCount({
367
- table: "user"
368
- });
369
-
370
- // 条件计数
371
- const activeCount = await befly.db.getCount({
372
- table: "user",
373
- where: { state: 1 }
374
- });
375
- ```
376
-
377
- ### exists - 检查存在
378
-
379
- 检查是否存在满足条件的记录(性能优化版)。
380
-
381
- ```typescript
382
- const hasAdmin = await befly.db.exists({
383
- table: "user",
384
- where: { roleCode: "admin" }
385
- });
386
-
387
- if (hasAdmin) {
388
- // 存在管理员
389
- }
390
- ```
391
-
392
- ### getFieldValue - 查询单字段
393
-
394
- 查询单条记录的单个字段值。
395
-
396
- ```typescript
397
- // 查询用户名
398
- const username = await befly.db.getFieldValue({
399
- table: "user",
400
- field: "username",
401
- where: { id: 1 }
402
- });
403
-
404
- // 查询余额
405
- const balance = await befly.db.getFieldValue<number>({
406
- table: "account",
407
- field: "balance",
408
- where: { userId: 1 }
409
- });
410
- ```
411
-
412
- ---
413
-
414
- ## 写入方法
415
-
416
- ### insData - 插入数据
417
-
418
- 插入单条数据,自动生成系统字段。
419
-
420
- ```typescript
421
- interface InsertOptions {
422
- table: string;
423
- data: Record<string, any>;
424
- }
425
- ```
426
-
427
- **自动生成的字段:**
428
-
429
- | 字段 | 说明 |
430
- | ------------ | ----------------- |
431
- | `id` | 基于时间的唯一 ID |
432
- | `created_at` | 创建时间戳 |
433
- | `updated_at` | 更新时间戳 |
434
- | `state` | 状态,默认 1 |
435
-
436
- **示例:**
437
-
438
- ```typescript
439
- // 插入用户
440
- const userId = await befly.db.insData({
441
- table: "user",
442
- data: {
443
- username: "john",
444
- email: "john@example.com",
445
- password: hashedPassword,
446
- categoryId: 1
447
- }
448
- });
449
- // 返回新记录的 ID
450
-
451
- // 系统字段不可覆盖
452
- await befly.db.insData({
453
- table: "user",
454
- data: {
455
- id: 999, // ❌ 会被忽略,自动生成
456
- createdAt: 0, // ❌ 会被忽略,自动生成
457
- state: 2, // ❌ 会被忽略,强制设为 1
458
- username: "john" // ✅ 正常写入
459
- }
460
- });
461
- ```
462
-
463
- ### insBatch - 批量插入
464
-
465
- 批量插入多条数据(最多 1000 条)。
466
-
467
- ```typescript
468
- // 批量插入
469
- const ids = await befly.db.insBatch("user", [
470
- { username: "user1", email: "user1@example.com" },
471
- { username: "user2", email: "user2@example.com" },
472
- { username: "user3", email: "user3@example.com" }
473
- ]);
474
- // 返回: [id1, id2, id3]
475
- ```
476
-
477
- ### updData - 更新数据
478
-
479
- 更新满足条件的记录。
480
-
481
- ```typescript
482
- interface UpdateOptions {
483
- table: string;
484
- data: Record<string, any>;
485
- where: WhereConditions;
486
- }
487
- ```
488
-
489
- **示例:**
490
-
491
- ```typescript
492
- // 更新用户
493
- const affected = await befly.db.updData({
494
- table: "user",
495
- data: {
496
- nickname: "新昵称",
497
- email: "new@example.com"
498
- },
499
- where: { id: 1 }
500
- });
501
- // 返回受影响的行数
502
-
503
- // 批量更新
504
- await befly.db.updData({
505
- table: "user",
506
- data: { state: 2 },
507
- where: { status: 5 }
508
- });
509
- ```
510
-
511
- **注意事项:**
512
-
513
- - `updated_at` 自动更新为当前时间
514
- - `id`、`created_at`、`deleted_at` 不可修改
515
- - `state` 允许修改(用于禁用/启用)
516
-
517
- ### delData - 软删除
518
-
519
- 软删除记录(设置 `state=0` 和 `deleted_at`)。
520
-
521
- ```typescript
522
- // 软删除
523
- const affected = await befly.db.delData({
524
- table: "user",
525
- where: { id: 1 }
526
- });
527
- ```
528
-
529
- ### delForce - 硬删除
530
-
531
- 物理删除记录(不可恢复)。
532
-
533
- ```typescript
534
- // 硬删除
535
- const affected = await befly.db.delForce({
536
- table: "temp_data",
537
- where: { expiredAt$lt: Date.now() }
538
- });
539
- ```
540
-
541
- > ⚠️ **警告**:硬删除不可恢复,请谨慎使用。
542
-
543
- ### disableData - 禁用
544
-
545
- 禁用记录(设置 `state=2`)。
546
-
547
- ```typescript
548
- // 禁用用户
549
- await befly.db.disableData({
550
- table: "user",
551
- where: { id: 1 }
552
- });
553
- ```
554
-
555
- ### enableData - 启用
556
-
557
- 启用记录(设置 `state=1`)。
558
-
559
- ```typescript
560
- // 启用用户
561
- await befly.db.enableData({
562
- table: "user",
563
- where: { id: 1 }
564
- });
565
- ```
566
-
567
- ---
568
-
569
- ## 数值操作
570
-
571
- ### increment - 自增
572
-
573
- 对数值字段进行自增操作。
574
-
575
- ```typescript
576
- // 阅读数 +1
577
- await befly.db.increment("article", "viewCount", { id: 1 });
578
-
579
- // 阅读数 +10
580
- await befly.db.increment("article", "viewCount", { id: 1 }, 10);
581
- ```
582
-
583
- ### decrement - 自减
584
-
585
- 对数值字段进行自减操作。
586
-
587
- ```typescript
588
- // 库存 -1
589
- await befly.db.decrement("product", "stock", { id: 1 });
590
-
591
- // 余额 -100
592
- await befly.db.decrement("account", "balance", { userId: 1 }, 100);
593
- ```
594
-
595
- ---
596
-
597
- ## 事务操作
598
-
599
- 使用 `trans` 方法执行事务,自动处理 commit/rollback。
600
-
601
- ```typescript
602
- // 转账示例
603
- const result = await befly.db.trans(async (tx) => {
604
- // 扣除转出方余额
605
- await tx.decrement("account", "balance", { userId: 1 }, 100);
606
-
607
- // 增加转入方余额
608
- await tx.increment("account", "balance", { userId: 2 }, 100);
609
-
610
- // 记录转账日志
611
- await tx.insData({
612
- table: "transfer_log",
613
- data: {
614
- fromUserId: 1,
615
- toUserId: 2,
616
- amount: 100
617
- }
618
- });
619
-
620
- return { success: true };
621
- });
622
-
623
- // 事务中抛出异常会自动回滚
624
- await befly.db.trans(async (tx) => {
625
- await tx.updData({ table: "user", data: { balance: 0 }, where: { id: 1 } });
626
-
627
- throw new Error("业务校验失败"); // 自动回滚
628
- });
629
- ```
630
-
631
- ---
632
-
633
- ## 多表联查
634
-
635
- DbHelper 的查询方法(getOne、getList、getAll、getCount)支持通过 `joins` 参数进行多表联查。
636
-
637
- ### 简洁写法(推荐)
638
-
639
- 直接在查询方法中使用 `joins` 参数,无需手动构建 SQL。
640
-
641
- ```typescript
642
- // 单条联查
643
- const order = await befly.db.getOne({
644
- table: "order",
645
- joins: [{ table: "user", on: "order.user_id = user.id" }],
646
- fields: ["order.id", "order.totalAmount", "order.status", "user.username", "user.nickname"],
647
- where: { "order.id": orderId }
648
- });
649
- // 返回: { id: 1, totalAmount: 100, status: 'paid', username: 'john', nickname: '张三' }
650
-
651
- // 分页联查
652
- const result = await befly.db.getList({
653
- table: "order",
654
- joins: [
655
- { table: "user", on: "order.userId = user.id" },
656
- { table: "product", on: "order.productId = product.id" }
657
- ],
658
- fields: ["order.id", "order.totalAmount", "user.username", "product.name AS productName"],
659
- where: { "order.status": "paid" },
660
- orderBy: ["order.createdAt#DESC"],
661
- page: 1,
662
- limit: 10
663
- });
664
- // 返回: { list: [...], total: 100, page: 1, limit: 10, pages: 10 }
665
-
666
- // 联查计数
667
- const count = await befly.db.getCount({
668
- table: "order",
669
- joins: [{ table: "user", on: "order.userId = user.id" }],
670
- where: { "order.state": 1, "user.state": 1 }
671
- });
672
-
673
- // 联查全部
674
- const allOrders = await befly.db.getAll({
675
- table: "order",
676
- joins: [{ table: "user", on: "order.userId = user.id" }],
677
- fields: ["order.id", "user.username"],
678
- where: { "order.state": 1 },
679
- orderBy: ["order.id#DESC"]
680
- });
681
- // 返回: { lists: [...最多10000条], total: 真实总数 }
682
- ```
683
-
684
- ### JoinOption 参数说明
685
-
686
- ```typescript
687
- interface JoinOption {
688
- /** JOIN 类型:'left' | 'right' | 'inner',默认 'left' */
689
- type?: "left" | "right" | "inner";
690
- /** 表名(不支持别名) */
691
- table: string;
692
- /** JOIN 条件(如 'order.user_id = user.id') */
693
- on: string;
694
- }
695
- ```
696
-
697
- **示例:**
698
-
699
- ```typescript
700
- joins: [
701
- { table: "user", on: "order.userId = user.id" }, // LEFT JOIN(默认)
702
- { type: "left", table: "product", on: "order.productId = product.id" }, // LEFT JOIN
703
- { type: "inner", table: "category", on: "product.categoryId = category.id" }, // INNER JOIN
704
- { type: "right", table: "warehouse", on: "product.warehouseId = warehouse.id" } // RIGHT JOIN
705
- ];
706
- ```
707
-
708
- ### 联查注意事项
709
-
710
- 1. **使用完整表名**:字段需要带完整表名(如 `order.id`, `user.username`)
711
- 2. **字段自动转换**:`order.userId` 会自动转换为 `order.user_id`
712
- 3. **结果自动转换**:返回结果的字段名会自动转为小驼峰
713
- 4. **State 过滤**:默认只添加主表的 `state > 0`,联表需在 where 中显式指定
714
-
715
- ### 完整示例:订单列表 API
716
-
717
- ```typescript
718
- export default {
719
- name: "订单列表",
720
- fields: {
721
- keyword: { name: "关键词", type: "string", max: 50 },
722
- status: { name: "状态", type: "string" },
723
- page: Fields.page,
724
- limit: Fields.limit
725
- },
726
- handler: async (befly, ctx) => {
727
- const where: any = {
728
- "order.state": 1,
729
- "user.state": 1
730
- };
731
-
732
- // 关键词搜索
733
- if (ctx.body.keyword) {
734
- where.$or = [{ "user.username$like": `%${ctx.body.keyword}%` }, { "user.nickname$like": `%${ctx.body.keyword}%` }, { "product.name$like": `%${ctx.body.keyword}%` }];
735
- }
736
-
737
- // 状态过滤
738
- if (ctx.body.status) {
739
- where["order.status"] = ctx.body.status;
740
- }
741
-
742
- const result = await befly.db.getList({
743
- table: "order",
744
- joins: [
745
- { table: "user", on: "order.userId = user.id" },
746
- { table: "product", on: "order.productId = product.id" }
747
- ],
748
- fields: ["order.id", "order.quantity", "order.totalAmount", "order.status", "order.createdAt", "user.username", "user.nickname", "product.name AS productName", "product.price AS productPrice"],
749
- where: where,
750
- orderBy: ["order.createdAt#DESC"],
751
- page: ctx.body.page,
752
- limit: ctx.body.limit
753
- });
754
-
755
- return befly.tool.Yes("查询成功", result);
756
- }
757
- };
758
- ```
759
-
760
- ---
761
-
762
- ### 使用 query 原始 SQL
763
-
764
- 对于更复杂的场景(如 GROUP BY、子查询等),可以使用 `befly.db.query()` 执行原始 SQL。
765
-
766
- ```typescript
767
- // 带统计的联查:用户及其订单数量
768
- const usersWithOrderCount = await befly.db.query(
769
- `SELECT
770
- u.id, u.username, u.nickname,
771
- COUNT(o.id) AS orderCount,
772
- COALESCE(SUM(o.total_amount), 0) AS totalSpent
773
- FROM user u
774
- LEFT JOIN \`order\` o ON u.id = o.user_id AND o.state > 0
775
- WHERE u.state > 0
776
- GROUP BY u.id
777
- ORDER BY totalSpent DESC
778
- LIMIT ?`,
779
- [20]
780
- );
781
-
782
- // 需要手动转换字段名
783
- import { arrayKeysToCamel } from "befly/lib/arrayKeysToCamel";
784
- const list = arrayKeysToCamel(usersWithOrderCount);
785
- ```
786
-
787
- ---
788
-
789
- ## Where 条件语法
790
-
791
- ### 基础条件
792
-
793
- ```typescript
794
- // 等于
795
- where: { id: 1 }
796
- // → WHERE id = 1
797
-
798
- // 多条件(AND)
799
- where: { state: 1, status: 2 }
800
- // → WHERE state = 1 AND status = 2
801
- ```
802
-
803
- ### 比较操作符
804
-
805
- | 操作符 | 说明 | SQL |
806
- | -------------- | -------- | ---- |
807
- | `$ne` / `$not` | 不等于 | `!=` |
808
- | `$gt` | 大于 | `>` |
809
- | `$gte` | 大于等于 | `>=` |
810
- | `$lt` | 小于 | `<` |
811
- | `$lte` | 小于等于 | `<=` |
812
-
813
- **两种写法:**
814
-
815
- ```typescript
816
- // 写法1:一级属性格式(推荐)
817
- where: {
818
- age$gt: 18,
819
- age$lte: 60
820
- }
821
- // → WHERE age > 18 AND age <= 60
822
-
823
- // 写法2:嵌套对象格式
824
- where: {
825
- age: { $gt: 18, $lte: 60 }
826
- }
827
- // → WHERE age > 18 AND age <= 60
828
- ```
829
-
830
- ### 逻辑操作符
831
-
832
- **$or - 或条件**
833
-
834
- ```typescript
835
- // 用户名或邮箱匹配
836
- where: {
837
- $or: [{ username: "admin" }, { email: "admin@example.com" }];
838
- }
839
- // → WHERE (username = 'admin' OR email = 'admin@example.com')
840
- ```
841
-
842
- **$and - 与条件**
843
-
844
- ```typescript
845
- // 显式 AND(通常不需要,多条件默认就是 AND)
846
- where: {
847
- $and: [{ state: 1 }, { status: 2 }];
848
- }
849
- // → WHERE state = 1 AND status = 2
850
- ```
851
-
852
- **组合使用**
853
-
854
- ```typescript
855
- // 复杂条件
856
- where: {
857
- state: 1,
858
- $or: [
859
- { roleCode: 'admin' },
860
- { roleCode: 'super' }
861
- ]
862
- }
863
- // → WHERE state = 1 AND (role_code = 'admin' OR role_code = 'super')
864
- ```
865
-
866
- ### 范围操作符
867
-
868
- **$in - 在列表中**
869
-
870
- ```typescript
871
- where: {
872
- categoryId$in: [1, 2, 3];
873
- }
874
- // → WHERE category_id IN (1, 2, 3)
875
-
876
- where: {
877
- status$in: ["pending", "processing"];
878
- }
879
- // → WHERE status IN ('pending', 'processing')
880
- ```
881
-
882
- **$nin / $notIn - 不在列表中**
883
-
884
- ```typescript
885
- where: {
886
- state$nin: [0, 2];
887
- }
888
- // → WHERE state NOT IN (0, 2)
889
- ```
890
-
891
- **$between - 区间**
892
-
893
- ```typescript
894
- where: {
895
- age$between: [18, 60];
896
- }
897
- // → WHERE age BETWEEN 18 AND 60
898
-
899
- where: {
900
- createdAt$between: [startTime, endTime];
901
- }
902
- // → WHERE created_at BETWEEN {startTime} AND {endTime}
903
- ```
904
-
905
- **$notBetween - 不在区间**
906
-
907
- ```typescript
908
- where: {
909
- price$notBetween: [100, 500];
910
- }
911
- // → WHERE price NOT BETWEEN 100 AND 500
912
- ```
913
-
914
- ### 空值操作符
915
-
916
- **$null - 为空**
917
-
918
- ```typescript
919
- where: {
920
- deletedAt$null: true;
921
- }
922
- // → WHERE deleted_at IS NULL
923
- ```
924
-
925
- **$notNull - 不为空**
926
-
927
- ```typescript
928
- where: {
929
- email$notNull: true;
930
- }
931
- // → WHERE email IS NOT NULL
932
- ```
933
-
934
- ### 模糊匹配
935
-
936
- **$like - 模糊匹配**
937
-
938
- ```typescript
939
- // 包含
940
- where: {
941
- username$like: "%admin%";
942
- }
943
- // → WHERE username LIKE '%admin%'
944
-
945
- // 以...开头
946
- where: {
947
- email$like: "test%";
948
- }
949
- // → WHERE email LIKE 'test%'
950
-
951
- // 以...结尾
952
- where: {
953
- phone$like: "%1234";
954
- }
955
- // → WHERE phone LIKE '%1234'
956
- ```
957
-
958
- **$notLike - 不匹配**
959
-
960
- ```typescript
961
- where: {
962
- username$notLike: "%test%";
963
- }
964
- // → WHERE username NOT LIKE '%test%'
965
- ```
966
-
967
- ---
968
-
969
- ## 字段选择语法
970
-
971
- ### 查询所有字段
972
-
973
- ```typescript
974
- // 以下三种方式等效
975
- fields: [];
976
- fields: undefined;
977
- // 不传 fields 参数
978
- ```
979
-
980
- ### 指定字段
981
-
982
- ```typescript
983
- fields: ["id", "username", "email"];
984
- // → SELECT id, username, email
985
- ```
986
-
987
- ### 排除字段
988
-
989
- 使用 `!` 前缀排除字段(查询除指定字段外的所有字段)。
990
-
991
- ```typescript
992
- fields: ["!password", "!token", "!salt"];
993
- // → SELECT id, username, email, created_at, ... (除了 password, token, salt)
994
- ```
995
-
996
- > ⚠️ **注意**:不能混用指定字段和排除字段。
997
-
998
- ---
999
-
1000
- ## 排序语法
1001
-
1002
- 使用 `字段#方向` 格式,方向为 `ASC`(升序)或 `DESC`(降序)。
1003
-
1004
- ```typescript
1005
- // 单字段排序
1006
- orderBy: ["createdAt#DESC"];
1007
- // → ORDER BY created_at DESC
1008
-
1009
- // 多字段排序
1010
- orderBy: ["sort#ASC", "id#DESC"];
1011
- // → ORDER BY sort ASC, id DESC
1012
- ```
1013
-
1014
- ---
1015
-
1016
- ## 系统字段说明
1017
-
1018
- 每条记录自动包含以下系统字段:
1019
-
1020
- | 字段 | 类型 | 说明 | 插入时 | 更新时 |
1021
- | ------------ | ------- | ------------------ | ---------- | ------------ |
1022
- | `id` | BIGINT | 主键,基于时间生成 | 自动生成 | 不可修改 |
1023
- | `created_at` | BIGINT | 创建时间戳 | 自动生成 | 不可修改 |
1024
- | `updated_at` | BIGINT | 更新时间戳 | 自动生成 | 自动更新 |
1025
- | `deleted_at` | BIGINT | 删除时间戳 | 不生成 | 软删除时设置 |
1026
- | `state` | TINYINT | 状态 | 强制设为 1 | 可修改 |
1027
-
1028
- ### State 状态值
1029
-
1030
- | 值 | 含义 | 说明 |
1031
- | --- | ------ | -------------- |
1032
- | 0 | 已删除 | 软删除后的状态 |
1033
- | 1 | 正常 | 默认状态 |
1034
- | 2 | 禁用 | 禁用状态 |
1035
-
1036
- ### 默认 State 过滤
1037
-
1038
- 所有查询方法默认添加 `state > 0` 条件,**仅过滤软删除的数据(state=0)**。
1039
-
1040
- **过滤效果**:
1041
-
1042
- | state 值 | 默认查询结果 |
1043
- | -------- | ------------------- |
1044
- | 0 | ❌ 被过滤(软删除) |
1045
- | 1 | ✅ 可查询(正常) |
1046
- | 2 | ✅ 可查询(禁用) |
1047
-
1048
- > ⚠️ **注意**:禁用数据(state=2)默认**可以**查询到,如需过滤禁用数据,需显式指定 `state: 1`。
1049
-
1050
- ```typescript
1051
- // 默认查询:state > 0,包含正常和禁用数据
1052
- getOne({ table: "user", where: { id: 1 } });
1053
- // → WHERE id = 1 AND state > 0
1054
-
1055
- // 只查询正常状态的数据
1056
- getList({ table: "user", where: { state: 1 } });
1057
- // → WHERE state = 1
1058
-
1059
- // 只查询禁用状态的数据
1060
- getList({ table: "user", where: { state: 2 } });
1061
- // → WHERE state = 2
1062
-
1063
- // 查询所有状态(包括软删除)
1064
- getOne({ table: "user", where: { id: 1, state$gte: 0 } });
1065
- // → WHERE id = 1 AND state >= 0
1066
- ```
1067
-
1068
- ---
1069
-
1070
- ## 完整示例
1071
-
1072
- ### 用户管理 API
1073
-
1074
- ```typescript
1075
- // 用户列表
1076
- export default {
1077
- name: "用户列表",
1078
- fields: {
1079
- keyword: { name: "关键词", type: "string", max: 50 },
1080
- departmentId: { name: "部门ID", type: "number" },
1081
- page: Fields.page,
1082
- limit: Fields.limit
1083
- },
1084
- handler: async (befly, ctx) => {
1085
- const where: any = {};
1086
-
1087
- // 关键词搜索
1088
- if (ctx.body.keyword) {
1089
- where.$or = [{ username$like: `%${ctx.body.keyword}%` }, { nickname$like: `%${ctx.body.keyword}%` }, { email$like: `%${ctx.body.keyword}%` }];
1090
- }
1091
-
1092
- // 部门过滤
1093
- if (ctx.body.departmentId) {
1094
- where.departmentId = ctx.body.departmentId;
1095
- }
1096
-
1097
- const result = await befly.db.getList({
1098
- table: "user",
1099
- fields: ["!password", "!token"],
1100
- where: where,
1101
- orderBy: ["createdAt#DESC"],
1102
- page: ctx.body.page,
1103
- limit: ctx.body.limit
1104
- });
1105
-
1106
- return befly.tool.Yes("查询成功", result);
1107
- }
1108
- };
1109
- ```
1110
-
1111
- ### 订单创建(事务)
1112
-
1113
- ```typescript
1114
- export default {
1115
- name: "创建订单",
1116
- fields: {
1117
- productId: { name: "商品ID", type: "number" },
1118
- quantity: { name: "数量", type: "number", min: 1 }
1119
- },
1120
- required: ["productId", "quantity"],
1121
- handler: async (befly, ctx) => {
1122
- const result = await befly.db.trans(async (tx) => {
1123
- // 1. 查询商品
1124
- const product = await tx.getOne({
1125
- table: "product",
1126
- where: { id: ctx.body.productId }
1127
- });
1128
-
1129
- if (!product) {
1130
- throw new Error("商品不存在");
1131
- }
1132
-
1133
- if (product.stock < ctx.body.quantity) {
1134
- throw new Error("库存不足");
1135
- }
1136
-
1137
- // 2. 扣减库存
1138
- await tx.decrement("product", "stock", { id: ctx.body.productId }, ctx.body.quantity);
1139
-
1140
- // 3. 创建订单
1141
- const orderId = await tx.insData({
1142
- table: "order",
1143
- data: {
1144
- userId: ctx.user.id,
1145
- productId: ctx.body.productId,
1146
- quantity: ctx.body.quantity,
1147
- totalAmount: product.price * ctx.body.quantity,
1148
- status: "pending"
1149
- }
1150
- });
1151
-
1152
- return { orderId: orderId };
1153
- });
1154
-
1155
- return befly.tool.Yes("订单创建成功", result);
1156
- }
1157
- };
1158
- ```
1159
-
1160
- ### 复杂查询
1161
-
1162
- ```typescript
1163
- // 查询最近7天内,状态为正常或待审核的文章
1164
- const articles = await befly.db.getList({
1165
- table: "article",
1166
- fields: ["id", "title", "authorId", "viewCount", "createdAt"],
1167
- where: {
1168
- createdAt$gte: Date.now() - 7 * 24 * 60 * 60 * 1000,
1169
- $or: [{ status: "published" }, { status: "pending" }],
1170
- categoryId$in: [1, 2, 3]
1171
- },
1172
- orderBy: ["viewCount#DESC", "createdAt#DESC"],
1173
- page: 1,
1174
- limit: 20
1175
- });
1176
- ```