为什么要从koa2转到egg的(含数据库搭建)?

2,692 阅读7分钟

前言

  • 【音乐博客】上线啦!

  • 为什么不继续使用Koa2?(以下情况是我个人开发koa遇到想解决的)

  • 最近想把接口改为**RESTful API**风格,而egg对RESTful API风格很友好的支持,当然koa也可以有写成这种风格,只是说人家egg集成好了,我们就没必要再造葫芦了

  • 在接口返回的时候,我想返回HTTP响应码给前端,发现koa并不能获取到,需要手动设置响应状态**response.status**,所以这也是我想转egg的第二个原因

  • koa只是一个基础的框架,开发过java的同学知道,**MVC**架构对代码可观性及编写开发还是挺舒服的,而egg选择了koa作为其基础框架,进行了一些增强,也采用MVC架构进行开发

  • 而egg的扩展、插件、代码风格相比koa,很明显增强了很多,是企业级应用的不二人选

  • 而后期也慢慢将**【音乐博客】**的koa逐渐转向egg进行重构,若遇到坑,也会写文章总结,希望和各位爱好egg的同学共同学习!

四个方面

1. 安装egg

  • egg我们选择的是typescript版本安装开发的

  • npm init egg --type=ts

  • 搭建项目成功之后的目录如下:

  • 可以看到app文件夹下的MVC层次已经搭建成功

  • plugin.js用来定义插件;config.default.js用做基本配置,同时自己也可以新建文件来区分开发环境、测试环境以及线上环境

  • 详情文件解释可移步到官方文档看详情

2. 数据库的选择与安装使用

2.1. egg-mysql

  • 在 egg 项目中安装 egg-mysql 

  • npm i egg-mysql

  • 安装好之后,npm i安装依赖,则可运行npm run dev

  • {app_root}/config/plugin.js 中启用 egg-mysql 插件: 

  • 'use strict';

    /** @type Egg.EggPlugin */
    module.exports = {
        //mysql
        mysql:{
            enable:true,
            package:'egg-mysql',
        }
    };
    
  • 在配置文件中配置 mysql 数据库连接地址 {app_root}/config/config.default.js 

  • 'use strict';

    /** @mysql */
    config.mysql = {
        //database configuration 
        client:{
            //host 
            host:'localhost',
            //port 
            port:'3306',
            //username 
            user:'root',
            //password 
            password:'123456',
            //database 
            database:'qynbgl'
        },
        //load into app,default is open //加载到应用程序,默认为打开
        app:true,
        //load into agent,default is close //加载到代理中,默认值为“关闭”
        agent:false,
    };
    

到此我们的egg-mysql就配置成功了,接下来对其进行使用

  • **egg-mysql 的使用 **

  • get 查找一条

  • let result = await this.app.mysql.get("user",{id:1})

  • get 查找多条数据

  • const page = 1, per = 10; //通过controller层传值过来service

    const data = await (this.app as any).mysql.select(this.model, {
          // columns: ['id', 'name'], //查询字段,全部查询则不写,相当于查询*
          where: {
            // name: 'Jack'
          }, //查询条件
          orders: [
            ['id', 'asc'] //降序desc,升序asc
          ],
          limit: (page - 1) * per, //查询条数
          offset: per //数据偏移量(分页查询使用)
        })
    
  • 添加数据

  • let result = await this.app.mysql.insert("user",{username:"lisi",password:"1234"})

  • 修改数据的第一种方式:根据主键修改(对象里面必须包含主键id)

  • let result = await this.app.mysql.update('user',{ id:2, username:'赵四' });

  • 修改数据的第二种方式:通过 sql 来修改数据

  • let results=await this.app.mysql.query( 'update user set username = ? where id = ?',["王五",2] );

  • 删除数据(根据id删除)

  • let result= await this.app.mysql.delete('user',{ id:3 });

  • 执行sql

  • this.app.mysql.query(sql,values);

2.2. egg-sequelize

  • 介绍

  • egg-mysql插件是底层操作数据库,所以性能是最好的

  • 而在复杂的应用中,我们可能会需要一个 ORM 框架来帮助我们管理数据层的代码

  • sequelize 是一个广泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。

  • 安装

  • npm install --save egg-sequelize mysql2

  • config/plugin.js 中引入 egg-sequelize 插件

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

  • config/config.default.js 中编写 sequelize 配置

  • 'use strict';

    /** @sequelize */
    
    config.sequelize = {
      dialect: 'mysql',
      host: '127.0.0.1',
      port: 3306,
      database: 'qynbgl',
      username: 'root',             //数据库的用户名
      password: '123456' 
    };
    

这里注意,官网上sequelize对象中有个坑没有写齐usernamepassword配置

  • **初始化数据库和 Migrations **

  • 我们可以通过mysql命令将表建好,但有个问题,如果结构发生改变,如何快速变更数据结构呢?

  • 这时候我们就需要 Migrations 来帮我们管理数据结构的变更了。

  • sequelize 提供了 sequelize-cli 工具来实现 Migrations

  • 安装 sequelize-cli 

  • npm install --save-dev sequelize-cli

  • 在 egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在 database 目录下,所以我们在项目根目录下新建一个 .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'),
    };
    
  • 初始化 Migrations 配置文件和目录

  • npx sequelize init:config npx sequelize init:migrations

  • 执行完后会生成 database/config.json 文件和 database/migrations 目录,我们修改一下 database/config.json 中的内容,将其改成我们项目中使用的数据库配置:

  • 'use strict';

    /** @config*/
    {
      "development": {
        "username": "root",
        "password": "123456",
        "database": "qynbgl",
        "host": "127.0.0.1",
        "dialect": "mysql"
      },
      "test": {
          ...
      },
      "production": {
        "username": "root",
        "password": "123456",
        "database": "qynbgl",
        "host": "127.0.0.1",
        "dialect": "mysql"
      }
    }
    
  • 此时 sequelize-cli 和相关的配置也都初始化好了,我们可以开始编写项目的第一个 Migration 文件来创建我们的一个 users 表了。

  • npx sequelize migration:generate --name=init-users

  • 执行完后会在 database/migrations 目录下生成一个 migration 文件(${timestamp}-init-users.js),我们修改它来处理初始化 users 表:

  • 'use strict';

    module.exports = {
      // 在执行数据库升级时调用的函数,创建 users 表
      up: async (queryInterface, Sequelize) => {
        const { INTEGER, DATE, STRING } = Sequelize;
        await queryInterface.createTable('users', {
          id: { type: INTEGER, primaryKey: true, autoIncrement: true },
          name: STRING(30),
          age: INTEGER,
          created_at: DATE,
          updated_at: DATE,
        });
      },
      // 在执行数据库降级时调用的函数,删除 users 表
      down: async queryInterface => {
        await queryInterface.dropTable('users');
      },
    };
    
  • 执行 migrate 进行数据库变更

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

执行之后,我们的数据库初始化就完成了(打开Navicat查看表显示创建成功)。

  • 编写代码

  • 现在终于可以开始编写代码实现业务逻辑了,首先我们来在 app/model/ 目录下编写 user 这个 Model:

  • 'use strict';

    module.exports = app => {
      const { STRING, INTEGER, DATE } = app.Sequelize;
    
      const User = app.model.define('user', {
        id: { type: INTEGER, primaryKey: true, autoIncrement: true },
        name: STRING(30),
        age: INTEGER,
        created_at: DATE,
        updated_at: DATE,
      });
    
      return User;
    };
    
  • 这个 Model 就可以在 Controller 和 Service 中通过 app.model.User 或者 ctx.model.User 访问到了,例如我们编写 app/controller/users.js

  • 'use strict';

    const Controller = require('egg').Controller;
    
    function toInt(str) {
      if (typeof str === 'number') return str;
      if (!str) return str;
      return parseInt(str, 10) || 0;
    }
    
    class UserController extends Controller {
      async index() {
        const ctx = this.ctx;
        const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
        ctx.body = await ctx.model.User.findAll(query);
      }
    
      async show() {
        const ctx = this.ctx;
        ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
      }
    
      async create() {
        const ctx = this.ctx;
        const { name, age } = ctx.request.body;
        const user = await ctx.model.User.create({ name, age });
        ctx.status = 201;
        ctx.body = user;
      }
    
      async update() {
        const ctx = this.ctx;
        const id = toInt(ctx.params.id);
        const user = await ctx.model.User.findByPk(id);
        if (!user) {
          ctx.status = 404;
          return;
        }
    
        const { name, age } = ctx.request.body;
        await user.update({ name, age });
        ctx.body = user;
      }
    
      async destroy() {
        const ctx = this.ctx;
        const id = toInt(ctx.params.id);
        const user = await ctx.model.User.findByPk(id);
        if (!user) {
          ctx.status = 404;
          return;
        }
    
        await user.destroy();
        ctx.status = 200;
      }
    }
    
    module.exports = UserController;
    
  • 最后我们将这个 controller 挂载到路由上:

// app/router.js
module.exports = app => { const { router, controller } = app; router.resources('users', '/users', controller.users); };

  • 针对users表的CURD操作的接口就开发完了,可通过Postman验证,通过GET请求http://127.0.0.1:8000/users可请求到数据

对数据库的操作尽量都在service层上操作

对于mysql(简单、性能好)和sequelize(复杂)的选择,可根据项目进行选择使用哪种方式操作数据库

mysql常用的语句

(1)关联表查询(查询评价表,关联用户表头像和昵称)
select evaluate.*, user.name, user.figureurl from evaluate 
left join user on evaluate.userId = user.id where cId = 123

(2)更新商品点赞+1,并且一次更新id为123三条记录
update commodity set praise = praise + 1 where id in (1,2,3)

3. RESTful API 

  • 'use strict';

    /** @app/router.js*/
    module.exports = app => {
      const { router, controller } = app;
      router.resources('posts', '/api/posts', controller.posts);
      router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
    };
    
  • 上面代码就在 /posts 路径上部署了一组 CRUD 路径结构,对应的 Controller 为 `app/controller/posts.js` 接下来, 你只需要在 posts.js 里面实现对应的函数就可以了。
  • 细心的朋友可以看到2.2. egg-sequelize编写代码app/controller/users.js文件里面的方法名字其实就是restful风格的命名

4. 编写中间件(对GET、post参数统一获取)

  • 我们知道egg中获取参数

  • 获取get的参数:ctx.query

  • 获取post的参数:ctx.request.body

  • 实现效果:我们现在想ctx.params就可以拿到请求的参数,不管是GET、POST请求,下面手写一个中间件

  • 编写中间件的几个步骤:

  • 在app目录下新建middleware文件夹

  • 在middleware里面新建params.js,内容如下

  • /** * 获取请求参数中间件 * 可以使用ctx.params获取get或post请求参数 */

    module.exports = options => {
      return async function params(ctx, next) {
        ctx.params = {
          ...ctx.query,
          ...ctx.request.body
        }
        await next();
      };
    };
    
  • 在/config/config.default.js里注入中间件

  • /** * appInfo */

    'use strict';
    
    module.exports = appInfo => {
      const config = exports = {};
    // 注入中间件
      config.middleware = [
        'params',
      ];
      return config;
    };
    
  • 使用

  • /** * 添加文章接口 */

    'use strict';
    
    const Service = require('egg').Service;
    
    class ArticleService extends Service {
      async add() {
        const { ctx } = this;
        // 获取请求参数
        const {
          userId,
          title,
          content,
        } = ctx.params;
    
        const result = await ctx.model.Article.create({
          userId,
          title,
          content,
        });
        return result;
      }
    }
    module.exports = ArticleService;
    

原文地址

参考

Egg 中结合Egg-mysql操作MySQL :blog.csdn.net/weixin_4062…

egg使用总结 (restful):www.jianshu.com/p/322a8dd47…

 eggjs添加获取get和post通用中间件 :www.jianshu.com/p/77f0ec461…

egg学习笔记(6)--egg+mysql(sequelize)+vue实现curd :segmentfault.com/a/119000001…