egg-mysql API 少?不如试试 egg-easy-mysqlx 这个新插件

534 阅读5分钟

egg-easy-mysqlx

前言

前段时间用 egg 开发了几个 node 服务,使用官方推荐的 egg-mysql 时发现功能有些弱,likeor>= 等都不支持,需要自己手写 sql 语句。上 github 上看了看 issue,发现有相关 pr 但是官方好像不为所动,emmmm……

思考一番决定自己动手,也正好学一学如何完整的发布一个 npm 包(从来没撸过 npm 包的菜鸡路过……)

简介

egg-easy-mysqlx 是一个 egg.js 的 mysql 插件,提供了常用的 CURD API 和比较完整的操作符,并增加了 TypeScript 支持。egg-easy-mysqlx 是 easy-mysqlx 的 egg 插件实现,所有的 CURD 方法都由 easy-mysqlx 实现,而 easy-mysqlx 则是在 node-mysql2 的基础上完成的。更多的细节实现可以查看上述两个项目。

接下来进入插件的详细使用介绍。

开启插件

// config/plugin.js
module.exports = {
  ...
  mysqlx: {
    enable: true,
    package: 'egg-easy-mysqlx',
  },
  ...
};

详细配置

配置同 egg-mysql,详细配置可移步 egg-mysql 配置页面查看,如需更详细的配置,亦可参考 node-mysql2。

// config/config.default.js
module.exports = appInfo => {
  ...
  config.mysqlx = {
    client: {
      host: 'localhost',
      port: 3306,
      user: 'local',
      password: '123456',
      database: 'test',
    },
    app: true,
    agent: false,
  };
  ...
  return {
    ...config,
    ...userConfig,
  };
};

基本使用

若无特殊说明,以下方法的参数均为对象

Read

使用 select 方法可以进行数据查询。select 方法的参数包含 tablecolumnwherelimitoffsetorder 6 个 key。

  • table —— 要操作的数据表名(必需)
  • column —— 字符串数组,需要查询的列(可选)
  • where —— 查询条件(可选)
  • limit —— 返回结果数量,默认为 1(可选)
  • offset —— 偏移量,默认为 0(可选)
  • order —— 排序(可选)

返回结果为一个对象数组。

where 对象较为复杂,这里单独说明一下。where 是一个拥有固定 key 的对象,每一个 key 都表示 sql 语句中的一个逻辑运算符,有:

  • eq —— equal(=)
  • ne —— not equal(!=)
  • gt —— greater than(>)
  • ge —— greater than or equal(>=)
  • lt —— less than(<)
  • le —— less than or equal(<=)
  • in —— in
  • ni —— not in
  • bw —— between ... and ...
  • like —— like
  • or —— or

上述的每个 key 对应的 value 也是一个对象(or 除外,其对应的 value 为一个数组),该对象则为数据表中的字段和对应的值,如:

  • eq: { name: 'tom' } ==> name = 'tom'
  • eq: { name: 'tom', age: 6 } ==> name = 'tom' AND age = 6
  • bw: { age: [6, 12] } ==> age BETWEEN 6 AND 12
  • or: [{ eq: { name: 'tom' } }, { eq: { name: 'jerry' } }] ==> name = 'tom' OR name = 'jerry'
const result = await this.app.mysqlx.select({
  table: 'test_table',
  column: ['id', 'name'],
  where: {
    or: [{ eq: { name: 'harden', status: 0 }, le: { age: 30 } }, { eq: { name: 'tim' } }],
  },
  limit: 10,
});
// 查询结果
result = [
  { id: 24, name: 'tim' },
  { id: 57, name: 'harden' },
];
const result = await this.app.mysqlx.select({
  table: 'test_table',
  // column 为空等同于不填,均表示 SELECT * FROM xxx
  column: [],
  where: {
    or: [{ eq: { name: 'harden', status: 0 }, le: { age: 30 } }, { eq: { name: 'tim' } }],
  },
});
// 查询结果
result = [
  {
    id: 24,
    name: 'tim',
    age: 90,
    status: 1,
    created_time: '2021-12-31 17:35:41',
    msg: 'message',
  },
];

Create

使用 insert 方法可以进行单条或者批量插入。insert 方法的参数包含 tablevalue 两个 key。

  • table —— 要操作的数据表名(必需)
  • value —— 单条插入时,是一个对象;批量插入时,则是一个对象数组(必需)

执行结果通过 affectedRows 进行判断。

// 单条插入
const result = await this.app.mysqlx.insert({
  table: 'test_table',
  value: {
    name: 'timo',
    age: '9',
    status: 0,
  },
});
// 插入成功
result.affectedRows ==> 1;
// 批量插入
const result = await this.app.mysqlx.insert({
  table: 'test_table',
  value: [
    {
      name: 'yasuo',
      age: '21',
      status: 1,
    },
    {
      name: 'yohn',
      age: '24',
      status: 1,
    },
    {
      name: 'akl',
      age: '26',
      status: 0,
    },
  ],
});
// 插入成功
result.affectedRows ==> 3;

Update

使用 update 方法可以进行数据更新。update 方法的参数包含 tablevaluewhere 三个 key。

  • table —— 要操作的数据表名(必需)
  • value —— 更改的数据(必需)
  • where —— 筛选条件(必需)

执行结果同样通过 affectedRows 进行判断。

const result = await this.app.mysqlx.update({
  table: 'test_table',
  value: { msg: 'update timo message' },
  where: {
    eq: { name: 'timo' },
  },
});

Delete

使用 delete 方法可以进行数据删除。delete 方法的参数包含 tablewhere 两个 key。

  • table —— 要操作的数据表名(必需)
  • where —— 筛选条件(可选)

执行结果同样通过 affectedRows 进行判断。

const result = await this.app.mysqlx.delete({
  table: 'test_table',
  where: {
    eq: { name: 'timo' },
  },
});
// where 不填时,会删除表中所有数据,慎用
const result = await this.app.mysqlx.delete({
  table: 'test_table',
});

Count

为了方便使用,也提供了 count 方法用于查询符合条件的数据条数。count 方法的参数同 delete 方法。

返回结果为 number。

const result = await this.app.mysqlx.count({
  table: TABLE,
  where: {
    eq: { status: 1 },
  },
});
// 结果
result ==> 13;

Query

上述提供的方法可能不能完全满足实际需求,插件同样也提供了 query 方法用于手写 sql 语句。query 方法的参数不是对象,其有两个参数:sqlvalues

  • sql —— 要执行的 sql 语句(必需)
  • values —— where 子句中对应的值(可选)
const result = await this.app.mysqlx.query(`SELECT id, name, age FROM test_table WHERE id >= ? AND status = ?;`, [50, 0]);

上面的用法是推荐用法,这样插件会对语句进行预处理(prepared),可以有效防止 sql 注入。

如果习惯使用拼接字符串,则需要使用 escape 方法对传入的值转义一下,同样也可以防止 sql 注入。

const escapedId = this.app.mysqlx.escape(id);
const escapedStatus = this.app.mysqlx.escape(status);
const result = await this.app.mysqlx.query(`SELECT id, name, age FROM test_table WHERE id >= ${escapedId} AND status = ${escapedStatus};`);

事务

mysql 一个事务将一组连续的数据库操作放在一个单一的工作单元来执行。该组内的每个单独的操作是成功,事务才能成功。如果事务中的任何操作失败,则整个事务将失败。

插件同样也提供了手动自动两种事务处理的方式。

手动控制

beginTransactioncommitrollback 用于手动执行事务。

// 开启事务
const tran = await this.app.mysqlx.beginTransaction();
  try {
    // 多次操作
    const res = await tran.select({
      table: 'test_table',
      column: ['id'],
      where: { eq: { name: 'yohn' } },
    });
    const res1 = await tran.update({
      table: 'test_table',
      value: { msg: 'update yohn message wohhhhh' },
      where: { eq: { id: res[0].id } },
    });
    // 操作完成,提交事务
    await tran.commit();
    return true;
  } catch (error) {
    // 操作有失败,回滚事务
    await tran.rollback();
    throw error;
  }

自动事务

autoTransaction 方法用于自动执行事务。其有两个参数:scopectx

  • scope —— 一个包含多次 CURD 操作的异步函数
  • ctx —— 上下文对象,即 eggjs 中的 ctx,传入 ctx 可以保证即便在出现事务嵌套的情况下,一次请求中同时只有一个激活状态的事务
const result = await this.app.mysqlx.autoTransaction(async tran => {
  const res = await tran.select({
    table: TABLE,
    column: ['id'],
    where: { eq: { name: 'yohn' } },
  });
  expect(res).toEqual([{ id: 60 }]);
  const res1 = await tran.update({
    table: TABLE,
    value: { msg: 'update yohn message' },
    where: { eq: { id: res[0].id } },
  });
  expect(res1.affectedRows).toEqual(1);
  const result = await tran.insert({
    table: TABLE,
    value: {
      name: 'vn',
      age: 77,
    },
  });
  return result;
});

总结

本插件的实现主要参考了 ali-rds(egg-mysql 的主要依赖),但提供了更多的方法,可以更加方便地在 egg 项目中进行 mysql 的操作。同时,增加了 typescript 支持,可以在 ts 环境下提供方便的智能提示,coding 体验提升不少。

如有需要欢迎大家多多尝试,也欢迎大家多提 issue,完善这个小项目。