🚀【eggjs实战入门】🚀—— 3、看完就会操作mysql

362 阅读8分钟

课程引导

  1. 🚀【eggjs实战10天入门-第1天】🚀—— 搭建项目
  2. 🚀【eggjs实战10天入门-第2天】🚀—— controller、service和config
  3. 🚀【eggjs实战10天入门-第3天】🚀—— 操作mysql

本节目标

开始将数据存储到mysql和redis中

暂时不考虑参数不存在,读写报错的情况

1、myslq

1.1、安装mysql和navicat

安装mysql

// mac下
brew install mysql
brew services start mysql

mysql和navicat安装教程挺多的,大家可以搜索下,本篇就不讲了

安装navicat 然后链接本地启动的mysql image.png

image.png

image.png

当出现这个节目的时候,代表我们已经通过navicat链接到了本地的mysql.如果你有远程服务器,也可以安装在服务器上,这里我们就先使用本地数据库了。

1.2、项目中安装mysql和sequelize

我们直接使用ORM 框架来帮助我们管理数据层的代码,现在社区常用的有sequelize/typeOrm,现在Prisma呼声也很高,感兴趣的可以尝试下这个。我平常用sequelize多一些,这里就用sequelize作为示例了。

我们首先安装mysql2和egg-sequelize(选择mysql2是因为据说性能更高一些,只是据说,我没有做过测评)(不过npm里面mysql2的下载量确实高一些)

image.png 很奇怪的是从12月18日开始,整个的下载量开始急速下降,不知道是什么原因。有兴趣的可以分析下这个下降趋势

yarn add egg-sequelize mysql2 -S

1.3、插件(plugin)

这里要引入一个新的概念,插件。eggjs的插件其实是相对更加独立的一个迷你应用。 像orm、校验、日志等这种独立的业务逻辑都可以在插件中引入。同时我们也可以根据eggjs的规范开发自己的插件。 插件的开启特别的简单

只需要在app/config/plugin.js中配置一下就可以了。比如我们开启下sequelize插件

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

1.4、mysql的配置

我们可以想象一下,如果我们要使用mysql,就肯定需要要写mysql的配置。比如mysql的地址,密码,库名。那我们之前讲到的config配置就派上了用场。我们可以做如下的配置

dialect: 'mysql', // 使用的数据库,支持 mysql, mariadb, postgres, mssql等数据库 
database: 'test', // 数据库名称
host: 'localhost', // 服务主机地址 
port: 3306, // 端口 
username: 'root', // 用户名 
password: '', // 密码 
delegate: 'myModel', // 【可选】加载所有的模型models到 `app[delegate]` and `ctx[delegate]`对象中,进行委托, 默认是model 
baseDir: 'my_model', // 【可选】加载 `app/${baseDir}`文件夹下的所有js文件作为models,默认为 `model` 
exclude: 'index.js', // 【可选】加载所有模型models时,忽略 `app/${baseDir}/index.js` 文件,支持文件路径和数组

但其实我们在使用过程中,会出现链接多个库的情况,那我们该怎么处理呢 首先我们可以在navicat里面可视化操作创建两个数据库,me-help,me-blog 这里我们字符集设置为utf8mb4(这个字符集可以支持表情的)

image.png

这样我们就得到了两个空的数据库 image.png

接下来我们开始在项目中配置这两个数据库


module.exports = appInfo => {
  const config = {}
  config.keys = appInfo.name + '_1672833991623_8554';
  config.cdn = {
    AK: 'default',
    SK: 'default',
    BucketName: 'xxx-xxx',
    DoMain: 'https://xxx.xxx.com'
  }
  config.security = {
    csrf: {
      enable: true,
      headerName: 'token',
    },
  };
  config.sequelize = {
    datasources: [
      {
        delegate: 'blogModel',
        baseDir: 'blog_model',
        dialect: 'mysql',
        username: 'root',
        password: 'ab123456',
        database: 'me-blog',
        host: '127.0.0.1',
        port: 3306,
        timezone: '+08:00',
        define: {
          charset: 'utf8mb4',
          collate: 'utf8mb4_unicode_ci',
        },
        dialectOptions: {
          dateStrings: true,
          typeCast: true,
        },
        logging: false
      },
      {
        
        delegate: 'helpModel',
        baseDir: 'help_model',
        dialect: 'mysql',
        username: 'root',
        password: 'ab123456',
        database: 'me-help',
        host: '127.0.0.1',
        port: 3306,
        timezone: '+08:00',
        define: {
          charset: 'utf8mb4',
          collate: 'utf8mb4_unicode_ci',
        },
        logging: false
      }
    ]
  }
  return {
    ...config
  };
};

1.5、开始增删改查

1.5.1、设计表结构

官方提的建议是通过sequelize-cli进行表的初始化设计。但是我们这里不准备使用官方推荐的模式。

1、不同的公司操作规范不同,我们这边是不允许通过项目去修改数据库格式的,这也是比较高危的操作。一般有运维的话,会有专门的一套工具去初始化或者修改表结构。

2、我采用的是通过navicat进行可视化表结构的创建,然后通过开发的cli把当前环境的表结构初始化为项目的model

比如我们在me-help数据库创建一个日记表(todo_list), 记录我们每天写的日记。

字段为id,titlecontentupdate_timecreate_time,我们创建5个字段。同时讲id设置为主键,做自动递增

字段类型功能备注
idint主键主键最多只能有一个,且值不能重复不可为空。我们一般通过表的主键可以确定唯一的一条数据
titlevarchar标题
contenttext内容
create_timedatetime创建的时间
update_timedatetime更新的时间

image.png

创建好字段后点击保存,我们为这个表起名为todo_list

表名+字段名一般都有自己的规范,这里我们采用单词全小写,多组此采用下划线相连。如todo_list

image.png

image.png

1.5.2、创建model

在seqlize中,我们需要创建和线上数据库对应的模型,也就是model

一般情况下,将与数据库表字段对应的mode文件放在app/model下,egg-sequelize插件会自动加载和处理。

但是我们设置的me-help的baseDir为help_model,所以和me-help相关的model,我们都放在help_model文件夹中

app下新建文件夹help_model,并创建表todo_list对应的model文件abc.js,

为什么要使用abc.js呢,这里主要是给大家标识好每个字段的作用,暂时抛弃语义化的命名

有的教程里面到处定义的User,使用的时候也是User,新入门的时候很难搞清楚之间的对应关系

egg-sequelize开源地址(github.com/eggjs/egg-s…

比如文件路径为app/help_model/abc.js

'use strict';

module.exports = app => {
  const DataTypes = app.Sequelize;
  const attributes = {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
      comment: "id",
      field: "id"
    },
    title: {
      type: DataTypes.STRING(255),
      comment: "标题",
      field: "title"
    },
    content: {
      type: DataTypes.TEXT,
      comment: "内容",
      field: "content"
    },
    createTime: {
      type: DataTypes.DATE,
      field: "create_time"
    },
    updateTime: {
      type: DataTypes.DATE,
      field: "update_time"
    }
  };
  const options = {
    timestamps: false,
    freezeTableName: true
  };
  return app.model.define("todo_list", attributes, options);
};

我们一定要关注好这一句,const TodoListModel = app.model.define("todo_list", attributes, options);

关于模型定义的参数

  1. 第一个参数为我们需要映射的表名,做到和数据库里面的名字完全一致
  2. 第二个参数attributes为各个字段的配置
  3. 第三个参数,是一个对象,我常用的两个参数如下
timestamps: false // 是否自动添加时间戳字段 (updatedAt, createdAt)
freezeTableName: true // 如果不设置或者为false的话,sequelize会将第一个参数转为复数形式
1.5.3、搞清楚调用关系

接下来我们会使用app.helpModel.Abc去操作这个todo_list表 这时候大家可能有2个疑问

  1. app.helpModel.Abc这个玩意是怎么得来的? 这里面的关系我们用一个表格来演示

一般情况下是这样 model文件 | class name | | ----------------- | ------------------------ | | abc.js | app.model.Abc | | User.js | app.model.User |

image.png

但是如果我在config.xx.js里面定义了delegate:helpModel model文件 | class name | | ----------------- | ------------------------ | | abc.js | app.helpModel.Abc | | user_group.js | app.helpModel.UserGroup |user/profile.js | app.model.User.Profile |

这就是app.helpModel.Abc的由来,眼睑的朋友可能看到Abc和User是大写的,这是因为Abc和user是挂载到helpModel上的一个class,sequlize会把当前文件名转化为大驼峰

  1. app.helpModel.Abc这是操作哪个表? 操作哪个表取决于abc.js这个文件里面的定义 在app/help_model/abc.js最后一句我们可以看到第一个参数是todo_list
return app.helpModel.define("todo_list", attributes, options);

那就说明app.helpModel.Abc是要操作me-help数据库的todo_list表

1.5.4、增删改查
  • 1、写操作

今天发现阳了,我决定记录一下,于是我新增了一条变阳的数据

使用ctx.helpModel.Abc.create进行新增数据

const { Service } = require('egg')

class BaseService extends Service {
  // 我们使用create方法增加一条数据
  async add({title, content}) {
    const { ctx } = this;
    const user = await ctx.helpModel.Abc.create({
      title,
      content,
      createTime: new Date(),
      updateTime: new Date()
    });
    return user
  }
}
module.exports = BaseService;

image.png

image.png 这时候我们就可以看到,数据库里面有个刚从我们新增的一行数据。

过了一会,我一看温度计,卧槽,已经退烧了,那赶紧再记录一下转阴记录,我们再增加一条

image.png

  • 2、读操作

使用ctx.helpModel.Abc.findAll进行查询全部

const { Service } = require('egg')

class BaseService extends Service {
  async getAll() {
    const { ctx } = this;
    const result = await ctx.helpModel.Abc.findAll();
    return result
  }
}
module.exports = BaseService;

image.png 这时我们发现可以读到刚才写入的两条数据了

  • 3、更新操作

在我们喜滋滋认为自己转阴的时候,又量了下体温,发现刚才是自己烧迷糊了,看错温度计了,自己还是🐑。本着认真仔细的态度,我们这时候需要把第二条的转阴记录改一下

发现我们的系统还不支持更新,于是挣扎着爬起来,继续写更新接口

使用ctx.helpModel.Abc.update进行更新

因为是更新操作,所以我们把updateTime时间也更新一下

const { Service } = require('egg')

class BaseService extends Service {
  async updateTodo({ id, title, content }) {
    const { ctx } = this;
    const result = await ctx.helpModel.Abc.update({
      title,
      content,
      updateTime: new Date()
    }, {
      where: {
        id
      }
    });
    return { result: "更新成功" }
  }

}
module.exports = BaseService;

image.png

image.png 这时我们查看一下数据库,发现数据也修改成功了

  • 4、删除数据

突然从睡梦中醒来,发现变阳是做了个梦,还梦游着写了几个接口

趁着梦里的知识还没忘,我赶紧爬起来,抓紧写了个删除接口

const { Service } = require('egg')

class BaseService extends Service {
  async delTodo({ id }) {
    const { ctx } = this;
    await ctx.helpModel.Abc.destroy({
      where: {
        id
      }
    });
    return { result: "删除成功" }
  }
}
module.exports = BaseService;

image.png

执行后发现数据库果然空了。

1.6、小结

sequlize的中文文档(www.sequelize.cn/core-concep…

我们刚才用到的只是非常简单的几个api,先入个门再说

github源码 tag为1.2.0