각진 세상에 둥근 춤을 추자

[CRUD] 5. 게시글 작성 기능 구현 - 단일 파일 업로드 및 다운로드 본문

프로젝트/CRUD

[CRUD] 5. 게시글 작성 기능 구현 - 단일 파일 업로드 및 다운로드

circle.j 2024. 1. 10. 18:29

<input type="file"> 태그를 이용해 단일 파일 업로드 기능을 구현한다. 

 


1. application.properties 

 

application.properties에 파일 업로드를 위한 multipart 설정을 추가한다. 

#############################################
#파일 업로드 설정
#############################################
spring.servlet.multipart.location=img/
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB

 


2. HTML 화면 작성하기 

 

HTML에 파일 업로드를 위한 <input> 태그를 작성한다. 

아이디를 fileUpload라고 작성한다. 

<input type="file" id="fileUpload">

 


3. 파일 정보를 저장할 VO 작성하기

 

파일 정보를 저장할 VO를 작성한다. 

@Data
public class FileVO {

    private int fno;			// 파일 번호
    private String parent;		// 게시글 번호
    private String newName;		// 새 파일명
    private String oriName;		// 기존 파일명
    private String del_yn;		// 삭제여부

    /* 추가: 게시글 번호 */
    private String no;

}

 


4. JS - Ajax로 파일 전송하기
let btnSave = document.getElementById("btnSave");                   // 저장 버튼

/* Initialize */
document.addEventListener('DOMContentLoaded', function() {
    /** CRUD 버튼 이벤트 등록 */
    addEventListenerCRUDBtn();
});

function addEventListenerCRUDBtn(){
    btnSave.addEventListener("click", fnSave);
}

function fnSave() {

    let fileUpload = document.getElementById("fileUpload");            // 파일 업로드
    let formData = new FormData();                                     // 파일 업로드를 위한 폼 생성

    let selectedFile = fileUpload.files[0];     // 업로드한 파일

    let jsonData = {};

    jsonData = {
        "name":selectedFile.name
    };


    if(confirm("등록하시겠습니까?")){

        ajaxAPI("/board/write", jsonData, "POST").then(response => {

            // 글 번호
            let no = response.no;

            // 파일 업로드
            console.log("selectedFile: " + selectedFile);

           formData.append("file", selectedFile);
           formData.append("oriName", selectedFile.name);


            $.ajax({
                url: '/board/upload',
                processData: false,
                contentType: false,
                data: formData,
                type: 'POST',
                beforeSend: function (xhr) {
                    xhr.setRequestHeader(header, token);
                },
                success: function(result) {
                    console.log("result: " + result);

                }
            })

    }

}

 


5. Controller - 서버에 업로드하기 

Controller에 파일을 업로드할 경로(서버)를 설정한 후 해당 경로에 업로드한다. 

@PostMapping("/board/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file){

    String uploadPath = "/Users/yiwonjeong/Desktop/upload";	// 파일 업로드 경로
    String originalFilename = file.getOriginalFilename();	// 파일 기존 이름

    log.warn("multipart upload: "+ file);
    log.warn("Original Filename: " + file.getOriginalFilename());
    log.warn("Content Type: " + file.getContentType());
    log.warn("Size: " + file.getSize());

	// 파일 업로드 경로에 파일 업로드하기 
    Path filePath = Paths.get(uploadPath, originalFilename);
     try {
    Files.write(filePath, file.getBytes());
        return ResponseEntity.ok("File uploaded successfully!");
    } catch (IOException e) {
        log.error("Error uploading file: " + e.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Failed to upload file. Please try again.");
    }
}

 


6. Service - 파일 정보를 DB에 저장하기 

Service에서 파일명을 새로 수정한 후 해당 파일의 정보를 DB에 저장한다. 

public void updateBoard(BoardVO boardVO){

    // 새 파일명 생성
    String oriName = boardVO.getName();
    String tempName = oriName.substring(oriName.lastIndexOf("."));
    String newName = UUID.randomUUID().toString() + tempName;
    int fno = boardVO.getFno();

    // 파일 정보 작성
    FileVO fileVO = new FileVO();
    fileVO.setOriName(oriName);
    fileVO.setNewName(newName);
    fileVO.setParent(boardVO.getNo());
    fileVO.setFno(fno);

    dao.updateFile(fileVO);

}

 


7. Dao - xml 파일과 연결하기
public void updateFile(FileVO fileVO);

 


8. xml - DB에 저장하기
<insert id="insertFile" parameterType="kr.co.crud.domain.FileVO">
    INSERT INTO CRUD_FILE SET
        `parent` = #{parent},
        `newName` = #{newName},
        `oriName` = #{oriName}
</insert>

 


9. 파일 다운로드 

앞서 업로드한 파일을 화면에 표시한 뒤, 클릭 시 파일을 다운로드한다. 

아래 HTML 코드와 같이 파일명과 파일번호 값을 넣은 후 아이디를 작성한다. 

<div class="view-file" id="fileDownload" th:text="${board.oriName}" th:data-fno="${board.fno}">file</div>

 

해당 페이지와 연결된 JS에서 클릭 시 파일을 다운로드 받을 수 있게 한다. 

let btnFileDownload = document.getElementById("fileDownload");          // 파일 다운로드 버튼

/* Initialize */
document.addEventListener('DOMContentLoaded', function() {
    /** CRUD 버튼 이벤트 등록 */
    addEventListenerCRUDBtn();
});

function addEventListenerCRUDBtn(){
	btnFileDownload.addEventListener("click", fnFileDownload);
}

/* 파일 다운로드 */
function fnFileDownload() {
    let fileNo = btnFileDownload.getAttribute("data-fno");

    ajaxAPI("/board/fileDownload?fno=" + fileNo, null, "GET").then(response => {
        console.log("fileDownload ajax success");
    });

}

 

해당 파일의 번호를 통해 서버에 업로드된 파일을 다운로드 받는다. 

아래는 컨트롤러 파일 코드이다. 

편의상 selectFile을 통해 파일 번호로 파일 이름을 찾아주었다. 

@GetMapping("/board/fileDownload")
@ResponseBody
public ResponseEntity<Resource> fileDownload(@RequestParam("fno") String fno) throws IOException {

    int fnoInt = Integer.parseInt(fno);

    FileVO file = service.selectFile(fnoInt);

    ResponseEntity<Resource> response = service.fileDownload(file.getOriName());

    return response;

}

 

서비스 파일에서 정해둔 서버(경로)에 저장된 파일을 찾아 다운로드한다. 

public ResponseEntity<Resource> fileDownload(String oriName) throws IOException {

    String uploadPath = "/Users/yiwonjeong/Desktop/upload/";
    Path path = Paths.get(uploadPath, oriName);

    String contentType = Files.probeContentType(path);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentDisposition(ContentDisposition.builder("attachment")
            .filename(oriName)
            .build());

    headers.add(HttpHeaders.CONTENT_TYPE, contentType);
    Resource resource = new InputStreamResource(Files.newInputStream(path));

    return new ResponseEntity<>(resource, headers, HttpStatus.OK);

}