[Spring Boot] 게시판 프로젝트 - 3

Hyeonseok Jeong·2023년 10월 1일
0

추석을 보내고 게시판 프로젝트의 회고록이자 설명글 3편을 작성해본다.
모두 메리 추석~

사용한 기술 스택

  • Spring Boot 3
  • Spring Security
  • Spring JPA
  • Java
  • MySQL
  • Thymeleaf
  • LomBok
  • HTML
  • CSS
  • Javascript

구현한 기능

  • 스프링 시큐리티를 이용한 로그인 기능 (작성 완료)
  • 스프링 시큐리티를 이용한 회원가입 기능 (작성 완료)
  • 게시판 리스트
  • 게시판 디테일 (다음글, 이전글 링크 구현)
  • 게시글 생성 & 다중 파일 업로드 기능 구현
  • 파일 다운로드 기능
  • 게시글 수정
  • 검색 기능
  • 페이지 네이션 기능
  • 댓글 생성
  • 댓글 삭제

오늘의 구현

  • 게시글 생성 & 다중 파일 업로드 기능 구현
  • 파일 다운로드 기능

게시글 생성 & 다중 파일 업로드 기능 구현

게시글 생성의 경우 파일 업로드하는 부분이 까다로웠다 저번 Servlet& JSP 글에서 파일업로드 부분을 따로 하지않고 나중에 aws로 해볼려고 했는데 스프링을 공부하면서 가장 중요한 기본 로컬로 구현하는것도 제대로 못하면서 할려고하는게 자만하는 것 같아 차근차근하기 위해 로컬로 파일 업로드 부분을 구현하였다. (여기서 말하는 로컬은 aws가 아닌 사용하고 있는 컴퓨터를 말함)

먼저 게시글 생성 부분이다.
게시글 생성 부분또한 회원가입당시 검증부분을 프론트에서 처리한것처럼 이와 같이 처리하였는데 이부분도 좀더 백엔드에서 한번더 처리해 주었으면 좋았을 것 같다. (이유를 한번 생각해 보았는데 백엔드에서 처리를 안하면 따로 요청을 우회해서 하게되면 그대로 회원가입이나 게시글이 생성될 수 있고 그로인한 해킹 같은 보안 관련 문제도 있을것 같다는 생각이든다.)

   public void create (BoardForm boardForm, SiteUser siteUser, List<MultipartFile> files){
        Board board = new Board();
        List<FileEntity> fileEntityList = new ArrayList<>();
        board.setSubject(boardForm.getSubject());
        board.setContent(boardForm.getContent());
        board.setRegDate(LocalDateTime.now());
        board.setHit(0);
        board.setSiteUser(siteUser);
        board.setFileEntityList(fileEntityList);
        this.boardRepository.save(board);

        try {
            for(MultipartFile file : files) {
                fileEntityList.add(this.fileService.saveFile(file, board));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

위의 코드는 서비스 로직에서의 게시글 생성 부분이다.
Board 객체를 생성후 각 속성에 맞춰 데이터를 집어 넣은다음 저장해주는 간단한 코드이다. (롬복사용하면서 setter를 이용해본다고 위와 같이 사용했는데 생성자를 통해서 등록하는게 더 간편하고 캡슐화적으로 좋을것 같다.)

물론 아직 끝은 아니다 중요한 코드가 남아있다

try {
            for(MultipartFile file : files) {
                fileEntityList.add(this.fileService.saveFile(file, board));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

해당 코드는 게시글 작성시 파일을 등록하였을경우 저장하기위해 사용되는 코드이다.
List<MultipartFile> 형식으로 받아온 파일을 for문으로 fileService의 saveFile메소드를 통해 실제 로컬에 파일 저장 및 DB에 파일 정보를 저장한후 반환되는 FileEntityList<FileEntity> 객체에 넣어 주어 게시글 디테일과같은 사용처 에서 사용할수 있도록 하였다.

  • fileService
package com.mysite.board.siteFile;


import com.mysite.board.siteBoard.Board;
import lombok.RequiredArgsConstructor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RequiredArgsConstructor
@Service
public class FileService {

    @Value("${file.dir}")
    private String fileDir;

    private final FileRepository fileRepository;

    public FileEntity saveFile(MultipartFile files, Board board) throws IOException {
        if (files.isEmpty()) {
            return null;
        }
        // 원래 파일 이름 추출
        String origName = files.getOriginalFilename();

        // 파일 이름으로 쓸 uuid 생성
        String uuid = UUID.randomUUID().toString();

        // 확장자 추출(ex : .png)
        String extension = origName.substring(origName.lastIndexOf("."));

        // uuid와 확장자 결합
        String savedName = uuid + extension;

        // 파일을 불러올 때 사용할 파일 경로
        String savedPath = fileDir + savedName;

        // 파일 엔티티 생성
        FileEntity file = FileEntity.builder()
                .orgNm(origName)
                .savedNm(savedName)
                .savedPath(savedPath)
                .board(board)
                .build();


        // 실제로 로컬에 uuid를 파일명으로 저장
        files.transferTo(new File(savedPath));

        // 데이터베이스에 파일 정보 저장
        FileEntity savedFile = fileRepository.save(file);

        return savedFile;
    }
}

그리고 이런 서비스 로직을 사용하는 컨트롤단을 살펴보자면

    @PreAuthorize("isAuthenticated()")
    @GetMapping("/create")
    public String create (BoardForm boardForm) {
        return "board_form";
    }

    @PreAuthorize("isAuthenticated()")
    @PostMapping("/create")
    public String create (BoardForm boardForm, @RequestParam("files") List<MultipartFile> files, Principal principal) {
        SiteUser siteUser = this.userService.getUser(principal.getName());
        this.boardService.create(boardForm, siteUser, files);
        return "redirect:/board/list";
    }

로그인(인증)을 하였을 경우에만 해당 페이지로 넘어갈 수 있도록 @PreAuthorize("isAuthenticatrd()")를 사용하였고, BoardForm 클래스를 만들어 타임리프에서 th:object="${boardForm}"th:field="*{subject}"와 같이 사용하여 폼데이터를 받아올 수 있도록 하였다
이후에는 Principal 를 이용해 현재 이용중인 사용자의 이름(getName())을 가져와 Service단에서 사용할 수 있도록 파라미터로 전달하면 게시글 생성이 완료된다.

  • 추가사항 - FileEntity
    위에서 언급한 FileEntity에 대한 코드를 적지 않아 추가
package com.mysite.board.siteFile;

import com.mysite.board.siteBoard.Board;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@Table(name = "file")
@Entity
public class FileEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orgNm;

    private String savedNm;

    private String savedPath;

    @ManyToOne
    private Board board;


    @Builder
    public FileEntity(Long id, String orgNm, String savedNm, String savedPath, Board board) {
        this.id = id;
        this.orgNm = orgNm;
        this.savedNm = savedNm;
        this.savedPath = savedPath;
        this.board = board;
    }
}

사진

  • 게시글 생성

  • 파일 다운로드

마무리

이번 복습글에서 복습할건 사실 무엇보다 파일 생성 부분이다.
지금도 처음부터 끝까지 한번 설명해보세요 라는 질문이 들어온다면 버벅이며 중간에 끊길것같다.

블로그 글도 정확하게 설명하기 보단 두루뭉실한 부분이 있는데 FileService, FileEntity 이 두 부분에 대해서는 사용을 할경우 코드를 보면서 아 이렇게 했고 저부분은 UID를 생성해서 똑같은 파일명을 가진 파일을 저장할경우 판별하기위해 UID를 생성해서 해당 이름으로 로컬에 파일을 저장하고 나중에 찾는구나 라고 설명 자체는 가능하다.

하지만 좀더 자세하게 MultipartFile 이 스프링 프레임워크에서 어떤 로직을 가지고 어떻게 사용되는지 설명해 보라고한다면 어려울것 같다 MultipartFile의 경우 Form 태그의 기본적으로 사용할경우 보내지는 데이터의 형식과 다르게 전송된다는 부분은 이해하고 있지만 그 다른부분이 스프링 프레임워크에서 어떤식으로 사용되는지 아직까지는 완숙하지 못한것 같다.

물론 한탄만이 아닌 나 스스로 부족한 부분을 알고 공부하기 위해서 글을 적는거니 여유가 생겼을때 다시한번 처음부터 공부를 해볼예정이다.

그럼 오늘치 끝!

  • 이상하게 사진이 등록되지 않는다 왜지;;;

+ 파일 다운로드

파일 다운로드부분도 있는데 왜 작성하다 말고 끝을 했는지;;;

    @GetMapping("/download/{id}")
    public ResponseEntity<Resource> download(@PathVariable("id") Long id) throws MalformedURLException {
        FileEntity file = this.fileRepository.findById(id).orElse(null);
        UrlResource resource = new UrlResource("file:" + file.getSavedPath());
        String encodedFileName = UriUtils.encode(file.getOrgNm(), StandardCharsets.UTF_8);
        String contentDisposition = "attachment; filename=\"" + encodedFileName + "\"";

        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition).body(resource);
    }

파일 다운로드 경우 디테일 페이지에서 게시글이 가지고 있는 파일의 id를 받아 로컬에 존재하는 파일의 인코딩된 이름을 찾아 사용자가 다운로드 할 수 있게 해준다.

이부분은 사실 말그대로 로컬에 있는 파일을 찾기 -> 확인하기 -> 인코딩하기 -> 다운로드 하기 가 끝인 작업이라 그렇게 설명할 부분은 없는것 같다
라고 말했지만 사실 많지만 아직 미숙해서 넘어간거라고 할 수 있다., 좀더 알아봐야할것들 ResponseEntity, new UrlResource, UriUtils 등...

일단 선조치 후보고느낌으로 사용해보고 공부한다는 마인드로 하고있긴한데 포트폴리오하는라 급급해서 자꾸 넘어가는 느낌이지만 나 스스로 나중에 할 자신이 있다는 마인드로 우선 순위를 통해 포트폴리오를 한후 마무리에서 적었던 것 처럼 처음부터 공부를 다시 해봐야겠다.

그럼 정말 끝

profile
풀스텍 개발자

0개의 댓글