library-picturebook-activity/backend/src/upload/upload.service.ts
2026-01-13 11:11:49 +08:00

134 lines
3.5 KiB
TypeScript
Raw Permalink 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 { Injectable, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { OssService } from '../oss/oss.service';
import * as fs from 'fs';
import * as path from 'path';
import { randomBytes } from 'crypto';
@Injectable()
export class UploadService {
private readonly uploadDir: string;
private readonly useOss: boolean;
constructor(
private configService: ConfigService,
private ossService: OssService,
) {
// 本地上传文件存储目录(作为 COS 的备用方案)
this.uploadDir = path.join(process.cwd(), 'uploads');
// 确保本地上传目录存在
if (!fs.existsSync(this.uploadDir)) {
fs.mkdirSync(this.uploadDir, { recursive: true });
}
// 检查是否使用 COS
this.useOss = this.ossService.isEnabled();
if (this.useOss) {
console.log('文件上传将使用腾讯云 COS');
} else {
console.log('文件上传将使用本地存储,目录:', this.uploadDir);
}
}
async uploadFile(
file: Express.Multer.File,
tenantId?: number,
userId?: number,
): Promise<{ url: string; fileName: string; size: number }> {
if (!file) {
throw new BadRequestException('文件不存在');
}
// 优先使用 COS
if (this.useOss) {
return this.uploadToOss(file, tenantId, userId);
}
// 备用方案:本地存储
return this.uploadToLocal(file, tenantId, userId);
}
/**
* 上传到腾讯云 COS
*/
private async uploadToOss(
file: Express.Multer.File,
tenantId?: number,
userId?: number,
): Promise<{ url: string; fileName: string; size: number }> {
try {
const result = await this.ossService.uploadFile(
file.buffer,
file.originalname,
tenantId,
userId,
);
return {
url: result.url,
fileName: result.fileName,
size: file.size,
};
} catch (error: any) {
console.error('COS 上传失败,尝试本地存储:', error.message);
// COS 失败时回退到本地存储
return this.uploadToLocal(file, tenantId, userId);
}
}
/**
* 上传到本地存储
*/
private async uploadToLocal(
file: Express.Multer.File,
tenantId?: number,
userId?: number,
): Promise<{ url: string; fileName: string; size: number }> {
// 生成唯一文件名
const fileExt = path.extname(file.originalname);
const uniqueId = randomBytes(16).toString('hex');
const fileName = `${uniqueId}${fileExt}`;
// 根据租户ID和用户ID创建目录结构uploads/tenantId/userId/
let targetDir = this.uploadDir;
if (tenantId) {
targetDir = path.join(targetDir, `tenant_${tenantId}`);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
if (userId) {
targetDir = path.join(targetDir, `user_${userId}`);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
}
}
// 文件保存路径
const filePath = path.join(targetDir, fileName);
// 保存文件
fs.writeFileSync(filePath, file.buffer);
// 生成访问URL
// 格式:/api/uploads/tenantId/userId/fileName 或 /api/uploads/fileName
let urlPath = '/api/uploads';
if (tenantId) {
urlPath += `/tenant_${tenantId}`;
if (userId) {
urlPath += `/user_${userId}`;
}
}
urlPath += `/${fileName}`;
return {
url: urlPath,
fileName: file.originalname,
size: file.size,
};
}
}