befly 3.9.37 → 3.9.39

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 (155) hide show
  1. package/README.md +38 -39
  2. package/befly.config.ts +62 -40
  3. package/checks/checkApi.ts +16 -16
  4. package/checks/checkApp.ts +19 -25
  5. package/checks/checkTable.ts +42 -42
  6. package/docs/README.md +42 -35
  7. package/docs/{api.md → api/api.md} +225 -235
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +155 -153
  10. package/docs/{examples.md → guide/examples.md} +181 -181
  11. package/docs/guide/quickstart.md +331 -0
  12. package/docs/hooks/auth.md +38 -0
  13. package/docs/hooks/cors.md +28 -0
  14. package/docs/{hook.md → hooks/hook.md} +140 -57
  15. package/docs/hooks/parser.md +19 -0
  16. package/docs/hooks/rateLimit.md +47 -0
  17. package/docs/{redis.md → infra/redis.md} +84 -93
  18. package/docs/plugins/cipher.md +61 -0
  19. package/docs/plugins/database.md +128 -0
  20. package/docs/{plugin.md → plugins/plugin.md} +83 -81
  21. package/docs/quickstart.md +26 -26
  22. package/docs/{addon.md → reference/addon.md} +46 -46
  23. package/docs/{config.md → reference/config.md} +32 -80
  24. package/docs/{logger.md → reference/logger.md} +52 -52
  25. package/docs/{sync.md → reference/sync.md} +32 -35
  26. package/docs/{table.md → reference/table.md} +7 -7
  27. package/docs/{validator.md → reference/validator.md} +57 -57
  28. package/hooks/auth.ts +8 -4
  29. package/hooks/cors.ts +13 -13
  30. package/hooks/parser.ts +37 -17
  31. package/hooks/permission.ts +26 -14
  32. package/hooks/rateLimit.ts +276 -0
  33. package/hooks/validator.ts +15 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -81
  36. package/lib/cacheKeys.ts +38 -0
  37. package/lib/cipher.ts +30 -30
  38. package/lib/connect.ts +28 -28
  39. package/lib/dbHelper.ts +211 -109
  40. package/lib/jwt.ts +16 -16
  41. package/lib/logger.ts +610 -19
  42. package/lib/redisHelper.ts +185 -44
  43. package/lib/sqlBuilder.ts +90 -91
  44. package/lib/validator.ts +59 -39
  45. package/loader/loadApis.ts +53 -47
  46. package/loader/loadHooks.ts +40 -14
  47. package/loader/loadPlugins.ts +16 -17
  48. package/main.ts +57 -47
  49. package/package.json +47 -45
  50. package/paths.ts +15 -14
  51. package/plugins/cache.ts +5 -4
  52. package/plugins/cipher.ts +3 -3
  53. package/plugins/config.ts +2 -2
  54. package/plugins/db.ts +9 -9
  55. package/plugins/jwt.ts +3 -3
  56. package/plugins/logger.ts +8 -12
  57. package/plugins/redis.ts +8 -8
  58. package/plugins/tool.ts +6 -6
  59. package/router/api.ts +85 -56
  60. package/router/static.ts +12 -12
  61. package/sync/syncAll.ts +12 -12
  62. package/sync/syncApi.ts +55 -54
  63. package/sync/syncDb/apply.ts +20 -19
  64. package/sync/syncDb/constants.ts +25 -23
  65. package/sync/syncDb/ddl.ts +35 -36
  66. package/sync/syncDb/helpers.ts +6 -9
  67. package/sync/syncDb/schema.ts +10 -9
  68. package/sync/syncDb/sqlite.ts +7 -8
  69. package/sync/syncDb/table.ts +37 -35
  70. package/sync/syncDb/tableCreate.ts +21 -20
  71. package/sync/syncDb/types.ts +23 -20
  72. package/sync/syncDb/version.ts +10 -10
  73. package/sync/syncDb.ts +43 -36
  74. package/sync/syncDev.ts +74 -66
  75. package/sync/syncMenu.ts +190 -57
  76. package/tests/api-integration-array-number.test.ts +282 -0
  77. package/tests/befly-config-env.test.ts +78 -0
  78. package/tests/cacheHelper.test.ts +135 -104
  79. package/tests/cacheKeys.test.ts +41 -0
  80. package/tests/cipher.test.ts +90 -89
  81. package/tests/dbHelper-advanced.test.ts +140 -134
  82. package/tests/dbHelper-all-array-types.test.ts +316 -0
  83. package/tests/dbHelper-array-serialization.test.ts +258 -0
  84. package/tests/dbHelper-columns.test.ts +56 -55
  85. package/tests/dbHelper-execute.test.ts +45 -44
  86. package/tests/dbHelper-joins.test.ts +124 -119
  87. package/tests/fields-redis-cache.test.ts +29 -27
  88. package/tests/fields-validate.test.ts +38 -38
  89. package/tests/getClientIp.test.ts +54 -0
  90. package/tests/integration.test.ts +69 -67
  91. package/tests/jwt.test.ts +27 -26
  92. package/tests/logger.test.ts +267 -34
  93. package/tests/rateLimit-hook.test.ts +477 -0
  94. package/tests/redisHelper.test.ts +187 -188
  95. package/tests/redisKeys.test.ts +6 -73
  96. package/tests/scanConfig.test.ts +144 -0
  97. package/tests/sqlBuilder-advanced.test.ts +217 -215
  98. package/tests/sqlBuilder.test.ts +92 -91
  99. package/tests/sync-connection.test.ts +29 -29
  100. package/tests/syncDb-apply.test.ts +97 -96
  101. package/tests/syncDb-array-number.test.ts +160 -0
  102. package/tests/syncDb-constants.test.ts +48 -47
  103. package/tests/syncDb-ddl.test.ts +99 -98
  104. package/tests/syncDb-helpers.test.ts +29 -28
  105. package/tests/syncDb-schema.test.ts +61 -60
  106. package/tests/syncDb-types.test.ts +60 -59
  107. package/tests/syncMenu-paths.test.ts +68 -0
  108. package/tests/util.test.ts +42 -41
  109. package/tests/validator-array-number.test.ts +310 -0
  110. package/tests/validator-default.test.ts +373 -0
  111. package/tests/validator.test.ts +271 -266
  112. package/tsconfig.json +4 -5
  113. package/types/api.d.ts +7 -12
  114. package/types/befly.d.ts +60 -13
  115. package/types/cache.d.ts +8 -4
  116. package/types/common.d.ts +17 -9
  117. package/types/context.d.ts +2 -2
  118. package/types/crypto.d.ts +23 -0
  119. package/types/database.d.ts +19 -19
  120. package/types/hook.d.ts +2 -2
  121. package/types/jwt.d.ts +118 -0
  122. package/types/logger.d.ts +30 -0
  123. package/types/plugin.d.ts +4 -4
  124. package/types/redis.d.ts +7 -3
  125. package/types/roleApisCache.ts +23 -0
  126. package/types/sync.d.ts +10 -10
  127. package/types/table.d.ts +50 -9
  128. package/types/validate.d.ts +69 -0
  129. package/utils/addonHelper.ts +90 -0
  130. package/utils/arrayKeysToCamel.ts +18 -0
  131. package/utils/calcPerfTime.ts +13 -0
  132. package/utils/configTypes.ts +3 -0
  133. package/utils/cors.ts +19 -0
  134. package/utils/fieldClear.ts +75 -0
  135. package/utils/genShortId.ts +12 -0
  136. package/utils/getClientIp.ts +45 -0
  137. package/utils/keysToCamel.ts +22 -0
  138. package/utils/keysToSnake.ts +22 -0
  139. package/utils/modules.ts +98 -0
  140. package/utils/pickFields.ts +19 -0
  141. package/utils/process.ts +56 -0
  142. package/utils/regex.ts +225 -0
  143. package/utils/response.ts +115 -0
  144. package/utils/route.ts +23 -0
  145. package/utils/scanConfig.ts +142 -0
  146. package/utils/scanFiles.ts +48 -0
  147. package/.prettierignore +0 -2
  148. package/.prettierrc +0 -12
  149. package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
  150. package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
  151. package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
  152. package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
  153. package/hooks/requestLogger.ts +0 -84
  154. package/types/index.ts +0 -24
  155. package/util.ts +0 -283
@@ -1,4 +1,4 @@
1
- # 实战示例
1
+ # 实战示例
2
2
 
3
3
  > 完整的 CRUD 模块开发示例
4
4
 
@@ -40,28 +40,28 @@
40
40
  `apis/user/register.ts`:
41
41
 
42
42
  ```typescript
43
- import type { ApiRoute } from 'befly/types';
43
+ import type { ApiRoute } from "befly/types/api";
44
44
 
45
45
  export default {
46
- name: '用户注册',
47
- method: 'POST',
46
+ name: "用户注册",
47
+ method: "POST",
48
48
  auth: false,
49
49
  fields: {
50
- email: { name: '邮箱', type: 'string', min: 5, max: 100, regexp: '@email' },
51
- password: { name: '密码', type: 'string', min: 6, max: 100 },
52
- nickname: { name: '昵称', type: 'string', min: 2, max: 50 }
50
+ email: { name: "邮箱", type: "string", min: 5, max: 100, regexp: "@email" },
51
+ password: { name: "密码", type: "string", min: 6, max: 100 },
52
+ nickname: { name: "昵称", type: "string", min: 2, max: 50 }
53
53
  },
54
- required: ['email', 'password'],
54
+ required: ["email", "password"],
55
55
  handler: async (befly, ctx) => {
56
56
  // 检查邮箱是否已存在
57
57
  const exists = await befly.db.getDetail({
58
- table: 'user',
59
- columns: ['id'],
58
+ table: "user",
59
+ columns: ["id"],
60
60
  where: { email: ctx.body.email }
61
61
  });
62
62
 
63
63
  if (exists?.id) {
64
- return No('该邮箱已被注册');
64
+ return No("该邮箱已被注册");
65
65
  }
66
66
 
67
67
  // 加密密码
@@ -69,15 +69,15 @@ export default {
69
69
 
70
70
  // 创建用户
71
71
  const result = await befly.db.insData({
72
- table: 'user',
72
+ table: "user",
73
73
  data: {
74
74
  email: ctx.body.email,
75
75
  password: hashedPassword,
76
- nickname: ctx.body.nickname || '用户'
76
+ nickname: ctx.body.nickname || "用户"
77
77
  }
78
78
  });
79
79
 
80
- return Yes('注册成功', { id: result.insertId });
80
+ return Yes("注册成功", { id: result.insertId });
81
81
  }
82
82
  } as ApiRoute;
83
83
  ```
@@ -87,42 +87,42 @@ export default {
87
87
  `apis/user/login.ts`:
88
88
 
89
89
  ```typescript
90
- import type { ApiRoute } from 'befly/types';
90
+ import type { ApiRoute } from "befly/types/api";
91
91
 
92
92
  export default {
93
- name: '用户登录',
94
- method: 'POST',
93
+ name: "用户登录",
94
+ method: "POST",
95
95
  auth: false,
96
96
  fields: {
97
- email: { name: '邮箱', type: 'string', min: 5, max: 100, regexp: '@email' },
98
- password: { name: '密码', type: 'string', min: 6, max: 100 }
97
+ email: { name: "邮箱", type: "string", min: 5, max: 100, regexp: "@email" },
98
+ password: { name: "密码", type: "string", min: 6, max: 100 }
99
99
  },
100
- required: ['email', 'password'],
100
+ required: ["email", "password"],
101
101
  handler: async (befly, ctx) => {
102
102
  // 查询用户
103
103
  const user = await befly.db.getDetail({
104
- table: 'user',
105
- columns: ['id', 'email', 'password', 'nickname', 'avatar', 'role', 'state'],
104
+ table: "user",
105
+ columns: ["id", "email", "password", "nickname", "avatar", "role", "state"],
106
106
  where: { email: ctx.body.email }
107
107
  });
108
108
 
109
109
  if (!user?.id) {
110
- return No('用户不存在');
110
+ return No("用户不存在");
111
111
  }
112
112
 
113
113
  if (user.state !== 1) {
114
- return No('账户已被禁用');
114
+ return No("账户已被禁用");
115
115
  }
116
116
 
117
117
  // 验证密码
118
118
  const isValid = await befly.cipher.verifyPassword(ctx.body.password, user.password);
119
119
  if (!isValid) {
120
- return No('密码错误');
120
+ return No("密码错误");
121
121
  }
122
122
 
123
123
  // 更新登录信息
124
124
  await befly.db.updData({
125
- table: 'user',
125
+ table: "user",
126
126
  data: {
127
127
  loginCount: user.loginCount + 1,
128
128
  lastLoginAt: Date.now()
@@ -136,7 +136,7 @@ export default {
136
136
  role: user.role
137
137
  });
138
138
 
139
- return Yes('登录成功', {
139
+ return Yes("登录成功", {
140
140
  token: token,
141
141
  user: {
142
142
  id: user.id,
@@ -155,24 +155,24 @@ export default {
155
155
  `apis/user/info.ts`:
156
156
 
157
157
  ```typescript
158
- import type { ApiRoute } from 'befly/types';
158
+ import type { ApiRoute } from "befly/types/api";
159
159
 
160
160
  export default {
161
- name: '获取用户信息',
162
- method: 'GET',
161
+ name: "获取用户信息",
162
+ method: "GET",
163
163
  auth: true,
164
164
  handler: async (befly, ctx) => {
165
165
  const user = await befly.db.getDetail({
166
- table: 'user',
167
- columns: ['id', 'email', 'nickname', 'avatar', 'phone', 'gender', 'birthday', 'bio', 'role', 'createdAt'],
166
+ table: "user",
167
+ columns: ["id", "email", "nickname", "avatar", "phone", "gender", "birthday", "bio", "role", "createdAt"],
168
168
  where: { id: ctx.user.userId }
169
169
  });
170
170
 
171
171
  if (!user?.id) {
172
- return No('用户不存在');
172
+ return No("用户不存在");
173
173
  }
174
174
 
175
- return Yes('获取成功', user);
175
+ return Yes("获取成功", user);
176
176
  }
177
177
  } as ApiRoute;
178
178
  ```
@@ -182,19 +182,19 @@ export default {
182
182
  `apis/user/update.ts`:
183
183
 
184
184
  ```typescript
185
- import type { ApiRoute } from 'befly/types';
185
+ import type { ApiRoute } from "befly/types/api";
186
186
 
187
187
  export default {
188
- name: '更新用户信息',
189
- method: 'POST',
188
+ name: "更新用户信息",
189
+ method: "POST",
190
190
  auth: true,
191
191
  fields: {
192
- nickname: { name: '昵称', type: 'string', min: 2, max: 50 },
193
- avatar: { name: '头像', type: 'string', max: 500 },
194
- phone: { name: '手机号', type: 'string', max: 20, regexp: '@phone' },
195
- gender: { name: '性别', type: 'number', min: 0, max: 2 },
196
- birthday: { name: '生日', type: 'string', max: 10 },
197
- bio: { name: '简介', type: 'string', max: 500 }
192
+ nickname: { name: "昵称", type: "string", min: 2, max: 50 },
193
+ avatar: { name: "头像", type: "string", max: 500 },
194
+ phone: { name: "手机号", type: "string", max: 20, regexp: "@phone" },
195
+ gender: { name: "性别", type: "number", min: 0, max: 2 },
196
+ birthday: { name: "生日", type: "string", max: 10 },
197
+ bio: { name: "简介", type: "string", max: 500 }
198
198
  },
199
199
  handler: async (befly, ctx) => {
200
200
  const updateData: Record<string, any> = {};
@@ -208,16 +208,16 @@ export default {
208
208
  if (ctx.body.bio !== undefined) updateData.bio = ctx.body.bio;
209
209
 
210
210
  if (Object.keys(updateData).length === 0) {
211
- return No('没有需要更新的字段');
211
+ return No("没有需要更新的字段");
212
212
  }
213
213
 
214
214
  await befly.db.updData({
215
- table: 'user',
215
+ table: "user",
216
216
  data: updateData,
217
217
  where: { id: ctx.user.userId }
218
218
  });
219
219
 
220
- return Yes('更新成功');
220
+ return Yes("更新成功");
221
221
  }
222
222
  } as ApiRoute;
223
223
  ```
@@ -227,33 +227,33 @@ export default {
227
227
  `apis/user/changePassword.ts`:
228
228
 
229
229
  ```typescript
230
- import type { ApiRoute } from 'befly/types';
230
+ import type { ApiRoute } from "befly/types/api";
231
231
 
232
232
  export default {
233
- name: '修改密码',
234
- method: 'POST',
233
+ name: "修改密码",
234
+ method: "POST",
235
235
  auth: true,
236
236
  fields: {
237
- oldPassword: { name: '原密码', type: 'string', min: 6, max: 100 },
238
- newPassword: { name: '新密码', type: 'string', min: 6, max: 100 }
237
+ oldPassword: { name: "原密码", type: "string", min: 6, max: 100 },
238
+ newPassword: { name: "新密码", type: "string", min: 6, max: 100 }
239
239
  },
240
- required: ['oldPassword', 'newPassword'],
240
+ required: ["oldPassword", "newPassword"],
241
241
  handler: async (befly, ctx) => {
242
242
  // 获取用户密码
243
243
  const user = await befly.db.getDetail({
244
- table: 'user',
245
- columns: ['id', 'password'],
244
+ table: "user",
245
+ columns: ["id", "password"],
246
246
  where: { id: ctx.user.userId }
247
247
  });
248
248
 
249
249
  if (!user?.id) {
250
- return No('用户不存在');
250
+ return No("用户不存在");
251
251
  }
252
252
 
253
253
  // 验证原密码
254
254
  const isValid = await befly.cipher.verifyPassword(ctx.body.oldPassword, user.password);
255
255
  if (!isValid) {
256
- return No('原密码错误');
256
+ return No("原密码错误");
257
257
  }
258
258
 
259
259
  // 加密新密码
@@ -261,12 +261,12 @@ export default {
261
261
 
262
262
  // 更新密码
263
263
  await befly.db.updData({
264
- table: 'user',
264
+ table: "user",
265
265
  data: { password: hashedPassword },
266
266
  where: { id: ctx.user.userId }
267
267
  });
268
268
 
269
- return Yes('密码修改成功');
269
+ return Yes("密码修改成功");
270
270
  }
271
271
  } as ApiRoute;
272
272
  ```
@@ -276,19 +276,19 @@ export default {
276
276
  `apis/user/list.ts`:
277
277
 
278
278
  ```typescript
279
- import type { ApiRoute } from 'befly/types';
279
+ import type { ApiRoute } from "befly/types/api";
280
280
 
281
281
  export default {
282
- name: '用户列表',
283
- method: 'POST',
282
+ name: "用户列表",
283
+ method: "POST",
284
284
  auth: true,
285
- permission: 'user:list',
285
+ permission: "user:list",
286
286
  fields: {
287
- page: '@page',
288
- limit: '@limit',
289
- keyword: '@keyword',
290
- state: '@state',
291
- role: { name: '角色', type: 'string', max: 20 }
287
+ page: "@page",
288
+ limit: "@limit",
289
+ keyword: "@keyword",
290
+ state: "@state",
291
+ role: { name: "角色", type: "string", max: 20 }
292
292
  },
293
293
  handler: async (befly, ctx) => {
294
294
  const { page, limit, keyword, state, role } = ctx.body;
@@ -309,15 +309,15 @@ export default {
309
309
  }
310
310
 
311
311
  const result = await befly.db.getList({
312
- table: 'user',
313
- columns: ['id', 'email', 'nickname', 'avatar', 'phone', 'role', 'state', 'loginCount', 'lastLoginAt', 'createdAt'],
312
+ table: "user",
313
+ columns: ["id", "email", "nickname", "avatar", "phone", "role", "state", "loginCount", "lastLoginAt", "createdAt"],
314
314
  where: where,
315
315
  page: page || 1,
316
316
  limit: limit || 20,
317
- orderBy: { id: 'desc' }
317
+ orderBy: { id: "desc" }
318
318
  });
319
319
 
320
- return Yes('获取成功', result);
320
+ return Yes("获取成功", result);
321
321
  }
322
322
  } as ApiRoute;
323
323
  ```
@@ -368,34 +368,34 @@ export default {
368
368
  `apis/article/create.ts`:
369
369
 
370
370
  ```typescript
371
- import type { ApiRoute } from 'befly/types';
371
+ import type { ApiRoute } from "befly/types/api";
372
372
 
373
373
  export default {
374
- name: '发布文章',
375
- method: 'POST',
374
+ name: "发布文章",
375
+ method: "POST",
376
376
  auth: true,
377
377
  fields: {
378
- title: { name: '标题', type: 'string', min: 2, max: 200 },
379
- content: { name: '内容', type: 'text', min: 1, max: 100000 },
380
- summary: { name: '摘要', type: 'string', max: 500 },
381
- cover: { name: '封面', type: 'string', max: 500 },
382
- categoryId: { name: '分类', type: 'number', min: 0 },
383
- tags: { name: '标签', type: 'array_string', max: 10 }
378
+ title: { name: "标题", type: "string", min: 2, max: 200 },
379
+ content: { name: "内容", type: "text", min: 1, max: 100000 },
380
+ summary: { name: "摘要", type: "string", max: 500 },
381
+ cover: { name: "封面", type: "string", max: 500 },
382
+ categoryId: { name: "分类", type: "number", min: 0 },
383
+ tags: { name: "标签", type: "array_string", max: 10 }
384
384
  },
385
- required: ['title', 'content'],
385
+ required: ["title", "content"],
386
386
  handler: async (befly, ctx) => {
387
387
  const { title, content, summary, cover, categoryId, tags } = ctx.body;
388
388
 
389
389
  // 自动生成摘要
390
- const autoSummary = summary || content.replace(/<[^>]+>/g, '').slice(0, 200);
390
+ const autoSummary = summary || content.replace(/<[^>]+>/g, "").slice(0, 200);
391
391
 
392
392
  const result = await befly.db.insData({
393
- table: 'article',
393
+ table: "article",
394
394
  data: {
395
395
  title: title,
396
396
  content: content,
397
397
  summary: autoSummary,
398
- cover: cover || '',
398
+ cover: cover || "",
399
399
  categoryId: categoryId || 0,
400
400
  tags: tags || [],
401
401
  authorId: ctx.user.userId,
@@ -406,13 +406,13 @@ export default {
406
406
  // 更新分类文章数
407
407
  if (categoryId) {
408
408
  await befly.db.updData({
409
- table: 'category',
409
+ table: "category",
410
410
  data: { articleCount: { $incr: 1 } },
411
411
  where: { id: categoryId }
412
412
  });
413
413
  }
414
414
 
415
- return Yes('发布成功', { id: result.insertId });
415
+ return Yes("发布成功", { id: result.insertId });
416
416
  }
417
417
  } as ApiRoute;
418
418
  ```
@@ -422,39 +422,39 @@ export default {
422
422
  `apis/article/update.ts`:
423
423
 
424
424
  ```typescript
425
- import type { ApiRoute } from 'befly/types';
425
+ import type { ApiRoute } from "befly/types/api";
426
426
 
427
427
  export default {
428
- name: '编辑文章',
429
- method: 'POST',
428
+ name: "编辑文章",
429
+ method: "POST",
430
430
  auth: true,
431
431
  fields: {
432
- id: '@id',
433
- title: { name: '标题', type: 'string', min: 2, max: 200 },
434
- content: { name: '内容', type: 'text', min: 1, max: 100000 },
435
- summary: { name: '摘要', type: 'string', max: 500 },
436
- cover: { name: '封面', type: 'string', max: 500 },
437
- categoryId: { name: '分类', type: 'number', min: 0 },
438
- tags: { name: '标签', type: 'array_string', max: 10 }
432
+ id: "@id",
433
+ title: { name: "标题", type: "string", min: 2, max: 200 },
434
+ content: { name: "内容", type: "text", min: 1, max: 100000 },
435
+ summary: { name: "摘要", type: "string", max: 500 },
436
+ cover: { name: "封面", type: "string", max: 500 },
437
+ categoryId: { name: "分类", type: "number", min: 0 },
438
+ tags: { name: "标签", type: "array_string", max: 10 }
439
439
  },
440
- required: ['id'],
440
+ required: ["id"],
441
441
  handler: async (befly, ctx) => {
442
442
  const { id, title, content, summary, cover, categoryId, tags } = ctx.body;
443
443
 
444
444
  // 检查文章是否存在
445
445
  const article = await befly.db.getDetail({
446
- table: 'article',
447
- columns: ['id', 'authorId', 'categoryId'],
446
+ table: "article",
447
+ columns: ["id", "authorId", "categoryId"],
448
448
  where: { id: id }
449
449
  });
450
450
 
451
451
  if (!article?.id) {
452
- return No('文章不存在');
452
+ return No("文章不存在");
453
453
  }
454
454
 
455
455
  // 检查权限(只能编辑自己的文章,管理员除外)
456
- if (article.authorId !== ctx.user.userId && ctx.user.role !== 'admin') {
457
- return No('没有权限编辑此文章');
456
+ if (article.authorId !== ctx.user.userId && ctx.user.role !== "admin") {
457
+ return No("没有权限编辑此文章");
458
458
  }
459
459
 
460
460
  const updateData: Record<string, any> = {};
@@ -466,11 +466,11 @@ export default {
466
466
  if (tags !== undefined) updateData.tags = tags;
467
467
 
468
468
  if (Object.keys(updateData).length === 0) {
469
- return No('没有需要更新的字段');
469
+ return No("没有需要更新的字段");
470
470
  }
471
471
 
472
472
  await befly.db.updData({
473
- table: 'article',
473
+ table: "article",
474
474
  data: updateData,
475
475
  where: { id: id }
476
476
  });
@@ -479,21 +479,21 @@ export default {
479
479
  if (categoryId !== undefined && categoryId !== article.categoryId) {
480
480
  if (article.categoryId) {
481
481
  await befly.db.updData({
482
- table: 'category',
482
+ table: "category",
483
483
  data: { articleCount: { $decr: 1 } },
484
484
  where: { id: article.categoryId }
485
485
  });
486
486
  }
487
487
  if (categoryId) {
488
488
  await befly.db.updData({
489
- table: 'category',
489
+ table: "category",
490
490
  data: { articleCount: { $incr: 1 } },
491
491
  where: { id: categoryId }
492
492
  });
493
493
  }
494
494
  }
495
495
 
496
- return Yes('更新成功');
496
+ return Yes("更新成功");
497
497
  }
498
498
  } as ApiRoute;
499
499
  ```
@@ -503,48 +503,48 @@ export default {
503
503
  `apis/article/delete.ts`:
504
504
 
505
505
  ```typescript
506
- import type { ApiRoute } from 'befly/types';
506
+ import type { ApiRoute } from "befly/types/api";
507
507
 
508
508
  export default {
509
- name: '删除文章',
510
- method: 'POST',
509
+ name: "删除文章",
510
+ method: "POST",
511
511
  auth: true,
512
512
  fields: {
513
- id: '@id'
513
+ id: "@id"
514
514
  },
515
- required: ['id'],
515
+ required: ["id"],
516
516
  handler: async (befly, ctx) => {
517
517
  const article = await befly.db.getDetail({
518
- table: 'article',
519
- columns: ['id', 'authorId', 'categoryId'],
518
+ table: "article",
519
+ columns: ["id", "authorId", "categoryId"],
520
520
  where: { id: ctx.body.id }
521
521
  });
522
522
 
523
523
  if (!article?.id) {
524
- return No('文章不存在');
524
+ return No("文章不存在");
525
525
  }
526
526
 
527
527
  // 检查权限
528
- if (article.authorId !== ctx.user.userId && ctx.user.role !== 'admin') {
529
- return No('没有权限删除此文章');
528
+ if (article.authorId !== ctx.user.userId && ctx.user.role !== "admin") {
529
+ return No("没有权限删除此文章");
530
530
  }
531
531
 
532
532
  // 软删除
533
533
  await befly.db.delData({
534
- table: 'article',
534
+ table: "article",
535
535
  where: { id: ctx.body.id }
536
536
  });
537
537
 
538
538
  // 更新分类文章数
539
539
  if (article.categoryId) {
540
540
  await befly.db.updData({
541
- table: 'category',
541
+ table: "category",
542
542
  data: { articleCount: { $decr: 1 } },
543
543
  where: { id: article.categoryId }
544
544
  });
545
545
  }
546
546
 
547
- return Yes('删除成功');
547
+ return Yes("删除成功");
548
548
  }
549
549
  } as ApiRoute;
550
550
  ```
@@ -554,21 +554,21 @@ export default {
554
554
  `apis/article/list.ts`:
555
555
 
556
556
  ```typescript
557
- import type { ApiRoute } from 'befly/types';
557
+ import type { ApiRoute } from "befly/types/api";
558
558
 
559
559
  export default {
560
- name: '文章列表',
561
- method: 'POST',
560
+ name: "文章列表",
561
+ method: "POST",
562
562
  auth: false,
563
563
  fields: {
564
- page: '@page',
565
- limit: '@limit',
566
- keyword: '@keyword',
567
- categoryId: { name: '分类', type: 'number', min: 0 },
568
- authorId: { name: '作者', type: 'number', min: 0 },
569
- isTop: { name: '置顶', type: 'number', min: 0, max: 1 },
570
- isRecommend: { name: '推荐', type: 'number', min: 0, max: 1 },
571
- orderBy: { name: '排序', type: 'string', max: 20 }
564
+ page: "@page",
565
+ limit: "@limit",
566
+ keyword: "@keyword",
567
+ categoryId: { name: "分类", type: "number", min: 0 },
568
+ authorId: { name: "作者", type: "number", min: 0 },
569
+ isTop: { name: "置顶", type: "number", min: 0, max: 1 },
570
+ isRecommend: { name: "推荐", type: "number", min: 0, max: 1 },
571
+ orderBy: { name: "排序", type: "string", max: 20 }
572
572
  },
573
573
  handler: async (befly, ctx) => {
574
574
  const { page, limit, categoryId, authorId, keyword, isTop, isRecommend, orderBy } = ctx.body;
@@ -585,20 +585,20 @@ export default {
585
585
  }
586
586
 
587
587
  // 排序
588
- let order: Record<string, 'asc' | 'desc'> = { isTop: 'desc', publishedAt: 'desc' };
589
- if (orderBy === 'views') order = { viewCount: 'desc' };
590
- if (orderBy === 'likes') order = { likeCount: 'desc' };
588
+ let order: Record<string, "asc" | "desc"> = { isTop: "desc", publishedAt: "desc" };
589
+ if (orderBy === "views") order = { viewCount: "desc" };
590
+ if (orderBy === "likes") order = { likeCount: "desc" };
591
591
 
592
592
  const result = await befly.db.getList({
593
- table: 'article',
594
- columns: ['id', 'title', 'summary', 'cover', 'categoryId', 'tags', 'authorId', 'viewCount', 'likeCount', 'commentCount', 'isTop', 'isRecommend', 'publishedAt'],
593
+ table: "article",
594
+ columns: ["id", "title", "summary", "cover", "categoryId", "tags", "authorId", "viewCount", "likeCount", "commentCount", "isTop", "isRecommend", "publishedAt"],
595
595
  where: where,
596
596
  page: page || 1,
597
597
  limit: limit || 10,
598
598
  orderBy: order
599
599
  });
600
600
 
601
- return Yes('获取成功', result);
601
+ return Yes("获取成功", result);
602
602
  }
603
603
  } as ApiRoute;
604
604
  ```
@@ -608,37 +608,37 @@ export default {
608
608
  `apis/article/detail.ts`:
609
609
 
610
610
  ```typescript
611
- import type { ApiRoute } from 'befly/types';
611
+ import type { ApiRoute } from "befly/types/api";
612
612
 
613
613
  export default {
614
- name: '文章详情',
615
- method: 'GET',
614
+ name: "文章详情",
615
+ method: "GET",
616
616
  auth: false,
617
617
  fields: {
618
- id: '@id'
618
+ id: "@id"
619
619
  },
620
- required: ['id'],
620
+ required: ["id"],
621
621
  handler: async (befly, ctx) => {
622
622
  const article = await befly.db.getDetail({
623
- table: 'article',
623
+ table: "article",
624
624
  where: { id: ctx.body.id, state: 1 }
625
625
  });
626
626
 
627
627
  if (!article?.id) {
628
- return No('文章不存在');
628
+ return No("文章不存在");
629
629
  }
630
630
 
631
631
  // 增加浏览量
632
632
  await befly.db.updData({
633
- table: 'article',
633
+ table: "article",
634
634
  data: { viewCount: { $incr: 1 } },
635
635
  where: { id: ctx.body.id }
636
636
  });
637
637
 
638
638
  // 获取作者信息
639
639
  const author = await befly.db.getDetail({
640
- table: 'user',
641
- columns: ['id', 'nickname', 'avatar'],
640
+ table: "user",
641
+ columns: ["id", "nickname", "avatar"],
642
642
  where: { id: article.authorId }
643
643
  });
644
644
 
@@ -646,13 +646,13 @@ export default {
646
646
  let category = null;
647
647
  if (article.categoryId) {
648
648
  category = await befly.db.getDetail({
649
- table: 'category',
650
- columns: ['id', 'name', 'slug'],
649
+ table: "category",
650
+ columns: ["id", "name", "slug"],
651
651
  where: { id: article.categoryId }
652
652
  });
653
653
  }
654
654
 
655
- return Yes('获取成功', {
655
+ return Yes("获取成功", {
656
656
  ...article,
657
657
  viewCount: article.viewCount + 1,
658
658
  author: author,
@@ -671,39 +671,39 @@ export default {
671
671
  `apis/common/upload.ts`:
672
672
 
673
673
  ```typescript
674
- import { join } from 'pathe';
675
- import { existsSync, mkdirSync } from 'node:fs';
676
- import type { ApiRoute } from 'befly/types';
674
+ import { join } from "pathe";
675
+ import { existsSync, mkdirSync } from "node:fs";
676
+ import type { ApiRoute } from "befly/types/api";
677
677
 
678
678
  export default {
679
- name: '文件上传',
680
- method: 'POST',
679
+ name: "文件上传",
680
+ method: "POST",
681
681
  auth: true,
682
682
  handler: async (befly, ctx) => {
683
683
  const formData = await ctx.req.formData();
684
- const file = formData.get('file') as File | null;
684
+ const file = formData.get("file") as File | null;
685
685
 
686
686
  if (!file) {
687
- return No('请选择文件');
687
+ return No("请选择文件");
688
688
  }
689
689
 
690
690
  // 检查文件大小(10MB)
691
691
  if (file.size > 10 * 1024 * 1024) {
692
- return No('文件大小不能超过 10MB');
692
+ return No("文件大小不能超过 10MB");
693
693
  }
694
694
 
695
695
  // 检查文件类型
696
- const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
696
+ const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
697
697
  if (!allowedTypes.includes(file.type)) {
698
- return No('只支持 jpg/png/gif/webp 格式');
698
+ return No("只支持 jpg/png/gif/webp 格式");
699
699
  }
700
700
 
701
701
  // 生成文件名
702
- const ext = file.name.split('.').pop();
702
+ const ext = file.name.split(".").pop();
703
703
  const fileName = `${Date.now()}_${befly.cipher.randomString(8)}.${ext}`;
704
704
 
705
705
  // 保存目录
706
- const uploadDir = join(process.cwd(), 'uploads', new Date().toISOString().slice(0, 7));
706
+ const uploadDir = join(process.cwd(), "uploads", new Date().toISOString().slice(0, 7));
707
707
  if (!existsSync(uploadDir)) {
708
708
  mkdirSync(uploadDir, { recursive: true });
709
709
  }
@@ -716,7 +716,7 @@ export default {
716
716
  // 返回 URL
717
717
  const url = `/uploads/${new Date().toISOString().slice(0, 7)}/${fileName}`;
718
718
 
719
- return Yes('上传成功', {
719
+ return Yes("上传成功", {
720
720
  url: url,
721
721
  name: file.name,
722
722
  size: file.size,
@@ -735,34 +735,34 @@ export default {
735
735
  `apis/user/export.ts`:
736
736
 
737
737
  ```typescript
738
- import type { ApiRoute } from 'befly/types';
738
+ import type { ApiRoute } from "befly/types/api";
739
739
 
740
740
  export default {
741
- name: '导出用户',
742
- method: 'GET',
741
+ name: "导出用户",
742
+ method: "GET",
743
743
  auth: true,
744
- permission: 'user:export',
744
+ permission: "user:export",
745
745
  handler: async (befly, ctx) => {
746
746
  // 获取所有用户
747
747
  const result = await befly.db.getList({
748
- table: 'user',
749
- columns: ['id', 'email', 'nickname', 'phone', 'role', 'state', 'createdAt'],
748
+ table: "user",
749
+ columns: ["id", "email", "nickname", "phone", "role", "state", "createdAt"],
750
750
  where: { state: { $gte: 0 } },
751
751
  page: 1,
752
752
  limit: 10000
753
753
  });
754
754
 
755
755
  // 生成 CSV
756
- const headers = ['ID', '邮箱', '昵称', '手机号', '角色', '状态', '注册时间'];
757
- const rows = result.list.map((user: any) => [user.id, user.email, user.nickname, user.phone || '', user.role, user.state === 1 ? '正常' : '禁用', new Date(user.createdAt).toLocaleString()]);
756
+ const headers = ["ID", "邮箱", "昵称", "手机号", "角色", "状态", "注册时间"];
757
+ const rows = result.list.map((user: any) => [user.id, user.email, user.nickname, user.phone || "", user.role, user.state === 1 ? "正常" : "禁用", new Date(user.createdAt).toLocaleString()]);
758
758
 
759
- const csv = [headers.join(','), ...rows.map((row: any[]) => row.map((cell) => `"${cell}"`).join(','))].join('\n');
759
+ const csv = [headers.join(","), ...rows.map((row: any[]) => row.map((cell) => `"${cell}"`).join(","))].join("\n");
760
760
 
761
761
  // 返回文件
762
762
  return new Response(csv, {
763
763
  headers: {
764
- 'Content-Type': 'text/csv; charset=utf-8',
765
- 'Content-Disposition': `attachment; filename="users_${Date.now()}.csv"`
764
+ "Content-Type": "text/csv; charset=utf-8",
765
+ "Content-Disposition": `attachment; filename="users_${Date.now()}.csv"`
766
766
  }
767
767
  });
768
768
  }
@@ -788,11 +788,11 @@ if (ctx.body.phone !== undefined) updateData.phone = ctx.body.phone;
788
788
  if (ctx.body.gender !== undefined) updateData.gender = ctx.body.gender;
789
789
 
790
790
  if (Object.keys(updateData).length === 0) {
791
- return No('没有需要更新的字段');
791
+ return No("没有需要更新的字段");
792
792
  }
793
793
 
794
794
  await befly.db.updData({
795
- table: 'user',
795
+ table: "user",
796
796
  data: updateData,
797
797
  where: { id: ctx.user.userId }
798
798
  });
@@ -809,11 +809,11 @@ const data = { nickname: nickname, avatar: avatar, phone: phone, gender: gender,
809
809
  // 使用 cleanFields 检查是否有有效数据
810
810
  const cleanData = befly.tool.cleanFields(data);
811
811
  if (Object.keys(cleanData).length === 0) {
812
- return No('没有需要更新的字段');
812
+ return No("没有需要更新的字段");
813
813
  }
814
814
 
815
815
  await befly.db.updData({
816
- table: 'user',
816
+ table: "user",
817
817
  data: cleanData,
818
818
  where: { id: ctx.user.userId }
819
819
  });
@@ -834,7 +834,7 @@ const data = befly.tool.cleanFields(
834
834
  );
835
835
 
836
836
  await befly.db.updData({
837
- table: 'menu',
837
+ table: "menu",
838
838
  data: data,
839
839
  where: { id: ctx.body.id }
840
840
  });
@@ -849,8 +849,8 @@ where 条件同样支持自动过滤:
849
849
  const { keyword, state, categoryId, startDate, endDate } = ctx.body;
850
850
 
851
851
  const result = await befly.db.getList({
852
- table: 'article',
853
- columns: ['id', 'title', 'createdAt'],
852
+ table: "article",
853
+ columns: ["id", "title", "createdAt"],
854
854
  where: {
855
855
  state: state, // undefined 时忽略
856
856
  categoryId: categoryId, // undefined 时忽略