@eyugame/dao
Version:
A modelling tool for RealtimeDatabase(firebase) & Dynamodb(Amazon) & Redis
1,047 lines (980 loc) • 39.7 kB
JavaScript
/**
* Redis-dao define function
* @author clay
*/
;
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;