mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-10-14 13:29:19 +00:00
Compare commits
3 Commits
97d536766e
...
4310e94547
Author | SHA1 | Date | |
---|---|---|---|
4310e94547 | |||
fcd50a2569 | |||
cbea0c2b33 |
@ -2,3 +2,6 @@ RD_PORT=6379
|
|||||||
RD_ADD=127.0.0.1
|
RD_ADD=127.0.0.1
|
||||||
WS_SECRET=114514
|
WS_SECRET=114514
|
||||||
TOKEN=54188
|
TOKEN=54188
|
||||||
|
OPENLIST_API_BASE_URL=http://127.0.0.1:5244
|
||||||
|
OPENLIST_API_BASE_USERNAME=USER
|
||||||
|
OPENLIST_API_BASE_PASSWORD=123456
|
@ -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
8
pnpm-lock.yaml
generated
@ -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:
|
||||||
|
@ -13,6 +13,7 @@ import { BotModule } from './modules/bot/bot.module';
|
|||||||
import { CdnModule } from './modules/cdn/cdn.module';
|
import { CdnModule } from './modules/cdn/cdn.module';
|
||||||
import { WordsModule } from './modules/words/words.module';
|
import { WordsModule } from './modules/words/words.module';
|
||||||
import { MemeModule } from './modules/meme/meme.module';
|
import { MemeModule } from './modules/meme/meme.module';
|
||||||
|
import { OpenListModule } from './core/openlist/openlist.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -30,6 +31,7 @@ import { MemeModule } from './modules/meme/meme.module';
|
|||||||
CdnModule,
|
CdnModule,
|
||||||
WordsModule,
|
WordsModule,
|
||||||
MemeModule,
|
MemeModule,
|
||||||
|
OpenListModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
10
src/core/openlist/openlist.module.ts
Normal file
10
src/core/openlist/openlist.module.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { OpenListService } from './openlist.service';
|
||||||
|
import { AppConfigModule } from '../../config/config.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [AppConfigModule],
|
||||||
|
providers: [OpenListService],
|
||||||
|
exports: [OpenListService],
|
||||||
|
})
|
||||||
|
export class OpenListModule {}
|
146
src/core/openlist/openlist.service.ts
Normal file
146
src/core/openlist/openlist.service.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { AppConfigService } from '../../config/config.service';
|
||||||
|
import { FileInfo, FsList } from './openlist.types';
|
||||||
|
import { OpenListUtils } from './openlist.utils';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OpenListService {
|
||||||
|
private readonly logger = new Logger(OpenListService.name);
|
||||||
|
private token: string | undefined;
|
||||||
|
private tokenExpireTime: moment.Moment | undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(AppConfigService)
|
||||||
|
private readonly configService: AppConfigService,
|
||||||
|
) {
|
||||||
|
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 Token,如果Token已过期,则重新获取
|
||||||
|
* @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失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出目录下的所有文件
|
||||||
|
* @param path 目录路径
|
||||||
|
* @returns 目录下的文件列表
|
||||||
|
*/
|
||||||
|
public async listFiles(path: string): Promise<FsList> {
|
||||||
|
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 文件
|
||||||
|
* @param filePathOnserver 服务器路径
|
||||||
|
*/
|
||||||
|
public async uploadFile(
|
||||||
|
filePath: string,
|
||||||
|
file: any,
|
||||||
|
filePathOnserver: 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.uploadFile(token, filePath, filePathOnserver, file);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('上传文件失败:', error);
|
||||||
|
throw new Error('上传文件失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/core/openlist/openlist.types.ts
Normal file
63
src/core/openlist/openlist.types.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* /api/fs/list 文件目录列表
|
||||||
|
*/
|
||||||
|
export interface FsList {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
content: [
|
||||||
|
name: string,
|
||||||
|
size: number,
|
||||||
|
is_dir: boolean,
|
||||||
|
modified: string, //修改时间
|
||||||
|
sign: string, //签名
|
||||||
|
thumb: string, //略缩图
|
||||||
|
type: number, //类型
|
||||||
|
];
|
||||||
|
total: number; //总数
|
||||||
|
readme: string; //说明?
|
||||||
|
write: boolean; //是否可写入
|
||||||
|
provider: string;
|
||||||
|
header: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /api/fs/get 获取文件/目录信息
|
||||||
|
*/
|
||||||
|
export interface FileInfo {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
is_dir: boolean;
|
||||||
|
modified: string;
|
||||||
|
sign: string;
|
||||||
|
thumb: string;
|
||||||
|
type: number;
|
||||||
|
raw_url: string; //原始url
|
||||||
|
readme: string;
|
||||||
|
provider: string;
|
||||||
|
created: string; //创建时间
|
||||||
|
header: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /api/fs/put 流式上传文件
|
||||||
|
*/
|
||||||
|
export interface FileUpload {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: {
|
||||||
|
task: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state: number;
|
||||||
|
status: string;
|
||||||
|
progress: number;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
157
src/core/openlist/openlist.utils.ts
Normal file
157
src/core/openlist/openlist.utils.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { AppConfigService } from '../../config/config.service';
|
||||||
|
import { Inject, Logger } from '@nestjs/common';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { FileInfo, FileUpload, FsList } from './openlist.types';
|
||||||
|
|
||||||
|
export class OpenListUtils {
|
||||||
|
private static readonly logger = new Logger(OpenListUtils.name);
|
||||||
|
private static apiBaseUrl: string | undefined;
|
||||||
|
|
||||||
|
static init(@Inject(AppConfigService) configService: AppConfigService) {
|
||||||
|
this.apiBaseUrl = configService.get('OPENLIST_API_BASE_URL');
|
||||||
|
this.logger.log('OpenListUtils初始化..');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 JWT Token
|
||||||
|
* @param username 用户名
|
||||||
|
* @param password 密码
|
||||||
|
* @returns token
|
||||||
|
*/
|
||||||
|
static async getToken(username: string, password: string): Promise<string> {
|
||||||
|
const url = `${this.apiBaseUrl}/api/auth/login`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(url, {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
});
|
||||||
|
//this.logger.debug(response);
|
||||||
|
if (response.data.data.token) {
|
||||||
|
const token: string = response.data.data.token;
|
||||||
|
this.logger.debug(`获取Token成功: ${token}`);
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
this.logger.error(`获取Token失败: ${response.data.data.message}`);
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('获取Token失败..', error);
|
||||||
|
throw new Error('获取Token失败..');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目录列表
|
||||||
|
* @param token 用户 Token
|
||||||
|
* @param path 目录路径
|
||||||
|
*/
|
||||||
|
static async listDirectory(token: string, path: string): Promise<FsList> {
|
||||||
|
const url = `${this.apiBaseUrl}/api/fs/list`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: { path },
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
this.logger.log(`列出目录${path}成功..`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`列出目录${path}失败..`, error);
|
||||||
|
throw new Error(`列出目录${path}失败..`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取某个文件的详细信息
|
||||||
|
* @param token 用户 Token
|
||||||
|
* @param filePath 文件路径
|
||||||
|
*/
|
||||||
|
static async getFileInfo(token: string, filePath: string): Promise<FileInfo> {
|
||||||
|
const url = `${this.apiBaseUrl}/fs/info`;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: { path: filePath },
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
this.logger.log('获取文件信息成功..');
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('获取文件信息失败..', error);
|
||||||
|
throw new Error('获取文件信息失败..');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param token 用户 Token
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @param downloadPath 本地下载路径
|
||||||
|
*/
|
||||||
|
static async downloadFile(
|
||||||
|
token: string,
|
||||||
|
filePath: string,
|
||||||
|
downloadPath: string,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const fileInfo = await this.getFileInfo(token, filePath);
|
||||||
|
const rawUrl = fileInfo.data.raw_url;
|
||||||
|
if (!rawUrl) {
|
||||||
|
this.logger.error('文件没有找到 raw_url 地址..');
|
||||||
|
throw new Error('文件没有找到 raw_url 地址..');
|
||||||
|
}
|
||||||
|
const response = await axios.get(rawUrl, {
|
||||||
|
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 filePathOnServer 服务器上保存的文件路径
|
||||||
|
* @param file 文件流
|
||||||
|
*/
|
||||||
|
static async uploadFile(
|
||||||
|
token: string,
|
||||||
|
filePath: string,
|
||||||
|
filePathOnServer: string,
|
||||||
|
file: fs.ReadStream,
|
||||||
|
): Promise<FileUpload> {
|
||||||
|
const url = `${this.apiBaseUrl}/api/fs/put`;
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Content-Length': file.bytesRead,
|
||||||
|
'File-Path': encodeURIComponent(filePathOnServer),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.put(url, file, {
|
||||||
|
headers,
|
||||||
|
params: {
|
||||||
|
path: filePath,
|
||||||
|
'As-Task': 'true', //作为任务
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.logger.log(`文件上传成功: ${filePathOnServer}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('上传文件失败..', error);
|
||||||
|
throw new Error('上传文件失败..');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user