实现 LocalStorage 存储支持,添加跳过配置功能及相关 API,更新 Docker 部署兼容性测试脚本
This commit is contained in:
+42
-2
@@ -2,6 +2,7 @@
|
||||
|
||||
import { AdminConfig } from './admin.types';
|
||||
import { D1Storage } from './d1.db';
|
||||
import { LocalStorage } from './localstorage.db';
|
||||
import { RedisStorage } from './redis.db';
|
||||
import { Favorite, IStorage, PlayRecord } from './types';
|
||||
import { UpstashRedisStorage } from './upstash.db';
|
||||
@@ -26,8 +27,8 @@ function createStorage(): IStorage {
|
||||
return new D1Storage();
|
||||
case 'localstorage':
|
||||
default:
|
||||
// 默认返回内存实现,保证本地开发可用
|
||||
return null as unknown as IStorage;
|
||||
// 使用 LocalStorage 实现,适用于本地开发和简单部署
|
||||
return new LocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +182,45 @@ export class DbManager {
|
||||
await (this.storage as any).setAdminConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 跳过配置 ----------
|
||||
async getSkipConfig(
|
||||
userName: string,
|
||||
key: string
|
||||
): Promise<any> {
|
||||
if (typeof (this.storage as any).getSkipConfig === 'function') {
|
||||
return (this.storage as any).getSkipConfig(userName, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async saveSkipConfig(
|
||||
userName: string,
|
||||
key: string,
|
||||
config: any
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).setSkipConfig === 'function') {
|
||||
await (this.storage as any).setSkipConfig(userName, key, config);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(
|
||||
userName: string
|
||||
): Promise<{ [key: string]: any }> {
|
||||
if (typeof (this.storage as any).getAllSkipConfigs === 'function') {
|
||||
return (this.storage as any).getAllSkipConfigs(userName);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async deleteSkipConfig(
|
||||
userName: string,
|
||||
key: string
|
||||
): Promise<void> {
|
||||
if (typeof (this.storage as any).deleteSkipConfig === 'function') {
|
||||
await (this.storage as any).deleteSkipConfig(userName, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出默认实例
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
/* eslint-disable no-console */
|
||||
import { AdminConfig } from './admin.types';
|
||||
import { EpisodeSkipConfig, Favorite, IStorage, PlayRecord } from './types';
|
||||
|
||||
/**
|
||||
* LocalStorage 存储实现
|
||||
* 主要用于本地开发和简单部署场景
|
||||
*/
|
||||
export class LocalStorage implements IStorage {
|
||||
private getStorageKey(prefix: string, userName: string, key?: string): string {
|
||||
if (key) {
|
||||
return `katelyatv_${prefix}_${userName}_${key}`;
|
||||
}
|
||||
return `katelyatv_${prefix}_${userName}`;
|
||||
}
|
||||
|
||||
// ---------- 播放记录 ----------
|
||||
async getPlayRecord(userName: string, key: string): Promise<PlayRecord | null> {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('playrecord', userName, key);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting play record:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setPlayRecord(userName: string, key: string, record: PlayRecord): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('playrecord', userName, key);
|
||||
localStorage.setItem(storageKey, JSON.stringify(record));
|
||||
} catch (error) {
|
||||
console.error('Error setting play record:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllPlayRecords(userName: string): Promise<{ [key: string]: PlayRecord }> {
|
||||
if (typeof window === 'undefined') return {};
|
||||
|
||||
try {
|
||||
const prefix = this.getStorageKey('playrecord', userName);
|
||||
const records: { [key: string]: PlayRecord } = {};
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const storageKey = localStorage.key(i);
|
||||
if (storageKey && storageKey.startsWith(prefix + '_')) {
|
||||
const key = storageKey.replace(prefix + '_', '');
|
||||
const data = localStorage.getItem(storageKey);
|
||||
if (data) {
|
||||
records[key] = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return records;
|
||||
} catch (error) {
|
||||
console.error('Error getting all play records:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async deletePlayRecord(userName: string, key: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('playrecord', userName, key);
|
||||
localStorage.removeItem(storageKey);
|
||||
} catch (error) {
|
||||
console.error('Error deleting play record:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 收藏 ----------
|
||||
async getFavorite(userName: string, key: string): Promise<Favorite | null> {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('favorite', userName, key);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting favorite:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setFavorite(userName: string, key: string, favorite: Favorite): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('favorite', userName, key);
|
||||
localStorage.setItem(storageKey, JSON.stringify(favorite));
|
||||
} catch (error) {
|
||||
console.error('Error setting favorite:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllFavorites(userName: string): Promise<{ [key: string]: Favorite }> {
|
||||
if (typeof window === 'undefined') return {};
|
||||
|
||||
try {
|
||||
const prefix = this.getStorageKey('favorite', userName);
|
||||
const favorites: { [key: string]: Favorite } = {};
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const storageKey = localStorage.key(i);
|
||||
if (storageKey && storageKey.startsWith(prefix + '_')) {
|
||||
const key = storageKey.replace(prefix + '_', '');
|
||||
const data = localStorage.getItem(storageKey);
|
||||
if (data) {
|
||||
favorites[key] = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return favorites;
|
||||
} catch (error) {
|
||||
console.error('Error getting all favorites:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFavorite(userName: string, key: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('favorite', userName, key);
|
||||
localStorage.removeItem(storageKey);
|
||||
} catch (error) {
|
||||
console.error('Error deleting favorite:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 用户管理 ----------
|
||||
async registerUser(userName: string, password: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('user', userName);
|
||||
const userData = { password, createdAt: new Date().toISOString() };
|
||||
localStorage.setItem(storageKey, JSON.stringify(userData));
|
||||
} catch (error) {
|
||||
console.error('Error registering user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async verifyUser(userName: string, password: string): Promise<boolean> {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('user', userName);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
if (!data) return false;
|
||||
|
||||
const userData = JSON.parse(data);
|
||||
return userData.password === password;
|
||||
} catch (error) {
|
||||
console.error('Error verifying user:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkUserExist(userName: string): Promise<boolean> {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('user', userName);
|
||||
return localStorage.getItem(storageKey) !== null;
|
||||
} catch (error) {
|
||||
console.error('Error checking user existence:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 搜索历史 ----------
|
||||
async getSearchHistory(userName: string): Promise<string[]> {
|
||||
if (typeof window === 'undefined') return [];
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('searchhistory', userName);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch (error) {
|
||||
console.error('Error getting search history:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async addSearchHistory(userName: string, keyword: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const history = await this.getSearchHistory(userName);
|
||||
// 移除重复项并添加到开头
|
||||
const newHistory = [keyword, ...history.filter(item => item !== keyword)];
|
||||
// 限制历史记录数量
|
||||
const limitedHistory = newHistory.slice(0, 50);
|
||||
|
||||
const storageKey = this.getStorageKey('searchhistory', userName);
|
||||
localStorage.setItem(storageKey, JSON.stringify(limitedHistory));
|
||||
} catch (error) {
|
||||
console.error('Error adding search history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('searchhistory', userName);
|
||||
|
||||
if (!keyword) {
|
||||
// 删除所有搜索历史
|
||||
localStorage.removeItem(storageKey);
|
||||
} else {
|
||||
// 删除特定搜索历史
|
||||
const history = await this.getSearchHistory(userName);
|
||||
const newHistory = history.filter(item => item !== keyword);
|
||||
localStorage.setItem(storageKey, JSON.stringify(newHistory));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting search history:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 跳过配置 ----------
|
||||
async getSkipConfig(userName: string, key: string): Promise<EpisodeSkipConfig | null> {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('skipconfig', userName, key);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting skip config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setSkipConfig(userName: string, key: string, config: EpisodeSkipConfig): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('skipconfig', userName, key);
|
||||
localStorage.setItem(storageKey, JSON.stringify(config));
|
||||
} catch (error) {
|
||||
console.error('Error setting skip config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllSkipConfigs(userName: string): Promise<{ [key: string]: EpisodeSkipConfig }> {
|
||||
if (typeof window === 'undefined') return {};
|
||||
|
||||
try {
|
||||
const prefix = this.getStorageKey('skipconfig', userName);
|
||||
const configs: { [key: string]: EpisodeSkipConfig } = {};
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const storageKey = localStorage.key(i);
|
||||
if (storageKey && storageKey.startsWith(prefix + '_')) {
|
||||
const key = storageKey.replace(prefix + '_', '');
|
||||
const data = localStorage.getItem(storageKey);
|
||||
if (data) {
|
||||
configs[key] = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configs;
|
||||
} catch (error) {
|
||||
console.error('Error getting all skip configs:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSkipConfig(userName: string, key: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('skipconfig', userName, key);
|
||||
localStorage.removeItem(storageKey);
|
||||
} catch (error) {
|
||||
console.error('Error deleting skip config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 管理员功能 ----------
|
||||
async getAllUsers(): Promise<string[]> {
|
||||
if (typeof window === 'undefined') return [];
|
||||
|
||||
try {
|
||||
const users: string[] = [];
|
||||
const prefix = 'katelyatv_user_';
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const storageKey = localStorage.key(i);
|
||||
if (storageKey && storageKey.startsWith(prefix)) {
|
||||
const userName = storageKey.replace(prefix, '');
|
||||
users.push(userName);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
} catch (error) {
|
||||
console.error('Error getting all users:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getAdminConfig(): Promise<AdminConfig | null> {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const data = localStorage.getItem('katelyatv_admin_config');
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.error('Error getting admin config:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setAdminConfig(config: AdminConfig): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
localStorage.setItem('katelyatv_admin_config', JSON.stringify(config));
|
||||
} catch (error) {
|
||||
console.error('Error setting admin config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 用户管理(管理员功能)----------
|
||||
async changePassword(userName: string, newPassword: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
const storageKey = this.getStorageKey('user', userName);
|
||||
const data = localStorage.getItem(storageKey);
|
||||
if (!data) {
|
||||
throw new Error('用户不存在');
|
||||
}
|
||||
|
||||
const userData = JSON.parse(data);
|
||||
userData.password = newPassword;
|
||||
userData.updatedAt = new Date().toISOString();
|
||||
localStorage.setItem(storageKey, JSON.stringify(userData));
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteUser(userName: string): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
// 删除用户账号
|
||||
const userKey = this.getStorageKey('user', userName);
|
||||
localStorage.removeItem(userKey);
|
||||
|
||||
// 删除用户相关的所有数据
|
||||
const prefixes = ['playrecord', 'favorite', 'searchhistory', 'skipconfig'];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
const dataPrefix = this.getStorageKey(prefix, userName);
|
||||
const keysToRemove: string[] = [];
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const storageKey = localStorage.key(i);
|
||||
if (storageKey && (storageKey === dataPrefix || storageKey.startsWith(dataPrefix + '_'))) {
|
||||
keysToRemove.push(storageKey);
|
||||
}
|
||||
}
|
||||
|
||||
keysToRemove.forEach(key => localStorage.removeItem(key));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user