扫描修改为后台扫描,openlist登录增加账号密码
This commit is contained in:
@@ -94,6 +94,8 @@ export interface AdminConfig {
|
||||
OpenListConfig?: {
|
||||
URL: string; // OpenList 服务器地址
|
||||
Token: string; // 认证 Token
|
||||
Username?: string; // 账号(可选,用于登录获取Token)
|
||||
Password?: string; // 密码(可选,用于登录获取Token)
|
||||
RootPath: string; // 根目录路径,默认 "/"
|
||||
LastRefreshTime?: number; // 上次刷新时间戳
|
||||
ResourceCount?: number; // 资源数量
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface MetaInfo {
|
||||
vote_average: number;
|
||||
media_type: 'movie' | 'tv';
|
||||
last_updated: number;
|
||||
failed?: boolean; // 标记是否搜索失败
|
||||
};
|
||||
};
|
||||
last_refresh: number;
|
||||
|
||||
+117
-5
@@ -32,9 +32,94 @@ export interface OpenListGetResponse {
|
||||
export class OpenListClient {
|
||||
constructor(
|
||||
private baseURL: string,
|
||||
private token: string
|
||||
private token: string,
|
||||
private username?: string,
|
||||
private password?: string
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 使用账号密码登录获取Token
|
||||
*/
|
||||
static async login(
|
||||
baseURL: string,
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<string> {
|
||||
const response = await fetch(`${baseURL}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenList 登录失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.code !== 200 || !data.data?.token) {
|
||||
throw new Error('OpenList 登录失败: 未获取到Token');
|
||||
}
|
||||
|
||||
return data.data.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token(如果配置了账号密码)
|
||||
*/
|
||||
private async refreshToken(): Promise<boolean> {
|
||||
if (!this.username || !this.password) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求,如果401则尝试刷新Token后重试
|
||||
*/
|
||||
private async fetchWithRetry(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
retried = false
|
||||
): Promise<Response> {
|
||||
const response = await fetch(url, options);
|
||||
|
||||
// 如果是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);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private getHeaders() {
|
||||
return {
|
||||
Authorization: this.token, // 不带 bearer
|
||||
@@ -48,7 +133,7 @@ export class OpenListClient {
|
||||
page = 1,
|
||||
perPage = 100
|
||||
): Promise<OpenListListResponse> {
|
||||
const response = await fetch(`${this.baseURL}/api/fs/list`, {
|
||||
const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/list`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -69,7 +154,7 @@ export class OpenListClient {
|
||||
|
||||
// 获取文件信息
|
||||
async getFile(path: string): Promise<OpenListGetResponse> {
|
||||
const response = await fetch(`${this.baseURL}/api/fs/get`, {
|
||||
const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/get`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -87,7 +172,7 @@ export class OpenListClient {
|
||||
|
||||
// 上传文件
|
||||
async uploadFile(path: string, content: string): Promise<void> {
|
||||
const response = await fetch(`${this.baseURL}/api/fs/put`, {
|
||||
const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/put`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: this.token,
|
||||
@@ -102,6 +187,33 @@ export class OpenListClient {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`OpenList 上传失败: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
// 上传成功后刷新目录缓存
|
||||
const dir = path.substring(0, path.lastIndexOf('/')) || '/';
|
||||
await this.refreshDirectory(dir);
|
||||
}
|
||||
|
||||
// 刷新目录缓存
|
||||
async refreshDirectory(path: string): Promise<void> {
|
||||
try {
|
||||
const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/list`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
path,
|
||||
password: '',
|
||||
refresh: true,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`刷新目录缓存失败: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('刷新目录缓存失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
@@ -109,7 +221,7 @@ export class OpenListClient {
|
||||
const dir = path.substring(0, path.lastIndexOf('/')) || '/';
|
||||
const fileName = path.substring(path.lastIndexOf('/') + 1);
|
||||
|
||||
const response = await fetch(`${this.baseURL}/api/fs/remove`, {
|
||||
const response = await this.fetchWithRetry(`${this.baseURL}/api/fs/remove`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, no-console */
|
||||
|
||||
/**
|
||||
* 后台扫描任务管理
|
||||
*/
|
||||
|
||||
export interface ScanTask {
|
||||
id: string;
|
||||
status: 'running' | 'completed' | 'failed';
|
||||
progress: {
|
||||
current: number;
|
||||
total: number;
|
||||
currentFolder?: string;
|
||||
};
|
||||
result?: {
|
||||
total: number;
|
||||
new: number;
|
||||
existing: number;
|
||||
errors: number;
|
||||
};
|
||||
error?: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
}
|
||||
|
||||
const tasks = new Map<string, ScanTask>();
|
||||
|
||||
export function createScanTask(): string {
|
||||
const id = `scan_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
||||
const task: ScanTask = {
|
||||
id,
|
||||
status: 'running',
|
||||
progress: {
|
||||
current: 0,
|
||||
total: 0,
|
||||
},
|
||||
startTime: Date.now(),
|
||||
};
|
||||
tasks.set(id, task);
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getScanTask(id: string): ScanTask | null {
|
||||
return tasks.get(id) || null;
|
||||
}
|
||||
|
||||
export function updateScanTaskProgress(
|
||||
id: string,
|
||||
current: number,
|
||||
total: number,
|
||||
currentFolder?: string
|
||||
): void {
|
||||
let task = tasks.get(id);
|
||||
if (!task) {
|
||||
// 如果任务不存在(可能因为模块重新加载),重新创建任务
|
||||
console.warn(`[ScanTask] 任务 ${id} 不存在,重新创建`);
|
||||
task = {
|
||||
id,
|
||||
status: 'running',
|
||||
progress: {
|
||||
current: 0,
|
||||
total: 0,
|
||||
},
|
||||
startTime: Date.now(),
|
||||
};
|
||||
tasks.set(id, task);
|
||||
}
|
||||
task.progress = { current, total, currentFolder };
|
||||
}
|
||||
|
||||
export function completeScanTask(
|
||||
id: string,
|
||||
result: ScanTask['result']
|
||||
): void {
|
||||
let task = tasks.get(id);
|
||||
if (!task) {
|
||||
// 如果任务不存在(可能因为模块重新加载),重新创建任务
|
||||
console.warn(`[ScanTask] 任务 ${id} 不存在,重新创建并标记为完成`);
|
||||
task = {
|
||||
id,
|
||||
status: 'completed',
|
||||
progress: {
|
||||
current: result?.total || 0,
|
||||
total: result?.total || 0,
|
||||
},
|
||||
startTime: Date.now() - 60000, // 假设任务运行了1分钟
|
||||
endTime: Date.now(),
|
||||
result,
|
||||
};
|
||||
tasks.set(id, task);
|
||||
return;
|
||||
}
|
||||
task.status = 'completed';
|
||||
task.result = result;
|
||||
task.endTime = Date.now();
|
||||
}
|
||||
|
||||
export function failScanTask(id: string, error: string): void {
|
||||
let task = tasks.get(id);
|
||||
if (!task) {
|
||||
// 如果任务不存在(可能因为模块重新加载),重新创建任务
|
||||
console.warn(`[ScanTask] 任务 ${id} 不存在,重新创建并标记为失败`);
|
||||
task = {
|
||||
id,
|
||||
status: 'failed',
|
||||
progress: {
|
||||
current: 0,
|
||||
total: 0,
|
||||
},
|
||||
startTime: Date.now() - 60000, // 假设任务运行了1分钟
|
||||
endTime: Date.now(),
|
||||
error,
|
||||
};
|
||||
tasks.set(id, task);
|
||||
return;
|
||||
}
|
||||
task.status = 'failed';
|
||||
task.error = error;
|
||||
task.endTime = Date.now();
|
||||
}
|
||||
|
||||
export function cleanupOldTasks(): void {
|
||||
const now = Date.now();
|
||||
const maxAge = 60 * 60 * 1000; // 1小时
|
||||
|
||||
for (const [id, task] of Array.from(tasks.entries())) {
|
||||
if (task.endTime && now - task.endTime > maxAge) {
|
||||
tasks.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user