You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React结合Multer上传图片至数据库时Sequelize非空验证错误求助

图片上传时Sequelize非空验证错误排查与解决

我正在尝试通过React(搭配Formik、Yup、Axios)上传图片,后端基于Express框架,使用Multer处理文件上传,通过Sequelize将图片信息存入数据库。但目前遇到SequelizeValidationError: notNull Violation: Please enter image错误——虽然Postman调用接口返回成功,但服务器端抛出该非空验证错误。

后端代码(Express+Multer+Sequelize)

const express = require("express");
const router = express.Router();
const { Admin } = require("../models");
const bcrypt = require("bcrypt");
const multer = require("multer");
const path = require("path");

const storage = multer.diskStorage({
    destination: "./Image",
    filename: (req, file, cb) => {
        return cb(null, `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`)
    }
});

const upload = multer({
    storage: storage,
    fileFilter: function(req, file, cb) {
        if(!file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG)$/)) {
            req.fileValidationError = "Only the image file is allowed";
            return cb(new Error("Only the image file is allowed"), false);
        }
        cb(null, true);
    },
});

router.post("/", upload.single("image"), async (req, res) => {
    const { 
            image,
            username, 
            password,
        } = req.body;
    bcrypt.hash(password, 10).then((hash) => {
        Admin.create({
            image: image,
            username: username,
            password: hash,
        });
        res.json("Success");
    });
});

router.post("/login", async (req, res) => {
    try {
        const {username, password} = req.body;

        const user = await Admin.findOne({where: {username: username}});

        if (!user) return res.json({error: "Admin User doesn't exist"});

        bcrypt.compare(password, user.password).then((match) => {
            if(!match) return res.json({error: "Username and password is incorrect"});
            res.json("Login Success");
        });
    } catch (err) {
        console.log("Error authentication the prompt", err);
    }
});

module.exports = router;

服务器错误信息

throw new sequelizeError.ValidationError(null, this.errors);
            ^

ValidationError [SequelizeValidationError]: notNull Violation: Please enter image
    at InstanceValidator._validate (D:\My Portfolio\React Js\Admin\server\node_modules\sequelize\lib\instance-validator.js:50:13)
    at async InstanceValidator._validateAndRunHooks (D:\My Portfolio\React Js\Admin\server\node_modules\sequelize\lib\instance-validator.js:60:7)
    at async InstanceValidator.validate (D:\My Portfolio\React Js\Admin\server\node_modules\sequelize\lib\instance-validator.js:54:12)
    at async model.save (D:\My Portfolio\React Js\Admin\server\node_modules\sequelize\lib\model.js:2368:7)
    at async Function.create (D:\My Portfolio\React Js\Admin\server\node_modules\sequelize\lib\model.js:1344:12) {
  errors: [
    ValidationErrorItem {
      message: 'Please enter image',
      type: 'notNull Violation',
      path: 'image',
      value: null,
      origin: 'CORE',
      instance: Admin {
        dataValues: {
          id: null,
          image: undefined,
          username: 'Rameljay',
          password: '$2b$10$a9ltyD7176rTQ33sKtpiuea8xS.SBz/FV0423XRa6dUAGGOXST0Fm',
          updatedAt: 2022-11-28T23:47:08.298Z,
          createdAt: 2022-11-28T23:47:08.298Z
        },
        _previousDataValues: {
          username: undefined,
          password: undefined
        },
        uniqno: 1,
        _changed: Set(9) {
          'username',
          'password'
        },
        _options: {
          isNewRecord: true,
          _schema: null,
          _schemaDelimiter: '',
}

前端代码(React+Formik+Yup)

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
import axios from 'axios';

const initialValues = {
        image: "",
        username: "",
        password: "",
    };

 const validationSchema = Yup.object().shape({
        image: Yup.mixed().required("You need to provide a file"),
        username: Yup.string().min(3).max(20).required(),
        password: Yup.string().min(2).max(20).required(),
    });

   const onSubmit = (data) => {
        axios.post("http://localhost:3001/auth", data).then(() => {
            console.log(data);
        });
    };

<Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={onSubmit}
 >
<Form>
<label>Image</label>
                    <ErrorMessage name="image" component="span"/>
                    <input
                        autoComplete="off"
                        id="image"
                        name="file"
                        type="file"
                        onChange={(e) => {this.image("image", e.target.files[0])}}
                    />
                    <label>Username</label>
                    <ErrorMessage name="username" component="span" />
                    <Field
                        autoComplete="off"
                        id="username"
                        name="username"
                        placeholder="Ex. guko"
                    />
                    <label>Password</label>
                    <ErrorMessage name="password" component="span" />
                    <Field
                        autoComplete="off"
                        type="password"
                        id="password"
                        name="password"
                        placeholder="Ex. guko1234"
                    />
                    <button type="submit">Register</button>
</From>
</Formik>

问题根源

  1. 字段名不匹配:前端input的name设为file,但后端multer监听的是image字段,Formik绑定的也是image字段,三者不统一导致文件无法正确识别。
  2. 提交格式错误:上传文件需用FormData格式,前端直接传JSON数据,导致文件无法传递到后端。
  3. 后端取值错误:multer处理后的文件信息存在req.file对象中,而非req.body.image,后端从req.body拿值会得到undefined,触发Sequelize非空验证。

修复方案

前端修改

  • 统一字段名:将input的name改为image,与后端、Formik字段保持一致。
  • 使用FormData提交:修改onSubmit函数,将数据转为FormData格式,并设置正确的请求头。
  • 修正Formik文件更新逻辑:用Formik提供的setFieldValue更新文件字段(函数组件无this)。

修改后的关键代码:

// 提交函数修改
const onSubmit = (data) => {
  const formData = new FormData();
  formData.append("image", data.image);
  formData.append("username", data.username);
  formData.append("password", data.password);
  
  axios.post("http://localhost:3001/auth", formData, {
    headers: { "Content-Type": "multipart/form-data" },
  }).then(() => {
    console.log("上传成功");
  }).catch(err => console.error(err));
};

// Formik组件修改(需获取props)
<Formik
  initialValues={initialValues}
  validationSchema={validationSchema}
  onSubmit={onSubmit}
>
  {(props) => (
    <Form>
      <label>Image</label>
      <ErrorMessage name="image" component="span"/>
      <input
        autoComplete="off"
        id="image"
        name="image"
        type="file"
        onChange={(e) => {
          const file = e.target.files[0];
          props.setFieldValue("image", file);
        }}
      />
      {/* 其他表单字段保持不变 */}
    </Form>
  )}
</Formik>

后端修改

  • 从req.file.filename获取存储的文件名,作为image字段的值存入数据库。
  • 完善异步逻辑:用await处理哈希和数据库操作,添加错误捕获,避免提前返回响应。

修改后的路由代码:

router.post("/", upload.single("image"), async (req, res) => {
  try {
    const { username, password } = req.body;
    
    // 校验文件是否存在
    if (!req.file) {
      return res.status(400).json({ error: "请上传图片" });
    }

    const hash = await bcrypt.hash(password, 10);
    await Admin.create({
      image: req.file.filename, // 存储生成的文件名
      username: username,
      password: hash,
    });
    res.json("Success");
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "服务器错误" });
  }
});

额外注意事项

  • 确保后端./Image目录已创建,否则multer会抛出文件写入错误。
  • Sequelize的Admin模型中image字段设置为非空,后端必须先校验req.file存在后再创建记录。

内容的提问来源于stack exchange,提问作者Ramel Jay Cuña

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:40:30