手把手,教你用nodejs搭建后台最小系统(大量图文)系列二
前段时间利用nodejs封装了一个最小系统,也终于是把很久之前的愿望给实现了,在这里把如何实现分享出来。
本系列文章有两篇,上一篇详见下面传送门:
传送门:《手摸手,教你用nodejs搭建后台最小系统(大量图文)系列一》
项目地址:https://gitee.com/zhkumsg/node-base-demo

原文再续,书接上一回~~~
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个已经介绍完毕,现在正式开始介绍通用查询。在开始介绍通用查询底层前,我们先看一下通用查询的时序图

可以看到,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

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

/**
* 通用查询 * @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); },

/**
* 获取条件语句 * @param {*} condition 条件 * @param {*} flag */ getwhere(condition, flag) { let sqlwhereall = []; //条件判断较多,具体代码见后面的github链接 return sqlwhereall.join(""); }

/**
* 执行查询(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不就可以执行增删改了吗?

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

第五步:优化ServiceClient底层

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

9、开发接口路由

10、添加权限判断

11、实现跨域登陆

12、踩坑经历
时间差异、sql事务、参数化查询、global connection、cache、token

13、管理系统开发
