반응형
게시판 기능
- 글 작성, 조회, 수정, 삭제, 리스트 조회, 댓글, 좋아요 기능
- 자세한 기능 설계는 [Spring Boot] 게시판 만들기 1 - 설계 & 결과 참고
BoardRepository
- findAllByCategoryAndUserUserRoleNot() : 해당 카테고리에 있는 게시글을 페이지에 맞게 조회, 이 때 ADMIN이 작성한 글(공지)는 포함 X
- findAllByCategoryAndUserUserRole() : 해당 카테고리에 있는 공지 글 조회
- findAllByCategoryAndTitleContainsAndUserUserRoleNot(), findAllByCategoryAndUserNicknameContainsAndUserUserRoleNot() : 검색 기능에 사용
- findAllByUserLoginId() : 유저의 마이 페이지에서 내가 작성한 글 조회 시 사용
- countAllByUserUserRole() : 전체 공지글이 몇개 있는지 조회 시 사용
- countAllByCategoryAndUserUserRoleNot() : 해당 카테고리에 공지글을 제외한 글이 몇개 있는지 조회 시 사용
@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {
Page<Board> findAllByCategoryAndUserUserRoleNot(BoardCategory category, UserRole userRole, PageRequest pageRequest);
Page<Board> findAllByCategoryAndTitleContainsAndUserUserRoleNot(BoardCategory category, String title, UserRole userRole, PageRequest pageRequest);
Page<Board> findAllByCategoryAndUserNicknameContainsAndUserUserRoleNot(BoardCategory category, String nickname, UserRole userRole, PageRequest pageRequest);
List<Board> findAllByUserLoginId(String loginId);
List<Board> findAllByCategoryAndUserUserRole(BoardCategory category, UserRole userRole);
Long countAllByUserUserRole(UserRole userRole);
Long countAllByCategoryAndUserUserRoleNot(BoardCategory category, UserRole userRole);
}
BoardService
- getBoardList() : 해당 카테고리에 있는 글 list를 return
- 이 때, 검색을 했다면 제목, 유저 닉네임에 키워드가 포함되는 글을 return
- writeBoard() : 글 작성
- Board 저장 -> 이미지가 있다면 이미지 저장 후 저장된 Board에 해당 이미지 정보 추가 삽입
- 만약 가입 인사 게시판에 글이 저장된 경우라면 로그인 한 유저의 등급을 SILVER로 조정
- findMyBoard() : 마이 페이지에서 내가 작성한 글, 내가 좋아요 누른 글, 내가 댓글을 추가한 글을 category로 분류해 List를 return 해주는 메소드
- 내가 좋아요 누른 글은 로그인 한 유저의 loginId를 가지는 Like를 모두 불러온 후 Like에 저장된 Board들을 List로 변환 후 return
- 내가 댓글을 추가한 글도 좋아요와 비슷하긴 하지만 하나의 글에 여러개의 댓글을 단 경우, 한번만 return 되게 하기 위해 HashSet을 사용하여 중복을 제거해서 return
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
private final UserRepository userRepository;
private final LikeRepository likeRepository;
private final CommentRepository commentRepository;
private final UploadImageService uploadImageService;
public Page<Board> getBoardList(BoardCategory category, PageRequest pageRequest, String searchType, String keyword) {
if (searchType != null && keyword != null) {
if (searchType.equals("title")) {
return boardRepository.findAllByCategoryAndTitleContainsAndUserUserRoleNot(category, keyword, UserRole.ADMIN, pageRequest);
} else {
return boardRepository.findAllByCategoryAndUserNicknameContainsAndUserUserRoleNot(category, keyword, UserRole.ADMIN, pageRequest);
}
}
return boardRepository.findAllByCategoryAndUserUserRoleNot(category, UserRole.ADMIN, pageRequest);
}
public List<Board> getNotice(BoardCategory category) {
return boardRepository.findAllByCategoryAndUserUserRole(category, UserRole.ADMIN);
}
public BoardDto getBoard(Long boardId, String category) {
Optional<Board> optBoard = boardRepository.findById(boardId);
// id에 해당하는 게시글이 없거나 카테고리가 일치하지 않으면 null return
if (optBoard.isEmpty() || !optBoard.get().getCategory().toString().equalsIgnoreCase(category)) {
return null;
}
return BoardDto.of(optBoard.get());
}
@Transactional
public Long writeBoard(BoardCreateRequest req, BoardCategory category, String loginId, Authentication auth) throws IOException {
User loginUser = userRepository.findByLoginId(loginId).get();
Board savedBoard = boardRepository.save(req.toEntity(category, loginUser));
UploadImage uploadImage = uploadImageService.saveImage(req.getUploadImage(), savedBoard);
if (uploadImage != null) {
savedBoard.setUploadImage(uploadImage);
}
if (category.equals(BoardCategory.GREETING)) {
loginUser.rankUp(UserRole.SILVER, auth);
}
return savedBoard.getId();
}
@Transactional
public Long editBoard(Long boardId, String category, BoardDto dto) throws IOException {
Optional<Board> optBoard = boardRepository.findById(boardId);
// id에 해당하는 게시글이 없거나 카테고리가 일치하지 않으면 null return
if (optBoard.isEmpty() || !optBoard.get().getCategory().toString().equalsIgnoreCase(category)) {
return null;
}
Board board = optBoard.get();
// 게시글에 이미지가 있었으면 삭제
if (board.getUploadImage() != null) {
uploadImageService.deleteImage(board.getUploadImage());
board.setUploadImage(null);
}
UploadImage uploadImage = uploadImageService.saveImage(dto.getNewImage(), board);
if (uploadImage != null) {
board.setUploadImage(uploadImage);
}
board.update(dto);
return board.getId();
}
public Long deleteBoard(Long boardId, String category) throws IOException {
Optional<Board> optBoard = boardRepository.findById(boardId);
// id에 해당하는 게시글이 없거나 카테고리가 일치하지 않으면 null return
if (optBoard.isEmpty() || !optBoard.get().getCategory().toString().equalsIgnoreCase(category)) {
return null;
}
User boardUser = optBoard.get().getUser();
boardUser.likeChange(boardUser.getReceivedLikeCnt() - optBoard.get().getLikeCnt());
if (optBoard.get().getUser() != null) {
uploadImageService.deleteImage(optBoard.get().getUploadImage());
}
boardRepository.deleteById(boardId);
return boardId;
}
public String getCategory(Long boardId) {
Board board = boardRepository.findById(boardId).get();
return board.getCategory().toString().toLowerCase();
}
public List<Board> findMyBoard(String category, String loginId) {
if (category.equals("board")) {
return boardRepository.findAllByUserLoginId(loginId);
} else if (category.equals("like")) {
List<Like> likes = likeRepository.findAllByUserLoginId(loginId);
List<Board> boards = new ArrayList<>();
for (Like like : likes) {
boards.add(like.getBoard());
}
return boards;
} else if (category.equals("comment")) {
List<Comment> comments = commentRepository.findAllByUserLoginId(loginId);
List<Board> boards = new ArrayList<>();
HashSet<Long> commentIds = new HashSet<>();
for (Comment comment : comments) {
if (!commentIds.contains(comment.getBoard().getId())) {
boards.add(comment.getBoard());
commentIds.add(comment.getBoard().getId());
}
}
return boards;
}
return null;
}
public BoardCntDto getBoardCnt(){
return BoardCntDto.builder()
.totalBoardCnt(boardRepository.count())
.totalNoticeCnt(boardRepository.countAllByUserUserRole(UserRole.ADMIN))
.totalGreetingCnt(boardRepository.countAllByCategoryAndUserUserRoleNot(BoardCategory.GREETING, UserRole.ADMIN))
.totalFreeCnt(boardRepository.countAllByCategoryAndUserUserRoleNot(BoardCategory.FREE, UserRole.ADMIN))
.totalGoldCnt(boardRepository.countAllByCategoryAndUserUserRoleNot(BoardCategory.GOLD, UserRole.ADMIN))
.build();
}
}
BoardController
- /boards/{category} : 카테고리에 해당하는 글 리스트를 return 하는 URL
- sortType으로 정렬 방식을 입력받아 PageRequest 설정을 통해 정렬 구현
- searchType, keyword로 글 제목, 작성자 닉네임에 keyword가 포함되는 글을 return 함으로써 검색 기능 구현
- /boards/{category}/{boardId} : 게시글 상세 조회 페이지
- 만약 로그인 한 유저라면 loginUserLoginId를 전송함으로써, 화면에서 본인이 작성한 글인지 확인 할 수 있게함
- 로그인 한 유저가 이 글에 좋아요를 눌렀는지 여부를 likeCheck에 담아 전송함으로써, 화면에서 하트를 누르면 좋아요를 추가 시킬지, 제거 시킬지 알 수 있게함
- 또한, 글 상세 페이지에서는 해당 글에 추가된 댓글 리스트도 같이 전송 시킴
- /boards/images/{filename} : Resource 타입으로 파일을 전송함으로써, 해당 게시글에 추가된 이미지를 화면에서 출력시킴
- /boards/images/download/{filename} : ResponseEntity<UrlResource> 타입으로 파일을 전송함으로써, 해당 URL에 접속 시 저장된 이미지를 다운로드 하게 함
@Controller
@RequestMapping("/boards")
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
private final LikeService likeService;
private final CommentService commentService;
private final UploadImageService uploadImageService;
@GetMapping("/{category}")
public String boardListPage(@PathVariable String category, Model model,
@RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false) String sortType,
@RequestParam(required = false) String searchType,
@RequestParam(required = false) String keyword) {
BoardCategory boardCategory = BoardCategory.of(category);
if (boardCategory == null) {
model.addAttribute("message", "카테고리가 존재하지 않습니다.");
model.addAttribute("nextUrl", "/");
return "printMessage";
}
model.addAttribute("notices", boardService.getNotice(boardCategory));
PageRequest pageRequest = PageRequest.of(page - 1, 10, Sort.by("id").descending());
if (sortType != null) {
if (sortType.equals("date")) {
pageRequest = PageRequest.of(page - 1, 10, Sort.by("createdAt").descending());
} else if (sortType.equals("like")) {
pageRequest = PageRequest.of(page - 1, 10, Sort.by("likeCnt").descending());
} else if (sortType.equals("comment")) {
pageRequest = PageRequest.of(page - 1, 10, Sort.by("commentCnt").descending());
}
}
model.addAttribute("category", category);
model.addAttribute("boards", boardService.getBoardList(boardCategory, pageRequest, searchType, keyword));
model.addAttribute("boardSearchRequest", new BoardSearchRequest(sortType, searchType, keyword));
return "boards/list";
}
@GetMapping("/{category}/write")
public String boardWritePage(@PathVariable String category, Model model) {
BoardCategory boardCategory = BoardCategory.of(category);
if (boardCategory == null) {
model.addAttribute("message", "카테고리가 존재하지 않습니다.");
model.addAttribute("nextUrl", "/");
return "printMessage";
}
model.addAttribute("category", category);
model.addAttribute("boardCreateRequest", new BoardCreateRequest());
return "boards/write";
}
@PostMapping("/{category}")
public String boardWrite(@PathVariable String category, @ModelAttribute BoardCreateRequest req,
Authentication auth, Model model) throws IOException {
BoardCategory boardCategory = BoardCategory.of(category);
if (boardCategory == null) {
model.addAttribute("message", "카테고리가 존재하지 않습니다.");
model.addAttribute("nextUrl", "/");
return "printMessage";
}
Long savedBoardId = boardService.writeBoard(req, boardCategory, auth.getName(), auth);
if (boardCategory.equals(BoardCategory.GREETING)) {
model.addAttribute("message", "가입인사를 작성하여 SILVER 등급으로 승급했습니다!\n이제 자유게시판에 글을 작성할 수 있습니다!");
} else {
model.addAttribute("message", savedBoardId + "번 글이 등록되었습니다.");
}
model.addAttribute("nextUrl", "/boards/" + category + "/" + savedBoardId);
return "printMessage";
}
@GetMapping("/{category}/{boardId}")
public String boardDetailPage(@PathVariable String category, @PathVariable Long boardId, Model model,
Authentication auth) {
if (auth != null) {
model.addAttribute("loginUserLoginId", auth.getName());
model.addAttribute("likeCheck", likeService.checkLike(auth.getName(), boardId));
}
BoardDto boardDto = boardService.getBoard(boardId, category);
// id에 해당하는 게시글이 없거나 카테고리가 일치하지 않는 경우
if (boardDto == null) {
model.addAttribute("message", "해당 게시글이 존재하지 않습니다");
model.addAttribute("nextUrl", "/boards/" + category);
return "printMessage";
}
model.addAttribute("boardDto", boardDto);
model.addAttribute("category", category);
model.addAttribute("commentCreateRequest", new CommentCreateRequest());
model.addAttribute("commentList", commentService.findAll(boardId));
return "boards/detail";
}
@PostMapping("/{category}/{boardId}/edit")
public String boardEdit(@PathVariable String category, @PathVariable Long boardId,
@ModelAttribute BoardDto dto, Model model) throws IOException {
Long editedBoardId = boardService.editBoard(boardId, category, dto);
if (editedBoardId == null) {
model.addAttribute("message", "해당 게시글이 존재하지 않습니다.");
model.addAttribute("nextUrl", "/boards/" + category);
} else {
model.addAttribute("message", editedBoardId + "번 글이 수정되었습니다.");
model.addAttribute("nextUrl", "/boards/" + category + "/" + boardId);
}
return "printMessage";
}
@GetMapping("/{category}/{boardId}/delete")
public String boardDelete(@PathVariable String category, @PathVariable Long boardId, Model model) throws IOException {
if (category.equals("greeting")) {
model.addAttribute("message", "가입인사는 삭제할 수 없습니다.");
model.addAttribute("nextUrl", "/boards/greeting");
return "printMessage";
}
Long deletedBoardId = boardService.deleteBoard(boardId, category);
// id에 해당하는 게시글이 없거나 카테고리가 일치하지 않으면 에러 메세지 출력
// 게시글이 존재해 삭제했으면 삭제 완료 메세지 출력
model.addAttribute("message", deletedBoardId == null ? "해당 게시글이 존재하지 않습니다" : deletedBoardId + "번 글이 삭제되었습니다.");
model.addAttribute("nextUrl", "/boards/" + category);
return "printMessage";
}
@ResponseBody
@GetMapping("/images/{filename}")
public Resource showImage(@PathVariable String filename) throws MalformedURLException {
return new UrlResource("file:" + uploadImageService.getFullPath(filename));
}
@GetMapping("/images/download/{boardId}")
public ResponseEntity<UrlResource> downloadImage(@PathVariable Long boardId) throws MalformedURLException {
return uploadImageService.downloadImage(boardId);
}
}
Board 관련 DTO
BoardCreateRequest
- Board를 입력받아 DB에 저장할 때 사용하는 DTO
@Data
public class BoardCreateRequest {
private String title;
private String body;
private MultipartFile uploadImage;
public Board toEntity(BoardCategory category, User user) {
return Board.builder()
.user(user)
.category(category)
.title(title)
.body(body)
.likeCnt(0)
.commentCnt(0)
.build();
}
}
BoardSearchRequest
- 게시글 리스트에서 검색할 때 사용하는 DTO
@Data
@AllArgsConstructor
public class BoardSearchRequest {
private String sortType;
private String searchType;
private String keyword;
}
BoardDto
- 게시글 조회, 리스트 조회, 수정 등에 사용되는 DTO
@Data
@Builder
public class BoardDto {
private Long id;
private String userLoginId;
private String userNickname;
private String title;
private String body;
private Integer likeCnt;
private LocalDateTime createdAt;
private LocalDateTime lastModifiedAt;
private MultipartFile newImage;
private UploadImage uploadImage;
public static BoardDto of(Board board) {
return BoardDto.builder()
.id(board.getId())
.userLoginId(board.getUser().getLoginId())
.userNickname(board.getUser().getNickname())
.title(board.getTitle())
.body(board.getBody())
.createdAt(board.getCreatedAt())
.lastModifiedAt(board.getLastModifiedAt())
.likeCnt(board.getLikes().size())
.uploadImage(board.getUploadImage())
.build();
}
}
BoardCntDto
- 홈 화면에서 각각의 카테고리에 해당하는 Board 수를 출력하기 위해 사용되는 DTO
@Data
@Builder
public class BoardCntDto {
private Long totalNoticeCnt;
private Long totalBoardCnt;
private Long totalGreetingCnt;
private Long totalFreeCnt;
private Long totalGoldCnt;
}
반응형
'Spring Boot > 프로젝트' 카테고리의 다른 글
[Spring Boot] 게시판 만들기 6 - 화면 제작 (0) | 2023.04.17 |
---|---|
[Spring Boot] 게시판 만들기 5 - 댓글, 좋아요, 파일 업로드 관련 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 3 - 유저 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 2 - 라이브러리 설치, ERD, Entity 생성 (1) | 2023.04.17 |
[Spring Boot] 게시판 만들기 1 - 설계 & 결과 (2) | 2023.04.16 |