From 84fa95fc5749b9b71faddf9dab1965dae7acf750 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Mon, 22 Dec 2025 21:38:51 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4openlist=20token=E7=9A=84?= =?UTF-8?q?=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/page.tsx | 27 +---- src/app/api/admin/openlist/route.ts | 46 ++++---- src/app/api/cms-proxy/route.ts | 8 +- src/app/api/detail/route.ts | 2 +- src/app/api/openlist/correct/route.ts | 3 +- src/app/api/openlist/detail/route.ts | 3 +- src/app/api/openlist/list/route.ts | 3 +- src/app/api/openlist/play/route.ts | 3 +- src/app/api/openlist/refresh-video/route.ts | 3 +- src/app/api/openlist/refresh/route.ts | 7 +- src/app/api/search/ws/route.ts | 2 +- src/app/layout.tsx | 4 +- src/lib/admin.types.ts | 5 +- src/lib/openlist.client.ts | 112 ++++++++++++-------- 14 files changed, 111 insertions(+), 117 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 0dcbc12..8dff29c 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -2541,7 +2541,6 @@ const OpenListConfigComponent = ({ const { alertModal, showAlert, hideAlert } = useAlertModal(); const { isLoading, withLoading } = useLoadingState(); const [url, setUrl] = useState(''); - const [token, setToken] = useState(''); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [rootPath, setRootPath] = useState('/'); @@ -2558,7 +2557,6 @@ const OpenListConfigComponent = ({ useEffect(() => { if (config?.OpenListConfig) { setUrl(config.OpenListConfig.URL || ''); - setToken(config.OpenListConfig.Token || ''); setUsername(config.OpenListConfig.Username || ''); setPassword(config.OpenListConfig.Password || ''); setRootPath(config.OpenListConfig.RootPath || '/'); @@ -2566,7 +2564,7 @@ const OpenListConfigComponent = ({ }, [config]); useEffect(() => { - if (config?.OpenListConfig?.URL && config?.OpenListConfig?.Token) { + if (config?.OpenListConfig?.URL && config?.OpenListConfig?.Username && config?.OpenListConfig?.Password) { fetchVideos(); } }, [config]); @@ -2595,7 +2593,6 @@ const OpenListConfigComponent = ({ body: JSON.stringify({ action: 'save', URL: url, - Token: token, Username: username, Password: password, RootPath: rootPath, @@ -2731,26 +2728,10 @@ const OpenListConfigComponent = ({ /> -
- - setToken(e.target.value)} - placeholder='your-token' - className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent' - /> -

- 可以直接填写Token,或使用下方账号密码登录获取 -

-
-
{/* 视频列表区域 */} - {config?.OpenListConfig?.URL && config?.OpenListConfig?.Token && ( + {config?.OpenListConfig?.URL && config?.OpenListConfig?.Username && config?.OpenListConfig?.Password && (
diff --git a/src/app/api/admin/openlist/route.ts b/src/app/api/admin/openlist/route.ts index 2311eb7..a8393fb 100644 --- a/src/app/api/admin/openlist/route.ts +++ b/src/app/api/admin/openlist/route.ts @@ -26,7 +26,7 @@ export async function POST(request: NextRequest) { try { const body = await request.json(); - const { action, URL, Token, Username, Password, RootPath } = body; + const { action, URL, Username, Password, RootPath } = body; const authInfo = getAuthInfoFromCookie(request); if (!authInfo || !authInfo.username) { @@ -49,40 +49,30 @@ export async function POST(request: NextRequest) { if (action === 'save') { // 保存配置 - if (!URL) { - return NextResponse.json({ error: '缺少URL参数' }, { status: 400 }); - } - - let finalToken = Token; - - // 如果没有Token但有账号密码,尝试登录获取Token - if (!finalToken && Username && Password) { - try { - console.log('[OpenList Config] 使用账号密码登录获取Token'); - finalToken = await OpenListClient.login(URL, Username, Password); - console.log('[OpenList Config] 登录成功,获取到Token'); - } catch (error) { - console.error('[OpenList Config] 登录失败:', error); - return NextResponse.json( - { error: '使用账号密码登录失败: ' + (error as Error).message }, - { status: 400 } - ); - } - } - - // 检查是否有Token - if (!finalToken) { + if (!URL || !Username || !Password) { return NextResponse.json( - { error: '请提供Token或账号密码' }, + { error: '请提供 URL、账号和密码' }, + { status: 400 } + ); + } + + // 验证账号密码是否正确 + try { + console.log('[OpenList Config] 验证账号密码'); + await OpenListClient.login(URL, Username, Password); + console.log('[OpenList Config] 账号密码验证成功'); + } catch (error) { + console.error('[OpenList Config] 账号密码验证失败:', error); + return NextResponse.json( + { error: '账号密码验证失败: ' + (error as Error).message }, { status: 400 } ); } adminConfig.OpenListConfig = { URL, - Token: finalToken, - Username: Username || undefined, - Password: Password || undefined, + Username, + Password, RootPath: RootPath || '/', LastRefreshTime: adminConfig.OpenListConfig?.LastRefreshTime, ResourceCount: adminConfig.OpenListConfig?.ResourceCount, diff --git a/src/app/api/cms-proxy/route.ts b/src/app/api/cms-proxy/route.ts index a11baf2..0008414 100644 --- a/src/app/api/cms-proxy/route.ts +++ b/src/app/api/cms-proxy/route.ts @@ -264,7 +264,7 @@ async function handleOpenListProxy(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json( { code: 0, msg: 'OpenList 未配置', list: [] }, { status: 200 } @@ -272,7 +272,11 @@ async function handleOpenListProxy(request: NextRequest) { } const rootPath = openListConfig.RootPath || '/'; - const client = new OpenListClient(openListConfig.URL, openListConfig.Token); + const client = new OpenListClient( + openListConfig.URL, + openListConfig.Username, + openListConfig.Password + ); // 读取 metainfo (从数据库或缓存) let metaInfo: MetaInfo | null = getCachedMetaInfo(rootPath); diff --git a/src/app/api/detail/route.ts b/src/app/api/detail/route.ts index 66717fd..580cd4a 100644 --- a/src/app/api/detail/route.ts +++ b/src/app/api/detail/route.ts @@ -26,7 +26,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { throw new Error('OpenList 未配置'); } diff --git a/src/app/api/openlist/correct/route.ts b/src/app/api/openlist/correct/route.ts index 8a27ea5..e956b2a 100644 --- a/src/app/api/openlist/correct/route.ts +++ b/src/app/api/openlist/correct/route.ts @@ -39,7 +39,7 @@ export async function POST(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json( { error: 'OpenList 未配置' }, { status: 400 } @@ -49,7 +49,6 @@ export async function POST(request: NextRequest) { const rootPath = openListConfig.RootPath || '/'; const client = new OpenListClient( openListConfig.URL, - openListConfig.Token, openListConfig.Username, openListConfig.Password ); diff --git a/src/app/api/openlist/detail/route.ts b/src/app/api/openlist/detail/route.ts index 1d40be3..300afea 100644 --- a/src/app/api/openlist/detail/route.ts +++ b/src/app/api/openlist/detail/route.ts @@ -35,7 +35,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json({ error: 'OpenList 未配置' }, { status: 400 }); } @@ -43,7 +43,6 @@ export async function GET(request: NextRequest) { const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folderName}`; const client = new OpenListClient( openListConfig.URL, - openListConfig.Token, openListConfig.Username, openListConfig.Password ); diff --git a/src/app/api/openlist/list/route.ts b/src/app/api/openlist/list/route.ts index e5e2db9..4c18a84 100644 --- a/src/app/api/openlist/list/route.ts +++ b/src/app/api/openlist/list/route.ts @@ -34,7 +34,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json( { error: 'OpenList 未配置', list: [], total: 0 }, { status: 200 } @@ -44,7 +44,6 @@ export async function GET(request: NextRequest) { const rootPath = openListConfig.RootPath || '/'; const client = new OpenListClient( openListConfig.URL, - openListConfig.Token, openListConfig.Username, openListConfig.Password ); diff --git a/src/app/api/openlist/play/route.ts b/src/app/api/openlist/play/route.ts index 9e5fa9f..731db4f 100644 --- a/src/app/api/openlist/play/route.ts +++ b/src/app/api/openlist/play/route.ts @@ -31,7 +31,7 @@ export async function GET(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json({ error: 'OpenList 未配置' }, { status: 400 }); } @@ -41,7 +41,6 @@ export async function GET(request: NextRequest) { const client = new OpenListClient( openListConfig.URL, - openListConfig.Token, openListConfig.Username, openListConfig.Password ); diff --git a/src/app/api/openlist/refresh-video/route.ts b/src/app/api/openlist/refresh-video/route.ts index a33ef6c..50527e3 100644 --- a/src/app/api/openlist/refresh-video/route.ts +++ b/src/app/api/openlist/refresh-video/route.ts @@ -30,7 +30,7 @@ export async function POST(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json({ error: 'OpenList 未配置' }, { status: 400 }); } @@ -38,7 +38,6 @@ export async function POST(request: NextRequest) { const folderPath = `${rootPath}${rootPath.endsWith('/') ? '' : '/'}${folder}`; const client = new OpenListClient( openListConfig.URL, - openListConfig.Token, openListConfig.Username, openListConfig.Password ); diff --git a/src/app/api/openlist/refresh/route.ts b/src/app/api/openlist/refresh/route.ts index 409a070..dccd53b 100644 --- a/src/app/api/openlist/refresh/route.ts +++ b/src/app/api/openlist/refresh/route.ts @@ -39,7 +39,7 @@ export async function POST(request: NextRequest) { const config = await getConfig(); const openListConfig = config.OpenListConfig; - if (!openListConfig || !openListConfig.URL || !openListConfig.Token) { + if (!openListConfig || !openListConfig.URL || !openListConfig.Username || !openListConfig.Password) { return NextResponse.json( { error: 'OpenList 未配置' }, { status: 400 } @@ -66,7 +66,6 @@ export async function POST(request: NextRequest) { performScan( taskId, openListConfig.URL, - openListConfig.Token, openListConfig.RootPath || '/', tmdbApiKey, tmdbProxy, @@ -97,20 +96,18 @@ export async function POST(request: NextRequest) { async function performScan( taskId: string, url: string, - token: string, rootPath: string, tmdbApiKey: string, tmdbProxy?: string, username?: string, password?: string ): Promise { - const client = new OpenListClient(url, token, username, password); + const client = new OpenListClient(url, username!, password!); console.log('[OpenList Refresh] 开始扫描:', { taskId, rootPath, url, - hasToken: !!token, }); // 立即更新进度,确保任务可被查询 diff --git a/src/app/api/search/ws/route.ts b/src/app/api/search/ws/route.ts index 0747b17..c896d41 100644 --- a/src/app/api/search/ws/route.ts +++ b/src/app/api/search/ws/route.ts @@ -34,7 +34,7 @@ export async function GET(request: NextRequest) { const apiSites = await getAvailableApiSites(authInfo.username); // 检查是否配置了 OpenList - const hasOpenList = !!(config.OpenListConfig?.URL && config.OpenListConfig?.Token); + const hasOpenList = !!(config.OpenListConfig?.URL && config.OpenListConfig?.Username && config.OpenListConfig?.Password); // 共享状态 let streamClosed = false; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0918ead..7fc07cc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -90,7 +90,9 @@ export default async function RootLayout({ tmdbApiKey = config.SiteConfig.TMDBApiKey || ''; // 检查是否配置了 OpenList openListEnabled = !!( - config.OpenListConfig?.URL && config.OpenListConfig?.Token + config.OpenListConfig?.URL && + config.OpenListConfig?.Username && + config.OpenListConfig?.Password ); } diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index 8990ec1..528178b 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -93,9 +93,8 @@ export interface AdminConfig { }; OpenListConfig?: { URL: string; // OpenList 服务器地址 - Token: string; // 认证 Token - Username?: string; // 账号(可选,用于登录获取Token) - Password?: string; // 密码(可选,用于登录获取Token) + Username: string; // 账号(用于登录获取Token) + Password: string; // 密码(用于登录获取Token) RootPath: string; // 根目录路径,默认 "/" LastRefreshTime?: number; // 上次刷新时间戳 ResourceCount?: number; // 资源数量 diff --git a/src/lib/openlist.client.ts b/src/lib/openlist.client.ts index 743c750..fd79721 100644 --- a/src/lib/openlist.client.ts +++ b/src/lib/openlist.client.ts @@ -1,5 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +// Token 内存缓存 +const tokenCache = new Map(); + export interface OpenListFile { name: string; size: number; @@ -30,11 +33,12 @@ export interface OpenListGetResponse { } export class OpenListClient { + private token: string = ''; + constructor( private baseURL: string, - private token: string, - private username?: string, - private password?: string + private username: string, + private password: string ) {} /** @@ -69,60 +73,81 @@ export class OpenListClient { } /** - * 刷新Token(如果配置了账号密码) + * 获取缓存的 Token 或重新登录 */ - private async refreshToken(): Promise { - if (!this.username || !this.password) { - return false; + private async getToken(): Promise { + const cacheKey = `${this.baseURL}:${this.username}`; + const cached = tokenCache.get(cacheKey); + + // 如果有缓存且未过期,直接返回 + if (cached && cached.expiresAt > Date.now()) { + this.token = cached.token; + return this.token; } - try { - console.log('[OpenListClient] Token可能失效,尝试使用账号密码重新登录'); - this.token = await OpenListClient.login( - this.baseURL, - this.username, - this.password - ); - console.log('[OpenListClient] Token刷新成功'); - return true; - } catch (error) { - console.error('[OpenListClient] Token刷新失败:', error); - return false; - } + // 否则重新登录 + console.log('[OpenListClient] Token 不存在或已过期,重新登录'); + this.token = await OpenListClient.login( + this.baseURL, + this.username, + this.password + ); + + // 缓存 Token,设置 1 小时过期 + tokenCache.set(cacheKey, { + token: this.token, + expiresAt: Date.now() + 60 * 60 * 1000, + }); + + console.log('[OpenListClient] 登录成功,Token 已缓存'); + return this.token; } /** - * 执行请求,如果401则尝试刷新Token后重试 + * 清除 Token 缓存(当 Token 失效时调用) + */ + private clearTokenCache(): void { + const cacheKey = `${this.baseURL}:${this.username}`; + tokenCache.delete(cacheKey); + console.log('[OpenListClient] Token 缓存已清除'); + } + + /** + * 执行请求,如果401则清除缓存并重新登录后重试 */ private async fetchWithRetry( url: string, options: RequestInit, retried = false ): Promise { - const response = await fetch(url, options); + // 获取 Token + const token = await this.getToken(); - // 如果是401且未重试过且有账号密码,尝试刷新Token后重试 - if (response.status === 401 && !retried && this.username && this.password) { - const refreshed = await this.refreshToken(); - if (refreshed) { - // 更新请求头中的Token - const newOptions = { - ...options, - headers: { - ...options.headers, - Authorization: this.token, - }, - }; - return this.fetchWithRetry(url, newOptions, true); - } + // 更新请求头中的 Token + const requestOptions = { + ...options, + headers: { + ...options.headers, + Authorization: token, + }, + }; + + const response = await fetch(url, requestOptions); + + // 如果是401且未重试过,清除缓存并重新登录后重试 + if (response.status === 401 && !retried) { + console.log('[OpenListClient] 收到 401,清除 Token 缓存并重试'); + this.clearTokenCache(); + return this.fetchWithRetry(url, options, true); } return response; } - private getHeaders() { + private async getHeaders() { + const token = await this.getToken(); return { - Authorization: this.token, // 不带 bearer + Authorization: token, // 不带 bearer 'Content-Type': 'application/json', }; } @@ -135,7 +160,7 @@ export class OpenListClient { ): Promise { const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/list`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify({ path, password: '', @@ -156,7 +181,7 @@ export class OpenListClient { async getFile(path: string): Promise { const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/get`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify({ path, password: '', @@ -172,10 +197,11 @@ export class OpenListClient { // 上传文件 async uploadFile(path: string, content: string): Promise { + const token = await this.getToken(); const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/put`, { method: 'PUT', headers: { - Authorization: this.token, + Authorization: token, 'Content-Type': 'text/plain; charset=utf-8', 'File-Path': encodeURIComponent(path), 'As-Task': 'false', @@ -198,7 +224,7 @@ export class OpenListClient { try { const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/list`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify({ path, password: '', @@ -223,7 +249,7 @@ export class OpenListClient { const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/remove`, { method: 'POST', - headers: this.getHeaders(), + headers: await this.getHeaders(), body: JSON.stringify({ names: [fileName], dir: dir,