Skip to content

Commit 0b678bf

Browse files
committed
feat(0.0.7): 增加文件上传支持,修复问题。发布0.0.7
1 parent cd85164 commit 0b678bf

File tree

8 files changed

+253
-67
lines changed

8 files changed

+253
-67
lines changed

CHANGELOG.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
> 次版本:每季度发布一次,向下兼容的功能性新增
44
> 修订版本:每周发布一次(紧急版本随时发布),向下兼容的问题修正
55
6-
## 0.0.6 [Current]
6+
## 0.0.7 [Current]
7+
###### 发布日期:2023-09-20
8+
###### 兼容性:0.x.x
9+
10+
+ 移除内置swagger-ui
11+
+ 移除koa-static,使用koa-send实现带前缀的静态路径
12+
+ 单例对象提到全局变量中,实现运行时单例
13+
+ 使用formidable实现文件上传
14+
+ addRoute实现传入多余参数,req和res,用于第三方扩展实现装饰器hook
15+
+ 修复404路由,进行多余的body转换
16+
17+
18+
## 0.0.6
719
###### 发布日期:2023-09-08
820
###### 兼容性:0.x.x
921

package.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "brisk-controller",
3-
"version": "0.0.6",
3+
"version": "0.0.7",
44
"description": "fast light brisk controller in nodejs(koa)",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",
@@ -35,14 +35,14 @@
3535
},
3636
"homepage": "https://github.com/ruixiaozi/brisk-controller#readme",
3737
"dependencies": {
38-
"brisk-ioc": "0.0.6",
39-
"brisk-log": "0.0.6",
40-
"brisk-ts-extends": "0.0.6",
38+
"brisk-ioc": "0.0.7",
39+
"brisk-log": "0.0.7",
40+
"brisk-ts-extends": "0.0.7",
4141
"co-body": "6.1.0",
42+
"formidable": "3.5.1",
4243
"koa": "2.14.1",
4344
"koa-cors": "0.0.16",
44-
"koa-static": "5.0.0",
45-
"koa2-swagger-ui": "5.6.0",
45+
"koa-send": "5.0.1",
4646
"path-to-regexp": "6.2.1",
4747
"portfinder": "1.0.32",
4848
"xml2js": "0.6.2"
@@ -51,6 +51,7 @@
5151
"@commitlint/cli": "16.2.1",
5252
"@commitlint/config-conventional": "16.2.1",
5353
"@types/co-body": "6.1.0",
54+
"@types/formidable": "3.4.3",
5455
"@types/jest": "29.2.5",
5556
"@types/koa": "2.13.5",
5657
"@types/koa-cors": "0.0.2",

src/core/core.ts

+40-28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { koaSwagger } from 'koa2-swagger-ui';
21
import { getLogger, LOGGER_LEVEL_E } from 'brisk-log';
32
import Koa, { Context } from 'koa';
43
import cors from 'koa-cors';
5-
import staticMidware from 'koa-static';
4+
import { staticMidWare } from './static';
65
import path from 'path';
76
import { Server } from 'http';
87
import { getPortPromise } from 'portfinder';
@@ -22,20 +21,29 @@ import {
2221
} from '../types';
2322
import { isLike, TypeKind } from 'brisk-ts-extends';
2423
import { get, getParentTypeKind, getSubTypeKind } from 'brisk-ts-extends/runtime';
25-
import { addSwaggerRoute, addSwaggerTag, getSwaggerHandler, initSwaggerConfig } from './swagger';
24+
import { addSwaggerRoute, addSwaggerTag, getSwaggerHandler, reInitSwaggerConfig } from './swagger';
2625
import { isValid, parseBoolean } from './utils';
27-
import { addRoute, BriskControllerError, initRouter, isFormData, isJson, isXml, router, setBaseUrl } from './router';
26+
import { addRoute, BriskControllerError, reInitRouter, isFormData, isJson, isXml, router, setBaseUrl, isFile } from './router';
27+
import { getBean, setBean } from 'brisk-ioc';
2828

2929

30-
const defaultRegion = Symbol('briskController');
31-
const logger = getLogger(defaultRegion);
30+
// 保存运行时region
31+
const globalVal: {
32+
_briskControllerRegion?: symbol,
33+
[key: string | symbol | number]: any,
34+
} = globalThis;
35+
36+
if (!globalVal._briskControllerRegion) {
37+
globalVal._briskControllerRegion = Symbol('briskController');
38+
}
39+
40+
41+
const logger = getLogger(globalVal._briskControllerRegion);
3242
logger.configure({
3343
// 默认是info级别,可通过配置全局来改变此等级
3444
level: LOGGER_LEVEL_E.info,
3545
});
3646

37-
let server: Server;
38-
3947

4048
/**
4149
* 抛出错误响应
@@ -219,7 +227,8 @@ function getParameters(ctx: Context, params?: BriskControllerParameter[]) {
219227
value = isJson(ctx) || isXml(ctx) ? data[item.name] : undefined;
220228
break;
221229
case BRISK_CONTROLLER_PARAMETER_IS_E.FORM_DATA:
222-
value = isFormData(ctx) ? data[item.name] : undefined;
230+
// application/x-www-form-urlencoded 和 multipart/form-data
231+
value = isFormData(ctx) || isFile(ctx) ? data[item.name] : undefined;
223232
break;
224233
case BRISK_CONTROLLER_PARAMETER_IS_E.BODY:
225234
value = isJson(ctx) || isXml(ctx) ? data : undefined;
@@ -239,6 +248,9 @@ function getParameters(ctx: Context, params?: BriskControllerParameter[]) {
239248
case BRISK_CONTROLLER_PARAMETER_IS_E.STATE:
240249
// 直接返回state值,不需要校验
241250
return ctx.state?.[item.name];
251+
case BRISK_CONTROLLER_PARAMETER_IS_E.FILE:
252+
// 直接返回文件,不需要校验
253+
return (ctx.request as any).files?.[item.name];
242254
default:
243255
return undefined;
244256
}
@@ -297,7 +309,8 @@ export function addRequest(requestPath: string, handler: BriskControllerRequestH
297309
data: (ctx.request as any).body,
298310
query: ctx.request.query,
299311
});
300-
const res = await Promise.resolve(handler(...getParameters(ctx, option?.params)));
312+
// 将ctx的req和res作为多余的参数传入,用于其他框架做方法装饰器hook
313+
const res = await Promise.resolve(handler(...getParameters(ctx, option?.params), ctx.request, ctx.response));
301314
if (typeof res === 'object') {
302315
if (res._extra) {
303316
const extra = res._extra;
@@ -360,9 +373,6 @@ export async function start(port: number = 3000, option?: BriskControllerOption)
360373
}));
361374
}
362375

363-
if (option?.staticPath) {
364-
app.use(staticMidware(path.resolve(option.staticPath)));
365-
}
366376

367377
const realPort = await getPortPromise({
368378
port,
@@ -377,35 +387,37 @@ export async function start(port: number = 3000, option?: BriskControllerOption)
377387
description: '系统生成',
378388
},
379389
});
380-
381-
// 打包字节码,koaSwagger运行会存在问题
382-
app.use(koaSwagger({
383-
routePrefix: '/swagger',
384-
hideTopbar: true,
385-
swaggerOptions: {
386-
url: './swagger.json',
387-
},
388-
}));
389390
}
390391

391392
app.use(router);
392393

394+
if (option?.staticPath) {
395+
app.use(staticMidWare(path.resolve(option.staticPath), {
396+
prefix: globalBaseUrl,
397+
}));
398+
}
399+
393400
await new Promise((resolve) => {
394-
server = app.listen(realPort, '0.0.0.0', () => {
395-
logger.info(`listen http://0.0.0.0:${realPort}`);
396-
logger.info(`listen http://127.0.0.1:${realPort}`);
397-
logger.info(`listen http://localhost:${realPort}`);
401+
const server = app.listen(realPort, '0.0.0.0', () => {
402+
logger.info(`listen http://0.0.0.0:${realPort}${globalBaseUrl}`);
403+
logger.info(`listen http://127.0.0.1:${realPort}${globalBaseUrl}`);
404+
logger.info(`listen http://localhost:${realPort}${globalBaseUrl}`);
405+
if (option?.swagger) {
406+
logger.info(`swagger http://localhost:${realPort}${globalBaseUrl}${'/swagger.json'}`);
407+
}
398408
resolve(null);
399409
});
410+
setBean('KoaServer', server, globalVal._briskControllerRegion);
400411
});
401412

402413
return app;
403414
}
404415

405416
export function distory(): Promise<void> {
406-
initRouter();
407-
initSwaggerConfig();
417+
reInitRouter();
418+
reInitSwaggerConfig();
408419
return new Promise((resolve, reject) => {
420+
const server: Server | undefined = getBean('KoaServer', globalVal._briskControllerRegion);
409421
server?.close((error) => {
410422
if (error) {
411423
logger.error('close error', error);

src/core/router.ts

+49-12
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,29 @@ import parse from 'co-body';
1414
import { getLogger, LOGGER_LEVEL_E } from 'brisk-log';
1515
import nodePath from 'path';
1616
import { parseStringPromise } from 'xml2js';
17+
import { isLike } from 'brisk-ts-extends';
18+
import { IncomingForm } from 'formidable';
19+
20+
21+
// 保存运行时参数
22+
const globalVal: {
23+
_briskControllerBaseUrl?: string,
24+
// 路径、方法名、类型、处理器列表(按顺序执行)
25+
_briskControllerRouters?: Map<string, Map<BRISK_CONTROLLER_METHOD_E, Map<BRISK_CONTROLLER_ROUTER_TYPE_E, BriskControllerRouterHandler[]>>>,
26+
[key: string | symbol | number]: any,
27+
} = globalThis;
28+
29+
if (!globalVal._briskControllerBaseUrl) {
30+
globalVal._briskControllerBaseUrl = '/';
31+
}
32+
33+
if (!globalVal._briskControllerRouters) {
34+
globalVal._briskControllerRouters = new Map();
35+
}
1736

18-
let baseUrl = '/';
1937

2038
export function setBaseUrl(_baseUrl: string) {
21-
baseUrl = _baseUrl;
39+
globalVal._briskControllerBaseUrl = _baseUrl;
2240
}
2341

2442
export class BriskControllerError extends Error {
@@ -48,8 +66,6 @@ export class BriskControllerError extends Error {
4866

4967
}
5068

51-
// 路径、方法名、类型、处理器列表(按顺序执行)
52-
let routers: Map<string, Map<BRISK_CONTROLLER_METHOD_E, Map<BRISK_CONTROLLER_ROUTER_TYPE_E, BriskControllerRouterHandler[]>>> = new Map();
5369

5470
const defaultRegion = Symbol('briskControllerRouter');
5571
const logger = getLogger(defaultRegion);
@@ -60,10 +76,10 @@ logger.configure({
6076

6177
export function addRoute(path: string, handler: BriskControllerRouterHandler, option?: BriskControllerRouterOption) {
6278
const realPath = path.length > 1 && path.charAt(path.length - 1) === '/' ? path.substring(0, path.length - 1) : path;
63-
let methodMap = routers.get(realPath);
79+
let methodMap = globalVal._briskControllerRouters!.get(realPath);
6480
if (!methodMap) {
6581
methodMap = new Map();
66-
routers.set(realPath, methodMap);
82+
globalVal._briskControllerRouters!.set(realPath, methodMap);
6783
}
6884

6985
const methods = Array.isArray(option?.method) ? option!.method : [option?.method || BRISK_CONTROLLER_METHOD_E.GET];
@@ -93,6 +109,7 @@ const jsonTypes = [
93109
const formTypes = [BRISK_CONTROLLER_MIME_TYPE_E.APPLICATION_X_WWW_FORM_URLENCODED];
94110
const textTypes = [BRISK_CONTROLLER_MIME_TYPE_E.TEXT_PLAIN];
95111
const xmlTypes = [BRISK_CONTROLLER_MIME_TYPE_E.TEXT_XML];
112+
const fileTypes = [BRISK_CONTROLLER_MIME_TYPE_E.MULTIPART_FORM_DATA];
96113

97114
export function isJson(ctx: Context) {
98115
return ctx.request.is(jsonTypes);
@@ -110,6 +127,10 @@ export function isXml(ctx: Context) {
110127
return ctx.request.is(xmlTypes);
111128
}
112129

130+
export function isFile(ctx: Context) {
131+
return ctx.request.is(fileTypes);
132+
}
133+
113134
function parseBody(ctx: Context) {
114135
if (isJson(ctx)) {
115136
return parse.json(ctx, {
@@ -147,29 +168,44 @@ function parseBody(ctx: Context) {
147168
});
148169
}
149170

171+
if (isFile(ctx)) {
172+
const incomingForm = new IncomingForm();
173+
return incomingForm.parse(ctx.req).then(([fields, files]) => ({
174+
parsed: fields,
175+
files,
176+
}));
177+
}
178+
150179
return Promise.resolve({});
151180
}
152181

153182

154183
export const router: Middleware = async(ctx: Context, next: Next) => {
155184
try {
156185
// 路径匹配
157-
const pathInfos = [...routers.keys()]
186+
const pathInfos = [...globalVal._briskControllerRouters!.keys()]
158187
// 筛选出匹配的
159188
.reduce((res, path) => {
160-
const matchRes = match(nodePath.posix.join(baseUrl, path))(ctx.request.path);
189+
const matchRes = match(nodePath.posix.join(globalVal._briskControllerBaseUrl!, path))(ctx.request.path);
161190
if (matchRes && matchRes.index === 0) {
162191
res.push({
163192
path,
164193
params: matchRes.params,
165-
methodMap: routers.get(path),
194+
methodMap: globalVal._briskControllerRouters!.get(path),
166195
});
167196
}
168197
return res;
169198
}, [] as BriskControllerPathInfo[])
170199
// 排序,层级越少的越放前面
171200
.sort((pathInfoA, pathInfoB) => (pathInfoA.path.match(/\//ug)?.length || 0) - (pathInfoB.path.match(/\//ug)?.length || 0));
172201

202+
// 无匹配
203+
if (!pathInfos.length) {
204+
// 执行后续中间件
205+
await next();
206+
return;
207+
}
208+
173209
// 所有路径下的方法列表不包含当前的方法,报405错误
174210
if (pathInfos.length && pathInfos.every((pathInfo) => !pathInfo.methodMap?.has(ctx.request.method.toLowerCase() as BRISK_CONTROLLER_METHOD_E))) {
175211
ctx.response.status = 405;
@@ -198,6 +234,7 @@ export const router: Middleware = async(ctx: Context, next: Next) => {
198234
try {
199235
const parseRes = await parseBody(ctx);
200236
(ctx.request as any).body = 'parsed' in parseRes ? parseRes.parsed : {};
237+
(ctx.request as any).files = 'files' in parseRes ? parseRes.files : {};
201238
if ((ctx.request as any).rawBody === undefined) {
202239
(ctx.request as any).rawBody = parseRes.raw;
203240
}
@@ -231,7 +268,7 @@ export const router: Middleware = async(ctx: Context, next: Next) => {
231268
await next();
232269
return;
233270
} catch (error) {
234-
if (error instanceof BriskControllerError) {
271+
if (isLike<BriskControllerError>(error)) {
235272
ctx.response.status = error.status;
236273
if (error.headers) {
237274
Object.entries(error.headers).forEach(([key, value]) => {
@@ -250,6 +287,6 @@ export const router: Middleware = async(ctx: Context, next: Next) => {
250287
}
251288
};
252289

253-
export function initRouter() {
254-
routers = new Map();
290+
export function reInitRouter() {
291+
globalVal._briskControllerRouters! = new Map();
255292
}

src/core/static.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Context, Next } from 'koa';
2+
import send, { SendOptions } from 'koa-send';
3+
import path from 'path';
4+
5+
6+
export interface BriskControllerStaticOption extends SendOptions {
7+
prefix?: string;
8+
}
9+
10+
export function staticMidWare(root: string, opts: BriskControllerStaticOption = {}) {
11+
opts.root = path.resolve(root);
12+
if (opts.index !== false) {
13+
opts.index = opts.index || 'index.html';
14+
};
15+
16+
return async function midware(ctx: Context, next: Next) {
17+
// 先执行后续的路由
18+
await next();
19+
20+
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
21+
return;
22+
};
23+
// 已经处理
24+
if ((ctx.body !== null && ctx.body !== undefined) || ctx.status !== 404) {
25+
return;
26+
};
27+
28+
if (opts.prefix) {
29+
// 前缀不匹配
30+
if (!ctx.path.startsWith(opts.prefix)) {
31+
return;
32+
}
33+
ctx.path = ctx.path.slice(opts.prefix.length);
34+
}
35+
36+
try {
37+
await send(ctx, ctx.path, opts);
38+
} catch (err: any) {
39+
if (err.status !== 404) {
40+
throw err;
41+
}
42+
}
43+
};
44+
}

0 commit comments

Comments
 (0)