Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- JavaScript
- GIT
- Python
- SQL
- tomcat
- Spring
- xPlatform
- react
- MSSQL
- R
- window
- table
- IntelliJ
- NPM
- Eclipse
- mybatis
- Java
- Android
- Sqoop
- hadoop
- plugin
- 보조정렬
- 공정능력
- es6
- Kotlin
- vaadin
- SPC
- SSL
- Express
- mapreduce
Archives
- Today
- Total
DBILITY
react spring boot multipart upload 본문
반응형
이거 보고 광고 한번 안 누른 이는 삼대가 재수 없을지어다!ㅋㅋ
누르고 복권을 사라!
어찌하다 보니 된다. 물론 참고한 페이지가 있다.
그림 1처럼 React로 UI를 만들고 파일 선택시 이미지가 바로 보여지게 하고, Save를 눌렀을때 axios를 통해 SpringBoot의 Controller로 전송.
Controller에선 @PostMapping을 통해 @RequestPart로 file이외의 param도 받고 싶었다. 중요한 것은 UI에서 전송할때 multipart로 보내니 file이외의 parameter도 blob처리를 해서 보내고 받으면 된다는 것이었다.
다음은 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