티스토리 뷰

728x90

저번 파일 업로드에 이어서 이번엔 파일 불러오기 및 다운로드를 구현해보겠습니다!

 

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 코드

구현을 다 하고 나면 이미 저장된 이미지 목록을 확인할 수 있고, 다운로드 버튼을 통해 각 사진을 다운로드 받을 수 있습니다.

기능 구현에 초점을 맞춰 디자인은 전혀 고려하지 않았습니다... ^_^

main 페이지 / 이미지 다운로드시

 

⭐️ .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);
    }

}

 

이렇게까지 하면 파일 불러오기 및 다운로드 기능의 구현이 끝났습니다!

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함