From 668146f414a841e50f05b1692daddab1dfb1de16 Mon Sep 17 00:00:00 2001
From: djteang <935037887@qq.com>
Date: Wed, 24 Sep 2025 22:12:34 +0800
Subject: [PATCH 1/2] =?UTF-8?q?fixed:=E8=87=AA=E5=AE=9A=E4=B9=89=E4=B8=BB?=
=?UTF-8?q?=E9=A2=98=E5=BA=94=E7=94=A8=E6=89=80=E6=9C=89=E4=BA=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 3 +
src/app/api/admin/theme/route.ts | 59 +++----
src/app/api/theme/route.ts | 46 ------
src/app/layout.tsx | 100 +++---------
src/components/GlobalThemeLoader.tsx | 138 ++++++++--------
src/components/ThemeManager.tsx | 59 ++++---
src/lib/config.ts | 13 ++
src/lib/upstash.db.ts | 228 +++++++++++++++++++--------
src/middleware.ts | 2 +-
9 files changed, 333 insertions(+), 315 deletions(-)
delete mode 100644 src/app/api/theme/route.ts
diff --git a/README.md b/README.md
index 91388b3..6fee402 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ services:
restart: on-failure
ports:
- '3000:3000'
+ - '3001:3001'
environment:
- USERNAME=admin
- PASSWORD=orange
@@ -111,6 +112,7 @@ services:
restart: on-failure
ports:
- '3000:3000'
+ - '3001:3001'
environment:
- USERNAME=admin
- PASSWORD=orange
@@ -147,6 +149,7 @@ services:
restart: on-failure
ports:
- '3000:3000'
+ - '3001:3001'
environment:
- USERNAME=admin
- PASSWORD=orange
diff --git a/src/app/api/admin/theme/route.ts b/src/app/api/admin/theme/route.ts
index 5130bfa..28b3a6c 100644
--- a/src/app/api/admin/theme/route.ts
+++ b/src/app/api/admin/theme/route.ts
@@ -3,6 +3,7 @@ import { getAuthInfoFromCookie } from '@/lib/auth';
import { db } from '@/lib/db';
import { AdminConfig } from '@/lib/admin.types';
import { headers, cookies } from 'next/headers';
+import { getConfig, setCachedConfig, clearCachedConfig } from '@/lib/config';
export async function GET() {
try {
@@ -22,12 +23,8 @@ export async function GET() {
return NextResponse.json({ error: '认证信息无效' }, { status: 401 });
}
- const config = await db.getAdminConfig();
- const themeConfig = config?.ThemeConfig || {
- defaultTheme: 'default' as const,
- customCSS: '',
- allowUserCustomization: true,
- };
+ const config = await getConfig();
+ const themeConfig = config.ThemeConfig;
return NextResponse.json({
success: true,
@@ -75,40 +72,7 @@ export async function POST(request: Request) {
}
// 获取当前配置
- const currentConfig = await db.getAdminConfig();
-
- // 如果没有配置,创建一个基础配置
- let baseConfig: AdminConfig;
- if (!currentConfig) {
- baseConfig = {
- ConfigSubscribtion: {
- URL: "",
- AutoUpdate: false,
- LastCheck: "",
- },
- ConfigFile: "",
- SiteConfig: {
- SiteName: "OrangeTV",
- Announcement: "",
- SearchDownstreamMaxPage: 10,
- SiteInterfaceCacheTime: 30,
- DoubanProxyType: "direct",
- DoubanProxy: "",
- DoubanImageProxyType: "direct",
- DoubanImageProxy: "",
- DisableYellowFilter: false,
- FluidSearch: true,
- RequireDeviceCode: false,
- },
- UserConfig: {
- Users: [],
- },
- SourceConfig: [],
- CustomCategories: [],
- };
- } else {
- baseConfig = currentConfig;
- }
+ const baseConfig = await getConfig();
// 更新主题配置
const updatedConfig: AdminConfig = {
@@ -120,10 +84,23 @@ export async function POST(request: Request) {
},
};
- console.log('保存主题配置:', updatedConfig.ThemeConfig);
+ console.log('=== 保存主题配置 ===');
+ console.log('请求参数:', { defaultTheme, customCSS, allowUserCustomization });
+ console.log('当前存储类型:', process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage');
+ console.log('待保存配置:', updatedConfig.ThemeConfig);
+ console.log('完整配置对象:', JSON.stringify(updatedConfig, null, 2));
+
await db.saveAdminConfig(updatedConfig);
console.log('主题配置保存成功');
+ // 直接更新缓存,确保缓存与数据库同步
+ await setCachedConfig(updatedConfig);
+ console.log('已更新配置缓存');
+
+ // 立即验证缓存中的配置
+ const cachedConfig = await getConfig();
+ console.log('保存后验证缓存中的配置:', cachedConfig.ThemeConfig);
+
return NextResponse.json({
success: true,
message: '主题配置已更新',
diff --git a/src/app/api/theme/route.ts b/src/app/api/theme/route.ts
deleted file mode 100644
index ff47fe5..0000000
--- a/src/app/api/theme/route.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { NextResponse } from 'next/server';
-import { db } from '@/lib/db';
-
-export async function GET() {
- try {
- console.log('API /theme: 开始获取主题配置...');
- const config = await db.getAdminConfig();
-
- console.log('API /theme: 获取到的管理员配置:', {
- hasConfig: !!config,
- hasThemeConfig: !!(config?.ThemeConfig),
- themeConfig: config?.ThemeConfig
- });
-
- const themeConfig = config?.ThemeConfig || {
- defaultTheme: 'default' as const,
- customCSS: '',
- allowUserCustomization: true,
- };
-
- console.log('API /theme: 最终返回的主题配置:', themeConfig);
-
- return NextResponse.json({
- success: true,
- data: themeConfig,
- });
- } catch (error) {
- console.error('API /theme: 获取主题配置失败,返回默认配置:', error);
-
- const defaultThemeConfig = {
- defaultTheme: 'default' as const,
- customCSS: '',
- allowUserCustomization: true,
- };
-
- return NextResponse.json(
- {
- success: true, // 改为 success: true,因为我们提供了有效的默认配置
- data: defaultThemeConfig,
- fallback: true, // 标记这是备用配置
- error: error instanceof Error ? error.message : '未知错误'
- },
- { status: 200 }
- );
- }
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 455031f..5ed01a7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -6,7 +6,6 @@ import { Inter } from 'next/font/google';
import './globals.css';
import { getConfig } from '@/lib/config';
-import { db } from '@/lib/db';
import { GlobalErrorIndicator } from '../components/GlobalErrorIndicator';
import { SiteProvider } from '../components/SiteProvider';
@@ -44,33 +43,6 @@ export default async function RootLayout({
}) {
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
- // 预先获取主题配置
- let themeConfig = {
- defaultTheme: 'default',
- customCSS: '',
- allowUserCustomization: true
- };
-
- try {
- if (storageType !== 'localstorage') {
- const adminConfig = await db.getAdminConfig();
- if (adminConfig?.ThemeConfig) {
- themeConfig = {
- defaultTheme: adminConfig.ThemeConfig.defaultTheme || 'default',
- customCSS: adminConfig.ThemeConfig.customCSS || '',
- allowUserCustomization: adminConfig.ThemeConfig.allowUserCustomization !== false,
- };
- console.log('服务端获取主题配置成功:', themeConfig);
- } else {
- console.log('服务端配置中没有主题配置,使用默认配置');
- }
- } else {
- console.log('LocalStorage模式,使用默认主题配置');
- }
- } catch (error) {
- console.error('服务端获取主题配置失败,使用默认配置:', error);
- }
-
let siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'OrangeTV';
let announcement =
process.env.ANNOUNCEMENT ||
@@ -122,7 +94,6 @@ export default async function RootLayout({
CUSTOM_CATEGORIES: customCategories,
FLUID_SEARCH: fluidSearch,
REQUIRE_DEVICE_CODE: requireDeviceCode,
- THEME_CONFIG: themeConfig,
};
return (
@@ -141,84 +112,59 @@ export default async function RootLayout({
}}
/>
- {/* 主题初始化脚本 - 内联执行,确保在生产环境中立即应用主题 */}
+ {/* 立即从缓存应用主题,避免闪烁 */}
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
+
{
useEffect(() => {
- const validateAndSyncTheme = async () => {
+ const syncThemeWithAPI = async () => {
try {
- // 检查服务端预设的配置
- const runtimeConfig = (window as any).RUNTIME_CONFIG;
- const serverThemeConfig = runtimeConfig?.THEME_CONFIG;
-
- console.log('检查服务端预设主题配置:', serverThemeConfig);
-
- if (serverThemeConfig && serverThemeConfig.defaultTheme !== 'default') {
- // 服务端有非默认配置,优先使用服务端配置
- console.log('使用服务端主题配置,跳过API检查:', serverThemeConfig);
-
- // 确保服务端配置已正确应用到DOM
- const html = document.documentElement;
- const currentTheme = html.getAttribute('data-theme');
-
- if (currentTheme !== serverThemeConfig.defaultTheme) {
- console.log('DOM主题与服务端配置不一致,重新应用:', {
- current: currentTheme,
- expected: serverThemeConfig.defaultTheme
- });
- applyTheme(serverThemeConfig.defaultTheme, serverThemeConfig.customCSS || '');
- } else {
- console.log('服务端主题配置已正确应用,无需更新');
- }
- return;
- }
-
- // 只有当服务端配置为默认值时,才从API获取配置
- console.log('服务端为默认配置,从API获取最新配置...');
- const response = await fetch('/api/theme');
+ console.log('从API同步主题配置...');
+ const response = await fetch('/api/admin/config');
const result = await response.json();
- if (result.success && result.data) {
- const { defaultTheme, customCSS } = result.data;
- const isFallback = result.fallback;
+ if (result?.Config?.ThemeConfig) {
+ const themeConfig = result.Config.ThemeConfig;
+ const { defaultTheme, customCSS, allowUserCustomization } = themeConfig;
- console.log('从API获取到主题配置:', {
+ console.log('API返回主题配置:', {
defaultTheme,
customCSS,
- isFallback: !!isFallback,
- error: result.error
+ allowUserCustomization
});
- if (isFallback) {
- console.log('API返回备用配置,保持服务端设置不变');
- return;
+ // 获取当前缓存的主题配置
+ const cachedTheme = getCachedTheme();
+
+ // 比较API配置与缓存配置
+ const configChanged = !cachedTheme ||
+ cachedTheme.defaultTheme !== defaultTheme ||
+ cachedTheme.customCSS !== customCSS;
+
+ if (configChanged) {
+ console.log('检测到主题配置变更,更新应用:', {
+ from: cachedTheme,
+ to: { defaultTheme, customCSS }
+ });
+ applyAndCacheTheme(defaultTheme, customCSS);
+ } else {
+ console.log('主题配置无变化,保持当前设置');
}
- // 只有API返回非默认配置且非备用配置时才应用
- if (defaultTheme !== 'default' || customCSS) {
- console.log('应用API获取的有效主题配置:', { defaultTheme, customCSS });
- applyTheme(defaultTheme, customCSS);
-
- // 更新运行时配置
- if (runtimeConfig) {
- runtimeConfig.THEME_CONFIG = { defaultTheme, customCSS, allowUserCustomization: true };
- }
- } else {
- console.log('API返回默认配置,保持当前状态');
+ // 将配置存储到运行时配置中,供ThemeManager使用
+ const runtimeConfig = (window as any).RUNTIME_CONFIG;
+ if (runtimeConfig) {
+ runtimeConfig.THEME_CONFIG = themeConfig;
}
} else {
- console.log('API获取失败,保持当前主题配置');
+ console.log('无法获取主题配置,使用默认配置:', result);
+ // API失败时,如果有缓存就保持,没有缓存就用默认
+ const cachedTheme = getCachedTheme();
+ if (!cachedTheme) {
+ console.log('无缓存,应用默认主题');
+ applyAndCacheTheme('default', '');
+ } else {
+ console.log('保持缓存主题:', cachedTheme);
+ }
}
} catch (error) {
- console.error('主题配置验证失败:', error);
-
- // 出错时优先使用服务端配置
- const runtimeConfig = (window as any).RUNTIME_CONFIG;
- const serverThemeConfig = runtimeConfig?.THEME_CONFIG;
-
- if (serverThemeConfig) {
- console.log('错误恢复:使用服务端配置:', serverThemeConfig);
- applyTheme(serverThemeConfig.defaultTheme, serverThemeConfig.customCSS || '');
+ console.error('API同步失败:', error);
+ // 错误时如果有缓存就保持,没有缓存就用默认
+ const cachedTheme = getCachedTheme();
+ if (!cachedTheme) {
+ console.log('无缓存且请求失败,应用默认主题');
+ applyAndCacheTheme('default', '');
} else {
- console.log('错误恢复:使用默认主题');
- applyTheme('default', '');
+ console.log('请求失败,保持缓存主题:', cachedTheme);
}
}
};
+ // 获取缓存的主题配置
+ const getCachedTheme = () => {
+ try {
+ const cached = localStorage.getItem('theme-cache');
+ return cached ? JSON.parse(cached) : null;
+ } catch (error) {
+ console.warn('读取主题缓存失败:', error);
+ localStorage.removeItem('theme-cache');
+ return null;
+ }
+ };
+
+ // 应用主题并缓存
+ const applyAndCacheTheme = (themeId: string, css: string = '') => {
+ applyTheme(themeId, css);
+
+ // 缓存主题配置
+ const themeConfig = { defaultTheme: themeId, customCSS: css };
+ try {
+ localStorage.setItem('theme-cache', JSON.stringify(themeConfig));
+ console.log('主题配置已缓存:', themeConfig);
+ } catch (error) {
+ console.warn('缓存主题配置失败:', error);
+ }
+ };
+
// 应用主题函数
const applyTheme = (themeId: string, css: string = '') => {
const html = document.documentElement;
@@ -108,9 +116,9 @@ const GlobalThemeLoader = () => {
customStyleEl.textContent = css;
};
- // 稍作延迟,确保DOM完全加载后再验证主题
+ // 延迟一点时间,确保页面缓存主题已应用,然后同步API配置
const timer = setTimeout(() => {
- validateAndSyncTheme();
+ syncThemeWithAPI();
}, 100);
return () => clearTimeout(timer);
diff --git a/src/components/ThemeManager.tsx b/src/components/ThemeManager.tsx
index be7e575..25483e9 100644
--- a/src/components/ThemeManager.tsx
+++ b/src/components/ThemeManager.tsx
@@ -277,32 +277,44 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
const isAdmin = role === 'admin' || role === 'owner';
- // 加载全局主题配置
+ // 更新主题缓存的辅助函数
+ const updateThemeCache = (themeId: string, css: string) => {
+ try {
+ const themeConfig = {
+ defaultTheme: themeId,
+ customCSS: css
+ };
+ localStorage.setItem('theme-cache', JSON.stringify(themeConfig));
+ console.log('主题配置已缓存:', themeConfig);
+ } catch (error) {
+ console.warn('缓存主题配置失败:', error);
+ }
+ };
+
+ // 从API加载主题配置(唯一数据源)
const loadGlobalThemeConfig = async () => {
try {
- // 优先使用运行时配置中的主题配置
- const runtimeConfig = (window as any).RUNTIME_CONFIG;
- const serverThemeConfig = runtimeConfig?.THEME_CONFIG;
-
- if (serverThemeConfig) {
- console.log('使用服务端预设的主题配置:', serverThemeConfig);
- setGlobalThemeConfig(serverThemeConfig);
- return serverThemeConfig;
- }
-
- // 如果没有服务端配置,则从API获取
- console.log('服务端无主题配置,从API获取...');
- const response = await fetch('/api/theme');
+ console.log('从API获取主题配置...');
+ const response = await fetch('/api/admin/config');
const result = await response.json();
- if (result.success) {
- if (result.fallback) {
- console.log('API返回备用配置,可能存在数据库访问问题');
+
+ if (result?.Config?.ThemeConfig) {
+ const themeConfig = result.Config.ThemeConfig;
+ console.log('API返回的主题配置:', themeConfig);
+ setGlobalThemeConfig(themeConfig);
+
+ // 更新运行时配置,保持同步
+ const runtimeConfig = (window as any).RUNTIME_CONFIG;
+ if (runtimeConfig) {
+ runtimeConfig.THEME_CONFIG = themeConfig;
}
- setGlobalThemeConfig(result.data);
- return result.data;
+
+ return themeConfig;
+ } else {
+ console.log('无法获取主题配置,可能未登录或权限不足:', result);
}
} catch (error) {
- console.error('加载全局主题配置失败:', error);
+ console.error('从API加载主题配置失败:', error);
}
return null;
};
@@ -332,6 +344,10 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
// 立即应用新的主题配置,确保当前页面也能看到更改
applyTheme(result.data.defaultTheme, result.data.customCSS);
+
+ // 更新本地缓存
+ updateThemeCache(result.data.defaultTheme, result.data.customCSS);
+
console.log('已立即应用新主题配置:', result.data.defaultTheme);
showAlert({
@@ -517,6 +533,9 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => {
allowUserCustomization: globalThemeConfig?.allowUserCustomization ?? true,
};
}
+
+ // 更新本地缓存
+ updateThemeCache(currentTheme, '');
}
showAlert({
diff --git a/src/lib/config.ts b/src/lib/config.ts
index 941678c..43e9119 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -379,6 +379,15 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
adminConfig.SiteConfig.RequireDeviceCode = process.env.NEXT_PUBLIC_REQUIRE_DEVICE_CODE !== 'false';
}
+ // 确保 ThemeConfig 存在
+ if (!adminConfig.ThemeConfig) {
+ adminConfig.ThemeConfig = {
+ defaultTheme: 'default',
+ customCSS: '',
+ allowUserCustomization: true,
+ };
+ }
+
// 站长变更自检
const ownerUser = process.env.USERNAME;
@@ -516,4 +525,8 @@ export async function getAvailableApiSites(user?: string): Promise {
export async function setCachedConfig(config: AdminConfig) {
cachedConfig = config;
+}
+
+export function clearCachedConfig() {
+ cachedConfig = undefined as any;
}
\ No newline at end of file
diff --git a/src/lib/upstash.db.ts b/src/lib/upstash.db.ts
index d715865..78c80ea 100644
--- a/src/lib/upstash.db.ts
+++ b/src/lib/upstash.db.ts
@@ -72,7 +72,14 @@ export class UpstashRedisStorage implements IStorage {
const val = await withRetry(() =>
this.client.get(this.prKey(userName, key))
);
- return val ? (val as PlayRecord) : null;
+ if (!val) return null;
+
+ try {
+ return typeof val === 'string' ? JSON.parse(val) : val as PlayRecord;
+ } catch (error) {
+ console.error('解析播放记录失败:', error);
+ return null;
+ }
}
async setPlayRecord(
@@ -80,7 +87,7 @@ export class UpstashRedisStorage implements IStorage {
key: string,
record: PlayRecord
): Promise {
- await withRetry(() => this.client.set(this.prKey(userName, key), record));
+ await withRetry(() => this.client.set(this.prKey(userName, key), JSON.stringify(record)));
}
async getAllPlayRecords(
@@ -94,9 +101,14 @@ export class UpstashRedisStorage implements IStorage {
for (const fullKey of keys) {
const value = await withRetry(() => this.client.get(fullKey));
if (value) {
- // 截取 source+id 部分
- const keyPart = ensureString(fullKey.replace(`u:${userName}:pr:`, ''));
- result[keyPart] = value as PlayRecord;
+ try {
+ // 截取 source+id 部分
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:pr:`, ''));
+ const record = typeof value === 'string' ? JSON.parse(value) : value as PlayRecord;
+ result[keyPart] = record;
+ } catch (error) {
+ console.error('解析播放记录失败:', error, 'key:', fullKey);
+ }
}
}
return result;
@@ -115,7 +127,14 @@ export class UpstashRedisStorage implements IStorage {
const val = await withRetry(() =>
this.client.get(this.favKey(userName, key))
);
- return val ? (val as Favorite) : null;
+ if (!val) return null;
+
+ try {
+ return typeof val === 'string' ? JSON.parse(val) : val as Favorite;
+ } catch (error) {
+ console.error('解析收藏失败:', error);
+ return null;
+ }
}
async setFavorite(
@@ -124,7 +143,7 @@ export class UpstashRedisStorage implements IStorage {
favorite: Favorite
): Promise {
await withRetry(() =>
- this.client.set(this.favKey(userName, key), favorite)
+ this.client.set(this.favKey(userName, key), JSON.stringify(favorite))
);
}
@@ -137,8 +156,13 @@ export class UpstashRedisStorage implements IStorage {
for (const fullKey of keys) {
const value = await withRetry(() => this.client.get(fullKey));
if (value) {
- const keyPart = ensureString(fullKey.replace(`u:${userName}:fav:`, ''));
- result[keyPart] = value as Favorite;
+ try {
+ const keyPart = ensureString(fullKey.replace(`u:${userName}:fav:`, ''));
+ const favorite = typeof value === 'string' ? JSON.parse(value) : value as Favorite;
+ result[keyPart] = favorite;
+ } catch (error) {
+ console.error('解析收藏失败:', error, 'key:', fullKey);
+ }
}
}
return result;
@@ -270,11 +294,24 @@ export class UpstashRedisStorage implements IStorage {
async getAdminConfig(): Promise {
const val = await withRetry(() => this.client.get(this.adminConfigKey()));
- return val ? (val as AdminConfig) : null;
+ if (!val) return null;
+
+ try {
+ // 尝试解析JSON字符串(兼容BaseRedisStorage格式)
+ if (typeof val === 'string') {
+ return JSON.parse(val) as AdminConfig;
+ }
+ // 如果已经是对象,直接返回(Upstash自动反序列化的情况)
+ return val as AdminConfig;
+ } catch (error) {
+ console.error('解析管理员配置失败:', error);
+ return null;
+ }
}
async setAdminConfig(config: AdminConfig): Promise {
- await withRetry(() => this.client.set(this.adminConfigKey(), config));
+ // 统一使用JSON字符串格式存储,与BaseRedisStorage保持一致
+ await withRetry(() => this.client.set(this.adminConfigKey(), JSON.stringify(config)));
}
// ---------- 跳过片头片尾配置 ----------
@@ -290,7 +327,14 @@ export class UpstashRedisStorage implements IStorage {
const val = await withRetry(() =>
this.client.get(this.skipConfigKey(userName, source, id))
);
- return val ? (val as SkipConfig) : null;
+ if (!val) return null;
+
+ try {
+ return typeof val === 'string' ? JSON.parse(val) : val as SkipConfig;
+ } catch (error) {
+ console.error('解析跳过配置失败:', error);
+ return null;
+ }
}
async setSkipConfig(
@@ -300,7 +344,7 @@ export class UpstashRedisStorage implements IStorage {
config: SkipConfig
): Promise {
await withRetry(() =>
- this.client.set(this.skipConfigKey(userName, source, id), config)
+ this.client.set(this.skipConfigKey(userName, source, id), JSON.stringify(config))
);
}
@@ -332,11 +376,16 @@ export class UpstashRedisStorage implements IStorage {
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;
+ try {
+ // 从key中提取source+id
+ const match = key.match(/^u:.+?:skip:(.+)$/);
+ if (match) {
+ const sourceAndId = match[1];
+ const config = typeof value === 'string' ? JSON.parse(value) : value as SkipConfig;
+ configs[sourceAndId] = config;
+ }
+ } catch (error) {
+ console.error('解析跳过配置失败:', error, 'key:', key);
}
}
});
@@ -373,7 +422,16 @@ export class UpstashRedisStorage implements IStorage {
async getDanmu(videoId: string): Promise {
const val = await withRetry(() => this.client.lrange(this.danmuKey(videoId), 0, -1));
- return val ? val.map(item => JSON.parse(ensureString(item))) : [];
+ if (!val || !Array.isArray(val)) return [];
+
+ return val.map(item => {
+ try {
+ return typeof item === 'string' ? JSON.parse(item) : item;
+ } catch (error) {
+ console.error('解析弹幕数据失败:', error);
+ return null;
+ }
+ }).filter(item => item !== null);
}
async saveDanmu(videoId: string, userName: string, danmu: {
@@ -425,9 +483,11 @@ export class UpstashRedisStorage implements IStorage {
if (!val) return null;
try {
- const data = JSON.parse(ensureString(val));
+ // 处理不同的序列化格式
+ const data = typeof val === 'string' ? JSON.parse(val) : val;
return data.machineCode || null;
- } catch {
+ } catch (error) {
+ console.error('解析用户机器码失败:', error);
return null;
}
}
@@ -439,7 +499,7 @@ export class UpstashRedisStorage implements IStorage {
bindTime: Date.now()
};
- // 保存用户的机器码
+ // 保存用户的机器码 - 统一使用JSON序列化
await withRetry(() =>
this.client.set(this.machineCodeKey(userName), JSON.stringify(data))
);
@@ -481,10 +541,11 @@ export class UpstashRedisStorage implements IStorage {
if (val) {
try {
- const data = JSON.parse(ensureString(val));
+ // 处理不同的序列化格式
+ const data = typeof val === 'string' ? JSON.parse(val) : val;
result[userName] = data;
- } catch {
- // 忽略解析错误
+ } catch (error) {
+ console.error('解析机器码用户数据失败:', error, 'key:', key);
}
}
}
@@ -536,9 +597,9 @@ export class UpstashRedisStorage implements IStorage {
// 消息管理
async saveMessage(message: ChatMessage): Promise {
- // 保存消息详情
+ // 保存消息详情 - 使用JSON序列化
await withRetry(() =>
- this.client.set(this.messageKey(message.id), message)
+ this.client.set(this.messageKey(message.id), JSON.stringify(message))
);
// 将消息ID添加到对话的消息列表中(按时间排序)
@@ -560,7 +621,12 @@ export class UpstashRedisStorage implements IStorage {
for (const messageId of messageIds) {
const messageData = await withRetry(() => this.client.get(this.messageKey(messageId as string)));
if (messageData) {
- messages.push(messageData as ChatMessage);
+ try {
+ const message = typeof messageData === 'string' ? JSON.parse(messageData) : messageData;
+ messages.push(message as ChatMessage);
+ } catch (error) {
+ console.error('解析消息失败:', error);
+ }
}
}
@@ -570,11 +636,15 @@ export class UpstashRedisStorage implements IStorage {
async markMessageAsRead(messageId: string): Promise {
const messageData = await withRetry(() => this.client.get(this.messageKey(messageId)));
if (messageData) {
- const message = messageData as ChatMessage;
- message.is_read = true;
- await withRetry(() =>
- this.client.set(this.messageKey(messageId), message)
- );
+ try {
+ const message = typeof messageData === 'string' ? JSON.parse(messageData) : messageData as ChatMessage;
+ message.is_read = true;
+ await withRetry(() =>
+ this.client.set(this.messageKey(messageId), JSON.stringify(message))
+ );
+ } catch (error) {
+ console.error('标记消息为已读失败:', error);
+ }
}
}
@@ -601,13 +671,20 @@ export class UpstashRedisStorage implements IStorage {
this.client.get(this.conversationKey(conversationId))
);
- return conversationData ? (conversationData as Conversation) : null;
+ if (!conversationData) return null;
+
+ try {
+ return typeof conversationData === 'string' ? JSON.parse(conversationData) : conversationData as Conversation;
+ } catch (error) {
+ console.error('解析对话失败:', error);
+ return null;
+ }
}
async createConversation(conversation: Conversation): Promise {
- // 保存对话详情
+ // 保存对话详情 - 使用JSON序列化
await withRetry(() =>
- this.client.set(this.conversationKey(conversation.id), conversation)
+ this.client.set(this.conversationKey(conversation.id), JSON.stringify(conversation))
);
// 将对话ID添加到每个参与者的对话列表中
@@ -623,7 +700,7 @@ export class UpstashRedisStorage implements IStorage {
if (conversation) {
Object.assign(conversation, updates);
await withRetry(() =>
- this.client.set(this.conversationKey(conversationId), conversation)
+ this.client.set(this.conversationKey(conversationId), JSON.stringify(conversation))
);
}
}
@@ -656,7 +733,12 @@ export class UpstashRedisStorage implements IStorage {
for (const friendId of friendIds) {
const friendData = await withRetry(() => this.client.get(this.friendKey(friendId)));
if (friendData) {
- friends.push(friendData as Friend);
+ try {
+ const friend = typeof friendData === 'string' ? JSON.parse(friendData) : friendData;
+ friends.push(friend as Friend);
+ } catch (error) {
+ console.error('解析好友数据失败:', error);
+ }
}
}
@@ -664,9 +746,9 @@ export class UpstashRedisStorage implements IStorage {
}
async addFriend(userName: string, friend: Friend): Promise {
- // 保存好友详情
+ // 保存好友详情 - 使用JSON序列化
await withRetry(() =>
- this.client.set(this.friendKey(friend.id), friend)
+ this.client.set(this.friendKey(friend.id), JSON.stringify(friend))
);
// 将好友ID添加到用户的好友列表中
@@ -688,11 +770,15 @@ export class UpstashRedisStorage implements IStorage {
async updateFriendStatus(friendId: string, status: Friend['status']): Promise {
const friendData = await withRetry(() => this.client.get(this.friendKey(friendId)));
if (friendData) {
- const friend = friendData as Friend;
- friend.status = status;
- await withRetry(() =>
- this.client.set(this.friendKey(friendId), friend)
- );
+ try {
+ const friend = typeof friendData === 'string' ? JSON.parse(friendData) : friendData as Friend;
+ friend.status = status;
+ await withRetry(() =>
+ this.client.set(this.friendKey(friendId), JSON.stringify(friend))
+ );
+ } catch (error) {
+ console.error('更新好友状态失败:', error);
+ }
}
}
@@ -706,10 +792,14 @@ export class UpstashRedisStorage implements IStorage {
for (const requestId of requestIds) {
const requestData = await withRetry(() => this.client.get(this.friendRequestKey(requestId)));
if (requestData) {
- const request = requestData as FriendRequest;
- // 只返回相关的申请(发送给该用户的或该用户发送的)
- if (request.to_user === userName || request.from_user === userName) {
- requests.push(request);
+ try {
+ const request = typeof requestData === 'string' ? JSON.parse(requestData) : requestData as FriendRequest;
+ // 只返回相关的申请(发送给该用户的或该用户发送的)
+ if (request.to_user === userName || request.from_user === userName) {
+ requests.push(request);
+ }
+ } catch (error) {
+ console.error('解析好友申请失败:', error);
}
}
}
@@ -718,9 +808,9 @@ export class UpstashRedisStorage implements IStorage {
}
async createFriendRequest(request: FriendRequest): Promise {
- // 保存申请详情
+ // 保存申请详情 - 使用JSON序列化
await withRetry(() =>
- this.client.set(this.friendRequestKey(request.id), request)
+ this.client.set(this.friendRequestKey(request.id), JSON.stringify(request))
);
// 将申请ID添加到双方的申请列表中
@@ -735,27 +825,35 @@ export class UpstashRedisStorage implements IStorage {
async updateFriendRequest(requestId: string, status: FriendRequest['status']): Promise {
const requestData = await withRetry(() => this.client.get(this.friendRequestKey(requestId)));
if (requestData) {
- const request = requestData as FriendRequest;
- request.status = status;
- request.updated_at = Date.now();
- await withRetry(() =>
- this.client.set(this.friendRequestKey(requestId), request)
- );
+ try {
+ const request = typeof requestData === 'string' ? JSON.parse(requestData) : requestData as FriendRequest;
+ request.status = status;
+ request.updated_at = Date.now();
+ await withRetry(() =>
+ this.client.set(this.friendRequestKey(requestId), JSON.stringify(request))
+ );
+ } catch (error) {
+ console.error('更新好友申请失败:', error);
+ }
}
}
async deleteFriendRequest(requestId: string): Promise {
const requestData = await withRetry(() => this.client.get(this.friendRequestKey(requestId)));
if (requestData) {
- const request = requestData as FriendRequest;
+ try {
+ const request = typeof requestData === 'string' ? JSON.parse(requestData) : requestData as FriendRequest;
- // 从双方的申请列表中移除
- await withRetry(() =>
- this.client.srem(this.userFriendRequestsKey(request.from_user), requestId)
- );
- await withRetry(() =>
- this.client.srem(this.userFriendRequestsKey(request.to_user), requestId)
- );
+ // 从双方的申请列表中移除
+ await withRetry(() =>
+ this.client.srem(this.userFriendRequestsKey(request.from_user), requestId)
+ );
+ await withRetry(() =>
+ this.client.srem(this.userFriendRequestsKey(request.to_user), requestId)
+ );
+ } catch (error) {
+ console.error('删除好友申请失败:', error);
+ }
}
// 删除申请详情
diff --git a/src/middleware.ts b/src/middleware.ts
index 3070d7b..b672d79 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
// 配置middleware匹配规则
export const config = {
matcher: [
- '/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/theme).*)',
+ '/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config).*)',
],
};
From 49d1d3b8b8c61b05067f3f0f1432188ce30be8e0 Mon Sep 17 00:00:00 2001
From: djteang <935037887@qq.com>
Date: Thu, 25 Sep 2025 00:07:07 +0800
Subject: [PATCH 2/2] =?UTF-8?q?fixed:=E8=87=AA=E5=AE=9A=E4=B9=89=E4=B8=BB?=
=?UTF-8?q?=E9=A2=98=E5=BA=94=E7=94=A8=E6=89=80=E6=9C=89=E4=BA=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/api/admin/config/route.ts | 77 ++++++++++++++++++++++---------
1 file changed, 55 insertions(+), 22 deletions(-)
diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts
index 7544700..846bab7 100644
--- a/src/app/api/admin/config/route.ts
+++ b/src/app/api/admin/config/route.ts
@@ -20,41 +20,74 @@ export async function GET(request: NextRequest) {
}
const authInfo = getAuthInfoFromCookie(request);
- if (!authInfo || !authInfo.username) {
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
- }
- const username = authInfo.username;
+ const username = authInfo?.username;
try {
const config = await getConfig();
- const result: AdminConfigResult = {
- Role: 'owner',
- Config: config,
- };
+
+ // 检查用户权限
+ let userRole = 'guest'; // 未登录用户为 guest
+ let isAdmin = false;
+
if (username === process.env.USERNAME) {
- result.Role = 'owner';
- } else {
+ userRole = 'owner';
+ isAdmin = true;
+ } else if (username) {
const user = config.UserConfig.Users.find((u) => u.username === username);
if (user && user.role === 'admin' && !user.banned) {
- result.Role = 'admin';
+ userRole = 'admin';
+ isAdmin = true;
+ } else if (user && !user.banned) {
+ userRole = 'user';
+ } else if (user && user.banned) {
+ userRole = 'banned';
} else {
- return NextResponse.json(
- { error: '你是管理员吗你就访问?' },
- { status: 401 }
- );
+ // 认证了但用户不存在,可能是数据不同步
+ userRole = 'unknown';
}
}
- return NextResponse.json(result, {
- headers: {
- 'Cache-Control': 'no-store', // 管理员配置不缓存
- },
- });
+ // 根据用户权限返回不同的配置信息
+ if (isAdmin) {
+ // 管理员返回完整配置
+ const result: AdminConfigResult = {
+ Role: userRole as 'admin' | 'owner',
+ Config: config,
+ };
+
+ return NextResponse.json(result, {
+ headers: {
+ 'Cache-Control': 'no-store', // 管理员配置不缓存
+ },
+ });
+ } else {
+ // 普通用户或未登录用户只返回公开配置
+ const publicConfig = {
+ ThemeConfig: config.ThemeConfig,
+ SiteConfig: {
+ SiteName: config.SiteConfig.SiteName,
+ Announcement: config.SiteConfig.Announcement,
+ // 其他公开的站点配置可以在这里添加
+ }
+ };
+
+ const result = {
+ Role: userRole,
+ Config: publicConfig,
+ };
+
+ console.log('返回公开配置给', userRole, ',包含主题配置:', !!publicConfig.ThemeConfig);
+ return NextResponse.json(result, {
+ headers: {
+ 'Cache-Control': 'public, max-age=60', // 公开配置可以缓存1分钟
+ },
+ });
+ }
} catch (error) {
- console.error('获取管理员配置失败:', error);
+ console.error('获取配置失败:', error);
return NextResponse.json(
{
- error: '获取管理员配置失败',
+ error: '获取配置失败',
details: (error as Error).message,
},
{ status: 500 }