2023-03-04 14:30:13 +08:00
|
|
|
|
import moment from 'moment'
|
|
|
|
|
import BaseModel from './BaseModel.js'
|
2023-05-10 05:55:21 +08:00
|
|
|
|
import MysUtil from './MysUtil.js'
|
2023-03-04 14:30:13 +08:00
|
|
|
|
|
|
|
|
|
const servs = ['mys', 'hoyolab']
|
|
|
|
|
// 超时时间不必精确,直接定24小时即可
|
|
|
|
|
const EX = 3600 * 24
|
2023-05-08 05:11:29 +08:00
|
|
|
|
const redisKeyRoot = 'Yz:cache:'
|
2023-03-04 14:30:13 +08:00
|
|
|
|
|
|
|
|
|
export default class DailyCache extends BaseModel {
|
2023-05-09 11:03:38 +08:00
|
|
|
|
constructor (uid, game = 'config') {
|
2023-03-04 14:30:13 +08:00
|
|
|
|
super()
|
2023-05-08 05:11:29 +08:00
|
|
|
|
const storeKey = DailyCache.getStoreKey(uid, game)
|
2023-03-04 14:30:13 +08:00
|
|
|
|
// 检查实例缓存
|
|
|
|
|
let self = this._getThis('store', storeKey)
|
|
|
|
|
if (self) {
|
|
|
|
|
return self
|
|
|
|
|
}
|
|
|
|
|
this.keyPre = `${redisKeyRoot}${storeKey}`
|
|
|
|
|
return this._cacheThis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 传入UID或server标示,返回当日存储对象
|
|
|
|
|
* @param uid
|
2023-05-09 11:03:38 +08:00
|
|
|
|
* @param game
|
2023-03-04 14:30:13 +08:00
|
|
|
|
* * 为空则返回与serv无关的dailyCache
|
|
|
|
|
* * 传入UID,会返回UID对应serv的cache对象
|
|
|
|
|
* * 传入servKey (mys/hoyolab),会返回指定的servCache
|
|
|
|
|
* @returns {DailyCache}
|
|
|
|
|
*/
|
2023-05-09 11:03:38 +08:00
|
|
|
|
static create (uid, game = 'config') {
|
|
|
|
|
return new DailyCache(uid, game)
|
2023-03-04 14:30:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 内部方法:获取redis表前缀
|
2023-05-09 11:03:38 +08:00
|
|
|
|
static getStoreKey (uid, game = 'config') {
|
|
|
|
|
let key
|
|
|
|
|
if (!uid || game === 'config') {
|
|
|
|
|
key = 'sys:config'
|
|
|
|
|
} else {
|
2023-05-10 05:55:21 +08:00
|
|
|
|
game = MysUtil.getGameKey(game)
|
2023-05-09 11:03:38 +08:00
|
|
|
|
let serv = /^[6-9]|^hoyo|^os/i.test(uid) ? servs[1] : servs[0]
|
|
|
|
|
key = `${game}:${serv}`
|
|
|
|
|
}
|
2023-03-04 14:30:13 +08:00
|
|
|
|
const date = moment().format('MM-DD')
|
2023-05-09 11:03:38 +08:00
|
|
|
|
return `${key}-${date}`
|
2023-03-04 14:30:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 遍历所有servCache
|
|
|
|
|
* @param fn
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
static async eachCache (fn) {
|
|
|
|
|
for (const serv of servs) {
|
|
|
|
|
let cache = DailyCache.create(serv)
|
|
|
|
|
if (cache) {
|
|
|
|
|
await fn(cache)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除过期的DailyCache
|
|
|
|
|
*/
|
|
|
|
|
static async clearOutdatedData () {
|
|
|
|
|
let keys = await redis.keys(`${redisKeyRoot}*`)
|
|
|
|
|
const date = moment().format('MM-DD')
|
2023-05-09 11:03:38 +08:00
|
|
|
|
const testReg = new RegExp(`^${redisKeyRoot}(mys|hoyolab|config)-\\d{2}-\\d{2}`)
|
|
|
|
|
const todayReg = new RegExp(`^${redisKeyRoot}(mys|hoyolab|config)-${date}`)
|
2023-03-04 14:30:13 +08:00
|
|
|
|
for (let key of keys) {
|
|
|
|
|
if (testReg.test(key) && !todayReg.test(key)) {
|
|
|
|
|
await redis.del(key)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-08 05:11:29 +08:00
|
|
|
|
// 内部方法,用于decode value
|
|
|
|
|
static decodeValue (value, decode = false) {
|
|
|
|
|
if (value && decode) {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(value)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 内部方法,用于encode value
|
|
|
|
|
static encodeValue (value) {
|
|
|
|
|
if (typeof (value) === 'object') {
|
|
|
|
|
return JSON.stringify(value) || ''
|
|
|
|
|
}
|
|
|
|
|
if (typeof (value) === 'undefined') {
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
return '' + value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** ---- 基础方法 ---- **/
|
|
|
|
|
// 内部方法:获取redis表key键值
|
|
|
|
|
getTableKey (key, sub = '') {
|
|
|
|
|
if (sub) {
|
|
|
|
|
return `${this.keyPre}:${key}-${sub}`
|
|
|
|
|
} else {
|
|
|
|
|
return `${this.keyPre}:${key}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 【基础数据结构】:Key-Value
|
|
|
|
|
*
|
|
|
|
|
* 每个key对应一个Value
|
|
|
|
|
* 使用redis kv存储,所有操作需要指定表名
|
|
|
|
|
*
|
|
|
|
|
* **/
|
|
|
|
|
|
2023-03-04 14:30:13 +08:00
|
|
|
|
/**
|
|
|
|
|
* 设置指定表的过期时间
|
|
|
|
|
* @param table 表
|
|
|
|
|
* @param hasCount 是否具有count表(KeyList)
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async exTable (table, hasCount = false) {
|
|
|
|
|
await redis.expire(this.getTableKey(table), EX)
|
|
|
|
|
if (hasCount) {
|
|
|
|
|
await redis.expire(this.getTableKey(table, 'count'), EX)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 清空删除指定表
|
|
|
|
|
* @param table
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async empty (table) {
|
|
|
|
|
await redis.del(this.getTableKey(table))
|
|
|
|
|
await redis.del(this.getTableKey(table, 'count'))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取表指定key内容
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key 数据存储key
|
|
|
|
|
* @param decode 是否对内容进行decode
|
|
|
|
|
* @returns {Promise<any|boolean>}
|
|
|
|
|
*/
|
|
|
|
|
async kGet (table, key, decode = false) {
|
|
|
|
|
let value = await redis.hGet(this.getTableKey(table), '' + key)
|
|
|
|
|
return DailyCache.decodeValue(value, decode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置表指定key内容
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key 数据存储key
|
|
|
|
|
* @param value 数据,若传入对象或数组会自动encode
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async kSet (table, key, value) {
|
|
|
|
|
value = DailyCache.encodeValue(value)
|
|
|
|
|
await redis.hSet(this.getTableKey(table), '' + key, value)
|
|
|
|
|
await this.exTable(this.getTableKey(table))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除表中指定key内容
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key 数据存储key
|
|
|
|
|
* @returns {Promise<number>}
|
|
|
|
|
*/
|
|
|
|
|
async kDel (table, key) {
|
|
|
|
|
return await redis.hDel(this.getTableKey(table), '' + key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定表内容
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param decode 是否对内容进行decode
|
|
|
|
|
* @returns {Promise<any|boolean>}
|
|
|
|
|
*/
|
|
|
|
|
async get (table, decode = false) {
|
|
|
|
|
const tableKey = this.getTableKey(table)
|
|
|
|
|
let value = await redis.get(tableKey)
|
|
|
|
|
return DailyCache.decodeValue(value, decode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置指定表内容
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param value 数据,若传入对象或数组会自动encode
|
|
|
|
|
* @returns {Promise<any|boolean>}
|
|
|
|
|
*/
|
|
|
|
|
async set (table, value) {
|
|
|
|
|
value = DailyCache.encodeValue(value)
|
|
|
|
|
return await redis.set(this.getTableKey(table), value, { EX })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 【基础数据结构】:Key-List
|
|
|
|
|
*
|
|
|
|
|
* 每个key对应一个list,key必须为数字,list间的item不重复
|
|
|
|
|
* 若重复item被添加,则会将item移至指定key对应List中
|
|
|
|
|
*
|
|
|
|
|
* 会自动统计每个list长度并排序
|
|
|
|
|
* 使用redis sorted map存储,所有操作需要指定表名
|
|
|
|
|
*
|
|
|
|
|
* **/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为KeyList添加 item
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key 添加item对应 key键值
|
|
|
|
|
* @param item 添加的item
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async zAdd (table, key, item) {
|
|
|
|
|
const tableKey = this.getTableKey(table)
|
|
|
|
|
await redis.zAdd(tableKey, { score: key, value: item + '' })
|
|
|
|
|
|
|
|
|
|
// 同时更新数量,用于数量统计
|
|
|
|
|
let count = await this.zCount(table, key) || 0
|
|
|
|
|
const countKey = this.getTableKey(table, 'count')
|
|
|
|
|
await redis.zAdd(countKey, { score: count, value: key + '' })
|
|
|
|
|
await this.exTable(this.getTableKey(table), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据key获取list
|
|
|
|
|
/**
|
|
|
|
|
* 根据Key获取List
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key key键值
|
|
|
|
|
* @returns {Promise<Array<ConvertArgumentType<string | Buffer, string>>>}
|
|
|
|
|
*/
|
|
|
|
|
async zList (table, key) {
|
|
|
|
|
return await redis.zRangeByScore(this.getTableKey(table), key, key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定item所在List对应的key键值
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param item item
|
|
|
|
|
* @returns {Promise<number>}
|
|
|
|
|
*/
|
|
|
|
|
async zKey (table, item) {
|
|
|
|
|
return await redis.zScore(this.getTableKey(table), item + '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定key对应List的长度
|
|
|
|
|
* @param table 表名
|
|
|
|
|
* @param key 需要获取长度的key
|
|
|
|
|
* @returns {Promise<number>} 长度值
|
|
|
|
|
*/
|
|
|
|
|
async zCount (table, key) {
|
|
|
|
|
return await redis.zCount(this.getTableKey(table), key, key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取当前KeyList中,List长度最小的一个key
|
|
|
|
|
* 由于内部场景使用,简单规定List长度有效范围为0-60
|
|
|
|
|
* @param table
|
|
|
|
|
* @returns {Promise<string>}
|
|
|
|
|
*/
|
|
|
|
|
async zMinKey (table) {
|
|
|
|
|
let keys = await redis.zRangeByScore(this.getTableKey(table, 'count'), 0, 60)
|
|
|
|
|
return keys[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在当前KeyList中禁用指定的key
|
|
|
|
|
* 会保留所有已有item记录,但不再被zMinKey识别并返回
|
|
|
|
|
* 主要用于标记CK查询次数超限场景(已经查询的记录仍然有效)
|
|
|
|
|
* @param table
|
|
|
|
|
* @param key
|
|
|
|
|
* @param delCount 是否同时删除count记录,删除后不会被zGetDisableKey获取
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async zDisableKey (table, key, delCount = false) {
|
|
|
|
|
// 将count标记为99次,记录并防止被后续分配
|
|
|
|
|
const countKey = this.getTableKey(table, 'count')
|
|
|
|
|
if (delCount) {
|
|
|
|
|
await redis.zRem(countKey, key)
|
|
|
|
|
} else {
|
|
|
|
|
await redis.zAdd(countKey, { score: 99, value: key })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取已禁用的key列表,用于主动清除数据使用
|
|
|
|
|
* @param table
|
|
|
|
|
* @returns {Promise<Array<ConvertArgumentType<string | Buffer, string>>>}
|
|
|
|
|
*/
|
|
|
|
|
async zGetDisableKey (table) {
|
|
|
|
|
return await redis.zRangeByScore(this.getTableKey(table, 'count'), 99, 99)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 删除某个key
|
|
|
|
|
// 清空所有查询关联,同时不再被zMinKey识别并返回
|
|
|
|
|
/**
|
|
|
|
|
* 删除指定key记录
|
|
|
|
|
* 清空所有查询关联,同时不再被zMinKey识别并返回
|
|
|
|
|
* 与zDisableKey的区别在于会删除detail中已存在的记录
|
|
|
|
|
* 主要用于CK失效场景(已经查询的记录也同时失效)
|
|
|
|
|
* @param table
|
|
|
|
|
* @param key
|
|
|
|
|
* @param delCount 是否同时删除count记录,删除后不会被zGetDisableKey获取
|
|
|
|
|
* @returns {Promise<boolean>}
|
|
|
|
|
*/
|
|
|
|
|
async zDel (table, key, delCount = false) {
|
|
|
|
|
// 删除key对应list所有记录
|
2023-12-07 04:58:58 +08:00
|
|
|
|
key = key + ''
|
2023-03-04 14:30:13 +08:00
|
|
|
|
let check = redis.zScore(this.getTableKey(table, 'count'), key)
|
|
|
|
|
await redis.zRemRangeByScore(this.getTableKey(table), key, key)
|
|
|
|
|
await this.zDisableKey(table, key, delCount)
|
|
|
|
|
return !!check
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定表格的key:List count 统计数据
|
|
|
|
|
* @param table
|
|
|
|
|
* @returns {Promise<{key:count}>}
|
|
|
|
|
*/
|
|
|
|
|
async zStat (table) {
|
|
|
|
|
const countKey = this.getTableKey(table, 'count')
|
|
|
|
|
return await redis.zRangeByScoreWithScores(countKey, 0, 100)
|
|
|
|
|
}
|
|
|
|
|
}
|