게시글을 작성할 때 이미지 파일도 업로드 가능하게 기능을 추가하였다.
참고로 파일 업로드를 구현할 때 폼에 enctype="multipart/form-data"를 추가해줘야한다.
해시태그는 구현중이다........
file:
dir: /Users/이름/Desktop/file-upload/
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Board extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob
private String content;
@ManyToOne(fetch = FetchType.LAZY) "user" 추가해주면 됨.
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // 글이 삭제되면 댓글 모두 삭제
@OrderBy("createdDate")
private List<Reply> replies = new ArrayList<>();
@OneToMany(mappedBy = "board",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ImgFile> imgFiles = new ArrayList<>();
@OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Tag> tags = new ArrayList<>();
public void setUser(User user){
this.user = user;
}
public void setImgFile(List<ImgFile> imgFiles){
this.imgFiles = imgFiles;
for (ImgFile file : imgFiles) {
file.setBoard(this);
}
}
public void addTag(Tag tag){
tags.add(tag);
tag.setBoard(this);
}
public void update(String title, String content) {
this.title = title;
this.content = content;
}
@Builder
public Board(String title, String content) {
this.title = title;
this.content = content;
}
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED) // 이거 추가해주니까 되네... 이유가..
public class ImgFile extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String originFilename; // 업로드된 파일 이름
private String storeFilename; // 저장한 파일명 ex) 132-5sdf-23451-1as.png -> UUID로 식별자 생성 + 확장자
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
private Board board;
@Builder
public ImgFile(String originFilename, String storeFilename) {
this.originFilename = originFilename;
this.storeFilename = storeFilename;
}
public void setBoard(Board board) {
this.board = board;
}
}
@Component
public class FileStore {
@Value("${file.dir}") // application.yaml or application.properties에 지정한 파일 경로
private String fileDir;
// 파일 저장 경로 Full
public String getFullPath(String filename){
return fileDir + filename;
}
public ImgFile storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()){
return null;
}
String originalFileName = multipartFile.getOriginalFilename();
String ext = extractedExt(originalFileName);
String storeFileName = createStoreFileName(ext);
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return ImgFile.builder()
.originFilename(originalFileName)
.storeFilename(storeFileName)
.build();
}
// 여러 개 업로드 하는 경우
public List<ImgFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException{
List<ImgFile> files = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
if (multipartFile.isEmpty()) {
return null;
}
files.add(storeFile(multipartFile));
}
return files;
}
// UUID를 활용해 저장용 파일명
private String createStoreFileName(String ext) {
String uuid = UUID.randomUUID().toString();
String storedFileName = uuid + "." + ext;
return storedFileName;
}
// 파일 확장자 추출
private String extractedExt(String originalFileName) {
int pos = originalFileName.lastIndexOf(".");
String ext = originalFileName.substring(pos + 1);
return ext;
}
}
@Service
@RequiredArgsConstructor
public class FileService {
private final FileStore fileStore;
private final FileRepository fileRepository;
public List<ImgFile> transferImgFile(List<MultipartFile> imgFile) throws IOException {
List<ImgFile> imgFiles = fileStore.storeFiles(imgFile);
return imgFiles;
}
public void deleteBeforeFile(Board board){
if (!board.getImgFiles().isEmpty()) {
// 이전 첨부파일 삭제
if (!board.getImgFiles().isEmpty()) {
for (ImgFile imgFile : board.getImgFiles()) {
// 파일 시스템에서 실제 이미지 파일 삭제
File imgFileOnDisk = new File(fileStore.getFullPath(imgFile.getStoreFilename()));
if (imgFileOnDisk.exists()) {
imgFileOnDisk.delete();
}
// 데이터베이스에서 이미지 파일 레코드 삭제
this.delete(imgFile);
}
}
}
}
public void delete(ImgFile imgFile){
fileRepository.delete(imgFile);
}
}
public interface FileRepository extends JpaRepository<ImgFile, Long> {
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BoardSaveRequestDto {
private String title;
private String content;
private List<MultipartFile> imgFiles;
public Board toEntity() {
return Board.builder()
.title(title)
.content(content)
.imgFiles(new ArrayList<>())
.build();
}
}
...
// 글 등록
@PostMapping("/board/form")
public String boardSave(@ModelAttribute BoardSaveRequestDto boardSaveRequestDto,
@RequestBody TagSaveRequestDto tagSaveRequestDto, // 해시태그
HttpSession session) throws IOException {
User loginUser = (User) session.getAttribute("loginUser");
boardService.save(boardSaveRequestDto, loginUser);
return "redirect:/boards";
}
...
// 게시판 등록
private final BoardRepository boardRepository;
private final FileService FileService;
...
@Transactional
public Long save(BoardSaveRequestDto request, User user) throws IOException {
List<ImgFile> imgFiles = FileService.transferImgFile(request.getImgFiles());// List<multipartfile> -> List<ImgFile>
Board board = request.toEntity(); // title, content
board.setUser(user);
if (imgFiles == null){
board.setImgFile(new ArrayList<>()); // 첨부파일이 없더라도 게시글이 저장이 되도록하기 위해서 추가함.
} else {
board.setImgFile(imgFiles); //
}
Board savedBoard = boardRepository.save(board);
return savedBoard.getId();
}
다음은 게시글을 수정할 때의 로직이다.
// 게시판 등록
// 게시글 수정
@Transactional
public void update(Long id, BoardUpdateRequestDto request) throws IOException {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 포스트입니다."));
List<ImgFile> imgFiles = FileService.transferImgFile(request.getImgFiles()); // 새로 업로드한 파일
FileService.deleteBeforeFile(board); // 이전에 업로드 한 파일 삭제
// 수정한 첨부파일로 연관관계 매핑
board.setImgFile(imgFiles);
board.update(request.getTitle(), request.getContent());
뭔가 부족함이 많은 코드인거같다. 계속 공부하면서 리팩토링 해야겠다.
피드백을 주시면 감사하겠습니다.........