简体   繁体   English

如何将标题和图像文件从前端 (React.js) 传递到后端 API (Spring Boot)?

[英]How can I pass both the title and an image file from my front end (React.js) to my backend API (Spring Boot)?

I am attempting to make an application that allows a registered user to submit an image file (.jpeg, .png, etc) along with a title of that image.我正在尝试制作一个允许注册用户提交图像文件(.jpeg、.png 等)以及该图像标题的应用程序。 I am having trouble wrapping my mind around how to do this.我无法思考如何做到这一点。

I need to send the image to an amazon AWS S3 bucket, and I was able to figure out how to do this, but adding a title input has confused me a lot in terms of how to get both the file and title from my front-end (JSON) to my back-end API and have it be saved to my post database (JPA).我需要将图像发送到亚马逊 AWS S3 存储桶,并且我能够弄清楚如何执行此操作,但是添加标题输入让我很困惑如何从我的前端获取文件和标题 - end (JSON) 到我的后端 API 并将其保存到我的帖子数据库 (JPA)。 I have a user_post database that has the following column: ID (post ID primary key), user_id (foreign key from User table), title, & post_image (containing the file that is to be saved in the DB).我有一个包含以下列的 user_post 数据库:ID(帖子 ID 主键)、user_id(来自用户表的外键)、标题和 post_image(包含要保存在数据库中的文件)。 So basically, I need to figure out how to get the input (both file and title) at once, send it to my back-end and save it in the Post DB.所以基本上,我需要弄清楚如何立即获取输入(文件和标题),将其发送到我的后端并将其保存在 Post DB 中。

Do I need to pass the title as an argument in my post controller?我是否需要在我的帖子 controller 中将标题作为参数传递? Since the UserPost DB is a one to many relationship with a User, I need to pass that logged in user's ID to my controller so that it gets sent to the post DB, I'm just not sure how.由于 UserPost 数据库是与用户的一对多关系,我需要将登录用户的 ID 传递给我的 controller 以便它被发送到 post 数据库,我只是不确定如何。

I have been searching online everywhere for help and figured I would give Stack Overflow a shot on this as I desperately need help.我一直在网上到处寻找帮助,并认为我会在这方面给 Stack Overflow 一个机会,因为我迫切需要帮助。 I am using JWT authentication through local storage for now to access user information such as username and id.我现在通过本地存储使用 JWT 身份验证来访问用户信息,例如用户名和 ID。 Before I was testing my API with Postman by using the "file" key in the body and then proceeding to choose the image that is to be selected, but how can I do this if I want to add a title as well?在我通过使用主体中的“文件”键然后继续选择要选择的图像来使用 Postman 测试我的 API 之前,但是如果我也想添加标题,我该怎么做呢? Ideally, it would be the same but with text instead of file for the type as seen below but I am not sure.理想情况下,它会是相同的,但使用文本而不是文件作为类型,如下所示,但我不确定。 (Of course depending on what key I set for the title if I am thinking of it right). (当然,如果我认为正确,则取决于我为标题设置的键)。

在此处输入图像描述

This is a screenshot of my front-end just for a visual in case it helps at all.这是我的前端的屏幕截图,仅用于视觉效果,以防万一。

在此处输入图像描述

Here is my code, I apologize for it being so messy, I am working on cleaning it up as soon as I can.这是我的代码,我为它如此混乱而道歉,我正在努力尽快清理它。 I will start with the front-end.我将从前端开始。 I believe I need to set the key for my title input so that it gets passed to my back-end API, but I am not even sure if I am doing that right.我相信我需要为我的标题输入设置密钥,以便它被传递到我的后端 API,但我什至不确定我是否做对了。 I also needed to use a custom form validation since I am dealing with an image upload.我还需要使用自定义表单验证,因为我正在处理图像上传。 I ended up putting the call to my API in my useForm.js file since my form validations are done in that custom hook.我最终在 useForm.js 文件中调用了 API,因为我的表单验证是在该自定义挂钩中完成的。

Upload.jsx:上传.jsx:

import '../../components/pages/styles/Uploads.css';
import {Formik, Field, Form, ErrorMessage} from 'formik';
import * as Yup from 'yup';
import {useEffect, useState} from 'react';
import {} from 'react-router-dom';
import {useDispatch, useSelector} from 'react-redux';
import axios from 'axios';
import {clearMessage} from '../../slices/messages';
import authHeader from '../../services/auth-header';
import useForm from '../../hooks/useForm';
const API_URL = 'http://localhost:8080/api/posts';

function Uploads(onUpload) {
    const {user: currentUser} = useSelector((state) => state.auth);
    const [file, setFile] = useState();
    const [title, setTitle] = useState();
    const [description, setDescription] = useState('');
    const [loading, setLoading] = useState(false);
    const [content, setContent] = useState('');
    const [preview, setPreview] = useState(null);
    const initialValues = {
        title: '',
    };

    const [formErrors, setFormErrors] = useState();

    useEffect(() => {}, []);

    const handlePost = (formValue) => {};

    const onAddImage = (file) => {
        window.URL.revokeObjectURL(preview);
        if (!file) return;
        setPreview(window.URL.createObjectURL(file));
    };

    //The postUserImage function below was moved to useForm.js 
    //since my form validations are done there. I might have 
    //messed this up. 

    // const postUserImage = async (event) => {
    //This is the code that will get passed to my backend. I need the image and title to be added here somehow.
    //  event.preventDefault();

    //  const formData = new FormData();
    //  formData.append('file', file);
    //  formData.append('title', title);

    //  const result = await axios.post('/upload', formData, {
    //      headers: {...authHeader(), 'Content-Type': 'multipart/form-data'},
    //  });
    //  console.log(result.data);
    // };

    //Custom hook call
    // const {handleChange, values, errors, handleSubmit} = useForm(postUserImage);
    const initialState = {title: ''};
    const validations = [
        ({title}) => isRequired(title) || {title: 'Title is required'},
    ];
    const {values, isValid, errors, changeHandler, submitHandler, touched} =
        useForm(initialState, validations, onUpload);

    return (
        <div className='page'>
            <div className='upload-card'>
                <div id='preview'>
                    <img
                        src={preview || require('../../assets/user-solid.jpeg')}
                        id='image'
                        alt='Thumbnail'
                        className='user-post'
                    />
                </div>
            </div>
            <div className='upload-container'>
                <div className='post-form-container'>
                    <p id='upload-form-label'>Hello, feel free to post an image!</p>
                    <form
                        // onSubmit={'return Validate(this);'}

                        onSubmit={submitHandler}
                        className='upload-form'
                    >
                        <div className='panel'>
                            <div className='button_outer'>
                                <div className='btn_upload'>
                                    <input
                                        filename={file}
                                        onChange={(e) => onAddImage(e.target.files[0])}
                                        type='file'
                                        accept='.jpeg,.svg,.gif,.png'
                                        id='image-selection-btn'
                                    ></input>
                                    Choose your Art
                                </div>
                            </div>
                        </div>
                        <input
                            name='title'
                            type='text'
                            className='form-control'
                            placeholder='Enter Title'
                            id='cred-input'
                            required
                            value={values.title}
                            onChange={changeHandler}
                        />

                        {touched.title && errors.title && (
                            <p className='error'>{errors.title}</p>
                        )}

                        <button type='submit' id='post-upload-btn' disabled={isValid}>
                            Upload Image
                        </button>
                    </form>
                </div>
            </div>
        </div>
    );
    function isRequired(value) {
        return value != null && value.trim().length > 0;
    }

    function isSame(value1, value2) {
        return value1 === value2;
    }
}

export default Uploads;

useForm.js:使用表单.js:

import React, {useState} from 'react';
import {omit} from 'lodash';
import authHeader from '../services/auth-header';
import axios from 'axios';

function useForm(initialState = {}, validations = [], onSubmit = () => {}) {
    const API_URL = 'http://localhost:8080/api/posts';
    // Add the 'onSubmit' argument
    const {isValid: initialIsValid, errors: initialErrors} = validate(
        validations,
        initialState
    );
    const [values, setValues] = useState(initialState);
    const [errors, setErrors] = useState(initialErrors);
    const [isValid, setValid] = useState(initialIsValid);
    const [touched, setTouched] = useState({});
    const [file, setFile] = useState();
    const [title, setTitle] = useState();
    const changeHandler = (event) => {
        const newValues = {...values, [event.target.name]: event.target.value};
        const {isValid, errors} = validate(validations, newValues);
        setValues(newValues);
        setValid(isValid);
        setErrors(errors);
        setTouched({...touched, [event.target.name]: true});
    };
    // Add this
    const submitHandler = async (event) => {
        event.preventDefault();
        onSubmit(values);
        const formData = new FormData();
        formData.append('file', file);
        formData.append('title', values.title);

        const result = await axios.post('/upload', formData, {
            headers: {...authHeader(), 'Content-Type': 'multipart/form-data'},
        });
        console.log(result.data);
    };
    return {values, changeHandler, isValid, errors, touched, submitHandler}; // Add 'submitHandler'
}

function validate(validations, values) {
    const errors = validations
        .map((validation) => validation(values))
        .filter((validation) => typeof validation === 'object');
    return {
        isValid: errors.length === 0,
        errors: errors.reduce((errors, error) => ({...errors, ...error}), {}),
    };
}

export default useForm;

Back-end code:后端代码:

User.java:用户.java:

package com.Application.models;

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@Entity
@Table( name = "users",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = "username"),


        })

public class User {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;


    @Column(name = "username")
    @NotBlank
    @Size(max = 20)
    private String username;

    @Column(name = "email")
    @NotBlank
    @Size(max = 50)
    @Email
    private String email;

    @NotBlank
    @Size(max = 120)
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable( name = "user_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();

    @OneToMany(cascade = CascadeType.ALL,
            fetch = FetchType.LAZY,
            mappedBy = "user")
    @Column(name = "user_post")
    private Set<UserPosts> userPosts = new HashSet<>();

    public User(String username, String email
                ,String password) {

        this.username = username;
        this.email = email;
        this.password = password;
    }

    public User() {

    }

public Long getId() {
    return id;
}
    public void setId(Long id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    public Set<Role> getRoles() {
        return roles;
    }
    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

UserRepository.java:用户库.java:

package com.Application.repository;
import java.util.Optional;
import java.util.UUID;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.HashTek.HashTekApplication.models.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
    Optional<User> findByEmail(String email);

    Boolean existsByUsername(String username);
    Boolean existsByEmail(String email);
}

UserPost.java: UserPost.java:

package com.Application.models;

import javax.persistence.*;

@Entity
@Table(name = "user_posts")
public class UserPosts {
    @Id
    @Column(name = "id")
    private Long id;

    @ManyToOne(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @Column(name = "title")
    private String title;

    @Column(name = "post_image")
    private String postImage;

    @Column(name = "likes")
    private Long likes;

    @Column(name = "views")
    private Long views;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getPostImage() {
        return postImage;
    }

    public String getPostImageComplete() {
        return " https://hashtekbucket.s3.us-east-2.amazonaws.com/" + postImage;
    }

    public void setPostImage(String postImage) {
        this.postImage = postImage;
    }

    public Long getLikes() {
        return likes;
    }

    public void setLikes(Long likes) {
        this.likes = likes;
    }

    public Long getViews() {
        return views;
    }

    public void setViews(Long views) {
        this.views = views;
    }
}

UserPostController.java: UserPostController.java:

package com.Application.controller;

import com.Application.models.User;
import com.Application.models.UserPosts;
import com.Application.payload.response.MessageResponse;
import com.Application.repository.UserPostsRepository;
import com.Application.security.jwt.JwtUtils;
import com.Application.security.services.CustomUserDetails;
import com.Application.security.services.CustomUserDetailsService;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.PutObjectRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

import static org.apache.http.entity.ContentType.*;
import static org.apache.http.entity.ContentType.IMAGE_GIF;

@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequestMapping("/api/posts")
public class UserPostsController {
    private static final String AUTH_HEADER = "authorization";

    //  @Autowired
    //  private final UserPostsRepository userPostRepo;
    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    JwtUtils jwtUtils;

    /**  AWS CREDENTIALS SOURCE
     For AWS Builder  **/
    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;
    @Value("${cloud.aws.credentials.secret-key}")
    private String accessSecret;
    @Value("${cloud.aws.region.static}")
    private String region;

    //    public UserPostsController(UserPostsRepository userPostRepo) {
    //        this.userPostRepo = userPostRepo;
    //    }

    @PostMapping(
        path = "/upload",
        consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity < ? > userPostUpload(@RequestParam("file") MultipartFile file,
        RedirectAttributes redirectAttributes,
        Model model,
        UserPosts userPosts, HttpServletRequest request, User user) {

        //Check if POST is empty
        if (file.isEmpty()) {

            return ResponseEntity.ok(new MessageResponse("Please select a file to upload"));
        }

        isImage(file);

        //Hard coded bucketName -> linked to AWS
        String bucketName = "hashtekbucket";

        //Add timestamp to name to prevent duplicate names
        String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();

        //getting aws access
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, accessSecret);

        //Building S3Client connection
        AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
            .withRegion(Regions.US_EAST_2)
            .withCredentials(new AWSStaticCredentialsProvider(credentials))
            .withRegion(region).build();

        try {
            //            //PUSH TO BUCKET
            File fileObj = convertMultiPartFileToFile(file);
            s3Client.putObject(new PutObjectRequest(bucketName, fileName, fileObj));
            fileObj.delete();
            //Show Successful Upload
            redirectAttributes.addFlashAttribute("message_2", fileName + ": SuccessFully Uploaded On AWS S3");

            //JWT Token retrieval from HTTP request header
            final String authHeader = request.getHeader(AUTH_HEADER);
            String username = null;
            String jwt = null;

            if (authHeader != null && authHeader.startsWith("Bearer")) {
                jwt = authHeader.substring(7);
                username = jwtUtils.getUserNameFromJwtToken(jwt);
            }

            //Load logged in Username from JWT Token obtained
            CustomUserDetails customUser = (CustomUserDetails) userDetailsService.loadUserByUsername(username);
            Long userId = customUser.getId();

            UserPosts myUserPost = new UserPosts();

            myUserPost.setId(userId);
            myUserPost.setPostImage(fileName);

            // The code under here is how I saved a user profile by ID,               // but I am getting an error in my post repo
            // since there is no post Id until a user creates one.

            //            //Find User by id
            //            userProfile = userProfRepo.findByUserId(userId);
            //
            //            //Set resource name to
            //            userProfile.setProfile_banner(fileName);
            //
            //            //Save database changes
            //            userProfRepo.save(userProfile);
        } catch (Exception e) {
            e.printStackTrace();
        }
        model.addAttribute("userPosts", userPosts);
        return ResponseEntity.ok(new MessageResponse("File Upload Successful"));

    }

    private void isImage(MultipartFile file) {
        if (!Arrays.asList(
                IMAGE_JPEG.getMimeType(),
                IMAGE_SVG.getMimeType(),
                IMAGE_PNG.getMimeType(),
                IMAGE_GIF.getMimeType()).contains(file.getContentType())) {
            throw new IllegalStateException("File must be an image [" + file.getContentType() + "]");
        }

    }

    /**
     File Conversion
     **/
    private File convertMultiPartFileToFile(MultipartFile file) {
        File convertedFile = new File(file.getOriginalFilename());
        try (FileOutputStream fos = new FileOutputStream(convertedFile)) {
            fos.write(file.getBytes());
        } catch (IOException e) {
            // log.error("Error converting multipartFile to file", e);
        }
        return convertedFile;
    }


}

If anyone could help me at all, any information, tips, or anything, it would mean the world.如果有人可以帮助我,任何信息、提示或任何东西,那将意味着整个世界。 If I am missing anything that seems important please let me know.如果我遗漏了任何重要的东西,请告诉我。

So basically, I need to figure out how to get the input (both file AND title) at once,所以基本上,我需要弄清楚如何立即获取输入(文件和标题),

You need to send a POST request to the backend with your image and the title.您需要将带有图像和标题的 POST 请求发送到后端。 It HTML 5, the standard way to do this is with a form with an input of type file它 HTML 5,执行此操作的标准方法是使用带有文件类型输入的表单

There is actually also JavaScript API to set the file input to only accept image files;其实还有 JavaScript API 设置文件输入只接受图片文件; I think this worked using file extension.我认为这可以使用文件扩展名。

send it to my backend and save it in the Post DB.将它发送到我的后端并将其保存在 Post DB 中。 Am I gonna need to pass the title as an argument in my post controller?我是否需要在我的帖子 controller 中将标题作为参数传递?

Standard file posts have the name of the file included标准文件帖子包含文件的名称

Since the UserPost DB is a one to many relationship with a User, I am gonna need to pass that logged in user's ID to my controller so that it gets sent to the post DB, just not sure how.由于 UserPost 数据库是与用户的一对多关系,我需要将登录用户的 ID 传递到我的 controller,以便它被发送到 post 数据库,只是不确定如何。

You should not send the user id.您不应该发送用户 ID。 In fact, you should typically be wary of sending the user id to the front end at all.事实上,您通常应该对将用户 ID 发送到前端保持警惕。 Everything in javascript can be inspected by the user... or someone else who sits at his/her desk. javascript 中的所有内容都可以由用户...或坐在他/她办公桌前的其他人检查。

Instead you can rely on the current session (create on the backend) for user identification.相反,您可以依靠当前的 session(在后端创建)来进行用户识别。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在javascript前端显示来自Spring boot后端的图像文件 - Display an image file from Spring boot backend in javascript front end CORS 阻塞了我的后端服务器,如何解决? 使用 Springboot java 作为后端和 react js 作为我的前端 - CORS blocking my backend server, how to fix? Using Springboot java as backend and react js as my front end 无法从 React.js 将图像发布到我的 API 路由 - Cannot POST image to my API Route from React.js 我可以在同一个文件结构中同时运行我的 Node.js/Express 后端和 React.js 前端吗? - Can I run my Node.js/Express backend and React.js frontend together in the same file structure? 如何将新的 React JS 前端嵌入到我现有的单体 Node JS 应用程序中? - How can I embed new React JS Front-end in my existing monolithic Node JS App? 如何将后端创建用户 function 连接到前端(Node.js、React.js、MongoDB) - How to connect backend create user function to front end (Node.js, React.js, MongoDB) 如何使用 Node.js 服务器将我的图像数据从前端保存到 Mongoose 数据库 - How can I save my image data from front end to Mongoose database using Node.js server 如何将数据从 React 前端 Hooks 传递到 Express 后端 - How to pass data from React front end Hooks to Express backend 无法将数组类型的变量从React.js前端发送到Django后端 - Cannot send an array-type variable from React.js front-end to Django backend 如何通过道具[REACT.JS]将图片网址从父组件传递到子组件 - How can I pass image url from parent component to child component via props [REACT.JS]
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM