繁体   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)?

我正在尝试制作一个允许注册用户提交图像文件(.jpeg、.png 等)以及该图像标题的应用程序。 我无法思考如何做到这一点。

我需要将图像发送到亚马逊 AWS S3 存储桶,并且我能够弄清楚如何执行此操作,但是添加标题输入让我很困惑如何从我的前端获取文件和标题 - end (JSON) 到我的后端 API 并将其保存到我的帖子数据库 (JPA)。 我有一个包含以下列的 user_post 数据库:ID(帖子 ID 主键)、user_id(来自用户表的外键)、标题和 post_image(包含要保存在数据库中的文件)。 所以基本上,我需要弄清楚如何立即获取输入(文件和标题),将其发送到我的后端并将其保存在 Post DB 中。

我是否需要在我的帖子 controller 中将标题作为参数传递? 由于 UserPost 数据库是与用户的一对多关系,我需要将登录用户的 ID 传递给我的 controller 以便它被发送到 post 数据库,我只是不确定如何。

我一直在网上到处寻找帮助,并认为我会在这方面给 Stack Overflow 一个机会,因为我迫切需要帮助。 我现在通过本地存储使用 JWT 身份验证来访问用户信息,例如用户名和 ID。 在我通过使用主体中的“文件”键然后继续选择要选择的图像来使用 Postman 测试我的 API 之前,但是如果我也想添加标题,我该怎么做呢? 理想情况下,它会是相同的,但使用文本而不是文件作为类型,如下所示,但我不确定。 (当然,如果我认为正确,则取决于我为标题设置的键)。

在此处输入图像描述

这是我的前端的屏幕截图,仅用于视觉效果,以防万一。

在此处输入图像描述

这是我的代码,我为它如此混乱而道歉,我正在努力尽快清理它。 我将从前端开始。 我相信我需要为我的标题输入设置密钥,以便它被传递到我的后端 API,但我什至不确定我是否做对了。 我还需要使用自定义表单验证,因为我正在处理图像上传。 我最终在 useForm.js 文件中调用了 API,因为我的表单验证是在该自定义挂钩中完成的。

上传.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;

使用表单.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;

后端代码:

用户.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;
    }
}

用户库.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:

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:

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;
    }


}

如果有人可以帮助我,任何信息、提示或任何东西,那将意味着整个世界。 如果我遗漏了任何重要的东西,请告诉我。

所以基本上,我需要弄清楚如何立即获取输入(文件和标题),

您需要将带有图像和标题的 POST 请求发送到后端。 它 HTML 5,执行此操作的标准方法是使用带有文件类型输入的表单

其实还有 JavaScript API 设置文件输入只接受图片文件; 我认为这可以使用文件扩展名。

将它发送到我的后端并将其保存在 Post DB 中。 我是否需要在我的帖子 controller 中将标题作为参数传递?

标准文件帖子包含文件的名称

由于 UserPost 数据库是与用户的一对多关系,我需要将登录用户的 ID 传递到我的 controller,以便它被发送到 post 数据库,只是不确定如何。

您不应该发送用户 ID。 事实上,您通常应该对将用户 ID 发送到前端保持警惕。 javascript 中的所有内容都可以由用户...或坐在他/她办公桌前的其他人检查。

相反,您可以依靠当前的 session(在后端创建)来进行用户识别。

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM