Egg.js简单使用

371 阅读6分钟

所使用代码已上传github, 注意数据库配置方面。如有问题欢迎交流。 参考学习 jspang.com/article/79

简单使用 -- 骨架类型

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

image.png 启动:yarn dev

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

image.pngimage.png image.pngimage.png 刷新即可看到效果

- 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 对比

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

image.png

编写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);
  });
});

image.png 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后 image.pngimage.pngimage.png

严格传参

少参数就会404 image.pngimage.pngimage.png

POST 请求

image.pngimage.png 添加config/config.default.js, 关闭csrf安全策略

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

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

image.png image.png 服务端接受请求:

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

image.png

Service 服务

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

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

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

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

在/app/service目录下编写自己的服务 image.pngimage.pngimage.png service方法的可调用性 一个service方法完成后,可以在其它的Controller里进行使用。比如在home.js中进行使用。打开/app/controller/home.js文件下,新建一个testGetGirl( )方法,然后新增好路由,这样id即可被数据库得到image.pngimage.png 起名的时候最好和Controller对应起来。写法和Controller类似,并且在任何Controller下都可以得到Service提供的数据。

View中使用EJS模板引擎

介绍

可选的模板不少,具体参考官方文档。 image.png 服务端渲染的好处

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

EJS1

安装和配置、使用:yarn add egg-view-ejs image.pngimage.png image.png /app/view/下新建 zhuba.html文件即可被渲染,访问该路由地址即可 image.png

EJS2

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

image.png

  • 数据的循环显示 (for)

image.png

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

EJS3

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

image.png

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

image.pngimage.png 修改config.default.js可以将访问public变成assets,当然此时使用public会404

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

和正常使用一致 image.pngimage.png

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 差:

配置和加密

一些配置选项,比如有效时间、服务端操作设置和中文编写加密这些操作。 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 要想只在某一页面使用需要在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

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

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.pngimage.pngimage.png

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

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

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

定时任务编写

定时任务需要按照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

// 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.pngimage.png

汇总

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)