目 录CONTENT

文章目录

nodejs数据库项目的搭建

Administrator
2021-11-18 / 0 评论 / 1 点赞 / 5486 阅读 / 15910 字

nodejs数据库项目的搭建

image.png

假设我们现在要搭建上面的管理系统项目

  1. 先通过思维导图划分自己的模块与功能
  2. 确定数据库的字段,再创建数据库
  3. 通过nodejs搭建

一般情况下的数据库都会涉及到表的操作,一个模块可能就是一个表,上面有三个模块,在数据库里面可能就是三个表的操作

但是无论是哪一张表的操作都离不开四个操作“增,删,改,查”。这个时候针对四种操作,我们项目应该如何搭建

初始化项目

新建文件夹rental_house,然后打开文件夹,调出命令行,输入如下命令

$ npm init --yes

形成package.json

{
    "name": "rental_house",
    "version": "1.0.0",
    "description": "房屋出租系统 ",
    "main": "app.js",
    "scripts": {

    },
    "keywords": [
        "node.js", "房屋出租"
    ],
    "author": "杨标",
    "license": "MIT"
}

安装依赖包

因为当前只需要完成数据库操作的功能,所以我们只用安装mysql的包即可

$ npm install mysql --save

导入数据库操作的封装对象

之前的时候我们已经封装了一个文件叫DBUtils.js的文件,将这个文件放在utils的目录

utils的目录是用于放第三方工具类的,可以放自己的封装的工具,也可以放其它地方获取的工具类方法

编写服务层

服务层就是用于操作数据库的,为前面的程序提供的服务的

前面的程序指的是Controller,也可能是API等一系列操作

服务层其实就是一个一个对象,这个对象用于操作数据库,这个对象里面编写了很多常用的数据库的操作方法,如新增,修改,删除,查询,分页查询 等一系列方法

image.png

创建service的文件夹

根据上面的图我们可以得到一点,我们的service应该不只一个文件, 所以我们新建一个文件夹

根据数据库创建service对象

因为service主要是提供服务,操作数据表,所以正常情况下应该是一个数据表对应一个service文件。现在有三个数据表,所以我们构建三个数据库文件

  1. admin_info管理员表
  2. room_info房间表
  3. stu_info学生表
表名对象名备注
admin_infoAdminInfoService.js
room_infoRoomInfoService.js
stu_infoStuInfoService.js

完成功能里面具体的操作

假设当前项目经理分配给我的是room_info房间管理的模块,所以我现在只用专注于RoomInfoService.js这个即可

image.pngimage-20211020155758376

当我们拿到上面的原型图,我们的servic肯定要为这一个界面提供服务,那么RoomInfoServcie里面应该如何编写代码呢

/**
 * @description RoomInfoServcie 操作room_info数据表
 */

class RoomInfoServcie {
    add() {

    }
    deleteById() {

    }
    update() {

    }
    query() {

    }
    findById() {

    }
}
module.exports = RoomInfoServcie;

现在强制约束,每个service至少是有这5个方法

image.pngimage-20211020162207030

image.pngimage-20211020162158525

我们在中间添加了一层BaseService,以期达到项目模块的低耦合的操作

graph LR A[RoomInfoServcie.js]-->|继承|B[BaseService.js]-->|继承|C[DBUtils.js]

image.pngimage-20211118140102580

DBUtils.js文件

/**
 * @description 专门用于操作数据库的文件
 * @author 杨标
 * @version 1.0
 */

const mysql = require("mysql");

class DBUtils {
    /**
     * @description 获取数据库连接
     * @returns {mysql.Connection}
     */
    getConn() {
        let conn = mysql.createConnection({
            host: "192.168.1.254",
            port: 3306,
            user: "dev_h2103",
            password: "123456",
            database: "rental_house"
        });
        return conn;
    }
    /**
     * @description 执行一条SQL语句返回结果
     * @param {string} strSql 要执行的SQL语句 
     * @param {Array} params 执行SQL语句所需要的参数 
     * @returns {Promise<Array>|Promise<mysql.OkPacket>} 返回数据库执行的承诺
     */
    executeSql(strSql, params = []) {
        let p = new Promise((resolve, reject) => {
            let conn = this.getConn();
            conn.connect();
            conn.query(strSql, params, (error, results) => {
                if (error) {
                    reject(error);
                }
                else {
                    resolve(results);
                }
                conn.end();
            });
        });
        return p;
    }
}
module.exports = DBUtils;

BaseService.js文件

/**
 * @description 公共的基础服务类,主要用于解耦,并提供基础的方法
 */
const DBUtils = require("../utils/DBUtils.js");
class BaseService extends DBUtils {

}
module.exports = BaseService;

RoomInfoService.js文件

/**
 * @description RoomInfoServcie 操作room_info数据表
 */

const BaseService = require("./BaseService.js");

class RoomInfoServcie extends BaseService {
    /**
     * @description 新增房间信息
     * @param {{room_name, max_count, kt, networknetwork, washroom, room_size}} param 
     * @returns {Promise<import("mysql").OkPacket>} 返回数据库操作的结果
     */
    add({ room_name, max_count, kt, network, washroom, room_size }) {
        let strSql = `insert into room_info (room_name,max_count,kt,network,washroom,room_size) values (?,?,?,?,?,?) `;
        return this.executeSql(strSql, [room_name, max_count, kt, network, washroom, room_size]);
    }
    /**
     * @description 根据id删除房间信息
     * @param {number} id 要删除项的主键id 
     * @returns {Promise<boolean>} true代表删除成功,false代表删了作制作
     */
    async deleteById(id) {
        let strSql = ` delete from room_info where id = ? `;
        let results = await this.executeSql(strSql, [id]);
        return results.affectedRows > 0 ? true : false;
    }
    /**
     * @description 修改房间信息
     * @param {{ id, room_name, max_count, kt, network, washroom, room_size }} param 修改的对象
     * @returns {Promise<boolean>}  true代表修改成功,false代表修改失败
     */
    async update({ id, room_name, max_count, kt, network, washroom, room_size }) {
        let strSql = ` UPDATE room_info SET room_name=?, max_count=?, kt=?, network=?, washroom=?, room_size=? WHERE id=?; `;
        let results = await this.executeSql(strSql, [room_name, max_count, kt, network, washroom, room_size, id]);
        return results.affectedRows > 0 ? true : false;
    }
    /**
     * @description 查询房间信息
     * @param {{ room_name, kt, network, washroom }} param 查询条件
     * @returns {Promise<Array>} 返回数据库执行结果
     */
    query({ room_name, kt, network, washroom }) {
        let strSql = ` select * from room_info where 1 `;
        let ps = [];
        if (room_name) {
            strSql += ` and room_name like ? `;
            ps.push(`%${room_name}%`);
        }
        if (kt) {
            strSql += ` and kt = ? `;
            ps.push(kt);
        }
        if (network) {
            strSql += ` and network  = ? `;
            ps.push(network);
        }
        if (washroom) {
            strSql += ` and washroom = ? `;
            ps.push(washroom);
        }
        return this.executeSql(strSql, ps);
    }
    /**
     * @description 根据一个id得到一条数据
     * @param {number} id 主键id 
     * @returns {Promise<object>} 返回数据库查询结果
     */
    async findById(id) {
        let strSql = ` select * from room_info where id = ? `;
        let results = await this.executeSql(strSql, [id]);
        return results[0];
    }
    /**
     * @description 获取所有数据
     * @returns {Promise<[]>} 返回数据库查询结果
     */
    getAllList() {
        let strSql = ` select * from room_info `;
        return this.executeSql(strSql);
    }
}

module.exports = RoomInfoServcie;

BaseService的功能重构

/**
 * @description 公共的基础服务类,主要用于解耦,并提供基础的方法
 */
const DBUtils = require("../utils/DBUtils.js");
class BaseService extends DBUtils {
    constructor() {
        super();
        this.tableMap = {
            admin_info: "admin_info",
            room_info: "room_info",
            stu_info: "stu_info"
        };
        this.currentTableName = "";
    }

    /**
     * @description 根据一个主键去删除
     * @param {number} id  
     * @returns {Promise<boolean>} true代表删除成功,false代表删除失败
     */
    async deleteById(id) {
        let strSql = ` delete from ${this.currentTableName} where id = ? `;
        let results = await this.executeSql(strSql, [id]);
        return results.affectedRows > 0 ? true : false;
    }
    /**
     * @description 根据主键id查询信息
     * @param {number} id 查询的主键id 
     * @returns {Promise<object>} 数据库查询的结果
     */
    async findById(id) {
        let strSql = ` select * from ${this.currentTableName} where id = ? `;
        let results = await this.executeSql(strSql, [id]);
        return results[0];
    }
    /**
     * @description 查询所有数据
     * @returns {Promise<[]>} 返回数据库查询的结果
     */
    getAllList() {
        let strSql = ` select * from ${this.currentTableName} `;
        return this.executeSql(strSql);
    }
}


module.exports = BaseService;

在上面的代码里面,为了更好的实现与数据库的解耦关系,我们在当前的BaseService里面创建了一个tableMap对象,用于映射数据库的表,同时提取出了3个公共的方法

StuInfoService.js

/**
 * @description StuInfoServcie 操作stu_info数据表
 */

const BaseService = require("./BaseService.js");
class StuInfoService extends BaseService {
    constructor() {
        super();
        this.currentTableName = this.tableMap.stu_info;
    }
}

module.exports = StuInfoService;
classDiagram class RoomInfoService{ <<service>> query() update() } class BaseService{ <<service>> findById(id) deleteById(id) getAllList() tableMap currentTableName } RoomInfoService-->BaseService:extends class DBUtils { <<数据库操作>> getConn() executeSql(strSql,params) } BaseService-->DBUtils:extends

image.pngimage-20211118140118971


简单工厂模式

我们后期在测试这些功能(或者使用这些service)的时候我们又发现一个问题,如果当前的某一个文件同时使用了多个service这个每个文件都需要导入一次,很麻烦

/**
 * 测试基础的公共方法
 */
const AdminInfoService = require("../services/AdminInfoService.js");
const RoomInfoService = require("../services/RoomInfoService.js");
const StuInfoService = require("../services/StuInfoService.js");
//每个文件都要导这么多的service,怎么办呢

同时导入的文件每次都要new 一下才能使用,这个时候就很有问题

image.pngimage-20211021161230328

现象我为了实现与项目前端面结合,我们需要降低这个耦合性,所以我们会生产一个对象叫工厂对象,这个工厂专门用于生成所有的Service,最后只要让这一个文件与所有的页面实现耦合就可以了

ServiceFactory.js文件

/**
 * 工厂模式
 */
class ServiceFactory {
    constructor(){
        throw new Error("当前对象不需要new");
    }
    static createAdminInfoService() {
        let AdminInfoService = require("../services/AdminInfoService.js");
        return new AdminInfoService();
    }
    static createRoomInfoService() {
        let RoomInfoService = require("../services/RoomInfoService.js")
        return new RoomInfoService();
    }
    static createStuInfoService() {
        let StuInfoService = require("../services/StuInfoService.js")
        return new StuInfoService();
    }
}
module.exports = ServiceFactory;

上面是我们手动创建的一个工厂,这样后期我们要使用Service统一使用这个文件就可以了

当我们再去使用这个Service的时候就可以像这面这种试使用了

/**
 * 测试基础的公共方法
 */

const ServiceFactory = require("../factory/ServiceFactory.js");
//测试admin_info 获取所有数据
/*
const getAdminInfoAllData = async () => {
    let results = await ServiceFactory.createAdminInfoService().getAllList();
    console.log(results);
}

const getRoomInfo = async () => { 
    let results = await ServiceFactory.createRoomInfoService().findById(2);
    console.log(results);
}
getRoomInfo();
*/

ServiceFactory.createAdminInfoService().getAllList().then(results => console.log(results));
ServiceFactory.createRoomInfoService().findById(2).then(results => console.log(results));

这个时候我们可以看到所有的Service都是由当前的工厂ServiceFactory产生

  1. ServcieFactory里面所有的方法都是静态方法,不需要new就可以使用

之前的工厂模式代码里面,我们使用了class的静态方法,现在还有一种情况可以使用下面的代码【标哥推荐】

/**
 * 工厂模式
 */
const ServiceFactory = Object.freeze({
    get adminInfoService() {
        let AdminInfoService = require("../services/AdminInfoService.js");
        return new AdminInfoService();
    },
    get roomInfoService() {
        let RoomInfoService = require("../services/RoomInfoService.js")
        return new RoomInfoService();
    },
    get stuInfoService() {
        let StuInfoService = require("../services/StuInfoService.js")
        return new StuInfoService();
    }
});
module.exports = ServiceFactory;
/**
 * 1.访问器属性get
 * 2.使用对象的冻结来保护对象的内部
 */

抽象工厂模式

/**
 * 抽象工厂模式
 */
const path = require("path");
const fs = require("fs");

/**
 * @typedef ServiceFactoryType
 * @property {import("../services/AdminInfoService.js")} adminInfoService
 * @property {import("../services/RoomInfoService.js")} roomInfoService
 * @property {import("../services/StuInfoService.js")} stuInfoService
 */


/**
 * @type {ServiceFactoryType}
 */
const ServiceFactory = (() => {
    let obj = {}
    let arr = fs.readdirSync(path.join(__dirname, "../services"));
    for (let item of arr) {
        //1.1构建属性名
        let propertyName = item.replace(".js", "").replace(/^[A-Z]/, p => p.toLowerCase());
        //1.2导入文件,反射对象
        //1.3判断导入进来的文件否是一个构造函数
        let temp = require(path.join(__dirname, "../services", item));
        if (typeof temp === "function") {
            obj[propertyName] = Reflect.construct(temp, []);
        }
    }
    return obj;
})();


module.exports = ServiceFactory;

在当前的抽象工厂模式里面,我们直接读取了services文件夹的路径,然后找到文件 ,动态创建对象,动态导入文件,然后反射这个构造函数得到对象

后期如果有新的数据表,我们只需要在services文件夹下面创建新的XXXService.js文件就可以了,系统会自动的帮我们反射出来

1

评论区