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>
问题根源
- 字段名不匹配:前端input的
name设为file,但后端multer监听的是image字段,Formik绑定的也是image字段,三者不统一导致文件无法正确识别。 - 提交格式错误:上传文件需用
FormData格式,前端直接传JSON数据,导致文件无法传递到后端。 - 后端取值错误: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
相关产品推荐
相关产品推荐

