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/