crystelf-core/modules/public/file.service.ts
2025-07-07 21:48:42 +08:00

94 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import path from 'path';
import fs from 'fs/promises';
import paths from '../../core/utils/system/path';
import logger from '../../core/utils/system/logger';
import { existsSync } from 'fs';
class FileService {
private readonly filePath: string;
constructor() {
this.filePath = paths.get('files');
logger.info(`晶灵云图数据中心初始化..数据存储在: ${this.filePath}`);
}
/**
* 获取文件
* @param relativePath 文件绝对路径
*/
public async getFile(relativePath: string): Promise<string | null> {
if (!this.isValidPath(relativePath) && !this.isValidFilename(path.basename(relativePath))) {
throw new Error('非法路径请求');
}
const filePath = path.join(this.filePath, relativePath);
logger.debug(`尝试访问文件路径: ${filePath}`);
return existsSync(filePath) ? filePath : null;
}
/**
* 保存文件到目标目录
* @param file multer的file
* @param dir 可选上传目录,相对目录
*/
public async saveUploadedFile(
file: Express.Multer.File,
dir: string = ''
): Promise<{ fullPath: string; relativePath: string }> {
const baseDir = paths.get('uploads');
const targetDir = path.join(baseDir, dir);
if (!existsSync(targetDir)) {
await fs.mkdir(targetDir, { recursive: true });
logger.debug(`已创建上传目录: ${targetDir}`);
}
const fileName = `${Date.now()}-${file.originalname.replace(/\s+/g, '_')}`;
const finalPath = path.join(targetDir, fileName);
await fs.rename(file.path, finalPath);
logger.info(`保存上传文件: ${finalPath}`);
return {
fullPath: finalPath,
relativePath: `uploads/${dir}/${fileName}`,
};
}
/**
* 定时删除文件
* @param filePath 文件绝对路径
* @param timeoutMs 毫秒默认10分钟
*/
public async scheduleDelete(filePath: string, timeoutMs: number = 10 * 60 * 1000): Promise<void> {
setTimeout(async () => {
try {
await fs.unlink(filePath);
logger.info(`已自动删除文件: ${filePath}`);
} catch (err) {
logger.warn(`删除文件失败: ${filePath}`, err);
}
}, timeoutMs);
}
/**
* 检查路径合法性
* @param relativePath
* @private
*/
private isValidPath(relativePath: string): boolean {
try {
const normalized = path.normalize(relativePath);
let flag = true;
if (normalized.startsWith('../') && path.isAbsolute(normalized)) flag = false;
return flag;
} catch (err) {
logger.error(err);
return false;
}
}
private isValidFilename(filename: string): boolean {
return /^[a-zA-Z0-9_\-.]+$/.test(filename);
}
}
export default FileService;