123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- const db = uniCloud.database()
- function getType(value) {
- return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
- }
- const validator = {
- key: function(value) {
- const err = new Error('Invalid key')
- if (typeof value !== 'string') {
- throw err
- }
- const valueTrim = value.trim()
- if (!valueTrim || valueTrim !== value) {
- throw err
- }
- },
- value: function(value) {
- // 仅作简单校验
- const type = getType(value)
- const validValueType = ['null', 'number', 'string', 'array', 'object']
- if (validValueType.indexOf(type) === -1) {
- throw new Error('Invalid value type')
- }
- },
- duration: function(value) {
- const err = new Error('Invalid duration')
- if (value === undefined) {
- return
- }
- if (typeof value !== 'number' || value === 0) {
- throw err
- }
- }
- }
- /**
- * 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示
- * 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在
- */
- class DatabaseCache {
- constructor({
- collection = 'opendb-open-data'
- } = {}) {
- this.type = 'db'
- this.collection = db.collection(collection)
- }
- _serializeValue(value) {
- return value === undefined ? null : JSON.stringify(value)
- }
- _deserializeValue(value) {
- return value ? JSON.parse(value) : value
- }
- async set(key, value, duration) {
- validator.key(key)
- validator.value(value)
- validator.duration(duration)
- value = this._serializeValue(value)
- await this.collection.doc(key).set({
- value,
- expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1
- })
- }
- async _getWithDuration(key) {
- const getKeyRes = await this.collection.doc(key).get()
- const record = getKeyRes.data[0]
- if (!record) {
- return {
- value: null,
- duration: -2
- }
- }
- const value = this._deserializeValue(record.value)
- const expired = record.expired
- if (expired === -1) {
- return {
- value,
- duration: -1
- }
- }
- const duration = expired - Date.now()
- if (duration <= 0) {
- await this.remove(key)
- return {
- value: null,
- duration: -2
- }
- }
- return {
- value,
- duration: Math.floor(duration / 1000)
- }
- }
- async get(key, {
- withDuration = true
- } = {}) {
- const result = await this._getWithDuration(key)
- if (!withDuration) {
- delete result.duration
- }
- return result
- }
- async remove(key) {
- await this.collection.doc(key).remove()
- }
- }
- class RedisCache {
- constructor() {
- this.type = 'redis'
- this.redis = uniCloud.redis()
- }
- _serializeValue(value) {
- return value === undefined ? null : JSON.stringify(value)
- }
- _deserializeValue(value) {
- return value ? JSON.parse(value) : value
- }
- async set(key, value, duration) {
- validator.key(key)
- validator.value(value)
- validator.duration(duration)
- value = this._serializeValue(value)
- if (!duration || duration === -1) {
- await this.redis.set(key, value)
- } else {
- await this.redis.set(key, value, 'EX', duration)
- }
- }
- async get(key, {
- withDuration = false
- } = {}) {
- let value = await this.redis.get(key)
- value = this._deserializeValue(value)
- if (!withDuration) {
- return {
- value
- }
- }
- const durationSecond = await this.redis.ttl(key)
- let duration
- switch (durationSecond) {
- case -1:
- duration = -1
- break
- case -2:
- duration = -2
- break
- default:
- duration = durationSecond
- break
- }
- return {
- value,
- duration
- }
- }
- async remove(key) {
- await this.redis.del(key)
- }
- }
- class Cache {
- constructor({
- type,
- collection
- } = {}) {
- if (type === 'database') {
- return new DatabaseCache({
- collection
- })
- } else if (type === 'redis') {
- return new RedisCache()
- } else {
- throw new Error('Invalid cache type')
- }
- }
- }
- class CacheKey {
- constructor({
- type,
- collection,
- cache,
- key,
- fallback
- } = {}) {
- this.cache = cache || new Cache({
- type,
- collection
- })
- this.key = key
- this.fallback = fallback
- }
- async set(value, duration) {
- await this.cache.set(this.key, value, duration)
- }
- async setWithSync(value, duration, syncMethod) {
- await Promise.all([
- this.set(this.key, value, duration),
- syncMethod(value, duration)
- ])
- }
- async get() {
- let {
- value,
- duration
- } = await this.cache.get(this.key)
- if (value !== null && value !== undefined) {
- return {
- value,
- duration
- }
- }
- if (!this.fallback) {
- return {
- value: null,
- duration: -2
- }
- }
- const fallbackResult = await this.fallback()
- value = fallbackResult.value
- duration = fallbackResult.duration
- if (value !== null && duration !== undefined) {
- await this.cache.set(this.key, value, duration)
- }
- return {
- value,
- duration
- }
- }
- async remove() {
- await this.cache.remove(this.key)
- }
- }
- class CacheKeyCascade {
- constructor({
- layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey]
- fallback
- } = {}) {
- this.layers = layers
- this.cacheLayers = []
- let lastCacheKey
- for (let i = 0; i < layers.length; i++) {
- const {
- type,
- cache,
- collection,
- key
- } = layers[i]
- const lastCacheKeyTemp = lastCacheKey
- try {
- const currentCacheKey = new CacheKey({
- type,
- collection,
- cache,
- key,
- fallback: i === 0 ? fallback : function() {
- return lastCacheKeyTemp.get()
- }
- })
- this.cacheLayers.push(currentCacheKey)
- lastCacheKey = currentCacheKey
- } catch (e) {}
- }
- this.highLevelCache = lastCacheKey
- }
- async set(value, duration) {
- return Promise.all(
- this.cacheLayers.map(item => {
- return item.set(value, duration)
- })
- )
- }
- async setWithSync(value, duration, syncMethod) {
- const setPromise = this.cacheLayers.map(item => {
- return item.set(value, duration)
- })
- return Promise.all(
- [
- ...setPromise,
- syncMethod(value, duration)
- ]
- )
- }
- async get() {
- return this.highLevelCache.get()
- }
- async remove() {
- await Promise.all(
- this.cacheLayers.map(cacheKeyItem => {
- return cacheKeyItem.remove()
- })
- )
- }
- }
- module.exports = {
- Cache,
- DatabaseCache,
- RedisCache,
- CacheKey,
- CacheKeyCascade
- }
|