写篇文章让自己清楚egg——(6)egg服务器操作mysql

627 阅读6分钟

完成服务器对数据库的操作是必要的知识和能力,这里需要具有一定数据库语句知识以及代码中出现的async和await等知识。

egg服务器想要操作数据库,进行增删改查,同样需要插件实现。

下载插件egg-mysql

终端执行该指令(注意终端位置)

    npm i --save egg-mysql

启动插件

添加启动代码在config/plugin.js中(参考cors插件)

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

配置mysql插件

提示:按照mysql的步骤这里不讲解,建议提前学习积累一定使用经验,并安装好,记下用户名、数据库名、密码等信息以备使用。

配置在config.default.js文件的config对象内。

  • 单个数据库配置
...
  const config = exports = {
    ...
    mysql: {
      // 单数据库信息配置
      client: {
        // host -- localhost 或 数据库所在ip 或 host名 
        host: 'localhost',
        // 端口号
        port: '3306',
        // 用户名 ---数据库的用户名
        user: 'root',
        // 密码  ---数据库密码
        password: '123456',
        // 数据库名
        database: 'test',
      },
      // 是否加载到 app 上,默认开启
      app: true,
      // 是否加载到 agent 上,默认关闭
      agent: false,
    },
  };
...
  • 数据库参考图

image.png

image.png

  • 多个数据库配置(clients对象成为数据库配置对象的集合对象,参考单个数据库写法)
  clients: {
    // clientId, 获取client实例,需要通过 app.mysql.get('clientId') 获取
    db1: {
      host: 'xxx',
      port: '3306',
      user: 'root',
      password: 'xxx',
      database: '数据库名1',
    },
    db2: {
      host: 'xxx',
      port: '3307',
      user: 'test_user',
      password: 'xxx',
      database: '数据库名2',
    },
    // ...
  },

构建Service层

这里我们提出一个Service概念,Service 就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。不用强硬理解,我们避免不了学习理解不了的知识,如果没有好的资源帮助理解,我们可以先会用,积累一定使用经验后我们慢慢就会理解。

1.创建Service(初始化)

我们创建Service就和Controller结构类似,app下创建一个文件夹service以及index.js文件,并初始化代码:(我们承接上次登录持久化的项目代码)

image.png

我们设计数据库中表login,id为主键。

2.搭建对应的controller(初始化)

ctx可以调用Service层接口,一一对应Service方法。

image.png

这里我们其实实现了查询功能。

数据库增删改查

  • 我们先创建一个静态网页(mysql.html)不断补充内容,对应四个路由四个请求(增删改查):
        <!-- http://juejinlogin.com/public/mysql.html -->
        <form action="" onsubmit="select(event)">
            <label for="">id:<input type="text" id="id"></label>
            <input type="submit" id="btn" value="查询">
        </form>
        <div class="mysql">
            还未查询
        </div>
        <script>
            function select(event){
                event.preventDefault();
                let id = document.querySelector('#id').value;
                let xhr = new XMLHttpRequest()
                xhr.open('get','http://juejinlogin.com/mysql_find?id='+id)
                xhr.timeout = 5000;
                xhr.onload=()=>{
                    console.log('接收到服务器响应');
                    console.log(JSON.parse(xhr.responseText));
                    show(JSON.parse(xhr.responseText).user);
                }
                xhr.onerror=()=>{
                    console.log('请求失败')
                }
                xhr.send(null);
            }
            function show(user){
                if(user)
                    name = `查询到:用户${user.name},他的密码是${user.password}`
                else
                    name=`查无此人`
                document.querySelector('.mysql').innerHTML=name
            }
        </script>

image.png

1.查询

  • 关键mysql插件代码:
    const user = await this.app.mysql.get('login', { id: uid });
    //等效于:select * from `login` where `id` = `uid值`,多个关键字就再第二参数对象内再添加新的属性就行了
  • 首先我们手动在数据库中增加一条信息,并且按照如下格式:

image.png

  • 直接沿用Service和controller初始化代码,添加一个路由关联:
  router.get('/mysql_find', controller.mysql.index);
  • 实现效果:

image.png

get方法只能获取到一条数据,并且是满足条件的第一条数据。多条数据获取使用select方法(请看最下面)。

2.增加

  • 关键mysql插件代码:
    const result = await this.app.mysql.insert('posts', { title: 'Hello World' }); 

insert即插入添加,除了通过一个关键字增加数据外,还可以一次增加多个数据,以及通过多个关键字增加数据。

//insert方法第二参数的另外写法:
insert('posts', { title: 'Hello World', name: '2333' }); //多个关键字
insert('posts', [{ title: 'Hello World1' },{ title: 'Hello World2' }]); //多条数据
  • 编写静态页面交互内容:
...
        <form action="" onsubmit="add(event)">
            <label for="">name:<input type="text" id="addname"></label>
            <label for="">password:<input type="password" id="addpwd"></label>
            <input type="submit" id="btn" value="增加">
        </form>
...
            function add(event){
                event.preventDefault();
                let name = document.querySelector('#addname').value;
                let pwd = document.querySelector('#addpwd').value;
                let xhr = new XMLHttpRequest()
                xhr.open('post','http://juejinlogin.com/mysql_add')
                xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
                xhr.timeout = 5000;
                xhr.onload=()=>{
                    console.log('接收到服务器响应');
                    console.log(JSON.parse(xhr.responseText));
                }
                xhr.onerror=()=>{
                    console.log('请求失败')
                }
                xhr.send(`name=${name}&pwd=${pwd}`);
            }

效果:

image.png

  • 设置Service和controller代码:
//controller\mysql.js内新增方法。
  async add() {
    const { ctx } = this;
    const res = await ctx.service.index.add(ctx.request.body);
    ctx.body = {
      msg: 'add ok',
      result: res,
    };
  }
//service\index.js内新增方法。
  async add(msg) {
    const result = await this.app.mysql.insert('login', { name: msg.name, password: msg.password });//这里可以直接msg作为参数二,只是阅读性差。
    return { result };
  }
  • 添加路由和静态网页新的结合和请求。
  router.post('/mysql_add', controller.mysql.add);
  • 效果:

image.png

3.删除

  • 关键mysql插件代码:
    const result = await this.app.mysql.delete('posts', { author: 'fengmk2' });
    //等效于 delete form `posts` where `author` = `fengmk2`
  • 其他部分新增代码基本是和上面的写法模式类似,只是关联的是这个delete方法:
        <form action="" onsubmit="del(event)">
            <label for="">name:<input type="text" id="delname"></label>
            <label for="">password:<input type="password" id="delpwd"></label>
            <input type="submit" id="btn" value="删除">
        </form>
            function del(event){
                event.preventDefault();
                let name = document.querySelector('#delname').value;
                let pwd = document.querySelector('#delpwd').value;
                let xhr = new XMLHttpRequest()
                xhr.open('post','http://juejinlogin.com/mysql_del')//主要是路由不一样。
                xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
                xhr.timeout = 5000;
                xhr.onload=()=>{
                    console.log('接收到服务器响应');
                    console.log(JSON.parse(xhr.responseText));
                }
                xhr.onerror=()=>{
                    console.log('请求失败')
                }
                xhr.send(`name=${name}&password=${pwd}`);
            }
//controller\mysql.js内新增方法。
  async del() {
    const { ctx } = this;
    const res = await ctx.service.index.del(ctx.request.body);
    ctx.body = {
      msg: 'del ok',
      result: res,
    };
  }
//service\index.js内新增方法。  
  async del(msg) {
    // const result = await this.app.mysql.delete('login', { name: msg.name, password: msg.password });
    const result = await this.app.mysql.delete('login', msg);
    return { result };
  }
//路由
    router.post('/mysql_del', controller.mysql.del);
  • 效果:

image.png

image.png

4.修改

update操作是比较复杂的操作。

  • 关键mysql插件代码
    const result = await this.app.mysql.update('posts', row, options); 
    //这里row是用更新的数据,不包含主键id,而option是起where语句效果。
    const result = await this.app.mysql.update('posts', row);
    //这里row是用更新的数据,因为包含id主键,所以自动进行找到进行更新。

我们跳过繁杂的设计过程(请参考上面写法),我们主要集中在service层的实现,以写代学。

  • service层实现:
  async update(msg) {
    const option = {
      where: {
        name: msg.oldname,
      },
    };
    const result = await this.app.mysql.update('login', { name: msg.name, password: msg.password }, option);
    return { result };
  }

主要注意的就是更新是三个参数(特殊用法最后讲),第三个参数的用于where,以及特殊的结构。

  • 效果:

image.png

image.png

5.特殊写法补充:

补充两个方法:selectquery,和特殊的literals函数。

  • 查询整个表 或 特定集合的数据:
    const results = await this.app.mysql.select('posts');
    let list = await this.app.mysql.select('index',{where: { module: module }})

get条件查询方法变成select全查询方法,通过参数二的配置对象可以实现多个数据的查询。

  • 手写数据库语句方法:(通过query,我们可以直接书写并使用数据库语句
await this.app.mysql.query('INSERT INTO `posts`(`title`) VALUES('Hello World')');
//等效于:await this.app.mysql.insert('posts', { title: 'Hello World' });
  • 数据库语句占位符写法:(通过字符来进行数据库语句的占位)
await this.app.mysql.query('INSERT INTO `posts`(`title`) VALUES(?)',['Hello World']);
//等效于:await this.app.mysql.insert('posts', { title: 'Hello World' });
  • 数据库的常用函数:(具体需求请查文档)
this.app.mysql.literals.now
//等效于:数据库中的now()函数。

官方文档

因为是egg-mysql插件,所以我们即使具备数据库和egg.js知识,也并不能直接得知对应数据库操作的方法api,虽然我们可以用query直接写mysql语句,但是还是要学会使用官网文档:

这部分代码量并不大,但是重复的结构较多,需要理解其中每个代码的前后和因果关系,这样方能举一反三,同时方便查错。