befly 3.10.18 → 3.11.1

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 (223) hide show
  1. package/README.md +83 -307
  2. package/dist/befly.config.d.ts +7 -0
  3. package/{befly.config.ts → dist/befly.config.js} +11 -36
  4. package/dist/befly.js +15621 -0
  5. package/dist/befly.min.js +21 -0
  6. package/dist/checks/checkApi.d.ts +1 -0
  7. package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
  8. package/dist/checks/checkHook.d.ts +1 -0
  9. package/dist/checks/checkHook.js +86 -0
  10. package/dist/checks/checkMenu.d.ts +7 -0
  11. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
  12. package/dist/checks/checkPlugin.d.ts +1 -0
  13. package/dist/checks/checkPlugin.js +86 -0
  14. package/dist/checks/checkTable.d.ts +6 -0
  15. package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
  16. package/dist/configs/presetFields.d.ts +4 -0
  17. package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
  18. package/dist/configs/presetRegexp.d.ts +145 -0
  19. package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
  20. package/dist/hooks/auth.d.ts +7 -0
  21. package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
  22. package/dist/hooks/cors.d.ts +11 -0
  23. package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
  24. package/dist/hooks/parser.d.ts +14 -0
  25. package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
  26. package/dist/hooks/permission.d.ts +14 -0
  27. package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
  28. package/dist/hooks/validator.d.ts +11 -0
  29. package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
  30. package/dist/index.d.ts +26 -0
  31. package/{main.ts → dist/index.js} +61 -100
  32. package/dist/lib/asyncContext.d.ts +21 -0
  33. package/dist/lib/asyncContext.js +27 -0
  34. package/dist/lib/cacheHelper.d.ts +95 -0
  35. package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
  36. package/dist/lib/cacheKeys.d.ts +23 -0
  37. package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
  38. package/dist/lib/cipher.d.ts +153 -0
  39. package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
  40. package/dist/lib/connect.d.ts +91 -0
  41. package/{lib/connect.ts → dist/lib/connect.js} +47 -88
  42. package/dist/lib/dbDialect.d.ts +87 -0
  43. package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
  44. package/dist/lib/dbHelper.d.ts +204 -0
  45. package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
  46. package/dist/lib/dbUtils.d.ts +68 -0
  47. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
  48. package/dist/lib/jwt.d.ts +13 -0
  49. package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
  50. package/dist/lib/logger.d.ts +42 -0
  51. package/dist/lib/logger.js +1144 -0
  52. package/dist/lib/redisHelper.d.ts +185 -0
  53. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
  54. package/dist/lib/sqlBuilder.d.ts +160 -0
  55. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
  56. package/dist/lib/sqlCheck.d.ts +23 -0
  57. package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
  58. package/dist/lib/validator.d.ts +45 -0
  59. package/{lib/validator.ts → dist/lib/validator.js} +44 -61
  60. package/dist/loader/loadApis.d.ts +12 -0
  61. package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
  62. package/dist/loader/loadHooks.d.ts +7 -0
  63. package/dist/loader/loadHooks.js +35 -0
  64. package/dist/loader/loadPlugins.d.ts +8 -0
  65. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
  66. package/dist/paths.d.ts +93 -0
  67. package/{paths.ts → dist/paths.js} +6 -19
  68. package/dist/plugins/cache.d.ts +16 -0
  69. package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
  70. package/dist/plugins/cipher.d.ts +12 -0
  71. package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
  72. package/dist/plugins/config.d.ts +12 -0
  73. package/dist/plugins/config.js +8 -0
  74. package/dist/plugins/db.d.ts +16 -0
  75. package/{plugins/db.ts → dist/plugins/db.js} +11 -17
  76. package/dist/plugins/jwt.d.ts +12 -0
  77. package/dist/plugins/jwt.js +12 -0
  78. package/dist/plugins/logger.d.ts +32 -0
  79. package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
  80. package/dist/plugins/redis.d.ts +16 -0
  81. package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
  82. package/dist/plugins/tool.d.ts +81 -0
  83. package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
  84. package/dist/router/api.d.ts +14 -0
  85. package/dist/router/api.js +107 -0
  86. package/dist/router/static.d.ts +9 -0
  87. package/{router/static.ts → dist/router/static.js} +20 -34
  88. package/dist/scripts/ensureDist.d.ts +1 -0
  89. package/dist/scripts/ensureDist.js +296 -0
  90. package/dist/sync/syncApi.d.ts +3 -0
  91. package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
  92. package/dist/sync/syncCache.d.ts +2 -0
  93. package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
  94. package/dist/sync/syncDev.d.ts +6 -0
  95. package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
  96. package/dist/sync/syncMenu.d.ts +14 -0
  97. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
  98. package/dist/sync/syncTable.d.ts +151 -0
  99. package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
  100. package/{types → dist/types}/api.d.ts +12 -51
  101. package/dist/types/api.js +4 -0
  102. package/{types → dist/types}/befly.d.ts +32 -227
  103. package/dist/types/befly.js +4 -0
  104. package/{types → dist/types}/cache.d.ts +7 -15
  105. package/dist/types/cache.js +4 -0
  106. package/dist/types/cipher.d.ts +27 -0
  107. package/dist/types/cipher.js +7 -0
  108. package/{types → dist/types}/common.d.ts +8 -33
  109. package/dist/types/common.js +5 -0
  110. package/{types → dist/types}/context.d.ts +3 -5
  111. package/dist/types/context.js +4 -0
  112. package/{types → dist/types}/crypto.d.ts +0 -3
  113. package/dist/types/crypto.js +4 -0
  114. package/dist/types/database.d.ts +138 -0
  115. package/dist/types/database.js +4 -0
  116. package/dist/types/hook.d.ts +17 -0
  117. package/dist/types/hook.js +6 -0
  118. package/dist/types/jwt.d.ts +75 -0
  119. package/dist/types/jwt.js +4 -0
  120. package/dist/types/logger.d.ts +59 -0
  121. package/dist/types/logger.js +6 -0
  122. package/dist/types/plugin.d.ts +16 -0
  123. package/dist/types/plugin.js +6 -0
  124. package/dist/types/redis.d.ts +71 -0
  125. package/dist/types/redis.js +4 -0
  126. package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
  127. package/dist/types/roleApisCache.js +8 -0
  128. package/dist/types/sync.d.ts +92 -0
  129. package/dist/types/sync.js +4 -0
  130. package/dist/types/table.d.ts +34 -0
  131. package/dist/types/table.js +4 -0
  132. package/dist/types/validate.d.ts +67 -0
  133. package/dist/types/validate.js +4 -0
  134. package/dist/utils/calcPerfTime.d.ts +4 -0
  135. package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
  136. package/dist/utils/convertBigIntFields.d.ts +11 -0
  137. package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
  138. package/dist/utils/cors.d.ts +8 -0
  139. package/{utils/cors.ts → dist/utils/cors.js} +1 -3
  140. package/dist/utils/disableMenusGlob.d.ts +13 -0
  141. package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
  142. package/dist/utils/fieldClear.d.ts +11 -0
  143. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
  144. package/dist/utils/getClientIp.d.ts +6 -0
  145. package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
  146. package/dist/utils/importDefault.d.ts +1 -0
  147. package/dist/utils/importDefault.js +29 -0
  148. package/dist/utils/isDirentDirectory.d.ts +2 -0
  149. package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
  150. package/dist/utils/loadMenuConfigs.d.ts +29 -0
  151. package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
  152. package/dist/utils/mergeAndConcat.d.ts +7 -0
  153. package/dist/utils/mergeAndConcat.js +72 -0
  154. package/dist/utils/processAtSymbol.d.ts +4 -0
  155. package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
  156. package/dist/utils/processInfo.d.ts +24 -0
  157. package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
  158. package/dist/utils/response.d.ts +20 -0
  159. package/{utils/response.ts → dist/utils/response.js} +28 -49
  160. package/dist/utils/scanAddons.d.ts +17 -0
  161. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
  162. package/dist/utils/scanConfig.d.ts +26 -0
  163. package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
  164. package/dist/utils/scanCoreBuiltins.d.ts +3 -0
  165. package/dist/utils/scanCoreBuiltins.js +65 -0
  166. package/dist/utils/scanFiles.d.ts +30 -0
  167. package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
  168. package/dist/utils/scanSources.d.ts +10 -0
  169. package/dist/utils/scanSources.js +46 -0
  170. package/dist/utils/sortModules.d.ts +28 -0
  171. package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
  172. package/dist/utils/util.d.ts +84 -0
  173. package/dist/utils/util.js +262 -0
  174. package/package.json +26 -34
  175. package/.gitignore +0 -0
  176. package/bunfig.toml +0 -3
  177. package/checks/checkHook.ts +0 -48
  178. package/checks/checkPlugin.ts +0 -48
  179. package/configs/presetRegexp.ts +0 -225
  180. package/docs/README.md +0 -98
  181. package/docs/api/api.md +0 -1921
  182. package/docs/guide/examples.md +0 -926
  183. package/docs/guide/quickstart.md +0 -354
  184. package/docs/hooks/auth.md +0 -38
  185. package/docs/hooks/cors.md +0 -28
  186. package/docs/hooks/hook.md +0 -838
  187. package/docs/hooks/parser.md +0 -19
  188. package/docs/hooks/rateLimit.md +0 -47
  189. package/docs/infra/redis.md +0 -628
  190. package/docs/plugins/cipher.md +0 -61
  191. package/docs/plugins/database.md +0 -189
  192. package/docs/plugins/plugin.md +0 -986
  193. package/docs/reference/addon.md +0 -510
  194. package/docs/reference/config.md +0 -573
  195. package/docs/reference/logger.md +0 -495
  196. package/docs/reference/sync.md +0 -478
  197. package/docs/reference/table.md +0 -763
  198. package/docs/reference/validator.md +0 -620
  199. package/lib/asyncContext.ts +0 -43
  200. package/lib/logger.ts +0 -811
  201. package/loader/loadHooks.ts +0 -51
  202. package/plugins/config.ts +0 -13
  203. package/plugins/jwt.ts +0 -15
  204. package/router/api.ts +0 -130
  205. package/tsconfig.json +0 -8
  206. package/types/database.d.ts +0 -541
  207. package/types/hook.d.ts +0 -25
  208. package/types/jwt.d.ts +0 -118
  209. package/types/logger.d.ts +0 -65
  210. package/types/plugin.d.ts +0 -19
  211. package/types/redis.d.ts +0 -83
  212. package/types/sync.d.ts +0 -398
  213. package/types/table.d.ts +0 -216
  214. package/types/validate.d.ts +0 -69
  215. package/utils/arrayKeysToCamel.ts +0 -18
  216. package/utils/configTypes.ts +0 -3
  217. package/utils/genShortId.ts +0 -12
  218. package/utils/importDefault.ts +0 -21
  219. package/utils/keysToCamel.ts +0 -22
  220. package/utils/keysToSnake.ts +0 -22
  221. package/utils/pickFields.ts +0 -19
  222. package/utils/scanSources.ts +0 -64
  223. package/utils/sqlLog.ts +0 -37
@@ -1,926 +0,0 @@
1
- # 实战示例
2
-
3
- > 完整的 CRUD 模块开发示例
4
-
5
- ## 目录
6
-
7
- - [用户管理模块](#用户管理模块)
8
- - [文章管理模块](#文章管理模块)
9
- - [文件上传](#文件上传)
10
- - [数据导出](#数据导出)
11
-
12
- ---
13
-
14
- ## 用户管理模块
15
-
16
- 一个完整的用户管理模块,包含注册、登录、信息获取、更新、列表查询。
17
-
18
- ### 表定义
19
-
20
- `tables/user.json`:
21
-
22
- ```json
23
- {
24
- "email": "邮箱|string|5|100||true|@email",
25
- "password": "密码|string|6|100||true",
26
- "nickname": "昵称|string|2|50|用户",
27
- "avatar": "头像|string|0|500",
28
- "phone": "手机号|string|0|20||false|@phone",
29
- "gender": "性别|number|0|2|0",
30
- "birthday": "生日|string|0|10",
31
- "bio": "简介|string|0|500",
32
- "role": "角色|string|2|20|user",
33
- "login_count": "登录次数|number|0||0",
34
- "last_login_at": "最后登录|number|0||"
35
- }
36
- ```
37
-
38
- ### 用户注册
39
-
40
- `apis/user/register.ts`:
41
-
42
- ```typescript
43
- import type { ApiRoute } from "befly/types/api";
44
-
45
- export default {
46
- name: "用户注册",
47
- method: "POST",
48
- auth: false,
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 }
53
- },
54
- required: ["email", "password"],
55
- handler: async (befly, ctx) => {
56
- // 检查邮箱是否已存在
57
- const existsRes = await befly.db.getOne({
58
- table: "user",
59
- fields: ["id"],
60
- where: { email: ctx.body.email }
61
- });
62
-
63
- if (existsRes.data?.id) {
64
- return befly.tool.No("该邮箱已被注册");
65
- }
66
-
67
- // 加密密码
68
- const hashedPassword = await befly.cipher.hashPassword(ctx.body.password);
69
-
70
- // 创建用户
71
- const idRes = await befly.db.insData({
72
- table: "user",
73
- data: {
74
- email: ctx.body.email,
75
- password: hashedPassword,
76
- nickname: ctx.body.nickname || "用户"
77
- }
78
- });
79
-
80
- return befly.tool.Yes("注册成功", { id: idRes.data });
81
- }
82
- } as ApiRoute;
83
- ```
84
-
85
- ### 用户登录
86
-
87
- `apis/user/login.ts`:
88
-
89
- ```typescript
90
- import type { ApiRoute } from "befly/types/api";
91
-
92
- export default {
93
- name: "用户登录",
94
- method: "POST",
95
- auth: false,
96
- fields: {
97
- email: { name: "邮箱", type: "string", min: 5, max: 100, regexp: "@email" },
98
- password: { name: "密码", type: "string", min: 6, max: 100 }
99
- },
100
- required: ["email", "password"],
101
- handler: async (befly, ctx) => {
102
- // 查询用户
103
- const userRes = await befly.db.getOne({
104
- table: "user",
105
- fields: ["id", "email", "password", "nickname", "avatar", "role", "state", "loginCount"],
106
- where: { email: ctx.body.email }
107
- });
108
-
109
- const user = userRes.data;
110
-
111
- if (!user?.id) {
112
- return befly.tool.No("用户不存在");
113
- }
114
-
115
- if (user.state !== 1) {
116
- return befly.tool.No("账户已被禁用");
117
- }
118
-
119
- // 验证密码
120
- const isValid = await befly.cipher.verifyPassword(ctx.body.password, user.password);
121
- if (!isValid) {
122
- return befly.tool.No("密码错误");
123
- }
124
-
125
- // 更新登录信息
126
- await befly.db.updData({
127
- table: "user",
128
- data: {
129
- loginCount: (user.loginCount || 0) + 1,
130
- lastLoginAt: Date.now()
131
- },
132
- where: { id: user.id }
133
- });
134
-
135
- // 签发令牌
136
- const token = befly.jwt.sign({
137
- id: user.id,
138
- role: user.role
139
- });
140
-
141
- return befly.tool.Yes("登录成功", {
142
- token: token,
143
- user: {
144
- id: user.id,
145
- email: user.email,
146
- nickname: user.nickname,
147
- avatar: user.avatar,
148
- role: user.role
149
- }
150
- });
151
- }
152
- } as ApiRoute;
153
- ```
154
-
155
- ### 获取用户信息
156
-
157
- `apis/user/info.ts`:
158
-
159
- ```typescript
160
- import type { ApiRoute } from "befly/types/api";
161
-
162
- export default {
163
- name: "获取用户信息",
164
- method: "GET",
165
- auth: true,
166
- handler: async (befly, ctx) => {
167
- const userRes = await befly.db.getOne({
168
- table: "user",
169
- fields: ["id", "email", "nickname", "avatar", "phone", "gender", "birthday", "bio", "role", "createdAt"],
170
- where: { id: ctx.user?.id }
171
- });
172
-
173
- const user = userRes.data;
174
-
175
- if (!user?.id) {
176
- return befly.tool.No("用户不存在");
177
- }
178
-
179
- return befly.tool.Yes("获取成功", user);
180
- }
181
- } as ApiRoute;
182
- ```
183
-
184
- ### 更新用户信息
185
-
186
- `apis/user/update.ts`:
187
-
188
- ```typescript
189
- import type { ApiRoute } from "befly/types/api";
190
-
191
- export default {
192
- name: "更新用户信息",
193
- method: "POST",
194
- auth: true,
195
- fields: {
196
- nickname: { name: "昵称", type: "string", min: 2, max: 50 },
197
- avatar: { name: "头像", type: "string", max: 500 },
198
- phone: { name: "手机号", type: "string", max: 20, regexp: "@phone" },
199
- gender: { name: "性别", type: "number", min: 0, max: 2 },
200
- birthday: { name: "生日", type: "string", max: 10 },
201
- bio: { name: "简介", type: "string", max: 500 }
202
- },
203
- handler: async (befly, ctx) => {
204
- const updateData: Record<string, any> = {};
205
-
206
- // 只更新提交的字段
207
- if (ctx.body.nickname !== undefined) updateData.nickname = ctx.body.nickname;
208
- if (ctx.body.avatar !== undefined) updateData.avatar = ctx.body.avatar;
209
- if (ctx.body.phone !== undefined) updateData.phone = ctx.body.phone;
210
- if (ctx.body.gender !== undefined) updateData.gender = ctx.body.gender;
211
- if (ctx.body.birthday !== undefined) updateData.birthday = ctx.body.birthday;
212
- if (ctx.body.bio !== undefined) updateData.bio = ctx.body.bio;
213
-
214
- if (Object.keys(updateData).length === 0) {
215
- return befly.tool.No("没有需要更新的字段");
216
- }
217
-
218
- await befly.db.updData({
219
- table: "user",
220
- data: updateData,
221
- where: { id: ctx.user?.id }
222
- });
223
-
224
- return befly.tool.Yes("更新成功");
225
- }
226
- } as ApiRoute;
227
- ```
228
-
229
- ### 修改密码
230
-
231
- `apis/user/changePassword.ts`:
232
-
233
- ```typescript
234
- import type { ApiRoute } from "befly/types/api";
235
-
236
- export default {
237
- name: "修改密码",
238
- method: "POST",
239
- auth: true,
240
- fields: {
241
- oldPassword: { name: "原密码", type: "string", min: 6, max: 100 },
242
- newPassword: { name: "新密码", type: "string", min: 6, max: 100 }
243
- },
244
- required: ["oldPassword", "newPassword"],
245
- handler: async (befly, ctx) => {
246
- // 获取用户密码
247
- const userRes = await befly.db.getOne({
248
- table: "user",
249
- fields: ["id", "password"],
250
- where: { id: ctx.user?.id }
251
- });
252
-
253
- const user = userRes.data;
254
-
255
- if (!user?.id) {
256
- return befly.tool.No("用户不存在");
257
- }
258
-
259
- // 验证原密码
260
- const isValid = await befly.cipher.verifyPassword(ctx.body.oldPassword, user.password);
261
- if (!isValid) {
262
- return befly.tool.No("原密码错误");
263
- }
264
-
265
- // 加密新密码
266
- const hashedPassword = await befly.cipher.hashPassword(ctx.body.newPassword);
267
-
268
- // 更新密码
269
- await befly.db.updData({
270
- table: "user",
271
- data: { password: hashedPassword },
272
- where: { id: ctx.user?.id }
273
- });
274
-
275
- return befly.tool.Yes("密码修改成功");
276
- }
277
- } as ApiRoute;
278
- ```
279
-
280
- ### 用户列表(管理员)
281
-
282
- `apis/user/list.ts`:
283
-
284
- ```typescript
285
- import type { ApiRoute } from "befly/types/api";
286
-
287
- export default {
288
- name: "用户列表",
289
- method: "POST",
290
- auth: true,
291
- permission: "user:list",
292
- fields: {
293
- page: "@page",
294
- limit: "@limit",
295
- keyword: "@keyword",
296
- state: "@state",
297
- role: { name: "角色", type: "string", max: 20 }
298
- },
299
- handler: async (befly, ctx) => {
300
- const { page, limit, keyword, state, role } = ctx.body;
301
-
302
- // 构建查询条件
303
- const where: Record<string, any> = {};
304
-
305
- if (keyword) {
306
- where.$or = [{ email: { $like: `%${keyword}%` } }, { nickname: { $like: `%${keyword}%` } }, { phone: { $like: `%${keyword}%` } }];
307
- }
308
-
309
- if (state !== undefined) {
310
- where.state = state;
311
- }
312
-
313
- if (role) {
314
- where.role = role;
315
- }
316
-
317
- const result = await befly.db.getList({
318
- table: "user",
319
- fields: ["id", "email", "nickname", "avatar", "phone", "role", "state", "loginCount", "lastLoginAt", "createdAt"],
320
- where: where,
321
- page: page || 1,
322
- limit: limit || 20,
323
- orderBy: ["id#DESC"]
324
- });
325
-
326
- return befly.tool.Yes("获取成功", result.data);
327
- }
328
- } as ApiRoute;
329
- ```
330
-
331
- ---
332
-
333
- ## 文章管理模块
334
-
335
- 一个完整的文章管理模块,包含发布、编辑、删除、列表、详情。
336
-
337
- ### 表定义
338
-
339
- `tables/article.json`:
340
-
341
- ```json
342
- {
343
- "title": "标题|string|2|200||true",
344
- "content": "内容|text|0|100000||true",
345
- "summary": "摘要|string|0|500",
346
- "cover": "封面|string|0|500",
347
- "category_id": "分类ID|number|0||",
348
- "tags": "标签|array_string|0|10|[]",
349
- "author_id": "作者ID|number|1||true",
350
- "view_count": "浏览量|number|0||0",
351
- "like_count": "点赞量|number|0||0",
352
- "comment_count": "评论量|number|0||0",
353
- "is_top": "是否置顶|number|0|1|0",
354
- "is_recommend": "是否推荐|number|0|1|0",
355
- "published_at": "发布时间|number|0||"
356
- }
357
- ```
358
-
359
- `tables/category.json`:
360
-
361
- ```json
362
- {
363
- "name": "分类名|string|2|50||true",
364
- "slug": "别名|string|2|50||true",
365
- "description": "描述|string|0|200",
366
- "parent_id": "父分类|number|0||0",
367
- "sort": "排序|number|0|9999|0",
368
- "article_count": "文章数|number|0||0"
369
- }
370
- ```
371
-
372
- ### 发布文章
373
-
374
- `apis/article/create.ts`:
375
-
376
- ```typescript
377
- import type { ApiRoute } from "befly/types/api";
378
-
379
- export default {
380
- name: "发布文章",
381
- method: "POST",
382
- auth: true,
383
- fields: {
384
- title: { name: "标题", type: "string", min: 2, max: 200 },
385
- content: { name: "内容", type: "text", min: 1, max: 100000 },
386
- summary: { name: "摘要", type: "string", max: 500 },
387
- cover: { name: "封面", type: "string", max: 500 },
388
- categoryId: { name: "分类", type: "number", min: 0 },
389
- tags: { name: "标签", type: "array_string", max: 10 }
390
- },
391
- required: ["title", "content"],
392
- handler: async (befly, ctx) => {
393
- const { title, content, summary, cover, categoryId, tags } = ctx.body;
394
-
395
- // 自动生成摘要
396
- const autoSummary = summary || content.replace(/<[^>]+>/g, "").slice(0, 200);
397
-
398
- const result = await befly.db.insData({
399
- table: "article",
400
- data: {
401
- title: title,
402
- content: content,
403
- summary: autoSummary,
404
- cover: cover || "",
405
- categoryId: categoryId || 0,
406
- tags: tags || [],
407
- authorId: ctx.user?.id,
408
- publishedAt: Date.now()
409
- }
410
- });
411
-
412
- // 更新分类文章数
413
- if (categoryId) {
414
- await befly.db.updData({
415
- table: "category",
416
- data: { articleCount: { $incr: 1 } },
417
- where: { id: categoryId }
418
- });
419
- }
420
-
421
- return befly.tool.Yes("发布成功", { id: result.data });
422
- }
423
- } as ApiRoute;
424
- ```
425
-
426
- ### 编辑文章
427
-
428
- `apis/article/update.ts`:
429
-
430
- ```typescript
431
- import type { ApiRoute } from "befly/types/api";
432
-
433
- export default {
434
- name: "编辑文章",
435
- method: "POST",
436
- auth: true,
437
- fields: {
438
- id: "@id",
439
- title: { name: "标题", type: "string", min: 2, max: 200 },
440
- content: { name: "内容", type: "text", min: 1, max: 100000 },
441
- summary: { name: "摘要", type: "string", max: 500 },
442
- cover: { name: "封面", type: "string", max: 500 },
443
- categoryId: { name: "分类", type: "number", min: 0 },
444
- tags: { name: "标签", type: "array_string", max: 10 }
445
- },
446
- required: ["id"],
447
- handler: async (befly, ctx) => {
448
- const { id, title, content, summary, cover, categoryId, tags } = ctx.body;
449
-
450
- // 检查文章是否存在
451
- const articleRes = await befly.db.getOne({
452
- table: "article",
453
- fields: ["id", "authorId", "categoryId"],
454
- where: { id: id }
455
- });
456
-
457
- const article = articleRes.data;
458
-
459
- if (!article?.id) {
460
- return befly.tool.No("文章不存在");
461
- }
462
-
463
- // 检查权限(只能编辑自己的文章,管理员除外)
464
- if (article.authorId !== ctx.user?.id && ctx.user?.role !== "admin") {
465
- return befly.tool.No("没有权限编辑此文章");
466
- }
467
-
468
- const updateData: Record<string, any> = {};
469
- if (title !== undefined) updateData.title = title;
470
- if (content !== undefined) updateData.content = content;
471
- if (summary !== undefined) updateData.summary = summary;
472
- if (cover !== undefined) updateData.cover = cover;
473
- if (categoryId !== undefined) updateData.categoryId = categoryId;
474
- if (tags !== undefined) updateData.tags = tags;
475
-
476
- if (Object.keys(updateData).length === 0) {
477
- return befly.tool.No("没有需要更新的字段");
478
- }
479
-
480
- await befly.db.updData({
481
- table: "article",
482
- data: updateData,
483
- where: { id: id }
484
- });
485
-
486
- // 更新分类文章数(如果分类变更)
487
- if (categoryId !== undefined && categoryId !== article.categoryId) {
488
- if (article.categoryId) {
489
- await befly.db.updData({
490
- table: "category",
491
- data: { articleCount: { $decr: 1 } },
492
- where: { id: article.categoryId }
493
- });
494
- }
495
- if (categoryId) {
496
- await befly.db.updData({
497
- table: "category",
498
- data: { articleCount: { $incr: 1 } },
499
- where: { id: categoryId }
500
- });
501
- }
502
- }
503
-
504
- return befly.tool.Yes("更新成功");
505
- }
506
- } as ApiRoute;
507
- ```
508
-
509
- ### 删除文章
510
-
511
- `apis/article/delete.ts`:
512
-
513
- ```typescript
514
- import type { ApiRoute } from "befly/types/api";
515
-
516
- export default {
517
- name: "删除文章",
518
- method: "POST",
519
- auth: true,
520
- fields: {
521
- id: "@id"
522
- },
523
- required: ["id"],
524
- handler: async (befly, ctx) => {
525
- const articleRes = await befly.db.getOne({
526
- table: "article",
527
- fields: ["id", "authorId", "categoryId"],
528
- where: { id: ctx.body.id }
529
- });
530
-
531
- const article = articleRes.data;
532
-
533
- if (!article?.id) {
534
- return befly.tool.No("文章不存在");
535
- }
536
-
537
- // 检查权限
538
- if (article.authorId !== ctx.user?.id && ctx.user?.role !== "admin") {
539
- return befly.tool.No("没有权限删除此文章");
540
- }
541
-
542
- // 软删除
543
- await befly.db.delData({
544
- table: "article",
545
- where: { id: ctx.body.id }
546
- });
547
-
548
- // 更新分类文章数
549
- if (article.categoryId) {
550
- await befly.db.updData({
551
- table: "category",
552
- data: { articleCount: { $decr: 1 } },
553
- where: { id: article.categoryId }
554
- });
555
- }
556
-
557
- return befly.tool.Yes("删除成功");
558
- }
559
- } as ApiRoute;
560
- ```
561
-
562
- ### 文章列表
563
-
564
- `apis/article/list.ts`:
565
-
566
- ```typescript
567
- import type { ApiRoute } from "befly/types/api";
568
-
569
- export default {
570
- name: "文章列表",
571
- method: "POST",
572
- auth: false,
573
- fields: {
574
- page: "@page",
575
- limit: "@limit",
576
- keyword: "@keyword",
577
- categoryId: { name: "分类", type: "number", min: 0 },
578
- authorId: { name: "作者", type: "number", min: 0 },
579
- isTop: { name: "置顶", type: "number", min: 0, max: 1 },
580
- isRecommend: { name: "推荐", type: "number", min: 0, max: 1 },
581
- orderBy: { name: "排序", type: "string", max: 20 }
582
- },
583
- handler: async (befly, ctx) => {
584
- const { page, limit, categoryId, authorId, keyword, isTop, isRecommend, orderBy } = ctx.body;
585
-
586
- const where: Record<string, any> = { state: 1 };
587
-
588
- if (categoryId) where.categoryId = categoryId;
589
- if (authorId) where.authorId = authorId;
590
- if (isTop !== undefined) where.isTop = isTop;
591
- if (isRecommend !== undefined) where.isRecommend = isRecommend;
592
-
593
- if (keyword) {
594
- where.$or = [{ title: { $like: `%${keyword}%` } }, { summary: { $like: `%${keyword}%` } }];
595
- }
596
-
597
- // 排序
598
- let orderByList: string[] = ["isTop#DESC", "publishedAt#DESC"];
599
- if (orderBy === "views") orderByList = ["viewCount#DESC"];
600
- if (orderBy === "likes") orderByList = ["likeCount#DESC"];
601
-
602
- const result = await befly.db.getList({
603
- table: "article",
604
- fields: ["id", "title", "summary", "cover", "categoryId", "tags", "authorId", "viewCount", "likeCount", "commentCount", "isTop", "isRecommend", "publishedAt"],
605
- where: where,
606
- page: page || 1,
607
- limit: limit || 10,
608
- orderBy: orderByList
609
- });
610
-
611
- return befly.tool.Yes("获取成功", result.data);
612
- }
613
- } as ApiRoute;
614
- ```
615
-
616
- ### 文章详情
617
-
618
- `apis/article/detail.ts`:
619
-
620
- ```typescript
621
- import type { ApiRoute } from "befly/types/api";
622
-
623
- export default {
624
- name: "文章详情",
625
- method: "GET",
626
- auth: false,
627
- fields: {
628
- id: "@id"
629
- },
630
- required: ["id"],
631
- handler: async (befly, ctx) => {
632
- const articleRes = await befly.db.getOne({
633
- table: "article",
634
- where: { id: ctx.body.id, state: 1 }
635
- });
636
-
637
- const article = articleRes.data;
638
-
639
- if (!article?.id) {
640
- return befly.tool.No("文章不存在");
641
- }
642
-
643
- // 增加浏览量
644
- await befly.db.updData({
645
- table: "article",
646
- data: { viewCount: { $incr: 1 } },
647
- where: { id: ctx.body.id }
648
- });
649
-
650
- // 获取作者信息
651
- const authorRes = await befly.db.getOne({
652
- table: "user",
653
- fields: ["id", "nickname", "avatar"],
654
- where: { id: article.authorId }
655
- });
656
-
657
- const author = authorRes.data;
658
-
659
- // 获取分类信息
660
- let category = null;
661
- if (article.categoryId) {
662
- const categoryRes = await befly.db.getOne({
663
- table: "category",
664
- fields: ["id", "name", "slug"],
665
- where: { id: article.categoryId }
666
- });
667
-
668
- category = categoryRes.data;
669
- }
670
-
671
- return befly.tool.Yes("获取成功", {
672
- id: article.id,
673
- title: article.title,
674
- content: article.content,
675
- summary: article.summary,
676
- cover: article.cover,
677
- categoryId: article.categoryId,
678
- tags: article.tags,
679
- authorId: article.authorId,
680
- viewCount: (article.viewCount || 0) + 1,
681
- likeCount: article.likeCount,
682
- commentCount: article.commentCount,
683
- isTop: article.isTop,
684
- isRecommend: article.isRecommend,
685
- publishedAt: article.publishedAt,
686
- author: author,
687
- category: category
688
- });
689
- }
690
- } as ApiRoute;
691
- ```
692
-
693
- ---
694
-
695
- ## 文件上传
696
-
697
- ### 单文件上传
698
-
699
- `apis/common/upload.ts`:
700
-
701
- ```typescript
702
- import { join } from "pathe";
703
- import { existsSync, mkdirSync } from "node:fs";
704
- import type { ApiRoute } from "befly/types/api";
705
-
706
- export default {
707
- name: "文件上传",
708
- method: "POST",
709
- auth: true,
710
- handler: async (befly, ctx) => {
711
- const formData = await ctx.req.formData();
712
- const file = formData.get("file") as File | null;
713
-
714
- if (!file) {
715
- return befly.tool.No("请选择文件");
716
- }
717
-
718
- // 检查文件大小(10MB)
719
- if (file.size > 10 * 1024 * 1024) {
720
- return befly.tool.No("文件大小不能超过 10MB");
721
- }
722
-
723
- // 检查文件类型
724
- const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
725
- if (!allowedTypes.includes(file.type)) {
726
- return befly.tool.No("只支持 jpg/png/gif/webp 格式");
727
- }
728
-
729
- // 生成文件名
730
- const ext = file.name.split(".").pop();
731
- const fileName = `${Date.now()}_${befly.cipher.randomString(8)}.${ext}`;
732
-
733
- // 保存目录
734
- const uploadDir = join(process.cwd(), "uploads", new Date().toISOString().slice(0, 7));
735
- if (!existsSync(uploadDir)) {
736
- mkdirSync(uploadDir, { recursive: true });
737
- }
738
-
739
- // 保存文件
740
- const filePath = join(uploadDir, fileName);
741
- const buffer = await file.arrayBuffer();
742
- await Bun.write(filePath, buffer);
743
-
744
- // 返回 URL
745
- const url = `/uploads/${new Date().toISOString().slice(0, 7)}/${fileName}`;
746
-
747
- return befly.tool.Yes("上传成功", {
748
- url: url,
749
- name: file.name,
750
- size: file.size,
751
- type: file.type
752
- });
753
- }
754
- } as ApiRoute;
755
- ```
756
-
757
- ---
758
-
759
- ## 数据导出
760
-
761
- ### 导出为 CSV
762
-
763
- `apis/user/export.ts`:
764
-
765
- ```typescript
766
- import type { ApiRoute } from "befly/types/api";
767
-
768
- export default {
769
- name: "导出用户",
770
- method: "GET",
771
- auth: true,
772
- permission: "user:export",
773
- handler: async (befly, ctx) => {
774
- // 获取所有用户
775
- const result = await befly.db.getList({
776
- table: "user",
777
- fields: ["id", "email", "nickname", "phone", "role", "state", "createdAt"],
778
- where: { state: { $gte: 0 } },
779
- page: 1,
780
- limit: 10000
781
- });
782
-
783
- // 生成 CSV
784
- const headers = ["ID", "邮箱", "昵称", "手机号", "角色", "状态", "注册时间"];
785
- const rows = result.data.lists.map((user: any) => [user.id, user.email, user.nickname, user.phone || "", user.role, user.state === 1 ? "正常" : "禁用", new Date(user.createdAt).toLocaleString()]);
786
-
787
- const csv = [headers.join(","), ...rows.map((row: any[]) => row.map((cell) => `"${cell}"`).join(","))].join("\n");
788
-
789
- // 返回文件
790
- return new Response(csv, {
791
- headers: {
792
- "Content-Type": "text/csv; charset=utf-8",
793
- "Content-Disposition": `attachment; filename="users_${Date.now()}.csv"`
794
- }
795
- });
796
- }
797
- } as ApiRoute;
798
- ```
799
-
800
- ---
801
-
802
- ## 代码优化技巧
803
-
804
- ### 利用自动过滤简化更新操作
805
-
806
- 数据库操作会**自动过滤 null 和 undefined 值**,因此可以大幅简化代码。
807
-
808
- #### 传统写法(繁琐)
809
-
810
- ```typescript
811
- // ❌ 手动检查每个字段
812
- const updateData: Record<string, any> = {};
813
- if (ctx.body.nickname !== undefined) updateData.nickname = ctx.body.nickname;
814
- if (ctx.body.avatar !== undefined) updateData.avatar = ctx.body.avatar;
815
- if (ctx.body.phone !== undefined) updateData.phone = ctx.body.phone;
816
- if (ctx.body.gender !== undefined) updateData.gender = ctx.body.gender;
817
-
818
- if (Object.keys(updateData).length === 0) {
819
- return No("没有需要更新的字段");
820
- }
821
-
822
- await befly.db.updData({
823
- table: "user",
824
- data: updateData,
825
- where: { id: ctx.user?.id }
826
- });
827
- ```
828
-
829
- #### 优化写法(简洁)
830
-
831
- ```typescript
832
- import { fieldClear } from "befly/utils/fieldClear";
833
-
834
- // ✅ 直接传入,undefined 值自动过滤
835
- const data = {
836
- nickname: ctx.body.nickname,
837
- avatar: ctx.body.avatar,
838
- phone: ctx.body.phone,
839
- gender: ctx.body.gender,
840
- birthday: ctx.body.birthday,
841
- bio: ctx.body.bio
842
- };
843
-
844
- // 使用 fieldClear 检查是否有有效数据
845
- const cleanData = fieldClear(data, { excludeValues: [null, undefined] });
846
- if (Object.keys(cleanData).length === 0) {
847
- return befly.tool.No("没有需要更新的字段");
848
- }
849
-
850
- await befly.db.updData({
851
- table: "user",
852
- data: cleanData,
853
- where: { id: ctx.user?.id }
854
- });
855
- ```
856
-
857
- ### 使用 fieldClear 进行精细控制
858
-
859
- 当需要保留特定值(如 0、空字符串)时,使用 `fieldClear` 的高级参数:
860
-
861
- ```typescript
862
- const { nickname, sort, state, remark } = ctx.body;
863
-
864
- // 保留 0 值(sort 和 state 允许为 0)
865
- const data = fieldClear(
866
- { nickname: nickname, sort: sort, state: state, remark: remark },
867
- { excludeValues: [null, undefined], keepMap: { sort: 0, state: 0 } }
868
- );
869
-
870
- await befly.db.updData({
871
- table: "menu",
872
- data: data,
873
- where: { id: ctx.body.id }
874
- });
875
- ```
876
-
877
- ### 查询条件的自动过滤
878
-
879
- where 条件同样支持自动过滤:
880
-
881
- ```typescript
882
- // ✅ 可选筛选条件,undefined 自动忽略
883
- const { keyword, state, categoryId, startDate, endDate } = ctx.body;
884
-
885
- const result = await befly.db.getList({
886
- table: "article",
887
- fields: ["id", "title", "createdAt"],
888
- where: {
889
- state: state, // undefined 时忽略
890
- categoryId: categoryId, // undefined 时忽略
891
- title: keyword ? { $like: `%${keyword}%` } : undefined, // 无关键词时忽略
892
- createdAt: startDate && endDate ? { $gte: startDate, $lte: endDate } : undefined
893
- },
894
- page: ctx.body.page || 1,
895
- limit: ctx.body.limit || 20
896
- });
897
- ```
898
-
899
- ---
900
-
901
- ## 完整目录结构
902
-
903
- ```
904
- apis/
905
- ├── user/
906
- │ ├── register.ts # 注册
907
- │ ├── login.ts # 登录
908
- │ ├── info.ts # 获取信息
909
- │ ├── update.ts # 更新信息
910
- │ ├── changePassword.ts # 修改密码
911
- │ ├── list.ts # 用户列表
912
- │ └── export.ts # 导出用户
913
- ├── article/
914
- │ ├── create.ts # 发布文章
915
- │ ├── update.ts # 编辑文章
916
- │ ├── delete.ts # 删除文章
917
- │ ├── list.ts # 文章列表
918
- │ └── detail.ts # 文章详情
919
- └── common/
920
- └── upload.ts # 文件上传
921
-
922
- tables/
923
- ├── user.json # 用户表
924
- ├── article.json # 文章表
925
- └── category.json # 分类表
926
- ```