Nest 单文件上传
1. 安装依赖
bash
npm install --save @nestjs/platform-express multer
npm install -D @types/multer
npm install nanoid创建一个模块 upload
upload.module.ts
ts
import { Module } from "@nestjs/common";
import { UploadService } from "./upload.service";
@Module({
providers: [UploadService],
exports: [UploadService],
})
export class UploadModule {}upload.service.ts
ts
import { HttpException, Inject, Injectable } from "@nestjs/common";
import { deleteFile } from "../../commonModules/utils/storage";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class UploadService {
@Inject()
private configService: ConfigService;
async uploadsingle(files, pathname) {
if (files["status"] == 403) {
for (let i = 0; i < files["value"].length; i++) {
deleteFile(files["value"][i].path, true);
}
return {
code: 403,
data: "",
message: files["data"],
};
// throw new HttpException(files['message'], 403);
} else {
const url: string[] = [];
files.forEach((item) => {
url.push(
this.configService.get("APP_STATIC_DOMAIN") +
item.destination +
"/" +
item.filename
);
});
return {
code: 200,
url,
};
}
}
}创建 utils/storage.ts
ts
import * as multer from "multer";
import * as path from "path";
import * as fs from "fs";
import { customAlphabet } from "nanoid";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
// 获取模块名称
console.log(req.body.module);
// 获取年份
const year = new Date().getFullYear();
// 获取月份
const month = new Date().getMonth() + 1;
// 月份前面补0
const formattedMonth = month.toString().padStart(2, "0");
// 获取日期
const day = new Date().getDate();
// 日期前面补0
const formattedDay = day.toString().padStart(2, "0");
// 基础拼接路径
let basicdir =
"/" +
process.env.APP_STATIC +
"/" +
process.env.APP_UPLOAD_PATH +
"/" +
req.body.module +
"/";
// 最终路径
let dir = basicdir + year + "/" + formattedMonth + "/" + formattedDay + "/";
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
cb(null, dir);
},
filename: (req, file, cb) => {
// 获取当前时间戳
const timestamp = new Date().getTime();
const nanoid = customAlphabet(
"1234567890abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ",
6
);
const randomName = nanoid();
return cb(
null,
`${timestamp}_${randomName}${path.extname(file.originalname)}`
);
},
});
/**
* @param { delPath:String } (需要删除文件的地址)
* @param { direct:Boolean } (是否需要处理地址)
*/
const deleteFile = (delPath, direct) => {
delPath = direct ? delPath : path.join(__dirname, delPath);
try {
/**
* @des 判断文件或文件夹是否存在
*/
if (fs.existsSync(delPath)) {
console.log(delPath);
fs.unlinkSync(delPath);
} else {
console.log("inexistence path:", delPath);
}
} catch (error) {
console.log("del error", error);
}
};
export { storage, deleteFile };创建管道验证 upload.pipe.ts
- common/pipe/upload.pipe.ts
ts
import { ArgumentMetadata, Injectable, PipeTransform } from "@nestjs/common";
import dotenv from "dotenv";
dotenv.config();
@Injectable()
export class UploadPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (value.length > 1) {
let count = 0;
let type = 0;
if (value.length > Number(process.env.APP_UPLOAD_MAX_FILE)) {
return {
status: 403,
data: `图片个数不能超过${Number(process.env.APP_UPLOAD_MAX_FILE)}`,
value: value,
};
}
// 多图
value.map((item: any) => {
// 判断大小
if (item.size > 1024 * 1024 * 10) {
count++;
}
// 判断类型
if (item.mimetype.indexOf("image") == -1) {
type++;
}
});
if (count > 0) {
return {
status: 403,
data: "图片大小不能超过10M",
value: value,
};
}
if (type > 0) {
return {
status: 403,
data: "必须是图片",
value: value,
};
}
return value;
} else {
// 单图
if (value[0].size > 1024 * 1024 * 10) {
return {
status: 403,
data: "图片大小不能超过10M",
value: value,
};
}
if (value[0].mimetype.indexOf("image") == -1) {
return {
status: 403,
data: "必须是图片",
value: value,
};
}
return value;
}
}
}其他模块使用
第一步 先引入 upload.module.ts
第二步 在 xxx.service.ts 中注入 uploadService
第三步 写控制器
ts
// 上传文件
@Post('uploadOnefile')
// 不验证token了我这里就是举例子
@ByGuardpass()
@UseInterceptors(
// 独立自己的
FilesInterceptor('image', 10000, {
storage: storage,
}),
)
async upload(
@Body() body: any,
@UploadedFiles(UploadPipe)
files: Array<Express.Multer.File>,
) {
let name = body.module;
return this.uploadService.uploadsingle(files, name);
}对应前端代码
特别注意
注意
使用 FormData 和 fetch API 上传图片(示例代码,需要根据后端 API 调整)
const formData = new FormData(); formData.append("module", "books");
// 添加其他字段,例如文件名 formData.append("image", file);
// 'image' 是后端期待的字段名,根据需要调整
const app = await NestFactory.create(AppModule, { cors: true }); 一定要开启跨域
代码如下
html
<!--
* @Author: jsopy
* @Date: 2025-09-12 19:38:28
* @LastEditTime: 2025-12-26 12:16:24
* @FilePath: /html/index.html
* @Description:
*
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片上传</title>
</head>
<body>
<form id="uploadForm" enctype="multipart/form-data">
<label for="imageInput">选择图片:</label>
<input type="file" id="imageInput" name="image" accept="image/*" />
<button type="button" onclick="uploadImage()">上传图片</button>
</form>
<div id="preview"></div>
<script>
function uploadImage() {
const fileInput = document.getElementById("imageInput");
const file = fileInput.files[0]; // 获取选中的文件
if (!file) {
alert("请选择一个文件");
return;
}
// 创建预览图片的URL(可选)
const preview = document.getElementById("preview");
const img = document.createElement("img");
img.src = URL.createObjectURL(file); // 创建图片的URL用于预览
img.style.maxWidth = "100%"; // 设置最大宽度以适应容器
preview.innerHTML = ""; // 清空之前的预览内容
preview.appendChild(img); // 将图片添加到预览区域
// 使用FormData和fetch API上传图片(示例代码,需要根据后端API调整)
const formData = new FormData();
// 必须他放在第一个位置
formData.append("module", "user"); // 添加其他字段,例如文件名
formData.append("image", file); // 'image' 是后端期待的字段名,根据需要调整
fetch("http://localhost:5000/v1/testdemo/uploadOnefile", {
// '/upload' 是你的后端处理上传的URL,需要替换为实际URL
method: "POST",
body: formData,
})
.then((response) => response.json()) // 假设服务器返回JSON格式的响应
.then((data) => {
console.log("Success:", data); // 处理响应数据,例如显示上传成功信息等
if (data.code == 200) {
alert("上传成功");
} else {
alert("上传失败");
}
})
.catch((error) => {
console.error("Error:", error); // 处理错误情况,例如显示错误信息等
alert("上传失败"); // 或者在页面上显示错误消息等操作
});
}
</script>
</body>
</html>