实现controller
===========

```js
//router/user.ts
const user = async (ctx: any, next: any) => {
    ctx.body = 'hello user';
}

const userInfo = async (ctx: any, next: any) => {
    ctx.body = 'hello userinfo';
}

export default {
    'get /': user,
    'get /userinfo': userInfo
}
```

我们看看router/user.ts，其实这里面的函数就是一个一个的controller，只不过是比较初级的版本。

通过观察，我们发现，我们的每一个函数都会传递一个参数,ctx过来，以表示本次请求koa帮我们生成的上下文对象。如果采用面向对象的方法，那我们一定要把这个属性封装到类里，变成成员变量。

在src下，新建一个controller目录，创建一个base.ts，用于写基类。

```
.
├── dist
│   ├── app.js
│   ├── loader.js
│   └── router
│       └── user.js
├── nodemon.json
├── package-lock.json
├── package.json
├── src
│   ├── app.ts
│   ├── controller // 新增
│   │   └── base.ts //新增
│   ├── loader.ts
│   └── router
│       └── user.ts
└── tsconfig.json

```

在base.ts中

```ts
//base.ts
import { BaseContext } from "koa";

export class Controller {
    ctx: BaseContext;
    constructor(ctx: BaseContext) {
        this.ctx = ctx;
    }
}
```
在这里简单的几行代码，我们定义了一个Controller类，这个类有一个成员变量ctx，类型是koa的``BaseContext``，构造函数有参数传递，代表每一次请求koa生成的ctx.

我们在base.ts的同级目录下，新建一个user.ts。

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

//user.ts

export class User extends Controller {
    async user() {
        this.ctx.body = 'hello user';
    }

    async userInfo() {
        this.ctx.body = 'hello userinfo';
    }
}
```
上述代码我们定义了一个User类，继承自Controller。这个类中的一个或者多个方法，都是一个路由所对应的方法。

我们可以轻易的在这个类中定义很多的方法，来表示一个模块。

- 想用ctx的时候，只需要this.ctx就可以拿到，非常的方便。
- 一组相关的endpoint(就是地址)，都内聚于一个类中，方便看，也方便维护。

重写controller的路由
===========

我们之前的路由其实有一个很大的问题，就是**路由太分散**，不方便进行统一的管理。在这里，我们势必要对其进行重写。

第一步
===========
删掉原来的router目录，在src目录下，新建一个router.ts文件，用于管理所有的路由。

此时的目录变成了

```bash
.
├── dist
│   ├── app.js
│   ├── loader.js
│   └── router
│       └── user.js
├── nodemon.json
├── package-lock.json
├── package.json
├── src
│   ├── app.ts
│   ├── controller
│   │   ├── base.ts
│   │   └── user.ts
│   ├── loader.ts
│   └── router.ts//新增
└── tsconfig.json
```

第二步，重写router.ts
===========
```js
module.exports = (controller: any) => {

    return {
        'get /': controller.user.user,
        'get /userinfo': controller.user.userinfo
    }
}
```
我们这里的设计是router模块导出一个函数，函数的参数是一个controller对象。
里面包含着我们要规定的路由信息。规则也很简单:

``controller.[文件名].[方法名]``

这样做的好处就是我们定义路由的时候，第二个参数就是文件名，第三个参数就是方法名，一目了然。




第三步
===========
使用面向对象重写loader.ts。

```ts
import * as fs from 'fs';
import * as Router from 'koa-router';
export class Loader {
    router: Router = new Router;
    controller: any = {};

    loadController() {
        const dirs = fs.readdirSync(__dirname + '/controller');
        dirs.forEach((filename) => {
            const property = filename.split('.')[0];
            const mod = require(__dirname + '/controller/' + filename).default;
            if (mod) {
                const methodNames = Object.getOwnPropertyNames(mod.prototype).filter((names) => {
                    if (names !== 'constructor') {
                        return names;
                    }
                })
                Object.defineProperty(this.controller, property, {
                    get() {
                        const merge: { [key: string]: any } = {};
                        methodNames.forEach((name) => {
                            merge[name] = {
                                type: mod,
                                methodName: name
                            }
                        })
                        return merge;
                    }
                })
                console.log(this.controller.user);
            }
        })
    }

    loadRouter() {

    }
}
```

聚焦到``loadController``函数。

我们为了实现`` 'get /': controller.user.user,`` 这样的函数定义，首先我们得构造一个controller成员变量，这个成员变量就是我们传递到router.ts中的变量。
``loadController``函数，看似复杂，其实做了一件非常简单的事情，就是构造一个controller对象，里面有所有controller的信息。

值得注意的是，我们在这里运用了比较多的高级Api特性，比如``Object.defineProperty``来操纵getter，``Object.getOwnPropertyNames``,``prototype``。通过反复运用这些玩意儿，我们的基础再次夯实。

还有一个loadRouter函数没有实现，实现起来会相当简单:

```ts
loadRouter() {
        this.loadController();
        const mod = require(__dirname + '/router.js');
        const routers = mod(this.controller);
        console.log(routers);
        Object.keys(routers).forEach((key) => {
            const [method, path] = key.split(' ');

            (<any>this.router)[method](path, async (ctx: BaseContext) => {
                const _class = routers[key].type;
                const handler = routers[key].methodName;
                const instance = new _class(ctx);
                instance[handler]();
            })
        })
        return this.router.routes();
    }
```

``loadRouter``函数主要做的是调用router.ts模块中的函数，获得我们刚刚定义的路由规则，然后进行路由方法和path的提取。

再根据刚刚我们提供的controller的信息，**重新构建一个controller对象，传递ctx参数进去**，再调用我们路由规则定义的方法，自此，我们就完成了controller的实现。

修改```app.ts```,

```ts
//app.js
import * as Koa from 'koa';
import { Loader } from './loader';

const app = new Koa;

const loader = new Loader;

app.use(loader.loadRouter());

app.listen(3000, '127.0.0.1', () => {
    console.log('服务器在运行');
})
```

启动我们的app，访问我们定义的路由地址，就可以看到我们之前的信息.

#总结一下
通过一系列的loader导入，我们的目录变成了

```bash
.
├── dist
│   ├── app.js
│   ├── controller     
│   │   ├── base.js
│   │   └── user.js  
│   ├── loader.js
│   ├── router
│   │   └── user.js
│   └── router.js
├── nodemon.json
├── package-lock.json
├── package.json
├── src
│   ├── app.ts
│   ├── controller  ---->专门放controller
│   │   ├── base.ts
│   │   └── user.ts
│   ├── loader.ts
│   └── router.ts   ---->专门放管理路由
└── tsconfig.json
```

我们在 user.ts文件中写
```ts
import { Controller } from "./base";

//user.ts

export default class User extends Controller {
    async user() {
        this.ctx.body = 'hello user';
    }

    async userInfo() {
        this.ctx.body = 'hello userinfo';
    }
}
```

在router.ts中定义规则,
```ts
module.exports = (controller: any) => {

    return {
        'get /': controller.user.user,
        'get /userinfo': controller.user.userInfo
    }
}
```

启动服务器以后，就可以轻松的访问到我们的逻辑了。


#为什么要每一个请求重新构建controller对象呢？一直用一个对象不行么？

答案是不行的，因为每一个请求都是独立的请求，需要新的ctx。

在高并发的情况下，如果一直公用一个controller，而不是重新创建，就会发生难以估计的错误。
那么我们必须保证在每一次请求中，我们的controller都是独立的一个新对象。


**每一个请求都有一个独立的ctx这个概念极其重要，在之后的旅途中，我们将频繁的使用到。**


