befly 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/env.js +1 -1
- package/main.js +63 -18
- package/package.json +2 -2
- package/utils/validate.js +66 -8
- package/plugins/cors.js +0 -15
package/config/env.js
CHANGED
|
@@ -21,7 +21,7 @@ export const Env = {
|
|
|
21
21
|
LOG_TO_CONSOLE: Number(process.env.LOG_TO_CONSOLE),
|
|
22
22
|
LOG_MAX_SIZE: Number(process.env.LOG_MAX_SIZE),
|
|
23
23
|
// 时区
|
|
24
|
-
|
|
24
|
+
TZ: process.env.TZ,
|
|
25
25
|
// 数据库配置
|
|
26
26
|
MYSQL_ENABLE: Number(process.env.MYSQL_ENABLE),
|
|
27
27
|
MYSQL_HOST: process.env.MYSQL_HOST,
|
package/main.js
CHANGED
|
@@ -10,6 +10,19 @@ import { Crypto2 } from './utils/crypto.js';
|
|
|
10
10
|
import { XMLParser } from './libs/xml/XMLParser.js';
|
|
11
11
|
import { isEmptyObject, isType, pickFields, sortPlugins, RYes, RNo, filename2, dirname2 } from './utils/util.js';
|
|
12
12
|
|
|
13
|
+
const setCorsOptions = (req) => {
|
|
14
|
+
return {
|
|
15
|
+
headers: {
|
|
16
|
+
'Access-Control-Allow-Origin': Env.ALLOWED_ORIGIN || req.headers.get('origin') || '*',
|
|
17
|
+
'Access-Control-Allow-Methods': Env.ALLOWED_METHODS || 'GET, POST, PUT, DELETE, OPTIONS',
|
|
18
|
+
'Access-Control-Allow-Headers': Env.ALLOWED_HEADERS || 'Content-Type, Authorization, authorization, token',
|
|
19
|
+
'Access-Control-Expose-Headers': Env.EXPOSE_HEADERS || 'Content-Range, X-Content-Range, Authorization, authorization, token',
|
|
20
|
+
'Access-Control-Max-Age': Env.MAX_AGE || 86400,
|
|
21
|
+
'Access-Control-Allow-Credentials': Env.ALLOW_CREDENTIALS || 'true'
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
13
26
|
class Befly {
|
|
14
27
|
constructor(options = {}) {
|
|
15
28
|
this.apiRoutes = new Map();
|
|
@@ -306,19 +319,28 @@ class Befly {
|
|
|
306
319
|
hostname: Env.APP_HOST,
|
|
307
320
|
routes: {
|
|
308
321
|
'/': async (req) => {
|
|
309
|
-
return Response.json(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
322
|
+
return Response.json(
|
|
323
|
+
{
|
|
324
|
+
code: 0,
|
|
325
|
+
msg: 'Befly 接口服务已启动',
|
|
326
|
+
data: {
|
|
327
|
+
mode: Env.NODE_ENV
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
headers: corsOptions.headers
|
|
314
332
|
}
|
|
315
|
-
|
|
333
|
+
);
|
|
316
334
|
},
|
|
317
335
|
'/api/*': async (req) => {
|
|
318
336
|
try {
|
|
337
|
+
const corsOptions = setCorsOptions(req);
|
|
319
338
|
// 直接返回options请求
|
|
320
339
|
if (req.method === 'OPTIONS') {
|
|
321
|
-
return new Response(
|
|
340
|
+
return new Response(null, {
|
|
341
|
+
status: 204,
|
|
342
|
+
headers: corsOptions.headers
|
|
343
|
+
});
|
|
322
344
|
}
|
|
323
345
|
// 初始化请求数据存储
|
|
324
346
|
const ctx = {
|
|
@@ -334,7 +356,10 @@ class Befly {
|
|
|
334
356
|
const api = this.apiRoutes.get(apiPath);
|
|
335
357
|
|
|
336
358
|
// 接口不存在
|
|
337
|
-
if (!api)
|
|
359
|
+
if (!api)
|
|
360
|
+
return Response.json(RNo('接口不存在'), {
|
|
361
|
+
headers: corsOptions.headers
|
|
362
|
+
});
|
|
338
363
|
|
|
339
364
|
const authHeader = req.headers.get('authorization');
|
|
340
365
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
@@ -386,7 +411,9 @@ class Befly {
|
|
|
386
411
|
stack: err.stack
|
|
387
412
|
});
|
|
388
413
|
|
|
389
|
-
return Response.json(RNo('无效的请求参数格式')
|
|
414
|
+
return Response.json(RNo('无效的请求参数格式'), {
|
|
415
|
+
headers: corsOptions.headers
|
|
416
|
+
});
|
|
390
417
|
}
|
|
391
418
|
}
|
|
392
419
|
|
|
@@ -416,17 +443,23 @@ class Befly {
|
|
|
416
443
|
|
|
417
444
|
// 登录验证 auth 有3种值 分别为 true、false、['admin', 'user']
|
|
418
445
|
if (api.auth === true && !ctx.user.id) {
|
|
419
|
-
return Response.json(RNo('未登录')
|
|
446
|
+
return Response.json(RNo('未登录'), {
|
|
447
|
+
headers: corsOptions.headers
|
|
448
|
+
});
|
|
420
449
|
}
|
|
421
450
|
|
|
422
451
|
if (api.auth && api.auth !== true && ctx.user.role !== api.auth) {
|
|
423
|
-
return Response.json(RNo('没有权限')
|
|
452
|
+
return Response.json(RNo('没有权限'), {
|
|
453
|
+
headers: corsOptions.headers
|
|
454
|
+
});
|
|
424
455
|
}
|
|
425
456
|
|
|
426
457
|
// 参数验证
|
|
427
458
|
const validate = validator.validate(ctx.body, api.fields, api.required);
|
|
428
459
|
if (validate.code !== 0) {
|
|
429
|
-
return Response.json(RNo('无效的请求参数格式', validate.fields)
|
|
460
|
+
return Response.json(RNo('无效的请求参数格式', validate.fields), {
|
|
461
|
+
headers: corsOptions.headers
|
|
462
|
+
});
|
|
430
463
|
}
|
|
431
464
|
|
|
432
465
|
// 执行函数
|
|
@@ -434,9 +467,13 @@ class Befly {
|
|
|
434
467
|
|
|
435
468
|
// 返回数据
|
|
436
469
|
if (result && typeof result === 'object' && 'code' in result) {
|
|
437
|
-
return Response.json(result
|
|
470
|
+
return Response.json(result, {
|
|
471
|
+
headers: corsOptions.headers
|
|
472
|
+
});
|
|
438
473
|
} else {
|
|
439
|
-
return new Response(result
|
|
474
|
+
return new Response(result, {
|
|
475
|
+
headers: corsOptions.headers
|
|
476
|
+
});
|
|
440
477
|
}
|
|
441
478
|
} catch (error) {
|
|
442
479
|
Logger.error({
|
|
@@ -445,10 +482,13 @@ class Befly {
|
|
|
445
482
|
stack: error.stack,
|
|
446
483
|
url: req.url
|
|
447
484
|
});
|
|
448
|
-
return Response.json(RNo('内部服务器错误')
|
|
485
|
+
return Response.json(RNo('内部服务器错误'), {
|
|
486
|
+
headers: corsOptions.headers
|
|
487
|
+
});
|
|
449
488
|
}
|
|
450
489
|
},
|
|
451
490
|
'/*': async (req) => {
|
|
491
|
+
const corsOptions = setCorsOptions(req);
|
|
452
492
|
const url = new URL(req.url);
|
|
453
493
|
const filePath = path.join(process.cwd(), 'public', url.pathname);
|
|
454
494
|
|
|
@@ -457,14 +497,19 @@ class Befly {
|
|
|
457
497
|
if (await file.exists()) {
|
|
458
498
|
return new Response(file, {
|
|
459
499
|
headers: {
|
|
460
|
-
'Content-Type': file.type || 'application/octet-stream'
|
|
500
|
+
'Content-Type': file.type || 'application/octet-stream',
|
|
501
|
+
...corsOptions.headers
|
|
461
502
|
}
|
|
462
503
|
});
|
|
463
504
|
} else {
|
|
464
|
-
return Response.json(RNo('文件未找到')
|
|
505
|
+
return Response.json(RNo('文件未找到'), {
|
|
506
|
+
headers: corsOptions.headers
|
|
507
|
+
});
|
|
465
508
|
}
|
|
466
509
|
} catch (error) {
|
|
467
|
-
return Response.json(RNo('文件读取失败')
|
|
510
|
+
return Response.json(RNo('文件读取失败'), {
|
|
511
|
+
headers: corsOptions.headers
|
|
512
|
+
});
|
|
468
513
|
}
|
|
469
514
|
},
|
|
470
515
|
...(this.appOptions.routes || {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Buma - 为 Bun 专属打造的 API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"README.md",
|
|
50
50
|
"vitest.config.js"
|
|
51
51
|
],
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "63bf1b29c48afde393f038181900222e6b124fc0"
|
|
53
53
|
}
|
package/utils/validate.js
CHANGED
|
@@ -134,7 +134,13 @@ export class Validator {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (spec && spec.trim() !== '') {
|
|
137
|
-
|
|
137
|
+
// 检查是否为枚举格式
|
|
138
|
+
if (this.isEnumSpec(spec)) {
|
|
139
|
+
return this.validateEnum(value, spec, name, fieldName, 'number');
|
|
140
|
+
} else {
|
|
141
|
+
// 原有的表达式验证逻辑
|
|
142
|
+
return this.validateNumberExpression(value, name, spec, fieldName);
|
|
143
|
+
}
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
return null;
|
|
@@ -143,6 +149,46 @@ export class Validator {
|
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
|
|
152
|
+
/**
|
|
153
|
+
* 判断是否为枚举格式
|
|
154
|
+
*/
|
|
155
|
+
isEnumSpec(spec) {
|
|
156
|
+
return spec && spec.startsWith('enum#');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 验证枚举值
|
|
161
|
+
*/
|
|
162
|
+
validateEnum(value, spec, name, fieldName, type) {
|
|
163
|
+
// 解析枚举值 "enum#1,2,3" -> ["1", "2", "3"]
|
|
164
|
+
const enumValues = spec
|
|
165
|
+
.substring(5)
|
|
166
|
+
.split(',')
|
|
167
|
+
.map((v) => v.trim());
|
|
168
|
+
|
|
169
|
+
if (type === 'number') {
|
|
170
|
+
// 数字类型:转换枚举值为数字进行比较
|
|
171
|
+
const numericEnumValues = enumValues.map((v) => parseFloat(v));
|
|
172
|
+
if (!numericEnumValues.includes(value)) {
|
|
173
|
+
return `${name}(${fieldName})必须是以下值之一: ${enumValues.join(', ')}`;
|
|
174
|
+
}
|
|
175
|
+
} else if (type === 'string') {
|
|
176
|
+
// 字符串类型:直接比较
|
|
177
|
+
if (!enumValues.includes(value)) {
|
|
178
|
+
return `${name}(${fieldName})必须是以下值之一: ${enumValues.join(', ')}`;
|
|
179
|
+
}
|
|
180
|
+
} else if (type === 'array') {
|
|
181
|
+
// 数组类型:检查每个元素是否在枚举值中
|
|
182
|
+
for (const item of value) {
|
|
183
|
+
if (!enumValues.includes(String(item))) {
|
|
184
|
+
return `${name}(${fieldName})中的元素"${item}"必须是以下值之一: ${enumValues.join(', ')}`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
146
192
|
/**
|
|
147
193
|
* 验证数字表达式
|
|
148
194
|
*/
|
|
@@ -196,9 +242,15 @@ export class Validator {
|
|
|
196
242
|
}
|
|
197
243
|
|
|
198
244
|
if (spec && spec.trim() !== '') {
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
return
|
|
245
|
+
// 检查是否为枚举格式
|
|
246
|
+
if (this.isEnumSpec(spec)) {
|
|
247
|
+
return this.validateEnum(value, spec, name, fieldName, 'string');
|
|
248
|
+
} else {
|
|
249
|
+
// 原有的正则表达式验证逻辑
|
|
250
|
+
const regExp = new RegExp(spec);
|
|
251
|
+
if (!regExp.test(value)) {
|
|
252
|
+
return `${name}(${fieldName})格式不正确`;
|
|
253
|
+
}
|
|
202
254
|
}
|
|
203
255
|
}
|
|
204
256
|
|
|
@@ -226,10 +278,16 @@ export class Validator {
|
|
|
226
278
|
}
|
|
227
279
|
|
|
228
280
|
if (spec && spec.trim() !== '') {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
281
|
+
// 检查是否为枚举格式
|
|
282
|
+
if (this.isEnumSpec(spec)) {
|
|
283
|
+
return this.validateEnum(value, spec, name, fieldName, 'array');
|
|
284
|
+
} else {
|
|
285
|
+
// 原有的正则表达式验证逻辑
|
|
286
|
+
const regExp = new RegExp(spec);
|
|
287
|
+
for (const item of value) {
|
|
288
|
+
if (!regExp.test(String(item))) {
|
|
289
|
+
return `${name}(${fieldName})中的元素"${item}"格式不正确`;
|
|
290
|
+
}
|
|
233
291
|
}
|
|
234
292
|
}
|
|
235
293
|
}
|
package/plugins/cors.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
after: ['_redis', '_db'],
|
|
3
|
-
async onGet(befly, ctx, req) {
|
|
4
|
-
// 设置 CORS 头部
|
|
5
|
-
req.headers.set('Access-Control-Allow-Origin', req.headers.get('origin'));
|
|
6
|
-
req.headers.set('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
|
|
7
|
-
req.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
8
|
-
req.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
9
|
-
|
|
10
|
-
// 处理预检请求
|
|
11
|
-
if (req.method === 'OPTIONS') {
|
|
12
|
-
req.status = 204;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|