DBILITY

react spring boot multipart upload 본문

front-end & ui/react

react spring boot multipart upload

DBILITY 2022. 2. 22. 16:08
반응형

이거 보고 광고 한번 안 누른 이는 삼대가 재수 없을지어다!ㅋㅋ

누르고 복권을 사라!

어찌하다 보니 된다. 물론 참고한 페이지가 있다.

https://satyajitpatnaik.medium.com/multipartfile-uploads-from-a-reactjs-ui-to-a-spring-boot-service-fdaeef9743dc

 

MultipartFile uploads from a ReactJS UI to a Spring Boot service

In this article, I will tell about the implementation of file uploads using Multipart requests. Multipart requests combine one or more…

satyajitpatnaik.medium.com

그림 1처럼 React로 UI를 만들고 파일 선택시 이미지가 바로 보여지게 하고, Save를 눌렀을때 axios를 통해 SpringBoot의 Controller로 전송.

Controller에선 @PostMapping을 통해 @RequestPart로 file이외의 param도 받고 싶었다. 중요한 것은 UI에서 전송할때 multipart로 보내니 file이외의 parameter도 blob처리를 해서 보내고 받으면 된다는 것이었다.

그림 1

다음은 React UI로 테스트라 useState를 썼다.

useRef hook을 살펴보자. current 속성을 가지는 object를 반환한다. current를 변경해도 re-rendering되지 않는다.

또, DOM이나 React element에 직접 접근할때도 사용한다. 여기서는 file type의 input에 직접 접근하여 click이벤트를 발생시키는데 사용하였다.jquery에선 어떻게 했더라..생각이 안남..블로그 어딘가 있을텐데..^^;

import {Button, Col, Form, Row} from "react-bootstrap";
import {useRef, useState} from "react";
import axios from "axios";
import {saveUrl} from "../data";


const New = () => {

    const [formField, setFormField] = useState({title: '', content: '', price: '0', inform_count: '0', file_name: '', file_path: '', file_type: ''});
    const uploadFile = useRef(null);

    const resetField = (e) => {
        setFormField({title: '', content: '', price: '0', inform_count: '0', file_name: '', file_path: '', file_type: ''});
    }

    const fieldChange = (e) => {
        let id = e.target.id;
        if (id === "title") {
            setFormField(Object.assign({}, formField, {title: e.target.value}));
        } else if (id === "content") {
            setFormField(Object.assign({}, formField, {content: e.target.value}));
        } else if (id === "price") {
            setFormField(Object.assign({}, formField, {price: e.target.value}));
        } else if (id === "inform_count") {
            setFormField(Object.assign({}, formField, {inform_count: e.target.value}));
        } else if (id === "uploadFile") {
            console.log(e);
            if (e.target.files.length > 0) {
                let file = e.target.files[0];
                let fileNam = file.name;
                let fileTyp = file.type;
                let reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = (e) => {
                    let image = reader.result;
                    setFormField(Object.assign({}, formField, {file_name: fileNam, file_path: image, file_type: fileTyp}));
                }
            } else {
                setFormField(Object.assign({}, formField, {file_name: '', file_path: '', file_type: ''}));
            }
        }
        console.log(formField);
    }
    const uploadClick = (e) => {
        uploadFile.current.click();
    };

    const saveData = (e) => {
        console.log(e);
        let formData = new FormData();
        let file;
        fetch(formField.file_path)
            .then(result => result.blob())
            .then(blob => {
                file = new File([blob], formField.file_name, {type: formField.file_type});
                //file.type = formField.file_type;
                formData.append("photo", file);
                return formData;
            })
            .then(formData => {
                let tmpData = Object.assign({},formField);
                delete tmpData.file_path;
                formData.append('roomDTO', new Blob([JSON.stringify(tmpData)],{type:'application/json'}));
                axios.post(saveUrl, formData, {
                    headers: {
                        'contentType': 'multipart/form-data',
                        'processData': false,
                        'cache': false
                    }
                }).then((response) => {
                    console.log(response);
                }).catch((reason) => {
                    console.log(reason);
                });
            })
            .catch(reason => {
                console.log(reason);
            });
    };

    return (
        <>
            <div className={'m-2'}>
                <Form>
                    <Form.Group controlId={'title'} as={Row}>
                        <Form.Label column={"sm"} xs={2}>Title</Form.Label>
                        <Col xs={10}>
                            <Form.Control type={'text'} size={"sm"} placeholder={'input title'} name={"title"} value={formField.title || ''}
                                          onChange={fieldChange}/>
                        </Col>
                    </Form.Group>
                    <Form.Group controlId={'content'} as={Row}>
                        <Form.Label column={"sm"} xs={2}>content</Form.Label>
                        <Col xs={10}>
                            <Form.Control as={"textarea"} rows={3} size={"sm"} placeholder={'input content'} name={"content"}
                                          value={formField.content || ''} onChange={fieldChange}/>
                        </Col>
                    </Form.Group>
                    <Form.Group controlId={'price'} as={Row}>
                        <Form.Label column={"sm"} xs={2}>price</Form.Label>
                        <Col xs={10}>
                            <Form.Control type={'number'} size={"sm"} name={"price"} min={0} value={formField.price || ''}
                                          onChange={fieldChange}/>
                        </Col>
                    </Form.Group>
                    <Form.Group controlId={'inform_count'} as={Row}>
                        <Form.Label column={"sm"} xs={2}>inform_count</Form.Label>
                        <Col xs={10}>
                            <Form.Control type={'number'} size={"sm"} name={"inform_count"} min={0} value={formField.inform_count || ''}
                                          onChange={fieldChange}/>
                        </Col>
                    </Form.Group>
                    <Form.Group controlId={'image'} as={Row}>
                        <Form.Label column={"sm"} xs={2}>image</Form.Label>
                        <Col xs={10}>
                            <Form.Row>
                                <Col xs={3}>
                                    <Button variant={'success btn-sm'} onClick={uploadClick}>파일선택</Button>
                                </Col>
                                <Col xs={"9"}>
                                    <Form.Control type={'input'} size={"sm"} name={"image"} readOnly={true}
                                                  value={formField.file_name || ''}/>
                                </Col>
                                {/*<Form.Control type={'file'} id={"uploadFile"} ref={uploadFile} style={{display:'none'}} key={fileImage.name||''} onChange={fieldChange}/>*/}
                                <Form.File id={"uploadFile"} ref={uploadFile} style={{display: 'none'}} key={formField.file_path || ''}
                                           onChange={fieldChange}/>
                            </Form.Row>
                            {
                                formField && (
                                    <div>
                                        <img src={formField.file_path} alt={formField.file_name} className={"w-100 border-0"} />
                                    </div>
                                )
                            }
                        </Col>
                    </Form.Group>
                    <Button variant={"primary"} type={"button"} onClick={saveData}>Save</Button>{` `}<Button variant={"info"}
                                                                                                             onClick={resetField}>Reset</Button>
                </Form>
            </div>
        </>
    )
}

export default New;

업로드 디렉토리를 uploads로 정하고 리소스설정을 했다.

package com.dbility.apps.dev.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET","POST")
                .allowedOrigins("http://localhost:3000")
                .allowCredentials(true);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
    }


    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/uploads/**")
                .addResourceLocations("file:uploads/")
                .setCachePeriod(20);
    }

    @Bean
    public CommonsMultipartResolver multipartResolver(){
        CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
        commonsMultipartResolver.setDefaultEncoding("UTF-8");
        commonsMultipartResolver.setMaxUploadSize(5*1024*1024);//5M
        commonsMultipartResolver.setMaxUploadSizePerFile(5*1024*1024);
        commonsMultipartResolver.setMaxInMemorySize(0);
        return commonsMultipartResolver;
    }
}

다음은 DTO와 Spring Boot Controller다.

package com.dbility.apps.dev.test;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RoomDTO {

    private String title;
    private String content;
    private String price;
    private String inform_count;
    private String file_name;
    private String file_type;

}
package com.dbility.apps.dev.test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestController
public class RoomsController {

    @Resource(name="roomsService")
    private RoomsService roomsService;

    //@CrossOrigin(origins = "http://localhost:3000")
    @GetMapping(value = "/findall")
    public List<RoomDTO> getRooms() throws Exception {
        return roomsService.findAll();
    }

    @PostMapping(value = "/save",consumes = {MediaType.APPLICATION_JSON_VALUE,MediaType.MULTIPART_FORM_DATA_VALUE},headers = {"Content-Type=multipart/form-data"})
    public Map<String,Object> save(@RequestPart(value = "roomDTO") RoomDTO roomDTO,
                                   @RequestPart(value="photo",required = false) MultipartFile photoFile,
                                   HttpServletRequest request) throws Exception {
        log.info("{}",roomDTO);
        log.info("{}",photoFile.getName());
        int retVal = roomsService.insertData(roomDTO,photoFile,request);
        Map<String, Object> rtMap = new HashMap<>();
        rtMap.put("SUCCESS",0);
        return rtMap;
    }

}

실제 업로드처리를 할 서비스다. 이렇게 하는게 맞는지는 나중에 알아봐야겠다.

package com.dbility.apps.dev.test;

import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service("roomsService")
public class RoomsServiceImpl implements RoomsService {

    @Resource(name = "roomsRepository")
    private RoomsRepository roomsRepository;

    @Autowired
    private ModelMapper modelMapper;

    @Override
    public List<RoomDTO> findAll() throws Exception {
        log.info("{}", LocalDateTime.now());
        List<RoomsEntity> roomList = roomsRepository.findAll();
        List<RoomDTO> roomDtoList = roomList.stream().map(room -> modelMapper.map(room, RoomDTO.class)).collect(Collectors.toList());
        return roomDtoList;
    }

    @Transactional
    @Override
    public int insertData(RoomDTO roomDTO, MultipartFile photoFile, HttpServletRequest request) throws Exception {

        int retVal = 0;
        //String uploadDir = request.getSession().getServletContext().getRealPath("/") +"static"+File.separator+"resources"+File.separator+"images";

        String uploadDir = "uploads"+File.separator+"images";
        log.info("{}", uploadDir);
        if (!photoFile.isEmpty()) {
            if (!photoFile.getOriginalFilename().isEmpty()) {
                try {
                    log.info("{}, {}", photoFile.getName(), photoFile.getOriginalFilename());
                    byte[] bytes = photoFile.getBytes();
                    File dir = new File(uploadDir);
                    if(!dir.exists()){
                        dir.mkdirs();
                    }
                    File uploadFile = new File(dir.getAbsolutePath()+File.separator+ photoFile.getOriginalFilename());
                    BufferedOutputStream uploadStream = new BufferedOutputStream(new FileOutputStream(uploadFile));
                    uploadStream.write(bytes);
                    uploadStream.close();

                    roomDTO.setFile_name("images/"+roomDTO.getFile_name());
                    RoomsEntity roomsEntity = modelMapper.map(roomDTO, RoomsEntity.class);

                    Object obj = roomsRepository.saveAndFlush(roomsEntity);
                    log.info("{}",obj);
                    if(!obj.equals(null)){
                        retVal = 1;
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    retVal = -1;
                }
            }
        }

        return retVal;
    }
}

Save를 눌러 저장을 실행하면 Back-End log가 다음과 같이 남는다. 

13:28:57.196 [http-nio-9090-exec-6] INFO         c.d.apps.dev.test.RoomsController   31 - RoomDTO(title=1, content=2, price=3, inform_count=4, file_name=화면 캡처 2022-02-16 115924.png, file_type=image/png)
13:28:57.196 [http-nio-9090-exec-6] INFO         c.d.apps.dev.test.RoomsController   32 - photo
13:28:57.196 [http-nio-9090-exec-6] DEBUG         o.h.e.t.internal.TransactionImpl   53 - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
13:28:57.196 [http-nio-9090-exec-6] DEBUG         o.h.e.t.internal.TransactionImpl   81 - begin
13:28:57.196 [http-nio-9090-exec-6] INFO        c.d.apps.dev.test.RoomsServiceImpl   45 - uploads\images
13:28:57.197 [http-nio-9090-exec-6] INFO        c.d.apps.dev.test.RoomsServiceImpl   49 - photo, 화면 캡처 2022-02-16 115924.png
13:28:57.199 [http-nio-9090-exec-6] DEBUG     org.hibernate.engine.spi.ActionQueue  281 - Executing identity-insert immediately
13:28:57.200 [http-nio-9090-exec-6] DEBUG                        org.hibernate.SQL  144 - 
    insert 
    into
        rooms
        (id, content, file_name, file_type, inform_count, price, title) 
    values
        (null, ?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        rooms
        (id, content, file_name, file_type, inform_count, price, title) 
    values
        (null, ?, ?, ?, ?, ?, ?)
13:28:57.200 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [1] as [VARCHAR] - [2]
13:28:57.200 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [2] as [VARCHAR] - [images/화면 캡처 2022-02-16 115924.png]
13:28:57.200 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [3] as [VARCHAR] - [image/png]
13:28:57.200 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [4] as [INTEGER] - [4]
13:28:57.201 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [5] as [BIGINT] - [3]
13:28:57.201 [http-nio-9090-exec-6] TRACE      o.h.type.descriptor.sql.BasicBinder   64 - binding parameter [6] as [VARCHAR] - [1]
13:28:57.201 [http-nio-9090-exec-6] DEBUG         o.h.id.IdentifierGeneratorHelper   78 - Natively generated identity: 5
13:28:57.201 [http-nio-9090-exec-6] DEBUG   o.h.r.j.i.ResourceRegistryStandardImpl  106 - HHH000387: ResultSet's statement was not registered
13:28:57.202 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  140 - Processing flush-time cascades
13:28:57.202 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  193 - Dirty checking collections
13:28:57.202 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  114 - Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
13:28:57.202 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  121 - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
13:28:57.202 [http-nio-9090-exec-6] DEBUG          o.h.internal.util.EntityPrinter  110 - Listing entities:
13:28:57.202 [http-nio-9090-exec-6] DEBUG          o.h.internal.util.EntityPrinter  117 - com.dbility.apps.dev.test.RoomsEntity{file_name=images/화면 캡처 2022-02-16 115924.png, file_type=image/png, price=3, inform_count=4, id=5, title=1, content=2}
13:28:57.202 [http-nio-9090-exec-6] INFO        c.d.apps.dev.test.RoomsServiceImpl   64 - RoomsEntity(id=5, title=1, content=2, price=3, inform_count=4, file_name=images/화면 캡처 2022-02-16 115924.png, file_type=image/png)
13:28:57.203 [http-nio-9090-exec-6] DEBUG         o.h.e.t.internal.TransactionImpl   98 - committing
13:28:57.203 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  140 - Processing flush-time cascades
13:28:57.203 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  193 - Dirty checking collections
13:28:57.203 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  114 - Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
13:28:57.203 [http-nio-9090-exec-6] DEBUG    o.h.e.i.AbstractFlushingEventListener  121 - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
13:28:57.203 [http-nio-9090-exec-6] DEBUG          o.h.internal.util.EntityPrinter  110 - Listing entities:
13:28:57.203 [http-nio-9090-exec-6] DEBUG          o.h.internal.util.EntityPrinter  117 - com.dbility.apps.dev.test.RoomsEntity{file_name=images/화면 캡처 2022-02-16 115924.png, file_type=image/png, price=3, inform_count=4, id=5, title=1, content=2}
반응형

'front-end & ui > react' 카테고리의 다른 글

react react-hook-form  (0) 2022.03.24
react state-management zustand  (0) 2022.03.15
react axios example  (0) 2022.02.16
react eslint message disable  (0) 2022.02.16
react react-icons  (0) 2022.02.15
Comments