befly 3.9.23 → 3.9.27

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.md CHANGED
@@ -1430,7 +1430,7 @@ export default {
1430
1430
  }
1431
1431
 
1432
1432
  // 查询订单明细
1433
- const items = await befly.db.getAll({
1433
+ const itemsResult = await befly.db.getAll({
1434
1434
  table: 'order_item',
1435
1435
  where: { orderId: order.id }
1436
1436
  });
@@ -1438,6 +1438,14 @@ export default {
1438
1438
  // 查询用户信息
1439
1439
  const user = await befly.db.getOne({
1440
1440
  table: 'user',
1441
+ where: { id: order.userId }
1442
+ });
1443
+
1444
+ return befly.tool.Yes('查询成功', {
1445
+ order: order,
1446
+ items: itemsResult.lists, // 订单明细列表
1447
+ user: user
1448
+ });
1441
1449
  where: { id: order.userId },
1442
1450
  columns: ['id', 'username', 'nickname', 'phone']
1443
1451
  });
@@ -1494,11 +1502,13 @@ export default {
1494
1502
 
1495
1503
  if (!config) {
1496
1504
  // 缓存不存在,从数据库查询
1497
- config = await befly.db.getAll({
1505
+ const result = await befly.db.getAll({
1498
1506
  table: 'sys_config',
1499
1507
  where: { state: 1 }
1500
1508
  });
1501
1509
 
1510
+ config = result.lists; // 获取配置列表
1511
+
1502
1512
  // 写入缓存
1503
1513
  await befly.redis.set(cacheKey, JSON.stringify(config), 'EX', 300);
1504
1514
  } else {
@@ -1546,8 +1556,8 @@ export default {
1546
1556
  export default {
1547
1557
  name: '导出用户数据',
1548
1558
  handler: async (befly, ctx) => {
1549
- // 查询所有用户(不分页)
1550
- const users = await befly.db.getAll({
1559
+ // 查询所有用户(不分页,注意上限 10000 条)
1560
+ const result = await befly.db.getAll({
1551
1561
  table: 'user',
1552
1562
  columns: ['id', 'username', 'nickname', 'email', 'phone', 'createdAt'],
1553
1563
  where: { state: 1 },
@@ -1556,11 +1566,11 @@ export default {
1556
1566
 
1557
1567
  // 转换为 CSV 格式
1558
1568
  const headers = ['ID', '用户名', '昵称', '邮箱', '手机', '注册时间'];
1559
- const rows = users.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
1569
+ const rows = result.lists.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
1560
1570
 
1561
1571
  const csv = [headers.join(','), ...rows.map((r: any[]) => r.join(','))].join('\n');
1562
1572
 
1563
- // 返回 CSV 文件
1573
+ // 返回 CSV 文件(注意:如果 total > 10000,只会导出前 10000 条)
1564
1574
  return new Response(csv, {
1565
1575
  headers: {
1566
1576
  'Content-Type': 'text/csv; charset=utf-8',
package/docs/database.md CHANGED
@@ -321,22 +321,39 @@ const result = await befly.db.getList({
321
321
 
322
322
  查询所有满足条件的记录(有上限保护,最多 10000 条)。
323
323
 
324
+ **返回值**:`{ lists: T[], total: number }`
325
+
326
+ - `lists`:数据数组(受 MAX_LIMIT 保护,最多 10000 条)
327
+ - `total`:真实总记录数(不受 MAX_LIMIT 限制)
328
+
324
329
  ```typescript
325
330
  // 查询所有
326
- const users = await befly.db.getAll({
331
+ const result = await befly.db.getAll({
327
332
  table: 'user'
328
333
  });
334
+ // result: { lists: [...], total: 实际总数 }
329
335
 
330
336
  // 带条件
331
- const activeUsers = await befly.db.getAll({
337
+ const activeResult = await befly.db.getAll({
332
338
  table: 'user',
333
339
  fields: ['id', 'username'],
334
340
  where: { state: 1 },
335
341
  orderBy: ['sort#ASC']
336
342
  });
343
+
344
+ // 访问数据
345
+ console.log(activeResult.lists); // 数据数组(最多 10000 条)
346
+ console.log(activeResult.total); // 真实总数(如 100000)
337
347
  ```
338
348
 
339
- > ⚠️ **警告**:此方法可能返回大量数据,建议使用 `getList` 分页查询。超过 1000 条会输出警告日志。
349
+ **重要说明**:
350
+
351
+ - `total` 是查询满足条件的真实总记录数
352
+ - `lists` 受 `MAX_LIMIT = 10000` 保护,最多返回 10000 条
353
+ - 如果总数超过 10000,`lists.length < total`
354
+ - 建议使用 `getList` 进行分页查询
355
+
356
+ > ⚠️ **警告**:此方法可能返回大量数据。超过 1000 条会输出警告日志,达到 10000 条会提示只返回部分数据。
340
357
 
341
358
  ### getCount - 查询数量
342
359
 
@@ -659,6 +676,7 @@ const allOrders = await befly.db.getAll({
659
676
  where: { 'order.state': 1 },
660
677
  orderBy: ['order.id#DESC']
661
678
  });
679
+ // 返回: { lists: [...最多10000条], total: 真实总数 }
662
680
  ```
663
681
 
664
682
  ### JoinOption 参数说明
package/docs/redis.md CHANGED
@@ -438,13 +438,14 @@ let articles = await befly.redis.getObject(cacheKey);
438
438
 
439
439
  if (!articles) {
440
440
  // 缓存未命中,查询数据库
441
- articles = await befly.db.getAll({
441
+ const result = await befly.db.getAll({
442
442
  table: 'article',
443
443
  fields: ['id', 'title', 'viewCount'],
444
- orderBy: ['viewCount#DESC'],
445
- limit: 10
444
+ orderBy: ['viewCount#DESC']
446
445
  });
447
446
 
447
+ articles = result.lists; // 获取数据列表(最多 10000 条)
448
+
448
449
  // 写入缓存,5分钟过期
449
450
  await befly.redis.setObject(cacheKey, articles, 300);
450
451
  }
@@ -42,7 +42,7 @@ export class CacheHelper {
42
42
  });
43
43
 
44
44
  // 缓存到 Redis
45
- const result = await this.befly.redis.setObject(RedisKeys.apisAll(), apiList);
45
+ const result = await this.befly.redis.setObject(RedisKeys.apisAll(), apiList.lists);
46
46
 
47
47
  if (result === null) {
48
48
  Logger.warn('⚠️ 接口缓存失败');
@@ -72,7 +72,7 @@ export class CacheHelper {
72
72
  });
73
73
 
74
74
  // 缓存到 Redis
75
- const result = await this.befly.redis.setObject(RedisKeys.menusAll(), menus);
75
+ const result = await this.befly.redis.setObject(RedisKeys.menusAll(), menus.lists);
76
76
 
77
77
  if (result === null) {
78
78
  Logger.warn('⚠️ 菜单缓存失败');
@@ -111,14 +111,14 @@ export class CacheHelper {
111
111
 
112
112
  // 构建接口 ID -> 路径的映射(避免重复过滤)
113
113
  const apiMap = new Map<number, string>();
114
- for (const api of allApis) {
114
+ for (const api of allApis.lists) {
115
115
  apiMap.set(api.id, `${api.method}${api.path}`);
116
116
  }
117
117
 
118
118
  // 收集需要缓存的角色权限
119
119
  const cacheOperations: Array<{ roleCode: string; apiPaths: string[] }> = [];
120
120
 
121
- for (const role of roles) {
121
+ for (const role of roles.lists) {
122
122
  if (!role.apis) continue;
123
123
 
124
124
  // 解析角色的接口 ID 列表并映射到路径
package/lib/dbHelper.ts CHANGED
@@ -13,7 +13,7 @@ import { RedisTTL, RedisKeys } from 'befly-shared/redisKeys';
13
13
  import { Logger } from './logger.js';
14
14
  import type { WhereConditions, JoinOption } from '../types/common.js';
15
15
  import type { BeflyContext } from '../types/befly.js';
16
- import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback } from '../types/database.js';
16
+ import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, AllResult, TransactionCallback } from '../types/database.js';
17
17
 
18
18
  /**
19
19
  * 数据库助手类
@@ -691,40 +691,66 @@ export class DbHelper {
691
691
  * where: { 'o.state': 1 }
692
692
  * })
693
693
  */
694
- async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<T[]> {
694
+ async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<AllResult<T>> {
695
695
  // 添加硬性上限保护,防止内存溢出
696
696
  const MAX_LIMIT = 10000;
697
697
  const WARNING_LIMIT = 1000;
698
698
 
699
699
  const prepared = await this.prepareQueryOptions({ ...options, page: 1, limit: 10 });
700
700
 
701
- const builder = new SqlBuilder().select(prepared.fields).from(prepared.table).where(this.addDefaultStateFilter(prepared.where)).limit(MAX_LIMIT);
701
+ const whereFiltered = this.addDefaultStateFilter(prepared.where);
702
+
703
+ // 查询真实总数
704
+ const countBuilder = new SqlBuilder().select(['COUNT(*) as total']).from(prepared.table).where(whereFiltered);
705
+
706
+ // 添加 JOIN(计数也需要)
707
+ this.applyJoins(countBuilder, prepared.joins);
708
+
709
+ const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
710
+ const countResult = await this.executeWithConn(countSql, countParams);
711
+ const total = countResult?.[0]?.total || 0;
712
+
713
+ // 如果总数为 0,直接返回
714
+ if (total === 0) {
715
+ return {
716
+ lists: [],
717
+ total: 0
718
+ };
719
+ }
720
+
721
+ // 查询数据(受上限保护)
722
+ const dataBuilder = new SqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(MAX_LIMIT);
702
723
 
703
724
  // 添加 JOIN
704
- this.applyJoins(builder, prepared.joins);
725
+ this.applyJoins(dataBuilder, prepared.joins);
705
726
 
706
727
  if (prepared.orderBy && prepared.orderBy.length > 0) {
707
- builder.orderBy(prepared.orderBy);
728
+ dataBuilder.orderBy(prepared.orderBy);
708
729
  }
709
730
 
710
- const { sql, params } = builder.toSelectSql();
711
- const result = (await this.executeWithConn(sql, params)) || [];
731
+ const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
732
+ const result = (await this.executeWithConn(dataSql, dataParams)) || [];
712
733
 
713
734
  // 警告日志:返回数据超过警告阈值
714
735
  if (result.length >= WARNING_LIMIT) {
715
- Logger.warn({ table: options.table, count: result.length }, 'getAll 返回数据过多,建议使用 getList 分页查询');
736
+ Logger.warn({ table: options.table, count: result.length, total: total }, 'getAll 返回数据过多,建议使用 getList 分页查询');
716
737
  }
717
738
 
718
739
  // 如果达到上限,额外警告
719
740
  if (result.length >= MAX_LIMIT) {
720
- Logger.warn({ table: options.table, limit: MAX_LIMIT }, 'getAll 达到最大限制,可能还有更多数据');
741
+ Logger.warn({ table: options.table, limit: MAX_LIMIT, total: total }, `getAll 达到最大限制 ${MAX_LIMIT},实际总数 ${total},只返回前 ${MAX_LIMIT} 条`);
721
742
  }
722
743
 
723
744
  // 字段名转换:下划线 → 小驼峰
724
745
  const camelResult = arrayKeysToCamel<T>(result);
725
746
 
726
747
  // 转换 BIGINT 字段(id, pid 等)为数字类型
727
- return this.convertBigIntFields<T>(camelResult);
748
+ const lists = this.convertBigIntFields<T>(camelResult);
749
+
750
+ return {
751
+ lists: lists,
752
+ total: total
753
+ };
728
754
  }
729
755
 
730
756
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.9.23",
3
+ "version": "3.9.27",
4
4
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
5
5
  "type": "module",
6
6
  "private": false,
@@ -74,7 +74,7 @@
74
74
  "pino": "^10.1.0",
75
75
  "pino-roll": "^4.0.0"
76
76
  },
77
- "gitHead": "9b5f1999ae83f1f0842f763845ec9d3e80e01f19",
77
+ "gitHead": "1f449cb3921fc8536f7392f039a6655b1b07b5e5",
78
78
  "devDependencies": {
79
79
  "typescript": "^5.9.3"
80
80
  }
package/sync/syncApi.ts CHANGED
@@ -199,11 +199,11 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<void> {
199
199
  async function deleteObsoleteRecords(helper: any, apiPaths: Set<string>): Promise<void> {
200
200
  const allRecords = await helper.getAll({
201
201
  table: 'addon_admin_api',
202
- fields: ['id', 'path'],
202
+ fields: ['id', 'addonName', 'path', 'method'],
203
203
  where: { state$gte: 0 }
204
204
  });
205
205
 
206
- for (const record of allRecords) {
206
+ for (const record of allRecords.lists) {
207
207
  if (record.path && !apiPaths.has(record.path)) {
208
208
  await helper.delForce({
209
209
  table: 'addon_admin_api',
@@ -255,7 +255,7 @@ export async function syncApiCommand(options: SyncApiOptions = {}): Promise<void
255
255
  });
256
256
 
257
257
  const redisHelper = new RedisHelper();
258
- await redisHelper.setObject(RedisKeys.apisAll(), apiList);
258
+ await redisHelper.setObject(RedisKeys.apisAll(), apiList.lists);
259
259
  } catch (error: any) {
260
260
  // 忽略缓存错误
261
261
  }
package/sync/syncDev.ts CHANGED
@@ -64,12 +64,12 @@ export async function syncDevCommand(options: SyncDevOptions = {}): Promise<void
64
64
  fields: ['id']
65
65
  });
66
66
 
67
- if (!allMenus || !Array.isArray(allMenus)) {
67
+ if (!allMenus || !Array.isArray(allMenus.lists)) {
68
68
  Logger.debug('[SyncDev] 菜单数据为空,跳过开发者账号同步');
69
69
  return;
70
70
  }
71
71
 
72
- const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
72
+ const menuIds = allMenus.lists.length > 0 ? allMenus.lists.map((m: any) => m.id).join(',') : '';
73
73
 
74
74
  // 查询所有接口 ID
75
75
  const existApi = await helper.tableExists('addon_admin_api');
@@ -80,8 +80,8 @@ export async function syncDevCommand(options: SyncDevOptions = {}): Promise<void
80
80
  fields: ['id']
81
81
  });
82
82
 
83
- if (allApis && Array.isArray(allApis) && allApis.length > 0) {
84
- apiIds = allApis.map((a: any) => a.id).join(',');
83
+ if (allApis && Array.isArray(allApis.lists) && allApis.lists.length > 0) {
84
+ apiIds = allApis.lists.map((a: any) => a.id).join(',');
85
85
  }
86
86
  }
87
87
 
package/sync/syncMenu.ts CHANGED
@@ -91,7 +91,7 @@ async function scanViewsDir(viewsDir: string, prefix: string, parentPath: string
91
91
  const menu: MenuConfig = {
92
92
  name: meta.name,
93
93
  path: fullPath,
94
- sort: meta.order || 100
94
+ sort: meta.order || 1
95
95
  };
96
96
 
97
97
  // 递归扫描子目录
@@ -104,7 +104,7 @@ async function scanViewsDir(viewsDir: string, prefix: string, parentPath: string
104
104
  }
105
105
 
106
106
  // 按 sort 排序
107
- menus.sort((a, b) => (a.sort || 100) - (b.sort || 100));
107
+ menus.sort((a, b) => (a.sort || 1) - (b.sort || 1));
108
108
 
109
109
  return menus;
110
110
  }
@@ -192,7 +192,7 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
192
192
  if (existing) {
193
193
  menuId = existing.id;
194
194
 
195
- const needUpdate = existing.pid !== pid || existing.name !== menu.name || existing.sort !== (menu.sort || 0);
195
+ const needUpdate = existing.pid !== pid || existing.name !== menu.name || existing.sort !== (menu.sort || 1);
196
196
 
197
197
  if (needUpdate) {
198
198
  await helper.updData({
@@ -201,7 +201,7 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
201
201
  data: {
202
202
  pid: pid,
203
203
  name: menu.name,
204
- sort: menu.sort || 0
204
+ sort: menu.sort || 1
205
205
  }
206
206
  });
207
207
  }
@@ -212,7 +212,7 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
212
212
  pid: pid,
213
213
  name: menu.name,
214
214
  path: menu.path || '',
215
- sort: menu.sort || 0
215
+ sort: menu.sort || 1
216
216
  }
217
217
  });
218
218
  }
@@ -235,7 +235,7 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
235
235
  fields: ['id', 'pid', 'name', 'path', 'sort']
236
236
  });
237
237
  const existingMenuMap = new Map<string, any>();
238
- for (const menu of allExistingMenus) {
238
+ for (const menu of allExistingMenus.lists) {
239
239
  if (menu.path) {
240
240
  existingMenuMap.set(menu.path, menu);
241
241
  }
@@ -261,7 +261,7 @@ async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Pro
261
261
  where: { state$gte: 0 }
262
262
  });
263
263
 
264
- for (const record of allRecords) {
264
+ for (const record of allRecords.lists) {
265
265
  if (record.path && !configPaths.has(record.path)) {
266
266
  await helper.delForce({
267
267
  table: 'addon_admin_menu',
@@ -375,7 +375,7 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<vo
375
375
  // 8. 缓存菜单数据到 Redis
376
376
  try {
377
377
  const redisHelper = new RedisHelper();
378
- await redisHelper.setObject(RedisKeys.menusAll(), allMenusData);
378
+ await redisHelper.setObject(RedisKeys.menusAll(), allMenusData.lists);
379
379
  } catch (error: any) {
380
380
  Logger.warn({ err: error }, 'Redis 缓存菜单数据失败');
381
381
  }
@@ -76,7 +76,7 @@ describe('CacheHelper', () => {
76
76
  { id: 1, name: '登录', path: '/api/login', method: 'POST' },
77
77
  { id: 2, name: '用户列表', path: '/api/user/list', method: 'GET' }
78
78
  ];
79
- mockDb.getAll = mock(() => Promise.resolve(apis));
79
+ mockDb.getAll = mock(() => Promise.resolve({ lists: apis, total: apis.length }));
80
80
 
81
81
  await cacheHelper.cacheApis();
82
82
 
@@ -115,7 +115,7 @@ describe('CacheHelper', () => {
115
115
  { id: 1, pid: 0, name: '首页', path: '/home', sort: 1 },
116
116
  { id: 2, pid: 0, name: '用户管理', path: '/user', sort: 2 }
117
117
  ];
118
- mockDb.getAll = mock(() => Promise.resolve(menus));
118
+ mockDb.getAll = mock(() => Promise.resolve({ lists: menus, total: menus.length }));
119
119
 
120
120
  await cacheHelper.cacheMenus();
121
121
 
@@ -148,9 +148,9 @@ describe('CacheHelper', () => {
148
148
  ];
149
149
 
150
150
  mockDb.getAll = mock((opts: any) => {
151
- if (opts.table === 'addon_admin_role') return Promise.resolve(roles);
152
- if (opts.table === 'addon_admin_api') return Promise.resolve(apis);
153
- return Promise.resolve([]);
151
+ if (opts.table === 'addon_admin_role') return Promise.resolve({ lists: roles, total: roles.length });
152
+ if (opts.table === 'addon_admin_api') return Promise.resolve({ lists: apis, total: apis.length });
153
+ return Promise.resolve({ lists: [], total: 0 });
154
154
  });
155
155
 
156
156
  await cacheHelper.cacheRolePermissions();
@@ -169,9 +169,9 @@ describe('CacheHelper', () => {
169
169
  const apis = [{ id: 1, path: '/api/login', method: 'POST' }];
170
170
 
171
171
  mockDb.getAll = mock((opts: any) => {
172
- if (opts.table === 'addon_admin_role') return Promise.resolve(roles);
173
- if (opts.table === 'addon_admin_api') return Promise.resolve(apis);
174
- return Promise.resolve([]);
172
+ if (opts.table === 'addon_admin_role') return Promise.resolve({ lists: roles, total: roles.length });
173
+ if (opts.table === 'addon_admin_api') return Promise.resolve({ lists: apis, total: apis.length });
174
+ return Promise.resolve({ lists: [], total: 0 });
175
175
  });
176
176
 
177
177
  await cacheHelper.cacheRolePermissions();
@@ -188,9 +188,9 @@ describe('CacheHelper', () => {
188
188
  const apis = [{ id: 1, path: '/api/test', method: 'GET' }];
189
189
 
190
190
  mockDb.getAll = mock((opts: any) => {
191
- if (opts.table === 'addon_admin_role') return Promise.resolve(roles);
192
- if (opts.table === 'addon_admin_api') return Promise.resolve(apis);
193
- return Promise.resolve([]);
191
+ if (opts.table === 'addon_admin_role') return Promise.resolve({ lists: roles, total: roles.length });
192
+ if (opts.table === 'addon_admin_api') return Promise.resolve({ lists: apis, total: apis.length });
193
+ return Promise.resolve({ lists: [], total: 0 });
194
194
  });
195
195
 
196
196
  await cacheHelper.cacheRolePermissions();
@@ -163,6 +163,16 @@ export interface ListResult<T = any> {
163
163
  pages: number;
164
164
  }
165
165
 
166
+ /**
167
+ * 全部查询结果
168
+ */
169
+ export interface AllResult<T = any> {
170
+ /** 数据列表 */
171
+ lists: T[];
172
+ /** 总条数 */
173
+ total: number;
174
+ }
175
+
166
176
  /**
167
177
  * 事务回调函数
168
178
  */
@@ -269,7 +279,7 @@ export interface DbHelper {
269
279
  * @template K - 表名类型
270
280
  * @returns 返回类型自动推断为对应表的记录数组
271
281
  */
272
- getAll<K extends TableName>(options: Omit<TypedQueryOptions<K>, 'page' | 'limit'>): Promise<TableType<K>[]>;
282
+ getAll<K extends TableName>(options: Omit<TypedQueryOptions<K>, 'page' | 'limit'>): Promise<AllResult<TableType<K>>>;
273
283
 
274
284
  /**
275
285
  * 插入数据(类型安全版本)
@@ -314,7 +324,7 @@ export interface DbHelper {
314
324
  * 查询所有数据(兼容版本,需手动指定泛型)
315
325
  * @template T - 返回类型
316
326
  */
317
- getAll<T = any>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<T[]>;
327
+ getAll<T = any>(options: Omit<QueryOptions, 'page' | 'limit'>): Promise<AllResult<T>>;
318
328
 
319
329
  /**
320
330
  * 插入数据(兼容版本)