befly 3.9.37 → 3.9.38

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/README.md CHANGED
@@ -293,7 +293,7 @@ export const beflyConfig = { ... };
293
293
 
294
294
  - **`packages/core`** - Befly 核心框架包(发布到 npm)
295
295
  - **`packages/tpl`** - API 项目模板示例
296
- - **`packages/admin`** - 后台管理系统(Vue3 + TinyVue + 自动导入)
296
+ - **`packages/admin`** - 后台管理系统(Vue3 + TDesign Vue Next + 自动导入)
297
297
 
298
298
  ## 🚀 快速启动
299
299
 
package/docs/api.md CHANGED
@@ -999,7 +999,7 @@ import adminTable from '../../tables/admin.json';
999
999
  export default {
1000
1000
  name: '添加管理员',
1001
1001
  fields: adminTable,
1002
- required: ['username', 'password', 'roleId'],
1002
+ required: ['username', 'password', 'roleCode'],
1003
1003
  handler: async (befly, ctx) => {
1004
1004
  // 检查用户名是否已存在
1005
1005
  const existing = await befly.db.getOne({
@@ -1014,11 +1014,10 @@ export default {
1014
1014
  // 查询角色信息
1015
1015
  const role = await befly.db.getOne({
1016
1016
  table: 'addon_admin_role',
1017
- where: { id: ctx.body.roleId },
1018
- columns: ['code']
1017
+ where: { code: ctx.body.roleCode }
1019
1018
  });
1020
1019
 
1021
- if (!role?.code) {
1020
+ if (!role) {
1022
1021
  return befly.tool.No('角色不存在');
1023
1022
  }
1024
1023
 
@@ -1032,7 +1031,6 @@ export default {
1032
1031
  username: ctx.body.username,
1033
1032
  password: hashedPassword,
1034
1033
  nickname: ctx.body.nickname,
1035
- roleId: ctx.body.roleId,
1036
1034
  roleCode: role.code
1037
1035
  }
1038
1036
  });
package/docs/database.md CHANGED
@@ -153,7 +153,7 @@ export default {
153
153
  name: '用户列表',
154
154
  fields: {
155
155
  keyword: { name: '关键词', type: 'string', max: 50 },
156
- roleId: { name: '角色ID', type: 'number' },
156
+ status: { name: '状态', type: 'number' },
157
157
  state: { name: '状态', type: 'number' }
158
158
  },
159
159
  handler: async (befly, ctx) => {
@@ -162,7 +162,7 @@ export default {
162
162
  const result = await befly.db.getList({
163
163
  table: 'user',
164
164
  where: {
165
- roleId: ctx.body.roleId, // 未传时为 undefined,自动忽略
165
+ status: ctx.body.status, // 未传时为 undefined,自动忽略
166
166
  state: ctx.body.state, // 未传时为 undefined,自动忽略
167
167
  username$like: ctx.body.keyword ? `%${ctx.body.keyword}%` : undefined // 无关键词时忽略
168
168
  },
@@ -441,7 +441,7 @@ const userId = await befly.db.insData({
441
441
  username: 'john',
442
442
  email: 'john@example.com',
443
443
  password: hashedPassword,
444
- roleId: 1
444
+ categoryId: 1
445
445
  }
446
446
  });
447
447
  // 返回新记录的 ID
@@ -502,7 +502,7 @@ const affected = await befly.db.updData({
502
502
  await befly.db.updData({
503
503
  table: 'user',
504
504
  data: { state: 2 },
505
- where: { roleId: 5 }
505
+ where: { status: 5 }
506
506
  });
507
507
  ```
508
508
 
@@ -794,8 +794,8 @@ where: { id: 1 }
794
794
  // → WHERE id = 1
795
795
 
796
796
  // 多条件(AND)
797
- where: { state: 1, roleId: 2 }
798
- // → WHERE state = 1 AND role_id = 2
797
+ where: { state: 1, status: 2 }
798
+ // → WHERE state = 1 AND status = 2
799
799
  ```
800
800
 
801
801
  ### 比较操作符
@@ -842,9 +842,9 @@ where: {
842
842
  ```typescript
843
843
  // 显式 AND(通常不需要,多条件默认就是 AND)
844
844
  where: {
845
- $and: [{ state: 1 }, { roleId: 2 }];
845
+ $and: [{ state: 1 }, { status: 2 }];
846
846
  }
847
- // → WHERE state = 1 AND role_id = 2
847
+ // → WHERE state = 1 AND status = 2
848
848
  ```
849
849
 
850
850
  **组合使用**
@@ -867,9 +867,9 @@ where: {
867
867
 
868
868
  ```typescript
869
869
  where: {
870
- roleId$in: [1, 2, 3];
870
+ categoryId$in: [1, 2, 3];
871
871
  }
872
- // → WHERE role_id IN (1, 2, 3)
872
+ // → WHERE category_id IN (1, 2, 3)
873
873
 
874
874
  where: {
875
875
  status$in: ['pending', 'processing'];
@@ -1075,7 +1075,7 @@ export default {
1075
1075
  name: '用户列表',
1076
1076
  fields: {
1077
1077
  keyword: { name: '关键词', type: 'string', max: 50 },
1078
- roleId: { name: '角色ID', type: 'number' },
1078
+ departmentId: { name: '部门ID', type: 'number' },
1079
1079
  page: Fields.page,
1080
1080
  limit: Fields.limit
1081
1081
  },
@@ -1087,9 +1087,9 @@ export default {
1087
1087
  where.$or = [{ username$like: `%${ctx.body.keyword}%` }, { nickname$like: `%${ctx.body.keyword}%` }, { email$like: `%${ctx.body.keyword}%` }];
1088
1088
  }
1089
1089
 
1090
- // 角色过滤
1091
- if (ctx.body.roleId) {
1092
- where.roleId = ctx.body.roleId;
1090
+ // 部门过滤
1091
+ if (ctx.body.departmentId) {
1092
+ where.departmentId = ctx.body.departmentId;
1093
1093
  }
1094
1094
 
1095
1095
  const result = await befly.db.getList({
package/docs/table.md CHANGED
@@ -184,8 +184,8 @@ interface FieldDefinition {
184
184
  "max": 150,
185
185
  "default": 0
186
186
  },
187
- "roleId": {
188
- "name": "角色ID",
187
+ "userId": {
188
+ "name": "用户ID",
189
189
  "type": "number",
190
190
  "min": 1,
191
191
  "index": true
@@ -287,8 +287,8 @@ interface FieldDefinition {
287
287
 
288
288
  ```json
289
289
  {
290
- "roleId": {
291
- "name": "角色ID",
290
+ "categoryId": {
291
+ "name": "分类ID",
292
292
  "type": "number",
293
293
  "index": true
294
294
  },
@@ -620,8 +620,8 @@ bun run sync:db --force
620
620
  "type": "string",
621
621
  "max": 500
622
622
  },
623
- "roleId": {
624
- "name": "角色ID",
623
+ "departmentId": {
624
+ "name": "部门ID",
625
625
  "type": "number",
626
626
  "min": 1,
627
627
  "index": true
@@ -19,6 +19,14 @@ const hook: Hook = {
19
19
  return;
20
20
  }
21
21
 
22
+ // 应用字段默认值
23
+ for (const [field, fieldDef] of Object.entries(ctx.api.fields)) {
24
+ // 字段未传值且定义了默认值时,应用默认值
25
+ if (ctx.body[field] === undefined && (fieldDef as any)?.default !== undefined) {
26
+ ctx.body[field] = (fieldDef as any).default;
27
+ }
28
+ }
29
+
22
30
  // 验证参数
23
31
  const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
24
32
 
@@ -37,7 +37,6 @@ export class CacheHelper {
37
37
  // 从数据库查询所有接口
38
38
  const apiList = await this.befly.db.getAll({
39
39
  table: 'addon_admin_api',
40
- fields: ['id', 'name', 'path', 'method', 'description', 'addonName', 'addonTitle'],
41
40
  orderBy: ['addonName#ASC', 'path#ASC']
42
41
  });
43
42
 
@@ -67,7 +66,6 @@ export class CacheHelper {
67
66
  // 从数据库查询所有菜单
68
67
  const menus = await this.befly.db.getAll({
69
68
  table: 'addon_admin_menu',
70
- fields: ['id', 'pid', 'name', 'path', 'icon', 'type', 'sort'],
71
69
  orderBy: ['sort#ASC', 'id#ASC']
72
70
  });
73
71
 
@@ -100,12 +98,10 @@ export class CacheHelper {
100
98
  // 并行查询角色和接口(利用自动 pipeline)
101
99
  const [roles, allApis] = await Promise.all([
102
100
  this.befly.db.getAll({
103
- table: 'addon_admin_role',
104
- fields: ['id', 'code', 'apis']
101
+ table: 'addon_admin_role'
105
102
  }),
106
103
  this.befly.db.getAll({
107
- table: 'addon_admin_api',
108
- fields: ['id', 'path', 'method']
104
+ table: 'addon_admin_api'
109
105
  })
110
106
  ]);
111
107
 
package/lib/dbHelper.ts CHANGED
@@ -283,7 +283,7 @@ export class DbHelper {
283
283
 
284
284
  // 联查时使用特殊处理逻辑
285
285
  if (hasJoins) {
286
- // 联查时字段直接处理(支持表别名)
286
+ // 联查时字段直接处理(支持表名.字段名格式)
287
287
  const processedFields = (options.fields || []).map((f) => this.processJoinField(f));
288
288
 
289
289
  return {
@@ -339,15 +339,26 @@ export class DbHelper {
339
339
  /**
340
340
  * 添加默认的 state 过滤条件
341
341
  * 默认查询 state > 0 的数据(排除已删除和特殊状态)
342
+ * @param where - where 条件
343
+ * @param table - 主表名(JOIN 查询时需要,用于添加表名前缀避免歧义)
344
+ * @param hasJoins - 是否有 JOIN 查询
342
345
  */
343
- private addDefaultStateFilter(where: WhereConditions = {}): WhereConditions {
346
+ private addDefaultStateFilter(where: WhereConditions = {}, table?: string, hasJoins: boolean = false): WhereConditions {
344
347
  // 如果用户已经指定了 state 条件,优先使用用户的条件
345
- const hasStateCondition = Object.keys(where).some((key) => key.startsWith('state'));
348
+ const hasStateCondition = Object.keys(where).some((key) => key.startsWith('state') || key.includes('.state'));
346
349
 
347
350
  if (hasStateCondition) {
348
351
  return where;
349
352
  }
350
353
 
354
+ // JOIN 查询时需要指定主表名前缀避免歧义
355
+ if (hasJoins && table) {
356
+ return {
357
+ ...where,
358
+ [`${table}.state$gt`]: 0
359
+ };
360
+ }
361
+
351
362
  // 默认查询 state > 0 的数据
352
363
  return {
353
364
  ...where,
@@ -541,7 +552,10 @@ export class DbHelper {
541
552
  async getCount(options: Omit<QueryOptions, 'fields' | 'page' | 'limit' | 'orderBy'>): Promise<number> {
542
553
  const { table, where, joins } = await this.prepareQueryOptions(options as QueryOptions);
543
554
 
544
- const builder = new SqlBuilder().select(['COUNT(*) as count']).from(table).where(this.addDefaultStateFilter(where));
555
+ const builder = new SqlBuilder()
556
+ .select(['COUNT(*) as count'])
557
+ .from(table)
558
+ .where(this.addDefaultStateFilter(where, table, !!joins));
545
559
 
546
560
  // 添加 JOIN
547
561
  this.applyJoins(builder, joins);
@@ -571,7 +585,10 @@ export class DbHelper {
571
585
  async getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<T | null> {
572
586
  const { table, fields, where, joins } = await this.prepareQueryOptions(options);
573
587
 
574
- const builder = new SqlBuilder().select(fields).from(table).where(this.addDefaultStateFilter(where));
588
+ const builder = new SqlBuilder()
589
+ .select(fields)
590
+ .from(table)
591
+ .where(this.addDefaultStateFilter(where, table, !!joins));
575
592
 
576
593
  // 添加 JOIN
577
594
  this.applyJoins(builder, joins);
@@ -623,7 +640,7 @@ export class DbHelper {
623
640
  }
624
641
 
625
642
  // 构建查询
626
- const whereFiltered = this.addDefaultStateFilter(prepared.where);
643
+ const whereFiltered = this.addDefaultStateFilter(prepared.where, prepared.table, !!prepared.joins);
627
644
 
628
645
  // 查询总数
629
646
  const countBuilder = new SqlBuilder().select(['COUNT(*) as total']).from(prepared.table).where(whereFiltered);
@@ -698,7 +715,7 @@ export class DbHelper {
698
715
 
699
716
  const prepared = await this.prepareQueryOptions({ ...options, page: 1, limit: 10 });
700
717
 
701
- const whereFiltered = this.addDefaultStateFilter(prepared.where);
718
+ const whereFiltered = this.addDefaultStateFilter(prepared.where, prepared.table, !!prepared.joins);
702
719
 
703
720
  // 查询真实总数
704
721
  const countBuilder = new SqlBuilder().select(['COUNT(*) as total']).from(prepared.table).where(whereFiltered);
@@ -893,7 +910,7 @@ export class DbHelper {
893
910
  };
894
911
 
895
912
  // 构建 SQL
896
- const whereFiltered = this.addDefaultStateFilter(snakeWhere);
913
+ const whereFiltered = this.addDefaultStateFilter(snakeWhere, snakeTable, false);
897
914
  const builder = new SqlBuilder().where(whereFiltered);
898
915
  const { sql, params } = builder.toUpdateSql(snakeTable, processed);
899
916
 
@@ -1003,7 +1020,11 @@ export class DbHelper {
1003
1020
  const { table, where } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 });
1004
1021
 
1005
1022
  // 使用 COUNT(1) 性能更好
1006
- const builder = new SqlBuilder().select(['COUNT(1) as cnt']).from(table).where(this.addDefaultStateFilter(where)).limit(1);
1023
+ const builder = new SqlBuilder()
1024
+ .select(['COUNT(1) as cnt'])
1025
+ .from(table)
1026
+ .where(this.addDefaultStateFilter(where, table, false))
1027
+ .limit(1);
1007
1028
 
1008
1029
  const { sql, params } = builder.toSelectSql();
1009
1030
  const result = await this.executeWithConn(sql, params);
@@ -1084,7 +1105,7 @@ export class DbHelper {
1084
1105
  const snakeWhere = this.whereKeysToSnake(cleanWhere);
1085
1106
 
1086
1107
  // 使用 SqlBuilder 构建安全的 WHERE 条件
1087
- const whereFiltered = this.addDefaultStateFilter(snakeWhere);
1108
+ const whereFiltered = this.addDefaultStateFilter(snakeWhere, snakeTable, false);
1088
1109
  const builder = new SqlBuilder().where(whereFiltered);
1089
1110
  const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
1090
1111
 
@@ -24,16 +24,16 @@ import type { ApiRoute } from '../types/api.js';
24
24
  */
25
25
  const PRESET_FIELDS: Record<string, any> = {
26
26
  '@id': { name: 'ID', type: 'number', min: 1, max: null },
27
- '@page': { name: '页码', type: 'number', min: 1, max: 9999 },
28
- '@limit': { name: '每页数量', type: 'number', min: 1, max: 100 },
29
- '@keyword': { name: '关键词', type: 'string', min: 1, max: 50 },
30
- '@state': { name: '状态', type: 'number', min: 0, max: 2 }
27
+ '@page': { name: '页码', type: 'number', min: 1, max: 9999, default: 1 },
28
+ '@limit': { name: '每页数量', type: 'number', min: 1, max: 100, default: 30 },
29
+ '@keyword': { name: '关键词', type: 'string', min: 0, max: 50 },
30
+ '@state': { name: '状态', type: 'number', regex: '^[0-2]$' }
31
31
  };
32
32
 
33
33
  /**
34
34
  * 处理字段定义,将 @ 符号引用替换为实际字段定义
35
35
  */
36
- function processFields(fields: Record<string, any>): Record<string, any> {
36
+ function processFields(fields: Record<string, any>, apiName: string, routePath: string): Record<string, any> {
37
37
  if (!fields || typeof fields !== 'object') return fields;
38
38
 
39
39
  const processed: Record<string, any> = {};
@@ -43,8 +43,9 @@ function processFields(fields: Record<string, any>): Record<string, any> {
43
43
  if (PRESET_FIELDS[value]) {
44
44
  processed[key] = PRESET_FIELDS[value];
45
45
  } else {
46
- // 未找到预定义字段,保持原值
47
- processed[key] = value;
46
+ // 未找到预定义字段,抛出错误
47
+ const validKeys = Object.keys(PRESET_FIELDS).join(', ');
48
+ throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
48
49
  }
49
50
  } else {
50
51
  // 普通字段定义,保持原样
@@ -110,13 +111,14 @@ export async function loadApis(apis: Map<string, ApiRoute>): Promise<void> {
110
111
  // 设置默认值
111
112
  const methodStr = (api.method || 'POST').toUpperCase();
112
113
  api.auth = api.auth !== undefined ? api.auth : true;
113
- // 处理字段定义,将 @ 引用替换为实际字段定义
114
- api.fields = processFields(api.fields || {});
115
- api.required = api.required || [];
116
114
 
117
- // 构建路由路径(不含方法)
115
+ // 构建路由路径(用于错误提示)
118
116
  const routePath = `/api${apiFile.routePrefix}${apiFile.relativePath}`;
119
117
 
118
+ // 处理字段定义,将 @ 引用替换为实际字段定义
119
+ api.fields = processFields(api.fields || {}, api.name || apiFile.relativePath, routePath);
120
+ api.required = api.required || [];
121
+
120
122
  // 支持逗号分隔的多方法,拆分后分别注册
121
123
  const methods = methodStr
122
124
  .split(',')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.9.37",
3
+ "version": "3.9.38",
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": "e078b94f087ddf21f289b55ab86c0b8dce105807",
77
+ "gitHead": "8cb371928961c63d6c3c416c6dd667f8fe285376",
78
78
  "devDependencies": {
79
79
  "typescript": "^5.9.3"
80
80
  }
package/sync/syncApi.ts CHANGED
@@ -199,7 +199,6 @@ 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', 'addonName', 'path', 'method'],
203
202
  where: { state$gte: 0 }
204
203
  });
205
204
 
@@ -250,8 +249,7 @@ export async function syncApiCommand(options: SyncApiOptions = {}): Promise<void
250
249
  try {
251
250
  const apiList = await helper.getAll({
252
251
  table: 'addon_admin_api',
253
- fields: ['id', 'name', 'path', 'method', 'description', 'addonName', 'addonTitle'],
254
- orderBy: ['addonName#ASC', 'path#ASC']
252
+ orderBy: ['id#ASC']
255
253
  });
256
254
 
257
255
  const redisHelper = new RedisHelper();
package/sync/syncDev.ts CHANGED
@@ -179,7 +179,6 @@ export async function syncDevCommand(options: SyncDevOptions = {}): Promise<void
179
179
  email: beflyConfig.devEmail,
180
180
  username: 'dev',
181
181
  password: hashed,
182
- roleId: devRole.id,
183
182
  roleCode: 'dev',
184
183
  roleType: 'admin'
185
184
  };
package/sync/syncMenu.ts CHANGED
@@ -231,8 +231,7 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
231
231
  */
232
232
  async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
233
233
  const allExistingMenus = await helper.getAll({
234
- table: 'addon_admin_menu',
235
- fields: ['id', 'pid', 'name', 'path', 'sort']
234
+ table: 'addon_admin_menu'
236
235
  });
237
236
  const existingMenuMap = new Map<string, any>();
238
237
  for (const menu of allExistingMenus.lists) {
@@ -368,7 +367,6 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<vo
368
367
  // 7. 获取最终菜单数据(用于缓存)
369
368
  const allMenusData = await helper.getAll({
370
369
  table: 'addon_admin_menu',
371
- fields: ['id', 'pid', 'name', 'path', 'sort'],
372
370
  orderBy: ['sort#ASC', 'id#ASC']
373
371
  });
374
372