в React при добавлении изображений, получаю - Spring MultipartException: Current request is not a multipart request

Как я могу решить проблему с MultipartException? Я пытаюсь создать новый Asset с images, используя Spring в качестве бэкенда и React в качестве фронтенда.

Я создал форму для добавления сведений о товаре и изображений товара и добавил enctype="multipart/form-data" но мне показывает AxiosError {message: 'Request failed with status code 500', name: 'AxiosError', code: 'ERR_BAD_RESPONSE', config: {…}, request: XMLHttpRequest, …

И Spring в консоли показывает:

org.springframework.web.multipart.MultipartException: Current request is not a multipart request

В Spring я создал две сущности для Ассетов и изображений. И при добавлении Ассета изображения должны добавляться вместе с ID Ассета. Чтобы я мог брать изображения из БД по ID элемента в дальнейшем.

Вот сущности:

Items Entity :

package com.demo.fijinv.Models;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "assets")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Assets {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    @Column(name = "asset_type")
    private String assettype;
    @Column(name = "asset_brand")
    private String assetBrand;
    @Column(name = "asset_model")
    private String assetModel;


    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "assets")
    private List<Image> images = new ArrayList<>();


    private Long previewImageId;
    private LocalDateTime creationDate;
    @PrePersist //to read about inversion of control
    private void init() {
        creationDate = LocalDateTime.now();
    }
    public void addImageToItemName(Image image) {
        image.setAssets(this);
        images.add(image);
    }

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Items> invItem = new ArrayList<>();
}

Images Entity:

package com.demo.fijinv.Models;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;
@Entity
@Table(name = "images")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Image {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "originalFileName")
    private String originalFileName;
    @Column(name = "size")
    private Long size;
    @Column(name = "contentType")
    private String contentType;
    @Column(name = "isPreviewImage")
    private boolean isPreviewImage;
    @Column(length = 10000000)
    @Lob
    private byte[] bytes;

    @ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    private Assets assets;

    @ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
    private List<Items> items;
}

И так же созданы Контроллеры для сущностей

Items Controller :

package com.demo.fijinv.Conteollers;

import com.demo.fijinv.Models.Assets;
import com.demo.fijinv.Models.Image;
import com.demo.fijinv.Repositories.AssetsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

@CrossOrigin("*")
@RestController
@RequestMapping("/api/assets")
public class AssetsController {
    @Autowired
    public AssetsRepository assetsRepository;

    @GetMapping
    public List<Assets> getAllAssets(){
        return assetsRepository.findAll();
    }

    @DeleteMapping("{id}")
    public ResponseEntity<Assets> deleteAsset(@PathVariable Long id){
        assetsRepository.deleteById(id);
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @PostMapping(consumes = {MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public void saveAsset(@RequestBody Assets assets, @RequestParam("file1") MultipartFile file1, @RequestParam("file2") MultipartFile file2, @RequestParam("file3") MultipartFile file3) throws IOException {
        Image image1;
        Image image2;
        Image image3;

        if(file1.getSize() != 0){
            image1 = toImageEntity(file1);
            image1.setPreviewImage(true);
            assets.addImageToItemName(image1);
        }
        if(file2.getSize() != 0){
            image2 = toImageEntity(file2);
            assets.addImageToItemName(image2);
        }
        if(file3.getSize() != 0){
            image3 = toImageEntity(file3);
            assets.addImageToItemName(image3);
        }

        Assets itemFromDB = assetsRepository.save(assets);
        itemFromDB.setPreviewImageId(itemFromDB.getImages().get(0).getId());
        assetsRepository.save(assets);
    }

    private Image toImageEntity(MultipartFile file) throws IOException {
        Image image = new Image();
        image.setName(file.getName());
        image.setOriginalFileName(file.getOriginalFilename());
        image.setContentType(file.getContentType());
        image.setSize(file.getSize());
        image.setBytes(file.getBytes());
        return image;
    }
}

Images Controller :

package com.demo.fijinv.Conteollers;

import com.demo.fijinv.Models.Image;
import com.demo.fijinv.Repositories.ImageRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayInputStream;
import java.util.List;

@CrossOrigin("*")
@RestController //no needed to present anything
@RequestMapping("/api/assets/images")
public class ImageController {
@Autowired
public ImageRepository imageRepository;

@GetMapping
private List<Image> getAllImages(){
    return imageRepository.findAll();
}

@GetMapping("{id}")
private ResponseEntity<?> getImageByID(@PathVariable Long id){
    Image image = imageRepository.findById(id).orElse(null);
    return ResponseEntity.ok()
            .header("filename", image.getOriginalFileName())
            .contentType(MediaType.valueOf(image.getContentType()))
            .contentLength(image.getSize())
            .body(new InputStreamResource(new ByteArrayInputStream(image.getBytes())));
}
}

Оба контроллера настроены на разрешение запросов от всех сервисов путем добавления @CrossOrigin("*"), что означает, что Spring должен получать запросы.

А для Frontend я сделал простую страницу с модальной формой, где могу добавлять товар с изображениями:

import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import { Modal, Button } from 'react-bootstrap'
import AssetsSetvice from "./../../Services/AssetsService"


const AssetsComponenet = () => {
    const [show, setShow] = useState(false);
    const modalShow = () => setShow(true);
    const modalHide = () => setShow(false);

    // getting all assets 
    const [assets, setAssets] = useState([]);
    useEffect(() => {
        AssetsSetvice.getAllAssets().then((res) => {
            setAssets(res.data);
        }).catch(err => {
            console.log(err)
        })
    }, []);
    const [assettype, setAssettype] = useState('');
    const [assetBrand, setAssetBrand] = useState('');
    const [assetModel, setAssetModel] = useState('');
    const [file1, setFile1] = useState('');
    const [file2, setFile2] = useState('');
    const [file3, setFile3] = useState('');
    const [showImage1, setShowImage1] = useState();
    const [showImage2, setShowImage2] = useState();
    const [showImage3, setShowImage3] = useState();

    const saveAsset = (event) => {
        event.preventDefault()
        const asset = {
        assettype,
        assetBrand,
        assetModel,
        file1,
        file2,
        file3
    }

        console.log(asset)
        
        AssetsSetvice.addNewAsset(asset).then((res) => {
            console.log(res.data)
            event.preventDefault();
        }).catch(err => {
            console.log(err)
        })
    }

    const saveAndClose = (ev) => {
        saveAsset(ev);
        setShow(false);
    }

    const deleteAsset = (id) => {
        if (window.confirm("Are you sure want to delete this asset?")) {
            AssetsSetvice.deleteAsset(id).then((response) => {
                console.log(`Asset with ${id} was deleted`);
                // window.location.replace("/users");
            }).catch(error => {
                console.log(`Something went worng : \n ${error}`);
            })
        }
    }

    let count = 1;
    return (
        <>
            <div className='container'>
                <h2 className='text-center mt-4 mb-4 bold'> List of Items</h2>
                <div className='mb-3'>
                    <button type='button' className='btn btn-primary' data-toggle="modal" onClick={modalShow} data-target="#addNewItemModal">
                        Add new Asset
                    </button>
                </div>
                <table className='table table-bordered table-striped'>
                    <thead>
                        <tr>
                            <th className="th-sm">№</th>

                            <th className="th-sm">Name</th>
                            <th className="th-sm">Brand</th>
                            <th className="th-sm">Model</th>
                            <th className="th-sm"> Action </th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            assets.map(
                                function (item) {
                                    return <tr key={item.id}>
                                        {/* <tr> */}
                                        <th className="th-sm">{count++}</th>
                                        <th className="th-sm">Asset Name</th>
                                        <th className="th-sm">Asset Brand</th>
                                        <th className="th-sm">Asset Model</th>
                                        <th className="th-sm">
                                            {/* <Link to={`/item-delete/${item.id}`} className="btn btn-primary"> Delete Item </Link> */}
                                            <Link onClick={(id) => { deleteAsset(id) }} className="btn btn-primary"> Delete Asset </Link>
                                        </th>
                                    </tr>
                                }
                            )
                        }
                    </tbody>
                </table>

                <Modal show={show} size='lg' onHide={modalHide} centered>
                    <Modal.Header closeButton>
                        <Modal.Title center>
                            Add New Item
                        </Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <div className='container-fluid'>
                            <form className='row' method='POST' enctype="multipart/form-data">
                                {/* <form className='row' > */}
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Name </label>
                                    <input type='text'
                                        placeholder='Item Name'
                                        className='form-control'
                                        // value={itemname}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssettype(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Brand </label>
                                    <input type='text'
                                        placeholder='Item Brand'
                                        className='form-control'
                                        // value={itembrand} 
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssetBrand(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> Item Model </label>
                                    <input type='text'
                                        placeholder='Item Model'
                                        className='form-control'
                                        // value={itemmodel}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setAssetModel(e.target.value)
                                            }
                                        }}
                                        required
                                    />
                                </div>

                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file1}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage1(URL.createObjectURL(e.target.files[0]))
                                                setImage1(e.target.files[0])
                                            }
                                        }}
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file2}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage2(URL.createObjectURL(e.target.files[0]))
                                                setImage2(e.target.files[0])
                                            }
                                        }}
                                        required
                                    />
                                </div>
                                <div className='col-md-4'>
                                    <label className='form-label'> First Image </label>
                                    <input type='file'
                                        className='form-control'
                                        // value={file3}
                                        onChange={(e) => {
                                            if (e.target.value != "") {
                                                setShowImage3(URL.createObjectURL(e.target.files[0]))
                                                setImage3(e.target.files[0])
                                            }
                                        }}
                                    />
                                </div>
                            </form>
                        </div>
                        <div className='row mt-4'>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage1} />
                            </div>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage2} />
                            </div>
                            <div className='col-md-4'>
                                <img style={{ width: "150px" }} className="rounded mx-auto d-block" src={showImage3} />
                            </div>
                        </div>
                    </Modal.Body>
                    <Modal.Footer center>
                        <Button center onClick={(e) => { saveAndClose(e) }} variant="primary">Add New Asset</Button>
                    </Modal.Footer>
                </Modal>

            </div>
        </>
    )
}

export default AssetsComponenet

Как вы можете видеть, в форме есть тип записи - multipart: <form className='row' method='POST' enctype="multipart/form-data"> но все равно я получаю ошибку что это не Multipart запрос в консоли Spring и в консоли браузера я получаю ошибку Axios статус 500.

Я использую Axios для получения данных из бэкенда, и я создал сервис для Items:

import axios from "axios";

const ASSETS_GOT_FROM_REST_API = "http://localhost:8082/api/assets";
const IMAGES_GOT_FROM_REST_API = "http://localhost:8082/api/assets/images";


class AssetsSetvice {
    getAllAssets() {
        return axios.get(ASSETS_GOT_FROM_REST_API);
    }
    addNewAsset(asset) {
        return axios.post(ASSETS_GOT_FROM_REST_API, asset);
    }
    deleteAsset(assetId) {
        return axios.delete(ASSETS_GOT_FROM_REST_API + "/" + assetId);
    }

    getAllImages() {
        return axios.get(IMAGES_GOT_FROM_REST_API);
    }
    addNewImages(images) {
        return axios.addNewImage(IMAGES_GOT_FROM_REST_API, images);
    }

}

export default new AssetsSetvice();

когда я вывожу Ассет в консоль браузера, который я пытаюсь добавить, я получаю :

assetBrand: "wersgsd"
assetModel: "ewtewry"
assettype: "erwerwe"
file1: File {name: 'lenovo-2.jpg', lastModified: 1684826495648, lastModifiedDate: Tue May 23 
2023 10:21:35 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 67725, …}
file2: File {name: 'lenovo-3.jpg', lastModified: 1684826589057, lastModifiedDate: Tue May 23 
2023 10:23:09 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 4437, …}
file3: File {name: 'm700-1.jfif', lastModified: 1684826441152, lastModifiedDate: Tue May 23 
2023 10:20:41 GMT+0300 (Eastern European Summer Time), webkitRelativePath: '', size: 3221, …}
[[Prototype]]: Object

Похоже, что я могу послать запрос post на сервер, но сервер отклоняет его.

Что я делаю не так? Как я могу решить эту проблему? Может ли данные ошбки связаны с ManyToOne или OneToMany? Или с тем что я пытаюсь с одной сущностью добавить данные в две таблицы...? Если да, то как это реализовать?


Ответы (0 шт):