手把手,教你用nodejs搭建后台最小系统(大量图文)系列二

前段时间利用nodejs封装了一个最小系统,也终于是把很久之前的愿望给实现了,在这里把如何实现分享出来。

本系列文章有两篇,上一篇详见下面传送门:

传送门:《手摸手,教你用nodejs搭建后台最小系统(大量图文)系列一》

项目地址:https://gitee.com/zhkumsg/node-base-demo


13908708-c9e8e8e468bad80c.png


原文再续,书接上一回~~~

8、开发底层模块

》common/SortParam.js

const MType = require("./MType");

const Direction = require("./Direction")

//定义SortParam实体类(排序字段)

const propertys = [

    "Type",

    "Field",

    "SortDirection"

];

class SortParam {

    constructor() {

        propertys.forEach(name => { this[name] = undefined; });

        this.set(arguments[0]);

        if (this.Type === undefined) { this.Type = MType.Mstring; }

        if (this.Field === undefined) { this.Field = ""; }

        if (this.SortDirection === undefined) { this.SortDirection = Direction.ASC; }

    }

    get(key) {

        return this[key];

    }

    set() {

        if (typeof arguments[0] === "object") {

            for (let name in arguments[0]) {

                if (propertys.indexOf(name) > -1) {

                    this[name] = arguments[0][name];

                }

            }

        }

    }

}

module.exports = SortParam;

这是一个与前面MType和Direction枚举相关的实体类,作用是表明字段采用何种排序方式。 SortParam实体类有三个属性,Type字段类型、Field字段名字、SortDirection排序方式。

其中Type属性是MType的枚举,Field属性是字符串、SortDirection属性是Direction枚举。

new SortParam({ Field: "ZK_ID", Type: MType.Mstring, SortDirection: Direction.ASC });

这表明是新建一个SortParam实例,对字符串类型的ZK_ID字段采用正序排列



》commom/MemoryCondition.js

const MType = require("./MType");

const MLogic = require("./MLogic");

const MOperator = require("./MOperator");

//定义MemoryCondition实体类(查询条件)

const propertys = [

    "Type",

    "Field",

    "Operator",

    "value",

    "Logic",

    "subCondition"

];

class MemoryCondition {

    constructor() {

        propertys.forEach(name => { this[name] = undefined; });

        this.set(arguments[0]);

        if (this.Type === undefined) { this.Type = MType.Mstring; }

        if (this.Field === undefined) { this.Field = ""; }

        if (this.Operator === undefined) { this.Operator = MOperator.Like; }

        if (this.Logic === undefined) { this.Logic = MLogic.And; }

    }

    get(key) {

        return this[key];

    }

    set() {

        if (typeof arguments[0] === "object") {

            for (let name in arguments[0]) {

                if (propertys.indexOf(name) > -1) {

                    this[name] = arguments[0][name];

                }

            }

        }

    }

}

module.exports = MemoryCondition;

查询条件实体类。该实体类有六个参数,其代表意义如下:

MemoryCondition.Type:字段类型(MType枚举)

MemoryCondition.Field:字段名称(字符串)

MemoryCondition.Operator:匹配逻辑(MOperator 枚举) 

MemoryCondition.value:值(任意类型)

MemoryCondition.Logic:条件间关系(MLogic枚举)

MemoryCondition.subCondition:子查询(这里用不上,该功能用于多表查询)  

比如查询用户表中,用户主键为admin,写法如下(该写法将会经常用)

new MemoryCondition({ Type: MType.Mstring, Field: "ZK_ID", Operator: MOperator.Equal, value: "admin", Logic: MLogic.And });

其中Type属性默认为MType.Mstring,Operator属性默认为MOperator.Like,Logic属性默认为MLogic.And。Field和value为必传字段。

上面的查询条件将会被Single底层翻译成

where ZK_ID = 'admin'



》common/MemoryResult.js

//定义MemoryResult实体类(查询结果)

const propertys = [

    "recordcount",

    "pagecount",

    "result",

    "ErrorMsg"

];

class MemoryResult {

    constructor() {

        propertys.forEach(name => { this[name] = undefined; });

        this.set(arguments[0]);

        if (this["recordcount"] === undefined) { this["recordcount"] = 0; }

        if (this["pagecount"] === undefined) { this["pagecount"] = 0; }

        if (this["result"] === undefined) { this["result"] = []; }

        if (this["ErrorMsg"] === undefined) { this["ErrorMsg"] = ""; }

    }

    get(key) {

        return this[key];

    }

    set() {

        if (typeof arguments[0] === "object") {

            for (let name in arguments[0]) {

                if (propertys.indexOf(name) > -1) {

                    this[name] = arguments[0][name];

                }

            }

        }

    }

}

module.exports = MemoryResult;

对于每一个sql搜索(查询),返回的结果都不一定一样,所以我们封装了一个实体类,用于包装返回结果,其包括recordcount查询总数、pagecount当前页码、result结果集(类似于C#下的DataTable,这里是数组)和ErrorMsg错误信息四个属性。

调用通用查询后将返回该实体类。



》common/ServiceClient.js 

在前面新建的11个文件中,简单的9个已经介绍完毕,现在正式开始介绍通用查询。在开始介绍通用查询底层前,我们先看一下通用查询的时序图

13908708-ea228eaa1ebd5999.png
通用查询时序图

可以看到,ServiceClient主要是做switch判断、Single做sql语句拼接、DataAccess做数据访问。这里先介绍ServiceClient.js

const { TableCfg, QueryModel, Single } = require('./common.module');

const {

    ZK_USERINFO,

    ZK_PERMITINFO,

    ZK_PERMITCONFIG,

    ZK_ROLEINFO,

    ZK_PARAMINFO,

    ZK_INVESTMENT

} = require('../model/model.module');

var ServiceClient = {

    /**

    * 通用查询

    * @param {*} model 实体枚举

    * @param {*} condition 条件

    * @param {*} size 类型

    * @param {*} pagesize 每页条数

    * @param {*} pageno 页码

    * @param {*} isPager 是否分页

    * @param {SortParam} sp 排序

    */

    Query(model, condition, size, pagesize, pageno, isPager, sp) {

        let result = new Array();

        switch (model) {

            case QueryModel.ZK_USERINFO: result = this.getqueryPara(ZK_USERINFO); break;

            case QueryModel.ZK_PERMITINFO: result = this.getqueryPara(ZK_PERMITINFO); break;

            case QueryModel.ZK_PERMITCONFIG: result = this.getqueryPara(ZK_PERMITCONFIG); break;

            case QueryModel.ZK_ROLEINFO: result = this.getqueryPara(ZK_ROLEINFO); break;

            case QueryModel.ZK_PARAMINFO: result = this.getqueryPara(ZK_PARAMINFO); break;

            case QueryModel.ZK_INVESTMENT: result = this.getqueryPara(ZK_INVESTMENT); break;

        }

        if (result.length > 0) {

            return Single.query(result[0], condition, size, pagesize, pageno, isPager, sp, result[1]);

        } else {

            return Promise.reject("实体枚举错误");

        }

    },

    /**

    * 读取配置文件,获取表名以和主键

    * @param {*} m 实体类

    */

    getqueryPara(m) {

        let result = new Array();

        if (TableCfg[m.name] != undefined) {

            result.push(TableCfg[m.name].name);

            result.push(Object.getOwnPropertyNames(new m).indexOf("EB_ISDELETE") > -1 ? "false" : null);

        }

        return result;

    }

};

module.exports = ServiceClient;

该对象中有两个方法,Query和getqueryPara,前者是查询接口,后者是是查找数据表对应情况的方法。

先说getqueryPara方法,它是通过读取TableCfg.js数据库映射文件,找到当前查询的表名和主键,最后在通过Object.getOwnPropertyNames方法实现判断是否存在EB_ISDELETE字段,判断EB_ISDELETE字段的意义是后续默认添加EB_ISDELETE='0'的判断(因为我们的系统默认是做逻辑删除,不做物理删除)。

对于Query方法,它接受model 实体枚举、condition 条件、size 类型、pagesize 每页条数、pageno 页码、isPager 是否分页、sp 排序共七个参数。内部根据查询枚举获取对应表名、主键和EB_ISDELETE字段情况,最后调用Single下的query方法(该方法将返回Promise实体,异步回调)

return Single.query(result[0], condition, size, pagesize, pageno, isPager, sp, result[1]);

PS1:前面没有介绍过common/common.module.js这个文件,其实它和model/model.module.js一样,都是把当前文件夹的全部文件统一导出。
PS2:Object.getOwnPropertyNames是js原生方法,用于遍历自身属性,相当于OOP下反射



》common/Single.js 

13908708-05eb93f6e71c8fd9.gif

没错,是到最重要的时候了,现在开始终于进入Single的介绍了。
Single.js可以算是通用查询的核心了,在该文件中实现了拼接sql字符串的目标,先大概看一下Single文件。

13908708-a147ef089bad83a7.png
Single.query查询方法,生成基础sql语据

/**

    * 通用查询

    * @param {*} tablename 表名

    * @param {*} condition 条件

    * @param {*} size

    * @param {*} pagesize

    * @param {*} pageno

    * @param {*} isPager 是否分页

    * @param {SortParam} sp 排序

    * @param {*} flag 是否有EB_ISDELETE字段

    */

    query(tablename, condition, size, pagesize, pageno, isPager, sp, flag) {

        let sql = "select * from " + tablename;

        if (flag === "false") {

            sql += " where EB_ISDELETE = '0'";

        }

        return this.querySQL(sql, condition, size, pagesize, pageno, isPager, sp, flag);

    },


13908708-978fd487440a4eb2.png
Single.getwhere拼接where条件

/**

    * 获取条件语句

    * @param {*} condition 条件

    * @param {*} flag

    */

    getwhere(condition, flag) {

            let sqlwhereall = [];

            //条件判断较多,具体代码见后面的github链接

            return sqlwhereall.join("");

    }


13908708-c2bfcf08c82a463d.png
Single.querySQL 根据基础sql语句和where条件,再根据分页情况调用数据访问底层

/**

    * 执行查询(promise)

    * @param {*} sql 基础sql语句

    * @param {*} condition 条件

    * @param {*} size

    * @param {*} pagesize

    * @param {*} pageno

    * @param {*} isPager

    * @param {SortParam} sp

    * @param {*} flag

    */

    querySQL(sql, condition, size, pagesize, pageno, isPager, sp, flag) {

        let ms = new MemoryResult();

        sql = sql + this.getwhere(condition, flag);

        let promise = new Promise((resolve, reject) => {

            if (isPager) {

                ds.GetTable("select count(1) as sam from (" + sql + ") as myTemp").then((dt, err) => {

                    if (err) {

                        reject(err);

                    } else {

                        if (dt.length > 0) {

                            ms.recordcount = Number.parseInt(dt[0].sam, 10);

                            ms.pagecount = Math.floor(ms.recordcount / pagesize);

                        }

                        ds.GetTable(ds.datapagerSql(pageno, pagesize, sql, sp)).then((dt, err) => {

                            if (err) {

                                reject(err);

                            } else {

                                ms.result = dt;

                                resolve(ms);

                            }

                        });

                    }

                });

            } else {

                ds.GetTable(sql).then((dt, err) => {

                    if (err) {

                        reject(err);

                    } else {

                        ms.result = dt;

                        resolve(ms);

                    }

                });

            }

        });

        return promise;

    },


由于篇幅限制,具体代码这里不再粘贴,详细可见github链接(此处该有掌声)。


到这里,通用查询开发结束(getwhere方法后续将github链接)。

这时候再去执行前面的通用查询就可以正常执行了,可能有小伙伴已经忘了

let condition = [];  //查询条件实体类数组

let pagesize = 30; //页数

let pageno = 1; //页码

let sort = new SortParam(); //排序实体类

client.Query(QueryModel.ZK_USERINFO, condition, null, pagesize, pageno, true, sort).then(m => {

  console.log(m.result); //分页查询结果

}).catch(err => {

  throw err;

})

PS:在app.js下测试,测试前需要把配置和文件开发好,最后记得导入文件


第四步:实现快速增删改操作

经历了前面的通用查询开发后,相信你已经有点累了,然而总开发才完成了四分之一,后面继续下一个四分之一:实现快速增删改操作。

有小伙伴又忍不住要站起来了,嚷嚷着不是用数据访问底层DataAccess的RunQuery不就可以执行增删改了吗?

13908708-c9940f865634c341.png
这位同学请你坐下!你挡着后面端意大利面的二营长了!

是的,你没有说错,这的确可以,但是,我说过你可以不用写一句sql代码就能实现业务开发。

13908708-b15a4bb699b78103.png

第五步:优化ServiceClient底层

13908708-9de63957ebdac906.png

第六步:开发底层公共方法

13908708-7d3b4baa73f8f522.png

9、开发接口路由

13908708-13a9824c27f020a1.png

10、添加权限判断

13908708-a8b8c009011eecef.png

11、实现跨域登陆

13908708-5c9105629381e9e5.png

12、踩坑经历

时间差异、sql事务、参数化查询、global connection、cache、token

13908708-368fd295148dfc77.png

13、管理系统开发

13908708-305cca469a360b8e.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值