Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions application.example.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
server:
PORT: REPLACEYOURPORT

kodo:
ACCESS_KEY: REPLACEYOURACCESSKEY
SECRET_KEY: REPLACEYOURSECRETKEY
BUCKET: REPLACEYOURBUCKET
BASE_URL: REPLACEYOURBASEURL

database:
type: mysql
host: REPLACEYOURHOST
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
"@nestjs/jwt": "^11.0.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/serve-static": "^5.0.3",
"@nestjs/typeorm": "^11.0.0",
"@types/multer": "^1.4.12",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"mysql2": "^3.13.0",
"qiniu": "^7.14.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.21",
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions src/upload/upload.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UploadController } from './upload.controller';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Upload } from './entities/upload.entity';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
imports: [TypeOrmModule.forFeature([Upload])],
imports: [
TypeOrmModule.forFeature([Upload]),
ServeStaticModule.forRoot({
rootPath: join(process.cwd(), 'uploads'),
serveRoot: '/uploads',
}),
],
controllers: [UploadController],
providers: [UploadService],
})
Expand Down
131 changes: 33 additions & 98 deletions src/upload/upload.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,56 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as qiniu from 'qiniu';
import { Upload } from './entities/upload.entity';

interface QiniuResponse {
hash: string;
key: string;
}

interface QiniuCallbackInfo {
statusCode: number;
}
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

@Injectable()
export class UploadService {
private mac: qiniu.auth.digest.Mac;
private config: qiniu.conf.Config;
private bucketManager: qiniu.rs.BucketManager;
private uploadToken: string;
private uploadDir: string;
private baseUrl: string;

constructor(
private configService: ConfigService,
@InjectRepository(Upload)
private uploadRepository: Repository<Upload>,
) {
// 初始化七牛云配置
this.mac = new qiniu.auth.digest.Mac(
this.configService.get('kodo.ACCESS_KEY'),
this.configService.get('kodo.SECRET_KEY'),
);
this.config = new qiniu.conf.Config();
// 设置存储区域
this.config.zone = qiniu.zone.Zone_z0; // 根据你的空间所在的区域选择:华东(z0)、华北(z1)、华南(z2)、北美(na0)等
this.bucketManager = new qiniu.rs.BucketManager(this.mac, this.config);
const putPolicy = new qiniu.rs.PutPolicy({
scope: this.configService.get('kodo.BUCKET'),
});
this.uploadToken = putPolicy.uploadToken(this.mac);
this.uploadDir = path.join(process.cwd(), 'uploads');

// 确保上传目录存在
if (!fs.existsSync(this.uploadDir)) {
fs.mkdirSync(this.uploadDir, { recursive: true });
}
}

async uploadFile(file: Express.Multer.File): Promise<Upload> {
const formUploader = new qiniu.form_up.FormUploader(this.config);
const putExtra = new qiniu.form_up.PutExtra();

// 验证配置是否存在
const accessKey = this.configService.get<string>('kodo.ACCESS_KEY');
const secretKey = this.configService.get<string>('kodo.SECRET_KEY');
const bucket = this.configService.get<string>('kodo.BUCKET');
const baseUrl = this.configService.get<string>('kodo.BASE_URL');
try {
// 生成文件hash
const hash = crypto.createHash('sha256')
.update(file.buffer)
.digest('hex');

if (!accessKey || !secretKey || !bucket || !baseUrl) {
throw new Error(
'Missing required Qiniu configuration. Please check your configuration file.',
);
}
// 生成文件名(使用原始文件扩展名)
const ext = path.extname(file.originalname);
const key = `${hash}${ext}`;
const filePath = path.join(this.uploadDir, key);

return new Promise<Upload>((resolve, reject) => {
formUploader.put(
this.uploadToken,
null, // 使用七牛云生成的文件名
file.buffer,
putExtra,
(
err: Error | undefined,
body: QiniuResponse,
info: QiniuCallbackInfo,
) => {
try {
// 检查是否有错误
if (err) {
console.error('Upload error:', err);
return reject(
new Error('File upload failed due to an internal error.'),
);
}
// 保存文件
await fs.promises.writeFile(filePath, file.buffer);

// 检查 HTTP 状态码是否为成功
if (info.statusCode !== 200) {
console.error('Upload failed with status code:', info.statusCode);
return reject(
new Error(
`File upload failed with status code: ${info.statusCode}`,
),
);
}
// 获取基础 URL 并构造上传对象
const baseUrl = this.configService.get<string>('kodo.BASE_URL');
const upload = new Upload();
upload.hash = body.hash;
upload.key = body.key;
upload.url = `${baseUrl}/${body.key}`;
// 创建上传记录
const upload = new Upload();
upload.hash = hash;
upload.key = key;
upload.url = `/uploads/${key}`;

// 将上传信息保存到数据库
try {
this.uploadRepository
.save(upload)
.then((savedUpload) => resolve(savedUpload))
.catch((error) =>
reject(new Error(`Failed to save upload: ${error.message}`)),
); // 成功时返回保存的对象
} catch (dbError) {
console.error('Database save error:', dbError);
reject(
new Error('Failed to save upload information to the database.'),
);
}
} catch (unexpectedError) {
// 捕获任何意外错误
console.error('Unexpected error during upload:', unexpectedError);
reject(
new Error('An unexpected error occurred during file upload.'),
);
}
},
);
});
// 保存到数据库
return await this.uploadRepository.save(upload);
} catch (error) {
console.error('Upload error:', error);
throw new Error('File upload failed: ' + error.message);
}
}

async getFiles(page: number = 1, limit: number = 10) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading