134 lines
3.5 KiB
TypeScript
134 lines
3.5 KiB
TypeScript
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,
|
||
};
|
||
}
|
||
}
|