使用装饰器进行路由的重构
=====

如果熟悉es最新语法的同学，对装饰器一定不陌生，使用装饰器，我们能够很好的、轻松的给我们的函数，添加一些辅助功能，并且无需修改函数的定义。

typescript是支持装饰器功能，我们的思路可以模仿python web框架flask的写法：



```ts
import { Controller } from '../../src/base/controller';
import { bp } from '../../src/blueprint';


export default class user extends Controller {
    @bp.post('/uc')
    async post() {
        console.log(body.content);
        this.ctx.body = 'good routing'
    }
    @bp.get('/uc')
    async get() {
        console.log(body.content);
        this.ctx.body = 'good routing'
    }
}

```

这种定义路由的方式极其简单而且有效，当我们看到函数的时候，马上就可以知道对应的url和http方法，而且定义一个路由的时候，无需再跑到router.ts文件里改了又切回来。



装饰器类
=====

在src目录下，新建一个文件，再一个类，我们叫他Blueprint(蓝图).


```ts
interface Bp {
    httpMethod: string,
    constructor: any,
    handler: string
}
interface Bps {
    [key: string]: Array<Bp>
}

export class Blueprint {
    router: Bps = {} //用于保存路由的映射关系

    setRouter(url: string, blueprint: Bp) {
        const _bp = this.router[url];
        if (_bp) {
            //检查http method 是否冲突
            for (const index in _bp) {
                const object = _bp[index];
                if (object.httpMethod === blueprint.httpMethod) {
                    console.log(`路由地址 ${object.httpMethod} ${url} 已经存在`)
                    return
                }
            }
            //不冲突则注册
            this.router[url].push(blueprint);
        } else {
            this.router[url] = [];
            this.router[url].push(blueprint);
        }
    }

    /**
     * 用法@instance.get('/')
     * @param url 
     */
    get(url: string) {
        return (target: any, propertyKey: string) => {
            (<any>this).setRouter(url, {
                httpMethod: 'get',
                constructor: target.constructor,
                handler: propertyKey
            })
        }
    }

    /**
     * 返回路由
     */
    getRoute() {
        return this.router;
    }
}


export = new Blueprint;
```

我们的Blueprint类提供了三个方法
- get 装饰器，用于装饰我们的controller
- getRoute 返回所有的路由
- setRouter 将路由映射放入this.router中，并检查是否重复，因为每个地址且不同http method 不算冲突，所以我们保存的时候需要用到数组

第三个函数是什么意思呢，其实就是我们说的restapi了，举个例子：

``` ts
"http://127.0.0.1/3000" 
//你可以对这个地址进行get /post /put /delete
```

最后一个值得注意的是，我们导出的时候使用
```ts

export const bp = new Blueprint;
```

其原因就是我们这里需要一个全局变量去记录我们预先设置的路由，所以导出的时候，我们导出一个Blueprint类的实例


修改我们的loader
===

首先，到loader.ts中，修改loadRouter


```ts
import { bp } from './blueprint';

....
 loadRouter() {
        this.loadController();
        this.loadService();
        this.loadConfig();


        const r = bp.getRoute();
        Object.keys(r).forEach((url) => {
            r[url].forEach((object) => {
                (<any>this.router)[object.httpMethod](url, async (ctx: BaseContext) => {
                    const instance = new object.constructor(ctx, this.app);
                    await instance[object.handler]();
                })
            })
        })
        return this.router.routes();
    }
....
```
这里就很简单了，就是拿到所有的route，注册进去

再修改我们的loadController
```ts

    loadController() {
        const dirs = fs.readdirSync(__dirname + '/controller');
        dirs.forEach((filename) => {
            require(__dirname + '/controller/' + filename).default;
        })
    }
```
改成这样就ok了，代码极其简单。因为现在不需要对controller进行映射了，所以我们只是


最后，我们到任意一个controller中

````ts
import { Controller } from "./base";
import { bp } from "../blueprint";

//user.ts

export default class User extends Controller {
    async user() {
        this.ctx.body = this.ctx.service.check.index();

    }

    getConfig() {
        return (<any>this.app)['config']
    }

    @bp.get('/test')
    async userInfo() {
        this.ctx.body = '我是装饰器';
    }
}
````

使用我们的装饰器，然后登陆我们的网站，发送一个get请求，你就能看到相应的内容。