befly 1.1.2 → 1.2.0
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 +45 -10
- package/package.json +2 -2
- package/plugins/db.js +0 -1
- 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();
|
|
@@ -316,9 +329,13 @@ class Befly {
|
|
|
316
329
|
},
|
|
317
330
|
'/api/*': async (req) => {
|
|
318
331
|
try {
|
|
332
|
+
const corsOptions = setCorsOptions(req);
|
|
319
333
|
// 直接返回options请求
|
|
320
334
|
if (req.method === 'OPTIONS') {
|
|
321
|
-
return new Response(
|
|
335
|
+
return new Response(null, {
|
|
336
|
+
status: 204,
|
|
337
|
+
headers: corsOptions.headers
|
|
338
|
+
});
|
|
322
339
|
}
|
|
323
340
|
// 初始化请求数据存储
|
|
324
341
|
const ctx = {
|
|
@@ -416,17 +433,23 @@ class Befly {
|
|
|
416
433
|
|
|
417
434
|
// 登录验证 auth 有3种值 分别为 true、false、['admin', 'user']
|
|
418
435
|
if (api.auth === true && !ctx.user.id) {
|
|
419
|
-
return Response.json(RNo('未登录')
|
|
436
|
+
return Response.json(RNo('未登录'), {
|
|
437
|
+
headers: corsOptions.headers
|
|
438
|
+
});
|
|
420
439
|
}
|
|
421
440
|
|
|
422
441
|
if (api.auth && api.auth !== true && ctx.user.role !== api.auth) {
|
|
423
|
-
return Response.json(RNo('没有权限')
|
|
442
|
+
return Response.json(RNo('没有权限'), {
|
|
443
|
+
headers: corsOptions.headers
|
|
444
|
+
});
|
|
424
445
|
}
|
|
425
446
|
|
|
426
447
|
// 参数验证
|
|
427
448
|
const validate = validator.validate(ctx.body, api.fields, api.required);
|
|
428
449
|
if (validate.code !== 0) {
|
|
429
|
-
return Response.json(RNo('无效的请求参数格式', validate.fields)
|
|
450
|
+
return Response.json(RNo('无效的请求参数格式', validate.fields), {
|
|
451
|
+
headers: corsOptions.headers
|
|
452
|
+
});
|
|
430
453
|
}
|
|
431
454
|
|
|
432
455
|
// 执行函数
|
|
@@ -434,9 +457,13 @@ class Befly {
|
|
|
434
457
|
|
|
435
458
|
// 返回数据
|
|
436
459
|
if (result && typeof result === 'object' && 'code' in result) {
|
|
437
|
-
return Response.json(result
|
|
460
|
+
return Response.json(result, {
|
|
461
|
+
headers: corsOptions.headers
|
|
462
|
+
});
|
|
438
463
|
} else {
|
|
439
|
-
return new Response(result
|
|
464
|
+
return new Response(result, {
|
|
465
|
+
headers: corsOptions.headers
|
|
466
|
+
});
|
|
440
467
|
}
|
|
441
468
|
} catch (error) {
|
|
442
469
|
Logger.error({
|
|
@@ -445,10 +472,13 @@ class Befly {
|
|
|
445
472
|
stack: error.stack,
|
|
446
473
|
url: req.url
|
|
447
474
|
});
|
|
448
|
-
return Response.json(RNo('内部服务器错误')
|
|
475
|
+
return Response.json(RNo('内部服务器错误'), {
|
|
476
|
+
headers: corsOptions.headers
|
|
477
|
+
});
|
|
449
478
|
}
|
|
450
479
|
},
|
|
451
480
|
'/*': async (req) => {
|
|
481
|
+
const corsOptions = setCorsOptions(req);
|
|
452
482
|
const url = new URL(req.url);
|
|
453
483
|
const filePath = path.join(process.cwd(), 'public', url.pathname);
|
|
454
484
|
|
|
@@ -457,14 +487,19 @@ class Befly {
|
|
|
457
487
|
if (await file.exists()) {
|
|
458
488
|
return new Response(file, {
|
|
459
489
|
headers: {
|
|
460
|
-
'Content-Type': file.type || 'application/octet-stream'
|
|
490
|
+
'Content-Type': file.type || 'application/octet-stream',
|
|
491
|
+
...corsOptions.headers
|
|
461
492
|
}
|
|
462
493
|
});
|
|
463
494
|
} else {
|
|
464
|
-
return Response.json(RNo('文件未找到')
|
|
495
|
+
return Response.json(RNo('文件未找到'), {
|
|
496
|
+
headers: corsOptions.headers
|
|
497
|
+
});
|
|
465
498
|
}
|
|
466
499
|
} catch (error) {
|
|
467
|
-
return Response.json(RNo('文件读取失败')
|
|
500
|
+
return Response.json(RNo('文件读取失败'), {
|
|
501
|
+
headers: corsOptions.headers
|
|
502
|
+
});
|
|
468
503
|
}
|
|
469
504
|
},
|
|
470
505
|
...(this.appOptions.routes || {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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": "58489e0447cd7388d4dbe4d525ebb86ddea343e2"
|
|
53
53
|
}
|
package/plugins/db.js
CHANGED
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
|
-
};
|