Egg常用的插件

egg-mysql

安装与配置

首先安装egg-mysql的包

$ npm i --save egg-mysql

然后在plugin.ts开启插件

mysql: {
  enable: true,
  package: 'egg-mysql',
},

再然后在config中配置数据库信息

单数据库

// mysql数据库
config.mysql = {
  client: { // 单数据库信息配置
    host: 'localhost', // host
    port: '3306', // 端口号
    user: 'liuhao', // 用户名
    password: '123456', // 密码
    database: 'ssmbuild', // 数据库名
  },
  app: true, // 是否加载到 app 上,默认开启
  agent: false, // 是否加载到 agent 上,默认关闭
};

使用方法

await app.mysql.query(sql, values);

多数据库

// mysql数据库
config.mysql = {
  clients: { // 多据库信息配置
    db1: {
      host: 'localhost', // host
      port: '3306', // 端口号
      user: 'liuhao', // 用户名
      password: '123456', // 密码
      database: 'ssmbuild', // 数据库名
    },
    db2: {
      host: 'mysql.com', // host
      port: '3306', // 端口号
      user: 'root', // 用户名
      password: '123456', // 密码
      database: 'test', // 数据库名
    }
  },
  app: true, // 是否加载到 app 上,默认开启
  agent: false, // 是否加载到 agent 上,默认关闭
};

使用方法

const client1 = app.mysql.get('db1');
await client1.query(sql, values);

const client2 = app.mysql.get('db2');
await client2.query(sql, values);

注意

egg-mysql没有index.d.ts,所以编译不过,在typings/index.d.ts中添加配置

import 'egg';

declare module 'egg' {
  interface mysql {
    get(tableName: String, find: {}): Promise<Any>
    insert(tableName: String, find: {}): Promise<Any>
    update(tableName: String, find: {}): Promise<Any>
    delete(tableName: String, find: {}): Promise<Any>

    query(sql: String, values: Any[]): Promise<Any>
  }
  interface Application {
    mysql: mysql;
  }
}

详见寒风傲天博文

CRUD

Create

使用insert方法插入一条记录,插入成功判定 result.affectedRows === 1

// controller
public async insert() {
  const { ctx } = this;
  const book = {
    bookName: 'js',
    bookCounts: 2,
    detail: 'test',
  };
  const result = await ctx.service.book.create(book);
  if (result.affectedRows === 1) {
    ctx.body = '插入成功';
  } else {
    ctx.body = '插入失败';
  }
}
// service
public async create(book) {
  const { app } = this;
  const result = await app.mysql.insert('books', book);
  return result;
}

结果如图

image-20200816093109130

Read

使用 get 方法或 select 方法获取一条或多条记录。select 方法支持条件查询与结果的定制。

// controller
public async index() {
  const { ctx } = this;
  const result = await ctx.service.book.select();
  await ctx.render('book.ejs', {
    result,
  });
}

// service
public async select(options?: object) {
  const { app } = this;
  const result = await app.mysql.select('books', options);
  return result;
}

来看下效果

image-20200816094230315

定制查询

image-20200816094307434

Update&Delete

使用 update 方法更新数据库记录

const result = await app.mysql.update('posts', row, options);
// 参数: 表名,更新的数据,查询参数
const updateSuccess = result.affectedRows === 1;
// 插入成功判定

使用 delete 方法删除数据库记录

const result = await app.mysql.delete('posts', options);

直接执行sql查询

使用 query 执行sql 语句

const id = 1;
cosnt count = 4;
const results = await app.mysql.query('select * from books where id = ? and bookCounts > ?', [id, count]);

事务

image-20200816095912417

代码

代码已上传码云#2部分

egg-sequelize

安装与配置

安装egg-sequelize和mysql2

$ npm install --save egg-sequelize mysql2

在plugin中开启插件

sequelize: {
  enable: true,
  package: 'egg-sequelize',
},

在config中配置sequelize

// 配置sequellize
config.sequelize = {
    dialect: 'mysql', // 数据库类型
    database: 'ssmbuild', // 数据库名称
    host: '127.0.0.1', // 数据库ip地址
    port: 3306, // 数据库端口
    username: 'liuhao', // 数据库用户名
    password: '123456', // 数据库密码
};

经测试这种方式可以进行操作,但是,不能自动生成表,需要手动建表

配置sequelize-cli

安装

$ npm install --save-dev sequelize-cli

在项目根目录下新建一个 .sequelizerc 配置文件

'use strict';

const path = require('path');

module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};

然后运行命令初始化配置文件和目录

$ npx sequelize init:config
$ npx sequelize init:migrations

根目录下会生成数据库相关文件夹

image-20200816173123556

将config.js中的数据库配置改为自己的配置

然后运行命令生成表,以user为例

$ npx sequelize migration:generate --name=users

image-20200816174011956

可以看到多了一个users.js文件,开始配置user模型

module.exports = {
  // 在执行数据库升级时调用的函数,创建 users 表
  up: async (queryInterface, Sequelize) => {
    /**
     * Add altering commands here.
     *
     * Example:
     * await queryInterface.createTable('users', { id: Sequelize.INTEGER });
     */
    const { INTEGER, STRING } = Sequelize;
    await queryInterface.createTable('users', {
      id: {
        type: INTEGER, // INTEGER就是mysql中的int
        primaryKey: true,
        autoIncrement: true, // 自动增长
      },
      name: STRING(20),
      url: STRING(100),
      country: STRING(10),
    }, {
      // timestamps: false, // 去除createAt updateAt
      createdAt: false, // 表示不启用created_at
      updatedAt: false, // 表示不启用updated_at
      freezeTableName: true, // 使用自定义表名
      // 使用自定义表名之后上面写的users就直接就是你的表名,如果不加的话,你就可以写user,但是自己的表名为users,程序会自动将s加上
      tableName: 'users', // 自定义的表名,也可以不写,直接用define后面的也可以
    // 只要你使用了freezeTableName,程序就不会自动给你加上s了
    });
  },

  // 在执行数据库降级时调用的函数,删除 users 表
  down: async queryInterface => {
    /**
     * Add reverting commands here.
     *
     * Example:
     * await queryInterface.dropTable('users');
     */
    await queryInterface.dropTable('users');
  },
};

然后变更数据库

# 升级数据库
$ npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all

可以看到数据库中已经有了users表

image-20200816174451359

编码

OK,配置已经完成,现在开始进行愉快的编码

首先在app目录下新建一个model文件夹,将user模型添加到model下面,然后就可以通过app.model.User 或者 ctx.model.User 访问了,以insert为例

// controller
public async add() {
  const { ctx } = this;
  const result = await ctx.service.user.createUser({
    name: 'zhangsan',
    url: 'http://zhangsan.com',
    country: 'CN',
  });
  ctx.body = result;
}

// service
public async createUser(user: object) {
  const res = await this.ctx.model.User.create(user);
  return res;
}

然后就可以看到已经插入数据成功

image-20200816175238458

更多sequelize使用方法不多赘述,见之前的博文,传送门

代码

代码已上传码云#3部分

egg-passport

这里用微博的第三登录做为示范

安装及配置

首先运行命令安装passport支持

$ npm i --save egg-passport
$ npm i --save egg-passport-weibo

然后开启插件

passport: {
  enable: true,
  package: 'egg-passport',
},
passportWeibo: {
  enable: true,
  package: 'egg-passport-weibo',
},

然后在config.default中配置申请到的身份码

// weibo登录
config.passportWeibo = {
  key: 'your key',
  secret: 'your secret',
};

image-20200817150110149

编码

首先在router中挂载路由,这里使用的是插件提供的语法糖(这里挂载的时候可能会报错,因为没有index.d.ts文件,这个问题解决办法上面说过,这里不再赘述)

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

  router.get('/', controller.home.index);
    
  app.passport.mount('weibo');
  //  相当于
  // const github = app.passport.authenticate('github', {});
  // router.get('/passport/github', github);
  // router.get('/passport/github/callback', github);
};

然后在controller中编写登录操作

public async index() {
  // const { ctx } = this;
  // // ctx.body = await ctx.service.test.sayHi('egg');
  // await ctx.render('login.ejs');


  const { ctx } = this;

  if (ctx.isAuthenticated()) {
    ctx.body = `<div>
      <h2>${ctx.path}</h2>
      <hr>
      Logined user: <img src="${ctx.user.photo}"> ${ctx.user.displayName} / ${ctx.user.id} | <a href="/logout">Logout</a>
      <pre><code>${JSON.stringify(ctx.user, null, 2)}</code></pre>
      <hr>
      <a href="/">Home</a> | <a href="/user">User</a>
    </div>`;
  } else {
    ctx.session.returnTo = ctx.path;
    ctx.body = `
      <div>
        <h2>${ctx.path}</h2>
        <hr>
        Login with
        <a href="/passport/weibo">Weibo</a>
        <hr>
        <a href="/">Home</a> | <a href="/user">User</a>
      </div>
    `;
  }
}

由于我的服务器已经过期,没有可用的网站来审核,所以申请到的oauth码不能使用,如果通过审核,这就可以使用了

![QQ录屏20200817150434 00_00_00-00_00_30](https://cdn.easyremember.cn/img/QQ录屏20200817150434 00_00_00-00_00_30.gif)

此外

image-20200817151014007

API

  • ctx.user - 获取当前已登录的用户信息
  • ctx.isAuthenticated() - 检查该请求是否已授权
  • ctx.login(user, [options]) - 为用户启动一个登录的 session
  • ctx.logout() - 退出,将用户信息从 session 中清除
  • ctx.session.returnTo= - 在跳转验证前设置,可以指定成功后的 redirect 地址

还提供了 API:

  • app.passport.verify(async (ctx, user) => {}) - 校验用户

  • app.passport.serializeUser(async (ctx, user) => {}) - 序列化用户信息后存储进 session

  • app.passport.deserializeUser(async (ctx, user) => {}) - 反序列化后取出用户信息

  • app.passport.authenticate(strategy, options)

    - 生成指定的鉴权中间件

    • options.successRedirect - 指定鉴权成功后的 redirect 地址
    • options.loginURL - 跳转登录地址,默认为 /passport/${strategy}
    • options.callbackURL - 授权后回调地址,默认为 /passport/${strategy}/callback
  • app.passport.mount(strategy, options) - 语法糖,方便开发者配置路由

代码

代码已上传码云#4部分

egg-jwt

jwt(jsonwebtoken)是我认为潜力最大的一种用户状态验证方式

JWT原理

三部分组成,用.分割

  • header: 一个json对象,描述JWT的元数据
  • payload: 一个 JSON 对象,用来存放实际需要传递的数据
  • signature: 对header和payload两部分的签名,防止数据被篡改

安装与配置

运行命令安装

$ npm i egg-jwt --save

开启插件

jwt: {
  enable: true,
  package: 'egg-jwt',
}

在config.default中配置秘钥

// JWT秘钥
config.jwt = {
  secret: 'xiaofeng',
};

编码

生成

登录页使用post方法提交表单不再列出,主要给出生成token的操作(同样jwt没有index.d.ts,需要手动配置,见前文)

public async add() {
  const { ctx, app } = this;
  ctx.cookies.set('username', ctx.request.body.username);
  const token = app.jwt.sign({
    username: ctx.request.body.username,
  }, app.config.jwt.secret);
  ctx.set({ authorization: token }); //设置headers
  ctx.body = token;
}

效果如图

![QQ录屏20200817161322 00_00_00-00_00_30](https://cdn.easyremember.cn/img/QQ录屏20200817161322 00_00_00-00_00_30.gif)

验证

import { Context } from 'egg';

// 自定义的中间件:验证token
export default function jwtAuth(options: any): any {
  return async (ctx: Context, next: () => Promise<any>) => {
    const token = ctx.request.header.authorization;
    let decode = '';
    if (token) {
      try {
        // 解码token
        decode = await ctx.app.jwt.verify(token, options.secret);
        console.log('decode======>', decode);
        await next();
      } catch (error) {
        ctx.status = 401;
        ctx.body = {
          message: error.message,
        };
        return;
      }
    } else {
      ctx.status = 401;
      ctx.body = {
        message: '没有token',
      };
      return;
    }
  };
}

自定义验证token中间件,在路由文件中添加需要token才能访问的路由

import { Application } from 'egg';

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

  const token = middleware.jwtAuth(app.config.jwt);

  router.get('/news', token, controller.news.index);
};

没有获得token访问需要登录的路由时会提示没有token
image-20200903193358548
使用接口测试工具绑定一个已经生成的token
image-20200903193520323
image-20200903193558484
可以看到成功解析了token

代码

代码已上传码云#5部分,验证部分在#8

egg-validate

egg-validate用于对参数进行检验,比如检验一个用户名是不是字符串ctx.validate({ userName: 'string' });

安装及配置

运行命令

$ npm i egg-validate --save

plugin中开启插件

validate: {
  enable: true,
  package: 'egg-validate',
},

config中配置

// validate, 参数校验器
config.validate = {
  convert: true, // 对参数可以使用convertType规则进行类型转换
  // validateRoot: false,   // 限制被验证值必须是一个对象。
};

编码

编写一个post请求,接收一个对象作为参数

image-20200821124231323

(validator需要手动挂载,使用什么方法添加什么规则)

import { Controller } from 'egg';
// 规则
const createRule = {
  name: 'string',
  age: {
    type: 'number',
    min: 0,
    max: 100,
  },
  sex: [ 'man', 'woman' ],
};

export default class PostsController extends Controller {
  public async index() {
    const { ctx } = this;
    ctx.body = 'posts index';
  }

  public async create() {
    const { ctx, app } = this;
    // ctx.validate(createRule, ctx.request.body); // 自动处理错误,422
    const error = app.validator.validate(createRule, ctx.request.body); // 返回错误信息,开发者处理
    console.log(error);
    ctx.status = 201;
  }
}

效果如图,当出现错误时返回错误,没有错误error为空,created是自动加的![QQ录屏20200821123806 00_00_00-00_00_30](https://cdn.easyremember.cn/img/QQ录屏20200821123806 00_00_00-00_00_30.gif)

更多规则

image-20200821124114122

自定义规则

使用app.validator.addRule添加更多自定义规则(需要手动挂载)

例如:

 app.validator.addRule('newRule', (rule, value)=>{
    if (value.length < 3 || value.length > 10) {
      console.log("用户名的长度应该在3-10之间");
    }
  });

自定义规则需要单独在一个文件中添加

代码

代码已上传码云#6部分

egg-cors

image-20200821132803272

请求跨域是我们在日常开发中经常碰到的问题

egg解决跨域也是非常的简单,只要开启egg-cors就行了

安装及配置

运行命令

$ npm i egg-cors --save

plugin中开启插件

cors: {
  enable: true,
  package: 'egg-cors',
},

config中配置

  // 关闭csrf
  config.security = {
    csrf: {
      enable: false, // 关闭csrf
    },
    domainWhiteList: [ '*' ], // 白名单
  };

  // CORS
  config.cors = {
    origin: '*',
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
  };

然后就可以了

image-20200821133240161

代码

代码已上传码云#7部分


前端小白