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.
- package/README.md +38 -39
- package/befly.config.ts +62 -40
- package/checks/checkApi.ts +16 -16
- package/checks/checkApp.ts +19 -25
- package/checks/checkTable.ts +42 -42
- package/docs/README.md +42 -35
- package/docs/{api.md → api/api.md} +225 -235
- package/docs/cipher.md +71 -69
- package/docs/database.md +155 -153
- package/docs/{examples.md → guide/examples.md} +181 -181
- package/docs/guide/quickstart.md +331 -0
- package/docs/hooks/auth.md +38 -0
- package/docs/hooks/cors.md +28 -0
- package/docs/{hook.md → hooks/hook.md} +140 -57
- package/docs/hooks/parser.md +19 -0
- package/docs/hooks/rateLimit.md +47 -0
- package/docs/{redis.md → infra/redis.md} +84 -93
- package/docs/plugins/cipher.md +61 -0
- package/docs/plugins/database.md +128 -0
- package/docs/{plugin.md → plugins/plugin.md} +83 -81
- package/docs/quickstart.md +26 -26
- package/docs/{addon.md → reference/addon.md} +46 -46
- package/docs/{config.md → reference/config.md} +32 -80
- package/docs/{logger.md → reference/logger.md} +52 -52
- package/docs/{sync.md → reference/sync.md} +32 -35
- package/docs/{table.md → reference/table.md} +7 -7
- package/docs/{validator.md → reference/validator.md} +57 -57
- package/hooks/auth.ts +8 -4
- package/hooks/cors.ts +13 -13
- package/hooks/parser.ts +37 -17
- package/hooks/permission.ts +26 -14
- package/hooks/rateLimit.ts +276 -0
- package/hooks/validator.ts +15 -7
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -81
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +211 -109
- package/lib/jwt.ts +16 -16
- package/lib/logger.ts +610 -19
- package/lib/redisHelper.ts +185 -44
- package/lib/sqlBuilder.ts +90 -91
- package/lib/validator.ts +59 -39
- package/loader/loadApis.ts +53 -47
- package/loader/loadHooks.ts +40 -14
- package/loader/loadPlugins.ts +16 -17
- package/main.ts +57 -47
- package/package.json +47 -45
- package/paths.ts +15 -14
- package/plugins/cache.ts +5 -4
- package/plugins/cipher.ts +3 -3
- package/plugins/config.ts +2 -2
- package/plugins/db.ts +9 -9
- package/plugins/jwt.ts +3 -3
- package/plugins/logger.ts +8 -12
- package/plugins/redis.ts +8 -8
- package/plugins/tool.ts +6 -6
- package/router/api.ts +85 -56
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +12 -12
- package/sync/syncApi.ts +55 -54
- package/sync/syncDb/apply.ts +20 -19
- package/sync/syncDb/constants.ts +25 -23
- package/sync/syncDb/ddl.ts +35 -36
- package/sync/syncDb/helpers.ts +6 -9
- package/sync/syncDb/schema.ts +10 -9
- package/sync/syncDb/sqlite.ts +7 -8
- package/sync/syncDb/table.ts +37 -35
- package/sync/syncDb/tableCreate.ts +21 -20
- package/sync/syncDb/types.ts +23 -20
- package/sync/syncDb/version.ts +10 -10
- package/sync/syncDb.ts +43 -36
- package/sync/syncDev.ts +74 -66
- package/sync/syncMenu.ts +190 -57
- package/tests/api-integration-array-number.test.ts +282 -0
- package/tests/befly-config-env.test.ts +78 -0
- package/tests/cacheHelper.test.ts +135 -104
- package/tests/cacheKeys.test.ts +41 -0
- package/tests/cipher.test.ts +90 -89
- package/tests/dbHelper-advanced.test.ts +140 -134
- package/tests/dbHelper-all-array-types.test.ts +316 -0
- package/tests/dbHelper-array-serialization.test.ts +258 -0
- package/tests/dbHelper-columns.test.ts +56 -55
- package/tests/dbHelper-execute.test.ts +45 -44
- package/tests/dbHelper-joins.test.ts +124 -119
- package/tests/fields-redis-cache.test.ts +29 -27
- package/tests/fields-validate.test.ts +38 -38
- package/tests/getClientIp.test.ts +54 -0
- package/tests/integration.test.ts +69 -67
- package/tests/jwt.test.ts +27 -26
- package/tests/logger.test.ts +267 -34
- package/tests/rateLimit-hook.test.ts +477 -0
- package/tests/redisHelper.test.ts +187 -188
- package/tests/redisKeys.test.ts +6 -73
- package/tests/scanConfig.test.ts +144 -0
- package/tests/sqlBuilder-advanced.test.ts +217 -215
- package/tests/sqlBuilder.test.ts +92 -91
- package/tests/sync-connection.test.ts +29 -29
- package/tests/syncDb-apply.test.ts +97 -96
- package/tests/syncDb-array-number.test.ts +160 -0
- package/tests/syncDb-constants.test.ts +48 -47
- package/tests/syncDb-ddl.test.ts +99 -98
- package/tests/syncDb-helpers.test.ts +29 -28
- package/tests/syncDb-schema.test.ts +61 -60
- package/tests/syncDb-types.test.ts +60 -59
- package/tests/syncMenu-paths.test.ts +68 -0
- package/tests/util.test.ts +42 -41
- package/tests/validator-array-number.test.ts +310 -0
- package/tests/validator-default.test.ts +373 -0
- package/tests/validator.test.ts +271 -266
- package/tsconfig.json +4 -5
- package/types/api.d.ts +7 -12
- package/types/befly.d.ts +60 -13
- package/types/cache.d.ts +8 -4
- package/types/common.d.ts +17 -9
- package/types/context.d.ts +2 -2
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +19 -19
- package/types/hook.d.ts +2 -2
- package/types/jwt.d.ts +118 -0
- package/types/logger.d.ts +30 -0
- package/types/plugin.d.ts +4 -4
- package/types/redis.d.ts +7 -3
- package/types/roleApisCache.ts +23 -0
- package/types/sync.d.ts +10 -10
- package/types/table.d.ts +50 -9
- package/types/validate.d.ts +69 -0
- package/utils/addonHelper.ts +90 -0
- package/utils/arrayKeysToCamel.ts +18 -0
- package/utils/calcPerfTime.ts +13 -0
- package/utils/configTypes.ts +3 -0
- package/utils/cors.ts +19 -0
- package/utils/fieldClear.ts +75 -0
- package/utils/genShortId.ts +12 -0
- package/utils/getClientIp.ts +45 -0
- package/utils/keysToCamel.ts +22 -0
- package/utils/keysToSnake.ts +22 -0
- package/utils/modules.ts +98 -0
- package/utils/pickFields.ts +19 -0
- package/utils/process.ts +56 -0
- package/utils/regex.ts +225 -0
- package/utils/response.ts +115 -0
- package/utils/route.ts +23 -0
- package/utils/scanConfig.ts +142 -0
- package/utils/scanFiles.ts +48 -0
- package/.prettierignore +0 -2
- package/.prettierrc +0 -12
- package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
- package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
- package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
- package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
- package/hooks/requestLogger.ts +0 -84
- package/types/index.ts +0 -24
- package/util.ts +0 -283
|
@@ -123,24 +123,23 @@ addonAdmin/apis/
|
|
|
123
123
|
### 基础结构
|
|
124
124
|
|
|
125
125
|
```typescript
|
|
126
|
-
import type { ApiRoute } from
|
|
126
|
+
import type { ApiRoute } from "befly/types/api";
|
|
127
127
|
|
|
128
128
|
export default {
|
|
129
129
|
// 必填字段
|
|
130
|
-
name:
|
|
130
|
+
name: "接口名称", // 接口描述,用于日志和文档
|
|
131
131
|
handler: async (befly, ctx) => {
|
|
132
132
|
// 处理逻辑
|
|
133
|
-
return befly.tool.Yes(
|
|
133
|
+
return befly.tool.Yes("成功", { data });
|
|
134
134
|
},
|
|
135
135
|
|
|
136
136
|
// 可选字段
|
|
137
|
-
method:
|
|
137
|
+
method: "POST", // HTTP 方法,默认 POST
|
|
138
138
|
auth: true, // 是否需要认证,默认 true
|
|
139
139
|
fields: {}, // 字段定义(验证规则)
|
|
140
140
|
required: [], // 必填字段列表
|
|
141
|
-
rawBody: false
|
|
142
|
-
|
|
143
|
-
rateLimit: undefined // 限流配置
|
|
141
|
+
rawBody: false // 是否保留原始请求体
|
|
142
|
+
// 缓存/限流:已统一迁移为 Hook 能力(见 hook 文档/配置),不再挂载在接口定义上
|
|
144
143
|
} as ApiRoute;
|
|
145
144
|
```
|
|
146
145
|
|
|
@@ -155,7 +154,7 @@ interface ApiRoute<T = any, R = any> {
|
|
|
155
154
|
handler: ApiHandler<T, R>;
|
|
156
155
|
|
|
157
156
|
/** HTTP 方法(可选,默认 POST,支持逗号分隔多个方法) */
|
|
158
|
-
method?:
|
|
157
|
+
method?: "GET" | "POST" | "GET,POST" | "POST,GET";
|
|
159
158
|
|
|
160
159
|
/** 认证类型(可选,默认 true)
|
|
161
160
|
* - true: 需要登录
|
|
@@ -175,12 +174,6 @@ interface ApiRoute<T = any, R = any> {
|
|
|
175
174
|
*/
|
|
176
175
|
rawBody?: boolean;
|
|
177
176
|
|
|
178
|
-
/** 缓存配置(可选,单位:秒) */
|
|
179
|
-
cache?: number;
|
|
180
|
-
|
|
181
|
-
/** 限流配置(可选,格式:次数/秒,如 "10/60" 表示 60秒内10次) */
|
|
182
|
-
rateLimit?: string;
|
|
183
|
-
|
|
184
177
|
/** 路由路径(运行时生成,无需手动设置) */
|
|
185
178
|
route?: string;
|
|
186
179
|
}
|
|
@@ -318,26 +311,26 @@ befly.tool.Raw(ctx: RequestContext, data: Record<string, any> | string, options?
|
|
|
318
311
|
|
|
319
312
|
```typescript
|
|
320
313
|
// JSON 响应(自动)
|
|
321
|
-
return befly.tool.Raw(ctx, { code:
|
|
314
|
+
return befly.tool.Raw(ctx, { code: "SUCCESS", message: "成功" });
|
|
322
315
|
|
|
323
316
|
// 纯文本响应(自动)- 支付宝回调
|
|
324
|
-
return befly.tool.Raw(ctx,
|
|
317
|
+
return befly.tool.Raw(ctx, "success");
|
|
325
318
|
|
|
326
319
|
// XML 响应(自动判断)
|
|
327
|
-
return befly.tool.Raw(ctx,
|
|
320
|
+
return befly.tool.Raw(ctx, "<xml><return_code>SUCCESS</return_code></xml>");
|
|
328
321
|
|
|
329
322
|
// XML 响应(手动指定)
|
|
330
|
-
return befly.tool.Raw(ctx, xmlString, { contentType:
|
|
323
|
+
return befly.tool.Raw(ctx, xmlString, { contentType: "application/xml" });
|
|
331
324
|
|
|
332
325
|
// 自定义状态码
|
|
333
|
-
return befly.tool.Raw(ctx, { error:
|
|
326
|
+
return befly.tool.Raw(ctx, { error: "Not Found" }, { status: 404 });
|
|
334
327
|
|
|
335
328
|
// 自定义响应头
|
|
336
329
|
return befly.tool.Raw(
|
|
337
330
|
ctx,
|
|
338
|
-
{ code:
|
|
331
|
+
{ code: "SUCCESS" },
|
|
339
332
|
{
|
|
340
|
-
headers: {
|
|
333
|
+
headers: { "X-Custom-Header": "value" }
|
|
341
334
|
}
|
|
342
335
|
);
|
|
343
336
|
```
|
|
@@ -347,33 +340,33 @@ return befly.tool.Raw(
|
|
|
347
340
|
```typescript
|
|
348
341
|
// 微信支付回调
|
|
349
342
|
export default {
|
|
350
|
-
name:
|
|
343
|
+
name: "微信支付回调",
|
|
351
344
|
auth: false,
|
|
352
345
|
rawBody: true,
|
|
353
346
|
handler: async (befly, ctx) => {
|
|
354
347
|
if (!befly.weixin) {
|
|
355
|
-
return befly.tool.Raw(ctx, { code:
|
|
348
|
+
return befly.tool.Raw(ctx, { code: "SYSTEM_ERROR", message: "weixin 插件未配置" });
|
|
356
349
|
}
|
|
357
350
|
|
|
358
351
|
// 处理成功
|
|
359
|
-
return befly.tool.Raw(ctx, { code:
|
|
352
|
+
return befly.tool.Raw(ctx, { code: "SUCCESS", message: "" });
|
|
360
353
|
}
|
|
361
354
|
};
|
|
362
355
|
|
|
363
356
|
// 支付宝回调
|
|
364
357
|
export default {
|
|
365
|
-
name:
|
|
358
|
+
name: "支付宝回调",
|
|
366
359
|
auth: false,
|
|
367
360
|
rawBody: true,
|
|
368
361
|
handler: async (befly, ctx) => {
|
|
369
362
|
// 支付宝要求返回纯文本 "success"
|
|
370
|
-
return befly.tool.Raw(ctx,
|
|
363
|
+
return befly.tool.Raw(ctx, "success");
|
|
371
364
|
}
|
|
372
365
|
};
|
|
373
366
|
|
|
374
367
|
// 微信公众号 XML 回调
|
|
375
368
|
export default {
|
|
376
|
-
name:
|
|
369
|
+
name: "微信公众号回调",
|
|
377
370
|
auth: false,
|
|
378
371
|
rawBody: true,
|
|
379
372
|
handler: async (befly, ctx) => {
|
|
@@ -397,10 +390,10 @@ export default {
|
|
|
397
390
|
在 Hook 中使用,用于提前拦截请求:
|
|
398
391
|
|
|
399
392
|
```typescript
|
|
400
|
-
import { ErrorResponse } from
|
|
393
|
+
import { ErrorResponse } from "befly/utils/response";
|
|
401
394
|
|
|
402
395
|
// 在 Hook 中使用
|
|
403
|
-
ctx.response = ErrorResponse(ctx,
|
|
396
|
+
ctx.response = ErrorResponse(ctx, "未授权", 1, null);
|
|
404
397
|
```
|
|
405
398
|
|
|
406
399
|
### FinalResponse - 最终响应
|
|
@@ -419,33 +412,33 @@ ctx.response = ErrorResponse(ctx, '未授权', 1, null);
|
|
|
419
412
|
|
|
420
413
|
```typescript
|
|
421
414
|
const PRESET_FIELDS = {
|
|
422
|
-
|
|
423
|
-
name:
|
|
424
|
-
type:
|
|
415
|
+
"@id": {
|
|
416
|
+
name: "ID",
|
|
417
|
+
type: "number",
|
|
425
418
|
min: 1,
|
|
426
419
|
max: null
|
|
427
420
|
},
|
|
428
|
-
|
|
429
|
-
name:
|
|
430
|
-
type:
|
|
421
|
+
"@page": {
|
|
422
|
+
name: "页码",
|
|
423
|
+
type: "number",
|
|
431
424
|
min: 1,
|
|
432
425
|
max: 9999
|
|
433
426
|
},
|
|
434
|
-
|
|
435
|
-
name:
|
|
436
|
-
type:
|
|
427
|
+
"@limit": {
|
|
428
|
+
name: "每页数量",
|
|
429
|
+
type: "number",
|
|
437
430
|
min: 1,
|
|
438
431
|
max: 100
|
|
439
432
|
},
|
|
440
|
-
|
|
441
|
-
name:
|
|
442
|
-
type:
|
|
433
|
+
"@keyword": {
|
|
434
|
+
name: "关键词",
|
|
435
|
+
type: "string",
|
|
443
436
|
min: 1,
|
|
444
437
|
max: 50
|
|
445
438
|
},
|
|
446
|
-
|
|
447
|
-
name:
|
|
448
|
-
type:
|
|
439
|
+
"@state": {
|
|
440
|
+
name: "状态",
|
|
441
|
+
type: "number",
|
|
449
442
|
min: 0,
|
|
450
443
|
max: 2
|
|
451
444
|
}
|
|
@@ -497,14 +490,14 @@ fields: {
|
|
|
497
490
|
```typescript
|
|
498
491
|
// apis/article/list.ts
|
|
499
492
|
export default {
|
|
500
|
-
name:
|
|
493
|
+
name: "文章列表",
|
|
501
494
|
auth: true,
|
|
502
495
|
fields: {
|
|
503
|
-
page:
|
|
504
|
-
limit:
|
|
505
|
-
keyword:
|
|
506
|
-
state:
|
|
507
|
-
categoryId: { name:
|
|
496
|
+
page: "@page",
|
|
497
|
+
limit: "@limit",
|
|
498
|
+
keyword: "@keyword",
|
|
499
|
+
state: "@state",
|
|
500
|
+
categoryId: { name: "分类ID", type: "number", min: 0 }
|
|
508
501
|
},
|
|
509
502
|
handler: async (befly, ctx) => {
|
|
510
503
|
const { page, limit, keyword, categoryId } = ctx.body;
|
|
@@ -514,15 +507,15 @@ export default {
|
|
|
514
507
|
if (keyword) where.title = { $like: `%${keyword}%` };
|
|
515
508
|
|
|
516
509
|
const result = await befly.db.getList({
|
|
517
|
-
table:
|
|
518
|
-
columns: [
|
|
510
|
+
table: "article",
|
|
511
|
+
columns: ["id", "title", "summary", "createdAt"],
|
|
519
512
|
where: where,
|
|
520
513
|
page: page || 1,
|
|
521
514
|
limit: limit || 10,
|
|
522
|
-
orderBy: { id:
|
|
515
|
+
orderBy: { id: "desc" }
|
|
523
516
|
});
|
|
524
517
|
|
|
525
|
-
return befly.tool.Yes(
|
|
518
|
+
return befly.tool.Yes("获取成功", result);
|
|
526
519
|
}
|
|
527
520
|
} as ApiRoute;
|
|
528
521
|
```
|
|
@@ -532,23 +525,23 @@ export default {
|
|
|
532
525
|
```typescript
|
|
533
526
|
// apis/article/detail.ts
|
|
534
527
|
export default {
|
|
535
|
-
name:
|
|
528
|
+
name: "文章详情",
|
|
536
529
|
auth: false,
|
|
537
530
|
fields: {
|
|
538
|
-
id:
|
|
531
|
+
id: "@id"
|
|
539
532
|
},
|
|
540
|
-
required: [
|
|
533
|
+
required: ["id"],
|
|
541
534
|
handler: async (befly, ctx) => {
|
|
542
535
|
const article = await befly.db.getDetail({
|
|
543
|
-
table:
|
|
536
|
+
table: "article",
|
|
544
537
|
where: { id: ctx.body.id, state: 1 }
|
|
545
538
|
});
|
|
546
539
|
|
|
547
540
|
if (!article?.id) {
|
|
548
|
-
return befly.tool.No(
|
|
541
|
+
return befly.tool.No("文章不存在");
|
|
549
542
|
}
|
|
550
543
|
|
|
551
|
-
return befly.tool.Yes(
|
|
544
|
+
return befly.tool.Yes("获取成功", article);
|
|
552
545
|
}
|
|
553
546
|
} as ApiRoute;
|
|
554
547
|
```
|
|
@@ -556,19 +549,19 @@ export default {
|
|
|
556
549
|
```typescript
|
|
557
550
|
// apis/article/delete.ts
|
|
558
551
|
export default {
|
|
559
|
-
name:
|
|
552
|
+
name: "删除文章",
|
|
560
553
|
auth: true,
|
|
561
554
|
fields: {
|
|
562
|
-
id:
|
|
555
|
+
id: "@id"
|
|
563
556
|
},
|
|
564
|
-
required: [
|
|
557
|
+
required: ["id"],
|
|
565
558
|
handler: async (befly, ctx) => {
|
|
566
559
|
await befly.db.delData({
|
|
567
|
-
table:
|
|
560
|
+
table: "article",
|
|
568
561
|
where: { id: ctx.body.id }
|
|
569
562
|
});
|
|
570
563
|
|
|
571
|
-
return befly.tool.Yes(
|
|
564
|
+
return befly.tool.Yes("删除成功");
|
|
572
565
|
}
|
|
573
566
|
} as ApiRoute;
|
|
574
567
|
```
|
|
@@ -579,20 +572,20 @@ export default {
|
|
|
579
572
|
|
|
580
573
|
```typescript
|
|
581
574
|
export default {
|
|
582
|
-
name:
|
|
575
|
+
name: "大数据列表",
|
|
583
576
|
fields: {
|
|
584
|
-
page:
|
|
577
|
+
page: "@page",
|
|
585
578
|
// 覆盖默认的 @limit,允许更大的分页
|
|
586
579
|
limit: {
|
|
587
|
-
name:
|
|
588
|
-
type:
|
|
580
|
+
name: "每页数量",
|
|
581
|
+
type: "number",
|
|
589
582
|
min: 1,
|
|
590
583
|
max: 500 // 修改最大值为 500
|
|
591
584
|
}
|
|
592
585
|
},
|
|
593
586
|
handler: async (befly, ctx) => {
|
|
594
587
|
// ctx.body.limit 最大可以是 500
|
|
595
|
-
return befly.tool.Yes(
|
|
588
|
+
return befly.tool.Yes("获取成功");
|
|
596
589
|
}
|
|
597
590
|
} as ApiRoute;
|
|
598
591
|
```
|
|
@@ -723,8 +716,8 @@ export default {
|
|
|
723
716
|
```typescript
|
|
724
717
|
// API 定义
|
|
725
718
|
export default {
|
|
726
|
-
name:
|
|
727
|
-
method:
|
|
719
|
+
name: "微信支付回调",
|
|
720
|
+
method: "POST",
|
|
728
721
|
auth: false,
|
|
729
722
|
rawBody: true, // 跳过解析,保留原始请求
|
|
730
723
|
handler: async (befly, ctx) => {
|
|
@@ -768,8 +761,8 @@ export default {
|
|
|
768
761
|
```typescript
|
|
769
762
|
// apis/webhook/wechatPayV3.ts
|
|
770
763
|
export default {
|
|
771
|
-
name:
|
|
772
|
-
method:
|
|
764
|
+
name: "微信支付V3回调",
|
|
765
|
+
method: "POST",
|
|
773
766
|
auth: false,
|
|
774
767
|
rawBody: true, // 跳过解析,保留原始请求
|
|
775
768
|
handler: async (befly, ctx) => {
|
|
@@ -777,15 +770,15 @@ export default {
|
|
|
777
770
|
const rawBody = await ctx.req.text();
|
|
778
771
|
|
|
779
772
|
// 2. 获取微信签名头
|
|
780
|
-
const signature = ctx.req.headers.get(
|
|
781
|
-
const timestamp = ctx.req.headers.get(
|
|
782
|
-
const nonce = ctx.req.headers.get(
|
|
783
|
-
const serial = ctx.req.headers.get(
|
|
773
|
+
const signature = ctx.req.headers.get("Wechatpay-Signature");
|
|
774
|
+
const timestamp = ctx.req.headers.get("Wechatpay-Timestamp");
|
|
775
|
+
const nonce = ctx.req.headers.get("Wechatpay-Nonce");
|
|
776
|
+
const serial = ctx.req.headers.get("Wechatpay-Serial");
|
|
784
777
|
|
|
785
778
|
// 3. 验证签名
|
|
786
779
|
const verifyMessage = `${timestamp}\n${nonce}\n${rawBody}\n`;
|
|
787
780
|
if (!verifyRsaSign(verifyMessage, signature, serial)) {
|
|
788
|
-
return { code:
|
|
781
|
+
return { code: "FAIL", message: "签名验证失败" };
|
|
789
782
|
}
|
|
790
783
|
|
|
791
784
|
// 4. 解析并解密数据
|
|
@@ -795,9 +788,9 @@ export default {
|
|
|
795
788
|
const payResult = JSON.parse(decrypted);
|
|
796
789
|
|
|
797
790
|
// 5. 处理支付结果
|
|
798
|
-
if (payResult.trade_state ===
|
|
791
|
+
if (payResult.trade_state === "SUCCESS") {
|
|
799
792
|
await befly.db.updData({
|
|
800
|
-
table:
|
|
793
|
+
table: "order",
|
|
801
794
|
where: { orderNo: payResult.out_trade_no },
|
|
802
795
|
data: {
|
|
803
796
|
payStatus: 1,
|
|
@@ -807,7 +800,7 @@ export default {
|
|
|
807
800
|
});
|
|
808
801
|
}
|
|
809
802
|
|
|
810
|
-
return { code:
|
|
803
|
+
return { code: "SUCCESS", message: "" };
|
|
811
804
|
}
|
|
812
805
|
};
|
|
813
806
|
```
|
|
@@ -816,11 +809,11 @@ export default {
|
|
|
816
809
|
|
|
817
810
|
```typescript
|
|
818
811
|
// apis/webhook/github.ts
|
|
819
|
-
import { createHmac } from
|
|
812
|
+
import { createHmac } from "crypto";
|
|
820
813
|
|
|
821
814
|
export default {
|
|
822
|
-
name:
|
|
823
|
-
method:
|
|
815
|
+
name: "GitHub Webhook",
|
|
816
|
+
method: "POST",
|
|
824
817
|
auth: false,
|
|
825
818
|
rawBody: true,
|
|
826
819
|
handler: async (befly, ctx) => {
|
|
@@ -828,30 +821,30 @@ export default {
|
|
|
828
821
|
const rawBody = await ctx.req.text();
|
|
829
822
|
|
|
830
823
|
// 获取 GitHub 签名
|
|
831
|
-
const signature = ctx.req.headers.get(
|
|
824
|
+
const signature = ctx.req.headers.get("X-Hub-Signature-256");
|
|
832
825
|
if (!signature) {
|
|
833
|
-
return befly.tool.No(
|
|
826
|
+
return befly.tool.No("缺少签名");
|
|
834
827
|
}
|
|
835
828
|
|
|
836
829
|
// 验证 HMAC 签名
|
|
837
830
|
const secret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
838
|
-
const hmac = createHmac(
|
|
839
|
-
const expectedSignature =
|
|
831
|
+
const hmac = createHmac("sha256", secret);
|
|
832
|
+
const expectedSignature = "sha256=" + hmac.update(rawBody).digest("hex");
|
|
840
833
|
|
|
841
834
|
if (signature !== expectedSignature) {
|
|
842
|
-
return befly.tool.No(
|
|
835
|
+
return befly.tool.No("签名验证失败");
|
|
843
836
|
}
|
|
844
837
|
|
|
845
838
|
// 解析数据
|
|
846
839
|
const payload = JSON.parse(rawBody);
|
|
847
|
-
const event = ctx.req.headers.get(
|
|
840
|
+
const event = ctx.req.headers.get("X-GitHub-Event");
|
|
848
841
|
|
|
849
842
|
// 处理不同事件
|
|
850
|
-
if (event ===
|
|
851
|
-
befly.logger.info({ ref: payload.ref },
|
|
843
|
+
if (event === "push") {
|
|
844
|
+
befly.logger.info({ ref: payload.ref }, "GitHub Push 事件");
|
|
852
845
|
}
|
|
853
846
|
|
|
854
|
-
return befly.tool.Yes(
|
|
847
|
+
return befly.tool.Yes("处理成功");
|
|
855
848
|
}
|
|
856
849
|
};
|
|
857
850
|
```
|
|
@@ -861,7 +854,7 @@ export default {
|
|
|
861
854
|
```typescript
|
|
862
855
|
// apis/secure/receive.ts
|
|
863
856
|
export default {
|
|
864
|
-
name:
|
|
857
|
+
name: "接收加密数据",
|
|
865
858
|
auth: false,
|
|
866
859
|
rawBody: true,
|
|
867
860
|
handler: async (befly, ctx) => {
|
|
@@ -875,7 +868,7 @@ export default {
|
|
|
875
868
|
// 处理解密后的数据
|
|
876
869
|
// ...
|
|
877
870
|
|
|
878
|
-
return befly.tool.Yes(
|
|
871
|
+
return befly.tool.Yes("处理成功");
|
|
879
872
|
}
|
|
880
873
|
};
|
|
881
874
|
```
|
|
@@ -887,17 +880,17 @@ export default {
|
|
|
887
880
|
```typescript
|
|
888
881
|
// apis/user/batchImport.ts
|
|
889
882
|
export default {
|
|
890
|
-
name:
|
|
883
|
+
name: "批量导入用户",
|
|
891
884
|
// 不定义 fields,或 fields: {},会保留所有请求参数
|
|
892
885
|
handler: async (befly, ctx) => {
|
|
893
886
|
const { users } = ctx.body; // 正常解析
|
|
894
887
|
|
|
895
888
|
if (!Array.isArray(users) || users.length === 0) {
|
|
896
|
-
return befly.tool.No(
|
|
889
|
+
return befly.tool.No("用户列表不能为空");
|
|
897
890
|
}
|
|
898
891
|
|
|
899
892
|
// 批量插入...
|
|
900
|
-
return befly.tool.Yes(
|
|
893
|
+
return befly.tool.Yes("导入成功");
|
|
901
894
|
}
|
|
902
895
|
};
|
|
903
896
|
```
|
|
@@ -918,39 +911,39 @@ export default {
|
|
|
918
911
|
|
|
919
912
|
```typescript
|
|
920
913
|
// apis/auth/login.ts
|
|
921
|
-
import adminTable from
|
|
914
|
+
import adminTable from "../../tables/admin.json";
|
|
922
915
|
|
|
923
916
|
export default {
|
|
924
|
-
name:
|
|
917
|
+
name: "管理员登录",
|
|
925
918
|
auth: false, // 公开接口
|
|
926
919
|
fields: {
|
|
927
920
|
account: {
|
|
928
|
-
name:
|
|
929
|
-
type:
|
|
921
|
+
name: "账号",
|
|
922
|
+
type: "string",
|
|
930
923
|
min: 3,
|
|
931
924
|
max: 100
|
|
932
925
|
},
|
|
933
926
|
password: adminTable.password
|
|
934
927
|
},
|
|
935
|
-
required: [
|
|
928
|
+
required: ["account", "password"],
|
|
936
929
|
handler: async (befly, ctx) => {
|
|
937
930
|
// 查询用户
|
|
938
931
|
const admin = await befly.db.getOne({
|
|
939
|
-
table:
|
|
932
|
+
table: "addon_admin_admin",
|
|
940
933
|
where: {
|
|
941
934
|
$or: [{ username: ctx.body.account }, { email: ctx.body.account }]
|
|
942
935
|
}
|
|
943
936
|
});
|
|
944
937
|
|
|
945
938
|
if (!admin?.id) {
|
|
946
|
-
return befly.tool.No(
|
|
939
|
+
return befly.tool.No("账号或密码错误");
|
|
947
940
|
}
|
|
948
941
|
|
|
949
942
|
// 验证密码
|
|
950
943
|
const isValid = await befly.cipher.verifyPassword(ctx.body.password, admin.password);
|
|
951
944
|
|
|
952
945
|
if (!isValid) {
|
|
953
|
-
return befly.tool.No(
|
|
946
|
+
return befly.tool.No("账号或密码错误");
|
|
954
947
|
}
|
|
955
948
|
|
|
956
949
|
// 生成 Token
|
|
@@ -959,7 +952,7 @@ export default {
|
|
|
959
952
|
roleCode: admin.roleCode
|
|
960
953
|
});
|
|
961
954
|
|
|
962
|
-
return befly.tool.Yes(
|
|
955
|
+
return befly.tool.Yes("登录成功", {
|
|
963
956
|
token: token,
|
|
964
957
|
userInfo: admin
|
|
965
958
|
});
|
|
@@ -972,20 +965,20 @@ export default {
|
|
|
972
965
|
```typescript
|
|
973
966
|
// apis/admin/list.ts
|
|
974
967
|
export default {
|
|
975
|
-
name:
|
|
968
|
+
name: "获取管理员列表",
|
|
976
969
|
// auth: true, // 默认需要认证
|
|
977
970
|
handler: async (befly, ctx) => {
|
|
978
971
|
const result = await befly.db.getList({
|
|
979
|
-
table:
|
|
972
|
+
table: "addon_admin_admin",
|
|
980
973
|
page: ctx.body.page || 1,
|
|
981
974
|
limit: ctx.body.limit || 10,
|
|
982
975
|
where: {
|
|
983
|
-
roleCode: { $ne:
|
|
976
|
+
roleCode: { $ne: "dev" }
|
|
984
977
|
},
|
|
985
|
-
orderBy: [
|
|
978
|
+
orderBy: ["createdAt#DESC"]
|
|
986
979
|
});
|
|
987
980
|
|
|
988
|
-
return befly.tool.Yes(
|
|
981
|
+
return befly.tool.Yes("获取成功", result);
|
|
989
982
|
}
|
|
990
983
|
};
|
|
991
984
|
```
|
|
@@ -994,32 +987,31 @@ export default {
|
|
|
994
987
|
|
|
995
988
|
```typescript
|
|
996
989
|
// apis/admin/ins.ts
|
|
997
|
-
import adminTable from
|
|
990
|
+
import adminTable from "../../tables/admin.json";
|
|
998
991
|
|
|
999
992
|
export default {
|
|
1000
|
-
name:
|
|
993
|
+
name: "添加管理员",
|
|
1001
994
|
fields: adminTable,
|
|
1002
|
-
required: [
|
|
995
|
+
required: ["username", "password", "roleCode"],
|
|
1003
996
|
handler: async (befly, ctx) => {
|
|
1004
997
|
// 检查用户名是否已存在
|
|
1005
998
|
const existing = await befly.db.getOne({
|
|
1006
|
-
table:
|
|
999
|
+
table: "addon_admin_admin",
|
|
1007
1000
|
where: { username: ctx.body.username }
|
|
1008
1001
|
});
|
|
1009
1002
|
|
|
1010
1003
|
if (existing) {
|
|
1011
|
-
return befly.tool.No(
|
|
1004
|
+
return befly.tool.No("用户名已被使用");
|
|
1012
1005
|
}
|
|
1013
1006
|
|
|
1014
1007
|
// 查询角色信息
|
|
1015
1008
|
const role = await befly.db.getOne({
|
|
1016
|
-
table:
|
|
1017
|
-
where: {
|
|
1018
|
-
columns: ['code']
|
|
1009
|
+
table: "addon_admin_role",
|
|
1010
|
+
where: { code: ctx.body.roleCode }
|
|
1019
1011
|
});
|
|
1020
1012
|
|
|
1021
|
-
if (!role
|
|
1022
|
-
return befly.tool.No(
|
|
1013
|
+
if (!role) {
|
|
1014
|
+
return befly.tool.No("角色不存在");
|
|
1023
1015
|
}
|
|
1024
1016
|
|
|
1025
1017
|
// 加密密码
|
|
@@ -1027,17 +1019,16 @@ export default {
|
|
|
1027
1019
|
|
|
1028
1020
|
// 创建管理员
|
|
1029
1021
|
const adminId = await befly.db.insData({
|
|
1030
|
-
table:
|
|
1022
|
+
table: "addon_admin_admin",
|
|
1031
1023
|
data: {
|
|
1032
1024
|
username: ctx.body.username,
|
|
1033
1025
|
password: hashedPassword,
|
|
1034
1026
|
nickname: ctx.body.nickname,
|
|
1035
|
-
roleId: ctx.body.roleId,
|
|
1036
1027
|
roleCode: role.code
|
|
1037
1028
|
}
|
|
1038
1029
|
});
|
|
1039
1030
|
|
|
1040
|
-
return befly.tool.Yes(
|
|
1031
|
+
return befly.tool.Yes("添加成功", {
|
|
1041
1032
|
id: adminId
|
|
1042
1033
|
});
|
|
1043
1034
|
}
|
|
@@ -1048,33 +1039,33 @@ export default {
|
|
|
1048
1039
|
|
|
1049
1040
|
```typescript
|
|
1050
1041
|
// apis/admin/upd.ts
|
|
1051
|
-
import adminTable from
|
|
1042
|
+
import adminTable from "../../tables/admin.json";
|
|
1052
1043
|
|
|
1053
1044
|
export default {
|
|
1054
|
-
name:
|
|
1045
|
+
name: "更新管理员",
|
|
1055
1046
|
fields: adminTable,
|
|
1056
|
-
required: [
|
|
1047
|
+
required: ["id"],
|
|
1057
1048
|
handler: async (befly, ctx) => {
|
|
1058
1049
|
const { id, ...updateData } = ctx.body;
|
|
1059
1050
|
|
|
1060
1051
|
// 检查管理员是否存在
|
|
1061
1052
|
const admin = await befly.db.getOne({
|
|
1062
|
-
table:
|
|
1053
|
+
table: "addon_admin_admin",
|
|
1063
1054
|
where: { id: id }
|
|
1064
1055
|
});
|
|
1065
1056
|
|
|
1066
1057
|
if (!admin?.id) {
|
|
1067
|
-
return befly.tool.No(
|
|
1058
|
+
return befly.tool.No("管理员不存在");
|
|
1068
1059
|
}
|
|
1069
1060
|
|
|
1070
1061
|
// 更新管理员信息
|
|
1071
1062
|
await befly.db.updData({
|
|
1072
|
-
table:
|
|
1063
|
+
table: "addon_admin_admin",
|
|
1073
1064
|
data: updateData,
|
|
1074
1065
|
where: { id: id }
|
|
1075
1066
|
});
|
|
1076
1067
|
|
|
1077
|
-
return befly.tool.Yes(
|
|
1068
|
+
return befly.tool.Yes("更新成功");
|
|
1078
1069
|
}
|
|
1079
1070
|
};
|
|
1080
1071
|
```
|
|
@@ -1084,32 +1075,32 @@ export default {
|
|
|
1084
1075
|
```typescript
|
|
1085
1076
|
// apis/admin/del.ts
|
|
1086
1077
|
export default {
|
|
1087
|
-
name:
|
|
1078
|
+
name: "删除管理员",
|
|
1088
1079
|
fields: {},
|
|
1089
|
-
required: [
|
|
1080
|
+
required: ["id"],
|
|
1090
1081
|
handler: async (befly, ctx) => {
|
|
1091
1082
|
// 检查管理员是否存在
|
|
1092
1083
|
const admin = await befly.db.getOne({
|
|
1093
|
-
table:
|
|
1084
|
+
table: "addon_admin_admin",
|
|
1094
1085
|
where: { id: ctx.body.id }
|
|
1095
1086
|
});
|
|
1096
1087
|
|
|
1097
1088
|
if (!admin) {
|
|
1098
|
-
return befly.tool.No(
|
|
1089
|
+
return befly.tool.No("管理员不存在");
|
|
1099
1090
|
}
|
|
1100
1091
|
|
|
1101
1092
|
// 业务检查:不能删除开发者账号
|
|
1102
|
-
if (admin.roleCode ===
|
|
1103
|
-
return befly.tool.No(
|
|
1093
|
+
if (admin.roleCode === "dev") {
|
|
1094
|
+
return befly.tool.No("不能删除开发者账号");
|
|
1104
1095
|
}
|
|
1105
1096
|
|
|
1106
1097
|
// 删除管理员
|
|
1107
1098
|
await befly.db.delData({
|
|
1108
|
-
table:
|
|
1099
|
+
table: "addon_admin_admin",
|
|
1109
1100
|
where: { id: ctx.body.id }
|
|
1110
1101
|
});
|
|
1111
1102
|
|
|
1112
|
-
return befly.tool.Yes(
|
|
1103
|
+
return befly.tool.Yes("删除成功");
|
|
1113
1104
|
}
|
|
1114
1105
|
};
|
|
1115
1106
|
```
|
|
@@ -1119,29 +1110,29 @@ export default {
|
|
|
1119
1110
|
```typescript
|
|
1120
1111
|
// apis/admin/detail.ts
|
|
1121
1112
|
export default {
|
|
1122
|
-
name:
|
|
1113
|
+
name: "获取用户信息",
|
|
1123
1114
|
handler: async (befly, ctx) => {
|
|
1124
1115
|
const userId = ctx.user?.id;
|
|
1125
1116
|
|
|
1126
1117
|
if (!userId) {
|
|
1127
|
-
return befly.tool.No(
|
|
1118
|
+
return befly.tool.No("未授权");
|
|
1128
1119
|
}
|
|
1129
1120
|
|
|
1130
1121
|
// 查询用户信息
|
|
1131
1122
|
const admin = await befly.db.getOne({
|
|
1132
|
-
table:
|
|
1123
|
+
table: "addon_admin_admin",
|
|
1133
1124
|
where: { id: userId }
|
|
1134
1125
|
});
|
|
1135
1126
|
|
|
1136
1127
|
if (!admin) {
|
|
1137
|
-
return befly.tool.No(
|
|
1128
|
+
return befly.tool.No("用户不存在");
|
|
1138
1129
|
}
|
|
1139
1130
|
|
|
1140
1131
|
// 查询角色信息
|
|
1141
1132
|
let roleInfo = null;
|
|
1142
1133
|
if (admin.roleCode) {
|
|
1143
1134
|
roleInfo = await befly.db.getOne({
|
|
1144
|
-
table:
|
|
1135
|
+
table: "addon_admin_role",
|
|
1145
1136
|
where: { code: admin.roleCode }
|
|
1146
1137
|
});
|
|
1147
1138
|
}
|
|
@@ -1149,7 +1140,7 @@ export default {
|
|
|
1149
1140
|
// 返回用户信息(不包含密码)
|
|
1150
1141
|
const { password: _, ...userWithoutPassword } = admin;
|
|
1151
1142
|
|
|
1152
|
-
return befly.tool.Yes(
|
|
1143
|
+
return befly.tool.Yes("获取成功", {
|
|
1153
1144
|
...userWithoutPassword,
|
|
1154
1145
|
role: roleInfo
|
|
1155
1146
|
});
|
|
@@ -1162,27 +1153,27 @@ export default {
|
|
|
1162
1153
|
```typescript
|
|
1163
1154
|
// apis/article/search.ts
|
|
1164
1155
|
export default {
|
|
1165
|
-
name:
|
|
1166
|
-
method:
|
|
1156
|
+
name: "搜索文章",
|
|
1157
|
+
method: "GET,POST", // 同时支持 GET 和 POST
|
|
1167
1158
|
auth: false,
|
|
1168
1159
|
fields: {
|
|
1169
1160
|
keyword: {
|
|
1170
|
-
name:
|
|
1171
|
-
type:
|
|
1161
|
+
name: "关键词",
|
|
1162
|
+
type: "string",
|
|
1172
1163
|
min: 1,
|
|
1173
1164
|
max: 100
|
|
1174
1165
|
}
|
|
1175
1166
|
},
|
|
1176
|
-
required: [
|
|
1167
|
+
required: ["keyword"],
|
|
1177
1168
|
handler: async (befly, ctx) => {
|
|
1178
1169
|
const result = await befly.db.getList({
|
|
1179
|
-
table:
|
|
1170
|
+
table: "article",
|
|
1180
1171
|
where: {
|
|
1181
1172
|
title: { $like: `%${ctx.body.keyword}%` }
|
|
1182
1173
|
}
|
|
1183
1174
|
});
|
|
1184
1175
|
|
|
1185
|
-
return befly.tool.Yes(
|
|
1176
|
+
return befly.tool.Yes("搜索成功", result);
|
|
1186
1177
|
}
|
|
1187
1178
|
};
|
|
1188
1179
|
```
|
|
@@ -1192,8 +1183,8 @@ export default {
|
|
|
1192
1183
|
```typescript
|
|
1193
1184
|
// apis/webhook/wechat.ts
|
|
1194
1185
|
export default {
|
|
1195
|
-
name:
|
|
1196
|
-
method:
|
|
1186
|
+
name: "微信回调",
|
|
1187
|
+
method: "POST",
|
|
1197
1188
|
auth: false,
|
|
1198
1189
|
rawBody: true, // 不过滤字段,保留完整请求体
|
|
1199
1190
|
handler: async (befly, ctx) => {
|
|
@@ -1201,9 +1192,9 @@ export default {
|
|
|
1201
1192
|
const { ToUserName, FromUserName, MsgType, Content } = ctx.body;
|
|
1202
1193
|
|
|
1203
1194
|
// 处理微信消息
|
|
1204
|
-
befly.logger.info({ msg: Content },
|
|
1195
|
+
befly.logger.info({ msg: Content }, "收到微信消息");
|
|
1205
1196
|
|
|
1206
|
-
return befly.tool.Yes(
|
|
1197
|
+
return befly.tool.Yes("处理成功");
|
|
1207
1198
|
}
|
|
1208
1199
|
};
|
|
1209
1200
|
```
|
|
@@ -1262,7 +1253,7 @@ export default {
|
|
|
1262
1253
|
```typescript
|
|
1263
1254
|
// 在 Hook 中中断
|
|
1264
1255
|
if (!ctx.user?.id) {
|
|
1265
|
-
ctx.response = ErrorResponse(ctx,
|
|
1256
|
+
ctx.response = ErrorResponse(ctx, "未登录");
|
|
1266
1257
|
return; // 后续 Hook 和 handler 不会执行
|
|
1267
1258
|
}
|
|
1268
1259
|
```
|
|
@@ -1349,10 +1340,10 @@ befly.db.cleanFields<T>(
|
|
|
1349
1340
|
```typescript
|
|
1350
1341
|
// 默认排除 null 和 undefined
|
|
1351
1342
|
const cleanData = befly.db.cleanFields({
|
|
1352
|
-
name:
|
|
1343
|
+
name: "John",
|
|
1353
1344
|
age: null,
|
|
1354
1345
|
email: undefined,
|
|
1355
|
-
phone:
|
|
1346
|
+
phone: ""
|
|
1356
1347
|
});
|
|
1357
1348
|
// 结果: { name: 'John', phone: '' }
|
|
1358
1349
|
```
|
|
@@ -1361,7 +1352,7 @@ const cleanData = befly.db.cleanFields({
|
|
|
1361
1352
|
|
|
1362
1353
|
```typescript
|
|
1363
1354
|
// 同时排除 null、undefined 和空字符串
|
|
1364
|
-
const cleanData = befly.db.cleanFields({ name:
|
|
1355
|
+
const cleanData = befly.db.cleanFields({ name: "John", phone: "", age: null }, [null, undefined, ""]);
|
|
1365
1356
|
// 结果: { name: 'John' }
|
|
1366
1357
|
```
|
|
1367
1358
|
|
|
@@ -1369,7 +1360,7 @@ const cleanData = befly.db.cleanFields({ name: 'John', phone: '', age: null }, [
|
|
|
1369
1360
|
|
|
1370
1361
|
```typescript
|
|
1371
1362
|
// 即使值在排除列表中,也保留 status 字段的 null 值
|
|
1372
|
-
const cleanData = befly.db.cleanFields({ name:
|
|
1363
|
+
const cleanData = befly.db.cleanFields({ name: "John", status: null, count: 0 }, [null, undefined], { status: null });
|
|
1373
1364
|
// 结果: { name: 'John', status: null, count: 0 }
|
|
1374
1365
|
```
|
|
1375
1366
|
|
|
@@ -1407,7 +1398,7 @@ fields: {
|
|
|
1407
1398
|
```typescript
|
|
1408
1399
|
// ✅ 推荐:直接使用
|
|
1409
1400
|
const result = await befly.db.insData({
|
|
1410
|
-
table:
|
|
1401
|
+
table: "user",
|
|
1411
1402
|
data: {
|
|
1412
1403
|
username: ctx.body.username,
|
|
1413
1404
|
email: ctx.body.email
|
|
@@ -1423,7 +1414,7 @@ const { username, email } = ctx.body;
|
|
|
1423
1414
|
```typescript
|
|
1424
1415
|
// ✅ 推荐:明确每个字段
|
|
1425
1416
|
await befly.db.insData({
|
|
1426
|
-
table:
|
|
1417
|
+
table: "user",
|
|
1427
1418
|
data: {
|
|
1428
1419
|
username: ctx.body.username,
|
|
1429
1420
|
email: ctx.body.email,
|
|
@@ -1433,7 +1424,7 @@ await befly.db.insData({
|
|
|
1433
1424
|
|
|
1434
1425
|
// ❌ 避免:扩展运算符
|
|
1435
1426
|
await befly.db.insData({
|
|
1436
|
-
table:
|
|
1427
|
+
table: "user",
|
|
1437
1428
|
data: { ...ctx.body } // 危险!可能写入未预期的字段
|
|
1438
1429
|
});
|
|
1439
1430
|
```
|
|
@@ -1445,12 +1436,12 @@ handler: async (befly, ctx) => {
|
|
|
1445
1436
|
try {
|
|
1446
1437
|
// 业务逻辑
|
|
1447
1438
|
const result = await someOperation();
|
|
1448
|
-
return befly.tool.Yes(
|
|
1439
|
+
return befly.tool.Yes("成功", result);
|
|
1449
1440
|
} catch (error: any) {
|
|
1450
1441
|
// 记录错误日志
|
|
1451
|
-
befly.logger.error({ err: error },
|
|
1442
|
+
befly.logger.error({ err: error }, "操作失败");
|
|
1452
1443
|
// 返回友好错误信息
|
|
1453
|
-
return befly.tool.No(
|
|
1444
|
+
return befly.tool.No("操作失败,请稍后重试");
|
|
1454
1445
|
}
|
|
1455
1446
|
};
|
|
1456
1447
|
```
|
|
@@ -1460,7 +1451,7 @@ handler: async (befly, ctx) => {
|
|
|
1460
1451
|
```typescript
|
|
1461
1452
|
// ✅ 推荐:使用 Date.now()
|
|
1462
1453
|
await befly.db.updData({
|
|
1463
|
-
table:
|
|
1454
|
+
table: "user",
|
|
1464
1455
|
data: {
|
|
1465
1456
|
lastLoginTime: Date.now(), // number 类型
|
|
1466
1457
|
lastLoginIp: ctx.ip
|
|
@@ -1480,7 +1471,7 @@ lastLoginTime: new Date(); // 类型不一致
|
|
|
1480
1471
|
|
|
1481
1472
|
```typescript
|
|
1482
1473
|
export default {
|
|
1483
|
-
name:
|
|
1474
|
+
name: "公开接口",
|
|
1484
1475
|
auth: false, // 设置为 false
|
|
1485
1476
|
handler: async (befly, ctx) => {
|
|
1486
1477
|
// ...
|
|
@@ -1496,7 +1487,7 @@ handler: async (befly, ctx) => {
|
|
|
1496
1487
|
const roleCode = ctx.user?.roleCode;
|
|
1497
1488
|
|
|
1498
1489
|
if (!userId) {
|
|
1499
|
-
return befly.tool.No(
|
|
1490
|
+
return befly.tool.No("未登录");
|
|
1500
1491
|
}
|
|
1501
1492
|
};
|
|
1502
1493
|
```
|
|
@@ -1511,12 +1502,12 @@ handler: async (befly, ctx) => {
|
|
|
1511
1502
|
|
|
1512
1503
|
```typescript
|
|
1513
1504
|
// tpl/hooks/requestLog.ts
|
|
1514
|
-
import type { Hook } from
|
|
1505
|
+
import type { Hook } from "befly/types/hook";
|
|
1515
1506
|
|
|
1516
1507
|
const hook: Hook = {
|
|
1517
1508
|
order: 100, // 执行顺序
|
|
1518
1509
|
handler: async (befly, ctx) => {
|
|
1519
|
-
befly.logger.info({ route: ctx.route },
|
|
1510
|
+
befly.logger.info({ route: ctx.route }, "请求开始");
|
|
1520
1511
|
}
|
|
1521
1512
|
};
|
|
1522
1513
|
export default hook;
|
|
@@ -1540,42 +1531,42 @@ export default hook;
|
|
|
1540
1531
|
```typescript
|
|
1541
1532
|
// apis/order/create.ts
|
|
1542
1533
|
export default {
|
|
1543
|
-
name:
|
|
1534
|
+
name: "创建订单",
|
|
1544
1535
|
fields: {
|
|
1545
1536
|
productId: {
|
|
1546
|
-
name:
|
|
1547
|
-
type:
|
|
1537
|
+
name: "商品ID",
|
|
1538
|
+
type: "number",
|
|
1548
1539
|
min: 1
|
|
1549
1540
|
},
|
|
1550
1541
|
quantity: {
|
|
1551
|
-
name:
|
|
1552
|
-
type:
|
|
1542
|
+
name: "数量",
|
|
1543
|
+
type: "number",
|
|
1553
1544
|
min: 1,
|
|
1554
1545
|
max: 999
|
|
1555
1546
|
}
|
|
1556
1547
|
},
|
|
1557
|
-
required: [
|
|
1548
|
+
required: ["productId", "quantity"],
|
|
1558
1549
|
handler: async (befly, ctx) => {
|
|
1559
1550
|
// 使用事务确保库存扣减和订单创建的原子性
|
|
1560
1551
|
const result = await befly.db.transaction(async (trx) => {
|
|
1561
1552
|
// 1. 查询商品信息(带锁)
|
|
1562
1553
|
const product = await trx.getOne({
|
|
1563
|
-
table:
|
|
1554
|
+
table: "product",
|
|
1564
1555
|
where: { id: ctx.body.productId },
|
|
1565
1556
|
forUpdate: true // 行锁
|
|
1566
1557
|
});
|
|
1567
1558
|
|
|
1568
1559
|
if (!product) {
|
|
1569
|
-
throw new Error(
|
|
1560
|
+
throw new Error("商品不存在");
|
|
1570
1561
|
}
|
|
1571
1562
|
|
|
1572
1563
|
if (product.stock < ctx.body.quantity) {
|
|
1573
|
-
throw new Error(
|
|
1564
|
+
throw new Error("库存不足");
|
|
1574
1565
|
}
|
|
1575
1566
|
|
|
1576
1567
|
// 2. 扣减库存
|
|
1577
1568
|
await trx.updData({
|
|
1578
|
-
table:
|
|
1569
|
+
table: "product",
|
|
1579
1570
|
data: {
|
|
1580
1571
|
stock: product.stock - ctx.body.quantity
|
|
1581
1572
|
},
|
|
@@ -1584,19 +1575,19 @@ export default {
|
|
|
1584
1575
|
|
|
1585
1576
|
// 3. 创建订单
|
|
1586
1577
|
const orderId = await trx.insData({
|
|
1587
|
-
table:
|
|
1578
|
+
table: "order",
|
|
1588
1579
|
data: {
|
|
1589
1580
|
userId: ctx.user.id,
|
|
1590
1581
|
productId: ctx.body.productId,
|
|
1591
1582
|
quantity: ctx.body.quantity,
|
|
1592
1583
|
totalPrice: product.price * ctx.body.quantity,
|
|
1593
|
-
status:
|
|
1584
|
+
status: "pending"
|
|
1594
1585
|
}
|
|
1595
1586
|
});
|
|
1596
1587
|
|
|
1597
1588
|
// 4. 创建订单明细
|
|
1598
1589
|
await trx.insData({
|
|
1599
|
-
table:
|
|
1590
|
+
table: "order_item",
|
|
1600
1591
|
data: {
|
|
1601
1592
|
orderId: orderId,
|
|
1602
1593
|
productId: ctx.body.productId,
|
|
@@ -1609,7 +1600,7 @@ export default {
|
|
|
1609
1600
|
return { orderId: orderId };
|
|
1610
1601
|
});
|
|
1611
1602
|
|
|
1612
|
-
return befly.tool.Yes(
|
|
1603
|
+
return befly.tool.Yes("订单创建成功", result);
|
|
1613
1604
|
}
|
|
1614
1605
|
};
|
|
1615
1606
|
```
|
|
@@ -1621,22 +1612,22 @@ export default {
|
|
|
1621
1612
|
```typescript
|
|
1622
1613
|
// apis/user/batchImport.ts
|
|
1623
1614
|
export default {
|
|
1624
|
-
name:
|
|
1615
|
+
name: "批量导入用户",
|
|
1625
1616
|
rawBody: true, // 保留原始请求体
|
|
1626
1617
|
handler: async (befly, ctx) => {
|
|
1627
1618
|
const users = ctx.body.users;
|
|
1628
1619
|
|
|
1629
1620
|
if (!Array.isArray(users) || users.length === 0) {
|
|
1630
|
-
return befly.tool.No(
|
|
1621
|
+
return befly.tool.No("用户列表不能为空");
|
|
1631
1622
|
}
|
|
1632
1623
|
|
|
1633
1624
|
if (users.length > 100) {
|
|
1634
|
-
return befly.tool.No(
|
|
1625
|
+
return befly.tool.No("单次导入不能超过100条");
|
|
1635
1626
|
}
|
|
1636
1627
|
|
|
1637
1628
|
// 批量插入
|
|
1638
1629
|
const result = await befly.db.batchInsert({
|
|
1639
|
-
table:
|
|
1630
|
+
table: "user",
|
|
1640
1631
|
data: users.map((user: any) => ({
|
|
1641
1632
|
username: user.username,
|
|
1642
1633
|
email: user.email,
|
|
@@ -1645,7 +1636,7 @@ export default {
|
|
|
1645
1636
|
}))
|
|
1646
1637
|
});
|
|
1647
1638
|
|
|
1648
|
-
return befly.tool.Yes(
|
|
1639
|
+
return befly.tool.Yes("导入成功", {
|
|
1649
1640
|
total: users.length,
|
|
1650
1641
|
inserted: result.affectedRows
|
|
1651
1642
|
});
|
|
@@ -1658,25 +1649,25 @@ export default {
|
|
|
1658
1649
|
```typescript
|
|
1659
1650
|
// apis/article/batchUpdate.ts
|
|
1660
1651
|
export default {
|
|
1661
|
-
name:
|
|
1652
|
+
name: "批量更新文章状态",
|
|
1662
1653
|
rawBody: true,
|
|
1663
1654
|
handler: async (befly, ctx) => {
|
|
1664
1655
|
const { ids, state } = ctx.body;
|
|
1665
1656
|
|
|
1666
1657
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1667
|
-
return befly.tool.No(
|
|
1658
|
+
return befly.tool.No("文章ID列表不能为空");
|
|
1668
1659
|
}
|
|
1669
1660
|
|
|
1670
1661
|
// 批量更新
|
|
1671
1662
|
const result = await befly.db.updData({
|
|
1672
|
-
table:
|
|
1663
|
+
table: "article",
|
|
1673
1664
|
data: { state: state },
|
|
1674
1665
|
where: {
|
|
1675
1666
|
id: { $in: ids }
|
|
1676
1667
|
}
|
|
1677
1668
|
});
|
|
1678
1669
|
|
|
1679
|
-
return befly.tool.Yes(
|
|
1670
|
+
return befly.tool.Yes("更新成功", {
|
|
1680
1671
|
updated: result.affectedRows
|
|
1681
1672
|
});
|
|
1682
1673
|
}
|
|
@@ -1688,24 +1679,24 @@ export default {
|
|
|
1688
1679
|
```typescript
|
|
1689
1680
|
// apis/log/batchDelete.ts
|
|
1690
1681
|
export default {
|
|
1691
|
-
name:
|
|
1682
|
+
name: "批量删除日志",
|
|
1692
1683
|
rawBody: true,
|
|
1693
1684
|
handler: async (befly, ctx) => {
|
|
1694
1685
|
const { ids } = ctx.body;
|
|
1695
1686
|
|
|
1696
1687
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1697
|
-
return befly.tool.No(
|
|
1688
|
+
return befly.tool.No("日志ID列表不能为空");
|
|
1698
1689
|
}
|
|
1699
1690
|
|
|
1700
1691
|
// 批量软删除
|
|
1701
1692
|
const result = await befly.db.delData({
|
|
1702
|
-
table:
|
|
1693
|
+
table: "operate_log",
|
|
1703
1694
|
where: {
|
|
1704
1695
|
id: { $in: ids }
|
|
1705
1696
|
}
|
|
1706
1697
|
});
|
|
1707
1698
|
|
|
1708
|
-
return befly.tool.Yes(
|
|
1699
|
+
return befly.tool.Yes("删除成功", {
|
|
1709
1700
|
deleted: result.affectedRows
|
|
1710
1701
|
});
|
|
1711
1702
|
}
|
|
@@ -1767,25 +1758,25 @@ export default {
|
|
|
1767
1758
|
```typescript
|
|
1768
1759
|
// apis/article/listWithAuthor.ts
|
|
1769
1760
|
export default {
|
|
1770
|
-
name:
|
|
1761
|
+
name: "文章列表(含作者)",
|
|
1771
1762
|
handler: async (befly, ctx) => {
|
|
1772
1763
|
const result = await befly.db.getList({
|
|
1773
|
-
table:
|
|
1764
|
+
table: "article",
|
|
1774
1765
|
joins: [
|
|
1775
1766
|
{
|
|
1776
|
-
type:
|
|
1777
|
-
table:
|
|
1778
|
-
alias:
|
|
1779
|
-
on: {
|
|
1767
|
+
type: "LEFT",
|
|
1768
|
+
table: "user",
|
|
1769
|
+
alias: "author",
|
|
1770
|
+
on: { "article.authorId": "author.id" }
|
|
1780
1771
|
}
|
|
1781
1772
|
],
|
|
1782
|
-
columns: [
|
|
1773
|
+
columns: ["article.id", "article.title", "article.createdAt", "author.nickname AS authorName"],
|
|
1783
1774
|
page: ctx.body.page || 1,
|
|
1784
1775
|
limit: ctx.body.limit || 10,
|
|
1785
|
-
orderBy: [
|
|
1776
|
+
orderBy: ["article.createdAt#DESC"]
|
|
1786
1777
|
});
|
|
1787
1778
|
|
|
1788
|
-
return befly.tool.Yes(
|
|
1779
|
+
return befly.tool.Yes("获取成功", result);
|
|
1789
1780
|
}
|
|
1790
1781
|
};
|
|
1791
1782
|
```
|
|
@@ -1795,30 +1786,29 @@ export default {
|
|
|
1795
1786
|
```typescript
|
|
1796
1787
|
// apis/config/getSiteConfig.ts
|
|
1797
1788
|
export default {
|
|
1798
|
-
name:
|
|
1789
|
+
name: "获取站点配置",
|
|
1799
1790
|
auth: false,
|
|
1800
|
-
cache: 300, // 缓存 5 分钟
|
|
1801
1791
|
handler: async (befly, ctx) => {
|
|
1802
1792
|
// 先从缓存获取
|
|
1803
|
-
const cacheKey =
|
|
1793
|
+
const cacheKey = "site:config";
|
|
1804
1794
|
let config = await befly.redis.get(cacheKey);
|
|
1805
1795
|
|
|
1806
1796
|
if (!config) {
|
|
1807
1797
|
// 缓存不存在,从数据库查询
|
|
1808
1798
|
const result = await befly.db.getAll({
|
|
1809
|
-
table:
|
|
1799
|
+
table: "sys_config",
|
|
1810
1800
|
where: { state: 1 }
|
|
1811
1801
|
});
|
|
1812
1802
|
|
|
1813
1803
|
config = result.lists; // 获取配置列表
|
|
1814
1804
|
|
|
1815
1805
|
// 写入缓存
|
|
1816
|
-
await befly.redis.set(cacheKey, JSON.stringify(config),
|
|
1806
|
+
await befly.redis.set(cacheKey, JSON.stringify(config), "EX", 300);
|
|
1817
1807
|
} else {
|
|
1818
1808
|
config = JSON.parse(config);
|
|
1819
1809
|
}
|
|
1820
1810
|
|
|
1821
|
-
return befly.tool.Yes(
|
|
1811
|
+
return befly.tool.Yes("获取成功", config);
|
|
1822
1812
|
}
|
|
1823
1813
|
};
|
|
1824
1814
|
```
|
|
@@ -1828,22 +1818,22 @@ export default {
|
|
|
1828
1818
|
```typescript
|
|
1829
1819
|
// apis/task/execute.ts
|
|
1830
1820
|
export default {
|
|
1831
|
-
name:
|
|
1821
|
+
name: "执行定时任务",
|
|
1832
1822
|
handler: async (befly, ctx) => {
|
|
1833
1823
|
const lockKey = `lock:task:${ctx.body.taskId}`;
|
|
1834
1824
|
|
|
1835
1825
|
// 尝试获取锁(30秒超时)
|
|
1836
|
-
const locked = await befly.redis.set(lockKey, ctx.requestId,
|
|
1826
|
+
const locked = await befly.redis.set(lockKey, ctx.requestId, "EX", 30, "NX");
|
|
1837
1827
|
|
|
1838
1828
|
if (!locked) {
|
|
1839
|
-
return befly.tool.No(
|
|
1829
|
+
return befly.tool.No("任务正在执行中,请稍后");
|
|
1840
1830
|
}
|
|
1841
1831
|
|
|
1842
1832
|
try {
|
|
1843
1833
|
// 执行任务逻辑
|
|
1844
1834
|
await executeTask(ctx.body.taskId);
|
|
1845
1835
|
|
|
1846
|
-
return befly.tool.Yes(
|
|
1836
|
+
return befly.tool.Yes("任务执行成功");
|
|
1847
1837
|
} finally {
|
|
1848
1838
|
// 释放锁
|
|
1849
1839
|
await befly.redis.del(lockKey);
|
|
@@ -1857,27 +1847,27 @@ export default {
|
|
|
1857
1847
|
```typescript
|
|
1858
1848
|
// apis/report/exportUsers.ts
|
|
1859
1849
|
export default {
|
|
1860
|
-
name:
|
|
1850
|
+
name: "导出用户数据",
|
|
1861
1851
|
handler: async (befly, ctx) => {
|
|
1862
1852
|
// 查询所有用户(不分页,注意上限 10000 条)
|
|
1863
1853
|
const result = await befly.db.getAll({
|
|
1864
|
-
table:
|
|
1865
|
-
columns: [
|
|
1854
|
+
table: "user",
|
|
1855
|
+
columns: ["id", "username", "nickname", "email", "phone", "createdAt"],
|
|
1866
1856
|
where: { state: 1 },
|
|
1867
|
-
orderBy: [
|
|
1857
|
+
orderBy: ["createdAt#DESC"]
|
|
1868
1858
|
});
|
|
1869
1859
|
|
|
1870
1860
|
// 转换为 CSV 格式
|
|
1871
|
-
const headers = [
|
|
1861
|
+
const headers = ["ID", "用户名", "昵称", "邮箱", "手机", "注册时间"];
|
|
1872
1862
|
const rows = result.lists.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
|
|
1873
1863
|
|
|
1874
|
-
const csv = [headers.join(
|
|
1864
|
+
const csv = [headers.join(","), ...rows.map((r: any[]) => r.join(","))].join("\n");
|
|
1875
1865
|
|
|
1876
1866
|
// 返回 CSV 文件(注意:如果 total > 10000,只会导出前 10000 条)
|
|
1877
1867
|
return new Response(csv, {
|
|
1878
1868
|
headers: {
|
|
1879
|
-
|
|
1880
|
-
|
|
1869
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
1870
|
+
"Content-Disposition": 'attachment; filename="users.csv"'
|
|
1881
1871
|
}
|
|
1882
1872
|
});
|
|
1883
1873
|
}
|
|
@@ -1889,17 +1879,17 @@ export default {
|
|
|
1889
1879
|
```typescript
|
|
1890
1880
|
// apis/file/download.ts
|
|
1891
1881
|
export default {
|
|
1892
|
-
name:
|
|
1893
|
-
required: [
|
|
1882
|
+
name: "文件下载",
|
|
1883
|
+
required: ["fileId"],
|
|
1894
1884
|
handler: async (befly, ctx) => {
|
|
1895
1885
|
// 查询文件信息
|
|
1896
1886
|
const file = await befly.db.getOne({
|
|
1897
|
-
table:
|
|
1887
|
+
table: "file",
|
|
1898
1888
|
where: { id: ctx.body.fileId }
|
|
1899
1889
|
});
|
|
1900
1890
|
|
|
1901
1891
|
if (!file) {
|
|
1902
|
-
return befly.tool.No(
|
|
1892
|
+
return befly.tool.No("文件不存在");
|
|
1903
1893
|
}
|
|
1904
1894
|
|
|
1905
1895
|
// 读取文件并返回流
|
|
@@ -1907,9 +1897,9 @@ export default {
|
|
|
1907
1897
|
|
|
1908
1898
|
return new Response(fileStream, {
|
|
1909
1899
|
headers: {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1900
|
+
"Content-Type": file.mimeType,
|
|
1901
|
+
"Content-Disposition": `attachment; filename="${encodeURIComponent(file.name)}"`,
|
|
1902
|
+
"Content-Length": String(file.size)
|
|
1913
1903
|
}
|
|
1914
1904
|
});
|
|
1915
1905
|
}
|