EggJS-数据库

Mysql

一、安装配置

    $ npm i --save egg-mysql

    // config/plugin.js  开启插件
    exports.mysql = {
            enable: true,
            package: 'egg-mysql',
    };

egg-monogoose

一、安装配置

    $ npm i egg-mongoose --save

    // {app_root}/config/plugin.js
    exports.mongoose = {
        enable: true,
        package: 'egg-mongoose',
    };

    // {app_root}/config/config.default.js
    module.exports = {
        mongoose: {
            url: 'mongodb://127.0.0.1:27017/ibg_node_core',
            options: {
                db: { native_parser: true },
                server: { poolSize: 5 },
                user: 'core',
                pass: 'we123',
            },
        },
    }

    // 多个mongos配置
    // {app_root}/config/config.default.js
    exports.mongoose = {
        url: 'mongodb://mongosA:27501,mongosB:27501',
        options: {}
    };

    // log
    module.exports = {
        logger: {
            dir: '/home/admin/logs/demoapp',        // logger文件的目录
            level: 'DEBUG',                         // 开deubg模式
        },
    };


二、定义Schema

    // app/modle/reportWarningConfig.js

    module.exports = app => {
        const mongoose = app.mongoose;
        const ReportWarningConfigSchema = new mongoose.Schema({
                actionCode: { type: String, unique: true, trim: true }, // 动作编码
                interval: { type: Number, default: 1800 },  // 扫描间隔时间,单位:秒 默认 30分钟
                emailConfig: { // 发送邮件条件
                    appKey: { type: String, default: '', trim: true },
                    appSecret: { type: String, default: '', trim: true },
                    recipient: { type: [String] },  // 收件人
                },
                lastExecutedTime: { type: Date, default: Date.now },   // 最近执行时间
            }, 
            {
                timestamps: true,               // 设置timestamps,多加两个字段createdTime、updatedTime,来记录插入时间和更新时间
                collection: 'report_warning_config',
            }
        );

        return mongoose.model('ReportWarningConfig', ReportWarningConfigSchema);
    };


三、常用语句

    1、await app.model.login.find(条件): 返回的是一组数据, []

    2、await app.model.login.findOne(条件): 只返回一条数据, 如果没有匹配的返回null,

    3、await app.model.login.update(条件, 要更新的文档, 回调方法): 更新数据

    4、let loginTask = new app.model.login({ username: 'siguang', password: 'ssssss'});     
            let doc = await loginTask.save();        // 插入数据 

    5、exec(callback): 在查询、更新后会执行一个回调函数来查看是否成功执行

    6、save(function(err, kitten){  }): 存储


四、Example

    1、查询

        async queryMonitorTask(){
            let monitorTaskList = await app.model.ReportWarningConfig.find();          // app.model.ReportWarningConfig 来指定用哪个文档
            return app.renderBody({
                statusType: app.statusType.success,
                data: {
                    list: monitorTaskList
                }
            });
        }


    2、更新

        更新需要先将文档查回来后,在修改某一个字段,如果一直更新会覆盖

        async updateMonitorTask(params){
            let response;
            let condition = {_id: params.id};
            let doc = await app.model.ReportWarningConfig.findOne(condition);       // 通过id来取到当前数据

            if(doc){
                doc.interval = params.interval;
                doc.emailConfig.recipient = params.emailRecipient;
                doc.smsConfig.recipient = params.smsRecipient;

                await doc.save();
                response = app.renderBody({
                    statusType: error,
                    error: error,
                });
            }
            else{
                response = app.renderBody({
                    statusType: app.statusType.error,
                    error: doc,
                });
            }

            return response;
        }


    3、插入数据

        插入数据将使用save()

        async insertMonitorTask(){
        const {ctx, app, service} = this;
        const {actionCode, interval, emailRecipient, smsRecipient} = ctx.request.body;

        // 将要插入的数据加到model中
        let monitorTask = new app.model.ReportWarningConfig({   // 注意这里使用new 
            actionCode,
            interval,
            emailConfig: {
                recipient: emailRecipient
            },
            smsConfig: {
                recipient: smsRecipient
            }
        })

        // 使用save()来插入
        let doc = await monitorTask.save();

        let response = app.renderBody({
            statusType: app.statusType.success,
            data: doc._id
        });
        ctx.body = response;
    }

mongoose 积累

一、查询出来的数据不能修改

async getNewsContent(params){
    let doc = await app.model.News.findOne({_id: params.id});

    // 不能直接修改doc的对象,需要通过toObject()方法来转成对象的形势
    let newDoc = doc.toObject();
    newDoc.createdAt = app.dateFormat(new Date(newDoc.createdAt).getTime(), 'yyyy-MM-dd hh:mm:ss')
    newDoc.updatedAt = app.dateFormat(new Date(newDoc.updatedAt).getTime(), 'yyyy-MM-dd hh:mm:ss')
    console.log(`ResponseAAA ----- ${JSON.stringify(newDoc)}`)

    return {
        data: newDoc,
        message: '获取成功',
        status: 0
    }
}

| https://github.com/eggjs/egg-mongoose

Egg.js

| egg-validate POST参数规则校验 https://github.com/eggjs/egg-validate
| egg-mongoose Mongoose https://github.com/eggjs/egg-mongoose

egg-validate

validate只用于post请求的参数校验,get请求取出的都是字符串

$ npm i egg-validate --save

一、在config/plugin.js配置中添加

    exports.validate = {
        enable: true,
        package: 'egg-validate',
    };

二、Exmaple

    async refundApply() {
        const { ctx, app, service } = this;
        const paramRule = {
            partnerUserId: { type: 'string' },
            applyUserName: { type: 'string' },
            amount: { type: 'string' }, 
            sourceAccount: { type: 'string' }, 
        };

        const paramErrors = app.validator.validate(paramRule, ctx.request.body);
        if (paramErrors) {
            ctx.body = app.renderBody({
                statusType: app.statusType.paramsError,
                error: paramErrors,
            });
            return;
        }

        const response = await service.payTool.billquery.refundApply(ctx.request.body);
        ctx.body = response;
    }


三、验证规则  

    1、required - 是否当前字段必须有,required: false 可以为空

    2、allowEmpty - 允许为空

    2、int - 只能为整数

            max - 最大值
            min - 最小值

    3、number - 可以是整数和浮点数

    4、date - 日期 'YYYY-MM-DD'     birthoday: 'date'

    5、dateTime - 日期 YYYY-MM-DD HH:mm:ss

    6、boolean - 是否是布尔值        working: 'boolean'

    7、string - 是否是字符串, 字符串的四个规则:            

        allowEmpty - 允许为空字符串

        format - 使用正则来验证字符串的格式

        max - 字符串最大长度

        min - 字符串最小长度

        const rule = {
            username: { allowEmpty: true, min: 10, max: 100, format: /^\d+$/ }
        }

    8、email - 是否是email格式

    9、password - 密码验证规则 max最大、min最小、compare比较  pass: {type: password, max: 32, min: 6}

    10、url - 是否是url

    11、enum - 如果是枚举需要加一个规则

        // operateType的值必须是values数组中的一项,values必须为数组
        const paramRule = {
            operateType: { type: 'enum', values: ['REPAYMENT', 'CHARGE'] },        
        }
        const paramErrors = app.validator.validate(paramRule, ctx.request.body);

    12、object - 如果是对象,需要加一个规则 

    13、array - 如果是数组,需要加一个规则

        itemType 数组中每一个元素的规则 
        rule - An object that validate the items of the array. Only work with itemType.
        max - 数组最大长度
        min - 数组最小长度

        childrens: {
            type: 'array',
            itemType: 'object',
            required: false,
            rule: {
                name: 'string',
                age: 'int',
                gender: ['male', 'female'],
                birthday: {type: 'date', required: false}
            }
        }


https://github.com/node-modules/parameter
https://github.com/node-modules/parameter/blob/master/benchmark.js
https://github.com/node-modules/parameter/blob/master/example.js

egg-mongoose

$ npm i egg-mongoose --save

$ plugin.js

    exports.mongoose = {
        enable: true,
        package: 'egg-mongoose',
    };

$ config.default.js

    mongoose: {
  url: 'mongodb://127.0.0.1:27017/ibg_node_core',
  options: {},
}

$ 可以多数据库配置

egg-mysql

$ npm i egg-mysql --save

$ plugin.js

    exports.mysql = {
        enable: true,
        package: 'egg-mysql',
    };

$ config.default.js

    mysql: {
        // database configuration
        client: {
            // host
            host: 'mysql.com',
            // port
            port: '3306',
            // username
            user: 'test_user',
            // password
            password: 'test_password',
            // database
            database: 'test',    
        },
        // load into app, default is open
        app: true,
        // load into agent, default is close
        agent: false,
    }

$ app.mysql.query(sql, values)              获取sql

$ 可以多数据库配置

ejs 后端模板

$ npm install ejs

https://segmentfault.com/a/1190000004286562
http://www.360doc.com/content/16/0115/10/597197_528136785.shtml
https://github.com/tj/ejs

渲染模板nunjucks 后端模板

$ npm i egg-view-nunjucks --save

一、tpl文件要放到view目录里

二、配置
// config/plugin.js
exports.nunjucks = {
    enable: true,
    package: 'egg-view-nunjucks'
};

// config/config.default.js
exports.keys = <此处改为你自己的 Cookie 安全字符串>;

// 添加 view 配置
exports.view = {
    defaultViewEngine: 'nunjucks',
    mapping: {
        '.tpl': 'nunjucks',
    },
};

三、Controller
module.exports = app => {
    class NewsController extends app.Controller {
        async newsList(){
            const { ctx } = this;
            const dataList = {
                list: [
                    { id: 1, title: 'this is news 1', url: '/news/1' },
                    { id: 2, title: 'this is news 2', url: '/news/2' }
                ]
            }
            await ctx.render('news/newsList.tpl', dataList);                // 注意这里dataList必须是一个对象
        }
    }

    return NewsController;
}

四、newsList.tpl

<html>
    <head>
        <title>Hacker News</title>
    </head>
    <body>
        <ul class="news-view view">
            {% for item in list %}
					
  • {{ item.title }}
  • {% endfor %} </ul> </body> </html> http://mozilla.github.io/nunjucks/cn/templating.html https://github.com/eggjs/examples/tree/master/hackernews/app/view/news

    其它收集插件

    1、ms返回时间 - https://github.com/zeit/ms       // ms用来返回一个时间  ms('2h') 返回 7200000  
    
    2、MD5加密 - https://github.com/jkiss/crypto-js      npm i crypto-js --save  引用 const MD5 = require('crypto-js/md5');
    
    3、文件上传 - https://github.com/node-modules/formstream
    
    4、JSON串和对象互转 - https://github.com/ljharb/qs  // 可以根据key来进行排序 
    
    5、验证码 - https://github.com/lemonce/svg-captcha/blob/master/README_CN.md
    

    | 参考资料
    | https://github.com/eggjs eggjs官方插件
    |

    EggJS框架-问题积累

    数据库需要指定表

    module.exports = app => {
        const mongoose = app.mongoose;
        const UserSchema = new mongoose.Schema({
            username: { type: String },
            passworrd: { type: String }
        }, {collection: 'user'});
    
        return mongoose.model('User', UserSchema);
    }
    

    不支持ES6的import

    module.exports = app => {}      // egg使用ES5的导出所以不支持 import
    

    1、this.get(‘user-agent’) controller中通过this.get()获取请求头的信息

    bodyParser 中间件,egg内置,用来处里post

    加入全局方法:
    module.exports = {
    generateUuid() {
    const uuid = uuidV1();
    return uuid.replace(/-/g, ‘’).toUpperCase();
    },
    };

    参考资料
    http://koa.bootcss.com/

    EggJS基础

    Egg安装

    Egg.js属于MVC类型的NodeJS框架
    
    $ mkdir egg-example && cd egg-example
    
    $ npm init egg --type=simple
    
    $ npm i
    
    $ npm run dev
    
    $ open localhost:7001
    

    配置文件

    1、config
            |- config.default.js        // 默认配置文件, 所有环境都会加载这个配置文件, 一般用于开发环境的默认配置文件
            |- config.test.js           // 开发环境配置
            |- config.prod.js           // 生产环境配置
            |- config.unittest.js
            |- plugin.js                // 控制插件
            `- config.local.js
    
        module.exports = {
            mysql: {
                enable: true,           // 开启或关闭插件
                package: 'egg-mysql'    // package为一个npm模块,package.json中的dependencies中,框架会在node_modules目录中找到这个插件入口
            },
        };
    
        也可以指定path 来替代 package
        const path = require('path');
        module.exports = {
            mysql: {
                enable: true,
                path: path.join(__dirname, '../app/plugin/egg-mysql'),
            },
        };
    
    2、app.js文件
    
        app.js是入口文件,需要手动创建在根目录下,包括了生命周期函数
        module.exports = app => {
            console.log('start')
        }
    

    Router 路由

    用来接收请求URL,在交给controller处理
    
    app.get(路由名,路由URL路由,middleware(可以配置多个),controller)
    
    一、controller的两个写法
    
        # router
        module.exports = app => {
            // 1、controller目录的home.js进行处理
            app.get('/', 'home');             
    
            // 2、payTool目录、billquery.js文件、getLoanUser方法
            app.post('/getLoanUser', 'payTool.billquery.getLoanUser');
        };
    
        # controller/home.js
        module.exports = function* () {
            this.body = 'Hello World';  // 响应给客户端
        };
    
        # controller/payTool/billquery.js
        module.exports = app => {
            class BillqueryController extends app.Controller {
                async getLoanUser(){
                    const { ctx, app, service } = this;
                    const response = await service.payTool.billquery.getRepayPlan(ctx.request.body);
                    ctx.body = response;
                }
            }
        }
    
    
    二、路由中间件 - 在访问路由之前通过中间件来对路由进行一些处理
    
        app.get('router-name', 'middleware1', 'middleware2', 'controlll');
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
        2、路由中调用
    
            注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
            // 引用中间件
            const checkAuth = app.middleware.checkAuth();
    
            // 不需要验证
            app.get('/', 'home.index');
            app.get('/query', 'query.query.queryPage');
    
            // 需要验证是否登录的
            app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    
    
    三、参数获取
    
        1、获取url参数 ctx.query
    
            http://127.0.0.1:7001/search?name=egg  取出name的值
    
            // app/router.js
            module.exports = app => {
                app.get('/search', app.controller.search);
            };
    
            // app/controller/search.js
            module.exports = function* (ctx) {
                tx.body = `search: ${this.query.name}`;
            };
    
    
        2、路由参数 ctx.params
    
            http://127.0.0.1:7001/user/123/xiaoming   取出 123、xiaoming
    
            // app/router.js
            module.exports = app => {
                app.get('/user/:id/:name', app.controller.user.info);
            };
    
            // app/controller/user.js
            exports.info = function* (ctx) {
                ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
            };
    
    
    四、重定向 - 当路由不存在时会走一个默认的路由
    
        1、内部重定向
    
            // app/router.js
            module.exports = app => {
                app.get('index', '/home/index', 'home.index');
                app.redirect('/', '/home/index', 302);
            };
    
        2、外部重定义 controller中来执行路由的跳转 redirect
    
            // app/router.js
            module.exports = app => {
                app.get('/search', 'search');
            };
    
            // app/controller/search.js
            module.exports = function* () {
                const type = this.query.type;
                const q = this.query.q || 'nodejs';
    
                // 这里跳转
                ctx.redirect('/admin/home');
            };
    
    五、多路由映射 - 将路由分类并用一个路由主文件加载其它的路由文件
    
        // router.js 通过一个主文件将两个路由加载进来
        module.exports = app => {
            require('./router/news')(app);
            require('./router/admin')(app);
        };
    
        // router/news.js
        module.exports = app => {
            app.get('/news/list', app.controller.news.list);
            app.get('/news/detail', app.controller.news.detail);
        };
    
        // router/admin.js
        module.exports = app => {
            app.get('/admin/user', app.controller.admin.user);
            app.get('/admin/log', app.controller.admin.log);
        };
    
    六、路由规则
    
        // app/router.js
        module.exports = app => {
            app.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
        };
    

    Controller

    controller继承于app.controller, 负责解析用户输入,处理后返回相应的结果, 框架推荐Controller层主要对用户请求参数进行处理(校验、转换),然后用service处理业务结果并返回
    
    用户HTTP请求(路由处理) <=> 校验组装(controller处理)<=> 业务处理(service)
    
    一、Controller的两种写法
    
        1、函数写法
    
            // app/controller/posts.js
            exports.index = function* () {};
            exports.new = function* () {};
            exports.create = function* () {};
    
            // router 调用
            app.get('/index', 'posts/index');
            app.get('/new', 'posts/new');
            app.get('/create', 'posts/create');
    
    
        2、类写法
    
            // app/controller/post.js
            module.exports = app => {
                class PostController extends app.Controller {       // 定义一个PostController的类,继承了app.Controller
                    * create() {
                        const { ctx, service } = this;
                        const createRule = {
                            title: { type: 'string' },
                            content: { type: 'string' },
                        };
                        // 校验参数
                        ctx.validate(createRule);
                        // 组装参数
                        const author = ctx.session.userId;
                        const req = Object.assign(ctx.request.body, { author });
                        // 调用 Service 进行业务处理
                        const res = yield service.post.create(req);
                        // 设置响应内容和响应状态码
                        ctx.body = { id: res.id };
                        ctx.status = 201;
                    }
                }
                return PostController;
            }
    
            // router 调用   app/router.js
            module.exports = app => {
                app.post('createPost', '/api/posts', 'post.create');
            }
    
    
    二、app.Controller下this上挂载的几个属性
    
        1、this.ctx: 当前请求的上下文Context对象的实例
    
        2、this.app: Application对象的实例,可以取到框架的全局对象和方法
    
        3、this.service: 取到service下的方法
    
        4、this.config: 获取或添加修改config的配置
    
        5、this.logger: 日志打印
    
    
    三、获取请求
    
        ctx.method: 请求方法
    
        ctx.path: 请求路径
    
        ctx.host: 请求IP
    
        1、query 获取GET请求中传递参数
    
            url: /posts?category=egg&language=node  通过context.query拿到url的参数  
    
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
                    const queryName = ctx.query;    // {category: 'egg', language: 'node'}
                    ctx.body = queryName;
                }
            }
    
        2、queries 能取到重复的参数key
    
            // posts?category=egg&id=1&id=2&id=3 参数解析成对象,id有多个
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
                    const queryName = ctx.queries;    // {category: 'egg', id: [1,2,3]}
                    ctx.body = queryName;
                }
            }
    
        3、params 参数   
    
            路由: app.get('/projects/:projectId/app/:appId', 'app.listApp');
    
            // 请求: GET /projects/1/app/2
            class QueryController extends app.Controller{
                async getParams(app){
                    const { ctx, app, service } = this;
                    const params = {
                        id: ctx.params.projectId,
                        appid: ctx.params.appId
                    };
                    ctx.body = queryName;
                }
            }
    
        4、body 获取post传的数据
    
            框架内置bodyParser中间件,挂载到 context.request.body上,来取post的内容
    
            ctx.request.body  来获取post传过来的参数 {"username":"","password":"1111"}
    
            exports.listPosts = function* (ctx) {
                assert.equal(ctx.request.body.title, 'controller');
                assert.equal(ctx.request.body.content, 'what is controller');
            };
    
            <input type="text" name="title" value="" />
            <input type="text" name="content" value="" />
    
            config.default.js设置, 默认body最大长度为100kb
            bodyParser: {
                jsonLimit: '1mb',
                formLimit: '1mb',
            }
    
        5、获取上传文件
    
            浏览器上都是通过 Multipart/form-data 格式发送文件的,框架通过内置 Multipart 插件来支持获取用户上传的文件
    
            <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
                title: <input name="title" />
                file: <input name="file" type="file" />
                <button type="submit">上传</button>
            </form>
    
            const path = require('path');
            const sendToWormhole = require('stream-wormhole');
    
            module.exports = function* (ctx) {
                const stream = yield ctx.getFileStream();
                const name = 'egg-multipart-test/' + path.basename(stream.filename);
                // 文件处理,上传到云存储等等
                let result;
                try {
                    result = yield ctx.oss.put(name, stream);
                } catch (err) {
                    // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
                    yield sendToWormhole(stream);
                    throw err;
                }
                ctx.body = {
                    url: result.url,
                    // 所有表单字段都能通过 `stream.fields` 获取到
                    fields: stream.fields,
                };
            };
    
    
            在config/config.default.js 中配置来新增支持的文件扩展名,或者重写整个白名单
    
            新增支持的文件扩展名
            module.exports = {
                multipart: {
                    fileExtensions: [ '.apk' ], // 增加对 .apk 扩展名的支持
                },
            };
    
            覆盖整个白名单
            module.exports = {
                multipart: {
                    whitelist: [ '.png' ], // 覆盖整个白名单,只允许上传 '.png' 格式
                },
            };
    
        6、发送HTTP响应
    
            1)设置status
    
                exports.create = function* (ctx) {
                    // 设置状态码为 201
                    ctx.status = 201;
                };
    
            2)设置body 响应给请求方
    
                exports.show = function* (ctx) {
                    ctx.body = {
                        name: 'egg',
                        category: 'framework',
                        language: 'Node.js',
                    };
                };
                exports.page = function* (ctx) {
                    ctx.body = '<html><h1>Hello</h1></html>';
                };
    

    ctx.header 头

    获取整个 header 对象的方法: context.headers、context.header、context.request.headers、context.request.header
    
    get()方法获取请求 header 中的一个字段的值、字段不存在返回null:  context.get(name)、context.request.get(name)
    
    一、获取header
    
        1、ctx.header 取出头信息
    
                async from(ctx){
                    console.log(ctx.header);
                }
                返回结果
                { 
                    host: '127.0.0.1:7001',
                    connection: 'keep-alive',
                    'content-length': '21',
                    'postman-token': 'a7e094b5-21ce-eb94-e403-3d517c832f30',
                    'cache-control': 'no-cache',
                    origin: 'chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo',
                    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
                    'content-type': 'application/json',
                    accept: '*/*',
                    'accept-encoding': 'gzip, deflate, br',
                    'accept-language': 'zh-CN,zh;q=0.8,en;q=0.6',
                    cookie: 'csrfToken=RNHUjeOf0Zsl9jNl2f5BhreT' 
                }
    
        2、ctx.protocol 协议
    
        3、ctx.ips 返回ip 一个数组
    
        4、ctx.ip 返回请求方的ip
    
        5)、ctx.host
    
    
    二、设置header
    
        context.set(key, value) 方法可以设置一个响应头,context.set(headers) 设置多个 Header。
        exports.show = function* (ctx) {
            const start = Date.now();
            ctx.body = yield ctx.service.post.get();
            const used = Date.now() - start;
            // 设置一个响应头
            ctx.set('show-response-time', userd.toString());
        };
    
    可以通过context.cookies来获取和设置cookie值
    
    一、cookie操作
    
        获取 ctx.cookies.get('count');
    
        设置 ctx.cookies.set('count', ++count);
    
        删除 ctx.cookies.set('count', null);
    
    
    二、设置options
    
        context.cookies.set(key, value, options)
    
        1、maxAge: nunber - 在浏览器保存最长时间
    
        2、expires: date - 设置这个键值的失效时间
    
        3、path: string - 设置生效的URL路径
    
        4、domain: string - 设置生效的域名
    
        5、httpOnly: boolean - 设置是否可以被js访问
    
        6、secure: boolean - 设置键值只在HTTPS连接上传输
    
        7、overwrite: boolean - 设置key相当键值如何处理,true为后设置的覆盖前面设置的,false发送两个
    
        8、sign: boolean - 对cookie是否进行签名,true防止前端对这个值进行篡改
    
        9、encrypt: boolean - 对cookie进行加密
    
        ctx.cookies.set(key, value, {
            httpOnly: false,
            sign: false,
        });
    
    
    三、Cookie 密钥
    
        config/config.default.js
    
        module.exports = {
            keys: 'key1,key2',
        };
    

    Session

    context.session 来访问或者修改当前用户 Session 
    
    1、读取和设置 Session
    
        exports.fetchPosts = function* (ctx) {
            // 获取 Session 上的内容
            const user = ctx.session.user;
            const posts = yield ctx.service.post.fetch(userId);
    
            // 修改 Session 的值
            ctx.session.user = ctx.session.user ? ctx.session.user++ : 1;
    
            ctx.body = {
                success: true,
                posts,
            };
        };
    
    
    2、删除session
    
        exports.deleteSession = function* (ctx) {
            ctx.session = null;
        };
    
    
    3、设置 session配置
    
        // config.default.js
    
        exports.session = {
            key: 'EGG_SESS',
            maxAge: 24 * 3600 * 1000, // 1 天
            httpOnly: true,
            encrypt: true,
        };
    

    service 处理业务逻辑

    service 需要继承于 app.Service,service在复杂业务场景下做业务逻辑封装的抽象层
    
    一、service的ctx
    
        1、this.ctx.curl - 发起网络调用
    
        2、this.ctx.service.otherService - 调用service
    
        3、this.ctx.db - 发起数据库调用
    
    
    二、Example
    
        // 调用服务 app/controller/user.js
        module.exports = app => {
            class User extends app.controller {
                async addUser(){
                    let userData = {
                        name: 'siguang',
                        password: 'xxxxxx'
                    }
                    let isAddUser = yield ctx.service.user.createUser(userData)
                    if(setUser){
                        ctx.body = '添加成功'
                    }
                    ctx.body = isAddUser ? '添加成功' : '添加失败';
                }
            }
        }
    
        // 定义一个Service app/service/user.js
        module.exports = app => {                   // 中间件支持两个参数 options中间件配置,app当前应用Application的实例
            class User extends app.Service {
                async createUser(params){
                    const url = '/account/blance/createUser';
                    let result = await this.ctx.curl(url, {
                        method: 'POST',
                        dataType: 'json',   // 自动解析 JSON response
                        data: params,
                        timeout: 15000,     // 15 秒超时
                    }
                    this.ctx.logger.info('响应结果:', result.data);
                    return result.data;
                }
            }
            return User;
        };
    

    Middleware 中间件

    一、config.default.js 配置全局
    
        module.exports = {
            // 配置需要的中间件,数组顺序即为中间件的加载顺序
            middleware: [ 'gzip' ],
            // 配置 gzip 中间件的配置
            gzip: {
                threshold: 1024, // 小于 1k 的响应体不压缩
            },
        };
    
        // 框架中默认的中间件
        module.exports = {
            bodyParser: {
                jsonLimit: '10mb',
            },
        };
    
    
    二、通用配置
    
        enable:控制中间件是否开启。
    
        match:设置只有符合某些规则的请求才会经过这个中间件。
    
        ignore:设置符合某些规则的请求不经过这个中间件。
    
        bodyParser: {
            enable: false,
            jsonLimit: '10mb',
        },
    
    
    三、Example: 
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
    2、路由中调用
    
        注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
        // 引用中间件
        const checkAuth = app.middleware.checkAuth();
    
        // 不需要验证
        app.get('/', 'home.index');
        app.get('/query', 'query.query.queryPage');
    
        // 需要验证是否登录的
        app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    

    JSONP

    jsonp只能get请求
    
    // config/config.default.js  jsonp的配置
    exports.jsonp = {
        callback: 'callback', // 识别 query 中的 `callback` 参数
        limit: 100, // 函数名最长为 100 个字符
    };
    
    
    // app/router.js  路由加jsonp中间件
    module.exports = app => {
        const jsonp = app.jsonp();
        app.get('/api/posts/:id', jsonp, 'posts.show');         // 通过jsonp中间件来让路由支付一个中间件
        app.get('/api/posts', jsonp, 'posts.list');
    };
    
    // app/controller/posts.js
    exports.show = function* (ctx) {
        ctx.body = {
            name: 'egg',
            category: 'framework',
            language: 'Node.js',
        };
    };
    
    用户请求 /api/posts/1?callback=fn,响应为 JSONP 格式,如果用户请求 /api/posts/1
    

    HttpClient

    Node模拟客户端请求,curl请求默认 content-type: application/x-www-form-urlencoded,
    
    app.curl(url, options) 和 app.httpclient.request(url, options) 两种方法相同
    
    一、Example:
    
        module.exports = app => {
            class HttpClientController extends app.Controller{
                async getHttpClient(){
                    const {ctx, app, service} = this;
                    let result = await this.ctx.curl('http://goucai.diyicai.com/lottery/getissue.action?lotteryId=001&issueLen=100&d=1502966960306', {
                        methods: 'get'
                    });
    
                    let phones = [];
                    let resultObj = JSON.parse(result.data.toString());
                    resultObj.forEach((data, idx, arr)=>{
                        console.log('data------', data);
                        phones.push(data.endTime);
                    })
                    ctx.body = phones.join(',')
                }
            }
            return HttpClientController;
        }
    
    二、config配置 // config/config.default.js
    
        exports.httpclient = {
            // 默认开启 http/https KeepAlive 功能
            keepAlive: true,
    
            // 空闲的 KeepAlive socket 最长可以存活 4 秒
            freeSocketKeepAliveTimeout: 4000,
    
            // 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
            timeout: 30000,
    
            // 允许创建的最大 socket 数
            maxSockets: Infinity,
    
            // 最大空闲 socket 数
            maxFreeSockets: 256,
    
            // 是否开启本地 DNS 缓存,默认关闭
            // 一旦设置开启,则每个域名的 DNS 查询结果将在进程内缓存 10 秒
            enableDNSCache: false,
        };
    
    三、options对象
    
        let options = {};
        const result = yield ctx.curl('https://httpbin.org/get?foo=bar', options);
    
        1、mothod: 请求方法
    
        2、data: 需要发送的数据 { foo: 'bar' }
    
        3、dataAsQueryString: Boolean 如果为 true 即使在post情况下,也会强制将options.data以 querystringstringify处理后拼接到url的query参数上
    
        4、content(String|Buffer): 发送请求的正文,如果设置了此参数会忽略data参数
    
                ctx.curl(url, {
                    method: 'POST',
                    // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
                    content: '<xml><hello>world</hello></xml>',
                    headers: {
                        'content-type': 'text/html',
                    },
                });
    
        5、stream(ReadStream): 发送请求正文的可读数据流
    
                ctx.curl(url, {
                    method: 'POST',
                    stream: fs.createReadStream('/path/to/read'),
                });
    
        6、writeStream: 接受响应数据的可写数据流
    
                ctx.curl(url, {
                    writeStream: fs.createWriteStream('/path/to/store'),
                });
    
        7、consumeWriteStream: Boolean 是否等待 writeStream 完全写完才算响应全部接收完毕,默认是 true
    
        8、contentType: 请求数据的格式,默认undefined
    
        9、dataType: 响应数据格式
    
        10、headers: 自定义请求头
    
        11、timeout: 请求超时时间 默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。
    
        12、agent: 
    
        13、httpsAgent
    
        14、auth、digestAuth
    
        15、followRedirect:Boolean 是否自动跟进3xx的跳转响应
    
        16、maxRedirects: number 最大自动跳转次数
    
        17、formatRedirectUrl: 自定义实现302、301
    
        18、beforeRequest: 请求发送前会调用beforeRequest钩子
    
            ctx.curl(url, {
                beforeRequest: options => {
                    // 例如我们可以设置全局请求 id,方便日志跟踪
                    options.headers['x-request-id'] = uuid.v1();
                }
            });
    
        19、streaming:是否直接返回响应流
    
        20、gzip: Boolean是否开始gzip
    
        21、timing: Boolean 是否开启请求各阶段的时间没是
    
    
    四、Get请求
    
        1、options.method = 'post'  设置请求参数,默认是get请求可以不用加
    
            const result = yield ctx.curl('https://httpbin.org/get?foo=bar', {method: post});
    
        2、status: 响应状态码
    
        3、headers: 响应头信息 有{'conent-type': 'text/html'}
    
        4、data: 响应body, 返回的是Buffer类型, 如果设置了options.dataType会根据参数来处理
    
    
    五、Post请求
    
        module.exports = function* post(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method
                method: 'POST',
    
                // 通过 contentType 告诉 HttpClient 以 JSON 格式发送
                contentType: 'json',
    
                // 要传的数据 
                data: {
                    hello: 'world',
                    now: Date.now(),
                },
    
                // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
                dataType: 'json',
            });
            ctx.body = result.data;
        };
    
    
    六、From表单提交
    
        以 ontent-type: application/x-www-form-urlencoded 的格式提交请求数据
    
        // app/controller/form.js
        module.exports = function* form(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT 和 DELETE
                method: 'POST',
                // 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
                data: {
                        now: Date.now(),
                        foo: 'bar',
                },
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.form;
            // 响应最终会是类似以下的结果:
            // {
            //   "foo": "bar",
            //   "now": "1483864184348"
            // }
        };
    
    
    七、Multipart 方式上传
    
        From 表单提交时包含文件的时候,需要用到 multipart/form-data 进行提交了
    
        引用fromstream 第三方模块
    
        // app/controller/multipart.js
        const FormStream = require('formstream');
        module.exports = function* multipart(ctx) {
            const form = new FormStream();
    
            // 设置普通的 key value
            form.field('foo', 'bar');
    
            // 上传当前文件本身用于测试
            form.file('file', __filename);
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 生成符合 multipart/form-data 要求的请求 headers
                headers: form.headers(),
                // 以 stream 模式提交
                stream: form,
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.files;
            // 响应最终会是类似以下的结果:
            // {
            //   "file": "'use strict';\n\nconst For...."
            // }
        };
    
        // 添加更多文件
        form.file('file1', file1);
        form.file('file2', file2);
    
    
    八、以Stream 方式上传文件
    
        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送
    
        // app/controller/stream.js
        const fs = require('fs');
        module.exports = function* stream(ctx) {
            // 上传当前文件本身用于测试
            const fileStream = fs.createReadStream(__filename);
    
            // httpbin.org 不支持 stream 模式,使用本地 stream 接口代替
            const url = `${ctx.protocol}://${ctx.host}/stream`;
            const result = yield ctx.curl(url, {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 以 stream 模式提交
                stream: fileStream,
            });
    
            ctx.status = result.status;
            ctx.set(result.headers);
            ctx.body = result.data;
            // 响应最终会是类似以下的结果:
            // {"streamSize":574}
        };
    

    服务端模板渲染

    读取数据渲染模板,呈现给用户
    
    $ npm i egg-view-ejs --save         // 安装
    
    $ 配置
    
        // config/plugin.js
        exports.nunjucks = {
            enable: true,
            package: 'egg-view-nunjucks'        // 开启插件使用numjucks模板
        };
    
        // config/config.default.js
        exports.view = {
            defaultViewEngine: 'nunjucks',
            mapping: {
                '.tpl': 'nunjucks',
            },
        };
    
    render、renderString两个方法
    
        render(fileName, locals, viewOptions): filenName: 文件路径、locals: 渲染的数据、viewOptions: 用户传入的配置
    
        render(name, locals) 渲染模板文件, 并赋值给 ctx.body
    
        renderView(name, locals) 渲染模板文件, 仅返回不赋值
    
        renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
    
    - numjucks模板语法: http://mozilla.github.io/nunjucks/cn/templating.html  见模板md -
    

    静态资源

    Egg内置static插件,static默认映射到 public目录
    
    app/public
    ├── css
    │   └── news.css
    └── js
            ├── lib.js
            └── news.js
    

    框架扩展

    一、application 全局应用对象扩展
    
        访问 ctx.app,  Controller,Middleware,Helper,Service 中都可以通过 this.app 访问到 Application 对象
    
        // app.js
        module.exports = app => { 
            app.config
        };
    
        // 扩展 app.foo() 方法 app/extend/application.js
        module.exports = {
            foo(param) {
                // this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
            }
        };
    
    二、context 扩展,可以在ctx中调用扩展方法
    
        extend/content.js
        module.export = {
            async isIOS() {
                const iosReg = /iphone|ipad|ipod/i;
                return iosReg.test(this.get('user-agent'));
            },
        }
        引用 await this.ctx.isIOS();
    
    三、helper 用来编写一些实用函数
    
        // app/extend/helper.js
        const moment = require('moment');
        exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();
    
        // 在controller里使用
        async newsList(){
      const { ctx } = this;
      const time = ctx.helper.relativeTime();
      this.ctx.logger.info('data: %j', time)
    }
    
        // 也可以在模板里面使用:
        <!-- app/views/news/list.tpl -->
        {{ helper.relativeTime(item.time) }}
    
    
    四、request
    
    五、response
    

    Schedule 定时任务

    有些任务需要定时来完成,如定时上报,定时远程更新本地缓存,定时任务都统一存放在 app/schedule 目录下
    
    创建一个定时任务 app/schedule/update_cache.js
    
    module.exports = {
        // 通过 schedule 属性来设置定时任务的执行间隔等配置
        schedule: {
            interval: '1m',     // 1 分钟间隔
            type: 'all',        // 指定所有的 worker 都需要执行
            immediate: false,
      disable: !app.config.enableWarningSchedule,
        },
    
        // task 是真正定时任务执行时被运行的函数,第一个参数是一个匿名的 Context 实例
        * task(ctx) {
            const res = yield ctx.curl('http://www.api.com/cache', {
                dataType: 'json',
            });
            ctx.app.cache = res.data;
        }
    };
    
    1、type: 两种类型 worker 或 all,worker是每台机器只有一个worker执行定时任务, all是每台机器上的每个worker都会执行这个定时任务
    
    2、interval:定时执行的时间
    
    3、cron:指定一个执行的时间 ss mm hh dd mm dd
    
        // 每三小时准点执行一次
    cron: '0 0 */3 * * *',
    
    4、immediate:如果为true,定时任务会在启动并ready后立即执行一次任务
    
    5、disable:为true,这个定时任务不会被执行
    

    启动自定义

    用来进行应用启动时进行初始化工作
    
    // 通过入口文件app.js
    module.exports = app => {
        app.beforeStart(function* () {
            // 应用会等待这个函数执行完成才启动
            app.cities = yield app.curl('http://example.com/city.json', {
                method: 'GET',
                dataType: 'json',
            });
        });
    };
    

    logger 日志

    this.ctx.logger.info('xxxxxx');
    
    logger.debug()、logger.info()、logger.warn()、logger.error()
    
    config.deubgger 文件配置
    
    logger: {
        level: 'DEBUG',
        dir: '../app/logger',
    }
    

    本地开发

    egg-bin模块(用于本地开发和单元测试)
    
    package.json:
    
    {
        "scripts": {
            "dev": "egg-bin dev --port 7001"
        }
    }
    

    插件的开发

    自定义插件存储目录:  lib/plugin/...
    
    egg-ua插件
    
    1、创建package.json
    
        {
            "eggPlugin": {
                "name": "ua"
            }
        }
    
    2、egg-ua/us.js插件文件
    
        module.exports = {
            get isIOS() {
                const iosReg = /iphone|ipad|ipod/i;
                return iosReg.test(this.get('user-agent'));
            },
        };
    
    3、config/plugin.js   通过path来挂载插件
    
        const path = require('path');
        exports.ua = {
            enable: true,
            path: path.join(__dirname, '../lib/plugin/egg-ua'),
        };
    

    项目目录

    ├── package.json
    ├── app.js (可选)            // 启动初始化
    ├── agent.js (可选)
    ├── app
    |   ├── router.js           // 路由规则
    │   ├── controller          // 处理用户的输入,返回相应的结果
    │   |   └── home.js
    │   ├── service (可选)       // 服务,编写业务逻辑
    │   |   └── user.js
    │   ├── middleware (可选)    // 中间件
    │   |   └── response_time.js
    │   ├── schedule (可选)      // 定时任务
    │   |   └── my_task.js
    │   ├── public (可选)        // 静态目录
    │   |   └── reset.css
    │   ├── view (可选)          // 模板
    │   |   └── home.tpl
    │   └── extend (可选)        // 框架扩展
    │       ├── helper.js (可选)
    │       ├── request.js (可选)
    │       ├── response.js (可选)
    │       ├── context.js (可选)     // ctx扩展
    │       ├── application.js (可选)     // 全局方法
    │       └── agent.js (可选)
    ├── config                  // 配置
    |   ├── plugin.js
    |   ├── config.default.js
    │   ├── config.prod.js
    |   ├── config.test.js (可选)
    |   ├── config.local.js (可选)
    |   └── config.unittest.js (可选)
    └── test                    // 测试
            ├── middleware
            |   └── response_time.test.js
            └── controller
                    └── home.test.js
    

    积累

    一、egg启动时指定端口
    
        在package.json中
        script: {
            "dev-fundmgmt": "PLATFORM=fundmgmt egg-bin dev --port 20101",            // --port 20101
        }
    
    二、vue-cli中webpack是否使用热更新
    
        script: {
            "dev-fundmgmt": "HOT_ENV=true node build/dev-server.js FUNDMGMT",
        }
        HOT_ENV=true   为false
    
    
    二、vue中取启动里的变量
    
        script: {
            "dev-fundmgmt": "HOT_ENV=true node build/dev-server.js FUNDMGMT",
        }
    
        vue-cli中的dev.env.js
    
        var PLAT = process.argv;            // 取出命令中返回数组 ['HOT_ENV=true', 'node', 'build/dev-server.js', 'FUNDMGMT'];
        if (PLAT.toUpperCase().trim() === 'FUND') {
            PLAT = 'fund'
        } 
        else if(PLAT.toUpperCase().trim() === 'FUNDMGMT'){
            PLAT = 'fundmgmt';
        }
        else {
            PLAT = 'portal'
        }
    

    | http://koa.bootcss.com/

    EggJS基础

    | this.ctx - 当前请求的上下文 Content对象的实例ctx,Request、Response
    | this.app - 当前应用对象Application 对象的实例
    | this.service - 定义的Service,等价于 this.ctx.service
    | this.config - 应用配置
    | this.logger - 日志对象 方法debug, info, warn, error
    | app.cache - 缓存

    Egg介绍、安装

    Egg.js属于MVC类型的NodeJS框架
    
    一、下载脚手架:
    
        $ npm i egg-init -g
        $ egg-init egg-example --type=simple
        $ cd egg-example
        $ npm i
    
    二、启动项目:
    
        $ npm run dev
        $ open localhost:7001
    

    配置

    config
        |- config.default.js        // 默认配置文件, 所有环境都会加载这个配置文件, 一般用于开发环境的默认配置文件
        |- config.test.js           // 开发环境配置
        |- config.prod.js           // 生产环境配置
        |- config.unittest.js
        |- plugin.js                // 控制插件
        `- config.local.js
    
    一、引入插件
    
        module.exports = {
            mysql: {
                enable: true,           // 开启或关闭插件
                package: 'egg-mysql'    // package为一个npm模块,package.json中的dependencies中,框架会在node_modules目录中找到这个插件入口
            },
        };
    
        也可以指定path 来替代 package
        const path = require('path');
        module.exports = {
            mysql: {
                enable: true,
                path: path.join(__dirname, '../app/plugin/egg-mysql'),
            },
        };
    

    Router 路由

    用来接收请求URL,在交给controller处理
    
    app.get(路由名,路由URL路由,middleware(可以配置多个),controller)
    
    一、controller的两个写法
    
        # router
        module.exports = app => {
            // 1、controller目录的home.js进行处理
            app.get('/', 'home');             
    
            // 2、payTool目录、billquery.js文件、getLoanUser方法
            app.post('/getLoanUser', 'payTool.billquery.getLoanUser');
        };
    
        # controller/home.js
        module.exports = function* () {
            this.body = 'Hello World';  // 响应给客户端
        };
    
        # controller/payTool/billquery.js
        module.exports = app => {
            class BillqueryController extends app.Controller {
                async getLoanUser(){
                    const { ctx, app, service } = this;
                    const response = await service.payTool.billquery.getRepayPlan(ctx.request.body);
                    ctx.body = response;
                }
            }
        }
    
    
    二、路由中间件 - 在访问路由之前通过中间件来对路由进行一些处理
    
        app.get('router-name', 'middleware1', 'middleware2', 'controlll');
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
        2、路由中调用
    
            注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
            // 引用中间件
            const checkAuth = app.middleware.checkAuth();
    
            // 不需要验证
            app.get('/', 'home.index');
            app.get('/query', 'query.query.queryPage');
    
            // 需要验证是否登录的
            app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    
    
    三、参数获取
    
        1、获取url参数 ctx.query
    
            http://127.0.0.1:7001/search?name=egg  取出name的值
    
            // app/router.js
            module.exports = app => {
                app.get('/search', app.controller.search);
            };
    
            // app/controller/search.js
            module.exports = function* (ctx) {
                tx.body = `search: ${this.query.name}`;
            };
    
    
        2、路由参数 ctx.params
    
            http://127.0.0.1:7001/user/123/xiaoming   取出 123、xiaoming
    
            // app/router.js
            module.exports = app => {
                app.get('/user/:id/:name', app.controller.user.info);
            };
    
            // app/controller/user.js
            exports.info = function* (ctx) {
                ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
            };
    
    
    四、重定向 - 当路由不存在时会走一个默认的路由
    
        1、内部重定向
    
            // app/router.js
            module.exports = app => {
                app.get('index', '/home/index', 'home.index');
                app.redirect('/', '/home/index', 302);
            };
    
        2、外部重定义 controller中来执行路由的跳转 redirect
    
            // app/router.js
            module.exports = app => {
                app.get('/search', 'search');
            };
    
            // app/controller/search.js
            module.exports = function* () {
                const type = this.query.type;
                const q = this.query.q || 'nodejs';
    
                // 这里跳转
                ctx.redirect('/admin/home');
            };
    
    五、多路由映射 - 将路由分类并用一个路由主文件加载其它的路由文件
    
        // router.js 通过一个主文件将两个路由加载进来
        module.exports = app => {
            require('./router/news')(app);
            require('./router/admin')(app);
        };
    
        // router/news.js
        module.exports = app => {
            app.get('/news/list', app.controller.news.list);
            app.get('/news/detail', app.controller.news.detail);
        };
    
        // router/admin.js
        module.exports = app => {
            app.get('/admin/user', app.controller.admin.user);
            app.get('/admin/log', app.controller.admin.log);
        };
    
    六、路由规则
    
        // app/router.js
        module.exports = app => {
            app.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
        };
    

    Controller

    controller继承于app.controller, 负责解析用户输入,处理后返回相应的结果, 框架推荐Controller层主要对用户请求参数进行处理(校验、转换),然后用service处理业务结果并返回
    
    用户HTTP请求(路由处理) <=> 校验组装(controller处理)<=> 业务处理(service)
    
    一、Controller的两种写法
    
        1、函数写法
    
            // app/controller/posts.js
            exports.index = function* () {};
            exports.new = function* () {};
            exports.create = function* () {};
    
            // router 调用
            app.get('/index', 'posts/index');
            app.get('/new', 'posts/new');
            app.get('/create', 'posts/create');
    
    
        2、类写法
    
            // app/controller/post.js
            module.exports = app => {
                class PostController extends app.Controller {       // 定义一个PostController的类,继承了app.Controller
                    * create() {
                        const { ctx, service } = this;
                        const createRule = {
                            title: { type: 'string' },
                            content: { type: 'string' },
                        };
                        // 校验参数
                        ctx.validate(createRule);
                        // 组装参数
                        const author = ctx.session.userId;
                        const req = Object.assign(ctx.request.body, { author });
                        // 调用 Service 进行业务处理
                        const res = yield service.post.create(req);
                        // 设置响应内容和响应状态码
                        ctx.body = { id: res.id };
                        ctx.status = 201;
                    }
                }
                return PostController;
            }
    
            // router 调用   app/router.js
            module.exports = app => {
                app.post('createPost', '/api/posts', 'post.create');
            }
    
    
    二、app.Controller下this上挂载的几个属性
    
        1、this.ctx: 当前请求的上下文Context对象的实例
    
        2、this.app: Application对象的实例,可以取到框架的全局对象和方法
    
        3、this.service: 取到service下的方法
    
        4、this.config: 获取或添加修改config的配置
    
        5、this.logger: 日志打印
    
    
    三、获取请求
    
        ctx.method: 请求方法
    
        ctx.path: 请求路径
    
        ctx.host: 请求IP
    
        1、query 获取GET请求中传递参数
    
            url: /posts?category=egg&language=node  通过context.query拿到url的参数  
    
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
    
                    const queryName = ctx.query;    // {category: 'egg', language: 'node'}
                    ctx.body = queryName;
                }
            }
    
        2、queries 能取到重复的参数key
    
            // posts?category=egg&id=1&id=2&id=3 参数解析成对象,id有多个
    
            class QueryController extends app.Controller{
                async getQuery(app){
                    const { ctx, app, service } = this;
    
                    const queryName = ctx.queries;    // {category: 'egg', id: [1,2,3]}
                    ctx.body = queryName;
                }
            }
    
        3、params 参数   
    
            路由: app.get('/projects/:projectId/app/:appId', 'app.listApp');
    
            // 请求: GET /projects/1/app/2
            class QueryController extends app.Controller{
                async getParams(app){
                    const { ctx, app, service } = this;
    
                    const params = {
                        id: ctx.params.projectId,
                        appid: ctx.params.appId
                    };
                    ctx.body = queryName;
                }
            }
    
        4、body 获取post传的数据
    
            框架内置bodyParser中间件,挂载到 context.request.body上,来取post的内容
    
            ctx.request.body  来获取post传过来的参数 {"username":"","password":"1111"}
    
            exports.listPosts = function* (ctx) {
                assert.equal(ctx.request.body.title, 'controller');
                assert.equal(ctx.request.body.content, 'what is controller');
            };
    
            <input type="text" name="title" value="" />
            <input type="text" name="content" value="" />
    
            config.default.js设置, 默认body最大长度为100kb
            bodyParser: {
                jsonLimit: '1mb',
                formLimit: '1mb',
            }
    
        5、获取上传文件
    
            浏览器上都是通过 Multipart/form-data 格式发送文件的,框架通过内置 Multipart 插件来支持获取用户上传的文件
    
            <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
                title: <input name="title" />
                file: <input name="file" type="file" />
                <button type="submit">上传</button>
            </form>
    
            const path = require('path');
            const sendToWormhole = require('stream-wormhole');
    
            module.exports = function* (ctx) {
                const stream = yield ctx.getFileStream();
                const name = 'egg-multipart-test/' + path.basename(stream.filename);
                // 文件处理,上传到云存储等等
                let result;
                try {
                    result = yield ctx.oss.put(name, stream);
                } catch (err) {
                    // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
                    yield sendToWormhole(stream);
                    throw err;
                }
                ctx.body = {
                    url: result.url,
                    // 所有表单字段都能通过 `stream.fields` 获取到
                    fields: stream.fields,
                };
            };
    
    
            在config/config.default.js 中配置来新增支持的文件扩展名,或者重写整个白名单
    
            新增支持的文件扩展名
            module.exports = {
                multipart: {
                    fileExtensions: [ '.apk' ], // 增加对 .apk 扩展名的支持
                },
            };
    
            覆盖整个白名单
            module.exports = {
                multipart: {
                    whitelist: [ '.png' ], // 覆盖整个白名单,只允许上传 '.png' 格式
                },
            };
    
        6、发送HTTP响应
    
            1)设置status
    
                exports.create = function* (ctx) {
                    // 设置状态码为 201
                    ctx.status = 201;
                };
    
            2)设置body 响应给请求方
    
                exports.show = function* (ctx) {
                    ctx.body = {
                        name: 'egg',
                        category: 'framework',
                        language: 'Node.js',
                    };
                };
                exports.page = function* (ctx) {
                    ctx.body = '<html><h1>Hello</h1></html>';
                };
    

    ctx.header 头

    获取整个 header 对象的方法: context.headers、context.header、context.request.headers、context.request.header
    
    get()方法获取请求 header 中的一个字段的值、字段不存在返回null:  context.get(name)、context.request.get(name)
    
    一、获取header
    
        1、ctx.header 取出头信息
    
            async from(ctx){
                console.log(ctx.header);
            }
    
            返回结果
            { 
                host: '127.0.0.1:7001',
                connection: 'keep-alive',
                'content-length': '21',
                'postman-token': 'a7e094b5-21ce-eb94-e403-3d517c832f30',
                'cache-control': 'no-cache',
                origin: 'chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo',
                'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
                'content-type': 'application/json',
                accept: '*/*',
                'accept-encoding': 'gzip, deflate, br',
                'accept-language': 'zh-CN,zh;q=0.8,en;q=0.6',
                cookie: 'csrfToken=RNHUjeOf0Zsl9jNl2f5BhreT' 
            }
    
        2、ctx.protocol 协议
    
        3、ctx.ips 返回ip 一个数组
    
        4、ctx.ip 返回请求方的ip
    
        5)、ctx.host
    
    
    二、设置header
    
        context.set(key, value) 方法可以设置一个响应头,context.set(headers) 设置多个 Header。
    
        exports.show = function* (ctx) {
            const start = Date.now();
            ctx.body = yield ctx.service.post.get();
            const used = Date.now() - start;
            // 设置一个响应头
            ctx.set('show-response-time', userd.toString());
        };
    
    可以通过context.cookies来获取和设置cookie值
    
    一、cookie操作
    
        获取 ctx.cookies.get('count');
    
        设置 ctx.cookies.set('count', ++count);
    
        删除 ctx.cookies.set('count', null);
    
    
    二、设置options
    
        context.cookies.set(key, value, options)
    
        1、maxAge: nunber - 在浏览器保存最长时间
    
        2、expires: date - 设置这个键值的失效时间
    
        3、path: string - 设置生效的URL路径
    
        4、domain: string - 设置生效的域名
    
        5、httpOnly: boolean - 设置是否可以被js访问
    
        6、secure: boolean - 设置键值只在HTTPS连接上传输
    
        7、overwrite: boolean - 设置key相当键值如何处理,true为后设置的覆盖前面设置的,false发送两个
    
        8、sign: boolean - 对cookie是否进行签名,true防止前端对这个值进行篡改
    
        9、encrypt: boolean - 对cookie进行加密
    
        ctx.cookies.set(key, value, {
            httpOnly: false,
            sign: false,
        });
    
    
    三、Cookie 密钥
    
        config/config.default.js
    
        module.exports = {
            keys: 'key1,key2',
        };
    

    Session

    context.session 来访问或者修改当前用户 Session 
    
    1、读取和设置 Session
    
        exports.fetchPosts = function* (ctx) {
    
            // 获取 Session 上的内容
            const user = ctx.session.user;
            const posts = yield ctx.service.post.fetch(userId);
    
            // 修改 Session 的值
            ctx.session.user = ctx.session.user ? ctx.session.user++ : 1;
    
            ctx.body = {
                success: true,
                posts,
            };
        };
    
    
    2、删除session
    
        exports.deleteSession = function* (ctx) {
            ctx.session = null;
        };
    
    
    3、设置 session配置
    
        // config.default.js
    
        exports.session = {
            key: 'EGG_SESS',
            maxAge: 24 * 3600 * 1000, // 1 天
            httpOnly: true,
            encrypt: true,
        };
    

    service 处理业务逻辑

    service 需要继承于 app.Service,service在复杂业务场景下做业务逻辑封装的抽象层
    
    一、service的ctx
    
        1、this.ctx.curl - 发起网络调用
    
        2、this.ctx.service.otherService - 调用service
    
        3、this.ctx.db - 发起数据库调用
    
    
    二、Example
    
        // 调用服务 app/controller/user.js
        module.exports = app => {
            class User extends app.controller {
                async addUser(){
                    let userData = {
                        name: 'siguang',
                        password: 'xxxxxx'
                    }
                    let isAddUser = yield ctx.service.user.createUser(userData)
                    if(setUser){
                        ctx.body = '添加成功'
                    }
                    ctx.body = isAddUser ? '添加成功' : '添加失败';
                }
            }
        }
    
        // 定义一个Service app/service/user.js
        module.exports = app => {                   // 中间件支持两个参数 options中间件配置,app当前应用Application的实例
            class User extends app.Service {
    
                async createUser(params){
                    const url = '/account/blance/createUser';
                    let result = await this.ctx.curl(url, {
                        method: 'POST',
                        dataType: 'json',   // 自动解析 JSON response
                        data: params,
                        timeout: 15000,     // 15 秒超时
                    }
    
                    this.ctx.logger.info('响应结果:', result.data);
                    return result.data;
                }
            }
            return User;
        };
    

    中间件 Middleware

    一、config.default.js 配置全局
    
        module.exports = {
            // 配置需要的中间件,数组顺序即为中间件的加载顺序
            middleware: [ 'gzip' ],
            // 配置 gzip 中间件的配置
            gzip: {
                threshold: 1024, // 小于 1k 的响应体不压缩
            },
        };
    
        // 框架中默认的中间件
        module.exports = {
            bodyParser: {
                jsonLimit: '10mb',
            },
        };
    
    
    二、通用配置
    
        enable:控制中间件是否开启。
    
        match:设置只有符合某些规则的请求才会经过这个中间件。
    
        ignore:设置符合某些规则的请求不经过这个中间件。
    
        bodyParser: {
            enable: false,
            jsonLimit: '10mb',
        },
    
    
    三、Example: 
    
        1、创建中间件 checkAuth.js  middleware
    
            module.exports = app => {
                return async function checkAuth(ctx, next){
                    const user = ctx.session.user;
                    console.log(`UserSession ------ ${JSON.stringify(user)}`)
    
                    if(user){
                        console.log(`visited true`);
                        await next();
                    }
                    else{
                        console.log(`visited false`);
                        ctx.body = {
                            data:{},
                            status: 10001,
                            message: 'Session失效'
                        }
                    }
                } 
            }
    
        2、路由中调用
    
            注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
    
            // 引用中间件
            const checkAuth = app.middleware.checkAuth();
    
            // 不需要验证
            app.get('/', 'home.index');
            app.get('/query', 'query.query.queryPage');
    
            // 需要验证是否登录的
            app.post('/web/getUserList', checkAuth, 'user.user.getUserList');            // 获取用户列表
    

    Validate 参数校验插件

    validate只用于post请求的参数校验,get请求取出的都是字符串
    
    一、在config/plugin.js配置中添加
    
        exports.validate = {
            enable: true,
            package: 'egg-validate',
        };
    
    
    二、Exmaple
    
        async refundApply() {
            const { ctx, app, service } = this;
            const paramRule = {
                partnerUserId: { type: 'string' },
                applyUserName: { type: 'string' },
                amount: { type: 'string' }, 
                sourceAccount: { type: 'string' }, 
            };
    
            const paramErrors = app.validator.validate(paramRule, ctx.request.body);
            if (paramErrors) {
                ctx.body = app.renderBody({
                    statusType: app.statusType.paramsError,
                    error: paramErrors,
                });
                return;
            }
    
            const response = await service.payTool.billquery.refundApply(ctx.request.body);
            ctx.body = response;
        }
    
    
    三、验证规则  
    
        1、required - 是否当前字段必须有,required: false 可以为空
    
        2、allowEmpty - 允许为空
    
        2、int - 只能为整数
    
        3、number - 可以是整数和浮点数
    
        4、date - 日期 'YYYY-MM-DD'     birthoday: 'date'
    
        5、dateTime - 日期 YYYY-MM-DD HH:mm:ss
    
        6、boolean - 是否是布尔值        working: 'boolean'
    
        7、string - 是否是字符串, 字符串的四个规则:            
    
            allowEmpty - 允许为空字符串
    
            format - 使用正则来验证字符串的格式
    
            max - 字符串最大长度
    
            min - 字符串最小长度
    
            const rule = {
                username: { allowEmpty: true, min: 10, max: 100, format: /^\d+$/ }
            }
    
        8、email - 是否是email格式
    
        9、password - 密码验证规则 max最大、min最小、compare比较  pass: {type: password, max: 32, min: 6}
    
        10、url - 是否是url
    
        11、enum - 如果是枚举需要加一个规则
    
            // operateType的值必须是values数组中的一项,values必须为数组
            const paramRule = {
                operateType: { type: 'enum', values: ['REPAYMENT', 'CHARGE'] },        
            }
            const paramErrors = app.validator.validate(paramRule, ctx.request.body);
    
        12、object - 如果是对象,需要加一个规则 
    
        13、array - 如果是数组,需要加一个规则
    
            itemType 数组中每一个元素的规则 
            rule - An object that validate the items of the array. Only work with itemType.
            max - 数组最大长度
            min - 数组最小长度
    
        https://github.com/node-modules/parameter#rule
        https://github.com/node-modules/parameter/blob/master/benchmark.js
        https://github.com/node-modules/parameter/blob/master/example.js
    

    jsonp

    jsonp只能get请求
    
    // config/config.default.js  jsonp的配置
    exports.jsonp = {
        callback: 'callback', // 识别 query 中的 `callback` 参数
        limit: 100, // 函数名最长为 100 个字符
    };
    
    
    // app/router.js  路由加jsonp中间件
    module.exports = app => {
        const jsonp = app.jsonp();
        app.get('/api/posts/:id', jsonp, 'posts.show');         // 通过jsonp中间件来让路由支付一个中间件
        app.get('/api/posts', jsonp, 'posts.list');
    };
    
    // app/controller/posts.js
    exports.show = function* (ctx) {
        ctx.body = {
            name: 'egg',
            category: 'framework',
            language: 'Node.js',
        };
    };
    
    用户请求 /api/posts/1?callback=fn,响应为 JSONP 格式,如果用户请求 /api/posts/1
    

    HttpClient

    Node模拟客户端请求,curl请求默认 content-type: application/x-www-form-urlencoded,
    
    app.curl(url, options) 和 app.httpclient.request(url, options) 两种方法相同
    
    一、Example:
    
        module.exports = app => {
            class HttpClientController extends app.Controller{
                async getHttpClient(){
                    const {ctx, app, service} = this;
    
                    let result = await this.ctx.curl('http://goucai.diyicai.com/lottery/getissue.action?lotteryId=001&issueLen=100&d=1502966960306', {
                        methods: 'get'
                    });
    
                    let phones = [];
                    let resultObj = JSON.parse(result.data.toString());
                    resultObj.forEach((data, idx, arr)=>{
                        console.log('data------', data);
                        phones.push(data.endTime);
                    })
    
                    ctx.body = phones.join(',')
                }
            }
            return HttpClientController;
        }
    
    
    二、config配置 // config/config.default.js
    
        exports.httpclient = {
            // 默认开启 http/https KeepAlive 功能
            keepAlive: true,
    
            // 空闲的 KeepAlive socket 最长可以存活 4 秒
            freeSocketKeepAliveTimeout: 4000,
    
            // 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
            timeout: 30000,
    
            // 允许创建的最大 socket 数
            maxSockets: Infinity,
    
            // 最大空闲 socket 数
            maxFreeSockets: 256,
    
            // 是否开启本地 DNS 缓存,默认关闭
            // 一旦设置开启,则每个域名的 DNS 查询结果将在进程内缓存 10 秒
            enableDNSCache: false,
        };
    
    
    三、options对象
    
        let options = {};
        const result = yield ctx.curl('https://httpbin.org/get?foo=bar', options);
    
        1、mothod: 请求方法
    
        2、data: 需要发送的数据 { foo: 'bar' }
    
        3、dataAsQueryString: Boolean 如果为 true 即使在post情况下,也会强制将options.data以 querystringstringify处理后拼接到url的query参数上
    
        4、content(String|Buffer): 发送请求的正文,如果设置了此参数会忽略data参数
    
            ctx.curl(url, {
                method: 'POST',
                // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
                content: '<xml><hello>world</hello></xml>',
                headers: {
                    'content-type': 'text/html',
                },
            });
    
        5、stream(ReadStream): 发送请求正文的可读数据流
    
            ctx.curl(url, {
                method: 'POST',
                stream: fs.createReadStream('/path/to/read'),
            });
    
        6、writeStream: 接受响应数据的可写数据流
    
            ctx.curl(url, {
                writeStream: fs.createWriteStream('/path/to/store'),
            });
    
        7、consumeWriteStream: Boolean 是否等待 writeStream 完全写完才算响应全部接收完毕,默认是 true
    
        8、contentType: 请求数据的格式,默认undefined
    
        9、dataType: 响应数据格式
    
        10、headers: 自定义请求头
    
        11、timeout: 请求超时时间 默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。
    
        12、agent: 
    
        13、httpsAgent
    
        14、auth、digestAuth
    
        15、followRedirect:Boolean 是否自动跟进3xx的跳转响应
    
        16、maxRedirects: number 最大自动跳转次数
    
        17、formatRedirectUrl: 自定义实现302、301
    
        18、beforeRequest: 请求发送前会调用beforeRequest钩子
    
            ctx.curl(url, {
                beforeRequest: options => {
                    // 例如我们可以设置全局请求 id,方便日志跟踪
                    options.headers['x-request-id'] = uuid.v1();
                }
            });
    
        19、streaming:是否直接返回响应流
    
        20、gzip: Boolean是否开始gzip
    
        21、timing: Boolean 是否开启请求各阶段的时间没是
    
    
    四、Get请求
    
        1、options.method = 'post'  设置请求参数,默认是get请求可以不用加
    
             const result = yield ctx.curl('https://httpbin.org/get?foo=bar', {method: post});
    
        2、status: 响应状态码
    
        3、headers: 响应头信息 有{'conent-type': 'text/html'}
    
        4、data: 响应body, 返回的是Buffer类型, 如果设置了options.dataType会根据参数来处理
    
    
    五、Post请求
    
        module.exports = function* post(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
    
                // 必须指定 method
                method: 'POST',
    
                // 通过 contentType 告诉 HttpClient 以 JSON 格式发送
                contentType: 'json',
    
                // 要传的数据 
                data: {
                    hello: 'world',
                    now: Date.now(),
                },
    
                // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
                dataType: 'json',
            });
            ctx.body = result.data;
        };
    
    
    六、From表单提交
    
        以 ontent-type: application/x-www-form-urlencoded 的格式提交请求数据
    
        // app/controller/form.js
        module.exports = function* form(ctx) {
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT 和 DELETE
                method: 'POST',
                // 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
                data: {
                    now: Date.now(),
                    foo: 'bar',
                },
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.form;
            // 响应最终会是类似以下的结果:
            // {
            //   "foo": "bar",
            //   "now": "1483864184348"
            // }
        };
    
    
    七、Multipart 方式上传
    
        From 表单提交时包含文件的时候,需要用到 multipart/form-data 进行提交了
    
        引用fromstream 第三方模块
    
        // app/controller/multipart.js
        const FormStream = require('formstream');
        module.exports = function* multipart(ctx) {
            const form = new FormStream();
    
            // 设置普通的 key value
            form.field('foo', 'bar');
    
            // 上传当前文件本身用于测试
            form.file('file', __filename);
            const result = yield ctx.curl('https://httpbin.org/post', {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 生成符合 multipart/form-data 要求的请求 headers
                headers: form.headers(),
                // 以 stream 模式提交
                stream: form,
                // 明确告诉 HttpClient 以 JSON 格式处理响应 body
                dataType: 'json',
            });
    
            ctx.body = result.data.files;
            // 响应最终会是类似以下的结果:
            // {
            //   "file": "'use strict';\n\nconst For...."
            // }
        };
    
        // 添加更多文件
        form.file('file1', file1);
        form.file('file2', file2);
    
    
    八、以Stream 方式上传文件
    
        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送
    
        // app/controller/stream.js
        const fs = require('fs');
        module.exports = function* stream(ctx) {
            // 上传当前文件本身用于测试
            const fileStream = fs.createReadStream(__filename);
    
            // httpbin.org 不支持 stream 模式,使用本地 stream 接口代替
            const url = `${ctx.protocol}://${ctx.host}/stream`;
            const result = yield ctx.curl(url, {
                // 必须指定 method,支持 POST,PUT
                method: 'POST',
                // 以 stream 模式提交
                stream: fileStream,
            });
    
            ctx.status = result.status;
            ctx.set(result.headers);
            ctx.body = result.data;
            // 响应最终会是类似以下的结果:
            // {"streamSize":574}
        };
    

    模板渲染

    读取数据渲染模板,呈现给用户
    
    一、使用
    
        1、$ npm i egg-view-ejs --save         // 安装
    
        2、配置
    
            // config/plugin.js
            exports.nunjucks = {
                enable: true,
                package: 'egg-view-nunjucks'        // 开启插件使用numjucks模板
            };
    
            // config/config.default.js
            exports.view = {
                defaultViewEngine: 'nunjucks',
                mapping: {
                    '.tpl': 'nunjucks',
                },
            };
    
    
        3、render、renderString两个方法
    
            render(fileName, locals, viewOptions): filenName: 文件路径、locals: 渲染的数据、viewOptions: 用户传入的配置
    
        render(name, locals) 渲染模板文件, 并赋值给 ctx.body
    
        renderView(name, locals) 渲染模板文件, 仅返回不赋值
    
        renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
    
    
    - numjucks模板语法: http://mozilla.github.io/nunjucks/cn/templating.html  见模板md -
    

    静态资源

    Egg内置static插件,static默认映射到 public目录
    
    app/public
    ├── css
    │   └── news.css
    └── js
        ├── lib.js
        └── news.js
    

    内置对象

    一、Application - 全局的方法和对象挂载到Application中
    
        // extend/application.js 创建全局对象
        module.exports = {
            // 全局返回状态类型
            statusType: 'STATUS_TYPE',
    
            // 生成返回报文
            renderBody(params) {
                if (typeof params.statusType === 'undefined') {
                    throw new Error('statusType error');
                }
                const response = {
                    data: params.data || {},
                    message: params.message || this.statusMessage[params.statusType],
                    status: params.statusType,
                };
    
                if (params.error) response.error = params.error;
                return response;
            }
        }
    
        // 调用 controller、service都可以
        module.exports = app => {
            return class UserController extends app.Controller {
                async fetch() {
                    this.ctx.body = app.cache.get(this.ctx.query.id);
                }
            };
        };
    

    定时任务

    有些任务需要定时来完成,如定时上报,定时远程更新本地缓存,定时任务都统一存放在 app/schedule 目录下
    
    创建一个定时任务 app/schedule/update_cache.js
    
    module.exports = {
        // 通过 schedule 属性来设置定时任务的执行间隔等配置
        schedule: {
            interval: '1m',     // 1 分钟间隔
            type: 'all',        // 指定所有的 worker 都需要执行
        },
    
        // task 是真正定时任务执行时被运行的函数,第一个参数是一个匿名的 Context 实例
        * task(ctx) {
            const res = yield ctx.curl('http://www.api.com/cache', {
                dataType: 'json',
            });
            ctx.app.cache = res.data;
        }
    };
    

    框架扩展

    框架扩展自身功能: Application、Context、Request、Response、Helper,在这几个对象上扩展就在extend目录里创建相应的js文件,application.js
    
        Helper用来提供一些实用的utility函数
    
    一、Application 全局应用对象扩展
    
        访问 ctx.app,  Controller,Middleware,Helper,Service 中都可以通过 this.app 访问到 Application 对象
    
        // app.js
        module.exports = app => {
            app.config
        };
    
        // 扩展 app.foo() 方法 app/extend/application.js
        module.exports = {
            foo(param) {
                // this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
            },
        };
    
    
    二 、Context 扩展
    
        extend/content.js
    
        module.export = {
            async foo(param){
                ...
            }
        }
    
        引用 await this.ctx.foo({name: 'siguang'});
    
    
    三、Helper 用来编写一些实用函数
    
        // app/extend/helper.js
        const moment = require('moment');
        exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();
    
        // 也可以在模板里面使用:
        <!-- app/views/news/list.tpl -->
        {{ helper.relativeTime(item.time) }}
    

    启动自定义

    用来进行应用启动时进行初始化工作
    
    // 通过入口文件app.js
    module.exports = app => {
        app.beforeStart(function* () {
    
            // 应用会等待这个函数执行完成才启动
            app.cities = yield app.curl('http://example.com/city.json', {
                method: 'GET',
                dataType: 'json',
            });
        });
    };
    

    logger 日志

    this.ctx.logger.info('xxxxxx');
    
    logger.debug()、logger.info()、logger.warn()、logger.error()
    
    config.deubgger 文件配置
    
        logger: {
            level: 'DEBUG',
            dir: '../app/logger',
        }
    

    本地开发

    egg-bin模块(用于本地开发和单元测试)
    
    package.json:
    
       {
           "scripts": {
               "dev": "egg-bin dev --port 7001"
           }
       }
    

    插件的开发

    自定义插件存储目录:  lib/plugin/...
    
    egg-ua插件
    
    1、创建package.json
    
        {
            "eggPlugin": {
                "name": "ua"
            }
        }
    
    2、egg-ua/us.js插件文件
    
        module.exports = {
            get isIOS() {
                const iosReg = /iphone|ipad|ipod/i;
                return iosReg.test(this.get('user-agent'));
            },
        };
    
    3、config/plugin.js   通过path来挂载插件
    
        const path = require('path');
        exports.ua = {
            enable: true,
            path: path.join(__dirname, '../lib/plugin/egg-ua'),
        };
    

    项目目录

    ├── package.json
    ├── app.js (可选)            // 启动初始化
    ├── agent.js (可选)
    ├── app
    |   ├── router.js           // 路由规则
    │   ├── controller          // 处理用户的输入
    │   |   └── home.js
    │   ├── service (可选)       // 服务,编写业务逻辑
    │   |   └── user.js
    │   ├── middleware (可选)    // 中间件
    │   |   └── response_time.js
    │   ├── schedule (可选)      // 定时任务
    │   |   └── my_task.js
    │   ├── public (可选)        // 静态目录
    │   |   └── reset.css
    │   ├── view (可选)          // 模板
    │   |   └── home.tpl
    │   └── extend (可选)        // 框架扩展
    │       ├── helper.js (可选)
    │       ├── request.js (可选)
    │       ├── response.js (可选)
    │       ├── context.js (可选)     // ctx扩展
    │       ├── application.js (可选)     // 全局方法
    │       └── agent.js (可选)
    ├── config                  // 配置
    |   ├── plugin.js
    |   ├── config.default.js
    │   ├── config.prod.js
    |   ├── config.test.js (可选)
    |   ├── config.local.js (可选)
    |   └── config.unittest.js (可选)
    └── test                    // 测试
        ├── middleware
        |   └── response_time.test.js
        └── controller
            └── home.test.js
    

    参考资料
    http://koa.bootcss.com/