迁移跳过配置到新结构
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
## [205.0.0] - 2026-01-01
|
## [205.0.1] - 2026-01-01
|
||||||
|
### Changed
|
||||||
|
- 迁移跳过配置到新数据结构
|
||||||
|
|
||||||
|
## [205.0.0] - 2026-01-01
|
||||||
### Added
|
### Added
|
||||||
- oidc注册支持设置linuxdo信任等级
|
- oidc注册支持设置linuxdo信任等级
|
||||||
- 播放页增加更多推荐
|
- 播放页增加更多推荐
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
205.0.0
|
205.0.1
|
||||||
@@ -338,3 +338,4 @@ async function refreshOpenList() {
|
|||||||
console.error('OpenList 定时扫描失败:', err);
|
console.error('OpenList 定时扫描失败:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ export async function GET(request: NextRequest) {
|
|||||||
if (userInfoV2.banned) {
|
if (userInfoV2.banned) {
|
||||||
return NextResponse.json({ error: '用户已被封禁' }, { status: 401 });
|
return NextResponse.json({ error: '用户已被封禁' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否需要迁移跳过配置
|
||||||
|
if (!userInfoV2.skip_migrated) {
|
||||||
|
await db.migrateSkipConfigs(authInfo.username);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 站长也需要检查迁移
|
||||||
|
const userInfoV2 = await db.getUserInfoV2(authInfo.username);
|
||||||
|
if (!userInfoV2?.skip_migrated) {
|
||||||
|
await db.migrateSkipConfigs(authInfo.username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function WarningPage() {
|
|||||||
📢 重要更新说明
|
📢 重要更新说明
|
||||||
</p>
|
</p>
|
||||||
<p className='text-sm sm:text-base text-blue-700'>
|
<p className='text-sm sm:text-base text-blue-700'>
|
||||||
自 v2.0.5 版本起,已不再支持无数据库部署方式,请配置数据库相关环境变量。
|
自 v200.5.0 版本起,已不再支持无数据库部署方式,请配置数据库相关环境变量。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ export interface ChangelogEntry {
|
|||||||
|
|
||||||
export const changelog: ChangelogEntry[] = [
|
export const changelog: ChangelogEntry[] = [
|
||||||
{
|
{
|
||||||
|
version: '205.0.1',
|
||||||
|
date: '2026-01-01',
|
||||||
|
added: [
|
||||||
|
],
|
||||||
|
changed: [
|
||||||
|
"迁移跳过配置到新数据结构"
|
||||||
|
],
|
||||||
|
fixed: [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
version: '205.0.0',
|
version: '205.0.0',
|
||||||
date: '2026-01-01',
|
date: '2026-01-01',
|
||||||
added: [
|
added: [
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ export class DbManager {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
playrecord_migrated?: boolean;
|
playrecord_migrated?: boolean;
|
||||||
favorite_migrated?: boolean;
|
favorite_migrated?: boolean;
|
||||||
|
skip_migrated?: boolean;
|
||||||
} | null> {
|
} | null> {
|
||||||
if (typeof (this.storage as any).getUserInfoV2 === 'function') {
|
if (typeof (this.storage as any).getUserInfoV2 === 'function') {
|
||||||
return (this.storage as any).getUserInfoV2(userName);
|
return (this.storage as any).getUserInfoV2(userName);
|
||||||
@@ -271,6 +272,13 @@ export class DbManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 跳过配置迁移 ----------
|
||||||
|
async migrateSkipConfigs(userName: string): Promise<void> {
|
||||||
|
if (typeof (this.storage as any).migrateSkipConfigs === 'function') {
|
||||||
|
await (this.storage as any).migrateSkipConfigs(userName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 数据迁移 ----------
|
// ---------- 数据迁移 ----------
|
||||||
async migrateUsersFromConfig(adminConfig: AdminConfig): Promise<void> {
|
async migrateUsersFromConfig(adminConfig: AdminConfig): Promise<void> {
|
||||||
if (typeof (this.storage as any).createUserV2 !== 'function') {
|
if (typeof (this.storage as any).createUserV2 !== 'function') {
|
||||||
|
|||||||
+85
-20
@@ -550,7 +550,10 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
await this.withRetry(() => this.client.del(favoriteKeys));
|
await this.withRetry(() => this.client.del(favoriteKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除跳过片头片尾配置
|
// 删除跳过片头片尾配置(新hash结构)
|
||||||
|
await this.withRetry(() => this.client.del(this.skipHashKey(userName)));
|
||||||
|
|
||||||
|
// 删除旧的跳过配置key(如果有)
|
||||||
const skipConfigPattern = `u:${userName}:skip:*`;
|
const skipConfigPattern = `u:${userName}:skip:*`;
|
||||||
const skipConfigKeys = await this.withRetry(() =>
|
const skipConfigKeys = await this.withRetry(() =>
|
||||||
this.client.keys(skipConfigPattern)
|
this.client.keys(skipConfigPattern)
|
||||||
@@ -655,6 +658,7 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
playrecord_migrated?: boolean;
|
playrecord_migrated?: boolean;
|
||||||
favorite_migrated?: boolean;
|
favorite_migrated?: boolean;
|
||||||
|
skip_migrated?: boolean;
|
||||||
} | null> {
|
} | null> {
|
||||||
const userInfo = await this.withRetry(() =>
|
const userInfo = await this.withRetry(() =>
|
||||||
this.client.hGetAll(this.userInfoKey(userName))
|
this.client.hGetAll(this.userInfoKey(userName))
|
||||||
@@ -673,6 +677,7 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
created_at: parseInt(userInfo.created_at || '0', 10),
|
created_at: parseInt(userInfo.created_at || '0', 10),
|
||||||
playrecord_migrated: userInfo.playrecord_migrated === 'true',
|
playrecord_migrated: userInfo.playrecord_migrated === 'true',
|
||||||
favorite_migrated: userInfo.favorite_migrated === 'true',
|
favorite_migrated: userInfo.favorite_migrated === 'true',
|
||||||
|
skip_migrated: userInfo.skip_migrated === 'true',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,8 +952,8 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 跳过片头片尾配置 ----------
|
// ---------- 跳过片头片尾配置 ----------
|
||||||
private skipConfigKey(user: string, source: string, id: string) {
|
private skipHashKey(user: string) {
|
||||||
return `u:${user}:skip:${source}+${id}`;
|
return `u:${user}:skip`; // u:username:skip (hash结构)
|
||||||
}
|
}
|
||||||
|
|
||||||
private danmakuFilterConfigKey(user: string) {
|
private danmakuFilterConfigKey(user: string) {
|
||||||
@@ -960,8 +965,9 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
source: string,
|
source: string,
|
||||||
id: string
|
id: string
|
||||||
): Promise<SkipConfig | null> {
|
): Promise<SkipConfig | null> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
const val = await this.withRetry(() =>
|
const val = await this.withRetry(() =>
|
||||||
this.client.get(this.skipConfigKey(userName, source, id))
|
this.client.hGet(this.skipHashKey(userName), key)
|
||||||
);
|
);
|
||||||
return val ? (JSON.parse(val) as SkipConfig) : null;
|
return val ? (JSON.parse(val) as SkipConfig) : null;
|
||||||
}
|
}
|
||||||
@@ -972,11 +978,9 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
id: string,
|
id: string,
|
||||||
config: SkipConfig
|
config: SkipConfig
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
await this.withRetry(() =>
|
await this.withRetry(() =>
|
||||||
this.client.set(
|
this.client.hSet(this.skipHashKey(userName), key, JSON.stringify(config))
|
||||||
this.skipConfigKey(userName, source, id),
|
|
||||||
JSON.stringify(config)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,39 +989,100 @@ export abstract class BaseRedisStorage implements IStorage {
|
|||||||
source: string,
|
source: string,
|
||||||
id: string
|
id: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
await this.withRetry(() =>
|
await this.withRetry(() =>
|
||||||
this.client.del(this.skipConfigKey(userName, source, id))
|
this.client.hDel(this.skipHashKey(userName), key)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllSkipConfigs(
|
async getAllSkipConfigs(
|
||||||
userName: string
|
userName: string
|
||||||
): Promise<{ [key: string]: SkipConfig }> {
|
): Promise<{ [key: string]: SkipConfig }> {
|
||||||
const pattern = `u:${userName}:skip:*`;
|
const hashData = await this.withRetry(() =>
|
||||||
const keys = await this.withRetry(() => this.client.keys(pattern));
|
this.client.hGetAll(this.skipHashKey(userName))
|
||||||
|
);
|
||||||
|
|
||||||
if (keys.length === 0) {
|
const result: Record<string, SkipConfig> = {};
|
||||||
return {};
|
for (const [key, value] of Object.entries(hashData)) {
|
||||||
|
if (value) {
|
||||||
|
result[key] = JSON.parse(value) as SkipConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移跳过配置:从旧的多key结构迁移到新的hash结构
|
||||||
|
async migrateSkipConfigs(userName: string): Promise<void> {
|
||||||
|
const existingMigration = playRecordLocks.get(`${userName}:skip`);
|
||||||
|
if (existingMigration) {
|
||||||
|
console.log(`用户 ${userName} 的跳过配置正在迁移中,等待完成...`);
|
||||||
|
await existingMigration;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const configs: { [key: string]: SkipConfig } = {};
|
const migrationPromise = this.doSkipConfigMigration(userName);
|
||||||
|
playRecordLocks.set(`${userName}:skip`, migrationPromise);
|
||||||
|
|
||||||
// 批量获取所有配置
|
try {
|
||||||
const values = await this.withRetry(() => this.client.mGet(keys));
|
await migrationPromise;
|
||||||
|
} finally {
|
||||||
|
playRecordLocks.delete(`${userName}:skip`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keys.forEach((key, index) => {
|
private async doSkipConfigMigration(userName: string): Promise<void> {
|
||||||
|
console.log(`开始迁移用户 ${userName} 的跳过配置...`);
|
||||||
|
|
||||||
|
const userInfo = await this.getUserInfoV2(userName);
|
||||||
|
if (userInfo?.skip_migrated) {
|
||||||
|
console.log(`用户 ${userName} 的跳过配置已经迁移过,跳过`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = `u:${userName}:skip:*`;
|
||||||
|
const oldKeys: string[] = await this.withRetry(() => this.client.keys(pattern));
|
||||||
|
|
||||||
|
if (oldKeys.length === 0) {
|
||||||
|
console.log(`用户 ${userName} 没有旧的跳过配置,标记为已迁移`);
|
||||||
|
await this.withRetry(() =>
|
||||||
|
this.client.hSet(this.userInfoKey(userName), 'skip_migrated', 'true')
|
||||||
|
);
|
||||||
|
const { userInfoCache } = await import('./user-cache');
|
||||||
|
userInfoCache?.delete(userName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = await this.withRetry(() => this.client.mGet(oldKeys));
|
||||||
|
|
||||||
|
const hashData: Record<string, string> = {};
|
||||||
|
oldKeys.forEach((key, index) => {
|
||||||
const value = values[index];
|
const value = values[index];
|
||||||
if (value) {
|
if (value) {
|
||||||
// 从key中提取source+id
|
|
||||||
const match = key.match(/^u:.+?:skip:(.+)$/);
|
const match = key.match(/^u:.+?:skip:(.+)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const sourceAndId = match[1];
|
const sourceAndId = match[1];
|
||||||
configs[sourceAndId] = JSON.parse(value as string) as SkipConfig;
|
hashData[sourceAndId] = value as string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return configs;
|
if (Object.keys(hashData).length > 0) {
|
||||||
|
await this.withRetry(() =>
|
||||||
|
this.client.hSet(this.skipHashKey(userName), hashData)
|
||||||
|
);
|
||||||
|
console.log(`成功迁移 ${Object.keys(hashData).length} 条跳过配置到hash结构`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.withRetry(() => this.client.del(oldKeys));
|
||||||
|
console.log(`删除了 ${oldKeys.length} 个旧的跳过配置key`);
|
||||||
|
|
||||||
|
await this.withRetry(() =>
|
||||||
|
this.client.hSet(this.userInfoKey(userName), 'skip_migrated', 'true')
|
||||||
|
);
|
||||||
|
const { userInfoCache } = await import('./user-cache');
|
||||||
|
userInfoCache?.delete(userName);
|
||||||
|
|
||||||
|
console.log(`用户 ${userName} 的跳过配置迁移完成`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 弹幕过滤配置 ----------
|
// ---------- 弹幕过滤配置 ----------
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ export interface IStorage {
|
|||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
deleteSkipConfig(userName: string, source: string, id: string): Promise<void>;
|
deleteSkipConfig(userName: string, source: string, id: string): Promise<void>;
|
||||||
getAllSkipConfigs(userName: string): Promise<{ [key: string]: SkipConfig }>;
|
getAllSkipConfigs(userName: string): Promise<{ [key: string]: SkipConfig }>;
|
||||||
|
// 迁移跳过配置
|
||||||
|
migrateSkipConfigs(userName: string): Promise<void>;
|
||||||
|
|
||||||
// 弹幕过滤配置相关
|
// 弹幕过滤配置相关
|
||||||
getDanmakuFilterConfig(userName: string): Promise<DanmakuFilterConfig | null>;
|
getDanmakuFilterConfig(userName: string): Promise<DanmakuFilterConfig | null>;
|
||||||
|
|||||||
+87
-17
@@ -451,7 +451,10 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
await withRetry(() => this.client.del(...favoriteKeys));
|
await withRetry(() => this.client.del(...favoriteKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除跳过片头片尾配置
|
// 删除跳过片头片尾配置(新hash结构)
|
||||||
|
await withRetry(() => this.client.del(this.skipHashKey(userName)));
|
||||||
|
|
||||||
|
// 删除旧的跳过配置key(如果有)
|
||||||
const skipConfigPattern = `u:${userName}:skip:*`;
|
const skipConfigPattern = `u:${userName}:skip:*`;
|
||||||
const skipConfigKeys = await withRetry(() =>
|
const skipConfigKeys = await withRetry(() =>
|
||||||
this.client.keys(skipConfigPattern)
|
this.client.keys(skipConfigPattern)
|
||||||
@@ -564,6 +567,7 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
created_at: number;
|
created_at: number;
|
||||||
playrecord_migrated?: boolean;
|
playrecord_migrated?: boolean;
|
||||||
favorite_migrated?: boolean;
|
favorite_migrated?: boolean;
|
||||||
|
skip_migrated?: boolean;
|
||||||
} | null> {
|
} | null> {
|
||||||
// 先从缓存获取
|
// 先从缓存获取
|
||||||
const cached = userInfoCache?.get(userName);
|
const cached = userInfoCache?.get(userName);
|
||||||
@@ -607,6 +611,16 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 skip_migrated 字段
|
||||||
|
let skip_migrated: boolean | undefined = undefined;
|
||||||
|
if (userInfo.skip_migrated !== undefined) {
|
||||||
|
if (typeof userInfo.skip_migrated === 'boolean') {
|
||||||
|
skip_migrated = userInfo.skip_migrated;
|
||||||
|
} else if (typeof userInfo.skip_migrated === 'string') {
|
||||||
|
skip_migrated = userInfo.skip_migrated === 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 安全解析 tags 字段
|
// 安全解析 tags 字段
|
||||||
let tags: string[] | undefined = undefined;
|
let tags: string[] | undefined = undefined;
|
||||||
if (userInfo.tags) {
|
if (userInfo.tags) {
|
||||||
@@ -646,6 +660,7 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
created_at: parseInt((userInfo.created_at as string) || '0', 10),
|
created_at: parseInt((userInfo.created_at as string) || '0', 10),
|
||||||
playrecord_migrated,
|
playrecord_migrated,
|
||||||
favorite_migrated,
|
favorite_migrated,
|
||||||
|
skip_migrated,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 存入缓存
|
// 存入缓存
|
||||||
@@ -968,8 +983,8 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 跳过片头片尾配置 ----------
|
// ---------- 跳过片头片尾配置 ----------
|
||||||
private skipConfigKey(user: string, source: string, id: string) {
|
private skipHashKey(user: string) {
|
||||||
return `u:${user}:skip:${source}+${id}`;
|
return `u:${user}:skip`; // u:username:skip (hash结构)
|
||||||
}
|
}
|
||||||
|
|
||||||
private danmakuFilterConfigKey(user: string) {
|
private danmakuFilterConfigKey(user: string) {
|
||||||
@@ -981,8 +996,9 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
source: string,
|
source: string,
|
||||||
id: string
|
id: string
|
||||||
): Promise<SkipConfig | null> {
|
): Promise<SkipConfig | null> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
const val = await withRetry(() =>
|
const val = await withRetry(() =>
|
||||||
this.client.get(this.skipConfigKey(userName, source, id))
|
this.client.hget(this.skipHashKey(userName), key)
|
||||||
);
|
);
|
||||||
return val ? (val as SkipConfig) : null;
|
return val ? (val as SkipConfig) : null;
|
||||||
}
|
}
|
||||||
@@ -993,8 +1009,9 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
id: string,
|
id: string,
|
||||||
config: SkipConfig
|
config: SkipConfig
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
await withRetry(() =>
|
await withRetry(() =>
|
||||||
this.client.set(this.skipConfigKey(userName, source, id), config)
|
this.client.hset(this.skipHashKey(userName), { [key]: config })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,39 +1020,92 @@ export class UpstashRedisStorage implements IStorage {
|
|||||||
source: string,
|
source: string,
|
||||||
id: string
|
id: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const key = `${source}+${id}`;
|
||||||
await withRetry(() =>
|
await withRetry(() =>
|
||||||
this.client.del(this.skipConfigKey(userName, source, id))
|
this.client.hdel(this.skipHashKey(userName), key)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllSkipConfigs(
|
async getAllSkipConfigs(
|
||||||
userName: string
|
userName: string
|
||||||
): Promise<{ [key: string]: SkipConfig }> {
|
): Promise<{ [key: string]: SkipConfig }> {
|
||||||
const pattern = `u:${userName}:skip:*`;
|
const hashData = await withRetry(() =>
|
||||||
const keys = await withRetry(() => this.client.keys(pattern));
|
this.client.hgetall<Record<string, SkipConfig>>(this.skipHashKey(userName))
|
||||||
|
);
|
||||||
|
|
||||||
if (keys.length === 0) {
|
return hashData || {};
|
||||||
return {};
|
}
|
||||||
|
|
||||||
|
// 迁移跳过配置:从旧的多key结构迁移到新的hash结构
|
||||||
|
async migrateSkipConfigs(userName: string): Promise<void> {
|
||||||
|
const existingMigration = playRecordLocks.get(`${userName}:skip`);
|
||||||
|
if (existingMigration) {
|
||||||
|
console.log(`用户 ${userName} 的跳过配置正在迁移中,等待完成...`);
|
||||||
|
await existingMigration;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const configs: { [key: string]: SkipConfig } = {};
|
const migrationPromise = this.doSkipConfigMigration(userName);
|
||||||
|
playRecordLocks.set(`${userName}:skip`, migrationPromise);
|
||||||
|
|
||||||
// 批量获取所有配置
|
try {
|
||||||
const values = await withRetry(() => this.client.mget(keys));
|
await migrationPromise;
|
||||||
|
} finally {
|
||||||
|
playRecordLocks.delete(`${userName}:skip`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
keys.forEach((key, index) => {
|
private async doSkipConfigMigration(userName: string): Promise<void> {
|
||||||
|
console.log(`开始迁移用户 ${userName} 的跳过配置...`);
|
||||||
|
|
||||||
|
const userInfo = await this.getUserInfoV2(userName);
|
||||||
|
if (userInfo?.skip_migrated) {
|
||||||
|
console.log(`用户 ${userName} 的跳过配置已经迁移过,跳过`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = `u:${userName}:skip:*`;
|
||||||
|
const oldKeys: string[] = await withRetry(() => this.client.keys(pattern));
|
||||||
|
|
||||||
|
if (oldKeys.length === 0) {
|
||||||
|
console.log(`用户 ${userName} 没有旧的跳过配置,标记为已迁移`);
|
||||||
|
await withRetry(() =>
|
||||||
|
this.client.hset(this.userInfoKey(userName), { skip_migrated: 'true' })
|
||||||
|
);
|
||||||
|
userInfoCache?.delete(userName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = await withRetry(() => this.client.mget(oldKeys));
|
||||||
|
|
||||||
|
const hashData: Record<string, SkipConfig> = {};
|
||||||
|
oldKeys.forEach((key, index) => {
|
||||||
const value = values[index];
|
const value = values[index];
|
||||||
if (value) {
|
if (value) {
|
||||||
// 从key中提取source+id
|
|
||||||
const match = key.match(/^u:.+?:skip:(.+)$/);
|
const match = key.match(/^u:.+?:skip:(.+)$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const sourceAndId = match[1];
|
const sourceAndId = match[1];
|
||||||
configs[sourceAndId] = value as SkipConfig;
|
hashData[sourceAndId] = value as SkipConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return configs;
|
if (Object.keys(hashData).length > 0) {
|
||||||
|
await withRetry(() =>
|
||||||
|
this.client.hset(this.skipHashKey(userName), hashData)
|
||||||
|
);
|
||||||
|
console.log(`成功迁移 ${Object.keys(hashData).length} 条跳过配置到hash结构`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await withRetry(() => this.client.del(...oldKeys));
|
||||||
|
console.log(`删除了 ${oldKeys.length} 个旧的跳过配置key`);
|
||||||
|
|
||||||
|
await withRetry(() =>
|
||||||
|
this.client.hset(this.userInfoKey(userName), { skip_migrated: 'true' })
|
||||||
|
);
|
||||||
|
userInfoCache?.delete(userName);
|
||||||
|
|
||||||
|
console.log(`用户 ${userName} 的跳过配置迁移完成`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 弹幕过滤配置 ----------
|
// ---------- 弹幕过滤配置 ----------
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
const CURRENT_VERSION = '205.0.0';
|
const CURRENT_VERSION = '205.0.1';
|
||||||
|
|
||||||
// 导出当前版本号供其他地方使用
|
// 导出当前版本号供其他地方使用
|
||||||
export { CURRENT_VERSION };
|
export { CURRENT_VERSION };
|
||||||
|
|||||||
Reference in New Issue
Block a user