befly 3.9.10 → 3.9.12

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/hooks/parser.ts CHANGED
@@ -25,31 +25,52 @@ const hook: Hook = {
25
25
  // GET 请求:解析查询参数
26
26
  if (ctx.req.method === 'GET') {
27
27
  const url = new URL(ctx.req.url);
28
- if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
29
- ctx.body = pickFields(Object.fromEntries(url.searchParams), Object.keys(ctx.api.fields));
28
+ const params = Object.fromEntries(url.searchParams);
29
+ // rawBody 模式:保留完整请求参数,不过滤字段
30
+ if (ctx.api.rawBody) {
31
+ ctx.body = params;
32
+ } else if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
33
+ ctx.body = pickFields(params, Object.keys(ctx.api.fields));
30
34
  } else {
31
- ctx.body = Object.fromEntries(url.searchParams);
35
+ ctx.body = params;
32
36
  }
33
37
  } else if (ctx.req.method === 'POST') {
34
38
  // POST 请求:解析请求体
35
39
  const contentType = ctx.req.headers.get('content-type') || '';
40
+ // 获取 URL 查询参数(POST 请求也可能带参数,如微信回调)
41
+ const url = new URL(ctx.req.url);
42
+ const queryParams = Object.fromEntries(url.searchParams);
43
+
36
44
  try {
37
45
  // JSON 格式
38
46
  if (contentType.includes('application/json')) {
39
47
  const body = (await ctx.req.json()) as Record<string, any>;
40
- if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
41
- ctx.body = pickFields(body, Object.keys(ctx.api.fields));
48
+ // 合并 URL 参数和请求体(请求体优先)
49
+ const merged = { ...queryParams, ...body };
50
+ // rawBody 模式:保留完整请求体,不过滤字段
51
+ if (ctx.api.rawBody) {
52
+ ctx.body = merged;
53
+ } else if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
54
+ ctx.body = pickFields(merged, Object.keys(ctx.api.fields));
42
55
  } else {
43
- ctx.body = body;
56
+ ctx.body = merged;
44
57
  }
45
58
  } else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
46
59
  // XML 格式
47
60
  const text = await ctx.req.text();
48
- const body = xmlParser.parse(text) as Record<string, any>;
49
- if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
50
- ctx.body = pickFields(body, Object.keys(ctx.api.fields));
61
+ const parsed = xmlParser.parse(text) as Record<string, any>;
62
+ // 提取根节点内容(如 xml),使 body 扁平化
63
+ const rootKey = Object.keys(parsed)[0];
64
+ const body = rootKey && typeof parsed[rootKey] === 'object' ? parsed[rootKey] : parsed;
65
+ // 合并 URL 参数和请求体(请求体优先)
66
+ const merged = { ...queryParams, ...body };
67
+ // rawBody 模式:保留完整请求体,不过滤字段
68
+ if (ctx.api.rawBody) {
69
+ ctx.body = merged;
70
+ } else if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
71
+ ctx.body = pickFields(merged, Object.keys(ctx.api.fields));
51
72
  } else {
52
- ctx.body = body;
73
+ ctx.body = merged;
53
74
  }
54
75
  } else {
55
76
  // 不支持的 Content-Type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "3.9.10",
3
+ "version": "3.9.12",
4
4
  "description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
5
5
  "type": "module",
6
6
  "private": false,
@@ -74,7 +74,7 @@
74
74
  "pino": "^10.1.0",
75
75
  "pino-roll": "^4.0.0"
76
76
  },
77
- "gitHead": "c521a468291c1555672e972db43021dda1763402",
77
+ "gitHead": "1065d8c11e32a4d9cf6177ad484500c4c98b1e2f",
78
78
  "devDependencies": {
79
79
  "typescript": "^5.9.3"
80
80
  }
package/router/api.ts CHANGED
@@ -33,6 +33,7 @@ export function apiHandler(apis: Map<string, ApiRoute>, hooks: Hook[], context:
33
33
  const clientIp = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.headers.get('x-real-ip') || 'unknown';
34
34
 
35
35
  const ctx: RequestContext = {
36
+ method: req.method,
36
37
  body: {},
37
38
  user: {},
38
39
  req: req,
@@ -60,7 +61,16 @@ export function apiHandler(apis: Map<string, ApiRoute>, hooks: Hook[], context:
60
61
  }
61
62
  }
62
63
 
63
- // 5. 执行 API handler
64
+ // 5. 执行 preprocess 预处理(如果有)
65
+ if (ctx.api?.preprocess) {
66
+ await ctx.api.preprocess(context, ctx);
67
+ // 如果 preprocess 设置了 response,停止执行
68
+ if (ctx.response) {
69
+ return ctx.response;
70
+ }
71
+ }
72
+
73
+ // 6. 执行 API handler
64
74
  if (!ctx.api) {
65
75
  if (req.method !== 'OPTIONS') {
66
76
  ctx.response = Response.json(
@@ -83,7 +93,7 @@ export function apiHandler(apis: Map<string, ApiRoute>, hooks: Hook[], context:
83
93
  }
84
94
  }
85
95
 
86
- // 6. 返回响应(自动处理 response/result/日志)
96
+ // 7. 返回响应(自动处理 response/result/日志)
87
97
  return FinalResponse(ctx);
88
98
  } catch (err: any) {
89
99
  // 全局错误处理
package/types/api.d.ts CHANGED
@@ -85,6 +85,18 @@ export interface ApiRoute<T = any, R = any> {
85
85
  /** 必填字段(可选,默认 []) */
86
86
  required?: string[];
87
87
 
88
+ /** 是否保留原始请求体(可选,默认 false)
89
+ * - true: 不过滤字段,保留完整请求体(适用于微信回调、webhook 等场景)
90
+ * - false: 根据 fields 定义过滤字段
91
+ */
92
+ rawBody?: boolean;
93
+
94
+ /** 请求预处理函数(可选,在 handler 之前执行)
95
+ * 用于解密、转换请求数据等场景
96
+ * 可以修改 ctx.body
97
+ */
98
+ preprocess?: ApiHandler<T, void>;
99
+
88
100
  /** 缓存配置(可选,单位:秒) */
89
101
  cache?: number;
90
102
 
@@ -8,6 +8,8 @@ import type { ApiRoute } from './api.js';
8
8
  * 请求上下文接口
9
9
  */
10
10
  export interface RequestContext {
11
+ /** 请求方法 (GET/POST) */
12
+ method: string;
11
13
  /** 请求体参数 */
12
14
  body: Record<string, any>;
13
15
  /** 用户信息 */