如何通过HTML上传图片至PostgreSQL数据库(NestJS后端)
问题解决:File Input无法用[(ngModel)]绑定及图片路径存储方案
问题根源
file类型的<input>无法直接通过[(ngModel)]绑定数据,原因有两点:
ngModel仅针对普通表单值(字符串、数字等),而file input返回的是File对象,不属于ngModel的处理范畴;- 浏览器出于安全限制,不会暴露本地文件的真实路径,你无法直接获取本地路径字符串存入数据库。
解决方案
需要通过「前端用FormData传递文件+后端接收并存储文件,再将文件路径存入数据库」的流程实现需求,以下是具体代码修改:
前端(Angular)修改
- 调整Input标签,移除
[(ngModel)],改用事件监听获取文件:
<input type="file" accept=".png, .jpeg, .jpg" (change)="onFileSelected($event)" class="form-control mt-3">
- 在
food.component.ts中添加文件处理逻辑,用FormData封装数据:
selectedFile: File | null = null; // 监听文件选择事件 onFileSelected(event: Event) { const input = event.target as HTMLInputElement; if (input.files?.length) { this.selectedFile = input.files[0]; } } postFood() { if (this.sale === false) this.saleVal = 0; if (this.saleVal === 0) this.sale = false; // 用FormData封装表单数据和文件 const formData = new FormData(); formData.append('name', this.name); formData.append('price', this.price.toString()); formData.append('description', this.description); formData.append('category', this.category); formData.append('sale', this.sale.toString()); formData.append('genre', this.genre); formData.append('saleval', this.saleVal.toString()); this.selectedFile && formData.append('image', this.selectedFile); // 发起请求 this.productsService.postFood(formData).subscribe({ next: () => console.log('产品添加成功'), error: err => console.error('添加失败:', err) }); }
- 修改
products.service.ts的请求方法:
postFood(formData: FormData) { console.log('准备上传产品数据'); return this.http.post('http://localhost:3000/food', formData); }
后端(NestJS)修改
- 先安装文件上传依赖:
npm install @nestjs/platform-express multer
- 修改
food.controller.ts,配置文件接收和存储:
import { Controller, Post, Body, UploadedFile, UseInterceptors, FileInterceptor } from '@nestjs/common'; import { diskStorage } from 'multer'; import { extname } from 'path'; @Controller('food') export class FoodController { constructor(private readonly productsService: ProductsService) {} @Post() @UseInterceptors(FileInterceptor('image', { storage: diskStorage({ destination: './uploads', // 手动创建此目录用于存储上传的图片 filename: (req, file, cb) => { // 生成唯一文件名避免冲突 const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, `${file.fieldname}-${uniqueSuffix}${extname(file.originalname)}`); }, }), // 限制文件类型 fileFilter: (req, file, cb) => { const allowedExts = ['.png', '.jpeg', '.jpg']; const ext = extname(file.originalname).toLowerCase(); allowedExts.includes(ext) ? cb(null, true) : cb(new Error('仅支持PNG/JPEG/JPG格式'), false); }, })) async create( @Body() productData: Omit<Product, 'image'>, @UploadedFile() image: Express.Multer.File, ): Promise<any> { // 转换数据类型并拼接文件路径 const product = { ...productData, image: image ? `/uploads/${image.filename}` : '', // 存储相对路径 sale: productData.sale === 'true', price: parseFloat(productData.price), saleval: parseInt(productData.saleval), }; return this.productsService.create(product); } }
- 在
main.ts中配置静态资源访问,让前端能加载上传的图片:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { join } from 'path'; import { NestExpressApplication } from '@nestjs/platform-express'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); app.enableCors(); // 允许跨域(前端后端端口不同时需要) // 配置静态资源目录 app.useStaticAssets(join(__dirname, '..', 'uploads'), { prefix: '/uploads/' }); await app.listen(3000); } bootstrap();
- 确保PostgreSQL数据库中
product表的image字段为字符串类型,用于存储文件路径。
内容的提问来源于stack exchange,提问作者Hazem Kesba
相关产品推荐
相关产品推荐

