Compare commits

..

2 Commits

Author SHA1 Message Date
ff08a56d51 fix:优化文件模块 2025-09-18 18:18:11 +08:00
7d8b7c055c fix:优化文件模块 2025-09-18 18:17:09 +08:00
8 changed files with 177 additions and 101 deletions

View File

@ -1,10 +1,11 @@
RD_PORT=6379 RD_PORT=6379
RD_ADD=127.0.0.1 RD_ADD=127.0.0.1
WS_SECRET=114514 WS_SECRET=
TOKEN=54188 TOKEN=
OPENLIST_API_BASE_URL=http://127.0.0.1:5244 OPENLIST_API_BASE_URL=http://127.0.0.1:5244
OPENLIST_API_BASE_USERNAME=USER OPENLIST_API_BASE_USERNAME=
OPENLIST_API_BASE_PASSWORD=123456 OPENLIST_API_BASE_PASSWORD=
OPENLIST_API_MEME_PATH=/crystelf/meme OPENLIST_API_MEME_PATH=/crystelf/meme
OPENLIST_API_CDN_PATH=/crystelf/cdn OPENLIST_API_CDN_PATH=/crystelf/cdn
OPENLIST_API_BASE_PATH=D:\alist

View File

@ -1,9 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PathModule } from '../path/path.module'; import { PathModule } from '../path/path.module';
import { FilesService } from './files.service'; import { FilesService } from './files.service';
import { OpenListModule } from '../openlist/openlist.module';
import { AppConfigModule } from '../../config/config.module';
@Module({ @Module({
imports: [PathModule], imports: [PathModule, OpenListModule, AppConfigModule],
providers: [FilesService], providers: [FilesService],
exports: [FilesService], exports: [FilesService],
}) })

View File

@ -2,6 +2,8 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import * as path from 'path'; import * as path from 'path';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { PathService } from '../path/path.service'; import { PathService } from '../path/path.service';
import { OpenListService } from '../openlist/openlist.service';
import { AppConfigService } from '../../config/config.service';
@Injectable() @Injectable()
export class FilesService { export class FilesService {
private readonly logger = new Logger(FilesService.name); private readonly logger = new Logger(FilesService.name);
@ -9,6 +11,10 @@ export class FilesService {
constructor( constructor(
@Inject(PathService) @Inject(PathService)
private readonly paths: PathService, private readonly paths: PathService,
@Inject(AppConfigService)
private readonly configService: AppConfigService,
@Inject(OpenListService)
private readonly openListService: OpenListService,
) {} ) {}
/** /**
@ -30,4 +36,102 @@ export class FilesService {
this.logger.error(`创建目录失败: ${err}`); this.logger.error(`创建目录失败: ${err}`);
} }
} }
/**
*
* @param localPath
* @param localFiles
* @param remoteFiles
* @param remoteMemePath
* @param replacPath ,: `\crystelf\meme`
* @private
*/
public async compareAndDownloadFiles(
localPath: string,
localFiles: string[],
remoteFiles: any[],
remoteMemePath: string,
replacPath: string,
) {
const remoteBasePath = this.configService.get(`OPENLIST_API_BASE_PATH`);
for (const remoteFile of remoteFiles) {
let relativePath = path.relative(remoteMemePath, remoteFile.path);
//this.logger.debug(`relativePath: ${relativePath}`);
//this.logger.debug(remoteBasePath);
if (remoteBasePath) {
let remoteRelativePath = relativePath.replace(remoteBasePath, ''); //服务器下载用目录
relativePath = remoteRelativePath.replace(replacPath, ''); //本地储存用
this.logger.debug(`relativePath: ${relativePath}`);
this.logger.debug(`remoteRelativePath: ${remoteRelativePath}`);
const localFilePath = path.join(
localPath,
relativePath.replace(/\\/g, '/'),
);
if (remoteFile.is_dir) {
try {
//const localDirPath = path.dirname(localFilePath);
//await fs.mkdir(localDirPath, { recursive: true });
//this.logger.log(`文件夹已创建: ${localDirPath}`);
//相关逻辑已在oplist工具中处理
const subRemoteFiles =
await this.openListService.listFiles(remoteRelativePath);
if (subRemoteFiles.code === 200 && subRemoteFiles.data.content) {
await this.compareAndDownloadFiles(
localPath,
[],
subRemoteFiles.data.content,
remoteMemePath,
replacPath,
);
}
} catch (error) {
this.logger.error(`递归处理文件夹失败: ${localFilePath}`, error);
}
} else {
if (!localFiles.includes(localFilePath)) {
this.logger.log(`文件缺失: ${localFilePath}, 开始下载..`);
try {
await this.openListService.downloadFile(
remoteRelativePath,
localFilePath,
);
this.logger.log(`文件下载成功: ${localFilePath}`);
} catch (error) {
this.logger.error(`下载文件失败: ${localFilePath}`, error);
}
}
}
} else {
this.logger.error(`未配置远程根路径..`);
}
}
}
/**
*
* @param dir
* @private
*/
public async getLocalFileList(dir: string): Promise<string[]> {
const files: string[] = [];
const dirs: string[] = [];
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
dirs.push(fullPath);
} else {
files.push(fullPath);
}
}
for (const subDir of dirs) {
const subFiles = await this.getLocalFileList(subDir);
files.push(...subFiles);
}
} catch (error) {
this.logger.error(`读取本地目录失败: ${dir}`, error);
}
return files;
}
} }

View File

@ -40,7 +40,8 @@ export class OpenListUtils {
} }
} catch (error) { } catch (error) {
this.logger.error('获取Token失败..', error); this.logger.error('获取Token失败..', error);
throw new Error('获取Token失败..'); //throw new Error('获取Token失败..');
return 'null';
} }
} }
@ -55,7 +56,7 @@ export class OpenListUtils {
let data = JSON.stringify({ let data = JSON.stringify({
path: path, path: path,
}); });
//this.logger.debug(path); this.logger.debug(path);
let config = { let config = {
method: 'post', method: 'post',
url: `${url}`, url: `${url}`,
@ -66,6 +67,7 @@ export class OpenListUtils {
data: data, data: data,
}; };
let response = await axios(config); let response = await axios(config);
//this.logger.debug(response);
this.logger.log(`列出目录${path}成功..`); this.logger.log(`列出目录${path}成功..`);
return response.data; return response.data;
} catch (error) { } catch (error) {
@ -81,6 +83,7 @@ export class OpenListUtils {
*/ */
static async getFileInfo(token: string, filePath: string): Promise<FileInfo> { static async getFileInfo(token: string, filePath: string): Promise<FileInfo> {
const url = `${this.apiBaseUrl}/api/fs/get`; const url = `${this.apiBaseUrl}/api/fs/get`;
//this.logger.debug(filePath);
try { try {
let data = JSON.stringify({ let data = JSON.stringify({
path: filePath, path: filePath,

View File

@ -2,9 +2,12 @@ import { Module } from '@nestjs/common';
import { CdnController } from './cdn.controller'; import { CdnController } from './cdn.controller';
import { CdnService } from './cdn.service'; import { CdnService } from './cdn.service';
import { PathModule } from '../../core/path/path.module'; import { PathModule } from '../../core/path/path.module';
import { OpenListModule } from '../../core/openlist/openlist.module';
import { AppConfigModule } from '../../config/config.module';
import { FilesModule } from '../../core/files/files.module';
@Module({ @Module({
imports: [PathModule], imports: [PathModule, OpenListModule, AppConfigModule, FilesModule],
controllers: [CdnController], controllers: [CdnController],
providers: [CdnService], providers: [CdnService],
}) })

View File

@ -2,17 +2,63 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import * as path from 'path'; import * as path from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { PathService } from '../../core/path/path.service'; import { PathService } from '../../core/path/path.service';
import { OpenListService } from '../../core/openlist/openlist.service';
import { AppConfigService } from '../../config/config.service';
import { FilesService } from '../../core/files/files.service';
@Injectable() @Injectable()
export class CdnService { export class CdnService {
private readonly logger = new Logger(CdnService.name); private readonly logger = new Logger(CdnService.name);
private filePath: string; private filePath: string;
private readonly updateMs = 1 * 60 * 100; // 15min
@Inject(PathService) @Inject(PathService)
private readonly paths: PathService; private readonly paths: PathService;
constructor() { constructor(
@Inject(PathService)
private readonly pathService: PathService,
@Inject(OpenListService)
private readonly openListService: OpenListService,
@Inject(AppConfigService)
private readonly configService: AppConfigService,
@Inject(FilesService)
private readonly filesService: FilesService,
) {
this.startAutoUpdate();
this.logger.log(`晶灵云图数据中心初始化.. 数据存储在: ${this.filePath}`); this.logger.log(`晶灵云图数据中心初始化.. 数据存储在: ${this.filePath}`);
} }
private startAutoUpdate() {
setInterval(async () => {
const cdnPath = path.join(this.pathService.get('private'));
const remoteCdnPath = this.configService.get('OPENLIST_API_CDN_PATH');
if (remoteCdnPath) {
this.logger.log('定时检查晶灵cdn更新..');
try {
const remoteFiles =
await this.openListService.listFiles(remoteCdnPath);
if (remoteFiles.code === 200 && remoteFiles.data.content) {
let remoteFileList = remoteFiles.data.content;
const localFiles =
await this.filesService.getLocalFileList(cdnPath);
await this.filesService.compareAndDownloadFiles(
cdnPath,
localFiles,
remoteFileList,
remoteCdnPath,
'\crystelf\cdn',
);
} else {
this.logger.error(`晶灵cdn检查更新失败: ${remoteFiles.code}`);
}
} catch (error) {
this.logger.error(`晶灵cdn检查更新失败: ${error}`);
}
} else {
this.logger.warn('未配置远程表情包地址..');
}
}, this.updateMs);
}
/** /**
* *
* @param relativePath * @param relativePath
@ -25,10 +71,8 @@ export class CdnService {
) { ) {
throw new Error('非法路径请求'); throw new Error('非法路径请求');
} }
const filePath = path.join(this.filePath, relativePath); const filePath = path.join(this.filePath, relativePath);
this.logger.debug(`尝试访问文件路径: ${filePath}`); this.logger.debug(`尝试访问文件路径: ${filePath}`);
return existsSync(filePath) ? filePath : null; return existsSync(filePath) ? filePath : null;
} }

View File

@ -6,6 +6,7 @@ import { ToolsModule } from '../../core/tools/tools.module';
import { RedisModule } from '../../core/redis/redis.module'; import { RedisModule } from '../../core/redis/redis.module';
import { OpenListModule } from '../../core/openlist/openlist.module'; import { OpenListModule } from '../../core/openlist/openlist.module';
import { AppConfigModule } from '../../config/config.module'; import { AppConfigModule } from '../../config/config.module';
import { FilesModule } from '../../core/files/files.module';
@Module({ @Module({
imports: [ imports: [
@ -14,6 +15,7 @@ import { AppConfigModule } from '../../config/config.module';
ToolsModule, ToolsModule,
RedisModule, RedisModule,
AppConfigModule, AppConfigModule,
FilesModule,
], ],
providers: [MemeService], providers: [MemeService],
controllers: [MemeController], controllers: [MemeController],

View File

@ -4,6 +4,7 @@ import * as fs from 'fs/promises';
import { PathService } from '../../core/path/path.service'; import { PathService } from '../../core/path/path.service';
import { OpenListService } from '../../core/openlist/openlist.service'; import { OpenListService } from '../../core/openlist/openlist.service';
import { AppConfigService } from '../../config/config.service'; import { AppConfigService } from '../../config/config.service';
import { FilesService } from '../../core/files/files.service';
@Injectable() @Injectable()
export class MemeService { export class MemeService {
@ -17,6 +18,8 @@ export class MemeService {
private readonly openListService: OpenListService, private readonly openListService: OpenListService,
@Inject(AppConfigService) @Inject(AppConfigService)
private readonly configService: AppConfigService, private readonly configService: AppConfigService,
@Inject(FilesService)
private readonly filesService: FilesService,
) { ) {
this.startAutoUpdate(); this.startAutoUpdate();
} }
@ -32,13 +35,15 @@ export class MemeService {
await this.openListService.listFiles(remoteMemePath); await this.openListService.listFiles(remoteMemePath);
if (remoteFiles.code === 200 && remoteFiles.data.content) { if (remoteFiles.code === 200 && remoteFiles.data.content) {
let remoteFileList = remoteFiles.data.content; let remoteFileList = remoteFiles.data.content;
const localFiles = await this.getLocalFileList(memePath); const localFiles =
await this.filesService.getLocalFileList(memePath);
//this.logger.debug(localFiles); //this.logger.debug(localFiles);
await this.compareAndDownloadFiles( await this.filesService.compareAndDownloadFiles(
memePath, memePath,
localFiles, localFiles,
remoteFileList, remoteFileList,
remoteMemePath, remoteMemePath,
'\crystelf\meme',
); );
} else { } else {
this.logger.error('获取远程表情仓库文件失败..'); this.logger.error('获取远程表情仓库文件失败..');
@ -52,94 +57,6 @@ export class MemeService {
}, this.updateMs); }, this.updateMs);
} }
/**
*
* @param dir
* @private
*/
private async getLocalFileList(dir: string): Promise<string[]> {
const files: string[] = [];
const dirs: string[] = [];
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
dirs.push(fullPath);
} else if (/\.(jpg|jpeg|png|gif|webp)$/i.test(entry.name)) {
files.push(fullPath);
}
}
for (const subDir of dirs) {
const subFiles = await this.getLocalFileList(subDir);
files.push(...subFiles);
}
} catch (error) {
this.logger.error(`读取本地目录失败: ${dir}`, error);
}
return files;
}
/**
*
* @param localPath
* @param localFiles
* @param remoteFiles
* @param remoteMemePath
* @private
*/
private async compareAndDownloadFiles(
localPath: string,
localFiles: string[],
remoteFiles: any[],
remoteMemePath: string,
) {
for (const remoteFile of remoteFiles) {
let relativePath = path.relative(remoteMemePath, remoteFile.path);
//this.logger.debug(`relativePath: ${relativePath}`);
let remoteRelativePath = relativePath.replace(/D:\\alist/g, ''); //服务器下载用目录
relativePath = relativePath.replace(/D:\\alist\\crystelf\\meme/g, ''); //本地储存用
//this.logger.debug(`relativeEdPath: ${relativePath}`);
const localFilePath = path.join(
localPath,
relativePath.replace(/\\/g, '/'),
);
if (remoteFile.is_dir) {
try {
//const localDirPath = path.dirname(localFilePath);
//await fs.mkdir(localDirPath, { recursive: true });
//this.logger.log(`文件夹已创建: ${localDirPath}`);
//相关逻辑已在oplist工具中处理
const subRemoteFiles =
await this.openListService.listFiles(remoteRelativePath);
if (subRemoteFiles.code === 200 && subRemoteFiles.data.content) {
await this.compareAndDownloadFiles(
localPath,
[],
subRemoteFiles.data.content,
remoteMemePath,
);
}
} catch (error) {
this.logger.error(`递归处理文件夹失败: ${localFilePath}`, error);
}
} else {
if (!localFiles.includes(localFilePath)) {
this.logger.log(`文件缺失: ${localFilePath}, 开始下载..`);
try {
await this.openListService.downloadFile(
remoteRelativePath,
localFilePath,
);
this.logger.log(`文件下载成功: ${localFilePath}`);
} catch (error) {
this.logger.error(`下载文件失败: ${localFilePath}`, error);
}
}
}
}
}
/** /**
* *
* @param character * @param character