安装七牛云SDK
新建qiniu模块
别在意angular的图标
qiniu.service.ts 由于是私有存储,在上传和下载时都需要生成对应的token,具体实现可以查看文档七牛Node.jsSDK文件数据流上传 。注意:这里的key尽量别直接使用文件名,七牛云的上传策略会按key拦截或覆盖文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 import { Injectable } from "@nestjs/common" ;import { ConfigService } from "@nestjs/config" ;import * as qiniu from "qiniu" ;import { Readable } from "stream" ;import { UploadCallback } from "./types" ;import { v4 as uuidV4 } from "uuid" ;@Injectable ()export class QiniuService { constructor (private readonly configService: ConfigService ) {} ak = this .configService .get <string >("qiniu.ak" ); sk = this .configService .get <string >("qiniu.sk" ); bucket = this .configService .get <string >("qiniu.bucket" ); generateMac ( ) { return new qiniu.auth .digest .Mac (this .ak , this .sk ); } generateUploadToken ( ) { const putPolicy = new qiniu.rs .PutPolicy ({ scope : this .bucket , returnBody : '{"key":"$(key)","hash":"$(etag)","imageInfo":$(imageInfo),"fname":"$(fname)","fsize":$(fsize),"type":"$(mimeType)"}' , }); return putPolicy.uploadToken (this .generateMac ()); } async upload (file : Express .Multer .File ): Promise <UploadCallback > { const uploader = new qiniu.form_up .FormUploader ( new qiniu.conf .Config ({ zone : qiniu.zone .Zone_z0 , useHttpsDomain : true , }) ); const putExtra = new qiniu.form_up .PutExtra (); const stream = Readable .from (file.buffer ); return new Promise ((resolve, reject ) => { uploader.putStream ( this .generateUploadToken (), `${uuidV4()} .${file.originalname} ` , stream, putExtra, (e, respBody, respInfo ) => { if (e) { reject (e); } if (respInfo.statusCode === 200 ) { resolve (respBody); } else { reject (respBody); } } ); }); } async preview (key: string ) { const bucketManager = new qiniu.rs .BucketManager ( this .generateMac (), new qiniu.conf .Config ({ zone : qiniu.zone .Zone_z0 , useHttpsDomain : true , }) ); const domain = this .configService .get <string >("qiniu.domain" ); const deadline = Math .floor (Date .now () / 1000 ) + 3600 ; return bucketManager.privateDownloadUrl (domain, key, deadline); } }
qiniu.controller.ts controller的实现就比较简单,上传使用@nestjs/common
中的UseInterceptors
拦截器注解和UploadedFile
body参数注解即可,具体查看Nestjs文件上传 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import { Controller , Get , Post , Query , UploadedFile , UseInterceptors , } from "@nestjs/common" ; import { Express } from "express" ;import { QiniuService } from "./qiniu.service" ;import { FileInterceptor } from "@nestjs/platform-express" ;import { response } from "../../utils/response" ;import { UploadCallback } from "./types" ;@Controller ("/file" )export class QiniuController { constructor (private readonly qiniuService: QiniuService ) {} @Post ("/upload" ) @UseInterceptors (FileInterceptor ("file" )) async uploadFile (@UploadedFile () file: Express.Multer.File ) { try { const res : UploadCallback = await this .qiniuService .upload (file); const url = await this .qiniuService .preview (res.key ); return response.ok ({ ...res, url, }); } catch (e) { return response.fail ("上传失败" ); } } @Get ("/preview" ) async getPreviewUrl (@Query ("key" ) key: string ) { try { const url = await this .qiniuService .preview (key); return response.ok (url); } catch (e) { return response.fail (); } } }
qiniu.module.ts 最后将controller和service分别注册到module中即可。
1 2 3 4 5 6 7 8 9 10 11 12 import { Module } from "@nestjs/common" ;import { QiniuService } from "./qiniu.service" ;import { ConfigModule } from "@nestjs/config" ;import { QiniuController } from "./qiniu.controller" ;@Module ({ imports : [ConfigModule ], controllers : [QiniuController ], providers : [QiniuService ], exports : [QiniuService ], }) export class QiniuModule {}
app.module.ts 别忘了在app中注册QiniuModule
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import { MiddlewareConsumer , Module , NestModule } from "@nestjs/common" ;import { TypeOrmModule } from "@nestjs/typeorm" ;import { TenantMiddleware } from "./middleware/tenant.middleware" ;import { ConfigModule , ConfigService } from "@nestjs/config" ;import config from "./config" ;import { RedisModule } from "./modules/redis/redis.module" ;import { QiniuModule } from "./modules/qiniu/qiniu.module" ;@Module ({ imports : [ ConfigModule .forRoot ({ load : [config], envFilePath : [".env" , ".env.dev" , ".env.prod" , ".env.local" ], }), TypeOrmModule .forRootAsync ({ imports : [ConfigModule ], inject : [ConfigService ], useFactory : (configService: ConfigService ) => ({ type : "mysql" , host : configService.get <string >("database.host" ), port : configService.get <number >("database.port" ), username : configService.get <string >("database.username" ), password : configService.get <string >("database.password" ), database : configService.get <string >("database.name" ), autoLoadEntities : true , synchronize : true , }), }), RedisModule , QiniuModule , ], }) export class AppModule implements NestModule { configure (consumer : MiddlewareConsumer ): any { consumer.apply (TenantMiddleware ).forRoutes ("" ); } }
ApiFox中的测试结果