您的当前位置:首页正文

Nestjs上传图片/视频至服务器(支持多图片+多视频)

2024-11-08 来源:个人技术集锦

思考:数据库不适合使用blob来存储图片视频等,我们可以在用户上传静态资源时,后端将静态资源存储在服务器文件夹中,使用时间戳方式重新命名防止资源重名覆盖,将资源路径存储在数据库中。阅读本文你将会使用nestjs搭建服务器上传静态资源至mysql数据库

nest g resource upload
情景1:用户需要上传单张图片。
step1 首先在生成的upload.module.ts模块中指定存储路径,destination为图片存储路径,filename是将时间戳重命名,extname截取后缀拼接。

将图片存储在images文件夹下,并且不会重名覆盖。

import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';



@Module({
  imports: [
    MulterModule.register({ 
      storage: diskStorage({
        destination: join(__dirname, "../images"),
        filename: (_, file, callback) => {
          const fileName = `${new Date().getTime() + extname(file.originalname)}`
          return callback(null, fileName)
        }
      })
    }),
  ],
  controllers: [UploadController],
  providers: [UploadService],
})
export class UploadModule { }
 step2 main.ts中配置静态路径   prefix是虚拟前缀
 app.useStaticAssets(join(__dirname, 'images'), {
    prefix: '/xiaoluo/images'
  })

 step3 接受上传文件

在upload.controller.ts控制器中使用 UseInterceptors   
FileInterceptor是单个文件  读取字段名称   参数 使用 UploadedFile 装饰器接受file 文件 、整理data返回给前端      注意 API_POST是服务器端口  3000

import { Controller, Get, Post, Body, Patch, Param, Delete, UploadedFile, UploadedFiles, UseInterceptors, HttpStatus } from '@nestjs/common';
import { UploadService } from './upload.service';
import { CreateUploadDto } from './dto/create-upload.dto';
import { UpdateUploadDto } from './dto/update-upload.dto';
import { FileInterceptor } from '@nestjs/platform-express';
import { API_PORT } from '@/config';
 
@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) { }

  @Post('album')
  @UseInterceptors(FileInterceptor('file'))
  upload(@UploadedFile() file) {
    console.log(file)
    const data = {
      fileName: file.filename,
      imgURL: `http://127.0.0.1:${API_PORT}/xiaoluo/images/${file.filename}`,
      size: file.size,
      type: 'image'
    }
    return {
      data,
      code: HttpStatus.OK,
      msg: '上传成功'
    }
  }
}
step4 测试 

apifox测试上传单张图片  实际请求的是 

服务器

浏览器


情景2:用户需要上传多张图片,多个视频

要求在服务器端静态资源区分开图片和视频

step1 main.ts文件夹中配置好静态路径

 现在修改一下静态路径配置  需要区分图片和视频      /xiaoluo   就相当于resources文件夹

 app.useStaticAssets(join(__dirname, 'resources'), {
    prefix: '/xiaoluo'
  })
step2  upload.module.ts中使用工厂函数 useFactory

这里添加了Math.random()*1E9 拼接时间戳命名, 是因为多个静态资源上传仅使用时间戳可能会导致重名从而导致资源覆盖,将图片视频资源区分放在resources文件夹下,使用fs来判断是否存在存储静态资源的文件夹,若不存在则创建一个,注意fs需要使用require引入

import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
const fs = require('fs');
@Module({
  imports: [
    MulterModule.registerAsync({
      useFactory: () => ({
        storage: diskStorage({
          destination: (req, file, callback) => {
            const uploadFile = file.mimetype.startsWith('video/') ? 'videos' : 'images';
            const folderPath = join(__dirname, '../resources/', uploadFile);
            fs.existsSync(folderPath) || fs.mkdirSync(folderPath, { recursive: true });
            return callback(null,folderPath)
          },
          filename: (req, file, callback) => {
            const fileName = `${new Date().getTime()}-${Math.random() * 1E9}${extname(file.originalname)}`
            return callback(null, fileName)
          },
        }),
      })

    }),
  ],
  controllers: [UploadController],
  providers: [UploadService],
})
export class UploadModule { }
step3  upload.controller.ts中返回上传成功数据至前端

图片与视频的区别可由 file.mimetype来区分 利用data数组返回各资源的信息 

  @Post('albums')
  @UseInterceptors(FilesInterceptor('files'))
  uoloadFiles(@UploadedFiles() files) {
    if (!files) return handleResult(HttpStatus.BAD_REQUEST, 'files不能为空')
    const data = [];
    files.forEach(file => {
      const isImage = file.mimetype.startsWith('image/');
      const isVideo = file.mimetype.startsWith('video/');

      if (isImage) {
        data.push({
          filename: file.filename,
          imgURL: `http://127.0.0.1:${API_PORT}/xiaoluo/images/${file.filename}`,
          size: file.size,
          type: 'image', // 添加文件类型标识
        });
      } else if (isVideo) {
        data.push({
          filename: file.filename,
          videoURL: `http://127.0.0.1:${API_PORT}/xiaoluo/videos/${file.filename}`,
          size: file.size,
          type: 'video', // 添加文件类型标识
        });
      }
    })
    return {
      data,
      msg: '上传成功',
      code: HttpStatus.OK
    };
  }
step 4 测试   

这里实际的请求为   ""  上传了3张照片,2个视频

我们可以看到resources下的  images有了三张新增的图片,videos下有了两个新增的视频,

它们是由事件戳+随机数组成重新命名的

贴出返回的data

{
    "data": {
        "data": [
            {
                "filename": "1694055101271-447801383.86596245.mp4",
                "videoURL": "http://127.0.0.1:3000/xiaoluo/videos/1694055101271-447801383.86596245.mp4",
                "size": 2133610,
                "type": "video"
            },
            {
                "filename": "1694055101290-927929695.0190749.mp4",
                "videoURL": "http://127.0.0.1:3000/xiaoluo/videos/1694055101290-927929695.0190749.mp4",
                "size": 11591089,
                "type": "video"
            },
            {
                "filename": "1694055101374-116357625.88050213.webp",
                "imgURL": "http://127.0.0.1:3000/xiaoluo/images/1694055101374-116357625.88050213.webp",
                "size": 18176,
                "type": "image"
            },
            {
                "filename": "1694055101375-88369715.42459083.webp",
                "imgURL": "http://127.0.0.1:3000/xiaoluo/images/1694055101375-88369715.42459083.webp",
                "size": 9866,
                "type": "image"
            },
            {
                "filename": "1694055101375-343681736.23349094.jpg",
                "imgURL": "http://127.0.0.1:3000/xiaoluo/images/1694055101375-343681736.23349094.jpg",
                "size": 25733,
                "type": "image"
            }
        ],
        "msg": "上传成功",
        "code": 200
    },
    "success": true,
    "message": "成功"
}

Top