UNPKG

@eyugame/dao

Version:

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

752 lines (694 loc) 26.3 kB
/** * Firebase define */ 'use strict'; const joi = require('joi'); const admin = require('firebase-admin'); const utils = require('../utils'); /** * 构建Ref * @param {Object} partitionKey * @param {String} table * @param {String} hashKeyName * @param {String} rangeKeyName */ function makeRef(partitionKey, table, hashKeyName, rangeKeyName) { let result; const hashKey = partitionKey[hashKeyName]; if (rangeKeyName) { const rangeKey = partitionKey[rangeKeyName]; result = `${DAO_CONFIG.rootPath}/${table}/${rangeKey}/${hashKey}`; } else { result = `${DAO_CONFIG.rootPath}/${table}/${hashKey}`; } if (DEBUG) { console.log('路径:', result); } return result; } /** * 构建rangeKey Ref * @param table * @param rangeKey * @returns {string} */ function makeRangeKeyRef(table, rangeKey) { const result = `${DAO_CONFIG.rootPath}/${table}/${rangeKey}`; if (DEBUG) { console.log(`路径: ${result}`); } return result; } /** * 构建Group Ref(用于batchGet) */ function makeGroupRef(table) { return `${DAO_CONFIG.rootPath}/${table}/`; } /** * 获取对象 * @param {Object} partitionKey */ function load(partitionKey) { //检查 hashKey const schemaHashKey = this.prototype._schema.hashKey; if (!partitionKey[schemaHashKey]) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}获取异常, 无hashKey`)); } const rangeKeyName = this.prototype._schema.rangeKey; const rangeKeyValue = partitionKey[rangeKeyName]; if (rangeKeyName && !rangeKeyValue) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}获取异常, 无rangeKey`)); } const ref = makeRef(partitionKey, this.prototype._schema.tableName, schemaHashKey, rangeKeyName); const self = this; return admin.database().ref(ref).once('value') .then(snapshot => { if (!snapshot.exists()) { return null; } const val = snapshot.val(); if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}获取对象原始数据${JSON.stringify(val)}`) } let entityValue = { ...partitionKey }; Object.assign(entityValue, val); const entity = new self.prototype.constructor(entityValue); const result = utils.wrapperEntity(entity); return result; }); } /** * 创建实体 * @param {string} modelName * @param {object} schema */ function create(data) { //检查schema const { error, value } = joi.validate(data, this.prototype._schema.schema); if (error) { throw new Error(`实体${this.prototype._schema.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 ref = makeRef(partitionKey, this.prototype._schema.tableName, hashKeyName, rangeKeyName); //过滤字段 let copyEntity; if (this.prototype._schema.FIREBASE_IGNORED) { copyEntity = {}; for (let key in value) { if (this.prototype._schema.FIREBASE_IGNORED.includes(key)) { continue; } copyEntity[key] = value[key]; } } else { copyEntity = value; } const self = this; return admin.database().ref(ref).set(copyEntity) .then(() => { const entity = new self.prototype.constructor(value); return utils.wrapperEntity(entity); }); } /** * 加载or创建 * @param {Object} partitionKey * @param {Object} 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 (ex) { console.log('LoadOrCreate', ex); return Promise.reject(new Error(`LoadOrCreate实体${this.prototype._schema.tableName}异常, ${JSON.stringify(ex)}`)); } } /** * 实体是否存在(静态方法) * @param {object} partitionKey */ function exists(partitionKey) { //检查 hashKey const schemaHashKey = this.prototype._schema.hashKey; if (!partitionKey[schemaHashKey]) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}获取异常, 无hashKey`)); } const rangeKeyName = this.prototype._schema.rangeKey; const rangeKeyValue = partitionKey[rangeKeyName]; if (rangeKeyName && !rangeKeyValue) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}获取异常, 无rangeKey`)); } const ref = makeRef(partitionKey, this.prototype._schema.tableName, schemaHashKey, rangeKeyName); return admin.database().ref(ref).once('value') .then(snapshot => { if (!snapshot.exists()) { return false; } return true; }); } /** * 更新实体 指定属性 * @param {Object} entitySomeData * @param {Object} options */ function update(entitySomeData, options) { const partitionKey = {}; const hashKeyName = this.prototype._schema.hashKey; const rangeKeyName = this.prototype._schema.rangeKey; const hashKeyValue = entitySomeData[hashKeyName]; if (!hashKeyValue) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}更新参数无hashKey${hashKeyName}`)); } partitionKey[hashKeyName] = hashKeyValue; if (rangeKeyName) { const rangeKeyValue = entitySomeData[rangeKeyName]; if (!rangeKeyValue) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}更新参数无rangeKey`)); } partitionKey[rangeKeyName] = rangeKeyValue; } const ref = makeRef(partitionKey, this.prototype._schema.tableName, hashKeyName, rangeKeyName); let copyEntity; if (this.prototype._schema.FIREBASE_IGNORED) { copyEntity = {}; for (let key in entitySomeData) { //过滤 无需保存的属性 if (this.prototype._schema.FIREBASE_IGNORED.includes(key)) { continue; } copyEntity[key] = entitySomeData[key]; } } else { copyEntity = { ...entitySomeData }; } const selfProto = this.prototype; //使用事务 if (options && options.transaction) { return admin.database().ref(ref).transaction(val => { if (!val) { copyEntity[utils.ENTITY_ATTR_VERSION] = 1; return copyEntity; } //比较版本号 let dbVersion = val[utils.ENTITY_ATTR_VERSION] || 0; let entityVersion = entitySomeData[utils.ENTITY_ATTR_VERSION] || 0; if (dbVersion != entityVersion) { if (DEBUG) { console.log(`实体: ${selfProto._schema.tableName}, update操作, dbVersion: ${dbVersion}, entityVersion: ${entityVersion}`); } return; } let nextVersion = entityVersion + 1; entitySomeData[utils.ENTITY_ATTR_VERSION] = nextVersion; copyEntity[utils.ENTITY_ATTR_VERSION] = nextVersion; for (let key in copyEntity) { val[key] = copyEntity[key]; } return val; }).then(result => { if (!result.committed) { return Promise.reject(`实体${selfProto._schema.tableName}, 乐观锁事务失败`); } return Promise.resolve(); }, (error) => { return Promise.reject(error); }); } else { return admin.database().ref(ref).update(copyEntity).then(() => { return Promise.resolve(); }).catch(ex => Promise.reject(ex)); } } /** * 修改实体数值字段(静态方法, ACID) * @param partitionKey * @param keyValues 例1: {COIN: 10, DIAMOND: -10} 例2: {COIN: -10, DIAMOND: -10} * @param condition 例1: {LIFE: {min: 0, max: 200}}, 例2: {DIAMOND: {min: 0}} */ function alter(partitionKey, keyValues, condition) { const hashKeyName = this.prototype._schema.hashKey; const rangeKeyName = this.prototype._schema.rangeKey; if (!partitionKey[hashKeyName]) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作参数无hashKey${hashKeyName}`)); } if (rangeKeyName) { if (!partitionKey[rangeKeyName]) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作参数无rangeKey${rangeKeyName}`)); } } //检查 keyValue if (utils.isEmptyObject(keyValues)) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作未提供要更新的属性, ${JSON.stringify(keyValues)}`)); } for (let [k, v] of Object.entries(keyValues)) { if (!utils.isNumber(v)) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, value: ${v} 非数字`)); } if (v === 0) { return Promise.reject(new Error(`实体${this.prototype._schema.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(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}无schema定义`)); } } //检查condition 类型 if (condition) { for (let [k, v] of Object.entries(keyValues)) { let currentLimit = condition[k]; if (!currentLimit) { continue; } if (v > 0) { //增加 检查最大值 if ('max' in currentLimit) { if (!utils.isNumber(currentLimit.max)) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, max: ${currentLimit.max} 非数字`)); } //更新的值 不能大于 上限 if (v > currentLimit.max) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, 更新值: ${v} > 上限: ${currentLimit.max}`)); } } } else { //扣除 检查最小值 if ('min' in currentLimit) { if (!utils.isNumber(currentLimit.min)) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, min: ${currentLimit.min} 非数字`)); } } } } } if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}, alter操作, 更新数据: ${JSON.stringify(keyValues)}, 条件: ${JSON.stringify(condition)}`); } const ref = makeRef(partitionKey, this.prototype._schema.tableName, hashKeyName, rangeKeyName); const self = this; return admin.database().ref(ref).transaction(val => { if (!val) { return keyValues; } for (let [k, v] of Object.entries(keyValues)) { let oldValue = val[k] || 0; let newValue = oldValue + v; if (condition) { let currentLimit = condition[k]; if (currentLimit) { if (v > 0) { if (('max' in currentLimit) && newValue > currentLimit.max) { if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, old: ${oldValue}, new: ${newValue}, 超出上限${currentLimit.max}`); } return; } } else { if (('min' in currentLimit) && newValue < currentLimit.min) { if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, old: ${oldValue}, new: ${newValue}, 低出下限${currentLimit.min}`); } return; } } } } if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}, alter操作, key: ${k}, old: ${oldValue}, new: ${newValue}`); } val[k] = newValue; } //增加版本号 let oldVersion = val[utils.ENTITY_ATTR_VERSION] || 0; val[utils.ENTITY_ATTR_VERSION] = oldVersion + 1; return val; }).then(result => { console.log('alter 结果: ', JSON.stringify(result)); if (!result.committed) { return Promise.reject(`实体${self.prototype._schema.tableName}, alert事务失败`); } let entityVal = { ...partitionKey }; Object.assign(entityVal, result.snapshot.val()); let entity = new self.prototype.constructor(entityVal); return utils.wrapperEntity(entity); }, (error) => { return Promise.reject(error); }); } /** * 删除实体 (静态方法) * @param partitionKey * @returns {Promise<void>} */ async function destroy(partitionKey) { //检查 hashKey const schemaHashKey = this.prototype._schema.hashKey; if (!partitionKey[schemaHashKey]) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}删除异常, 无hashKey`)); } const rangeKeyName = this.prototype._schema.rangeKey; const rangeKeyValue = partitionKey[rangeKeyName]; if (rangeKeyName && !rangeKeyValue) { return Promise.reject(new Error(`模型: ${this.prototype._schema.tableName}删除异常, 无rangeKey`)); } const ref = makeRef(partitionKey, this.prototype._schema.tableName, schemaHashKey, rangeKeyName); return admin.database().ref(ref) .remove() .catch(ex => { console.log(`模型: ${this.prototype._schema.tableName}删除异常, ${JSON.stringify(ex)}`); return Promise.reject(ex); }) } /** * 查询所有 (schema必须有rangeKey) * @param rangeKey * @param options {limit:100,desc:false} * @returns {Promise<*|*>} */ async function listAll(rangeKey, options) { if (!this.prototype._schema.rangeKey) { return Promise.reject(new Error(`实体${this.prototype._schema.tableName}不支持listAll查询`)); } const ref = makeRangeKeyRef(this.prototype._schema.tableName, rangeKey); const selfProto = this.prototype; let query = admin.database().ref(ref); if (DEBUG) { console.log(`实体${this.prototype._schema.tableName}, listAll, options: ${JSON.stringify(options)}`); } if (options && options.limit) { if (typeof options.limit === 'string') { options.limit = Number.parseInt(options.limit); } if (typeof options.desc === 'string') { options.desc = (options.desc == 'true'); } if (options.desc) { query = query.limitToLast(options.limit); } else { query = query.limitToFirst(options.limit); } } return query .once('value') .then((snapshot) => { const entities = []; const hashKeyName = selfProto._schema.hashKey; const rangeKeyName = selfProto._schema.rangeKey; if (snapshot.exists()) { for (let [key, itemVal] of Object.entries(snapshot.val())) { //注入 hashKey if (!(hashKeyName in itemVal)) { itemVal[hashKeyName] = key; } //注入 rangeKey if (!(rangeKeyName in itemVal)) { itemVal[rangeKeyName] = rangeKey; } let entity = new selfProto.constructor(itemVal); entities.push(utils.wrapperEntity(entity)); } } return Promise.resolve(entities); }) .catch(ex => Promise.reject(ex)); } /** * 批量获取 (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; const queryKeys = []; for (let itemKey of keys) { let hashKeyValue; let keyType = utils.getType(itemKey); if (rangeKeyName) { if (keyType != 'object') { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } hashKeyValue = itemKey[hashKeyName]; const 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}, 批量获取, 键值非法`); } queryKeys.push({ child: `${rangeKeyValue}/${hashKeyValue}`, rangeKey: rangeKeyValue, hashKey: hashKeyValue }); } else { if (keyType == 'object') { hashKeyValue = itemKey[hashKeyName]; if (!hashKeyValue) { console.log(`实体: ${tableName}, 批量获取, 键值: ${JSON.stringify(itemKey)}非法`); throw new Error(`实体: ${tableName}, 批量获取, 键值非法`); } } else { hashKeyValue = itemKey; } queryKeys.push({ child: hashKeyValue, hashKey: hashKeyValue }); } } const groupRef = makeGroupRef(tableName); const children = admin.database().ref(groupRef); return new Promise(async (resolve, reject) => { try { let entities = []; for (let itemQuery of queryKeys) { let snapshot = await children.child(itemQuery.child).once('value'); if (!snapshot.exists()) { continue; } let value = snapshot.val(); //注入 partitionKey value[hashKeyName] = itemQuery.hashKey; if (rangeKeyName) { value[rangeKeyName] = itemQuery.rangeKey; } let entity = new selfProto.constructor(value); entities.push(utils.wrapperEntity(entity)); } resolve(entities); } catch (err) { console.log(`实体: ${tableName}, 批量获取异常:`, err); reject(`实体: ${tableName}, 批量获取异常`); } }) } /** * 对象保存 instance * @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; partitionKey[hashKeyName] = this._entity[hashKeyName]; if (rangeKeyName) { partitionKey[rangeKeyName] = this._entity[rangeKeyName]; } const ref = makeRef(partitionKey, selfProto._schema.tableName, hashKeyName, rangeKeyName); let copyEntity; if (selfProto._schema.FIREBASE_IGNORED) { copyEntity = {}; for (let key in this._entity) { if (selfProto._schema.FIREBASE_IGNORED.includes(key)) { continue; } copyEntity[key] = this._entity[key]; } } else { copyEntity = { ...this._entity }; } //使用事务 if (options && options.transaction) { return admin.database().ref(ref).transaction(val => { if (!val) { copyEntity[utils.ENTITY_ATTR_VERSION] = 1; return copyEntity; } //比较版本号 let dbVersion = val[utils.ENTITY_ATTR_VERSION] || 0; let entityVersion = self._entity[utils.ENTITY_ATTR_VERSION] || 0; if (dbVersion != entityVersion) { if (DEBUG) { console.log(`实体: ${selfProto._schema.tableName}, save操作, dbVersion: ${dbVersion}, entityVersion: ${entityVersion}`); } return; } //更新自身entity 版本号 let nextVersion = entityVersion + 1; self._entity[utils.ENTITY_ATTR_VERSION] = nextVersion; copyEntity[utils.ENTITY_ATTR_VERSION] = nextVersion; return copyEntity; }).then(result => { if (!result.committed) { return Promise.reject(`实体${selfProto._schema.tableName}, 乐观锁事务失败`); } return Promise.resolve(); }, (error) => { return Promise.reject(error); }); } else { return admin.database().ref(ref).update(copyEntity).then(() => Promise.resolve()).catch(ex => Promise.reject(ex)); } } /** * 删除实体 (实例方法) */ function remove() { const selfProto = Object.getPrototypeOf(this); const partitionKey = {}; const hashKeyName = selfProto._schema.hashKey; const rangeKeyName = selfProto._schema.rangeKey; partitionKey[hashKeyName] = this._entity[hashKeyName]; if (rangeKeyName) { partitionKey[rangeKeyName] = this._entity[rangeKeyName]; } const ref = makeRef(partitionKey, selfProto._schema.tableName, hashKeyName, rangeKeyName); return admin.database().ref(ref) .remove() .catch(ex => { console.log(`模型: ${selfProto._schema.tableName}删除异常, ${JSON.stringify(ex)}`); return Promise.reject(ex); }); } /** * 定义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; } if (!schema.FIREBASE_IGNORED) { if (schema.rangeKey) { schema.FIREBASE_IGNORED = [schema.hashKey, schema.rangeKey]; } else { schema.FIREBASE_IGNORED = [schema.hashKey]; } if (DEBUG) { console.log(`实体: ${schema.tableName}无自定义FIREBASE_IGNORED, 添加默认: ${JSON.stringify(schema.FIREBASE_IGNORED)}`) } } //属性值 entity.prototype._schema = schema; entity.prototype.toEntity = function () { const result = { ...this._entity }; //删除 hashKey delete result[Object.getPrototypeOf(this)._schema.hashKey]; //删除 version delete result[utils.ENTITY_ATTR_VERSION]; return result; } //更改entity属性 entity.prototype.modifyEntity = function (kv) { for (let key in kv) { this._entity[key] = kv[key]; } }; //添加 LOAD entity.load = load; //添加 CRUD entity.create = create; entity.update = update; entity.destroy = destroy; entity.exists = exists; entity.loadOrCreate = loadOrCreate; if (schema[utils.SCHEMA_BUILDER]) { entity.prototype._builder = schema[utils.SCHEMA_BUILDER]; } else { entity.prototype._builder = utils.entityBuilder; } entity.listAll = listAll; entity.alter = alter; entity.batchGet = batchGet; entity.prototype.save = save; entity.prototype.remove = remove; entity.prototype.toVO = utils.toVO; //添加自定义方法 const instanceMethods = schema[utils.SCHEMA_INSTANCE_METHODS]; if (instanceMethods) { for (let funcName in instanceMethods) { entity.prototype[funcName] = instanceMethods[funcName]; } } //添加 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;