思考:数据库不适合使用blob来存储图片视频等,我们可以在用户上传静态资源时,后端将静态资源存储在服务器文件夹中,使用时间戳方式重新命名防止资源重名覆盖,将资源路径存储在数据库中。阅读本文你将会使用nestjs搭建服务器上传静态资源至mysql数据库
nest g resource upload
将图片存储在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 { }
app.useStaticAssets(join(__dirname, 'images'), {
prefix: '/xiaoluo/images'
})
在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: '上传成功'
}
}
}
apifox测试上传单张图片 实际请求的是
服务器
浏览器
要求在服务器端静态资源区分开图片和视频
现在修改一下静态路径配置 需要区分图片和视频 /xiaoluo 就相当于resources文件夹
app.useStaticAssets(join(__dirname, 'resources'), {
prefix: '/xiaoluo'
})
这里添加了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 { }
图片与视频的区别可由 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
};
}
这里实际的请求为 "" 上传了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": "成功"
}