이전에 게시글 등록을 할 때는 작성자를 고려하지 않고 기능을 구현했다.
회원가입/로그인 기능까지 완성됐으니 게시글 등록시에도 작성자를 넣어주도록 하겠다.
/entity/Board.java와 /entity/Reply.java에 작성자 변수(속성) 추가
// 사용자가 여러 개의 게시글을 작성할 수 있다. 다대일 설정(Member가 부모, Board가 자식)
@ManyToOne
private Member writer;
- DB 확인
- WRITER_MID가 생성된 것을 확인할 수 있다.
/controller/ReplyController.java 코드 수정
package com.eunji.backboard.controller;
import java.security.Principal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import com.eunji.backboard.entity.Board;
import com.eunji.backboard.service.BoardService;
import com.eunji.backboard.service.ReplyService;
import com.eunji.backboard.validation.ReplyForm;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@RequestMapping("/reply")
@Controller
@RequiredArgsConstructor
@Log4j2
public class ReplyController {
private final ReplyService replyService;
private final BoardService boardService;
// Principal 객체 추가하면 로그인한 사용자명(Member 객체)을 알 수 있음(Member 객체를 조회할 수 있다)
@PostMapping("/create/{bno}")
public String create(Model model, @PathVariable("bno") Long bno,
@Valid ReplyForm replyForm, BindingResult bindingResult,
Principal principal) throws Exception {
Board board = this.boardService.getBoard(bno); // 게시글 정보 가져오기
if(bindingResult.hasErrors()) {
model.addAttribute("board", board);
return "board/detail";
}
this.replyService.setReply(board, replyForm.getContent());
log.info("[ReplyController] 댓글 저장 처리 완료");
return String.format("redirect:/board/detail/%s", bno);
}
}
- Principal을 파라미터에 추가
/service/MemberService.java에 getMember() 메서드 추가
public Member getMember(String username) {
Optional<Member> member = this.memberRepository.findByUsername(username);
if(member.isPresent())
return member.get();
else
throw new Exception("");
}
- 💥 throw new Exception()으로 예외처리를 하면 메서드 뒤에 항상 throws Exception을 적어줘야 되는 번거로음 발생
따라서 /common/NotFoundException.java를 생성해서 throws Exception 쓰는 데 반영해줄 예정이다
/common/NotFoundException.java 생성
package com.eunji.backboard.common;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "entity not found")
public class NotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public NotFoundException(String message) {
super(message); // RuntimeException에서 처리!
}
}
/service/MemberService.java에 getMember() 메서드 throw 부분 변경
public Member getMember(String username) {
Optional<Member> member = this.memberRepository.findByUsername(username);
if(member.isPresent())
return member.get();
else
throw new NotFoundException("Member not found!");
}
/service/ReplyService.java 코드 변경
public class ReplyService {
private final ReplyRepository replyRepository;
public void setReply(Board board, String content, Member writer) {
// builder를 사용한 방식
Reply reply = Reply.builder().content(content).createDate(LocalDateTime.now()).board(board).build();
log.info("댓글 객체 생성");
reply.setWriter(writer);
this.replyRepository.save(reply);
log.info("댓글 객체 저장");
}
}
- setReply() 파라미터 값으로 Member writer 추가
- reply.setWriter(writer) 코드 추가 - 작성자 넣어주는 것
/controller/ReplyController.java 코드 변경
package com.eunji.backboard.controller;
import java.security.Principal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import com.eunji.backboard.entity.Board;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.service.BoardService;
import com.eunji.backboard.service.MemberService;
import com.eunji.backboard.service.ReplyService;
import com.eunji.backboard.validation.ReplyForm;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@RequestMapping("/reply")
@Controller
@RequiredArgsConstructor
@Log4j2
public class ReplyController {
private final ReplyService replyService;
private final BoardService boardService;
private final MemberService memberService; // 작성자 입력을 위해 추가
// Principal 객체 추가하면 로그인한 사용자명(Member 객체)을 알 수 있음(Member 객체를 조회할 수 있다)
@PostMapping("/create/{bno}")
public String create(Model model, @PathVariable("bno") Long bno,
@Valid ReplyForm replyForm, BindingResult bindingResult,
Principal principal) throws Exception {
Board board = this.boardService.getBoard(bno); // 게시글 정보 가져오기
Member writer = this.memberService.getMember(principal.getName()); // principal.getName() -> 지금 로그인 중인 사람의 ID값
if(bindingResult.hasErrors()) {
model.addAttribute("board", board);
return "board/detail";
}
this.replyService.setReply(board, replyForm.getContent(), writer);
log.info("[ReplyController] 댓글 저장 처리 완료");
return String.format("redirect:/board/detail/%s", bno);
}
}
- private final MemberService memberService 추가
- 작성자 입력을 위해 추가
- Member writer = this.memberService.getMember(principal.getName());
- principal.getName(); -> 현재 로그인한 사람의 ID를 가져올 수 있다.
- this.replySerivce.setReply(board, replyForm.getContent(), writer);
- setReply() 파라미터에 writer 추가
Board에도 똑같이 적용
/service/BoardService.java setBoard() 코드 수정
public void setBoard(String title, String content, Member writer){
// 빌더로 생성한 객체
Board board = Board.builder().title(title).content(content)
.createDate(LocalDateTime.now()).build();
board.setWriter(writer);
// 객체 저장
this.boardRepository.save(board);
}
- setBoard() 메서드 파라미터 값으로 Member writer 추가
- board.setWriter(writer)을 통해 값을 넣어줌
/controller/BoardController.java create() 코드 수정
@PostMapping("/create")
public String create(@Valid BoardForm boardForm,
BindingResult bindingResult,
Principal principal) {
if(bindingResult.hasErrors()) {
return "board/create"; // 현재 html에 그대로 머무르기
}
Member writer = this.memberService.getMember(principal.getName()); // 현재 로그인 사용자 아이디
// this.boardService.setBoard(title, content);
this.boardService.setBoard(boardForm.getTitle(), boardForm.getContent(), writer);
return "redirect:/board/list";
}
- private final MemberService memberService;
- create() 메서드 파라미터값에 Principal 추가
- Member writer = this.memberService.getMember(principal.getName());를 통해
- 현재 로그인한 사용자 아이디를 들고 올 수 있다.
- 이후 this.boardService.setBoard() 파라미터 값에 writer 추가
- 로그인 없이 게시글을 등록하려고 하면 pricipal is null 관련 오류 발생
- 문제를 해결하기 위해 BoardController.java와 ReplyController.java에 @PreAuthorize 추가
/controller/BoardController.java create() get, post 메서드와 /controller/BoardController.java create() 코드 수정
- @PreAuthorize(isAuthenticated()) 어노테이션 추가
- BoardController.java
@PreAuthorize("isAuthenticated()") // 로그인 시만 작성 가능
@GetMapping("/create")
public String create(BoardForm boardForm){
return "board/create";
}
@PreAuthorize("isAuthenticated()") // 로그인 시만 작성 가능
@PostMapping("/create")
public String create(@Valid BoardForm boardForm,
BindingResult bindingResult,
Principal principal) {
if(bindingResult.hasErrors()) {
return "board/create"; // 현재 html에 그대로 머무르기
}
Member writer = this.memberService.getMember(principal.getName());
// this.boardService.setBoard(title, content);
this.boardService.setBoard(boardForm.getTitle(), boardForm.getContent(), writer);
return "redirect:/board/list";
}
- ReplyController.java
@PreAuthorize("isAuthenticated()") // 로그인시만 작성가능
@PostMapping("/create/{bno}")
public String create(Model model, @PathVariable("bno") Long bno,
@Valid ReplyForm replyForm, BindingResult bindingResult,
Principal principal) throws Exception {
Board board = this.boardService.getBoard(bno); // 게시글 정보 가져오기
Member writer = this.memberService.getMember(principal.getName()); // principal.getName() -> 지금 로그인 중인 사람의 ID값
if(bindingResult.hasErrors()) {
model.addAttribute("board", board);
return "board/detail";
}
this.replyService.setReply(board, replyForm.getContent(), writer);
log.info("[ReplyController] 댓글 저장 처리 완료");
return String.format("redirect:/board/detail/%s", bno);
}
/security/SecurityConfig.java에 @PreAuthorize 동작하도록 설정 추가
@EnableMethodSecurity(prePostEnabled = true) // @PreAuthorize 사용설정
/templates/board/detail.html 답변 textarea 로그인 전 로그인 후로 구분
<textarea sec:authorize="isAnonymous()" disabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
<textarea sec:authorize="isAuthenticated()" enabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
/templates/board/list.html에 작성자 추가
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container my-3">
<!-- 등록 버튼 -->
<div class="d-flex justify-content-end">
<a th:href="@{/board/create}" class="btn btn-sm btn-primary my-2">게시글 등록</a>
</div>
<!-- 게시글 리스트 -->
<table class="table table-light table-striped">
<thead class="table-dark">
<tr class="text-center">
<th>번호</th>
<th style="width: 60%">제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<!-- <tr th:each="board, loop: ${boardList}"> -->
<tr th:each="board, loop: ${paging}" class="text-center">
<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>
<!-- <td th:text="${loop.count}"></td> -->
<td class="text-start">
<a th:href="@{|/board/detail/${board.bno}|}" th:text="${board.title}"></a>
<span th:if="${#lists.size(board.replyList) > 0}" th:text="${#lists.size(board.replyList)}"
class="badge text-bg-success">Success</span>
</td>
<!-- 작성자 추가 -->
<td>
<span th:if="${board.writer != null}" th:text="${board.writer.username}"></span>
</td>
<td th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
</table>
<!-- 페이징 시작 -->
<div th:if="${!paging.isEmpty()}">
<ul class="pagination justify-content-center">
<!-- 이전버튼 -->
<li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
<a th:href="@{|?page=0|}" class="page-link">《</a>
</li>
<li th:classappend="${!paging.hasPrevious} ? disabled" class="page-item">
<a th:href="@{|?page=${paging.number-1}|}" class="page-link">〈</a>
</li>
<!-- 페이지번호버튼 -->
<li th:each="page : ${#numbers.sequence(0, paging.totalPages-1)}"
th:if="${page >= paging.number-5 and page <= paging.number+5}"
th:classappend="${page == paging.number} ? active" class="page-item">
<a th:href="@{|?page=${page}|}" th:text="${page + 1}" class="page-link"></a>
</li>
<!-- 다음버튼 -->
<li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
<a th:href="@{|?page=${paging.number+1}|}" class="page-link">〉</a>
</li>
<li th:classappend="${!paging.hasNext} ? disabled" class="page-item">
<a th:href="@{|?page=${paging.totalPages-1}|}" class="page-link">》</a>
</li>
</ul>
</div>
<!-- 페이징 끝 -->
</div>
</html>
- 작성자도 함께 뜨는 것을 확인할 수 있다
/templates/board/detail.html 게시글 작성자, 댓글 작성자 표시 추가
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container my-3">
<!-- 게시글 영역 -->
<h2 th:text="${board.title}" class="border-bottom py-2"></h2>
<div class="card text-bg-light mb-3">
<div class="card-body">
<div th:text="${board.content}" class="card-text"></div>
<div class="d-flex justify-content-end">
<!-- 작성자와 작성일을 표시하는 뱃지 -->
<div class="badge text-bg-secondary p-2">
<div class="mb-2">
<span th:if="${board.writer != null}"
th:text="${board.writer.username}"></span>
</div>
<div th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
<!-- 댓글 리스트 영역 -->
<h5 th:text="|${#lists.size(board.replyList)}개의 댓글|" class=" border-bottom my-3 py-2"></h5>
<div th:each="reply : ${board.replyList}" class="card border-info shadow-sm my-3">
<div class="card-body">
<div th:text="${reply.content}" class="card-text"></div>
<div class="d-flex justify-content-end">
<!-- 작성자와 작성일을 표시하는 뱃지 -->
<div class="badge text-bg-light p-2">
<div class="mb-2">
<span th:if="${reply.writer != null}"
th:text="${reply.writer.username}"></span>
</div>
<div th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
<!-- 답변기능 영역 -->
<form th:action="@{|/reply/create/${board.bno}|}" th:object="${replyForm}" method="post" class="my-3">
<div th:replace="~{errors :: formErrorFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
<textarea sec:authorize="isAuthenticated()" enabled name="content" th:field="*{content}" rows="10" class="form-control"></textarea>
<div class="d-flex justify-content-end mt-2">
<input type="submit" value="댓글등록" class="btn btn-sm btn-primary mx-1">
<a href="/board/list" class="btn btn-sm btn-secondary">목록</a>
</div>
</form>
</div>
</html>
- 게시글 작성자와 댓글 작성자가 나타나는 것을 확인할 수 있다
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] JPA 프로젝트 - 앵커 기능 구현 (0) | 2024.06.24 |
---|---|
[Spring Boot] JPA 프로젝트 - 수정 기능 구현 (0) | 2024.06.24 |
[Spring Boot] JPA 프로젝트 - 로그인, 로그아웃 기능 구현 (1) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - 회원가입 기능 구현 (1) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - Spring Security (0) | 2024.06.20 |