搭建 nodeJS 服务器之(2)sequelize

前言

《搭建 nodeJS 服务器之(2)sequelize》是系列教程的第二部分。包含模型的定义和使用、验证器,关联、事务、作用域,钩子和原始查询等知识。同时,本系列教程将会带你从零架构一个五脏俱全的后端项目。

传送门:

  • 《搭建 nodeJS 服务器之(1)koa》 - 基于 nodeJs 平台的下一代 web 开发框架
  • 《搭建 nodeJS 服务器之(3)测试》 - mocha + supertest(敬请期待)
  • 《joi 完全指南》 - 对象模式验证库(敬请期待)

Mysql 与 Sequelize 的关系

开始之前,我们先要对 ORM 有个大致的了解,如果你还是可耐的新萌,暂可先跳过一节,如果你想成为一名斗士,暂可漂洋过海再来看我,如果你拥着一颗炽热的心,那么,停下吧!请听我一叙。

叙,何为 ORM ?

首先,让我们请上一位人间大佬——百度百科(一副生无可恋可怜的表情看着你)

ORM(Object Relational Mapping),称为对象关系映射,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

最后,千呼万唤始出根本不存在的神界大佬——wiki(呵呵)

对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。

两位大佬一言一语间揭露了一个事实,我们都是渣渣(灰),还是让我说一句人话吧!ヾ(๑❛ ▿ ◠๑ )

Sequelize 呢!是一个基于 Promise 的 NodeJs ORM 库,目前支持 ostgres, MySQL, SQLite 和 Microsoft SQL Server 等数据库程序。Sequelize 相当一个中间人负责两者,谁呢?js 和 mysql 之间的交流,你没看错,他让你可以用 js 的语法和概念蹂躏数据库,同时还提供了一些更高级的抽象,降低了使用时的复杂度,甚至都不必知 mysql 是何方神圣。Σ>―(〃°ω°〃)♡→ 哇,开森不开森,心动不心动,熟悉的语言,熟悉的写法,简直是解放劳动力(懒癌患者)的神器呀!

(话锋一转)我们倘若要真想从脸到屁股彻底了解 sequelize,还必须看看它对 mysql 做了些什么 ( ◞˟૩˟)◞

山回路转 mysql (⋟﹏⋞),让我们看一下,Sequelize 中各部分与 Mysql 概念上的对应关系:

  • 实例化 Sequelize 连接到 Database: 通过实例化 Sequelize 类,连接到数据库程序指定的数据库。
  • 定义 Model 映射 Table: 通过模型映射数据表的定义并代理操作方法
  • 指定 DataTypes 声明 Data Types: 把数据库的数据类型变成在 js 上下文中更合适的用法。
  • 使用 Op 生成 Where 子句 Operators: 为选项对象提供强大的解耦和安全检测。
  • 关联 Association 替代复杂的 Foreign Key 和 多表查询: 用一套简单的方法管理复杂的多表查询。
  • 调用 Transcation 封装 Transation : 对事务一层简单而必要的封装。

题外话:Myqsl 是什么鬼,听明白的求原谅,一脸懵逼的请无视

从一个小项目开始

一眨眼,一个,二个(数手指),都快三个月了,你要是还记得 《搭建 nodeJS 服务器之(1)koa》 这篇文章,那真是祖坟上烧高香,记不得,呵呵(你肯定是没看过)。

在系列教程的第一篇中我们(喂喂喂!谁和你我们的)被作者带节奏,新建新建新建(文件目录),安装安装安装(依赖),但却那么小气只讲了下 koa 插件的用法和业务代码的组织方式。那么,从本章开始,我们就有钱把“场景”和“数据”这两位角儿请出来,唱一出对角戏。

划重点:本篇教程分为入门和进阶两部分,入门部分会从头到尾讲解一个文章发布平台,支持多用户、点赞、收藏和评论。进阶部分会以问答的方式进一步解疑答惑(灰色引用部分的形式)。

开始之前,千万别忘了先把 Sequelize 以及依赖包安装到本地(丢~~又让我安装)。

npm i sequelize mysql2 -D
复制代码

第一步,连接到数据库

Sequelize 是库的入口类,可做两件事情:

  1. 连接到数据库
  2. 设置数据表的全局配置。

所以嘛!暂且可把 Sequelize 的实例 看做 Mysql 中的 Database(数据库)

// app/config/databse.config.js
/* ☝  你看,我可是把当前代码所放的文件写的明明白白 */

export default {
  // 打开哪个数据库
  database: 'test',
  // 用户名
  username: 'root',
  // 密码
  password: '1234',
  // 使用哪个数据库程序
  dialect: 'mysql',
  // 地址
  host: 'localhost',
  // 端口
  port: 3306,
  // 连接池
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  },
  // 数据表相关的全局配置
  define: {
    // 是否冻结表名
    // 默认情况下,表名会转换为复数形式
    freezeTableName: true,
    // 是否为表添加 createdAt 和 updatedAt 字段
    // createdAt 记录表的创建时间
    // updatedAt 记录字段更新时间
    timestamps: true,
    // 是否为表添加 deletedAt 字段
    // 默认情况下, destroy() 方法会删除数据,
    // 设置 paranoid 为 true 时,将会更新 deletedAt 字段,并不会真实删除数据。
    paranoid: false
  }
}
复制代码

导入配置文件,并实例化 Sequelize。

// app/models/test/index.js

import Sequelize from 'sequelize'
import config from '../../config/database.config'

// 实例化,并指定配置
export const sequelize = new Sequelize(config)

// 测试连接
sequelize
  .authenticate()
  .then(() => {
    console.log('Connection has been established successfully.')
  })
  .catch(err => {
    console.error('Unable to connect to the database:', err)
  })
复制代码

划重点:models 目录用于存放 Sequelize 库相关文件,下层目录对应 Sequelize 打开 Mysql 中的 Database,每个下层目录中的 index.js 主文件用于整合 Model,而其他 .js 文件对应当前 Database 中的一张 Table

第二步,建立模型

Model 是由 sequelize.define()(sequelize 就是上小节中的实例) 方法定义用于映射数据模型和数据表之间的关系的对象模型。(╬◣д◢) 说人话,啊啊是,其实就是 Mysql 中的一张数据表啦。哈~哈~哈~哈~

那么,你还不快 (╯>д<)╯⁽˙³˙⁾ 新建一个文件 User.js 存放用户表的模型定义,如下:

// /models/test/User.js

export default (sequelize, DataTypes) =>
  // define() 方法接受三个参数
  // 表名,表字段的定义和表的配置信息
  sequelize.define('user', {
    id: {
      // Sequelize 库由 DataTypes 对象为字段定义类型
      type: DataTypes.INTEGER(11),
      // 允许为空
      allowNull: false,
      // 主键
      primaryKey: true,
      // 自增
      autoIncrement: true,
    },
    username: {
      type: DataTypes.STRING,
      allowNull: false,
      // 唯一
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
  })
复制代码

然后,导入并同步到 Mysql 中。

// /test/index.js

import Sequelize from 'sequelize'
import config from '../../config/database.config'

export const sequelize = new Sequelize(config)

// 导入
export const User = sequelize.import(__dirname + '/User')
// 同步到 Mysql 中
// 也就是将我们用 js 对象声明的模型通过 sequelize 转换成 mysql 中真正的一张数据表
sequelize.sync()

// ...
复制代码

划重点:推荐将所有的模型定义在 单文件 中以实现模块化,并通过 sequelize.import() 方法把模块导入到 index.js 中统一管理。

Sequelize 库会为我们执行以下 Mysql 原生命令在 test 中创建一张名为 user 的数据表。

CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER(11) NOT NULL auto_increment UNIQUE , `username` VARCHAR(255) NOT NULL UNIQUE, `password` VARCHAR(255) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
复制代码

sequelize.sync() 将模型同步到数据库的三种方法和区别?

// 标准同步
// 只有当数据库中不存在与模型同名的数据表时,才会同步
sequelize.sync()
// 动态同步
// 修改同名数据表结构,以适用模型。
sequelize.sync({alter: true})
// 强制同步
// 删除同名数据表后同步,谨慎使用,会导致数据丢失
sequelize.sync({force: true})

// 另外,当你指定表与表之间的关联后,修改被关联的表结构时会抛出异常。
// 需要先注释掉关联代码,然后更新同步模型后,再取消掉注释即可。

// 再另外,当你有新的关联时必须使用动态同步才会生效。
复制代码

同步成功后,我们把 sequelize.synce 注释掉。因为,我们再次重启应用后不需要再重新同步。

// sequelize.sync()
复制代码

然后,在 controllers 中创建同名 User.js 文件,存放用户相关的接口逻辑(注册,登录,登出,查询和删除等)

// /controllers/User.js

import { User } from '../models/test/'


export default class {
  static async register (ctx) {
    const post = ctx.request.body
    let result
    try {
      // 调用模型的 create()  方法插入一行数据
      result = await User.create(post)
    } catch (error) {
      return ctx.body = {success: false, error}
    }

    ctx.body = {success: true, data: result}
  }
  // ...
}
复制代码

这里你可能会有个疑问,就是控制器(controllers)中的逻辑分类应该是对应模型(model)还是路由(router)?其实这个问题很好回答,controllers 原本就是为了更好的管理 router 而分离出来的,而 router 的接口路径也应该恰好能够自我解释控制器逻辑的作用,所以控制器中的逻辑分类应该按照路由区别。

最后,挂载至路由。

// /router.js
import Router from 'koa-router'
const router = new Router

router.prefix('/api/v1')

import User from './controllers/User'

router
  .post('/register', User.register)
  // ...

export default router
复制代码

打开 postman(或者其他接口调试工具)发起请求。

查看 Mysql。

User.create() 方法返回的是一个由 Sequelize 库定义的结果集类(模型上大部分直接操作数据库的方法都返回这一结果集类)。

result = await User.create(post)

// 可直接获取结果集中的字段值
result.username
// 或者使用结果集对象提供的方法
result.getDataValue('username')
// 或者将结果集解析为一个 JSON 对象
result.toJSON()

// 踩坑必备
// 直接在结果集类上添加自定义数据是无效的
result.newAttr = 'newValue'
// 调用 setDataValue 方法或者调用 toJSON() 将它转换为一个对象
result.setDataValue('newAttr', 'newValue')
复制代码

那么,为什么可以直接将结果集对象赋值给 ctx.body? 因为在内部 koa 会把 ctx.body 序列化。

body = JSON.stringify(body)
复制代码

模型方法

除了用户模型外,我们还需定义文章模型(article)、文章的点赞模型(article_like)、文章的收藏模型(article_star)和文章的评论模型(article_comment)。

这些就不一一列出,请参考教程附属的项目源码(懒癌症表示还没写完教程,点了也没反应)(良心吧!)

这里列出了模型上一些操作数据库常用的方法。☞ 更多详情,请移步官方

findOne()
findAll()
findById()
findOrCreate()
findOrBuild()
findAndCountAll()
create()
bulkCreate()
update()
upsert()
destroy()
increment()
decrement()
count()
max()
min()
sun()
复制代码

数据类型

DataTypes 对象为模型的字段指定数据类型。

以下列出了部分 DataTypes 类型 对应的 Mysql 数据类型。☞ 更多详情,请移步官方

// 字符串
STRING(N=255)               // varchar(0~65535)
CHAR(N=255)                 // char(0~255)
TEXT(S=tiny/medium/long)    // s + text
复制代码
// 数字

// 整数
TINYINT(N?)         // tinyint(1-byte)
SMALLINT(N?)        // smallint(2-byte)
MEDIUMINT(N?)       // mediumint(3-byte)
INTEGER(N=255?)     // integer(6-byte)
BIGINT(N?)          // bigint(8-byte)

// 浮点数
FLOAT(n, n)         // float(4-byte)
DOUBLE(n, n)        // double(8-byte)
复制代码
// 布尔值
BOOLEAN             // tinyint(1)
复制代码
// 日期
DATE(n?)            // datetime(8-byte)
TIME                // timestamp(4-byte)
NOW                 // 默认值为 current timestamp
复制代码
// 其他
ENUM( any,...)      // ENUM('value1', ...) length > 65535 
JSON                 // JSON
复制代码

integerbigintfloatdouble 都支持 unsignedzerofill 属性

Sequelize.INTEGER(11).UNSIGNED.ZEROFILL
复制代码

验证器

Model 为每个字段都提供了验证选项。


export default (sequelize, DataTypes) =>
  sequelize.define('user', {
    // ...
    email: {
      type: DataTypes.STRING(255),
      allowNull: true,
      validate: {
        isEmail: true
      }
    }
  })
复制代码

另外,可指定 argsmsg 自定义参数和错误消息。

isEmail: {
    args: true, // 可省略,默认为 true
    msg: "邮箱格式不合法!"
}
复制代码

只有当创建(比如,调用 create() 方法)或更新(比如,调用 update() 方法)模型数据时,才会触发验证器,另外当设置 allowNull: true,且字段值为 null 时,也不会触发验证器。仅当验证器验证通过时才会真实将操作同步到数据库中。当验证未通过时,会抛出一个 SequelizeValidationError 异常对象(这也是为什么,需要在数据库操作的地方用 try catch 语句捕获错误,防止 nodeJs 进程退出)。

以下列出了所有内置的验证规则。☞ 更多详情,请移步官方

validate: {
    is: ["^[a-z]+$",'i'],     // 只允许字母
    is: /^[a-z]+$/i,          // 与上一个示例相同,使用了真正的正则表达式
    not: ["[a-z]",'i'],       // 不允许字母
    isEmail: true,            // 检查邮件格式 ([email protected])
    isUrl: true,              // 检查连接格式 (http://foo.com)
    isIP: true,               // 检查 IPv4 (129.89.23.1) 或 IPv6 格式
    isIPv4: true,             // 检查 IPv4 (129.89.23.1) 格式
    isIPv6: true,             // 检查 IPv6 格式
    isAlpha: true,            // 只允许字母
    isAlphanumeric: true,     // 只允许使用字母数字
    isNumeric: true,          // 只允许数字
    isInt: true,              // 检查是否为有效整数
    isFloat: true,            // 检查是否为有效浮点数
    isDecimal: true,          // 检查是否为任意数字
    isLowercase: true,        // 检查是否为小写
    isUppercase: true,        // 检查是否为大写
    notNull: true,            // 不允许为空
    isNull: true,             // 只允许为空
    notEmpty: true,           // 不允许空字符串
    equals: 'specific value', // 只允许一个特定值
    contains: 'foo',          // 检查是否包含特定的子字符串
    notIn: [['foo', 'bar']],  // 检查是否值不是其中之一
    isIn: [['foo', 'bar']],   // 检查是否值是其中之一
    notContains: 'bar',       // 不允许包含特定的子字符串
    len: [2,10],              // 只允许长度在2到10之间的值
    isUUID: 4,                // 只允许uuids
    isDate: true,             // 只允许日期字符串
    isAfter: "2011-11-05",    // 只允许在特定日期之后的日期字符串
    isBefore: "2011-11-05",   // 只允许在特定日期之前的日期字符串
    max: 23,                  // 只允许值 <= 23
    min: 23,                  // 只允许值 >= 23
    isCreditCard: true,       // 检查有效的信用卡号码
}
复制代码

Getters & Setters

GettersSetters 可以让你在获取和设置模型数据时做一些处理。


export default (sequelize, DataTypes) =>
  sequelize.define('user', {
    // ...
    sex: {
      type: DataTypes.BOLLEAN,
      allowNull: true,
      get () {
        const sex = this.getDataValue('sex')
        return sex ? '男' : '女'
      },
      set (val) {
        this.setDataValue('title', val === '男')
      }
    }
  })
复制代码

第三步,关联

欢迎达到战场的勇士(或是疯狂滚动鼠标的你)。模型创建好了吗?没的话,送你一个技能—— Ctrl+C Ctrl+V,偷懒后,记得在 index.js 中引入。

// models/test/index.js

// 导入
export const User = sequelize.import(__dirname + '/User')
export const Article = sequelize.import(__dirname + '/Article')
export const ArticleLike = sequelize.import(__dirname + '/Article_like')
export const ArticleStar = sequelize.import(__dirname + '/Article_star')
export const ArticleComment = sequelize.import(__dirname + '/Article_comment')
复制代码

关联知识点简要一览。更多详情,请移步官方

// models/test/index.js

// 在 source 上存在一对一关系的外键关联
source.belongsTo(target, {
    as: 'role'  // 使用别名(可代替目标模型),
    foreignKey: 'user_id'   // 外键名,
    targetKey:  'id'        // 目标健,默认主键
})
// 在 target 上存在一对一关系的外键关联
source.hasOne(target)

// 在 target 上存在一对多 source 的外键关联
source.hasMany(target)

// 在 target 上存在多对多的外键关系(必须通过另外一张数据表保存关联数据)
source.belongsToMany(target, {through: 'UserProject'})
target.belongsToMany(source, {through: 'UserProject'})
复制代码

接下来,我们来建立表与表之间的关联。显而易见,UserArticle 之间存在一对多的关系,每个用户可先制定个小目标,先发它一亿篇文章(UserArticle 为一对多,即用 hasMany 方法),反过来,一篇文章仅属于某个用户的私有财产(ArticleUser 为一对一,即用 belongsTo 方法)。

// models/test/index.js

// 外键 uid 将会放到 Article 上
User.hasMany(Article, {foreignKey: 'uid'})
// 同样,还是把外键放到 Article 上
Article.belongsTo(User, {foreignKey: 'uid'})
复制代码

同步后,查看数据表 Article 时,哇, o(゚Д゚)っ! 神奇呀!多出了一个字段, 正是uid,这个就是外键。

那么,问题就来了,为什么需要建立表与表之间的关联?它有何用?因为方便,如果你想要通过一次查询就把文章数据和文章所有关的点赞、收藏和评论数据一起找出来,并且放在一个数据结构中,那么关联是不可被替代的。在你 sequelize 一次查询多个表的关联数据时,它本质上是生成了一个复杂的 mysql 链表查询语句。而在 sequeliz 中你仅仅在需要时,指定即可。

UserArticleLikeArticleStarArticleComment 都存在与上述一样的关联关系,让我们一起快乐地大喊:“粘贴复制大法好”。

// models/test/index.js

User.hasMany(ArticleLike, {foreignKey: 'uid'})
ArticleLike.belongsTo(User, {foreignKey: 'uid'})

User.hasMany(ArticleStar, {foreignKey: 'uid'})
ArticleStar.belongsTo(User, {foreignKey: 'uid'})

User.hasMany(ArticleComment, {foreignKey: 'uid'})
ArticleComment.belongsTo(User, {foreignKey: 'uid'})
复制代码

另外, ArticleArticleLikeArticleStarArticleComment 之间也存在关联关系,比如一条评论,你既要知道谁写的评论(uid),还有知道评论了哪篇文章 (aid)

// models/test/index.js

Article.hasMany(ArticleLike, {foreignKey: 'aid'})
ArticleLike.belongsTo(Article, {foreignKey: 'aid'})

Article.hasMany(ArticleStar, {foreignKey: 'aid'})
ArticleStar.belongsTo(Article, {foreignKey: 'aid'})

Article.hasMany(ArticleComment, {foreignKey: 'aid'})
ArticleComment.belongsTo(Article, {foreignKey: 'aid'})
复制代码

同步后,看查看数据表 ArticleComment时,正如所料,它多出两个外键字段 uidaid

同步后数据表中没外键字段?(你这大骗子)
你不认真看教程,还怪起我了。哼, ̄へ ̄,自己翻到 sequelize.sync() 将模型同步到数据库的三种方法和区别? 看最后一行字。哼哼哼!!!不想找?往上翻,快!

对不起  ̄ω ̄=,我为我的无理道歉,作为赔偿,我手不停蹄(笑尿)的手绘(连线都画不直的菜鸡,手绘?你信吗?)了一张关联流程图,梳梳它们剪不断理还乱的关系吧。

关联使用

// 查询文章数据,同时关联评论数据
Article.findAll({
  // 通过 include 字段,把需要关联的模型指定即可。
  // 就辣么简单!
  inclue: [ArticleComment]
})

// 返回数据
{
    "id": 4,
    "title": "我是标题",
    "content": "我是内容",
    "createdAt": "2018-10-11T03:42:01.000Z",
    "updatedAt": "2018-10-11T03:42:01.000Z",
    "uid": 1,
    // 评论
    "article_comments": [/* */]
}
复制代码
// 带上所有已建立关联表的数据

Article.findAll({
  inclue: [{
    all: true  
  }]
})


// 返回数据
}
    "id": 4,
    "title": "我是标题",
    "content": "我是内容",
    "createdAt": "2018-10-11T03:42:01.000Z",
    "updatedAt": "2018-10-11T03:42:01.000Z",
    "uid": 1,
    // 用户
    "user": {
        "id": 1,
        "username": "sunny",
        "password": "1234",
        "createdAt": "2018-10-11T03:38:54.000Z",
        "updatedAt": "2018-10-11T03:38:54.000Z"
    },
    // 点赞
    "article_likes": [/* */]],
    // 收藏
    "article_stars": [/* */]],
    // 评论
    "article_comments": [/* */]]
}
复制代码
// 甚至你还可以深度递归(小心死循环)

Article.findAll({
  inclue: [{
    all: true,
    nested: true
  }]
})
复制代码

更多详情,请移步官方

第四步,接口逻辑

接口呢!也就是增删改查,还有什么好说的呢,直接上好代(酒)好(肉)码招待着各位吧 ٩(๑❛ᴗ❛๑)۶

// app/controllers/Article.js

import {Article} from "../models/test"

export default class {
  // 增
  static async add (ctx) {
    const post = ctx.request.body
    let result
    try {
      // 简单直了
      result = await Article.create(post)
    } catch (error) {
      return ctx.body = {success: false, error}
    }

    ctx.body = {success: true, data: result}
  }
  
   // 删
  static async remove (ctx) {
    const {id, uid} = ctx.request.body
    let result
    try {
      // 必须同时指定 id 和 uid 才能删除
      result = await Article.destroy({
        where: { id, uid }
      })
    } catch (error) {
      return ctx.body = {success: false, error}
    }

    ctx.body = {success: true, data: result}
  }

  // 改    
  static async update (ctx) {
    const post = ctx.request.body
    // 才不让你改所属的用户呢
    delete post.uid
    let result
      // 改呢,必须通过 where 指定主键
      result = await Article.update(post, {where: {id: post.id}})

    ctx.body = {success: true, data: result}
  }

  // 查
  static async find (ctx) {
    const {id, uid} = ctx.query
    let result
    try {
      result = await Article.findAll({
        // 可选的 id(查询指定文章数据) 和 uid(查询指定用户所有的文章数据)
        where: Object.assign({}, id && {id}, uid && {uid}),
        // 带上所有的关联数据
        include: [{
          all: true,
        }]
      })
    } catch (error) {
      return ctx.body = {success: false, error}
    }

    ctx.body = {success: true, data: result}
  }
}
复制代码

最后记得挂载到路由。

// app/router.js

import Article from './controllers/Article'

router
  .get('/article/find', Article.find)
  .post('/article/add', Article.add)
  .post('/article/update', Article.update)
  .post('/article/remove', Article.remove)
复制代码

Op(查询条件)

Op 对象集内置了一系列适用于 where 子句 查询的操作符(查询条件)。

// /models/test/index.js

//  导出 Op
export const Op = Sequelize.Op
复制代码
// /models/controllers/User.js

import {User, Op} from '../models/test/'


export default class {
  static async findTest (ctx) {
    let result
    try {
      result = await User.findAll({
        // 查询所有 id > 2 的用户
        where: {
          id: {
            [Op.gt]: 2
          }
        }
      })
    } catch (error) {
      return ctx.body = {success: false, error}
    }

    ctx.body = {success: true, data: result}
  }
}
复制代码

以下列出了所有内置的 Op 操作符更多详情,请移步官方

[Op.and]: {a: 5}           // 且 (a = 5)
[Op.or]: [{a: 5}, {a: 6}]  // (a = 5 或 a = 6)
[Op.gt]: 6,                // id > 6
[Op.gte]: 6,               // id >= 6
[Op.lt]: 10,               // id < 10
[Op.lte]: 10,              // id <= 10
[Op.ne]: 20,               // id != 20
[Op.eq]: 3,                // = 3
[Op.not]: true,            // 不是 TRUE
[Op.between]: [6, 10],     // 在 6 和 10 之间
[Op.notBetween]: [11, 15], // 不在 11 和 15 之间
[Op.in]: [1, 2],           // 在 [1, 2] 之中
[Op.notIn]: [1, 2],        // 不在 [1, 2] 之中
[Op.like]: '%hat',         // 包含 '%hat'
[Op.notLike]: '%hat'       // 不包含 '%hat'
[Op.iLike]: '%hat'         // 包含 '%hat' (不区分大小写)  (仅限 PG)
[Op.notILike]: '%hat'      // 不包含 '%hat'  (仅限 PG)
[Op.regexp]: '^[h|a|t]'    // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.iRegexp]: '^[h|a|t]'    // ~* '^[h|a|t]' (仅限 PG)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG)
[Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike
[Op.overlap]: [1, 2]       // && [1, 2] (PG数组重叠运算符)
[Op.contains]: [1, 2]      // @> [1, 2] (PG数组包含运算符)
[Op.contained]: [1, 2]     // <@ [1, 2] (PG数组包含于运算符)
[Op.any]: [2,3]            // 任何数组[2, 3]::INTEGER (仅限PG)

[Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG
复制代码

为什么不直接使用符号而是使用额外的封装层 Op,据官方说法是为了防止 SQL 注入和其他一些安全检测。另外,Op 对象集其实是一系列 Symbol 的集合。

Handle.js

Handle.js,一个基于 koa 和 sequelize 的中间库,让你只专注于接口逻辑。

它可以让你以最少量的代码,编写具有复用性和良好可读性的复杂接口代码,我们看一个简单的例子:

import Handle from 'handle.js'
// 导入 sequelize 模型
import { Article } from '../models/db'

// 把 article 传入 Handle,并实例化
const article = new Handle(Article)

// 生成一个查询当前模型所有数据的 koa 中间件
const find = article.findAll()

// 绑定到路由
router.get('/article/find', find)
复制代码

当你有一个分页逻辑(你一定写过),在 Handle 中你可以把它封装成一个函数。

// 为什么是偏函数?
// 因为我给分页预留了默认的配置项
function pagination (defaultCount = 5, defaultPage = 0) => {
  return d => {
    const count = ~~d.count || defaultCount
    const page = ~~d.page || defaultPage
    return {
      limit: count,
      offset: page * count
    }
  }
}
复制代码

然后把它放进 scope 中即可。

article
    .scope(pagination(10))
    .findAll()
复制代码

你也许已经看出来,可复用性体现哪里了?没错,就是通过 scope 你可以让每个接口轻松实现分页,你什么也不用做,只需要把一篮食材丢给 scope 它就会给你做出一盘香喷喷的盖浇米饭。scope 做了什么?很简单,它在内部深度合并了所有选项,就好像你把一个原本杂乱而负载过重的大胖子,拆分成了更小更轻快的小帅伙(这比喻有点奇怪),或者说你可以用搭积木的方式有序的易懂的(最重要是好看啊!)建筑起一座摩天大厦。而 Include 让你统一管理全局的关联数据并且通过简单的对象嵌套生成复杂的关联层级。

另外, Handle 为用户做了更多的事情,在 scope 的基础上提供了一个工具集,涵盖了一些常用的封装,让你真的就像在搭积木,轻轻松松就实现了一个复杂的接口。handle 还提供了一个全局管理关联数据的静态类,并提供了对 Mock 的支持,并致力于让所有的事情变得简单有序。值得一提的是 Handle 并不依赖 Koa,过程方法可以让它用在 express 项目中或者 websocket 应用中,Handle 更准确说是一个 sequelize 的包装库。

当你具有一定基础后,配合使用 handle.js 可以帮助你提高开发效率和写出可维护的代码。handle.js 已在 github 免费开源(地址:github.com/yeshimei/Ha…)如果你觉得对你有用欢迎 star,支持我更新和完善它,如果你它已经在你的项目中,更希望你能参与进来作为贡献者之一,感谢大家。

猜你喜欢

转载自juejin.im/post/5bfc03b851882516d725e013
今日推荐