登录
首页 >  数据库 >  MySQL

Sequelize-Automate: 自动生成 Sequelize Models

来源:SegmentFault

时间:2023-01-26 21:42:00 103浏览 收藏

小伙伴们对数据库编程感兴趣吗?是否正在学习相关知识点?如果是,那么本文《Sequelize-Automate: 自动生成 Sequelize Models》,就很适合你,本篇文章讲解的知识点主要包括MySQL、前端、Node.js、javascript、sequelize。在之后的文章中也会多多分享相关知识点,希望对大家的知识积累有所帮助!

本文的主角是 sequelize-automate

背景

Sequelize 是 Node.js 中常用的 ORM 库,其作用就是对数据库表和代码中的对象做一个映射,让我们能够通过面向对象的方式去查询和操作数据库。

举个例子,数据库可能有一张

class UserModel extends Model {}
User.init({
  id: DataTypes.INTEGER,
  name: DataTypes.STRING,
  birthday: DataTypes.DATE
}, { sequelize, modelName: 'userModel' });

然后可以通过

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary ket',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'user name',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'user email',
  `created_at` datetime NOT NULL COMMENT 'created datetime',
  `updated_at` datetime NOT NULL COMMENT 'updated datetime',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='User table'

则使用 sequelize-automate 可以自动生成的 Model 文件为

const {
  DataTypes
} = require('sequelize');

module.exports = sequelize => {
  const attributes = {
    id: {
      type: DataTypes.INTEGER(11).UNSIGNED,
      allowNull: false,
      defaultValue: null,
      primaryKey: true,
      autoIncrement: true,
      comment: "primary key",
      field: "id"
    },
    name: {
      type: DataTypes.STRING(100),
      allowNull: false,
      defaultValue: null,
      primaryKey: false,
      autoIncrement: false,
      comment: "user name",
      field: "name",
      unique: "uk_name"
    },
    email: {
      type: DataTypes.STRING(255),
      allowNull: false,
      defaultValue: null,
      primaryKey: false,
      autoIncrement: false,
      comment: "user email",
      field: "email"
    },
    created_at: {
      type: DataTypes.DATE,
      allowNull: false,
      defaultValue: null,
      primaryKey: false,
      autoIncrement: false,
      comment: "created datetime",
      field: "created_at"
    },
    updated_at: {
      type: DataTypes.DATE,
      allowNull: false,
      defaultValue: null,
      primaryKey: false,
      autoIncrement: false,
      comment: "updated datetime",
      field: "updated_at"
    }
  };
  const options = {
    tableName: "user",
    comment: "",
    indexes: []
  };
  const UserModel = sequelize.define("user_model", attributes, options);
  return UserModel;
};

这样我们就可以在项目中直接使用了:

const Sequelize = require('sequelize');
const UserModel = require('./models/user');

// Option 1: Passing parameters separately
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' | 'sqlite' */
});

const userModel = UserModel(sequelize);
const users = await userModel.findAll();

Sequelize-Automate 使用

sequelize-automate 提供了

$ npm install -g sequelize-automate

然后还需要安装使用的数据库对应的依赖包,这点与 sequelize 一致:

# 根据你使用的数据库,从下面的命令中选一个安装即可
$ npm install --save pg pg-hstore # Postgres
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # Microsoft SQL Server

当然,如果你已经在项目中使用了 sequelize,则一定会安装一个对应的数据库依赖包。

安装成功后,你就可以在项目目录中通过

 "scripts": {
    "sequelize-automate": "sequelize-automate"
  },

这样就可以通过

sequelize-automate -t js -h localhost -d test -u root -p root -P 3306  -e mysql -o models

如果在项目中使用的话,则可以将改命令添加到 package.json 中:

"scripts": {
    "sequelize-automate": "sequelize-automate -t js -h localhost -d test -u root -p root -P 3306  -e mysql -o models"
  },

然后通过

{
  "dbOptions": {
    "database": "test",
    "username": "root",
    "password": "root",
    "dialect": "mysql",
    "host": "localhost",
    "port": 3306,
    "logging": false
  },
  "options": {
    "type": "js",
    "dir": "models"
  }
}

当然也可以使用 JS 文件:

module.exports = {
  dbOptions: {
    database: "test",
    username: "root",
    password: "root",
    dialect: "mysql",
    host: "localhost",
    port: 3306,
    logging: false
  },
  options: {
    type: "js",
    dir: "models"
 }
}

然后就可以通过

dbOptions: {
  database: 'test',
  username: 'root',
  password: 'root',
  dialect: 'mysql',
  host: '127.0.0.1',
  port: 3306,
  define: {
    underscored: false,
    freezeTableName: false,
    charset: 'utf8mb4',
    timezone: '+00:00',
    dialectOptions: {
      collate: 'utf8_general_ci',
    },
    timestamps: false,
  },
};

通常我们会用到的就是

options: {
  type: 'js', // 指定 models 代码风格
  camelCase: false, // Models 文件中代码是否使用驼峰发命名
  fileNameCamelCase: true, // Model 文件名是否使用驼峰法命名,默认文件名会使用表名,如 `user_post.js`;如果为 true,则文件名为 `userPost.js`
  dir: 'models', // 指定输出 models 文件的目录
  typesDir: 'models', // 指定输出 TypeScript 类型定义的文件目录,只有 TypeScript / Midway 等会有类型定义
  emptyDir: false, // 生成 models 之前是否清空 `dir` 以及 `typesDir`
  tables: null, // 指定生成哪些表的 models,如 ['user', 'user_post'];如果为 null,则忽略改属性
  skipTables: null, // 指定跳过哪些表的 models,如 ['user'];如果为 null,则忽略改属性
  tsNoCheck: false, // 是否添加 `@ts-nocheck` 注释到 models 文件中
}

所有参数可以参考源码:src/index.js#L13

这里补充一点,之所以有

// @ts-nocheck
import { IApplicationContext, providerWrapper } from 'midway';
import { DataTypes } from 'sequelize';
import { IDB } from './db';
export default async function setupModel(context: IApplicationContext) {
  const db: IDB = await context.getAsync('DB');
  const attributes = {
     id: {
      type: DataTypes.BIGINT.UNSIGNED,
      allowNull: false,
      defaultValue: null,
      primaryKey: true,
      autoIncrement: true,
      comment: '主键',
      field: 'id',
    },
    name: {
      type: DataTypes.STRING(100),
      allowNull: false,
      defaultValue: null,
      primaryKey: false,
      autoIncrement: false,
      comment: null,
      field: 'name',
    },
  };
  const options = {
    tableName: 'flow',
    comment: '',
    indexs: [],
  };
  return db.sequelize.define('userModel', attributes, options);
}
providerWrapper([{
  id: 'UserModel',
  provider: setupModel,
}]);

API

上面主要讲了 sequelize-automate 的命令行使用方式,sequelize-automate 本身也提供了接口,让使用者自定义开发。主要有两个:

  • const Automate = require('sequelize-automate');
    
    // dbOptions 和 options 前面已经提到,这里不再赘述
    const dbOptions = {
      // ...
    };
    const options = {}
      // ...
    }
    
    // 创建一个 automate 实例
    const automate = new Automate(dbOptions, options);
    
    (async function main() {
      // // 获取 Models JSON 定义
      // const definitions = await automate.getDefinitions();
      // console.log(definitions);
    
      // 或生成代码
      const code = await automate.run();
      console.log(code);
    })()

    Sequelize-Automate 的实现

    sequelize-automate 的实现思路很简单,就是首先从数据库中查询到所有表信息,包括表结构、索引、外键等,然后将表信息转换为一个 JSON 定义,最后使用 AST 根据 JSON 定义去生成代码。

    获取表信息

    查询表信息依赖了 sequelize 的一些方法,这也是为什么 sequelize-automate 依赖了 sequelize,并且有个参数是

    {
        "user":{
            "structures":{
                "id":{
                    "type":"INT(11) UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":true,
                    "autoIncrement":true,
                    "comment":"primary ket"
                },
                "name":{
                    "type":"VARCHAR(100)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user name"
                },
                "email":{
                    "type":"VARCHAR(255)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user email"
                },
                "created_at":{
                    "type":"DATETIME",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"created datetime"
                },
                "updated_at":{
                    "type":"DATETIME",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"updated datetime"
                }
            },
            "indexes":[
                {
                    "primary":true,
                    "fields":[
                        {
                            "attribute":"id",
                            "order":"ASC"
                        }
                    ],
                    "name":"PRIMARY",
                    "tableName":"user",
                    "unique":true,
                    "type":"BTREE"
                },
                {
                    "primary":false,
                    "fields":[
                        {
                            "attribute":"name",
                            "order":"ASC"
                        }
                    ],
                    "name":"uk_name",
                    "tableName":"user",
                    "unique":true,
                    "type":"BTREE"
                }
            ],
            "foreignKeys":[
    
            ]
        },
        "user_post":{
            "structures":{
                "id":{
                    "type":"INT(11) UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":true,
                    "autoIncrement":true,
                    "comment":"primary key"
                },
                "user_id":{
                    "type":"INT(11) UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user id"
                },
                "title":{
                    "type":"VARCHAR(255)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"post title"
                },
                "content":{
                    "type":"TEXT",
                    "allowNull":true,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"post content"
                },
                "created_at":{
                    "type":"DATETIME",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"created datetime"
                },
                "updated_at":{
                    "type":"DATETIME",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"updated datetime"
                }
            },
            "indexes":[
                {
                    "primary":true,
                    "fields":[
                        {
                            "attribute":"id",
                            "order":"ASC"
                        }
                    ],
                    "name":"PRIMARY",
                    "tableName":"user_post",
                    "unique":true,
                    "type":"BTREE"
                },
                {
                    "primary":false,
                    "fields":[
                        {
                            "attribute":"user_id",
                            "order":"ASC"
                        }
                    ],
                    "name":"fk_user_id",
                    "tableName":"user_post",
                    "unique":false,
                    "type":"BTREE"
                }
            ],
            "foreignKeys":[
                {
                    "constraint_name":"fk_user_id",
                    "constraintName":"fk_user_id",
                    "constraintSchema":"test",
                    "constraintCatalog":"test",
                    "tableName":"user_post",
                    "tableSchema":"test",
                    "tableCatalog":"test",
                    "columnName":"user_id",
                    "referencedTableSchema":"test",
                    "referencedTableCatalog":"test",
                    "referencedTableName":"user",
                    "referencedColumnName":"id"
                }
            ]
        }
    }

    处理 Models JSON 定义

    得到表信息后,就需要将表信息转换为 sequelize models 的定义。比如:将

    [
        {
            "modelName":"user_model",
            "modelFileName":"user",
            "tableName":"user",
            "attributes":{
                "id":{
                    "type":"DataTypes.INTEGER(11).UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":true,
                    "autoIncrement":true,
                    "comment":"primary ket",
                    "field":"id"
                },
                "name":{
                    "type":"DataTypes.STRING(100)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user name",
                    "field":"name",
                    "unique":"uk_name"
                },
                "email":{
                    "type":"DataTypes.STRING(255)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user email",
                    "field":"email"
                },
                "created_at":{
                    "type":"DataTypes.DATE",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"created datetime",
                    "field":"created_at"
                },
                "updated_at":{
                    "type":"DataTypes.DATE",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"updated datetime",
                    "field":"updated_at"
                }
            },
            "indexes":[
    
            ]
        },
        {
            "modelName":"user_post_model",
            "modelFileName":"user_post",
            "tableName":"user_post",
            "attributes":{
                "id":{
                    "type":"DataTypes.INTEGER(11).UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":true,
                    "autoIncrement":true,
                    "comment":"primary key",
                    "field":"id"
                },
                "user_id":{
                    "type":"DataTypes.INTEGER(11).UNSIGNED",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"user id",
                    "field":"user_id",
                    "references":{
                        "key":"id",
                        "model":"user_model"
                    }
                },
                "title":{
                    "type":"DataTypes.STRING(255)",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"post title",
                    "field":"title"
                },
                "content":{
                    "type":"DataTypes.TEXT",
                    "allowNull":true,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"post content",
                    "field":"content"
                },
                "created_at":{
                    "type":"DataTypes.DATE",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"created datetime",
                    "field":"created_at"
                },
                "updated_at":{
                    "type":"DataTypes.DATE",
                    "allowNull":false,
                    "defaultValue":null,
                    "primaryKey":false,
                    "autoIncrement":false,
                    "comment":"updated datetime",
                    "field":"updated_at"
                }
            },
            "indexes":[
                {
                    "name":"fk_user_id",
                    "unique":false,
                    "type":"BTREE",
                    "fields":[
                        "user_id"
                    ]
                }
            ]
        }
    ]

    而后续的

    const t = require('@babel/types');
    
    const str = t.stringLiteral('Primary key');
    // { type: 'StringLiteral', value: 'Primary key' }

    生成对象

    const obj = t.objectProperty(t.identifier('comment'), t.stringLiteral("Primary key"));
    /**
    {
      type: 'ObjectProperty',
      key: { type: 'Identifier', name: 'comment' },
      value: { type: 'StringLiteral', value: 'Primary' },
      computed: false,
      shorthand: false,
      decorators: null
    }
    */

    然后就可以根据 AST 生成代码:

    const generator = require('@babel/generator').default;
    
    const code = generate(obj);
    // { code: 'comment: "Primary"', map: null, rawMappings: null }

    需要注意的是,如果要支持中文,则需要设置

    const obj = t.objectProperty(t.identifier('comment'), t.stringLiteral("主键"));
    
    const code1 = generate(obj);
    { code: 'comment: "\\u4E3B\\u952E"', map: null, rawMappings: null }
    
    const code2 = generate(obj, {
      jsescOption: {
        minimal: true,
      },
    });
    // { code: 'comment: "主键"', map: null, rawMappings: null }

    总结

    最开始写 sequelize-automate 是因为每次表结构修改了,都需要手动在代码里面修改 models ,修改起来非常繁琐而且容易写错,当表非常多的时候,写起来就更麻烦了。所以开发了这个小工具,能够让工具做的事情,就尽量让工具去做。

    在写 sequelize-automate 之前,其实我也发现了

    sequelize/sequelize-auto
    也可以用来自动生成 models,但这个包已经几年没有更新了,使用的 sequelize 还是 3.30 版本,现在 sequelize 已经更新到 6.0 了;并且它还有很多 BUG 没有修复,很难使用起来。我也去看了它的代码,感觉很混乱,全是回调嵌套,难以维护。其生成代码也是用的字符串拼接的方式,没有 AST 先进、高端、准确且可预测。所以,毫不犹豫的选择并使用 sequelize-automate 吧!

    今天关于《Sequelize-Automate: 自动生成 Sequelize Models》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于mysql的内容请关注golang学习网公众号!

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>
评论列表