| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- import { GenericCrudService } from '@/server/utils/generic-crud.service';
- import { DataSource } from 'typeorm';
- import { File } from './file.entity';
- import { MinioService } from './minio.service';
- import { v4 as uuidv4 } from 'uuid';
- import { logger } from '@/server/utils/logger';
- export class FileService extends GenericCrudService<File> {
- private readonly minioService: MinioService;
- constructor(dataSource: DataSource) {
- super(dataSource, File);
- this.minioService = new MinioService();
- }
- /**
- * 创建文件记录并生成预签名上传URL
- */
- async createFile(data: Partial<File>) {
- try {
- // 生成唯一文件存储路径
- const fileKey = `${data.uploadUserId}/${uuidv4()}-${data.name}`;
- // 生成MinIO上传策略
- const uploadPolicy = await this.minioService.generateUploadPolicy(fileKey);
-
- // 准备文件记录数据
- const fileData = {
- ...data,
- path: fileKey,
- uploadTime: new Date(),
- createdAt: new Date(),
- updatedAt: new Date()
- };
-
- // 保存文件记录到数据库
- const savedFile = await this.create(fileData as File);
-
- // 返回文件记录和上传策略
- return {
- file: savedFile,
- uploadPolicy
- };
- } catch (error) {
- logger.error('Failed to create file:', error);
- throw new Error('文件创建失败');
- }
- }
- /**
- * 删除文件记录及对应的MinIO文件
- */
- async deleteFile(id: number) {
- // 获取文件记录
- const file = await this.getById(id);
- if (!file) {
- throw new Error('文件不存在');
- }
- try {
- // 验证文件是否存在于MinIO
- const fileExists = await this.minioService.objectExists(this.minioService.bucketName, file.path);
- if (!fileExists) {
- logger.error(`File not found in MinIO: ${this.minioService.bucketName}/${file.path}`);
- // 仍然继续删除数据库记录,但记录警告日志
- } else {
- // 从MinIO删除文件
- await this.minioService.deleteObject(this.minioService.bucketName, file.path);
- }
- // 从数据库删除记录
- await this.delete(id);
- return true;
- } catch (error) {
- logger.error('Failed to delete file:', error);
- throw new Error('文件删除失败');
- }
- }
- /**
- * 获取文件访问URL
- */
- async getFileUrl(id: number) {
- const file = await this.getById(id);
- if (!file) {
- throw new Error('文件不存在');
- }
-
- return this.minioService.getPresignedFileUrl(this.minioService.bucketName, file.path);
- }
- /**
- * 获取文件下载URL(带Content-Disposition头)
- */
- async getFileDownloadUrl(id: number) {
- const file = await this.getById(id);
- if (!file) {
- throw new Error('文件不存在');
- }
-
- const url = await this.minioService.getPresignedFileDownloadUrl(
- this.minioService.bucketName,
- file.path,
- file.name
- );
-
- return {
- url,
- filename: file.name
- };
- }
- /**
- * 创建多部分上传策略
- */
- async createMultipartUploadPolicy(data: Partial<File>, partCount: number) {
- try {
- // 生成唯一文件存储路径
- const fileKey = `${data.uploadUserId}/${uuidv4()}-${data.name}`;
-
- // 初始化多部分上传
- const uploadId = await this.minioService.createMultipartUpload(
- this.minioService.bucketName,
- fileKey
- );
-
- // 生成各部分上传URL
- const uploadUrls = await this.minioService.generateMultipartUploadUrls(
- this.minioService.bucketName,
- fileKey,
- uploadId,
- partCount
- );
-
- // 准备文件记录数据
- const fileData = {
- ...data,
- path: fileKey,
- uploadTime: new Date(),
- createdAt: new Date(),
- updatedAt: new Date()
- };
-
- // 保存文件记录到数据库
- const savedFile = await this.create(fileData as File);
-
- // 返回文件记录和上传策略
- return {
- file: savedFile,
- uploadId,
- uploadUrls,
- bucket: this.minioService.bucketName,
- key: fileKey
- };
- } catch (error) {
- logger.error('Failed to create multipart upload policy:', error);
- throw new Error('创建多部分上传策略失败');
- }
- }
- /**
- * 完成分片上传
- */
- async completeMultipartUpload(data: {
- uploadId: string;
- bucket: string;
- key: string;
- parts: Array<{ partNumber: number; etag: string }>;
- }) {
- logger.db('Starting multipart upload completion:', {
- uploadId: data.uploadId,
- bucket: data.bucket,
- key: data.key,
- partsCount: data.parts.length
- });
- // 查找文件记录
- const file = await this.repository.findOneBy({ path: data.key });
- if (!file) {
- throw new Error('文件记录不存在');
- }
- try {
- // 完成MinIO分片上传 - 注意格式转换
- const result = await this.minioService.completeMultipartUpload(
- data.bucket,
- data.key,
- data.uploadId,
- data.parts.map(part => ({ PartNumber: part.partNumber, ETag: part.etag }))
- );
- // 更新文件大小等信息
- file.size = result.size;
- file.updatedAt = new Date();
- await this.repository.save(file);
- // 生成文件访问URL
- const url = this.minioService.getFileUrl(data.bucket, data.key);
- logger.db('Multipart upload completed successfully:', {
- fileId: file.id,
- size: result.size,
- key: data.key
- });
- return {
- fileId: file.id,
- url,
- key: data.key,
- size: result.size
- };
- } catch (error) {
- logger.error('Failed to complete multipart upload:', error);
- throw new Error('完成分片上传失败');
- }
- }
- }
|