UNPKG

@eyugame/dao

Version:

A modelling tool for RealtimeDatabase(firebase) & Dynamodb(Amazon) & Redis

1,047 lines (980 loc) 39.7 kB
/** * Redis-dao define function * @author clay */ 'use strict'; const joi = require('joi'); const redis = require('redis'); const utils = require('../utils'); const PREFIX = 'ent'; //域名称 const FIELD_ENTITY = ''; /** * 添加默认的版本号 * @param {object} entity */ function appendDataVersion(entity) { entity[utils.ENTITY_ATTR_VERSION] = 1; } /** * 构建 哈希 键值 * 数据格式 * 1: 只有 hashKey * set entityName_hashKey {'name':'hello',age:22} * 2: hashKey rangeKey * hset entityName_rangeKey hashKey {'name':'hello',age:22} */ function makeRedisHashKey(partitionKey, table, hashKeyName, rangeKeyName) { let result; const hashKeyValue = partitionKey[hashKeyName]; if (rangeKeyName) { let rangeKeyValue = partitionKey[rangeKeyName]; result = `${PREFIX}_${DAO_CONFIG.HASH_KEY_PREFIX}:${table}:${rangeKeyValue}`; } else { result = `${PREFIX}_${DAO_CONFIG.HASH_KEY_PREFIX}:${table}:${hashKeyValue}`; } return result; } /** * 构建 哈希 键值 * @param {string} table * @param {string} rangeKey */ function makeRedisRangeKey(table, rangeKey) { return `${PREFIX}_${DAO_CONFIG.HASH_KEY_PREFIX}:${table}:${rangeKey}`; } /** * 对象 => 数组 * ps: {'name':'harry','age':22} => ['name':'harry','age':22] * @param {object} obj */ function objectToArray(obj) { let result = []; for (let [k, v] of Object.entries(obj)) { result.push(k); result.push(v); } return result; } /** * 构建 指令hmset 需要的参数 * @param {string} redisHashKey * @param {object} entityData */ function makeHMSetArguments(redisHashKey, entityData) { let result = []; result.push(redisHashKey); for (let [k, v] of Object.entries(entityData)) { result.push(k); result.push(v); } if (DEBUG) { result.push(redis.print); } return result; } /** * 处理 GET/HGET 结果 * @param {Error} err * @param {object} res * @param {function} resolve * @param {function} reject */ function handleGetResult(err, reply, resolve, reject) { if (err) { console.log(`实体${ableName}创建对象异常${err}`); reject(err); return; } if (!reply) { resolve(null); } let value = JSON.parse(reply); let entity = new self.prototype.constructor(value); entity = utils.wrapperEntity(entity); resolve(entity); } /** * Object => JSON (Delete key when value is null) * @param {object} obj */ function dumpsJson(obj) { return JSON.stringify(obj, (ignoredKey, v) => { if (v !== null) { return v; } }) } const redisClient = redis.createClient(DAO_CONFIG.connect.port, DAO_CONFIG.connect.host, { retry_strategy: function (options) { if (options.error && options.error.code === "ECONNREFUSED") { // End reconnecting on a specific error and flush all commands with // a individual error return new Error("The server refused the connection"); } if (options.total_retry_time > 1000 * 60 * 60) { // End reconnecting after a specific timeout and flush all commands // with a individual error return new Error("Retry time exhausted"); } if (options.attempt > 10) { // End reconnecting with built in error return undefined; } // reconnect after return Math.min(options.attempt * 100, 3000); }, }); if (DAO_CONFIG.auth) { redisClient.auth(DAO_CONFIG.auth.password, function (authResult) { if (authResult) { console.log('Redis授权结果:', authResult); throw new Error(`Redis授权异常`); } }); } redisClient.on('error', function (err) { console.log('Redis-dao创建连接异常...', err); }); /** * 加载对象 (static) * @param {object} partitionKey */ function load(partitionKey) { const tableName = this.prototype._schema.tableName; //检查 hashKey const hashKeyName = this.prototype._schema.hashKey; const hashKeyValue = partitionKey[hashKeyName]; if (!hashKeyValue) { return Promise.reject(new Error(`实体: ${tableName}加载异常, 无hashKey`)); } const rangeKeyName = this.prototype._schema.rangeKey; const rangeKeyValue = partitionKey[rangeKeyName]; if (rangeKeyName && !rangeKeyValue) { return Promise.reject(new Error(`实体: ${tableName}加载异常, 无rangeKey`)); } const redisHashKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); if (DEBUG) { console.log(`实体: ${tableName}load hash key: ${redisHashKey}`) } const self = this; return new Promise((resolve, reject) => { let callbackFunc = (err, value, resolve, reject) => { if (err) { console.log(`实体${tableName}加载对象异常: ${err}`); reject(err); return; } if (!value) { resolve(null); return; } if (DEBUG) { console.log(`实体${tableName}加载对象原始数据: ${value}`) } let entityData; try { entityData = JSON.parse(value); } catch (jsonError) { console.log(`实体${tableName}加载对象反序列化json异常: ${jsonError}`); reject(jsonError); return; } entityData[hashKeyName] = hashKeyValue; if (rangeKeyName) { entityData[rangeKeyName] = rangeKeyValue; } let entity = new self.prototype.constructor(entityData); entity = utils.wrapperEntity(entity); resolve(entity); }; if (rangeKeyName) { redisClient.hget(redisHashKey, hashKeyValue, function (err, value) { callbackFunc(err, value, resolve, reject); }); } else { redisClient.get(redisHashKey, function (err, value) { callbackFunc(err, value, resolve, reject); }) } }); } /** * 创建对象 (static) * @param {object} data */ function create(data) { const tableName = this.prototype._schema.tableName; //检查schema const { error, value } = joi.validate(data, this.prototype._schema.schema); if (error) { throw new Error(`实体${tableName}创建异常, 数据校验失败: ${JSON.stringify(error.details)}, 实体数据:${JSON.stringify(data)}`); } const partitionKey = {}; const hashKeyName = this.prototype._schema.hashKey; const rangeKeyName = this.prototype._schema.rangeKey; partitionKey[hashKeyName] = data[hashKeyName]; if (rangeKeyName) { partitionKey[rangeKeyName] = data[rangeKeyName]; } const redisHashKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); const self = this; return new Promise((resolve, reject) => { appendDataVersion(value); let redisData = dumpsJson(value); if (rangeKeyName) { const hashKeyValue = data[hashKeyName]; redisClient.hsetnx(redisHashKey, hashKeyValue, redisData, function (err, res) { if (err) { console.log(`实体${ableName}创建对象异常${err}`); reject(err); return; } let entity = new self.prototype.constructor(value); entity = utils.wrapperEntity(entity); resolve(entity); }); } else { redisClient.setnx(redisHashKey, redisData, function (err, res) { if (err) { console.log(`实体${ableName}创建对象异常${err}`); reject(err); return; } let entity = new self.prototype.constructor(value); entity = utils.wrapperEntity(entity); resolve(entity); }) } }); } /** * 加载or创建对象 (static) * @param {object} partitionKey * @param {function} builder */ async function loadOrCreate(partitionKey, builder) { try { let result = await this.load(partitionKey); if (!result) { let data; if (builder) { if (utils.isAsyncFunc(builder)) { data = await builder(partitionKey); } else { data = builder(partitionKey); } } else { //使用默认的builder or schema定义的builder data = this.prototype._builder(partitionKey); } result = await this.create(data); } return result; } catch (err) { console.log('LoadOrCreate异常:', err); return Promise.reject(new Error(`LoadOrCreate实体${this.prototype._schema.tableName}异常`)); } } /** * 实体是否存在 (static) * @param {object}} partitionKey */ async function exists(partitionKey) { return this.load(partitionKey).then(entity => { if (entity) { return true; } return false; }).catch(ex => { console.log('exists', ex); return Promise.reject(new Error(`实体${this.prototype._target.tableName()}加载异常${JSON.stringify(ex)}`)); }); } /** * 修改实体数值字段 (static, atomic) * @param {object} partitionKey * @param {object} keyValues 例1: {COIN: 10, DIAMOND: -10} 例2: {COIN: -10, DIAMOND: -10} * @param {object} condition 例1: {LIFE: {min: 0, max: 200}}, 例2: {DIAMOND: {min: 0}} */ function alter(partitionKey, keyValues, condition) { const tableName = this.prototype._schema.tableName; const hashKeyName = this.prototype._schema.hashKey; const rangeKeyName = this.prototype._schema.rangeKey; if (!partitionKey[hashKeyName]) { return Promise.reject(new Error(`实体${tableName}, alter操作参数无hashKey${hashKeyName}`)); } if (rangeKeyName) { if (!partitionKey[rangeKeyName]) { return Promise.reject(new Error(`实体${tableName}, alter操作参数无rangeKey${rangeKeyName}`)); } } //检查 keyValue if (utils.isEmptyObject(keyValues)) { return Promise.reject(new Error(`实体${tableName}, alter操作未提供要更新的属性, ${JSON.stringify(keyValues)}`)); } for (let [k, v] of Object.entries(keyValues)) { if (!utils.isNumber(v)) { return Promise.reject(new Error(`实体${tableName}, alter操作, key: ${k}, value: ${v} 非数字`)); } if (v === 0) { return Promise.reject(new Error(`实体${tableName}, alter操作, key: ${k}, value: ${v} === 0`)); } //检查schema let schemaType = this.prototype._schema.schema[k]; //todo:lg 判断 joi类型 if (!schemaType) { return Promise.reject(new Error(`实体${tableName}, alter操作, key: ${k}无schema定义`)); } } const redisHashKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); const self = this; return new Promise((resolve, reject) => { redisClient.watch(redisHashKey, function (watchErr) { if (watchErr) { console.log(`实体${tableName}alter失败: [添加监控异常], ${watchErr}`); reject(watchErr); return; } let oldEntity = {}; let callbackFunc = (getError, getResult, resolve, reject) => { if (getError) { console.log(`实体${tableName}alter失败: [获取原始数据异常], ${getError}`); reject(getError); return; } if (getResult) { try { oldEntity = JSON.parse(getResult); } catch (jsonError) { console.log(`实体${tableName}alter失败: [原始数据JSON.parse异常], ${jsonError}`); reject(jsonError); return; } } for (let [k, v] of Object.entries(keyValues)) { let oldValue = oldEntity[k] || 0; //检查condition if (condition) { let currentLimit = condition[k]; if (!currentLimit) { continue; } if (v > 0) { //增加 检查最大值 if ('max' in currentLimit) { if (!utils.isNumber(currentLimit.max)) { reject(new Error(`实体${tableName}, alter操作, key: ${k}, max: ${currentLimit.max} 非数字`)); return; } //更新的值 不能大于 上限 if (v > currentLimit.max) { reject(new Error(`实体${tableName}, alter操作, key: ${k}, 更新值: ${v} > 上限: ${currentLimit.max}`)); return; } if (oldValue + v > currentLimit.max) { if (DEBUG) { console.log(`实体${tableName}, alter操作, key: ${k}, 更新值超出上限 old${oldValue}, changed: ${v}: ${currentLimit.max}`); } reject(new Error(`实体${tableName}, alter操作, key: ${k}, 更新值超出上限: ${currentLimit.max}`)); return; } } } else { //扣除 检查最小值 if ('min' in currentLimit) { if (!utils.isNumber(currentLimit.min)) { return Promise.reject(new Error(`实体${tableName}, alter操作, key: ${k}, min: ${currentLimit.min} 非数字`)); } if (oldValue + v < currentLimit.min) { if (DEBUG) { console.log(`实体${tableName}, alter操作, key: ${k}, 更新值超出下限 old${oldValue}, changed: ${v}: ${currentLimit.max}`); } reject(new Error(`实体${tableName}, alter操作, key: ${k}, 更新值超出下限: ${currentLimit.max}`)); return; } } } } oldEntity[k] = oldValue + v; } //更新版本号 let oldVersion = oldEntity[utils.ENTITY_ATTR_VERSION] || 0; oldEntity[utils.ENTITY_ATTR_VERSION] = oldVersion + 1; //添加 partitionKey Object.assign(oldEntity, partitionKey); let action; if (rangeKeyName) { const hashKeyValue = partitionKey[hashKeyName]; action = redisClient.multi().hset(redisHashKey, hashKeyValue, dumpsJson(oldEntity)); } else { action = redisClient.multi().set(redisHashKey, dumpsJson(oldEntity)); } action.exec(function (execError, results) { if (execError) { console.log(`实体${tableName}alter失败: [执行set || hset异常], ${execError}`); reject(`实体${tableName}alter失败: [执行set || hset异常], ${execError}`); return; } //数据冲突 事务放弃 if (!results) { console.log(`实体${tableName}alter失败: [操作冲突]]`); reject(`实体${tableName}alter失败: [操作冲突]`); return; } if (DEBUG) { console.log(`实体${tableName}alter结果:${results}`); } let entity = new self.prototype.constructor(oldEntity); resolve(utils.wrapperEntity(entity)); }); }; if (rangeKeyName) { redisClient.hget(redisHashKey, partitionKey[hashKeyName], function (getError, getResult) { callbackFunc(getError, getResult, resolve, reject); }); } else { redisClient.get(redisHashKey, function (getError, getResult) { callbackFunc(getError, getResult, resolve, reject); }) } }) }); } /** * 删除实体 (static) * @param partitionKey * @returns {Promise<any>} */ function destroy(partitionKey) { const tableName = this.prototype._schema.tableName; //检查 hashKey const schemaHashKey = this.prototype._schema.hashKey; if (!partitionKey[schemaHashKey]) { return Promise.reject(new Error(`实体: ${tableName}删除异常, 无hashKey`)); } const rangeKeyName = this.prototype._schema.rangeKey; const rangeKeyValue = partitionKey[rangeKeyName]; if (rangeKeyName && !rangeKeyValue) { return Promise.reject(new Error(`实体: ${tableName}删除异常, 无rangeKey`)); } const redisHashKey = makeRedisHashKey(partitionKey, tableName, schemaHashKey, rangeKeyName); return new Promise((resolve, reject) => { if (rangeKeyName) { redisClient.hdel(redisHashKey, fieldName, function (err, count) { if (err) { console.log(`实体: ${tableName}删除异常, ${err}`); reject(`实体: ${tableName}删除异常, ${err}`); return; } if (DEBUG) { console.log(`实体: ${tableName}成功删除数量${count}`); } resolve(); }); } else { redisClient.del(redisHashKey, function (err, count) { if (err) { console.log(`实体: ${tableName}删除异常, ${err}`); reject(`实体: ${tableName}删除异常, ${err}`); return; } if (DEBUG) { console.log(`实体: ${tableName}成功删除数量${count}`); } resolve(); }); } }); } /** * 更新实体 (static, atomic) * @param {object} entityUpdatedData * @param options {transaction:true} */ function update(entityUpdatedData, options) { const tableName = this.prototype._schema.tableName; const partitionKey = {}; const hashKeyName = this.prototype._schema.hashKey; const rangeKeyName = this.prototype._schema.rangeKey; const hashKeyValue = entityUpdatedData[hashKeyName]; if (!hashKeyValue) { return Promise.reject(new Error(`实体${tableName}更新参数无hashKey${hashKeyName}`)); } partitionKey[hashKeyName] = hashKeyValue; if (rangeKeyName) { const rangeKeyValue = entityUpdatedData[rangeKeyName]; if (!rangeKeyValue) { return Promise.reject(new Error(`实体${tableName}更新参数无rangeKey`)); } partitionKey[rangeKeyName] = rangeKeyValue; } //检查是否有多余的属性 for (let key in entityUpdatedData) { if (key in this.prototype._schema.schema) { continue; } return Promise.reject(new Error(`实体${tableName}要更新的数据, 非法的键值${key}`)); } const redisHashKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); return new Promise((resolve, reject) => { redisClient.watch(redisHashKey, function (watchError) { if (watchError) { console.log(`实体${tableName}更新失败: [添加监控异常], ${watchError}`); reject(watchError); return; } let callbackFunc = (getError, result, resolve, reject) => { if (getError) { console.log(`实体${tableName}更新失败: [获取原始数据异常], ${getError}`); reject(watchError); return; } let oldEntity = {}; if (result) { try { oldEntity = JSON.parse(result); } catch (jsonError) { console.log(`实体${tableName}更新失败: [原始数据JSON.parse异常], ${jsonError}`); reject(jsonError); return; } } //检查版本号 let newVersion; if (options && options.transaction) { let oldVersion = oldEntity[utils.ENTITY_ATTR_VERSION] || 0; let updateVersion = entityUpdatedData[utils.ENTITY_ATTR_VERSION] || 0; if (oldVersion !== updateVersion) { console.log(`实体${tableName}更新失败: [版本不一致], old-version: ${oldVersion}, update-version: ${updateVersion}`); reject(`实体${tableName}更新失败: [版本不一致]`); return; } newVersion = oldVersion + 1; } Object.assign(oldEntity, entityUpdatedData); if (newVersion) { oldEntity[utils.ENTITY_ATTR_VERSION] = newVersion; } let action; if (rangeKeyName) { action = redisClient.multi().hset(redisHashKey, hashKeyValue, dumpsJson(oldEntity)); } else { action = redisClient.multi().set(redisHashKey, dumpsJson(oldEntity)); } action.exec(function (execError, results) { if (execError) { console.log(`实体${tableName}更新失败: [执行hset异常], ${execError}`); reject(`实体${tableName}更新失败: [执行hset异常], ${execError}`); return; } //数据冲突 事务放弃 if (!results) { console.log(`实体${tableName}更新失败: [操作冲突]]`); reject(`实体${tableName}更新失败: [操作冲突]`); return; } if (DEBUG) { console.log(`实体${tableName}更新结果:${results}`); } resolve(); }) }; if (rangeKeyName) { redisClient.hget(redisHashKey, hashKeyValue, function (getError, result) { callbackFunc(getError, result, resolve, reject); }) } else { redisClient.get(redisHashKey, function (getError, result) { callbackFunc(getError, result, resolve, reject); }) } }); }); } /** * 加载所有 (static) * @param {string} rangeKey * @param {object} options not support */ function listAll(rangeKey, options) { if (options) { return Promise.reject(new Error('Redis-DAO不支持options参数')); } const tableName = this.prototype._schema.tableName; const rangeKeyName = this.prototype._schema.rangeKey; if (!rangeKeyName) { return Promise.reject(new Error(`实体${tableName}不支持listAll查询`)); } const selfProto = this.prototype; const redisHashKey = makeRedisRangeKey(tableName, rangeKey); return new Promise((resolve, reject) => { redisClient.hgetall(redisHashKey, function (err, items) { if (err) { console.log(`实体${tableName}, listAll异常${err}`); reject(`实体${tableName}, listAll异常${err}`); return; } if (!items) { resolve([]); return; } const result = []; for (let entityJson of Object.values(items)) { let entityObj = JSON.parse(entityJson); let entity = new selfProto.constructor(entityObj); result.push(utils.wrapperEntity(entity)); } resolve(result); }); }); } /** * 批量获取 (static) 当只有hashKey时, 可以简写 ['id1','id2'] * @param {arry} keys ['id1','id2'], [{hashKey:'1', rangeKey: '1'},{hashKey:'2', rangeKey: '2'}] */ function batchGet(keys) { const selfProto = this.prototype; const tableName = selfProto._schema.tableName; const hashKeyName = selfProto._schema.hashKey; const rangeKeyName = selfProto._schema.rangeKey; let queryKeys = []; let redisKeys = []; for (let itemKey of keys) { let partitionKey = {}; let keyType = utils.getType(itemKey); let hashKeyValue; let rangeKeyValue; if (rangeKeyName) { if (keyType != 'object') { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } hashKeyValue = itemKey[hashKeyName]; rangeKeyValue = itemKey[rangeKeyName]; if (!hashKeyValue) { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } if (!rangeKeyValue) { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } partitionKey[hashKeyName] = hashKeyValue; partitionKey[rangeKeyName] = rangeKeyValue; } else { if (keyType == 'object') { hashKeyValue = itemKey[hashKeyName]; if (!hashKeyValue) { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } partitionKey[hashKeyName] = itemKey[hashKeyName]; } else { hashKeyValue = itemKey; partitionKey[hashKeyName] = itemKey; } } let tmpRedisKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); queryKeys.push({ redisKey: tmpRedisKey, hashKey: hashKeyValue, rangeKey: rangeKeyValue }) redisKeys.push(tmpRedisKey); } const self = this; return new Promise((resolve, reject) => { if (rangeKeyName) { let action = redisClient.multi(); for (let itemQuery of queryKeys) { action.hget(itemQuery.redisKey, itemQuery.hashKey); } action.exec((err, reply) => { if (err) { console.log(`实体: ${tableName}, 批量获取执行异常:`, err); reject(err); return; } let result = []; for (let item of reply) { if (item == null) { continue; } let entity = new self.prototype.constructor(item); result.push(utils.wrapperEntity(entity)); } resolve(result); }) } else { redisClient.mget(redisKeys, function (err, reply) { if (err) { console.log(`实体: ${tableName}, 批量获取执行异常:`, err); reject(err); return; } let result = []; for (let item of reply) { if (item == null) { continue; } let entity = new self.prototype.constructor(item); result.push(utils.wrapperEntity(entity)); } resolve(result); }); } }); } /** * 保存对象 (instance, atomic) * @param {object} options */ function save(options) { const selfProto = Object.getPrototypeOf(this); const self = this; const partitionKey = {}; const hashKeyName = selfProto._schema.hashKey; const rangeKeyName = selfProto._schema.rangeKey; const hashKeyValue = this._entity[hashKeyName]; partitionKey[hashKeyName] = hashKeyValue; let rangeKeyValue; if (rangeKeyName) { rangeKeyValue = this._entity[rangeKeyName]; partitionKey[rangeKeyName] = rangeKeyValue; } const tableName = selfProto._schema.tableName; const redisHashKey = makeRedisHashKey(partitionKey, tableName, hashKeyName, rangeKeyName); return new Promise((resolve, reject) => { //使用事务 if (options && options.transaction) { redisClient.watch(redisHashKey, function (watchError) { if (watchError) { console.log(`实体${tableName}保存失败: [添加监控异常], ${watchError}`); reject(watchError); return; } let callbackFunc = (getError, result, reject) => { if (getError) { console.log(`实体${tableName}保存失败: [获取原始数据异常], ${getError}`); reject(watchError); return false; } let oldEntity; try { oldEntity = JSON.parse(result); } catch (jsonError) { console.log(`实体${tableName}保存失败: [原始数据JSON.parse异常], ${jsonError}`); reject(jsonError); return false; } //检查版本号 let oldVersion = oldEntity[utils.ENTITY_ATTR_VERSION] || 0; let updateVersion = self._entity[utils.ENTITY_ATTR_VERSION] || 0; if (oldVersion !== updateVersion) { console.log(`实体${tableName}保存失败: [版本不一致], old-version: ${oldVersion}, update-version: ${updateVersion}`); reject(`实体${tableName}保存失败: [版本不一致]`); return false; } return true; }; if (rangeKeyName) { redisClient.hget(redisHashKey, hashKeyValue, function (getError, result) { if (!callbackFunc(getError, result, reject)) { return; } let action; if (rangeKeyName) { action = redisClient.multi().hset(redisHashKey, hashKeyValue, dumpsJson(self._entity)); } else { action = redisClient.multi().set(redisHashKey, dumpsJson(self._entity)); } action.exec(function (execError, results) { if (execError) { console.log(`实体${tableName}保存失败: [执行hset异常], ${execError}`); reject(`实体${tableName}保存失败: [执行hset异常], ${execError}`); return; } //数据冲突 事务放弃 if (!results) { console.log(`实体${tableName}保存失败: [操作冲突]]`); reject(`实体${tableName}保存失败: [操作冲突]`); return; } if (DEBUG) { console.log(`实体${tableName}保存结果:${results}`); } resolve(); }); }) } else { redisClient.get(redisHashKey, function (getError, result) { if (!callbackFunc(getError, result, reject)) { return; } let action; if (rangeKeyName) { action = redisClient.multi().hset(redisHashKey, hashKeyValue, dumpsJson(self._entity)); } else { action = redisClient.multi().set(redisHashKey, dumpsJson(self._entity)); } action.exec(function (execError, results) { if (execError) { console.log(`实体${tableName}保存失败: [执行hset异常], ${execError}`); reject(`实体${tableName}保存失败: [执行hset异常], ${execError}`); return; } //数据冲突 事务放弃 if (!results) { console.log(`实体${tableName}保存失败: [操作冲突]]`); reject(`实体${tableName}保存失败: [操作冲突]`); return; } if (DEBUG) { console.log(`实体${tableName}保存结果:${results}`); } resolve(); }); }); } }); } else { if (rangeKeyName) { const hashKeyValue = this._entity[hashKeyName]; redisClient.hset(redisHashKey, hashKeyValue, dumpsJson(self._entity), function (err, res) { if (err) { console.log(`实体${tableName}保存异常, ${err}`); reject(err); return; } if (DEBUG) { console.log(`实体${tableName}保存成功, ${res}`); } resolve(); }) } else { redisClient.set(redisHashKey, dumpsJson(self._entity), function (err, res) { if (err) { console.log(`实体${tableName}保存异常, ${err}`); reject(err); return; } if (DEBUG) { console.log(`实体${tableName}保存成功, ${res}`); } resolve(); }); } } }); } /** * 删除实体 (instance) */ function remove() { const selfProto = Object.getPrototypeOf(this); const tableName = selfProto._schema.tableName; const partitionKey = {}; const hashKeyName = selfProto._schema.hashKey; const rangeKeyName = selfProto._schema.rangeKey; const hashKeyValue = this._entity[hashKeyName]; partitionKey[hashKeyName] = hashKeyValue; if (rangeKeyName) { partitionKey[rangeKeyName] = this._entity[rangeKeyName]; } const redisHashKey = makeRedisHashKey(partitionKey, selfProto._schema.tableName, hashKeyName, rangeKeyName); return new Promise((resolve, reject) => { if (rangeKeyName) { redisClient.hdel(redisHashKey, hashKeyValue, function (err, count) { if (err) { console.log(`实体: ${tableName}删除异常, ${err}`); reject(`实体: ${tableName}删除异常, ${err}`); return; } if (DEBUG) { console.log(`实体: ${tableName}成功删除数量${count}`); } resolve(); }); } else { redisClient.del(redisHashKey, function (err, count) { if (err) { console.log(`实体: ${tableName}删除异常, ${err}`); reject(`实体: ${tableName}删除异常, ${err}`); return; } if (DEBUG) { console.log(`实体: ${tableName}成功删除数量${count}`); } resolve(); }); } }); } /** * 定义Schema * @param {string} entityName * @param {object} schema */ const define = (entityName, schema) => { //检查Schema utils.checkSchema(entityName, schema); //构造函数 const entity = function (data) { //检查数据 const { error, value } = joi.validate(data, schema.schema); if (error) { throw new Error(`[模型${entityName}]原始数据${JSON.stringify(data)}验证异常: ${JSON.stringify(error.details)}`); } this._entity = value; } //添加 乐观锁版本号 if (!schema.schema[utils.ENTITY_ATTR_VERSION]) { schema.schema[utils.ENTITY_ATTR_VERSION] = joi.number().positive(); } if (!schema.tableName) { schema.tableName = entityName; } //定义 prototype entity.prototype._schema = schema; if (schema[utils.SCHEMA_BUILDER]) { entity.prototype._builder = schema[utils.SCHEMA_BUILDER]; } else { entity.prototype._builder = utils.entityBuilder; } //定义 prototype 方法 entity.prototype.save = save; entity.prototype.remove = remove; entity.prototype.toVO = utils.toVO; //添加 prototype 自定义方法 const instanceMethods = schema[utils.SCHEMA_INSTANCE_METHODS]; if (instanceMethods) { for (let [fnName, fn] of Object.entries(instanceMethods)) { entity.prototype[fnName] = fn; } } //定义 静态方法 entity.load = load; entity.create = create; entity.update = update; entity.destroy = destroy; entity.exists = exists; entity.loadOrCreate = loadOrCreate; entity.listAll = listAll; entity.alter = alter; entity.batchGet = batchGet; //添加 copyVersion方法 entity.copyAttrVersion = function (updateData, sourceEntity) { if (utils.ENTITY_ATTR_VERSION in sourceEntity._entity) { updateData[utils.ENTITY_ATTR_VERSION] = sourceEntity._entity[utils.ENTITY_ATTR_VERSION]; } }; return entity; } module.exports = define;