“使用 Spring Boot 后端和 React 前端上传带有图像的帖子时如何修复“415 不支持的媒体类型”错误?”

问题描述 投票:0回答:1

我在 Spring Boot 中实现了一个后端方法,允许用户创建带有或不带有图像的帖子。后端方法如下:

@PostMapping(value = "/posts/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseStatus(HttpStatus.CREATED)
@ApiResponse(responseCode = "400", description = "Please check the payload or token")
@ApiResponse(responseCode = "200", description = "Post added with image")
@Operation(summary = "Add a new Post with optional image")
@SecurityRequirement(name = "studyeasy-demo-api")
public ResponseEntity<?> addPostWithImage(
        @Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) @RequestPart("caption") PostPayloadDTO postPayloadDTO,
        @RequestPart(value = "file", required = false) MultipartFile file,
        @RequestParam("postTypeName") String postTypeName,
        Authentication authentication) {
    try {
        // Xác thực email và người dùng
        String email = authentication.getName();
        Optional<Account> optionalAccount = accountService.findByEmail(email);
        Users users = optionalAccount.get().getUser();
        Post post = new Post();
        post.setUser(users);
        post.setCaption(postPayloadDTO.getCaption());
        PostType postType = postTypeService.getPostTypeByName(postTypeName);
        post.setPostType(postType);
        postService.save(post);
        
        if (file != null && !file.isEmpty()) {
            String contentType = file.getContentType();
            if (contentType.equals("image/png") || contentType.equals("image/jpg") || contentType.equals("image/jpeg")) {
                String fileName = file.getOriginalFilename();
                String generatedString = RandomStringUtils.random(10, true, true);
                String finalImageName = generatedString + fileName;
                String absoluteFileLocation = AppUtil.get_photo_upload_path(finalImageName, PHOTOS_FOLDER_NAME, post.getPostId());

                Path path = Paths.get(absoluteFileLocation);
                Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);

                post.setPostImgURL(absoluteFileLocation); // Lưu đường dẫn ảnh vào post
                
                // Tạo đối tượng Photo và lưu thông tin file vào cơ sở dữ liệu
                Photo photo = new Photo();
                photo.setName(fileName); // Lưu tên gốc của file
                photo.setFileName(finalImageName); // Lưu tên file sau khi thêm chuỗi ngẫu nhiên
                photo.setOriginalFileName(fileName); // Lưu tên file gốc
                photo.setPost(post); // Liên kết ảnh với album
                photoService.save(photo); // Lưu ảnh vào database
            } else {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                        .body("Unsupported file format. Please use PNG, JPG, or JPEG.");
            }
        }

        List<PhotoDTO> photos = new ArrayList<>();
        for (Photo photo : photoService.findByPostId(post.getPostId())) {
            String link = "/posts/" + post.getPostId() + "/photos/" + photo.getId() + "/download-photo";
            photos.add(new PhotoDTO(photo.getId(), photo.getName(), photo.getDescription(), photo.getFileName(), link));
        }

        // Lưu post vào database
        post = postService.save(post);
        PostViewDTO postViewDTO = new PostViewDTO(post.getPostId(), post.getUser().getProfileImage(), post.getCaption(), post.getUser().getUserName(), photos);
        return ResponseEntity.ok(postViewDTO);

    } catch (Exception e) {
        log.debug("Error adding post with image: " + e.getMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
    }
}

我已经使用 Postman 测试了后端 API,无论有图像还是没有图像,它都能按预期工作。

但是,当我尝试从前端(React)调用相同的 API 时,我在浏览器控制台中收到以下错误:

POST http://localhost:3000/api/v1/posts/add 415 (Unsupported Media Type)

在后端日志中,我看到:

[org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/octet-stream' is not supported]

这是我的 React 前端的相关代码:

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchPostFileUploadWithAuth } from 'client/client';

const Share = () => {
  const [file, setFile] = useState(null);
  const [description, setDescription] = useState('');
  const navigate = useNavigate();

  const handleShare = async () => {
    const formData = new FormData();
    formData.append('caption', description);
    formData.append('postTypeName', 'normal');

    if (file) {
      formData.append('file', file);
    }

    try {
      await fetchPostFileUploadWithAuth('/posts/add', formData);
      setDescription('');
      setFile(null);
      navigate('/');
    } catch (error) {
      console.error('Failed to create post:', error);
    }
  };

  return (
    <div className="share">
      {/* JSX for file input, caption, etc. */}
    </div>
  );
};

export default Share;

当我尝试上传任何内容(带或不带图像)时,就会出现此问题。后端方法正确设置为消耗

multipart/form-data
,我在前端使用
FormData
来发送请求。

我使用以下函数进行 API 调用:

const fetchPostFileUploadWithAuth = async (uri, formData) => {
  const token = localStorage.getItem('token');
  const url = `${API_version}${uri}`;
  try {
    const response = await axios.post(url, formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
        "Authorization": `Bearer ${token}`,
      },
    });
    return response;
  } catch (error) {
    // Handle errors if the request fails
    console.error('Error fetching data:', error);
  }
};
java reactjs spring-boot api post
1个回答
0
投票

嗯,这个错误很熟悉,因为它让我彻夜难眠。 415 不支持的媒体类型。我认为这意味着请求的内容类型与 spring-boot 期望的不匹配。既然您已经确认您的 BE API 可以工作,我怀疑问题在于 React 如何发出请求。只是在这里做出有根据的猜测。您可以尝试以下一些方法:

  1. 检查

    FormData
    中的字段名称是否与控制器中的参数名称匹配。
    FormData
    caption
    file
    postTypeName

  2. 请检查您的

    axios
    版本。我把它放在这里是因为
    axios
    的某些版本存在
    FormData

    的已知问题
  3. 删除

    Content-Type
    标题,我认为是因为
    axios
    自动将
    Content-Type
    标题设置为带有边界的
    multipart/form-data
    ,这对于
    FormData
    正常工作至关重要。手动设置
    Content-Type
    可能会干扰此操作。因此,尝试从标题中删除
    'Content-Type': 'multipart/form-data'
    ,如下所示:

const response = await axios.post(url, formData, {
  headers: {
    "Authorization": `Bearer ${token}`,
  },
});
  1. 您可以在
    postTypeName
    中设置
    FormData
    而不是
    RequestParam
    :由于您当前已将
    postTypeName
    添加为
    RequestParam
    ,因此它可能无法在
    FormData
    对象中按预期工作。相反,请确保将
    postTypeName
    添加为控制器中的
    RequestPart
    参数。你明白吗?

我也会像这样改进你的

addPostWithImage
端点。

@PostMapping(value = "/posts/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseStatus(HttpStatus.CREATED)
@ApiResponse(responseCode = "400", description = "Please check the payload or token")
@ApiResponse(responseCode = "200", description = "Post added with image")
@Operation(summary = "Add a new Post with optional image")
@SecurityRequirement(name = "studyeasy-demo-api")
public ResponseEntity<?> addPostWithImage(
        @Parameter(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)) @RequestPart("caption") PostPayloadDTO postPayloadDTO,
        @RequestPart(value = "file", required = false) MultipartFile file,
        @RequestPart("postTypeName") String postTypeName, // I changed this from RequestParam to RequestPart
        Authentication authentication) {
    ...
}

这些是我可以立即通过您的代码发现的一些问题。尝试更改它们,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.