befly 3.9.38 → 3.9.40
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 +37 -38
- 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} +223 -231
- package/docs/cipher.md +71 -69
- package/docs/database.md +143 -141
- 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} +1 -1
- 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 +8 -8
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -77
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +183 -102
- 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 +48 -44
- 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 -52
- 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 -65
- package/sync/syncMenu.ts +190 -55
- 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,31 +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:
|
|
1009
|
+
table: "addon_admin_role",
|
|
1017
1010
|
where: { code: ctx.body.roleCode }
|
|
1018
1011
|
});
|
|
1019
1012
|
|
|
1020
1013
|
if (!role) {
|
|
1021
|
-
return befly.tool.No(
|
|
1014
|
+
return befly.tool.No("角色不存在");
|
|
1022
1015
|
}
|
|
1023
1016
|
|
|
1024
1017
|
// 加密密码
|
|
@@ -1026,7 +1019,7 @@ export default {
|
|
|
1026
1019
|
|
|
1027
1020
|
// 创建管理员
|
|
1028
1021
|
const adminId = await befly.db.insData({
|
|
1029
|
-
table:
|
|
1022
|
+
table: "addon_admin_admin",
|
|
1030
1023
|
data: {
|
|
1031
1024
|
username: ctx.body.username,
|
|
1032
1025
|
password: hashedPassword,
|
|
@@ -1035,7 +1028,7 @@ export default {
|
|
|
1035
1028
|
}
|
|
1036
1029
|
});
|
|
1037
1030
|
|
|
1038
|
-
return befly.tool.Yes(
|
|
1031
|
+
return befly.tool.Yes("添加成功", {
|
|
1039
1032
|
id: adminId
|
|
1040
1033
|
});
|
|
1041
1034
|
}
|
|
@@ -1046,33 +1039,33 @@ export default {
|
|
|
1046
1039
|
|
|
1047
1040
|
```typescript
|
|
1048
1041
|
// apis/admin/upd.ts
|
|
1049
|
-
import adminTable from
|
|
1042
|
+
import adminTable from "../../tables/admin.json";
|
|
1050
1043
|
|
|
1051
1044
|
export default {
|
|
1052
|
-
name:
|
|
1045
|
+
name: "更新管理员",
|
|
1053
1046
|
fields: adminTable,
|
|
1054
|
-
required: [
|
|
1047
|
+
required: ["id"],
|
|
1055
1048
|
handler: async (befly, ctx) => {
|
|
1056
1049
|
const { id, ...updateData } = ctx.body;
|
|
1057
1050
|
|
|
1058
1051
|
// 检查管理员是否存在
|
|
1059
1052
|
const admin = await befly.db.getOne({
|
|
1060
|
-
table:
|
|
1053
|
+
table: "addon_admin_admin",
|
|
1061
1054
|
where: { id: id }
|
|
1062
1055
|
});
|
|
1063
1056
|
|
|
1064
1057
|
if (!admin?.id) {
|
|
1065
|
-
return befly.tool.No(
|
|
1058
|
+
return befly.tool.No("管理员不存在");
|
|
1066
1059
|
}
|
|
1067
1060
|
|
|
1068
1061
|
// 更新管理员信息
|
|
1069
1062
|
await befly.db.updData({
|
|
1070
|
-
table:
|
|
1063
|
+
table: "addon_admin_admin",
|
|
1071
1064
|
data: updateData,
|
|
1072
1065
|
where: { id: id }
|
|
1073
1066
|
});
|
|
1074
1067
|
|
|
1075
|
-
return befly.tool.Yes(
|
|
1068
|
+
return befly.tool.Yes("更新成功");
|
|
1076
1069
|
}
|
|
1077
1070
|
};
|
|
1078
1071
|
```
|
|
@@ -1082,32 +1075,32 @@ export default {
|
|
|
1082
1075
|
```typescript
|
|
1083
1076
|
// apis/admin/del.ts
|
|
1084
1077
|
export default {
|
|
1085
|
-
name:
|
|
1078
|
+
name: "删除管理员",
|
|
1086
1079
|
fields: {},
|
|
1087
|
-
required: [
|
|
1080
|
+
required: ["id"],
|
|
1088
1081
|
handler: async (befly, ctx) => {
|
|
1089
1082
|
// 检查管理员是否存在
|
|
1090
1083
|
const admin = await befly.db.getOne({
|
|
1091
|
-
table:
|
|
1084
|
+
table: "addon_admin_admin",
|
|
1092
1085
|
where: { id: ctx.body.id }
|
|
1093
1086
|
});
|
|
1094
1087
|
|
|
1095
1088
|
if (!admin) {
|
|
1096
|
-
return befly.tool.No(
|
|
1089
|
+
return befly.tool.No("管理员不存在");
|
|
1097
1090
|
}
|
|
1098
1091
|
|
|
1099
1092
|
// 业务检查:不能删除开发者账号
|
|
1100
|
-
if (admin.roleCode ===
|
|
1101
|
-
return befly.tool.No(
|
|
1093
|
+
if (admin.roleCode === "dev") {
|
|
1094
|
+
return befly.tool.No("不能删除开发者账号");
|
|
1102
1095
|
}
|
|
1103
1096
|
|
|
1104
1097
|
// 删除管理员
|
|
1105
1098
|
await befly.db.delData({
|
|
1106
|
-
table:
|
|
1099
|
+
table: "addon_admin_admin",
|
|
1107
1100
|
where: { id: ctx.body.id }
|
|
1108
1101
|
});
|
|
1109
1102
|
|
|
1110
|
-
return befly.tool.Yes(
|
|
1103
|
+
return befly.tool.Yes("删除成功");
|
|
1111
1104
|
}
|
|
1112
1105
|
};
|
|
1113
1106
|
```
|
|
@@ -1117,29 +1110,29 @@ export default {
|
|
|
1117
1110
|
```typescript
|
|
1118
1111
|
// apis/admin/detail.ts
|
|
1119
1112
|
export default {
|
|
1120
|
-
name:
|
|
1113
|
+
name: "获取用户信息",
|
|
1121
1114
|
handler: async (befly, ctx) => {
|
|
1122
1115
|
const userId = ctx.user?.id;
|
|
1123
1116
|
|
|
1124
1117
|
if (!userId) {
|
|
1125
|
-
return befly.tool.No(
|
|
1118
|
+
return befly.tool.No("未授权");
|
|
1126
1119
|
}
|
|
1127
1120
|
|
|
1128
1121
|
// 查询用户信息
|
|
1129
1122
|
const admin = await befly.db.getOne({
|
|
1130
|
-
table:
|
|
1123
|
+
table: "addon_admin_admin",
|
|
1131
1124
|
where: { id: userId }
|
|
1132
1125
|
});
|
|
1133
1126
|
|
|
1134
1127
|
if (!admin) {
|
|
1135
|
-
return befly.tool.No(
|
|
1128
|
+
return befly.tool.No("用户不存在");
|
|
1136
1129
|
}
|
|
1137
1130
|
|
|
1138
1131
|
// 查询角色信息
|
|
1139
1132
|
let roleInfo = null;
|
|
1140
1133
|
if (admin.roleCode) {
|
|
1141
1134
|
roleInfo = await befly.db.getOne({
|
|
1142
|
-
table:
|
|
1135
|
+
table: "addon_admin_role",
|
|
1143
1136
|
where: { code: admin.roleCode }
|
|
1144
1137
|
});
|
|
1145
1138
|
}
|
|
@@ -1147,7 +1140,7 @@ export default {
|
|
|
1147
1140
|
// 返回用户信息(不包含密码)
|
|
1148
1141
|
const { password: _, ...userWithoutPassword } = admin;
|
|
1149
1142
|
|
|
1150
|
-
return befly.tool.Yes(
|
|
1143
|
+
return befly.tool.Yes("获取成功", {
|
|
1151
1144
|
...userWithoutPassword,
|
|
1152
1145
|
role: roleInfo
|
|
1153
1146
|
});
|
|
@@ -1160,27 +1153,27 @@ export default {
|
|
|
1160
1153
|
```typescript
|
|
1161
1154
|
// apis/article/search.ts
|
|
1162
1155
|
export default {
|
|
1163
|
-
name:
|
|
1164
|
-
method:
|
|
1156
|
+
name: "搜索文章",
|
|
1157
|
+
method: "GET,POST", // 同时支持 GET 和 POST
|
|
1165
1158
|
auth: false,
|
|
1166
1159
|
fields: {
|
|
1167
1160
|
keyword: {
|
|
1168
|
-
name:
|
|
1169
|
-
type:
|
|
1161
|
+
name: "关键词",
|
|
1162
|
+
type: "string",
|
|
1170
1163
|
min: 1,
|
|
1171
1164
|
max: 100
|
|
1172
1165
|
}
|
|
1173
1166
|
},
|
|
1174
|
-
required: [
|
|
1167
|
+
required: ["keyword"],
|
|
1175
1168
|
handler: async (befly, ctx) => {
|
|
1176
1169
|
const result = await befly.db.getList({
|
|
1177
|
-
table:
|
|
1170
|
+
table: "article",
|
|
1178
1171
|
where: {
|
|
1179
1172
|
title: { $like: `%${ctx.body.keyword}%` }
|
|
1180
1173
|
}
|
|
1181
1174
|
});
|
|
1182
1175
|
|
|
1183
|
-
return befly.tool.Yes(
|
|
1176
|
+
return befly.tool.Yes("搜索成功", result);
|
|
1184
1177
|
}
|
|
1185
1178
|
};
|
|
1186
1179
|
```
|
|
@@ -1190,8 +1183,8 @@ export default {
|
|
|
1190
1183
|
```typescript
|
|
1191
1184
|
// apis/webhook/wechat.ts
|
|
1192
1185
|
export default {
|
|
1193
|
-
name:
|
|
1194
|
-
method:
|
|
1186
|
+
name: "微信回调",
|
|
1187
|
+
method: "POST",
|
|
1195
1188
|
auth: false,
|
|
1196
1189
|
rawBody: true, // 不过滤字段,保留完整请求体
|
|
1197
1190
|
handler: async (befly, ctx) => {
|
|
@@ -1199,9 +1192,9 @@ export default {
|
|
|
1199
1192
|
const { ToUserName, FromUserName, MsgType, Content } = ctx.body;
|
|
1200
1193
|
|
|
1201
1194
|
// 处理微信消息
|
|
1202
|
-
befly.logger.info({ msg: Content },
|
|
1195
|
+
befly.logger.info({ msg: Content }, "收到微信消息");
|
|
1203
1196
|
|
|
1204
|
-
return befly.tool.Yes(
|
|
1197
|
+
return befly.tool.Yes("处理成功");
|
|
1205
1198
|
}
|
|
1206
1199
|
};
|
|
1207
1200
|
```
|
|
@@ -1260,7 +1253,7 @@ export default {
|
|
|
1260
1253
|
```typescript
|
|
1261
1254
|
// 在 Hook 中中断
|
|
1262
1255
|
if (!ctx.user?.id) {
|
|
1263
|
-
ctx.response = ErrorResponse(ctx,
|
|
1256
|
+
ctx.response = ErrorResponse(ctx, "未登录");
|
|
1264
1257
|
return; // 后续 Hook 和 handler 不会执行
|
|
1265
1258
|
}
|
|
1266
1259
|
```
|
|
@@ -1347,10 +1340,10 @@ befly.db.cleanFields<T>(
|
|
|
1347
1340
|
```typescript
|
|
1348
1341
|
// 默认排除 null 和 undefined
|
|
1349
1342
|
const cleanData = befly.db.cleanFields({
|
|
1350
|
-
name:
|
|
1343
|
+
name: "John",
|
|
1351
1344
|
age: null,
|
|
1352
1345
|
email: undefined,
|
|
1353
|
-
phone:
|
|
1346
|
+
phone: ""
|
|
1354
1347
|
});
|
|
1355
1348
|
// 结果: { name: 'John', phone: '' }
|
|
1356
1349
|
```
|
|
@@ -1359,7 +1352,7 @@ const cleanData = befly.db.cleanFields({
|
|
|
1359
1352
|
|
|
1360
1353
|
```typescript
|
|
1361
1354
|
// 同时排除 null、undefined 和空字符串
|
|
1362
|
-
const cleanData = befly.db.cleanFields({ name:
|
|
1355
|
+
const cleanData = befly.db.cleanFields({ name: "John", phone: "", age: null }, [null, undefined, ""]);
|
|
1363
1356
|
// 结果: { name: 'John' }
|
|
1364
1357
|
```
|
|
1365
1358
|
|
|
@@ -1367,7 +1360,7 @@ const cleanData = befly.db.cleanFields({ name: 'John', phone: '', age: null }, [
|
|
|
1367
1360
|
|
|
1368
1361
|
```typescript
|
|
1369
1362
|
// 即使值在排除列表中,也保留 status 字段的 null 值
|
|
1370
|
-
const cleanData = befly.db.cleanFields({ name:
|
|
1363
|
+
const cleanData = befly.db.cleanFields({ name: "John", status: null, count: 0 }, [null, undefined], { status: null });
|
|
1371
1364
|
// 结果: { name: 'John', status: null, count: 0 }
|
|
1372
1365
|
```
|
|
1373
1366
|
|
|
@@ -1405,7 +1398,7 @@ fields: {
|
|
|
1405
1398
|
```typescript
|
|
1406
1399
|
// ✅ 推荐:直接使用
|
|
1407
1400
|
const result = await befly.db.insData({
|
|
1408
|
-
table:
|
|
1401
|
+
table: "user",
|
|
1409
1402
|
data: {
|
|
1410
1403
|
username: ctx.body.username,
|
|
1411
1404
|
email: ctx.body.email
|
|
@@ -1421,7 +1414,7 @@ const { username, email } = ctx.body;
|
|
|
1421
1414
|
```typescript
|
|
1422
1415
|
// ✅ 推荐:明确每个字段
|
|
1423
1416
|
await befly.db.insData({
|
|
1424
|
-
table:
|
|
1417
|
+
table: "user",
|
|
1425
1418
|
data: {
|
|
1426
1419
|
username: ctx.body.username,
|
|
1427
1420
|
email: ctx.body.email,
|
|
@@ -1431,7 +1424,7 @@ await befly.db.insData({
|
|
|
1431
1424
|
|
|
1432
1425
|
// ❌ 避免:扩展运算符
|
|
1433
1426
|
await befly.db.insData({
|
|
1434
|
-
table:
|
|
1427
|
+
table: "user",
|
|
1435
1428
|
data: { ...ctx.body } // 危险!可能写入未预期的字段
|
|
1436
1429
|
});
|
|
1437
1430
|
```
|
|
@@ -1443,12 +1436,12 @@ handler: async (befly, ctx) => {
|
|
|
1443
1436
|
try {
|
|
1444
1437
|
// 业务逻辑
|
|
1445
1438
|
const result = await someOperation();
|
|
1446
|
-
return befly.tool.Yes(
|
|
1439
|
+
return befly.tool.Yes("成功", result);
|
|
1447
1440
|
} catch (error: any) {
|
|
1448
1441
|
// 记录错误日志
|
|
1449
|
-
befly.logger.error({ err: error },
|
|
1442
|
+
befly.logger.error({ err: error }, "操作失败");
|
|
1450
1443
|
// 返回友好错误信息
|
|
1451
|
-
return befly.tool.No(
|
|
1444
|
+
return befly.tool.No("操作失败,请稍后重试");
|
|
1452
1445
|
}
|
|
1453
1446
|
};
|
|
1454
1447
|
```
|
|
@@ -1458,7 +1451,7 @@ handler: async (befly, ctx) => {
|
|
|
1458
1451
|
```typescript
|
|
1459
1452
|
// ✅ 推荐:使用 Date.now()
|
|
1460
1453
|
await befly.db.updData({
|
|
1461
|
-
table:
|
|
1454
|
+
table: "user",
|
|
1462
1455
|
data: {
|
|
1463
1456
|
lastLoginTime: Date.now(), // number 类型
|
|
1464
1457
|
lastLoginIp: ctx.ip
|
|
@@ -1478,7 +1471,7 @@ lastLoginTime: new Date(); // 类型不一致
|
|
|
1478
1471
|
|
|
1479
1472
|
```typescript
|
|
1480
1473
|
export default {
|
|
1481
|
-
name:
|
|
1474
|
+
name: "公开接口",
|
|
1482
1475
|
auth: false, // 设置为 false
|
|
1483
1476
|
handler: async (befly, ctx) => {
|
|
1484
1477
|
// ...
|
|
@@ -1494,7 +1487,7 @@ handler: async (befly, ctx) => {
|
|
|
1494
1487
|
const roleCode = ctx.user?.roleCode;
|
|
1495
1488
|
|
|
1496
1489
|
if (!userId) {
|
|
1497
|
-
return befly.tool.No(
|
|
1490
|
+
return befly.tool.No("未登录");
|
|
1498
1491
|
}
|
|
1499
1492
|
};
|
|
1500
1493
|
```
|
|
@@ -1509,12 +1502,12 @@ handler: async (befly, ctx) => {
|
|
|
1509
1502
|
|
|
1510
1503
|
```typescript
|
|
1511
1504
|
// tpl/hooks/requestLog.ts
|
|
1512
|
-
import type { Hook } from
|
|
1505
|
+
import type { Hook } from "befly/types/hook";
|
|
1513
1506
|
|
|
1514
1507
|
const hook: Hook = {
|
|
1515
1508
|
order: 100, // 执行顺序
|
|
1516
1509
|
handler: async (befly, ctx) => {
|
|
1517
|
-
befly.logger.info({ route: ctx.route },
|
|
1510
|
+
befly.logger.info({ route: ctx.route }, "请求开始");
|
|
1518
1511
|
}
|
|
1519
1512
|
};
|
|
1520
1513
|
export default hook;
|
|
@@ -1538,42 +1531,42 @@ export default hook;
|
|
|
1538
1531
|
```typescript
|
|
1539
1532
|
// apis/order/create.ts
|
|
1540
1533
|
export default {
|
|
1541
|
-
name:
|
|
1534
|
+
name: "创建订单",
|
|
1542
1535
|
fields: {
|
|
1543
1536
|
productId: {
|
|
1544
|
-
name:
|
|
1545
|
-
type:
|
|
1537
|
+
name: "商品ID",
|
|
1538
|
+
type: "number",
|
|
1546
1539
|
min: 1
|
|
1547
1540
|
},
|
|
1548
1541
|
quantity: {
|
|
1549
|
-
name:
|
|
1550
|
-
type:
|
|
1542
|
+
name: "数量",
|
|
1543
|
+
type: "number",
|
|
1551
1544
|
min: 1,
|
|
1552
1545
|
max: 999
|
|
1553
1546
|
}
|
|
1554
1547
|
},
|
|
1555
|
-
required: [
|
|
1548
|
+
required: ["productId", "quantity"],
|
|
1556
1549
|
handler: async (befly, ctx) => {
|
|
1557
1550
|
// 使用事务确保库存扣减和订单创建的原子性
|
|
1558
1551
|
const result = await befly.db.transaction(async (trx) => {
|
|
1559
1552
|
// 1. 查询商品信息(带锁)
|
|
1560
1553
|
const product = await trx.getOne({
|
|
1561
|
-
table:
|
|
1554
|
+
table: "product",
|
|
1562
1555
|
where: { id: ctx.body.productId },
|
|
1563
1556
|
forUpdate: true // 行锁
|
|
1564
1557
|
});
|
|
1565
1558
|
|
|
1566
1559
|
if (!product) {
|
|
1567
|
-
throw new Error(
|
|
1560
|
+
throw new Error("商品不存在");
|
|
1568
1561
|
}
|
|
1569
1562
|
|
|
1570
1563
|
if (product.stock < ctx.body.quantity) {
|
|
1571
|
-
throw new Error(
|
|
1564
|
+
throw new Error("库存不足");
|
|
1572
1565
|
}
|
|
1573
1566
|
|
|
1574
1567
|
// 2. 扣减库存
|
|
1575
1568
|
await trx.updData({
|
|
1576
|
-
table:
|
|
1569
|
+
table: "product",
|
|
1577
1570
|
data: {
|
|
1578
1571
|
stock: product.stock - ctx.body.quantity
|
|
1579
1572
|
},
|
|
@@ -1582,19 +1575,19 @@ export default {
|
|
|
1582
1575
|
|
|
1583
1576
|
// 3. 创建订单
|
|
1584
1577
|
const orderId = await trx.insData({
|
|
1585
|
-
table:
|
|
1578
|
+
table: "order",
|
|
1586
1579
|
data: {
|
|
1587
1580
|
userId: ctx.user.id,
|
|
1588
1581
|
productId: ctx.body.productId,
|
|
1589
1582
|
quantity: ctx.body.quantity,
|
|
1590
1583
|
totalPrice: product.price * ctx.body.quantity,
|
|
1591
|
-
status:
|
|
1584
|
+
status: "pending"
|
|
1592
1585
|
}
|
|
1593
1586
|
});
|
|
1594
1587
|
|
|
1595
1588
|
// 4. 创建订单明细
|
|
1596
1589
|
await trx.insData({
|
|
1597
|
-
table:
|
|
1590
|
+
table: "order_item",
|
|
1598
1591
|
data: {
|
|
1599
1592
|
orderId: orderId,
|
|
1600
1593
|
productId: ctx.body.productId,
|
|
@@ -1607,7 +1600,7 @@ export default {
|
|
|
1607
1600
|
return { orderId: orderId };
|
|
1608
1601
|
});
|
|
1609
1602
|
|
|
1610
|
-
return befly.tool.Yes(
|
|
1603
|
+
return befly.tool.Yes("订单创建成功", result);
|
|
1611
1604
|
}
|
|
1612
1605
|
};
|
|
1613
1606
|
```
|
|
@@ -1619,22 +1612,22 @@ export default {
|
|
|
1619
1612
|
```typescript
|
|
1620
1613
|
// apis/user/batchImport.ts
|
|
1621
1614
|
export default {
|
|
1622
|
-
name:
|
|
1615
|
+
name: "批量导入用户",
|
|
1623
1616
|
rawBody: true, // 保留原始请求体
|
|
1624
1617
|
handler: async (befly, ctx) => {
|
|
1625
1618
|
const users = ctx.body.users;
|
|
1626
1619
|
|
|
1627
1620
|
if (!Array.isArray(users) || users.length === 0) {
|
|
1628
|
-
return befly.tool.No(
|
|
1621
|
+
return befly.tool.No("用户列表不能为空");
|
|
1629
1622
|
}
|
|
1630
1623
|
|
|
1631
1624
|
if (users.length > 100) {
|
|
1632
|
-
return befly.tool.No(
|
|
1625
|
+
return befly.tool.No("单次导入不能超过100条");
|
|
1633
1626
|
}
|
|
1634
1627
|
|
|
1635
1628
|
// 批量插入
|
|
1636
1629
|
const result = await befly.db.batchInsert({
|
|
1637
|
-
table:
|
|
1630
|
+
table: "user",
|
|
1638
1631
|
data: users.map((user: any) => ({
|
|
1639
1632
|
username: user.username,
|
|
1640
1633
|
email: user.email,
|
|
@@ -1643,7 +1636,7 @@ export default {
|
|
|
1643
1636
|
}))
|
|
1644
1637
|
});
|
|
1645
1638
|
|
|
1646
|
-
return befly.tool.Yes(
|
|
1639
|
+
return befly.tool.Yes("导入成功", {
|
|
1647
1640
|
total: users.length,
|
|
1648
1641
|
inserted: result.affectedRows
|
|
1649
1642
|
});
|
|
@@ -1656,25 +1649,25 @@ export default {
|
|
|
1656
1649
|
```typescript
|
|
1657
1650
|
// apis/article/batchUpdate.ts
|
|
1658
1651
|
export default {
|
|
1659
|
-
name:
|
|
1652
|
+
name: "批量更新文章状态",
|
|
1660
1653
|
rawBody: true,
|
|
1661
1654
|
handler: async (befly, ctx) => {
|
|
1662
1655
|
const { ids, state } = ctx.body;
|
|
1663
1656
|
|
|
1664
1657
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1665
|
-
return befly.tool.No(
|
|
1658
|
+
return befly.tool.No("文章ID列表不能为空");
|
|
1666
1659
|
}
|
|
1667
1660
|
|
|
1668
1661
|
// 批量更新
|
|
1669
1662
|
const result = await befly.db.updData({
|
|
1670
|
-
table:
|
|
1663
|
+
table: "article",
|
|
1671
1664
|
data: { state: state },
|
|
1672
1665
|
where: {
|
|
1673
1666
|
id: { $in: ids }
|
|
1674
1667
|
}
|
|
1675
1668
|
});
|
|
1676
1669
|
|
|
1677
|
-
return befly.tool.Yes(
|
|
1670
|
+
return befly.tool.Yes("更新成功", {
|
|
1678
1671
|
updated: result.affectedRows
|
|
1679
1672
|
});
|
|
1680
1673
|
}
|
|
@@ -1686,24 +1679,24 @@ export default {
|
|
|
1686
1679
|
```typescript
|
|
1687
1680
|
// apis/log/batchDelete.ts
|
|
1688
1681
|
export default {
|
|
1689
|
-
name:
|
|
1682
|
+
name: "批量删除日志",
|
|
1690
1683
|
rawBody: true,
|
|
1691
1684
|
handler: async (befly, ctx) => {
|
|
1692
1685
|
const { ids } = ctx.body;
|
|
1693
1686
|
|
|
1694
1687
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
1695
|
-
return befly.tool.No(
|
|
1688
|
+
return befly.tool.No("日志ID列表不能为空");
|
|
1696
1689
|
}
|
|
1697
1690
|
|
|
1698
1691
|
// 批量软删除
|
|
1699
1692
|
const result = await befly.db.delData({
|
|
1700
|
-
table:
|
|
1693
|
+
table: "operate_log",
|
|
1701
1694
|
where: {
|
|
1702
1695
|
id: { $in: ids }
|
|
1703
1696
|
}
|
|
1704
1697
|
});
|
|
1705
1698
|
|
|
1706
|
-
return befly.tool.Yes(
|
|
1699
|
+
return befly.tool.Yes("删除成功", {
|
|
1707
1700
|
deleted: result.affectedRows
|
|
1708
1701
|
});
|
|
1709
1702
|
}
|
|
@@ -1765,25 +1758,25 @@ export default {
|
|
|
1765
1758
|
```typescript
|
|
1766
1759
|
// apis/article/listWithAuthor.ts
|
|
1767
1760
|
export default {
|
|
1768
|
-
name:
|
|
1761
|
+
name: "文章列表(含作者)",
|
|
1769
1762
|
handler: async (befly, ctx) => {
|
|
1770
1763
|
const result = await befly.db.getList({
|
|
1771
|
-
table:
|
|
1764
|
+
table: "article",
|
|
1772
1765
|
joins: [
|
|
1773
1766
|
{
|
|
1774
|
-
type:
|
|
1775
|
-
table:
|
|
1776
|
-
alias:
|
|
1777
|
-
on: {
|
|
1767
|
+
type: "LEFT",
|
|
1768
|
+
table: "user",
|
|
1769
|
+
alias: "author",
|
|
1770
|
+
on: { "article.authorId": "author.id" }
|
|
1778
1771
|
}
|
|
1779
1772
|
],
|
|
1780
|
-
columns: [
|
|
1773
|
+
columns: ["article.id", "article.title", "article.createdAt", "author.nickname AS authorName"],
|
|
1781
1774
|
page: ctx.body.page || 1,
|
|
1782
1775
|
limit: ctx.body.limit || 10,
|
|
1783
|
-
orderBy: [
|
|
1776
|
+
orderBy: ["article.createdAt#DESC"]
|
|
1784
1777
|
});
|
|
1785
1778
|
|
|
1786
|
-
return befly.tool.Yes(
|
|
1779
|
+
return befly.tool.Yes("获取成功", result);
|
|
1787
1780
|
}
|
|
1788
1781
|
};
|
|
1789
1782
|
```
|
|
@@ -1793,30 +1786,29 @@ export default {
|
|
|
1793
1786
|
```typescript
|
|
1794
1787
|
// apis/config/getSiteConfig.ts
|
|
1795
1788
|
export default {
|
|
1796
|
-
name:
|
|
1789
|
+
name: "获取站点配置",
|
|
1797
1790
|
auth: false,
|
|
1798
|
-
cache: 300, // 缓存 5 分钟
|
|
1799
1791
|
handler: async (befly, ctx) => {
|
|
1800
1792
|
// 先从缓存获取
|
|
1801
|
-
const cacheKey =
|
|
1793
|
+
const cacheKey = "site:config";
|
|
1802
1794
|
let config = await befly.redis.get(cacheKey);
|
|
1803
1795
|
|
|
1804
1796
|
if (!config) {
|
|
1805
1797
|
// 缓存不存在,从数据库查询
|
|
1806
1798
|
const result = await befly.db.getAll({
|
|
1807
|
-
table:
|
|
1799
|
+
table: "sys_config",
|
|
1808
1800
|
where: { state: 1 }
|
|
1809
1801
|
});
|
|
1810
1802
|
|
|
1811
1803
|
config = result.lists; // 获取配置列表
|
|
1812
1804
|
|
|
1813
1805
|
// 写入缓存
|
|
1814
|
-
await befly.redis.set(cacheKey, JSON.stringify(config),
|
|
1806
|
+
await befly.redis.set(cacheKey, JSON.stringify(config), "EX", 300);
|
|
1815
1807
|
} else {
|
|
1816
1808
|
config = JSON.parse(config);
|
|
1817
1809
|
}
|
|
1818
1810
|
|
|
1819
|
-
return befly.tool.Yes(
|
|
1811
|
+
return befly.tool.Yes("获取成功", config);
|
|
1820
1812
|
}
|
|
1821
1813
|
};
|
|
1822
1814
|
```
|
|
@@ -1826,22 +1818,22 @@ export default {
|
|
|
1826
1818
|
```typescript
|
|
1827
1819
|
// apis/task/execute.ts
|
|
1828
1820
|
export default {
|
|
1829
|
-
name:
|
|
1821
|
+
name: "执行定时任务",
|
|
1830
1822
|
handler: async (befly, ctx) => {
|
|
1831
1823
|
const lockKey = `lock:task:${ctx.body.taskId}`;
|
|
1832
1824
|
|
|
1833
1825
|
// 尝试获取锁(30秒超时)
|
|
1834
|
-
const locked = await befly.redis.set(lockKey, ctx.requestId,
|
|
1826
|
+
const locked = await befly.redis.set(lockKey, ctx.requestId, "EX", 30, "NX");
|
|
1835
1827
|
|
|
1836
1828
|
if (!locked) {
|
|
1837
|
-
return befly.tool.No(
|
|
1829
|
+
return befly.tool.No("任务正在执行中,请稍后");
|
|
1838
1830
|
}
|
|
1839
1831
|
|
|
1840
1832
|
try {
|
|
1841
1833
|
// 执行任务逻辑
|
|
1842
1834
|
await executeTask(ctx.body.taskId);
|
|
1843
1835
|
|
|
1844
|
-
return befly.tool.Yes(
|
|
1836
|
+
return befly.tool.Yes("任务执行成功");
|
|
1845
1837
|
} finally {
|
|
1846
1838
|
// 释放锁
|
|
1847
1839
|
await befly.redis.del(lockKey);
|
|
@@ -1855,27 +1847,27 @@ export default {
|
|
|
1855
1847
|
```typescript
|
|
1856
1848
|
// apis/report/exportUsers.ts
|
|
1857
1849
|
export default {
|
|
1858
|
-
name:
|
|
1850
|
+
name: "导出用户数据",
|
|
1859
1851
|
handler: async (befly, ctx) => {
|
|
1860
1852
|
// 查询所有用户(不分页,注意上限 10000 条)
|
|
1861
1853
|
const result = await befly.db.getAll({
|
|
1862
|
-
table:
|
|
1863
|
-
columns: [
|
|
1854
|
+
table: "user",
|
|
1855
|
+
columns: ["id", "username", "nickname", "email", "phone", "createdAt"],
|
|
1864
1856
|
where: { state: 1 },
|
|
1865
|
-
orderBy: [
|
|
1857
|
+
orderBy: ["createdAt#DESC"]
|
|
1866
1858
|
});
|
|
1867
1859
|
|
|
1868
1860
|
// 转换为 CSV 格式
|
|
1869
|
-
const headers = [
|
|
1861
|
+
const headers = ["ID", "用户名", "昵称", "邮箱", "手机", "注册时间"];
|
|
1870
1862
|
const rows = result.lists.map((u: any) => [u.id, u.username, u.nickname, u.email, u.phone, new Date(u.createdAt).toLocaleString()]);
|
|
1871
1863
|
|
|
1872
|
-
const csv = [headers.join(
|
|
1864
|
+
const csv = [headers.join(","), ...rows.map((r: any[]) => r.join(","))].join("\n");
|
|
1873
1865
|
|
|
1874
1866
|
// 返回 CSV 文件(注意:如果 total > 10000,只会导出前 10000 条)
|
|
1875
1867
|
return new Response(csv, {
|
|
1876
1868
|
headers: {
|
|
1877
|
-
|
|
1878
|
-
|
|
1869
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
1870
|
+
"Content-Disposition": 'attachment; filename="users.csv"'
|
|
1879
1871
|
}
|
|
1880
1872
|
});
|
|
1881
1873
|
}
|
|
@@ -1887,17 +1879,17 @@ export default {
|
|
|
1887
1879
|
```typescript
|
|
1888
1880
|
// apis/file/download.ts
|
|
1889
1881
|
export default {
|
|
1890
|
-
name:
|
|
1891
|
-
required: [
|
|
1882
|
+
name: "文件下载",
|
|
1883
|
+
required: ["fileId"],
|
|
1892
1884
|
handler: async (befly, ctx) => {
|
|
1893
1885
|
// 查询文件信息
|
|
1894
1886
|
const file = await befly.db.getOne({
|
|
1895
|
-
table:
|
|
1887
|
+
table: "file",
|
|
1896
1888
|
where: { id: ctx.body.fileId }
|
|
1897
1889
|
});
|
|
1898
1890
|
|
|
1899
1891
|
if (!file) {
|
|
1900
|
-
return befly.tool.No(
|
|
1892
|
+
return befly.tool.No("文件不存在");
|
|
1901
1893
|
}
|
|
1902
1894
|
|
|
1903
1895
|
// 读取文件并返回流
|
|
@@ -1905,9 +1897,9 @@ export default {
|
|
|
1905
1897
|
|
|
1906
1898
|
return new Response(fileStream, {
|
|
1907
1899
|
headers: {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1900
|
+
"Content-Type": file.mimeType,
|
|
1901
|
+
"Content-Disposition": `attachment; filename="${encodeURIComponent(file.name)}"`,
|
|
1902
|
+
"Content-Length": String(file.size)
|
|
1911
1903
|
}
|
|
1912
1904
|
});
|
|
1913
1905
|
}
|