Koa入门(二)—— Koa使用Sequelize连接数据库

Koa使用Sequelize连接数据库

使用的基础目录结构:Koa入门(一)—— Koa项目基础框架搭建

基础表:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `password` varchar(64) NOT NULL COMMENT '密码',
  `nick_name` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
  `sex` tinyint(4) NOT NULL DEFAULT '1' COMMENT '性别,1:男;2:女',
  `created_at` datetime NOT NULL COMMENT '创建时间',
  `updated_at` datetime DEFAULT NULL COMMENT '最后更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

sequelize 可以根据模型自动生成数据库表字段信息,不过个人比较喜欢自己建表,不喜欢使用这个功能

数据库信息配置

// src/main/config/index.js
export default {
  middlewares: [
    'globalError'
  ],
  db: {
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    host: process.env.DB_HOST,
    database: process.env.DB_DATABASE,
    dialect: process.env.DB_DIALECT,
    define: {
      // 数据库字段下划线命名,匹配模型驼峰式命名
      underscored: true,
      // 禁用修改表名
      freezeTableName: true
    },
    timezone: '+08:00',
    // 返回的时间格式配置
    dialectOptions: {
      dateStrings: true,
      typeCast: true
    }
  }
};

process.env的所有配置都在 .env 文件里:

NODE_ENV=dev

PORT=4000

DB_USERNAME=koa_demo
DB_PASSWORD=koa_demo
DB_HOST=localhost
DB_DATABASE=koa-demo
DB_DIALECT=mysql

DB层

// src/main/db/index.js
import config from '@main/config';
import Sequelize from 'sequelize';
import requireDirectory from 'require-directory';

const sequelize = new Sequelize(config.db);

// 加载模型
requireDirectory(module, '../model', {
  visit: (obj) => {
    if (obj.modelName) {
      sequelize.define(obj.modelName, obj.attributes, obj.options);
    }
  }
});

export default sequelize;

这个文件负责加载 model 目录下的所有模型,暴露出 sequelize 供其他代码使用。

model层

import Sequelize from 'sequelize';
import { isPhone } from '@main/util/ValidateUtil';
import { ParamError } from '@main/common/CommonError';

export const modelName = 'user';

export const attributes = {
  phone: {
    type: Sequelize.STRING(20),
    unique: true,
    validate: {
      check(val) {
        if (val) {
          if (!isPhone(val)) {
            throw new ParamError('电话号码格式不正确');
          }
        } else {
          throw new ParamError('电话号码不能为空');
        }
      }
    }
  },
  password: {
    type: Sequelize.STRING(64),
    validate: {
      check(val) {
        if (val) {
          if (val.length < 6) {
            throw new ParamError('密码不能小于6位');
          }
        } else {
          throw new ParamError('密码不能为空');
        }
      }
    }
  },
  nickName: Sequelize.STRING(50),
  sex: Sequelize.TINYINT
};

export const options = {
  tableName: 't_user'
};

每个模型JS都会暴露三个信息:

  • modelName :模型名称。服务层将会通过这个模型名称获取到模型,DB层加载模型也是根据是否有modelName 来判断是否加载模型的,所以这个时必须提供的。
  • attributes :字段信息。可以带有一些字段值校验
  • options :表配置信息。目前只有表名配置,如果你某个表不想使用 created_at 等字段也可以在这里配置。

sequelize 会自动提供 id, created_at 和 updated_at 字段,所以这里就没配置了

这里使用了一些自定义的校验工具类,我放在了 src/main/util 目录下:

const PHONE_PATTERN = /(13\d|14[579]|15[^4\D]|17[^49\D]|18\d)\d{8}/g;

/**
 * 电话号码验证
 * @param {string} val 
 */
export const isPhone = (val) => {
  return PHONE_PATTERN.test(val);
};

/**
 * 空值验证
 * @param {*} val 
 */
export const isNull = (val) => {
  return val === undefined || val === null;
};

service层

// src/main/service/UserService.js
import sequelize from '@main/db';
import { isNull } from '@main/util/ValidateUtil';
import { ParamError } from '@main/common/CommonError';

const userModel = sequelize.model('user');

class UserService {
  static async save(user) {
    return await userModel.create(user);
  }

  static async findById(id) {
    return await userModel.findByPk(id);
  }

  static async updateById(user) {
    if (isNull(user.id)) {
      throw new ParamError('ID不能为空');
    }
    return await userModel.update(user, {
      where: {
        id: user.id
      }
    });
  }
};

export default UserService;

controller层

// src/main/controller/UserController.js
import Router from 'koa-router';
import UserService from '@main/service/UserService';
import koaBodyparser from 'koa-bodyparser'

let userRouter = new Router({
  prefix: '/api/user'
});

userRouter.get('/:id', async ctx => {
  ctx.body = ctx.renderJson(await UserService.findById(ctx.params.id));
});

userRouter.post('/', koaBodyparser(), async ctx => {
  let params = ctx.request.body;
  ctx.body = ctx.renderJson(await UserService.save(params));
});

userRouter.put('/', koaBodyparser(), async ctx => {
  let params = ctx.request.body;
  ctx.body = ctx.renderJson(await UserService.updateById(params));
});

export default userRouter;

这里的 ctx.renderJson 是在 app.js 中:

// ...
const app = new Koa();

/**
 * 统一JSON返回格式
 */
app.context.renderJson = (data, msg = '操作成功', code = 200) => {
  return {
    code,
    data,
    msg
  }
};

// ...

校验全局异常处理

这里 Sequelize 校验抛出的异常是 Sequelize 自己提供的异常类,所以需要在全局异常处理处配置一下:

// src/main/middleware/globalError.js
import { CommonError, ParamError } from '@main/common/CommonError';
import { ValidationError } from 'sequelize/lib/errors';

export default () => {
  return async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      if (err instanceof ValidationError) {
        ctx.body = new ParamError(err.errors[0].message);
      } else if (err instanceof CommonError) {
        ctx.body = err;
      } else {
        ctx.body = new CommonError();
      }
      console.error(err);
    }
  };
};

多数据库配置

如果你数据库使用了主从同步,主写,从读,那么就需要连接两个数据库,多数据库的配置该如何配置?

修改 .env 配置

NODE_ENV=dev

PORT=4000

# 主库配置
DB_WRITE_USERNAME=koa_demo_write
DB_WRITE_PASSWORD=koa_demo_write
DB_WRITE_HOST=127.0.0.1
DB_WRITE_DATABASE=koa-demo-write
DB_WRITE_DIALECT=mysql
# 从库配置
DB_READ_USERNAME=koa_demo_read
DB_READ_PASSWORD=koa_demo_read
DB_READ_HOST=127.0.0.1
DB_READ_DATABASE=koa-demo-read
DB_READ_DIALECT=mysql

修改 config 配置

export default {
  middlewares: [
    'globalError'
  ],
  db: {
    write: {
      username: process.env.DB_WRITE_USERNAME,
      password: process.env.DB_WRITE_PASSWORD,
      host: process.env.DB_WRITE_HOST,
      database: process.env.DB_WRITE_DATABASE,
      dialect: process.env.DB_WRITE_DIALECT,
      define: {
        // 数据库字段下划线命名,匹配模型驼峰式命名
        underscored: true,
        // 禁用修改表名
        freezeTableName: true
      },
      timezone: '+08:00',
      // 返回的时间格式配置
      dialectOptions: {
        dateStrings: true,
        typeCast: true
      }
    },
    read: {
      username: process.env.DB_READ_USERNAME,
      password: process.env.DB_READ_PASSWORD,
      host: process.env.DB_READ_HOST,
      database: process.env.DB_READ_DATABASE,
      dialect: process.env.DB_READ_DIALECT,
      define: {
        // 数据库字段下划线命名,匹配模型驼峰式命名
        underscored: true,
        // 禁用修改表名
        freezeTableName: true
      },
      timezone: '+08:00',
      // 返回的时间格式配置
      dialectOptions: {
        dateStrings: true,
        typeCast: true
      }
    }
  }
};

修改DB层

// src/main/db/index.js
import config from '@main/config';
import Sequelize from 'sequelize';
import requireDirectory from 'require-directory';

const sequelize = new Sequelize(config.db.write);

// 加载模型
requireDirectory(module, '../model', {
  visit: (obj) => {
    if (obj.modelName) {
      sequelize.define(obj.modelName, obj.attributes, obj.options);
    }
  }
});

export default sequelize;

index.js 配置写数据库

这里还要添加一个读数据库 read.js

import config from '@main/config';
import Sequelize from 'sequelize';
import requireDirectory from 'require-directory';

const sequelize = new Sequelize(config.db.read);

// 加载模型
requireDirectory(module, '../model', {
  visit: (obj) => {
    if (obj.modelName) {
      sequelize.define(obj.modelName, obj.attributes, obj.options);
    }
  }
});

export default sequelize;

因为读写数据库的表都是一样的,所以只要复制一份 index.jsread.js 修改 config.db.writeconfig.db.read 即可。

读写数据库的使用

使用的时候如果是写入操作就引入:

import writeSequelize from '@main/db';

const userModel = writeSequelize .model('user');

如果是读操作就引入:

import readSequelize from '@main/db/read.js';

const userModel = readSequelize .model('user');

Gitee源码查看

相关文章:

  • Koa入门(一)—— Koa项目基础框架搭建
  • Koa入门(三)—— Koa项目自动化测试
  • Koa入门(四)—— Koa使用JWT做权限认证
  • Koa入门(五)—— 使用log4j记录日志

你可能感兴趣的:(koa)