mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-10-14 05:19:19 +00:00
Compare commits
2 Commits
4fb3e00df5
...
ff08a56d51
Author | SHA1 | Date | |
---|---|---|---|
ff08a56d51 | |||
7d8b7c055c |
@ -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
|
||||||
|
|
||||||
|
@ -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],
|
||||||
})
|
})
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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],
|
||||||
})
|
})
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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],
|
||||||
|
@ -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 角色
|
||||||
|
Loading…
x
Reference in New Issue
Block a user