티스토리 뷰
저번 파일 업로드에 이어서 이번엔 파일 불러오기 및 다운로드를 구현해보겠습니다!
https://peachsoong.tistory.com/67
reactJS + springboot 파일 업로드 구현하기 (1)
✅ 개발 환경 Mac M1 노트북 React + Typescript SpringBoot + JPA - JDK 1.8 / Language Level 8 mySqk 사용 (로컬에서 돌렸습니다) IntelliJ 사용 React와 SpringBoot 환경이 모두 세팅되어 있다는 가정 하에..
peachsoong.tistory.com
기본 세팅 및 업로드는 위의 포스팅을 참고해주세요.
✅ React 코드
구현을 다 하고 나면 이미 저장된 이미지 목록을 확인할 수 있고, 다운로드 버튼을 통해 각 사진을 다운로드 받을 수 있습니다.
기능 구현에 초점을 맞춰 디자인은 전혀 고려하지 않았습니다... ^_^



⭐️ .env.development
아래의 코드를 보면 process.env.REACT_APP_API_URL이라는 변수가 계속 나타납니다!
프로젝트의 최상단에 .env.development 파일을 생성하여 아래의 내용을 넣습니다.
그럼 REACT_APP_API_URL 변수를 환경변수로 전역에서 사용할 수 있습니다.
.env.development 파일은 개발 환경 시 사용되고, 상황에 맞게 .env.local, .env.production 등 변수를 설정해주시면 됩니다.
REACT_APP_API_URL=http://localhost:8082
⭐️ 파일 불러오기
화면이 렌더링 되자마자 readImages 함수를 실행합니다.
이 함수는 GET request를 보내 저장된 이미지 리스트를 반환받습니다.
데이터에는 filename과 create_dt가 들어있습니다.

useEffect(() => {
readImages();
}, [])
const readImages = async () => {
await instance({ url: `/api/file/image`})
.then((response) => {
console.log('======= 이미지 목록 조회 성공 =======')
setImgList(response.data);
console.log(response.data);
})
.catch((error) => {
console.log('======= 이미지 목록 조회 실패 =======')
console.log(error);
})
}
⭐️ 파일 다운로드
다운로드 버튼을 눌렀을 때 a tag element를 생성합니다.
url은 download가 일어나는 url과 맵핑하고, download / type 속성을 지정합니다.
그리고 click event가 일어나게 해줍니다.
const downloadImage = async (filename) => {
const url = process.env.REACT_APP_API_URL+"/api/file/image/download?filename="+filename;
const download = document.createElement('a');
download.href = url;
download.setAttribute('download', filename);
download.setAttribute('type', 'application/json');
download.click();
}
⭐️ jsx 부분
imgList 변수에 담긴 파일 이름을 사용해서 백엔드에 저장된 URL로 접근하여 img src를 지정합니다.
그리고 다운로드 버튼을 누르면 위의 downloadImage 함수가 실행되어 이미지가 다운로드 됩니다.
return (
<div>
<h2>사진 업로드</h2>
<input type="file" id="file" onChange={handleChangeFile} multiple/>
<h3>업로드 한 사진 미리보기</h3>
{imgBase64.map((item) => {
return (
<img
key={item}
src={item}
alt={"First slide"}
style={{width:"200px", height:"150px"}}
/>
)
})}
<button onClick={WriteBoard} style={{border: '2px solid black'}}>이미지 업로드</button>
<br/>
<div>
<h2>사진 목록</h2>
{imgList.map((item) => {
return (
<div key={item.pid}>
<img
src={process.env.REACT_APP_API_URL+"/images/"+item.filename}
alt={"img"+item.pid}
style={{width:"200px", height:"150px"}}
/>
<button onClick={() => downloadImage(item.filename)}>다운로드</button>
</div>
)
})}
</div>
</div>
);
👉 main.tsx Full Code
import React, {useState, useEffect} from 'react';
import {instance, fileInstance} from "../hooks/useAxiosLoader";
function MainPage() {
const [imgBase64, setImgBase64] = useState([]);
const [imgFile, setImgFile] = useState(null);
const [imgList, setImgList] = useState([]);
useEffect(() => {
readImages();
}, [])
const handleChangeFile = (event: any) => {
console.log(event.target.files);
setImgFile(event.target.files);
setImgBase64([]);
for(let i=0 ; i<event.target.files.length ; i++) {
if(event.target.files[i]) {
let reader = new FileReader();
reader.readAsDataURL(event.target.files[i]);
reader.onloadend = () => {
const base64 = reader.result; // 비트맵 데이터 리턴, 이 데이터를 통해 파일 미리보기가 가능함
console.log(base64)
if(base64) {
let base64Sub = base64.toString()
setImgBase64(imgBase64 => [...imgBase64, base64Sub]);
}
}
}
}
}
const WriteBoard = async () => {
const fd = new FormData();
for(let i=0 ; i<imgFile.length ; i++) {
fd.append("file", imgFile[i]);
}
// 안돌아감.
// Object.values(imgFile).forEach((file) => {
// fd.append("file", file as Blob)
// });
fd.append(
"comment",
"hello world"
);
await fileInstance({
method: 'post',
url: '/api/file/image',
data: fd
})
.then((response) => {
if(response.data) {
console.log(response.data)
readImages();
setImgFile(null);
setImgBase64([]);
alert("업로드 완료!");
}
})
.catch((error) => {
console.log(error)
alert("실패!");
})
}
const readImages = async () => {
await instance({ url: `/api/file/image`})
.then((response) => {
console.log('======= 이미지 목록 조회 성공 =======')
setImgList(response.data);
console.log(response.data);
})
.catch((error) => {
console.log('======= 이미지 목록 조회 실패 =======')
console.log(error);
})
}
const downloadImage = async (filename) => {
const url = process.env.REACT_APP_API_URL+"/api/file/image/download?filename="+filename;
const download = document.createElement('a');
download.href = url;
download.setAttribute('download', filename);
download.setAttribute('type', 'application/json');
download.click();
}
return (
<div>
<h2>사진 업로드</h2>
<input type="file" id="file" onChange={handleChangeFile} multiple/>
<h3>업로드 한 사진 미리보기</h3>
{imgBase64.map((item) => {
return (
<img
key={item}
src={item}
alt={"First slide"}
style={{width:"200px", height:"150px"}}
/>
)
})}
<button onClick={WriteBoard} style={{border: '2px solid black'}}>이미지 업로드</button>
<br/>
<div>
<h2>사진 목록</h2>
{imgList.map((item) => {
return (
<div key={item.pid}>
<img
src={process.env.REACT_APP_API_URL+"/images/"+item.filename}
alt={"img"+item.pid}
style={{width:"200px", height:"150px"}}
/>
<button onClick={() => downloadImage(item.filename)}>다운로드</button>
</div>
)
})}
</div>
</div>
);
}
export default MainPage;
✅ Springboot 코드
저번 포스팅과 이어지므로 역시나 FileController.java, FileEntity, FileRepostiory 세 개의 파일이 필요합니다.
FileEntity, FileRepostiory 파일은 변동이 없으니 저번 포스팅을 참고해주세요!
⭐️ 파일 불러오기
모든 파일을 모두 불러오기 때문에 아주 간단하게 findAll을 사용하여 처리할 수 있습니다.
/**
* 이미지 불러오기
*/
@GetMapping("image")
public List<FileEntity> findAllImages() { return fileRepository.findAll(); }
⭐️ 파일 다운로드
프론트에서 파라미터로 파일 이름을 보냅니다.
파일을 업로드할 때 중복되지 않도록 안전한 이름을 사용했기 때문에, 이름 자체가 유니크한 키가 될 수 있다고 생각하여 구현했습니다.
/**
* 이미지 다운로드
*/
@GetMapping("image/download")
public List<FileEntity> downloadImage(
HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value="filename", required = true) String filename) {
File file = new File(filepath+filename);
FileInputStream fis = null;
BufferedInputStream bis = null;
ServletOutputStream sos = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
sos = response.getOutputStream();
String reFilename = "";
// IE로 실행한 경우인지 -> IE는 따로 인코딩 작업을 거쳐야 한다. request헤어에 MSIE 또는 Trident가 포함되어 있는지 확인
boolean isMSIE = request.getHeader("user-agent").indexOf("MSIE") != -1 || request.getHeader("user-agent").indexOf("Trident") != -1;
if(isMSIE) {
reFilename = URLEncoder.encode("이미지 파일.jpg", "utf-8");
reFilename = reFilename.replaceAll("\\+", "%20");
}
else {
reFilename = new String("이미지 파일.jpg".getBytes("utf-8"), "ISO-8859-1");
}
response.setContentType("application/octet-stream;charset=utf-8");
response.addHeader("Content-Disposition", "attachment;filename=\""+reFilename+"\"");
response.setContentLength((int)file.length());
int read = 0;
while((read = bis.read()) != -1) {
sos.write(read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
👉 FileController.java Full Code
package com.example.demo.controller;
import com.example.demo.entity.FileEntity;
import com.example.demo.repo.FileRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.filechooser.FileSystemView;
import java.io.*;
import java.net.URLEncoder;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/file")
public class FileController {
private final FileRepository fileRepository;
String filepath = "/Users/evelyn/Desktop/demo/src/main/resources/static/images/";
/**
* 이미지 불러오기
*/
@GetMapping("image")
public List<FileEntity> findAllImages() { return fileRepository.findAll(); }
/**
* 이미지 다운로드
*/
@GetMapping("image/download")
public List<FileEntity> downloadImage(
HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value="filename", required = true) String filename) {
File file = new File(filepath+filename);
FileInputStream fis = null;
BufferedInputStream bis = null;
ServletOutputStream sos = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
sos = response.getOutputStream();
String reFilename = "";
// IE로 실행한 경우인지 -> IE는 따로 인코딩 작업을 거쳐야 한다. request헤어에 MSIE 또는 Trident가 포함되어 있는지 확인
boolean isMSIE = request.getHeader("user-agent").indexOf("MSIE") != -1 || request.getHeader("user-agent").indexOf("Trident") != -1;
if(isMSIE) {
reFilename = URLEncoder.encode("이미지 파일.jpg", "utf-8");
reFilename = reFilename.replaceAll("\\+", "%20");
}
else {
reFilename = new String("이미지 파일.jpg".getBytes("utf-8"), "ISO-8859-1");
}
response.setContentType("application/octet-stream;charset=utf-8");
response.addHeader("Content-Disposition", "attachment;filename=\""+reFilename+"\"");
response.setContentLength((int)file.length());
int read = 0;
while((read = bis.read()) != -1) {
sos.write(read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 이미지 업로드
* @return Map<String, Object>
*/
@PostMapping("image")
public FileEntity uploadImage(HttpServletRequest request,
@RequestParam(value="file", required = false) MultipartFile[] files,
@RequestParam(value="comment", required = false) String comment) {
String FileNames = "";
// String filepath = "/images/";
String originFileName = files[0].getOriginalFilename();
long fileSize = files[0].getSize();
String safeFile = System.currentTimeMillis() + originFileName;
File f1 = new File(filepath + safeFile);
try {
files[0].transferTo(f1);
} catch (IOException e) {
e.printStackTrace();
}
final FileEntity file = FileEntity.builder()
.filename(safeFile)
.build();
return fileRepository.save(file);
}
}
이렇게까지 하면 파일 불러오기 및 다운로드 기능의 구현이 끝났습니다!
'reactJS' 카테고리의 다른 글
react + nextjs 스크롤탑 버튼 만들기 (0) | 2022.07.12 |
---|---|
reactJS + springboot 파일 업로드 구현하기 (1) (0) | 2022.04.26 |
typescript 기초 + react 프로젝트 생성 (0) | 2022.02.03 |
reactJS + typescript 프로젝트 만들기 (0) | 2022.01.27 |
open-graph-scraper 사용하기 (0) | 2022.01.26 |
- Total
- Today
- Yesterday
- 기초
- level1
- 자바스크립트
- 소프티어
- 프로그래머스
- reactjs
- JavaScript
- nomadcoder
- level3
- 이코테
- 파이썬
- React
- CORS
- 상태관리
- axios
- Hook
- css
- CS
- TypeScript
- 이진탐색
- programmers
- 노마드코더
- redux
- springboot
- dfs
- 이것이 취업을 위한 코딩테스트다
- 이것이코딩테스트다
- 면접을 위한 CS 전공지식 노트
- React.FC
- html
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |