Egg.js初步使用

Egg.js的简单初步使用

所使用代码已上传github, 注意数据库配置方面。如有问题可交流。

简单使用 – 骨架类型

eggjs 是阿里出品的企业级 node 框架,奉行 约定优于配置 ,一切基于约定开发,减少团队沟通成本,另一个比较重要的点是 egg 拥有完善的日志系统,对于 bug 定位等及其方便。

骨架类型说明
simple简单 egg 应用程序骨架
empty空的 egg 应用程序骨架
pluginegg plugin 骨架
frameworkegg framework 骨架

安装依赖 yarn create egg --type=simple 或者 npm install -g yarn ,然后yarn 或者 npm i安装

npx egg-init --type=ts showcase
cd showcase && npm i
npm run dev

在这里插入图片描述

启动:yarn dev

  • dev : 开发环境中使用,不用重启服务器,只要刷新。修改内容就会更改。
  • start:生产环境中使用,也就是开发完成,正式运营之后。以服务的方式运行。修改后要停止和重启后才会发生改变。

在这里插入图片描述
在这里插入图片描述

- app                        - 项目开发的主目录,工作中的代码几乎都写在这里面
-- controller                -- 控制器目录,所有的控制器都写在这个里面
-- router.js                 -- 项目的路由文件
- config                     - 项目配置目录,比如插件相关的配置
-- config.default.js         -- 系统默认配置文件
-- plugin.js                 -- 插件配置文件
- logs                       -- 项目启动后的日志文件夹
- node_modules               - 项目的运行/开发依赖包,都会放到这个文件夹下面
- test                       - 项目测试/单元测试时使用的目录
- run                        - 项目启动后生成的临时文件,用于保证项目正确运行
- typings                    - TypeScript配置目录,说明项目可以使用TS开发
- .eslintignore              - ESLint配置文件
- .eslintrc                  - ESLint配置文件,语法规则的详细配置文件
- .gitignore                 - git相关配置文件,比如那些文件归于Git管理,那些不需要
- jsconfig.js                - js配置文件,可以对所在目录下的所有JS代码个性化支持
- package.json               - 项目管理文件,包含包管理文件和命令管理文件
- README.MD                  - 项目描述文件  

Egg.js与Koa/Express 对比

框架的设计理念 约定优于配置 .

在这里插入图片描述

编写controller 和 单元测试

同步单元测试 和 异步单元测试

// zhuba.js
'use strict';

const Controller = require('egg').Controller;

class zhubaController extends Controller {
  async index() {
    const {
      ctx,
    } = this;
    ctx.body = '<h2>This is zhuBaController <h2>';
  }
}

module.exports = zhubaController;

// zhuba.test.js
'use strict';

const {
  app,
} = require('egg-mock/bootstrap');

describe('zhuba test', () => {
  it('zhuba index', () => {
    return app.httpRequest().get('/zhuba').expect(200)
      .expect('<h2>This is zhuBaController <h2>');
  });
});

// describe( )方法有两个参数,第一个是测试的描述(字符串类型),这个描述一般都是用文件的路径。
// 第二个参数是一个回调函数,里边是对这个控制器里边的具体方法的测试用例。

// 官方的 home.test.js
'use strict';

const {
  app,
  assert,
} = require('egg-mock/bootstrap');

describe('test/app/controller/home.test.js', () => {
  it('should assert', async () => {
    const pkg = require('../../../package.json');
    assert(app.config.keys.startsWith(pkg.name));
  });

  it('should GET /', async () => {
    return app.httpRequest()
      .get('/')
      .expect('Hello World! Egg.js')
      .expect(200);
  });
});

在这里插入图片描述

yarn test 全绿即是没问题

// zhuba.js
'use strict';

const Controller = require('egg').Controller;

class zhubaController extends Controller {
  async index() {
    const {
      ctx,
    } = this;
    ctx.body = '<h2>This is zhuBaController <h2>';
  }
  async getGirls() {
    const {
      ctx,
    } = this;
    await new Promise(resolve => {
      setTimeout(() => {
        resolve(ctx.body = '<h1>kunkun,正在向你走来</h1>');
      }, 1000);
    });

  }
}

module.exports = zhubaController;

// zhuba.test.js
'use strict';

const {
  app,
} = require('egg-mock/bootstrap');

describe('zhuba test', () => {
  it('zhuba index', () => {
    return app.httpRequest().get('/zhuba').expect(200)
      .expect('<h2>This is zhuBaController <h2>');
  });
  it('zhuba getGirls', async () => {
    await app.httpRequest().get('/getGirls').expect(200)
      .expect('<h1>kunkun,正在向你走来</h1>');
  });
});

Get 请求和参数传递

自由传参

配置好router后

在这里插入图片描述

严格传参

少参数就会404
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

POST 请求

在这里插入图片描述
![image.png](https://img-blog.csdnimg.cn/img_convert/c2f31af997442718ef6178074e36dd9c.png#averageHue=#153b46&clientId=u1f7a13f2-de01-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=187&id=u6a39d3d4&margin=[object Object]&name=image.png&originHeight=280&originWidth=658&originalType=binary&ratio=1&rotation=0&showTitle=false&size=136647&status=done&style=none&taskId=ub33f554c-543d-4037-9da2-909a25a234f&title=&width=438.6666666666667)
添加config/config.default.js, 关闭csrf安全策略

CSRF的全名为 Cross-site request forgery, 它的中文名为 伪造跨站请求。

// csrf 安全策略
  config.security = {
    csrf: {
      enable: false,
    }
  }

![image.png](https://img-blog.csdnimg.cn/img_convert/5e89bc6facae37620dede01ced9d13cf.png#averageHue=#0e343f&clientId=u1f7a13f2-de01-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=159&id=u4e2ca469&margin=[object Object]&name=image.png&originHeight=239&originWidth=917&originalType=binary&ratio=1&rotation=0&showTitle=false&size=105620&status=done&style=none&taskId=u39cb3433-ead6-4e4a-96b2-0919fea7997&title=&width=611.3333333333334)
![image.png](https://img-blog.csdnimg.cn/img_convert/c3271cb11a64e5ca63a629b98fe010ca.png#averageHue=#fdfbf7&clientId=u1f7a13f2-de01-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=u2388791f&margin=[object Object]&name=image.png&originHeight=502&originWidth=870&originalType=binary&ratio=1&rotation=0&showTitle=false&size=34611&status=done&style=none&taskId=u7ce19431-5a4f-4ce8-a9ef-d81999dc8ff&title=&width=435)
服务端接受请求:

async add(){
    const ctx = this.ctx
    ctx.body = {
      status: 200,
      data:ctx.request.body
    }
  }

![image.png](https://img-blog.csdnimg.cn/img_convert/95a0474fb2d51ca8624b20c82b890f42.png#averageHue=#fefefd&clientId=u1f7a13f2-de01-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=93&id=ua55dc232&margin=[object Object]&name=image.png&originHeight=140&originWidth=388&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6080&status=done&style=none&taskId=u13742b66-4a02-4d44-9901-46b63a04add&title=&width=258.6666666666667)

Service 服务

Service是用来编写和数据库直接交互的业务逻辑代码。Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。

简单来说,就是把业务逻辑代码进一步细化和分类,所以和数据库交互的代码都放到Service中。这样作有三个明显的好处。

  • 保持Controller中的逻辑更加简介,
  • 保持业务逻辑的独立性,抽象出来的Service可以被多个Controller调用。
  • 将逻辑和展现分离,更容易编写测试用例。

只要是和数据库的交互操作,都写在Service里,用了Egg框架,就要遵守它的约定。

在/app/service目录下编写自己的服务
![image.png](https://img-blog.csdnimg.cn/img_convert/351c7b7dd45cbced3c49b8a797d2b430.png#averageHue=#0c3540&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=349&id=uee6fb068&margin=[object Object]&name=image.png&originHeight=523&originWidth=980&originalType=binary&ratio=1&rotation=0&showTitle=false&size=231523&status=done&style=none&taskId=u3553ece9-336c-4010-b7f2-bb139e38d08&title=&width=653.3333333333334)![image.png](https://img-blog.csdnimg.cn/img_convert/b599081e153a5bfa43cba71cbf87ad7b.png#averageHue=#123640&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=182&id=u001c9690&margin=[object Object]&name=image.png&originHeight=273&originWidth=723&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119036&status=done&style=none&taskId=uc77db943-952d-4637-8c83-2ba7064d1f2&title=&width=482)![image.png](https://img-blog.csdnimg.cn/img_convert/dee41f4deae8af88959e0b87106d9679.png#averageHue=#ece465&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=185&id=u27c8311b&margin=[object Object]&name=image.png&originHeight=277&originWidth=532&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19880&status=done&style=none&taskId=u2af7b149-1988-4adb-b153-9c819c87b53&title=&width=354.6666666666667)
service方法的可调用性
一个service方法完成后,可以在其它的Controller里进行使用。比如在home.js中进行使用。打开/app/controller/home.js文件下,新建一个testGetGirl( )方法,然后新增好路由,这样id即可被数据库得到![image.png](https://img-blog.csdnimg.cn/img_convert/20d4918732bd514a1f49f5d62dc06cc6.png#averageHue=#133640&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=150&id=u1963d607&margin=[object Object]&name=image.png&originHeight=225&originWidth=707&originalType=binary&ratio=1&rotation=0&showTitle=false&size=110873&status=done&style=none&taskId=u31efbdb0-c569-46cf-917c-908f8d98cca&title=&width=471.3333333333333)![image.png](https://img-blog.csdnimg.cn/img_convert/e9fd3b4149f8d517a88d3da997cfcdac.png#averageHue=#e1cc82&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=138&id=u65df74da&margin=[object Object]&name=image.png&originHeight=276&originWidth=580&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19659&status=done&style=none&taskId=ud48e752e-caa3-436c-a9a6-3caae30b540&title=&width=290)
起名的时候最好和Controller对应起来。写法和Controller类似,并且在任何Controller下都可以得到Service提供的数据。

View中使用EJS模板引擎

介绍

可选的模板不少,具体参考官方文档。
![image.png](https://img-blog.csdnimg.cn/img_convert/39141dc18118be02c79eb832c2864c54.png#averageHue=#fefefd&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=267&id=u8906ec3e&margin=[object Object]&name=image.png&originHeight=533&originWidth=1094&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86960&status=done&style=none&taskId=ud1f80fa2-50a8-4b27-8f51-4f344a08688&title=&width=547)
服务端渲染的好处

  • 对SEO非常友好,单页应用,比如Vue是到客户端才生成的。这种应用对于国内的搜索引擎是没办法爬取的,这样SEO就不会有好的结果。所以如果是官网、新闻网站、博客这些展示类、宣传类的网址,必须要使用服务端渲染技术。
  • 后端渲染是老牌开发模式,渲染性能也是得到一致认可的。在PHP时代,这种后端渲染的技术达到了顶峰。
  • 对前后端分离开发模式的补充,并不是所有的功能都可以实现前后端分离的。特别现在流行的中台系统,有很多一次登录,处处可用的原则。这时候就需要服务端渲染来帮忙。

EJS1

安装和配置、使用:yarn add egg-view-ejs
![image.png](https://img-blog.csdnimg.cn/img_convert/6fd0571967ba7456cddfbbd84feccf7d.png#averageHue=#0c3540&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=283&id=u6cd94d80&margin=[object Object]&name=image.png&originHeight=424&originWidth=510&originalType=binary&ratio=1&rotation=0&showTitle=false&size=106554&status=done&style=none&taskId=uf70e9b8a-86c8-481c-903a-68cdd7ff8ae&title=&width=340)![image.png](https://img-blog.csdnimg.cn/img_convert/858961f1d6a050690018cf3a26605359.png#averageHue=#0b333e&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=254&id=ub4ea0a32&margin=[object Object]&name=image.png&originHeight=507&originWidth=522&originalType=binary&ratio=1&rotation=0&showTitle=false&size=118149&status=done&style=none&taskId=u88c32463-76ce-4d03-8fc3-3cce69f29bb&title=&width=261)
![image.png](https://img-blog.csdnimg.cn/img_convert/6d64dc473229bdb5daed8e7bd9adabe9.png#averageHue=#133a45&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=137&id=ueb0661f6&margin=[object Object]&name=image.png&originHeight=205&originWidth=1016&originalType=binary&ratio=1&rotation=0&showTitle=false&size=103912&status=done&style=none&taskId=u216688c9-435c-400f-b88d-c3a073d3391&title=&width=677.3333333333334)
/app/view/下新建 zhuba.html文件即可被渲染,访问该路由地址即可
![image.png](https://img-blog.csdnimg.cn/img_convert/93dc9ce96fdab0a2fcfdcc927d69f274.png#averageHue=#e8d78b&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=146&id=u3c6060c1&margin=[object Object]&name=image.png&originHeight=219&originWidth=501&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14686&status=done&style=none&taskId=uc87b6d4f-4acd-44d6-9609-e08c3b23dfc&title=&width=334)

EJS2

  • 显示controller中数据 (<%= 参数 %>)

![image.png](https://img-blog.csdnimg.cn/img_convert/ec70142a90745d4e37a76625834fbbaf.png#averageHue=#0c3540&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=262&id=u528adb55&margin=[object Object]&name=image.png&originHeight=393&originWidth=591&originalType=binary&ratio=1&rotation=0&showTitle=false&size=127298&status=done&style=none&taskId=u6cb51326-1128-4793-8208-311c9a30ed1&title=&width=394)

  • 数据的循环显示 (for)

![image.png](https://img-blog.csdnimg.cn/img_convert/37e8cce6da604ce1e84c14c8b2524ec4.png#averageHue=#153843&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=210&id=ua540f322&margin=[object Object]&name=image.png&originHeight=315&originWidth=597&originalType=binary&ratio=1&rotation=0&showTitle=false&size=124323&status=done&style=none&taskId=u0a83753e-a62a-4573-a4c5-794b40e4944&title=&width=398)

  • 修改默认分隔符
config.ejs={
  delimiter: "$"
}

EJS3

  • 公共代码片段的使用 只需要写一些代码片段,即抽离公共部分(组件思想) **<%- include('header.html') %>**

![image.png](https://img-blog.csdnimg.cn/img_convert/55f3c2239e94e1c1dee013344af6670b.png#averageHue=#0f353f&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=283&id=u1df5e82f&margin=[object Object]&name=image.png&originHeight=425&originWidth=1199&originalType=binary&ratio=1&rotation=0&showTitle=false&size=284640&status=done&style=none&taskId=u3ea0f48b-9189-44bb-9c09-9ee81b6d922&title=&width=799.3333333333334)

  • 配置静态资源:/app/public目录下,可以直接访问,不需配置路由,直接显示文件内容,因为Egg使用了egg-static插件

![image.png](https://img-blog.csdnimg.cn/img_convert/9055bda13906c00a533228f5441e9c5a.png#averageHue=#e0d989&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=143&id=u412d8ecb&margin=[object Object]&name=image.png&originHeight=214&originWidth=643&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15783&status=done&style=none&taskId=u6e480cf9-b4b5-48ff-aadf-a512b2ee5c4&title=&width=428.6666666666667)![image.png](https://img-blog.csdnimg.cn/img_convert/0e637e5094c7dd0054ede343132a2e31.png#averageHue=#b7d7da&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=121&id=ue9e5b977&margin=[object Object]&name=image.png&originHeight=182&originWidth=627&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23767&status=done&style=none&taskId=u62c11781-5cd9-4adc-9220-8e79ea27b1c&title=&width=418)
修改config.default.js可以将访问public变成assets,当然此时使用public会404

onfig.static = {
  prefix:"/assets/"
}
  • 使用静态资源 在.html中<link rel="stylesheet"type="text/css"href="public/css/default.css" />

和正常使用一致
![image.png](https://img-blog.csdnimg.cn/img_convert/0311595e1c4187efdc0bf07cc11a1942.png#averageHue=#efb874&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=249&id=ufe8b3a72&margin=[object Object]&name=image.png&originHeight=373&originWidth=591&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23337&status=done&style=none&taskId=ucc53eea3-df31-4659-b6fa-9a87b79d3c6&title=&width=394)![image.png](https://img-blog.csdnimg.cn/img_convert/54d5fb6446b3c2127b62795923069038.png#averageHue=#0d3641&clientId=u6138dc2e-c60d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=200&id=u07b83038&margin=[object Object]&name=image.png&originHeight=467&originWidth=1439&originalType=binary&ratio=1&rotation=0&showTitle=false&size=370032&status=done&style=none&taskId=u026c2b7c-3f1a-4f3c-a318-11f56b4afe9&title=&width=616)

Cookie

增删改查

HTTP请求是无状态的,但是在开发时,有些情况是需要知道请求的人是谁的。为了解决这个问题,HTTP协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保留,并在下一次请求同一个服务的时候带上。

// html中插入DOM和JS方法
<div>
    <button onclick="add()">增加Cookie</button>
    <button onclick="del()">删除Cookie</button>
    <button onclick="editor()">修改Cookie</button>
    <button onclick="show()">查看Cookie</button>
</div>
<script>
    function add(){
        fetch("/add",{
            method:"post",
            headers:{
                "Content-type":"application/json"
            }
        });
    }
    function del(){
        fetch("/del",{
            method:"post",
            headers:{
                "Content-type":"application/json"
            }
        });
    }
    function editor(){
        fetch("/editor",{
            method:"post",
            headers:{
                "Content-type":"application/json"
            }
        });
    }
    function show(){
        fetch("/show",{
            method:"post",
            headers:{
                "Content-type":"application/json"
            }
        });
    }
</script>
async add() {
  const ctx = this.ctx
  ctx.cookies.set("user", "jspang.com")
  ctx.body = {
    status:200,
    data:'Cookie添加成功'
  }
}
async del() {
  const ctx = this.ctx
  ctx.cookies.set("user", null)
  ctx.body = {
    status:200,
    data:'Cookie删除成功'
  }
}
async editor() {
  const ctx = this.ctx
  ctx.cookies.set("user",'bilibili')
  ctx.body = {
    status:200,
    data:'Cookie修改成功'
  }
}
async show() {
  const ctx = this.ctx
  const user=ctx.cookies.get("user")
  console.log(user)
  ctx.body = {
    status:200,
    data:'Cookie显示成功'
  }
}

// 配置路由
router.post('/add', controller.zhuba.add);
router.post('/del', controller.zhuba.del);
router.post('/editor', controller.zhuba.editor);
router.post('/show', controller.zhuba.show);

![image.png](https://img-blog.csdnimg.cn/img_convert/4927ff7bbe767508694b9ca9d6fc7be1.png#averageHue=#ecb06e&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=310&id=u0a1a3a75&margin=[object Object]&name=image.png&originHeight=465&originWidth=1914&originalType=binary&ratio=1&rotation=0&showTitle=false&size=90167&status=done&style=none&taskId=uc7786c44-0984-42c6-985b-bfc0a17f9c0&title=&width=1276)
差:
![image.png](https://img-blog.csdnimg.cn/img_convert/9e5e42f0cc3e35dc91a750ec5fdb7f09.png#clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=93&id=ud68ff80f&margin=[object Object]&name=image.png&originHeight=139&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&size=79766&status=done&style=none&taskId=ufab7fb3a-cdfe-491e-820b-fe75a8e1951&title=&width=672.6666666666666)

配置和加密

一些配置选项,比如有效时间、服务端操作设置和中文编写加密这些操作。
ctx.cookies.set( ) 方法是有三个参数的,第一个参数是key,第二个参数是value,第三个参数就可以进行配置。比如你需要配置Cookie的有效时间,可以使用maxAge属性。(这个时间是毫秒。)

  1. maxAge 时效设置

maxAge: 1000 * 2 (毫秒)

async add(){
  const ctx = this.ctx
  ctx.cookies.set("user","jspang.com",{
    maxAge:1000*2
  })
  ctx.body = {
    status:200,
    data:'Cookie添加成功'
  }
}
  1. HhttpOnly 是否只允许服务端来操作Cookie

httpOnly:false

伪造Cookie来绕过登录是黑客经常使用的一种手段,所以为了安全,Egg.js默认设置只允许服务端来操作Cookie。
比如通过JS的方式document.cookie获取Cookie是不能获取的(需要在浏览器的控制台输入获取)。当我们想通过客户端操作Cookie时,可以通过下面的代码进行设置。

sync add(){
  const ctx = this.ctx
  ctx.cookies.set("user","jspang.com",{
    maxAge:1000*60,
    httpOnly:false
  })
  ctx.body = {
    status:200,
    data:'Cookie添加成功'
  }
}
  1. encrypt 设置中文Cookie (set加密 show解密)

加密只要在第三个参数中,加入encrypt:true,就可以加密成功。

ctx.cookies.set("user","zhuba",{
  encrypt:true
})

直接通过ctx.cookies.get( )方法获取,获取的是undefind,也就是无法获取的。这时候需要再次配置解密才可以使用, 在show( )方法里配置代码如下。

const user=ctx.cookies.get("user",{
  encrypt:true
})

Session

Cookie和Session非常类似,Egg中的Session就存储再Cookie中,但是Session比Cookie的安全性更高。所以在开发中经常使用Cookie来保存是否登录,而用Session来保存登录信息和用户信息。

添加、获取、删除

添加:在add()方法中 ctx.session.username = 'zhuba'
获取:直接获取 const username = ctx.session.username
删除:把值赋为空即可 ctx.session.username = null
session是支持中文的,不需要加密解密

配置项

config.session = {
    key :"PANG_SESS",   // 设置Key的默认值
    httpOnly:true,      // 设置服务端操作
    maxAge:1000*60  ,   // 设置最大有效时间
    renew: true,        // 页面有访问动作自动刷新session 
}

中间件的编写

Egg是对Koa的二次封装,所以中间件这部分和Koa框架是一样的,也追寻洋葱圈模型。

Egg.js约定中间件要写在/app/middleware文件夹下

module.exports = options => {
  return async (ctx, next) => {
    if (ctx.session.counter) { // 没学数据库先使用session
      ctx.session.counter++;
    } else {
      ctx.session.counter = 1;
    }
    await next();
  }
};

手动挂载:config/config.default.js config.middleware = ['counter'];
在index()中使用, 可以发现中间件现在的作用域是全局的。
![image.png](https://img-blog.csdnimg.cn/img_convert/19883b8923fcb38b8e3824f904f1d04c.png#averageHue=#0e3846&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=115&id=u25a8e104&margin=[object Object]&name=image.png&originHeight=173&originWidth=991&originalType=binary&ratio=1&rotation=0&showTitle=false&size=95136&status=done&style=none&taskId=ud3544952-47c6-4777-9960-69b38eb318f&title=&width=660.6666666666666)
要想只在某一页面使用需要在router(路由)中配置中间件的使用,并去除全局挂载。

  const counter = app.middleware.counter()
  const {
    router,
    controller,
  } = app;
  router.get('/', counter, controller.home.index);

在实际开发中中间件还是有很多用处的,比如日志的记录、比如所有页面的Gzip压缩展示、比如全局埋点的使用。

Extend-application 方法扩展

eggjs的方法的扩展和编写

Egg.js可以对内部的五种对象进行扩展,以下是可扩展的对象、说明、this指向和使用方式。
![image.png](https://img-blog.csdnimg.cn/img_convert/2bccbcb21f7f40e1bdb3fa1b67da232d.png#averageHue=#d0dbe7&clientId=u975a7b25-27c8-4&crop=0.0466&crop=0.0825&crop=0.9266&crop=0.8712&from=paste&height=347&id=uc5f3d88c&margin=[object Object]&name=image.png&originHeight=1103&originWidth=1905&originalType=url&ratio=1&rotation=0&showTitle=false&size=195785&status=done&style=none&taskId=ub31fc84e-06aa-437c-9a0b-2e0292502c0&title=&width=600)

application对象方法拓展

按照Egg的约定,扩展的文件夹和文件的名字必须是固定的。比如要对application扩展,要在/app目录下,新建一个/extend文件夹,然后在建立一个application.js文件。

module.exports = {
  // 方法扩展
  currentTime() {
    const current = getTime();
    return current;
  },
};
function getTime() {
  const now = new Date();
  const year = now.getFullYear(); // 得到年份
  const month = now.getMonth() + 1; // 得到月份
  const date = now.getDate(); // 得到日期
  const hour = now.getHours(); // 得到小时数
  const minute = now.getMinutes(); // 得到分钟数
  const second = now.getSeconds(); // 得到秒数
  const nowTime = year + '年' + month + '月' + date + '日 ' + hour + ':' + minute + ':' + second;
  return nowTime;
}

使用:

// .js
async index() {
  const { ctx ,app } = this;
  await ctx.render(
    'zhuba.html',{
      nowTime: app.currentTime()
    })
}
// .html 模板
<%= nowTime %>

![image.png](https://img-blog.csdnimg.cn/img_convert/7284e4ed758c33988b0c973c35f000ad.png#averageHue=#fee4e3&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=175&id=u38584f50&margin=[object Object]&name=image.png&originHeight=262&originWidth=604&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31145&status=done&style=none&taskId=ub2800a60-b356-436d-b7c3-78aa044cde1&title=&width=402.6666666666667)

application对象属性拓展

对属性( property) 的扩展的关键字是get,也需要写在application.js文件里。

module.exports = {
    //方法扩展
    currentTime(){
        const current = getTime();
        return current;
    },
    //属性扩展
    get timeProp(){
        return getTime();
    }
};

加入get,就会默认是一个属性,可以直接以属性的形式在controller方法里进行调用。

Extend-context 上下文对象的方法拓展

之前通过上下文来获取传递参数时,get方法请求和post方法请求的获取方式是不同的,我们编写的方法可以让这两个请求获取参数的方法统一化,都用params( )方法。新建context.js,配置好页面和路由后使用

// context.js
module.exports = {
  params(key) {
    const method = this.request.method
    if (method === 'GET') {
      return key ? this.query[key] : this.query;
    }
    return key ? this.request.body[key] : this.request.body;
  },
};

// newContext zhuba.js
async newContext() {
  const {
    ctx,
  } = this;
  const params = ctx.params();
  console.log(params);
  ctx.body = 'newContext';
}
// router.js
router.get('/newContext', controller.zhuba.newContext);
router.post('/newContext', controller.zhuba.newContext);

![image.png](https://img-blog.csdnimg.cn/img_convert/c2d1c1c972ba443255eb736f5ab3a1c9.png#averageHue=#acdd9d&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=117&id=u9c78c3d0&margin=[object Object]&name=image.png&originHeight=176&originWidth=734&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18046&status=done&style=none&taskId=u9d536e7f-6060-4e82-a3d1-54bee14adcf&title=&width=489.3333333333333)![image.png](https://img-blog.csdnimg.cn/img_convert/7e23bc369b2294d16017e71b4d17ea4d.png#averageHue=#fdfefb&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=286&id=ue2bed7cc&margin=[object Object]&name=image.png&originHeight=429&originWidth=700&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33430&status=done&style=none&taskId=ud79b4ba8-9528-4674-8e9c-e1ca3ea9997&title=&width=466.6666666666667)![image.png](https://img-blog.csdnimg.cn/img_convert/ba068bf85be40ef61c037435effe10ac.png#averageHue=#0f3540&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=310&id=ue36c9d07&margin=[object Object]&name=image.png&originHeight=620&originWidth=1319&originalType=binary&ratio=1&rotation=0&showTitle=false&size=510231&status=done&style=none&taskId=u367f49f2-933a-4385-883b-856890bdf7b&title=&width=660)

Extend-request

Request 中的扩展一般是扩展的属性。比如扩展 Request 中的一个属性,通过属性直接得到请求头中的 token 属性。

// Extend-request
  async newRequest() {
    const {
      ctx,
    } = this;
    const token = ctx.request.token;
    ctx.body = {
      status: 200,
      body: token,
    };
  }

Egg.js 对 Request 的扩展也需要在/app/extend文件夹下,新建一个request.js文件,然后在这个文件里写扩展属性。

module.exports = {
  get token() {
    console.log('token', this.get('token'));
    return this.get('token');
  },
};
// http测试
POST http://127.0.0.1:7001/newRequest
Content-Type: application/json
token: 'zhuba'

{
    "name":"小红",
    "age":18
}

![image.png](https://img-blog.csdnimg.cn/img_convert/b996fd03e0de205771671d6f10b8dd4e.png#averageHue=#0e3540&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=499&id=u5d6a6ed8&margin=[object Object]&name=image.png&originHeight=749&originWidth=1347&originalType=binary&ratio=1&rotation=0&showTitle=false&size=618431&status=done&style=none&taskId=uc7ad11a6-2792-4057-88dd-242ecce08ee&title=&width=898)

Extend-response、helper

response

和上一个是差不多的, 需要设置的方法以set关键字开头,然后用this.set( )就可以设置返回的token了。

module.exports = {
  set token(token) {
    this.set('token', token);
  },
};

// zhuba.js
// newRespose
async newResponse() {
  const {
    ctx,
  } = this;
  ctx.response.token = 'zhuba.cloud';
  ctx.body = 'newRespose';
}
// router.js
router.get('/newResponse', controller.zhuba.newResponse);

![image.png](https://img-blog.csdnimg.cn/img_convert/39e3ead55fd6638d689057d8fa745f1d.png#averageHue=#debe84&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=511&id=u7dc956bb&margin=[object Object]&name=image.png&originHeight=1022&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=187541&status=done&style=none&taskId=u7964c0f8-5e80-4036-abd5-edaf0822659&title=&width=960)

helper

demo是编写一个字符串进行base64加密的方法。

module.exports = {
  base64Encode(str = '') {
    return new Buffer(str).toString('base64');
  },
};

// 重新利用一下原本的 newRespose
// newRespose
async newResponse() {
  const {
    ctx,
  } = this;
  ctx.response.token = 'zhuba.cloud';
  // ctx.body = 'newRespose';
  const testBase64 = ctx.helper.base64Encode('zhuba.cloud');
  ctx.body = testBase64;
}

![image.png](https://img-blog.csdnimg.cn/img_convert/9c70cd5a54465d8596104da345020754.png#averageHue=#ded58b&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=435&id=ufb3f41b7&margin=[object Object]&name=image.png&originHeight=653&originWidth=1590&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104194&status=done&style=none&taskId=ub246fa63-608e-4e15-ae92-0f0c3c93535&title=&width=1060)

定时任务编写

定时任务需要按照Egg的约定,/app目录下,新建shedule文件夹。然后在shedule文件夹下,新建一个get_time.js文件。设置每3秒钟,在控制台输出当前时间戳。

const Subscription = require('egg').Subscription;

class GetTime extends Subscription {
  static get schedule() {
    return {
      interval: '10s',
      type: 'worker',
    };
  }

  async subscribe() {
    console.log(Date.now());
  }
}

module.exports = GetTime;

也可以使用更复杂的cron属性进行定时。cron属性有6个参数。

*    *    *    *    *    *
┬    ┬    ┬    ┬    ┬    ┬
│    │    │    │    │    |
│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)
│    │    │    │    └───── month (1 - 12)
│    │    │    └────────── day of month (1 - 31)
│    │    └─────────────── hour (0 - 23)
│    └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)

比如设置每3秒钟,返回时间戳,可以写成下面的样子。

static get schedule(){
  return {
    cron: '*/3 * * * * *',
    type:'worker'
  };
}

配置连接MySql数据库

安装依赖和配置:yarn add egg-mysql -S

// /config/plugin.js
'use strict';

/** @type Egg.EggPlugin */
module.exports = {
  // had enabled by egg
  // static: {
  //   enable: true,
  // }
  ejs: {
    enable: true,
    package: 'egg-view-ejs',
  },
  mysql: {
    enable: true,
    package: 'egg-mysql',
  },
};

// /config/config.default.js
  // // ejs egg-mysql
  config.mysql = {
    app: true, // 是否挂载到app下面
    agent: false, // 是否挂载到代理下面
    client: {
      host: 'localhost', // 数据库地址
      port: '3306', // 端口
      user: 'root', // 用户名
      password: '12345', // 密码
      database: 'egg', // 连接的数据库名称
    },
  };

创建数据库和表

# 切换数据库
use egg

# 创建表
CREATE TABLE `girls` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  `age` int(11) NOT NULL,
  `skill` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

![image.png](https://img-blog.csdnimg.cn/img_convert/ea5191a48920b81c5fbaa7216c896da8.png#averageHue=#1f1e1d&clientId=u975a7b25-27c8-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=503&id=u63627aab&margin=[object Object]&name=image.png&originHeight=754&originWidth=947&originalType=binary&ratio=1&rotation=0&showTitle=false&size=84827&status=done&style=none&taskId=ucefc3108-2f98-45ab-a887-fd1db86f111&title=&width=631.3333333333334)

// router.js
// 操作数据库
router.get('/addGirl', controller.girlsManage.addGirl);
router.get('/delGirl', controller.girlsManage.delGirl);
router.get('/updateGirl', controller.girlsManage.updateGirl);
router.get('/getGirls', controller.girlsManage.getGirls);

// controller.girlsManage.js
'use strict';

const Controller = require('egg').Controller;

class GirlManage extends Controller {
  async addG() {
    const {
      ctx,
    } = this;
    const params = {
      name: '小白',
      age: 18,
      skill: '头疗',
    };
    const res = await ctx.service.testdb.addG(params);
    ctx.body = '添加女孩-成功!' + res;
  }

  async delG() {
    const {
      ctx,
    } = this;
    const id = {
      id: 3,
    };
    const res = await ctx.service.testdb.delG(id);
    console.log(res);
    if (res) {
      ctx.body = '删除女孩-成功';
    } else {
      ctx.body = '删除失败';
    }

  }

  async updateG() {
    const {
      ctx,
    } = this;
    const params = {
      id: 3,
      name: '小白',
      age: 20,
      skill: '头疗',
    };
    const res = await ctx.service.testdb.updateG(params);
    if (res) {
      ctx.body = '修改女孩-成功';
    } else {
      ctx.body = '修改失败';
    }
  }

  async getG() {
    const {
      ctx,
    } = this;
    const res = await ctx.service.testdb.getG(10);
    ctx.body = '查询女孩:' + JSON.stringify(res);
  }
}

module.exports = GirlManage;

// server/testdb.js
'use strict';

const Service = require('egg').Service;

class testdbService extends Service {
  // // 添加数据库
  async addG(params) {
    try {
      const {
        app,
      } = this;
      const res = await app.mysql.insert('girls', params);
      return res;
    } catch (error) {
      console.log(error);
      return null;
    }
  }

  // // 删除数据库
  async delG(id) {
    try {
      const {
        app,
      } = this;
      const res = await app.mysql.delete('girls', id);
      return res;
    } catch (error) {
      console.log(error);
      return null;
    }

  }

  // // 修改数据库
  async updateG(params) {
    try {
      const {
        app,
      } = this;
      const res = await app.mysql.update('girls', params);
      return res;
    } catch (error) {
      console.log(error);
      return null;
    }
  }

  // // 查询数据库
  async getG(id = 10) {
    console.log(id); // 没有数据意思一下
    try {
      const app = this.app;
      const res = await app.mysql.select('girls');
      return res;
    } catch (error) {
      console.log(error);
      return null;
    }
  }
}

module.exports = testdbService;

TS版体验

安装依赖和配置

npm init egg --type=ts
npm i

// egg-cors 跨域包 egg-jwt token生成以及验证包
npm install egg-cors egg-jwt --save 


// config/plugin.ts
import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  jwt: {
    enable: true,
    package: "egg-jwt"
  },
  cors: {
    enable: true,
    package: 'egg-cors',
  }
};

export default plugin;

// config/config.default.ts
config.jwt = {
  secret: "123456"//自定义 token 的加密条件字符串
};
config.security = {
  csrf: {
    enable: false,
    ignoreJSON: true
  },
  domainWhiteList: ['http://localhost:8080'],//允许访问接口的白名单
};
config.cors = {
  origin:'*',
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
};


// typings/index.d.ts
import 'egg';
declare module 'egg' {
    interface Application {
        jwt: any;
    }
}

创建路由、编写控制器

// router.js
import { Application } from "egg";

export default (app: Application) => {
  const { controller, router, jwt } = app;

  router.get("/", controller.home.index);

  // ----- //
  //正常路由
  router.post("/admin/login", controller.home.login);

  /*
   * 这里的第二个对象不再是控制器,而是 jwt 验证对象,第三个地方才是控制器
   * 只有在需要验证 token 的路由才需要第二个 是 jwt 否则第二个对象为控制器
   **/
  router.post("/admin", jwt, controller.home.index);
  // ----- //
};



// home.ts
import { Controller } from "egg";

export default class HomeController extends Controller {
  // public async index() {
  //   const { ctx } = this;
  //   ctx.body = await ctx.service.test.sayHi("egg");
  // }

  // ----- //
  // 验证登录并且生成 token
  public async login() {
    const { ctx, app } = this;
    //获取用户端传递过来的参数
    const data = ctx.request.body;
    // 进行验证 data 数据 登录是否成功
    // .........
    //成功过后进行一下操作
    //生成 token 的方式
    const token = app.jwt.sign(
      {
        username: data.username, //需要存储的 token 数据
        //......
      },
      app.config.jwt.secret
    );
    // 生成的token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1NjAzNDY5MDN9.B95GqH-fdRpyZIE5g_T0l8RgzNyWOyXepkLiynWqrJg
    // 返回 token 到前端
    ctx.body = token;
  }
  //访问admin数据时进行验证token,并且解析 token 的数据
  public async index() {
    const { ctx } = this;
    console.log(ctx.state.user);
    /*
     * 打印内容为:{ username : 'admin', iat: 1560346903 }
     * iat 为过期时间,可以单独写中间件验证,这里不做细究
     * 除了 iat 之后,其余的为当时存储的数据
     **/
    ctx.body = { code: 0, msg: "验证成功" };
  }
  // ----- //
}

axios测试 /admi/login

axios({
  method: 'post',
  url: 'http://127.0.0.1:7001/admin',
  data: {
    username: 'admin',
    lastName: '123456'
  },
  headers:{
    // 切记 token 不要直接发送,要在前面加上 Bearer 字符串和一个空格
    'Authorization':`Bearer ${token}`
  }
}).then(res=>{
  console.log(res.data)})

![image.png](https://img-blog.csdnimg.cn/img_convert/1aba18ed726df958c987ba89f5397766.png#averageHue=#e5d081&clientId=u10c23fe1-01de-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=186&id=u99a1c225&margin=[object Object]&name=image.png&originHeight=279&originWidth=455&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14577&status=done&style=none&taskId=u136306d5-0ceb-4611-beb5-5c20a2a7d0a&title=&width=303.3333333333333)![image.png](https://img-blog.csdnimg.cn/img_convert/89b4168e521debef8c12e1fb9194e947.png#averageHue=#fbfaf5&clientId=u10c23fe1-01de-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=250&id=u5d7534d5&margin=[object Object]&name=image.png&originHeight=499&originWidth=1343&originalType=binary&ratio=1&rotation=0&showTitle=false&size=77866&status=done&style=none&taskId=ua8eb430c-f9f2-42bb-9877-58fd9c432c8&title=&width=672)

汇总

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app/ # 一定要按约定写
|   ├── router.js # 用于配置 URL 路由规则
│   ├── controller/ # 用于存放控制器(解析用户的输入、加工处理、返回结果)
│   ├── model/ (可选) # 用于存放数据库模型
│   ├── service/ (可选) # 用于编写业务逻辑层
│   ├── middleware/ (可选) # 用于编写中间件
│   ├── schedule/ (可选) # 用于设置定时任务
│   ├── public/ (可选) # 用于放置静态资源
│   ├── view/ (可选) # 用于放置模板文件
│   └── extend/ (可选) # 用于框架的扩展
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config/
|   ├── plugin.js # 用于配置需要加载的插件
|   ├── config.{env}.js # 用于编写配置文件(env 可以是 default,prod,test,local,unittest)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

[山青花欲燃]

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值