library-picturebook-activity/backend/src/upload/upload.service.ts

134 lines
3.5 KiB
TypeScript
Raw Permalink Normal View History

2026-01-09 18:14:35 +08:00
import { Injectable, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
2026-01-13 11:11:49 +08:00
import { OssService } from '../oss/oss.service';
2026-01-09 18:14:35 +08:00
import * as fs from 'fs';
import * as path from 'path';
import { randomBytes } from 'crypto';
@Injectable()
export class UploadService {
private readonly uploadDir: string;
2026-01-13 11:11:49 +08:00
private readonly useOss: boolean;
2026-01-09 18:14:35 +08:00
2026-01-13 11:11:49 +08:00
constructor(
private configService: ConfigService,
private ossService: OssService,
) {
// 本地上传文件存储目录(作为 COS 的备用方案)
2026-01-09 18:14:35 +08:00
this.uploadDir = path.join(process.cwd(), 'uploads');
2026-01-13 11:11:49 +08:00
// 确保本地上传目录存在
2026-01-09 18:14:35 +08:00
if (!fs.existsSync(this.uploadDir)) {
fs.mkdirSync(this.uploadDir, { recursive: true });
}
2026-01-13 11:11:49 +08:00
// 检查是否使用 COS
this.useOss = this.ossService.isEnabled();
if (this.useOss) {
console.log('文件上传将使用腾讯云 COS');
} else {
console.log('文件上传将使用本地存储,目录:', this.uploadDir);
}
2026-01-09 18:14:35 +08:00
}
async uploadFile(
file: Express.Multer.File,
tenantId?: number,
userId?: number,
): Promise<{ url: string; fileName: string; size: number }> {
if (!file) {
throw new BadRequestException('文件不存在');
}
2026-01-13 11:11:49 +08:00
// 优先使用 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 }> {
2026-01-09 18:14:35 +08:00
// 生成唯一文件名
const fileExt = path.extname(file.originalname);
const uniqueId = randomBytes(16).toString('hex');
const fileName = `${uniqueId}${fileExt}`;
2026-01-13 11:11:49 +08:00
2026-01-09 18:14:35 +08:00
// 根据租户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 });
}
2026-01-13 11:11:49 +08:00
2026-01-09 18:14:35 +08:00
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,
};
}
}