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

基于Django+React的YouTube克隆:视频上传后遇Bad Request错误

Django + React YouTube克隆项目:前端视频上传报Bad Request问题

开发Django和React的YouTube克隆项目时,前端上传视频出现终端错误:

return (<QueryDict: {'title': ['Test Video Creation'], 'user': ['1'], 'description': ['Is it working?'], 'image': [<TemporaryUploadedFile: attachment.png (image/png)>], 'video': [<TemporaryUploadedFile: video-for-fatube.mp4 (video/mp4)>]}>Bad Request: /api/admin/create/
)

但后端配置无异常,Postman发送POST请求可成功创建视频。

以下是前端创建视频的create.js原代码:

import React, { useState } from 'react';
import axiosInstance from '../../axios';
import { useHistory } from 'react-router-dom';
//MaterialUI
import Avatar from '@material-ui/core/Avatar';
import axios from "axios";
import Button from '@material-ui/core/Button';
import CssBaseline from '@material-ui/core/CssBaseline';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import Container from '@material-ui/core/Container';
import IconButton from '@material-ui/core/IconButton';
import PhotoCamera from '@material-ui/icons/PhotoCamera';

const useStyles = makeStyles((theme) => ({
    paper: {
        marginTop: theme.spacing(8),
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    },
    avatar: {
        margin: theme.spacing(1),
        backgroundColor: theme.palette.secondary.main,
    },
    form: {
        width: '100%', // Fix IE 11 issue.
        marginTop: theme.spacing(3),
    },
    submit: {
        margin: theme.spacing(3, 0, 2),
    },
}));

export default function Create() {
    const history = useHistory();
    const initialFormData = Object.freeze({
        title: '',
        description: '',
    });

    const [videoData, updateFormData] = useState(initialFormData);
    const [videoimage, setVideoImage] = useState(null);
    const [videovideo, setVideoVideo] = useState(null);

    const handleChange = (e) => {
        if ([e.target.name] == 'image') {
            setVideoImage({
                image: e.target.files,
            });
            console.log(e.target.files);
        }
        if ([e.target.name] == 'video') {
            setVideoVideo({
                video: e.target.files,
            });
            console.log(e.target.files);
        }
        if ([e.target.name] == 'title') {
            updateFormData({
                ...videoData,
                [e.target.name]: e.target.value.trim(),
            });
        } else {
            updateFormData({
                ...videoData,
                [e.target.name]: e.target.value.trim(),
            });
        }
    };
    

    const handleSubmit = (e) => {
        const config = { headers: { 'Content-Type': 'multipart/form-data' } };
        const URL = 'http://127.0.0.1:8000/api/admin/create/';
        let formData = new FormData();
        formData.append('title', videoData.title);
        formData.append('user', 1);
        formData.append('description', videoData.description);
        formData.append('image', videoimage.image[0]);
        formData.append('video', videovideo.video[0]);
        axios
            .post(URL, formData, config)
            .then((res) => {
                console.log(res.data);
            })
            .catch((err) => console.log(err));
    };
    
    const classes = useStyles();

    return (
        <Container component="main" maxWidth="xs">
            <CssBaseline />
            <div className={classes.paper}>
                <Avatar className={classes.avatar}></Avatar>
                <Typography component="h1" variant="h5">
                    Create New Video
                </Typography>
                <form className={classes.form} noValidate>
                    <Grid container spacing={2}>
                        <Grid item xs={12}>
                            <TextField
                                variant="outlined"
                                required
                                fullWidth
                                id="title"
                                label="Video Title"
                                name="title"
                                autoComplete="title"
                                onChange={handleChange}
                            />
                        </Grid>
                        <Grid item xs={12}>
                            <TextField
                                variant="outlined"
                                required
                                fullWidth
                                id="description"
                                label="Video Description"
                                name="description"
                                autoComplete="description"
                                onChange={handleChange}
                                multiline
                                rows={4}
                            />
                        </Grid>
                        <input
                            accept="image/*"
                            className={classes.input}
                            id="video-image"
                            onChange={handleChange}
                            name="image"
                            type="file"
                        />
                        <input
                            accept="video/*"
                            className={classes.input}
                            id="video-video"
                            onChange={handleChange}
                            name="video"
                            type="file"
                        />
                    </Grid>
                    <Button
                        type="submit"
                        fullWidth
                        variant="contained"
                        color="primary"
                        className={classes.submit}
                        onClick={handleSubmit}
                    >
                        Create Video
                    </Button>
                </form>
            </div>
        </Container>
    );
}

问题根源

  1. 表单默认提交行为未阻止:按钮为type="submit",点击会触发表单默认的application/x-www-form-urlencoded提交,与axios的multipart/form-data请求冲突,导致后端解析异常。
  2. 手动设置Content-Type错误:手动指定multipart/form-data会丢失自动生成的boundary标识,后端无法正确解析文件。
  3. 空值未处理:若用户未选择图片/视频,直接访问videoimage.image[0]会抛出错误,中断请求。
  4. handleChange逻辑冗余:重复的表单字段更新逻辑可简化。

修复后的代码

1. 优化handleChange函数

const handleChange = (e) => {
    const { name, value, files } = e.target;
    if (name === 'image') {
        setVideoImage({ image: files });
    } else if (name === 'video') {
        setVideoVideo({ video: files });
    } else {
        updateFormData(prev => ({
            ...prev,
            [name]: value.trim()
        }));
    }
};

2. 修复handleSubmit函数

const handleSubmit = (e) => {
    e.preventDefault(); // 阻止表单默认提交
    const URL = 'http://127.0.0.1:8000/api/admin/create/';
    let formData = new FormData();
    formData.append('title', videoData.title);
    formData.append('user', 1);
    formData.append('description', videoData.description);
    
    // 空值判断,避免未选文件时报错
    if (videoimage?.image?.[0]) {
        formData.append('image', videoimage.image[0]);
    }
    if (videovideo?.video?.[0]) {
        formData.append('video', videovideo.video[0]);
    }
    
    axios
        .post(URL, formData) // axios自动处理Content-Type(含boundary)
        .then((res) => {
            console.log(res.data);
            history.push('/'); // 可选:创建成功后跳转首页
        })
        .catch((err) => console.error(err.response?.data || err)); // 打印详细错误信息
};

3. 调整文件输入框布局(可选)

将文件输入框包裹在Grid组件中,保证布局一致性:

<Grid item xs={12}>
    <input
        accept="image/*"
        className={classes.input}
        id="video-image"
        onChange={handleChange}
        name="image"
        type="file"
    />
</Grid>
<Grid item xs={12}>
    <input
        accept="video/*"
        className={classes.input}
        id="video-video"
        onChange={handleChange}
        name="video"
        type="file"
    />
</Grid>

验证修复

修改后重新上传视频,后端应能正确解析FormData,完成视频创建。若仍有问题,可查看err.response.data获取后端返回的具体错误信息,进一步排查。

内容的提问来源于stack exchange,提问作者PHILLIPP APETENOK

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 00:55:14