jbd
Version:
jBD Framework's Core
925 lines (751 loc) • 21.4 kB
JavaScript
/**
* ==========================================
* Name: Mongo
* Author: Buddy-Deus
* CreTime: 2014-11-20
* Description: db操作基类
* Log
* 2015-06-09 初始化
* 2018-07-26 增加Redis支持
* ==========================================
*/
jBD.define(function (module, exports, require) {
"use strict";
const STATE = {
Closed: 0,
Closeing: -1,
Opened: 1,
Opening: 2
},
ERROR = (dtd, code, msg) => {
let result = {errorCode: code, errorMsg: msg};
if (dtd) dtd.reject(result);
return result;
},
CONVER = jBD.Conver,
STRING = jBD.String;
let API, INFO, DB;
INFO = jBD.TSeal.extend({
className: "ConnectInfo",
create: function (port) {
this.PORT = port || 8080;
},
free: function () {
},
protocol: "db",
host: "",
port: 0,
uid: "",
pwd: "",
name: "",
opt: null,
cite: 0,
build: function (name) {
return STRING.Format("{protocol}://{act}{host}:{port}" + (name && this.name ? "/{name}" : ""), {
protocol: this.protocol,
act: this.uid ? this.uid + (this.pwd ? ":" + this.pwd : "") + "@" : "",
host: this.host,
port: this.port,
name: this.name
});
},
parse: function (value) {
switch (jBD.type(value, true)) {
case "string":
value = value.split("://");
if (value.length < 2) value.unshift("");
if (value.length < 3) value.push("");
value[2] = value[1].split("/");
value[1] = value[2][0];
value[2] = value[2].length > 1 ? value[2][1] : "";
value[1] = value[1].split("@");
if (value[1].length < 2) value[1].unshift("");
value[1][0] = value[1][0].split(":");
if (value[1][0].length < 2) value[1][0].push("");
value[1][1] = value[1][1].split(":");
if (value[1][1].length < 2) value[1][1].push(this.PORT);
value = {
protocol: value[0],
uid: value[1][0][0],
pwd: value[1][0][1],
host: value[1][1][0],
port: value[1][1][1],
name: value[2]
};
break;
case "object":
break;
default:
value = {};
break;
}
this.host = (value.host !== "localhost") && jBD.isString(value.host) ? value.host : "127.0.0.1";
this.port = CONVER.toInteger(value.port, this.PORT);
this.uid = jBD.isString(value.uid) ? value.uid : "";
this.pwd = jBD.isString(value.pwd) ? value.pwd : "";
if (this.cite < 1) {
if (value.name) this.name = value.name;
}
return this.build();
}
});
DB = jBD.TObject.extend({
className: "DB",
PORT: 0,
state: STATE.Closed,
mode: "db",
connectString: "",
connectInfo: null,
create: function (cfg) {
this.oper = null;
this.connectInfo = new INFO(this.PORT);
this.connectString = this.connectInfo.parse(cfg);
},
free: function () {
let INFO = this.connectInfo;
this.Close(true);
},
dbConnect: function (dtd, info, name = info, opt = name, done = opt) {},
dbDisconnect: function () {},
dbOpen: async function () {
const THIS = this,
INFO = THIS.connectInfo,
dtd = jBD.Deferred(true),
DONE = (dtd, info, db) => {
info.cite++;
dtd.resolve(db);
};
if (THIS.state === STATE.Opened) DONE(dtd, INFO, THIS.oper);
else {
if (THIS.state === STATE.Closed) {
// THIS.oper = null;
THIS.connectString = THIS.connectInfo.build();
if (!INFO.name) ERROR(dtd, 200, "连接字符串出错!");
else {
try {
THIS.oper = await THIS.dbConnect();
THIS.state = STATE.Opened;
DONE(dtd, INFO, THIS.oper);
}
catch (e) {
dtd.reject(e);
THIS.oper = null;
THIS.state = STATE.Closed;
}
}
}
}
return dtd.promise();
},
dbClose: function (force) { return this; },
Open: async function (info, name, opt) {
this.Close(true);
if (arguments.length == 1) {
name = info;
info = null;
}
const dtd = jBD.Deferred(true),
INFO = this.connectInfo;
if (info) INFO.parse(info);
if (name) INFO.name = name;
if (opt) INFO.opt = opt;
try {
let db = await this.dbOpen();
dtd.resolve(db);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Close: function (force) {
const INFO = this.connectInfo,
CLOSE = () => {
this.state = STATE.Closed;
this.oper = null;
INFO.cite = 0;
// INFO.name = null;
};
if (this.state != STATE.Opened || this.oper) CLOSE();
else {
if (force) this.dbClose(true, CLOSE);
else {
if (INFO.cite && --INFO.cite < 1) {
this.state = STATE.Closeing;
this.dbClose(false, CLOSE);
}
}
}
return this;
}
});
API = function (mode, cfg = mode) {
switch (mode) {
case "mongo":
default:
return new API.Mongo(cfg);
case "redis":
return new API.Redis(cfg);
}
};
API.STATE = STATE;
API.ERROR = ERROR;
API.INFO = INFO;
API.Config = db => {
if (!jBD.isObject(db)) db = {};
db.host = jBD.isString(db.host) ? db.host : "127.0.0.1";
db.port = CONVER.toInteger(db.port, 0);
db.max = CONVER.toInteger(db.max, 10);
db.uid = jBD.isString(db.uid) ? db.uid : "";
db.pwd = jBD.isString(db.pwd) ? db.pwd : "";
db.name = jBD.isString(db.name) ? db.name : "BD_Server";
if (!jBD.has(db.mode, ["mongo", "redis"])) db.mode = "mongo";
return db;
};
API.Mongo = (function (DB, STATE, ERROR) {
let Mongo, MONGO;
const drop = async (coll) => {
let dtd = jBD.Deferred(true);
try {
await coll.drop();
dtd.resolve();
}
catch (e) {
ERROR(dtd, 210, e.message || "");
}
return dtd.promise();
},
find = async (coll, select, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.find(select, opt).toArray();
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 211, e.message || "");
}
return dtd.promise();
},
findOne = async (coll, select, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.findOne(select, opt);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 212, e.message || "");
}
return dtd.promise();
},
count = async (coll, select) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.count(select);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 213, e.message || "");
}
return dtd.promise();
},
remove = async (coll, select, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.remove(select, opt);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 214, e.message || "");
}
return dtd.promise();
},
insert = async (coll, value, opt) => {
let dtd = jBD.Deferred(true);
try {
let func, rs;
if (!coll.insertMany) func = coll.insert;
else if (jBD.isArray(value)) func = coll.insertMany;
else func = coll.insertOne;
rs = await func.call(coll, value, opt);
dtd.resolve(rs.ops);
}
catch (e) {
ERROR(dtd, 215, e.message || "");
}
return dtd.promise();
},
findAndModify = async (coll, select, sort, value, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.findAndModify(select, sort, value, opt);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 216, e.message || "");
}
return dtd.promise();
},
update = async (coll, select, value, opt) => {
let dtd = jBD.Deferred(true);
try {
let func, rs;
if (!coll.updateMany) func = coll.update;
else {
if (opt.multi) func = coll.updateMany;
else func = coll.updateOne;
delete opt.multi;
}
rs = await func.call(coll, select, value, opt);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 217, e.message || "");
}
return dtd.promise();
},
aggregate = async (coll, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = coll.aggregate(opt).toArray();
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 218, e.message || "");
}
return dtd.promise();
},
mapReduce = async (coll, map, red, opt) => {
let dtd = jBD.Deferred(true);
try {
let rs = coll.mapReduce(map, red, opt);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 219, e.message || "");
}
return dtd.promise();
};
Mongo = DB.extend({
className: "Mongo",
PORT: 27017,
create: function (cfg) {
this.super("create", cfg);
if (!MONGO) MONGO = require("mongodb");
this.connectInfo.protocol = "mongodb";
this.connectString = this.connectInfo.build();
},
dbConnect: async function () {
const THIS = this,
INFO = THIS.connectInfo,
dtd = jBD.Deferred(true);
THIS.state = STATE.Opening;
try {
if (THIS.oper) THIS.oper = await THIS.oper.open();
else THIS.oper = await MONGO.MongoClient.connect(THIS.connectString, INFO.opt || {useNewUrlParser: true});
THIS.oper.once("close", () => {
if (THIS.state === STATE.Closed) {
if (INFO.cite > 0) {
THIS.dbConnect();
return;
}
}
this.state = STATE.Closed;
});
dtd.resolve(THIS.oper.db(INFO.name));
}
catch (e) {
ERROR(dtd, 200, e.message || "");
}
return dtd.promise();
},
dbClose: function (force, done) {
if (this.oper) this.oper.close(force === true);
if (jBD.isFunction(done)) done();
},
dbOper: async function (db, name) {
const dtd = jBD.Deferred(true);
try {
let coll = await db.collection(name);
dtd.resolve(coll);
}
catch (e) {
ERROR(dtd, 201, e.message || "");
}
return dtd.promise();
},
// ==============================================================
ObjectID: value => MONGO.ObjectID(value),
Code: value => MONGO.Code(value),
Time: (value, fmt) => {
let dt;
value = (value + "").slice(0, 8);
if (value.length < 8) dt = new Date();
else dt = new Date(parseInt(value, 16) * 1000);
return CONVER.toString(dt, fmt);
},
// ==============================================================
Find: async function (name, select, opt) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await find(coll, select || {}, opt || {});
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
FindOne: async function (name, select, opt) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await findOne(coll, select || {}, opt || {});
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Count: async function (name, select) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await count(coll, select || {});
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Insert: async function (name, value, opt) {
const THIS = this,
dtd = jBD.Deferred(true),
fmtOpt = opt => {
opt.multi = opt.multi === true;
opt.safe = opt.safe === true;
let autoinc = opt.autoinc;
if (jBD.isString(autoinc)) autoinc = {field: autoinc, initial: 1};
else if (!jBD.isObject(autoinc)) autoinc = null;
else {
autoinc = {
field: jBD.isString(autoinc.field) ? autoinc.field : "id",
initial: jBD.isNumber(autoinc.initial, {int: true}) ? autoinc.initial : 0
};
}
return autoinc;
};
if (jBD.isArray(value) && value.length < 1) ERROR(dtd, 213, "数据内容错误");
else {
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
autoinc = fmtOpt(opt = opt || {}),
rs;
if (autoinc) value[autoinc.field] = await THIS.AutoInc(autoinc.field, autoinc.initial, true);
rs = await insert(coll, value, opt);
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
}
return dtd.promise();
},
Update: async function (name, select, value, opt) {
const THIS = this,
dtd = jBD.Deferred(true),
fmtOpt = opt => {
let r = opt.find;
if (opt.upsert) opt.upsert = true;
else delete opt.upsert;
if (opt.safe) opt.safe = true;
else delete opt.safe;
if (opt.find) {
if (!opt.sort) delete opt.sort;
if (opt.new === false) delete opt.new;
else opt.new = true;
}
else {
if (opt.multi === false) delete opt.multi;
else opt.multi = true;
}
delete opt.find;
return r;
};
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs;
if (fmtOpt(opt = opt || {})) {
rs = await findAndModify(coll, select || {}, opt.sort || {_id: 1}, value, opt);
}
else {
rs = await update(coll, select || {}, value, opt);
rs = rs.result;
rs.value = rs.upserted || null;
}
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Remove: async function (name, select) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = remove(coll, select || {}, {safe: true});
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Init: async function (name, empty = true) {
const THIS = this,
dtd = jBD.Deferred(true);
name = jBD.isArray(name) ? name : [name];
empty = empty === true;
try {
let count = name.length,
db = await THIS.dbOpen(),
coll;
for (let i = 0; i < count; i++) {
coll = await THIS.dbOper(db, name[i]);
await insert(coll, {__init_temp: 1}, {safe: true});
await remove(coll, empty ? {} : {__init_temp: 1}, {safe: true});
}
dtd.resolve(db);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Drop: async function (name) {
const THIS = this,
dtd = jBD.Deferred(true);
name = jBD.isArray(name) ? name : [name];
try {
let count = name.length,
db = await THIS.dbOpen(),
coll;
for (let i = 0; i < count; i++) {
coll = await THIS.dbOper(db, name[i]);
await drop();
}
dtd.resolve(db);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Aggregate: async function (name, ...opt) {
const THIS = this,
dtd = jBD.Deferred(true);
if (opt.length < 1) ERROR(dtd, 200, "参数不完整");
else {
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await aggregate(coll, opt);
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
}
return dtd.promise();
},
MapReduce: async function (name, mrf, opt) {
const THIS = this,
dtd = jBD.Deferred(true),
chk = obj => jBD.isFunction(obj) || (jBD.isString(obj) && /^function/i.test(obj)) ? obj : null;
mrf = jBD.Conver.toArray(mrf, null, null);
mrf[0] = chk(mrf[0]);
mrf[1] = chk(mrf[1]);
mrf[2] = chk(mrf[2]);
opt = opt || {};
if (!mrf[0] || !mrf[1]) ERROR(dtd, 200, "参数不完整");
else {
try {
if (mrf[2]) opt.finalize = mrf[2];
if (!opt.out) opt.out = {inline: 1};
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs;
coll = await mapReduce(coll, mrf[0], mrf[1], opt);
if (opt.out.inline === 1) dtd.resolve(coll);
else {
rs = await find(coll, {}, {});
dtd.resolve(rs);
}
}
catch (e) {
dtd.reject(e);
}
}
return dtd.promise()
},
AutoInc: async function (field, initial, inc) {
const THIS = this,
dtd = jBD.Deferred(true);
field = jBD.isString(field) ? field : "id";
initial = jBD.isNumber(initial, {int: true}) ? initial : 0;
if (initial < 1) initial = 1;
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, "AutoIncrement"),
rs,
select = {},
value = {};
select[field] = {"$exists": true};
if (inc === true) {
value["$inc"] = {};
value["$inc"][field] = 1;
rs = await findAndModify(coll, select, {}, value, {safe: true, new: true});
rs = rs.value;
}
else {
rs = await findOne(coll, select, {safe: true});
}
if (rs) dtd.resolve(rs);
else {
select[field] = initial;
rs = await insert(coll, select, {safe: true});
dtd.resolve(rs[0][field]);
}
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
}
});
return Mongo;
})(DB, STATE, ERROR);
API.Redis = (function (DB, STATE, ERROR) {
let Redis, REDIS;
const get = async (coll, key) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.get(key);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 211, e.message || "");
}
return dtd.promise();
},
set = async (coll, key, value) => {
let dtd = jBD.Deferred(true);
try {
let rs = await coll.set(key, value);
dtd.resolve(rs);
}
catch (e) {
ERROR(dtd, 211, e.message || "");
}
return dtd.promise();
};
Redis = DB.extend({
className: "Redis",
PORT: 6379,
create: function (cfg) {
this.super("create", cfg);
if (!REDIS) REDIS = require("ioredis");
this.connectInfo.protocol = "redis";
this.connectString = this.connectInfo.build();
},
dbConnect: async function () {
const THIS = this,
INFO = THIS.connectInfo,
dtd = jBD.Deferred(true);
THIS.state = STATE.Opening;
try {
if (!THIS.oper) THIS.oper = REDIS.createClient(THIS.connectString);
THIS.oper.on("error", err => {
INFO.cite = 0;
this.state = STATE.Closed;
});
if (INFO.pwd) THIS.oper.auth(INFO.pwd);
dtd.resolve(THIS.oper);
}
catch (e) {
ERROR(dtd, 200, e.message || "");
}
return dtd.promise();
},
dbClose: function (force, done) {
const THIS = this;
if (THIS.oper) {
switch (THIS.state) {
case STATE.Opened:
case STATE.Opening:
THIS.oper.end(true);
default:
THIS.state = STATE.Closed;
break;
}
}
if (jBD.isFunction(done)) done();
},
dbOper: async function (db, name) {
const dtd = jBD.Deferred(true);
try {
// let coll = await db.select(name);
dtd.resolve(db);
}
catch (e) {
ERROR(dtd, 201, e.message || "");
}
return dtd.promise();
},
// ==============================================================
Get: async function (name, key) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await get(coll, key);
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
},
Set: async function (name, key) {
const THIS = this,
dtd = jBD.Deferred(true);
try {
let db = await THIS.dbOpen(),
coll = await THIS.dbOper(db, name),
rs = await set(coll, key);
dtd.resolve(rs);
}
catch (e) {
dtd.reject(e);
}
return dtd.promise();
}
});
return Redis;
})(DB, STATE, ERROR);
return API;
}, {module: module, exports: this}, ["String"], "DB");