diff --git a/.envExample b/.envExample index b7ae87e..5e45173 100644 --- a/.envExample +++ b/.envExample @@ -1,10 +1,11 @@ RD_PORT=6379 RD_ADD=127.0.0.1 -WS_SECRET=114514 -TOKEN=54188 +WS_SECRET= +TOKEN= OPENLIST_API_BASE_URL=http://127.0.0.1:5244 -OPENLIST_API_BASE_USERNAME=USER -OPENLIST_API_BASE_PASSWORD=123456 +OPENLIST_API_BASE_USERNAME= +OPENLIST_API_BASE_PASSWORD= OPENLIST_API_MEME_PATH=/crystelf/meme OPENLIST_API_CDN_PATH=/crystelf/cdn +OPENLIST_API_BASE_PATH=D:\alist diff --git a/src/core/files/files.module.ts b/src/core/files/files.module.ts index cb88f57..c468515 100644 --- a/src/core/files/files.module.ts +++ b/src/core/files/files.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; import { PathModule } from '../path/path.module'; import { FilesService } from './files.service'; +import { OpenListModule } from '../openlist/openlist.module'; +import { AppConfigModule } from '../../config/config.module'; @Module({ - imports: [PathModule], + imports: [PathModule, OpenListModule, AppConfigModule], providers: [FilesService], exports: [FilesService], }) diff --git a/src/core/files/files.service.ts b/src/core/files/files.service.ts index cd24b10..5ca0b23 100644 --- a/src/core/files/files.service.ts +++ b/src/core/files/files.service.ts @@ -2,6 +2,8 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import * as path from 'path'; import { promises as fs } from 'fs'; import { PathService } from '../path/path.service'; +import { OpenListService } from '../openlist/openlist.service'; +import { AppConfigService } from '../../config/config.service'; @Injectable() export class FilesService { private readonly logger = new Logger(FilesService.name); @@ -9,6 +11,10 @@ export class FilesService { constructor( @Inject(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}`); } } + + /** + * 比较本地文件和远程文件 下载缺失的文件 + * @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 { + 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; + } } diff --git a/src/core/openlist/openlist.utils.ts b/src/core/openlist/openlist.utils.ts index 2abf489..0c72c94 100644 --- a/src/core/openlist/openlist.utils.ts +++ b/src/core/openlist/openlist.utils.ts @@ -55,7 +55,7 @@ export class OpenListUtils { let data = JSON.stringify({ path: path, }); - //this.logger.debug(path); + this.logger.debug(path); let config = { method: 'post', url: `${url}`, @@ -66,6 +66,7 @@ export class OpenListUtils { data: data, }; let response = await axios(config); + //this.logger.debug(response); this.logger.log(`列出目录${path}成功..`); return response.data; } catch (error) { @@ -81,6 +82,7 @@ export class OpenListUtils { */ static async getFileInfo(token: string, filePath: string): Promise { const url = `${this.apiBaseUrl}/api/fs/get`; + //this.logger.debug(filePath); try { let data = JSON.stringify({ path: filePath, diff --git a/src/modules/cdn/cdn.module.ts b/src/modules/cdn/cdn.module.ts index b6add60..3d22c5d 100644 --- a/src/modules/cdn/cdn.module.ts +++ b/src/modules/cdn/cdn.module.ts @@ -2,9 +2,12 @@ import { Module } from '@nestjs/common'; import { CdnController } from './cdn.controller'; import { CdnService } from './cdn.service'; 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({ - imports: [PathModule], + imports: [PathModule, OpenListModule, AppConfigModule, FilesModule], controllers: [CdnController], providers: [CdnService], }) diff --git a/src/modules/cdn/cdn.service.ts b/src/modules/cdn/cdn.service.ts index 36fbad9..a18c6e4 100644 --- a/src/modules/cdn/cdn.service.ts +++ b/src/modules/cdn/cdn.service.ts @@ -2,17 +2,63 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import * as path from 'path'; import { existsSync } from 'fs'; 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() export class CdnService { private readonly logger = new Logger(CdnService.name); private filePath: string; + private readonly updateMs = 1 * 60 * 100; // 15min @Inject(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}`); } + 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 文件相对路径 @@ -25,10 +71,8 @@ export class CdnService { ) { throw new Error('非法路径请求'); } - const filePath = path.join(this.filePath, relativePath); this.logger.debug(`尝试访问文件路径: ${filePath}`); - return existsSync(filePath) ? filePath : null; } diff --git a/src/modules/meme/meme.module.ts b/src/modules/meme/meme.module.ts index 479f613..0d053be 100644 --- a/src/modules/meme/meme.module.ts +++ b/src/modules/meme/meme.module.ts @@ -6,6 +6,7 @@ import { ToolsModule } from '../../core/tools/tools.module'; import { RedisModule } from '../../core/redis/redis.module'; import { OpenListModule } from '../../core/openlist/openlist.module'; import { AppConfigModule } from '../../config/config.module'; +import { FilesModule } from '../../core/files/files.module'; @Module({ imports: [ @@ -14,6 +15,7 @@ import { AppConfigModule } from '../../config/config.module'; ToolsModule, RedisModule, AppConfigModule, + FilesModule, ], providers: [MemeService], controllers: [MemeController], diff --git a/src/modules/meme/meme.service.ts b/src/modules/meme/meme.service.ts index 364b244..8ae40d6 100644 --- a/src/modules/meme/meme.service.ts +++ b/src/modules/meme/meme.service.ts @@ -4,6 +4,7 @@ import * as fs from 'fs/promises'; 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() export class MemeService { @@ -17,6 +18,8 @@ export class MemeService { private readonly openListService: OpenListService, @Inject(AppConfigService) private readonly configService: AppConfigService, + @Inject(FilesService) + private readonly filesService: FilesService, ) { this.startAutoUpdate(); } @@ -32,13 +35,15 @@ export class MemeService { await this.openListService.listFiles(remoteMemePath); if (remoteFiles.code === 200 && 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); - await this.compareAndDownloadFiles( + await this.filesService.compareAndDownloadFiles( memePath, localFiles, remoteFileList, remoteMemePath, + '\crystelf\meme', ); } else { this.logger.error('获取远程表情仓库文件失败..'); @@ -52,94 +57,6 @@ export class MemeService { }, this.updateMs); } - /** - * 获取本地目录的文件列表 - * @param dir 本地路径 - * @private - */ - private async getLocalFileList(dir: string): Promise { - 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 角色