feat:oplist获取token实现

This commit is contained in:
Jerry 2025-09-15 18:08:56 +08:00
parent cbea0c2b33
commit fcd50a2569
4 changed files with 277 additions and 26 deletions

View File

@ -26,6 +26,7 @@
"@nestjs/websockets": "^11.1.6", "@nestjs/websockets": "^11.1.6",
"axios": "1.11.0", "axios": "1.11.0",
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"moment": "^2.30.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"simple-git": "^3.28.0", "simple-git": "^3.28.0",

8
pnpm-lock.yaml generated
View File

@ -41,6 +41,9 @@ importers:
ioredis: ioredis:
specifier: ^5.6.1 specifier: ^5.6.1
version: 5.6.1 version: 5.6.1
moment:
specifier: ^2.30.1
version: 2.30.1
reflect-metadata: reflect-metadata:
specifier: ^0.2.2 specifier: ^0.2.2
version: 0.2.2 version: 0.2.2
@ -2780,6 +2783,9 @@ packages:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true hasBin: true
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@ -6743,6 +6749,8 @@ snapshots:
dependencies: dependencies:
minimist: 1.2.8 minimist: 1.2.8
moment@2.30.1: {}
ms@2.1.3: {} ms@2.1.3: {}
multer@2.0.2: multer@2.0.2:

View File

@ -2,15 +2,157 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import { AppConfigService } from '../../config/config.service'; import { AppConfigService } from '../../config/config.service';
import { DirectoryList, FileInfo, UserInfo } from './openlist.types'; import { DirectoryList, FileInfo, UserInfo } from './openlist.types';
import { OpenListUtils } from './openlist.utils'; import { OpenListUtils } from './openlist.utils';
import * as moment from 'moment';
@Injectable() @Injectable()
export class OpenListService { export class OpenListService {
private readonly logger = new Logger(OpenListService.name); private readonly logger = new Logger(OpenListService.name);
private token: string | undefined;
private tokenExpireTime: moment.Moment | undefined;
constructor( constructor(
@Inject(AppConfigService) @Inject(AppConfigService)
private readonly configService: AppConfigService, private readonly configService: AppConfigService,
) { ) {
OpenListUtils.init(configService); this.initialize().then();
}
/**
*
*/
private async initialize() {
const apiBaseUrl = this.configService.get('OPENLIST_API_BASE_URL');
const username = this.configService.get('OPENLIST_API_BASE_USERNAME');
const password = this.configService.get('OPENLIST_API_BASE_PASSWORD');
OpenListUtils.init(this.configService);
if (username && password) {
this.token = await this.fetchToken(username, password);
this.tokenExpireTime = moment().add(48, 'hours');
this.logger.log(`OpenList服务初始化成功: ${apiBaseUrl}`);
} else {
this.logger.error(
`OpenList服务初始化失败,请检查是否填写.env处的用户名和密码..`,
);
}
}
/**
* OpenList的JWT TokenToken已过期
* @param username
* @param password
* @returns JWT Token
*/
private async fetchToken(
username: string,
password: string,
): Promise<string> {
if (
this.token &&
this.tokenExpireTime &&
moment().isBefore(this.tokenExpireTime)
) {
return this.token;
}
try {
const newToken = await OpenListUtils.getToken(username, password);
this.tokenExpireTime = moment().add(48, 'hours'); //过期时间
return newToken;
} catch (error) {
this.logger.error('获取Token失败:', error);
throw new Error('获取Token失败');
}
}
/**
*
* @returns
*/
public async getUserInfo(): Promise<UserInfo> {
try {
const token = await this.fetchToken(
<string>this.configService.get('OPENLIST_API_BASE_USERNAME'),
<string>this.configService.get('OPENLIST_API_BASE_PASSWORD'),
);
return await OpenListUtils.getUserInfo(token);
} catch (error) {
this.logger.error('获取用户信息失败:', error);
throw new Error('获取用户信息失败');
}
}
/**
*
* @param path
* @returns
*/
public async listFiles(path: string): Promise<DirectoryList[]> {
try {
const token = await this.fetchToken(
<string>this.configService.get('OPENLIST_API_BASE_USERNAME'),
<string>this.configService.get('OPENLIST_API_BASE_PASSWORD'),
);
return await OpenListUtils.listDirectory(token, path);
} catch (error) {
this.logger.error('列出目录失败:', error);
throw new Error('列出目录失败');
}
}
/**
*
* @param filePath
* @returns
*/
public async getFileInfo(filePath: string): Promise<FileInfo> {
try {
const token = await this.fetchToken(
<string>this.configService.get('OPENLIST_API_BASE_USERNAME'),
<string>this.configService.get('OPENLIST_API_BASE_PASSWORD'),
);
return await OpenListUtils.getFileInfo(token, filePath);
} catch (error) {
this.logger.error('获取文件信息失败:', error);
throw new Error('获取文件信息失败');
}
}
/**
*
* @param filePath
* @param downloadPath
*/
public async downloadFile(
filePath: string,
downloadPath: string,
): Promise<void> {
try {
const token = await this.fetchToken(
<string>this.configService.get('OPENLIST_API_BASE_USERNAME'),
<string>this.configService.get('OPENLIST_API_BASE_PASSWORD'),
);
await OpenListUtils.downloadFile(token, filePath, downloadPath);
} catch (error) {
this.logger.error('下载文件失败:', error);
throw new Error('下载文件失败');
}
}
/**
*
* @param filePath
* @param file
*/
public async uploadFile(filePath: string, file: any): Promise<void> {
try {
const token = await this.fetchToken(
<string>this.configService.get('OPENLIST_API_BASE_USERNAME'),
<string>this.configService.get('OPENLIST_API_BASE_PASSWORD'),
);
await OpenListUtils.uploadFile(token, filePath, file);
} catch (error) {
this.logger.error('上传文件失败:', error);
throw new Error('上传文件失败');
}
} }
} }

View File

@ -2,6 +2,7 @@ import axios from 'axios';
import { AppConfigService } from '../../config/config.service'; import { AppConfigService } from '../../config/config.service';
import { Inject, Logger } from '@nestjs/common'; import { Inject, Logger } from '@nestjs/common';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as fs from 'fs';
export class OpenListUtils { export class OpenListUtils {
private static readonly logger = new Logger(OpenListUtils.name); private static readonly logger = new Logger(OpenListUtils.name);
@ -9,6 +10,7 @@ export class OpenListUtils {
static init(@Inject(AppConfigService) configService: AppConfigService) { static init(@Inject(AppConfigService) configService: AppConfigService) {
this.apiBaseUrl = configService.get('OPENLIST_API_BASE_URL'); this.apiBaseUrl = configService.get('OPENLIST_API_BASE_URL');
this.logger.log('OpenListUtils初始化..');
} }
/** /**
@ -18,20 +20,25 @@ export class OpenListUtils {
* @returns token * @returns token
*/ */
static async getToken(username: string, password: string): Promise<string> { static async getToken(username: string, password: string): Promise<string> {
const url = `${this.apiBaseUrl}/auth/token`; const url = `${this.apiBaseUrl}/api/auth/login`;
const hashedPassword = this.hashPassword(password);
try { try {
const response = await axios.post(url, { const response = await axios.post(url, {
username, username: username,
password: hashedPassword, password: password,
}); });
const token = response.data.token; this.logger.debug(response);
if (response.data.data.token) {
const token: string = response.data.data.token;
this.logger.log(`获取Token成功: ${token}`); this.logger.log(`获取Token成功: ${token}`);
return token; return token;
} else {
this.logger.error(`获取Token失败: ${response.data.data.message}`);
return 'null';
}
} catch (error) { } catch (error) {
this.logger.error('获取 Token 失败', error); this.logger.error('获取Token失败..', error);
throw new Error('获取 Token 失败'); throw new Error('获取Token失败..');
} }
} }
@ -47,11 +54,11 @@ export class OpenListUtils {
const response = await axios.get(url, { const response = await axios.get(url, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
this.logger.log('获取用户信息成功'); this.logger.log('获取用户信息成功..');
return response.data; return response.data;
} catch (error) { } catch (error) {
this.logger.error('获取用户信息失败', error); this.logger.error('获取用户信息失败..', error);
throw new Error('获取用户信息失败'); throw new Error('获取用户信息失败..');
} }
} }
@ -69,11 +76,11 @@ export class OpenListUtils {
params: { path }, params: { path },
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
this.logger.log('列出目录成功'); this.logger.log('列出目录成功..');
return response.data; return response.data;
} catch (error) { } catch (error) {
this.logger.error('列出目录失败', error); this.logger.error('列出目录失败..', error);
throw new Error('列出目录失败'); throw new Error('列出目录失败..');
} }
} }
@ -91,11 +98,11 @@ export class OpenListUtils {
params: { path: filePath }, params: { path: filePath },
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
this.logger.log('获取文件信息成功'); this.logger.log('获取文件信息成功..');
return response.data; return response.data;
} catch (error) { } catch (error) {
this.logger.error('获取文件信息失败', error); this.logger.error('获取文件信息失败..', error);
throw new Error('获取文件信息失败'); throw new Error('获取文件信息失败..');
} }
} }
@ -124,17 +131,110 @@ export class OpenListUtils {
this.logger.log(`文件重命名成功: ${oldPath} => ${newPath}`); this.logger.log(`文件重命名成功: ${oldPath} => ${newPath}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
this.logger.error('文件重命名失败', error); this.logger.error('文件重命名失败..', error);
throw new Error('文件重命名失败'); throw new Error('文件重命名失败..');
} }
} }
/** /**
* sha256 hash *
* @param password * @param token Token
* @returns hashed * @param filePath
* @param downloadPath
*/ */
private static hashPassword(password: string): string { static async downloadFile(
return crypto.createHash('sha256').update(password).digest('hex'); token: string,
filePath: string,
downloadPath: string,
): Promise<void> {
const url = `${this.apiBaseUrl}/fs/download`;
try {
const response = await axios.get(url, {
params: { path: filePath },
headers: { Authorization: `Bearer ${token}` },
responseType: 'stream',
});
const writer = fs.createWriteStream(downloadPath);
response.data.pipe(writer);
writer.on('finish', () => {
this.logger.log(`文件下载成功: ${downloadPath}`);
});
writer.on('error', (error) => {
this.logger.error('下载文件失败', error);
throw new Error('下载文件失败');
});
} catch (error) {
this.logger.error('下载文件失败..', error);
throw new Error('下载文件失败..');
}
}
/**
*
* @param token Token
* @param filePath
* @param file
* @returns
*/
static async uploadFile(
token: string,
filePath: string,
file: any,
): Promise<any> {
const url = `${this.apiBaseUrl}/fs/upload`;
const formData = new FormData();
formData.append('file', file);
formData.append('path', filePath);
try {
const response = await axios.post(url, formData, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'multipart/form-data',
},
});
this.logger.log(`文件上传成功: ${filePath}`);
return response.data;
} catch (error) {
this.logger.error('上传文件失败..', error);
throw new Error('上传文件失败..');
}
}
/**
*
* @param token Token
* @param directoryPath
* @returns
*/
static async listFilesInDirectory(
token: string,
directoryPath: string,
): Promise<any[]> {
const directoryInfo = await this.listDirectory(token, directoryPath);
return directoryInfo.filter(
(item: { is_directory: any }) => !item.is_directory,
);
}
/**
*
* @param token Token
* @param filePath
* @param lastModified
* @returns
*/
static async checkFileUpdate(
token: string,
filePath: string,
lastModified: string,
): Promise<boolean> {
const fileInfo = await this.getFileInfo(token, filePath);
return fileInfo.modified_at !== lastModified;
} }
} }