使用 Sequelize 快速构建 PostgreSQL 数据的 CRUD 操作

1,437 阅读4分钟

【这是我参与8月更文挑战的第 4 天,活动详情查看:8月更文挑战

之前写过一个专栏《布道API》来介绍API的REST风格及推荐实践,今天开始来构建一个管理系统的API服务,首先需要处理的就是数据存储,本文将结合实际开发总结在 NodeJS 下使用 Sequelize 快速构建 PostgreSQL 数据的 CRUD 操作。

项目源代码:github.com/QuintionTan…

Sequelize

Sequelize 是一个基于 promise 的 Node.js ORM 工具,它具有强大的事务支持、关联关系、预读和延迟加载、读取复制等功能,支持的数据库包括:PostgreSQLMySQLMariaDBSQLiteMSSQL

Sequelize 类是引用 sequlize 模块后获取一个顶级对象,通过它来创建 sequlize 实例,也可以通过该对象来获取模内其它对象的引用,如:Utils工具类、Transaction 事务类等。创建实例后,可以通过实例来创建或定义 Model(模型)、执行查询、同步数据库结构等操作。

官方网站:docs.sequelizejs.com/

添加和配置

在安装模块之前,首先安装开发工具Sequelize-CLI

sudo npm install -g sequelize-cli

接下来在项目目录下安装数据存储相关的模块。

npm install  sequelize --save
npm install pg pg-hstore  --save

现在在项目根目录下创建文件.sequelizerc,代码如下:

const path = require('path');

module.exports = {
  "config": path.resolve('./config', 'db.json'),
  "models-path": path.resolve('./models'),
  'seeders-path': path.resolve('./seeders'),
  'migrations-path': path.resolve('./migrations')
};

该文件将告诉 Sequelize 初始化,以生成configmodels 到特定目录。接下来,输入命令初始化 Sequelize

sequelize init

该命令将创建 config/db.jsonmodels/index.jsmigrationsseeders 目录和文件。命令执行完毕之后打开并编辑 config/db.json 来配置数据库连接信息。

{
    "development": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres",
        "options": {
            "operatorsAliases": false
        },
        "logging": false
    },
    "test": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres"
    },
    "production": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres"
    }
}

目录说明:

  • migrations:所有迁移文件,通过sequelize db:migrate 创建相应数据表
  • seeders:种子文件,即初始化需要插入到数据库中的数据,运行sequelize db:seed:all

创建 Models 和 Migrations

使用CLI工具Sequelize-CLI 创建 administrators

sequelize model:create --name administrators --attributes id:integer,add_time:integer,last_login:integer,username:string,email:string,login_ip:string

执行后会生成两个文件

  • /src/migrations/20210803095520-create-administrators.js :创建数据表脚本,用于数据库初始化。
"use strict";
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable("administrators", {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER,
            },
            add_time: {
                type: Sequelize.INTEGER,
            },
            last_login: {
                type: Sequelize.INTEGER,
            },
            username: {
                type: Sequelize.STRING,
            },
            password: {
                type: Sequelize.STRING,
            },
            email: {
                type: Sequelize.STRING,
            },
            login_ip: {
                type: Sequelize.STRING,
            },
        });
    },
    down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable("administrators");
    },
};
  • /src/models/administrators.js :生成的model文件
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
    class administrators extends Model {}
    administrators.init(
        {
            id: {
                type: DataTypes.INTEGER,
                autoIncrement: true,
                primaryKey: true,
            },
            add_time: DataTypes.INTEGER,
            last_login: DataTypes.INTEGER,
            username: DataTypes.STRING,
            password: DataTypes.STRING,
            email: DataTypes.STRING,
            login_ip: DataTypes.STRING,
        },
        {
            sequelize,
            indexes: [
                {
                    unique: true,
                    fields: ["id"],
                },
            ],
            freezeTableName: true,
            timestamps: false, // 是否自动添加时间戳createAt,updateAt
            modelName: "administrators",
        }
    );
    return administrators;
};

现在执行命令:

sequelize db:migrate

执行成功后将在连接的数据库中创建数据表:administrators

创建 seed

seed 用于初始化插入数据,如管理员,在系统运行前需要创建一个默认账号,这些默认账号信息就写在 seed 文件中。创建 seed 命令如下:

sequelize seed:create --name administrator

执行成功后将会在 seeders 文件夹中创建文件,修改代码如下:

"use strict";

module.exports = {
    up: async (queryInterface, Sequelize) => {
        /**
         * Add seed commands here.
         *
         * Example:
         * await queryInterface.bulkInsert('People', [{
         *   name: 'John Doe',
         *   isBetaMember: false
         * }], {});
         */
        await queryInterface.bulkInsert(
            "administrators",
            [
                {
                    id:1,
                    username: "administrators",
                    password: "devpoint",
                    email: "QuintionTang@gmail.com",
                    add_time:1627828617,
                    last_time:1627828617
                },
            ],
            {}
        );
    },

    down: async (queryInterface, Sequelize) => {
        /**
         * Add commands to revert seed here.
         *
         * Example:
         * await queryInterface.bulkDelete('People', null, {});
         */
    },
};

现在将 seed 中的数据插入到数据库中,执行一下命令:

sequelize db:seed:all

创建 Services

创建文件夹 services , 文件夹中代码封装与 model 交互的方法,包括所有CRUD(创建,读取,更新和删除)操作,创建 administrators.js ,实现的逻辑为获取账号信息、更新账号信息,代码如下:

const AdministratorsModel = require("../models").administrators;

class AdministratorsService {
    constructor() {}
    async get(username) {
        try {
            const userinfo = await AdministratorsModel.findOne({
                where: { username },
            });
            return userinfo;
        } catch (error) {
            throw error;
        }
    }
    async add(newData) {
        try {
            return await AdministratorsModel.create(newData);
        } catch (error) {
            throw error;
        }
    }
    async del(id) {
        try {
            const isExist = await AdministratorsModel.findOne({
                where: { id: Number(id) },
            });

            if (isExist) {
                const deleted = await AdministratorsModel.destroy({
                    where: { id: Number(id) },
                });
                return deleted;
            }
            return null;
        } catch (error) {
            throw error;
        }
    }
    async update(id, updateData) {
        try {
            const isExist = await AdministratorsModel.findOne({
                where: { id: Number(id) },
            });

            if (isExist) {
                await AdministratorsModel.update(updateData, {
                    where: { id: Number(id) },
                });

                return updateData;
            }
            return null;
        } catch (error) {
            throw error;
        }
    }
}

module.exports = new AdministratorsService();

创建 Controllers

上面创建的 services 文件用于控制器,在控制器文件夹中创建一个名为 administrators.js 的文件, 代码如下:

const administratorsService = require("../services/administrators");
const util = require("../utils");

class AdministratorsController {
    constructor() {}
    async login(req, res) {
        const { username, passowrd } = req.body;

        try {
            const userinfo = await administratorsService.get(username);
            console.log(userinfo);
            if (!userinfo) {
                util.setError(200, 30004, `用户名不存在: ${username}`);
            } else {
                util.setSuccess(200, "登录成功", userinfo);
            }
            return util.send(res);
        } catch (error) {
            util.setError(404, error);
            return util.send(res);
        }
    }
}

module.exports = new AdministratorsController();

创建 Routers

在文件夹 routers 中创建文件 administrators.js 文件,代码如下:

const Router = require("express");
const administratorController = require("../controllers/administrators");

const administratorsRouter = Router();

administratorsRouter.post("/login", administratorController.login);

module.exports = administratorsRouter;

创建入口

现在来为服务创建接口,项目根目录下创建文件 app.js ,代码如下:

"use strict";
const administratorsRouter = require("./src/routers/administrators");
require("./src/utils/logger.js")(2);
const pjson = require("./package.json");
const os = require("os");
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const CONFIG = require("./config");

const cookieParser = require("cookie-parser");

function _version(serviceUrl) {
    const serviceInfo = {
        name: os.hostname(),
        os: os.platform(),
        os_v: os.release(),
        version: "v" + pjson.version,
    };
    console.info("   ");
    console.info("   ", serviceInfo.name);
    console.success("   ", serviceInfo.version);
    console.success("   ", serviceUrl);
    console.info("   ");
    console.info("   ");
}

function _isAuth(req) {
    if (req.cookies) {
        return req.cookies.auth;
    } else {
        return false;
    }
}
function _setAuth(res, userinfo) {
    res.cookie("auth", userinfo);
}
function _formatResponse(code, message, data) {
    return Object.assign(
        {
            code: code,
            message: message,
        },
        data
    );
}
const allowDomains = "*"; //如发布需改成:127.0.0.1
app.all(allowDomains, (req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("origin", "127.0.0.1:4200");
    res.header(
        "Access-Control-Allow-Headers",
        "Origin, X-Requested-With, Content-Type, Accept,application/x-www-form-urlencoded"
    );
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("Content-Type", "application/json;charset=utf-8");
    const noCheckPaths = ["/api/v1/auth/login"];
    if (req.method == "OPTIONS") {
        res.send(200);
    } else {
        if (noCheckPaths.includes(req.path)) {
            next();
        } else {
            const authInfo = _isAuth(req);
            if (authInfo && authInfo.name) {
                next();
            } else {
                res.send(401);
            }
        }
    }
});

app.use(cookieParser());
app.use(bodyParser.json());
app.use(
    bodyParser.urlencoded({
        extended: true,
    })
);
app.use("/api/v1/auth", administratorsRouter);

// 开始运行
const port = process.env.PORT || CONFIG.port;
_version(`running at http://127.0.0.1:${port}`);
app.listen(port);

现在执行命令 node app.js 启动服务,将看到终端效果如下:

1628084625552.jpg

至此,完成一个基本的 API 登录服务,还有待完善,后续在迭代中完善。文章涉及的代码在 GitHub 上。