diff --git a/application.example.yml b/application.example.yml index 9c92495..53c7e2c 100644 --- a/application.example.yml +++ b/application.example.yml @@ -1,12 +1,6 @@ server: PORT: REPLACEYOURPORT -kodo: - ACCESS_KEY: REPLACEYOURACCESSKEY - SECRET_KEY: REPLACEYOURSECRETKEY - BUCKET: REPLACEYOURBUCKET - BASE_URL: REPLACEYOURBASEURL - database: type: mysql host: REPLACEYOURHOST diff --git a/package.json b/package.json index 0359ffc..4e6abf6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1cd8fa..87fdb0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@nestjs/platform-express': specifier: ^11.0.1 version: 11.0.11(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.11) + '@nestjs/serve-static': + specifier: ^5.0.3 + version: 5.0.3(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.11)(express@5.0.1) '@nestjs/typeorm': specifier: ^11.0.0 version: 11.0.0(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.21(mysql2@3.13.0)(reflect-metadata@0.2.2)(ts-node@10.9.2(@swc/core@1.11.5)(@types/node@22.13.9)(typescript@5.8.2))) @@ -676,42 +679,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.0.1': resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.0.1': resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.0.1': resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.0.1': resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.0.1': resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.0.1': resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-win32-arm64-msvc@1.0.1': resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==} @@ -814,6 +824,22 @@ packages: peerDependencies: typescript: '>=4.8.2' + '@nestjs/serve-static@5.0.3': + resolution: {integrity: sha512-0jFjTlSVSLrI+mot8lfm+h2laXtKzCvgsVStv9T1ZBZTDwS26gM5czIhIESmWAod0PfrbCDFiu9C1MglObL8VA==} + peerDependencies: + '@fastify/static': ^8.0.4 + '@nestjs/common': ^11.0.2 + '@nestjs/core': ^11.0.2 + express: ^5.0.1 + fastify: ^5.2.1 + peerDependenciesMeta: + '@fastify/static': + optional: true + express: + optional: true + fastify: + optional: true + '@nestjs/testing@11.0.11': resolution: {integrity: sha512-SoMIrhRpElV53btmGnEwpIQmXn2Xcztb9ae3lv+eVVnPHQuyB2zlgDIQVNjicbj7+3jdycX52KctOoj2eXEo1Q==} peerDependencies: @@ -914,24 +940,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.11.5': resolution: {integrity: sha512-LhBHKjkZq5tJF1Lh0NJFpx7ROnCWLckrlIAIdSt9XfOV+zuEXJQOj+NFcM1eNk17GFfFyUMOZyGZxzYq5dveEQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.11.5': resolution: {integrity: sha512-dCi4xkxXlsk5sQYb3i413Cfh7+wMJeBYTvBZTD5xh+/DgRtIcIJLYJ2tNjWC4/C2i5fj+Ze9bKNSdd8weRWZ3A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.11.5': resolution: {integrity: sha512-K0AC4TreM5Oo/tXNXnE/Gf5+5y/HwUdd7xvUjOpZddcX/RlsbYOKWLgOtA3fdFIuta7XC+vrGKmIhm5l70DSVQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.11.5': resolution: {integrity: sha512-wzum8sYUsvPY7kgUfuqVYTgIPYmBC8KPksoNM1fz5UfhudU0ciQuYvUBD47GIGOevaoxhLkjPH4CB95vh1mJ9w==} @@ -4619,6 +4649,14 @@ snapshots: transitivePeerDependencies: - chokidar + '@nestjs/serve-static@5.0.3(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.11)(express@5.0.1)': + dependencies: + '@nestjs/common': 11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.0.11(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.0.11)(reflect-metadata@0.2.2)(rxjs@7.8.2) + path-to-regexp: 8.2.0 + optionalDependencies: + express: 5.0.1 + '@nestjs/testing@11.0.11(@nestjs/common@11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.11)(@nestjs/platform-express@11.0.11)': dependencies: '@nestjs/common': 11.0.11(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) diff --git a/src/upload/upload.module.ts b/src/upload/upload.module.ts index 6877676..798445c 100644 --- a/src/upload/upload.module.ts +++ b/src/upload/upload.module.ts @@ -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], }) diff --git a/src/upload/upload.service.ts b/src/upload/upload.service.ts index 124c140..a0d2ceb 100644 --- a/src/upload/upload.service.ts +++ b/src/upload/upload.service.ts @@ -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, ) { - // 初始化七牛云配置 - 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 { - const formUploader = new qiniu.form_up.FormUploader(this.config); - const putExtra = new qiniu.form_up.PutExtra(); - - // 验证配置是否存在 - const accessKey = this.configService.get('kodo.ACCESS_KEY'); - const secretKey = this.configService.get('kodo.SECRET_KEY'); - const bucket = this.configService.get('kodo.BUCKET'); - const baseUrl = this.configService.get('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((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('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) { diff --git a/uploads/5777a5357a32f52d87ba6a4070fa628549cf11a207614372268ecb625c62f7ac.jpeg b/uploads/5777a5357a32f52d87ba6a4070fa628549cf11a207614372268ecb625c62f7ac.jpeg new file mode 100644 index 0000000..5442fbf Binary files /dev/null and b/uploads/5777a5357a32f52d87ba6a4070fa628549cf11a207614372268ecb625c62f7ac.jpeg differ