diff --git a/packages/midway-decorator/src/index.ts b/packages/midway-decorator/src/index.ts index 9304824bee6e..b6a6d510e91b 100644 --- a/packages/midway-decorator/src/index.ts +++ b/packages/midway-decorator/src/index.ts @@ -5,6 +5,7 @@ export * from './common/priority'; export * from './faas/fun'; export * from './faas/handler'; export * from './web/requestMapping'; +export * from './web/paramMapping'; export * from './web/controller'; export * from './framework/config'; export * from './framework/logger'; diff --git a/packages/midway-decorator/src/web/paramMapping.ts b/packages/midway-decorator/src/web/paramMapping.ts new file mode 100644 index 000000000000..9fe40a714fda --- /dev/null +++ b/packages/midway-decorator/src/web/paramMapping.ts @@ -0,0 +1,71 @@ +import { attachMethodDataToClass } from 'injection'; + +export enum RouteParamTypes { + QUERY, + BODY, + PARAM, + CONTEXT, + HEADERS, + SESSION, + FILESTREAM, + FILESSTREAM, + NEXT, +} + +export interface RouterParamValue { + index?: number; + type?: RouteParamTypes; + data?: any; + extractValue?: (ctx, next) => Promise; +} + +export const ROUTE_ARGS_METADATA = '__routeArgsMetadata__'; + +export const extractValue = function extractValue(key, data) { + return async function(ctx, next) { + switch (key) { + case RouteParamTypes.NEXT: + return next; + case RouteParamTypes.CONTEXT: + return ctx; + case RouteParamTypes.BODY: + return data && ctx.request.body ? ctx.request.body[data] : ctx.request.body; + case RouteParamTypes.PARAM: + return data ? ctx.params[data] : ctx.params; + case RouteParamTypes.QUERY: + return data ? ctx.query[data] : ctx.query; + case RouteParamTypes.HEADERS: + return data ? ctx.headers[data] : ctx.headers; + case RouteParamTypes.SESSION: + return ctx.session; + case RouteParamTypes.FILESTREAM: + return ctx.getFileStream && ctx.getFileStream(data); + case RouteParamTypes.FILESSTREAM: + return ctx.multipart && ctx.multipart(data); + default: + return null; + } + }; +}; + +function createParamMapping(type: RouteParamTypes) { + return (data?: any) => { + return (target, key, index) => { + attachMethodDataToClass(ROUTE_ARGS_METADATA, { + index, + type, + data, + extractValue: extractValue(type, data) + }, target, key); + }; + }; + } + +export const ctx = createParamMapping(RouteParamTypes.CONTEXT); +export const body = createParamMapping(RouteParamTypes.BODY); +export const param = createParamMapping(RouteParamTypes.PARAM); +export const query = createParamMapping(RouteParamTypes.QUERY); +export const session = createParamMapping(RouteParamTypes.SESSION); +export const headers = createParamMapping(RouteParamTypes.HEADERS); +export const file = createParamMapping(RouteParamTypes.FILESTREAM); +export const files = createParamMapping(RouteParamTypes.FILESSTREAM); diff --git a/packages/midway-web/package.json b/packages/midway-web/package.json index 34aabc13791b..efd8699624c8 100644 --- a/packages/midway-web/package.json +++ b/packages/midway-web/package.json @@ -34,7 +34,8 @@ "midway-mock": "^1.7.0", "pedding": "^1.1.0", "react": "^16.2.0", - "react-dom": "^16.2.0" + "react-dom": "^16.2.0", + "mz-modules": "^2.1.0" }, "dependencies": { "@eggjs/router": "^2.0.0", @@ -46,8 +47,7 @@ "extend2": "^1.0.0", "injection": "^1.4.2", "midway-core": "^1.7.0", - "midway-schedule": "^1.7.0", - "mkdirp": "^0.5.1" + "midway-schedule": "^1.7.0" }, "author": "Harry Chen ", "repository": { diff --git a/packages/midway-web/src/loader/webLoader.ts b/packages/midway-web/src/loader/webLoader.ts index 2e41ac21273c..cb3c30bac342 100644 --- a/packages/midway-web/src/loader/webLoader.ts +++ b/packages/midway-web/src/loader/webLoader.ts @@ -5,11 +5,13 @@ import { KoaMiddleware, PRIORITY_KEY, RouterOption, - WEB_ROUTER_KEY + WEB_ROUTER_KEY, + ROUTE_ARGS_METADATA, + RouterParamValue } from '@midwayjs/decorator'; import * as extend from 'extend2'; import * as fs from 'fs'; -import { getClassMetadata, getProviderId, listModule } from 'injection'; +import { getClassMetadata, getMethodDataFromClass, getProviderId, listModule } from 'injection'; import { ContainerLoader, MidwayHandlerKey } from 'midway-core'; import * as path from 'path'; import { MidwayLoaderOptions, WebMiddleware } from '../interface'; @@ -199,6 +201,7 @@ export class MidwayWebLoader extends EggLoader { // implement @get @post const webRouterInfo: RouterOption[] = getClassMetadata(WEB_ROUTER_KEY, target); + if (webRouterInfo && typeof webRouterInfo[Symbol.iterator] === 'function') { for (const webRouter of webRouterInfo) { // get middleware @@ -209,11 +212,17 @@ export class MidwayWebLoader extends EggLoader { methodMiddlwares.push(middlewareImpl); }); + // implement @body @query @param @body + const routeArgsInfo = getMethodDataFromClass(ROUTE_ARGS_METADATA, target, webRouter.method) || []; + const routerArgs = [ webRouter.routerName, webRouter.path, ...methodMiddlwares, - this.generateController(`${controllerId}.${webRouter.method}`) + this.generateController( + `${controllerId}.${webRouter.method}`, + routeArgsInfo, + ) ]; // apply controller from request context @@ -268,11 +277,17 @@ export class MidwayWebLoader extends EggLoader { * wrap controller string to middleware function * @param controllerMapping like xxxController.index */ - public generateController(controllerMapping: string) { + public generateController(controllerMapping: string, routeArgsInfo: RouterParamValue[]) { const [controllerId, methodName] = controllerMapping.split('.'); return async (ctx, next) => { + const args = [ctx, next]; + if (Array.isArray(routeArgsInfo)) { + await Promise.all(routeArgsInfo.map(async({index, extractValue}) => { + args[index] = await extractValue(ctx, next); + })); + } const controller = await ctx.requestContext.getAsync(controllerId); - return controller[methodName].call(controller, ctx, next); + return controller[methodName].apply(controller, args); }; } diff --git a/packages/midway-web/test/enhance.test.ts b/packages/midway-web/test/enhance.test.ts index 66f2a5f934b8..849ca3de8c88 100644 --- a/packages/midway-web/test/enhance.test.ts +++ b/packages/midway-web/test/enhance.test.ts @@ -4,6 +4,7 @@ import * as path from 'path'; const utils = require('./utils'); const mm = require('mm'); const pedding = require('pedding'); +const rimraf = require('mz-modules/rimraf'); import { clearAllModule } from 'injection'; @@ -138,7 +139,10 @@ describe('/test/enhance.test.ts', () => { return app.ready(); }); - after(() => app.close()); + after(() => { + rimraf(path.join(app.config.baseDir, 'app/public')); + app.close(); + }); it('should load ts directory', (done) => { request(app.callback()) @@ -153,6 +157,94 @@ describe('/test/enhance.test.ts', () => { .expect(200) .expect('service,hello,a,b', done); }); + + it('should param controller be ok ', async () => { + // done = pedding(11, done); + + app.mockCsrf(); + + request(app.callback()) + .get('/param/query?name=1') + .expect(200) + .expect({name: '1'}); + + request(app.callback()) + .get('/param/query_id?id=1') + .expect(200) + .expect('1'); + + request(app.callback()) + .get('/param/param/12/test/456') + .expect(200) + .expect({id: '12', userId: '456'}); + + request(app.callback()) + .get('/param/param/12') + .expect(200) + .expect('12'); + + request(app.callback()) + .post('/param/body') + .type('form') + .send({ id: '1' }) + .expect(200) + .expect({id: '1'}); + + request(app.callback()) + .get('/param/body_id') + .type('form') + .send({ id: '1' }) + .expect(200) + .expect('1'); + + request(app.callback()) + .get('/param/session') + .expect('{}'); + + request(app.callback()) + .get('/param/headers') + .expect(200) + .expect('127'); + + request(app.callback()) + .get('/param/headers_host') + .expect(200) + .expect('127'); + + const imagePath = path.join(__dirname, 'fixtures/enhance', 'base-app-decorator', '1.jpg'); + const imagePath1 = path.join(__dirname, 'fixtures/enhance', 'base-app-decorator', '2.jpg'); + + await app.httpRequest() + .post('/param/file') + .field('name', 'form') + .attach('file', imagePath) + .expect('ok'); + + await app.httpRequest() + .get('/public/form.jpg') + .expect('content-length', '16424') + .expect(200); + + await app.httpRequest() + .post('/param/files') + .field('name1', '1') + .attach('file1', imagePath) + .field('name2', '2') + .attach('file2', imagePath1) + .field('name3', '3') + .expect('ok'); + + await app.httpRequest() + .get('/public/1.jpg') + .expect('content-length', '16424') + .expect(200); + + await app.httpRequest() + .get('/public/2.jpg') + .expect('content-length', '16424') + .expect(200); + + }); }); describe('load ts file and use third party module', () => { diff --git a/packages/midway-web/test/fixtures/enhance/base-app-decorator/1.jpg b/packages/midway-web/test/fixtures/enhance/base-app-decorator/1.jpg new file mode 100644 index 000000000000..bb9ca5418b2c Binary files /dev/null and b/packages/midway-web/test/fixtures/enhance/base-app-decorator/1.jpg differ diff --git a/packages/midway-web/test/fixtures/enhance/base-app-decorator/2.jpg b/packages/midway-web/test/fixtures/enhance/base-app-decorator/2.jpg new file mode 100644 index 000000000000..bb9ca5418b2c Binary files /dev/null and b/packages/midway-web/test/fixtures/enhance/base-app-decorator/2.jpg differ diff --git a/packages/midway-web/test/fixtures/enhance/base-app-decorator/src/app/controller/param.ts b/packages/midway-web/test/fixtures/enhance/base-app-decorator/src/app/controller/param.ts new file mode 100644 index 000000000000..2ca92a025eb2 --- /dev/null +++ b/packages/midway-web/test/fixtures/enhance/base-app-decorator/src/app/controller/param.ts @@ -0,0 +1,89 @@ +import { provide } from 'injection'; +import { controller, config, get, post, query, param, ctx, files, file, session, body, headers } from '../../../../../../../src'; +import * as path from 'path'; +import * as fs from 'fs'; + +import * as pump from 'mz-modules/pump'; + +@provide() +@controller('/param') +export class ParamController { + + @config('baseDir') + baseDir: string; + + @get('/query') + async query(@query() query, @ctx() ctx) { + ctx.body = query; + } + + @get('/query_id') + async queryId(@query('id') id, @ctx() ctx) { + ctx.body = id; + } + + @get('/param/:id/test/:userId') + async param(@param() param, @ctx() ctx) { + // service,hello,a,b + ctx.body = param; + } + + @get('/param/:id') + async paramId(@param('id') id, @ctx() ctx) { + ctx.body = id; + } + + @post('/body') + async body(@body() body, @ctx() ctx) { + ctx.body = body; + } + + @get('/body_id') + async bodyId(@body('id') id, @ctx() ctx) { + ctx.body = id; + } + + @post('/file') + async file(@file() stream, @ctx() ctx) { + const filename = encodeURIComponent(stream.fields.name) + path.extname(stream.filename).toLowerCase(); + const target = path.join(this.baseDir, 'app/public', filename); + const writeStream = fs.createWriteStream(target); + await pump(stream, writeStream); + ctx.body = 'ok'; + } + + @post('/files') + async files(@files({ autoFields: true }) parts, @ctx() ctx) { + + let stream = await parts(); + + while (stream) { + const filename = stream.filename.toLowerCase(); + const target = path.join(this.baseDir, 'app/public', filename); + const writeStream = fs.createWriteStream(target); + await pump(stream, writeStream); + stream = await parts(); + } + + ctx.body = 'ok'; + } + + @get('/session') + async session(@session() session, @ctx() ctx) { + // service,hello,a,b + ctx.body = session; + } + + @get('/headers') + async header(@headers() headers, @ctx() ctx) { + // service,hello,a,b + ctx.body = headers.host.substring(0, 3); + } + + @get('/headers_host') + async headerHost(@headers('host') host, @ctx() ctx) { + // service,hello,a,b + ctx.body = host.substring(0, 3); + } + +}