mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-21 09:14:42 +08:00
fix: cross devices skip configs sync
This commit is contained in:
@@ -1 +1 @@
|
||||
20250729193611
|
||||
20250729213020
|
||||
@@ -25,9 +25,8 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json(config);
|
||||
} else {
|
||||
// 获取所有配置
|
||||
// 注意:这里需要实现获取所有跳过片头片尾配置的方法
|
||||
// 由于当前接口设计是按source+id获取单个配置,这里返回空对象
|
||||
return NextResponse.json({});
|
||||
const configs = await db.getAllSkipConfigs(authInfo.username);
|
||||
return NextResponse.json(configs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取跳过片头片尾配置失败:', error);
|
||||
|
||||
@@ -554,4 +554,34 @@ export class D1Storage implements IStorage {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(
|
||||
userName: string
|
||||
): Promise<{ [key: string]: SkipConfig }> {
|
||||
try {
|
||||
const db = await this.getDatabase();
|
||||
const result = await db
|
||||
.prepare(
|
||||
'SELECT source, id_video, enable, intro_time, outro_time FROM skip_configs WHERE username = ?'
|
||||
)
|
||||
.bind(userName)
|
||||
.all<any>();
|
||||
|
||||
const configs: { [key: string]: SkipConfig } = {};
|
||||
|
||||
result.results.forEach((row) => {
|
||||
const key = `${row.source}+${row.id_video}`;
|
||||
configs[key] = {
|
||||
enable: Boolean(row.enable),
|
||||
intro_time: row.intro_time,
|
||||
outro_time: row.outro_time,
|
||||
};
|
||||
});
|
||||
|
||||
return configs;
|
||||
} catch (err) {
|
||||
console.error('Failed to get all skip configs:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,6 +1426,67 @@ export async function saveSkipConfig(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有跳过片头片尾配置。
|
||||
* 数据库存储模式下使用混合缓存策略:优先返回缓存数据,后台异步同步最新数据。
|
||||
*/
|
||||
export async function getAllSkipConfigs(): Promise<Record<string, SkipConfig>> {
|
||||
// 服务器端渲染阶段直接返回空
|
||||
if (typeof window === 'undefined') {
|
||||
return {};
|
||||
}
|
||||
|
||||
// 数据库存储模式:使用混合缓存策略(包括 redis、d1、upstash)
|
||||
if (STORAGE_TYPE !== 'localstorage') {
|
||||
// 优先从缓存获取数据
|
||||
const cachedData = cacheManager.getCachedSkipConfigs();
|
||||
|
||||
if (cachedData) {
|
||||
// 返回缓存数据,同时后台异步更新
|
||||
fetchFromApi<Record<string, SkipConfig>>(`/api/skipconfigs`)
|
||||
.then((freshData) => {
|
||||
// 只有数据真正不同时才更新缓存
|
||||
if (JSON.stringify(cachedData) !== JSON.stringify(freshData)) {
|
||||
cacheManager.cacheSkipConfigs(freshData);
|
||||
// 触发数据更新事件
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('skipConfigsUpdated', {
|
||||
detail: freshData,
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn('后台同步跳过片头片尾配置失败:', err);
|
||||
});
|
||||
|
||||
return cachedData;
|
||||
} else {
|
||||
// 缓存为空,直接从 API 获取并缓存
|
||||
try {
|
||||
const freshData = await fetchFromApi<Record<string, SkipConfig>>(
|
||||
`/api/skipconfigs`
|
||||
);
|
||||
cacheManager.cacheSkipConfigs(freshData);
|
||||
return freshData;
|
||||
} catch (err) {
|
||||
console.error('获取跳过片头片尾配置失败:', err);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// localStorage 模式
|
||||
try {
|
||||
const raw = localStorage.getItem('moontv_skip_configs');
|
||||
if (!raw) return {};
|
||||
return JSON.parse(raw) as Record<string, SkipConfig>;
|
||||
} catch (err) {
|
||||
console.error('读取跳过片头片尾配置失败:', err);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除跳过片头片尾配置。
|
||||
* 数据库存储模式下使用乐观更新:先更新缓存,再异步同步到数据库。
|
||||
|
||||
@@ -214,6 +214,15 @@ export class DbManager {
|
||||
await (this.storage as any).deleteSkipConfig(userName, source, id);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(
|
||||
userName: string
|
||||
): Promise<{ [key: string]: SkipConfig }> {
|
||||
if (typeof (this.storage as any).getAllSkipConfigs === 'function') {
|
||||
return (this.storage as any).getAllSkipConfigs(userName);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 导出默认实例
|
||||
|
||||
@@ -332,6 +332,36 @@ export class RedisStorage implements IStorage {
|
||||
this.client.del(this.skipConfigKey(userName, source, id))
|
||||
);
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(
|
||||
userName: string
|
||||
): Promise<{ [key: string]: SkipConfig }> {
|
||||
const pattern = `u:${userName}:skip:*`;
|
||||
const keys = await withRetry(() => this.client.keys(pattern));
|
||||
|
||||
if (keys.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const configs: { [key: string]: SkipConfig } = {};
|
||||
|
||||
// 批量获取所有配置
|
||||
const values = await withRetry(() => this.client.mGet(keys));
|
||||
|
||||
keys.forEach((key, index) => {
|
||||
const value = values[index];
|
||||
if (value) {
|
||||
// 从key中提取source+id
|
||||
const match = key.match(/^u:.+?:skip:(.+)$/);
|
||||
if (match) {
|
||||
const sourceAndId = match[1];
|
||||
configs[sourceAndId] = JSON.parse(value as string) as SkipConfig;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return configs;
|
||||
}
|
||||
}
|
||||
|
||||
// 单例 Redis 客户端
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface IStorage {
|
||||
config: SkipConfig
|
||||
): Promise<void>;
|
||||
deleteSkipConfig(userName: string, source: string, id: string): Promise<void>;
|
||||
getAllSkipConfigs(userName: string): Promise<{ [key: string]: SkipConfig }>;
|
||||
}
|
||||
|
||||
// 搜索结果数据结构
|
||||
|
||||
@@ -313,6 +313,36 @@ export class UpstashRedisStorage implements IStorage {
|
||||
this.client.del(this.skipConfigKey(userName, source, id))
|
||||
);
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(
|
||||
userName: string
|
||||
): Promise<{ [key: string]: SkipConfig }> {
|
||||
const pattern = `u:${userName}:skip:*`;
|
||||
const keys = await withRetry(() => this.client.keys(pattern));
|
||||
|
||||
if (keys.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const configs: { [key: string]: SkipConfig } = {};
|
||||
|
||||
// 批量获取所有配置
|
||||
const values = await withRetry(() => this.client.mget(keys));
|
||||
|
||||
keys.forEach((key, index) => {
|
||||
const value = values[index];
|
||||
if (value) {
|
||||
// 从key中提取source+id
|
||||
const match = key.match(/^u:.+?:skip:(.+)$/);
|
||||
if (match) {
|
||||
const sourceAndId = match[1];
|
||||
configs[sourceAndId] = value as SkipConfig;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return configs;
|
||||
}
|
||||
}
|
||||
|
||||
// 单例 Upstash Redis 客户端
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
const CURRENT_VERSION = '20250729193611';
|
||||
const CURRENT_VERSION = '20250729213020';
|
||||
|
||||
// 版本检查结果枚举
|
||||
export enum UpdateStatus {
|
||||
|
||||
Reference in New Issue
Block a user