Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 포함관계
- 자바 멀티스레딩
- 인텔리제이 기초 설정
- While
- 반복문
- JAVA객체지향
- continue문
- JAVA기초
- this예약어
- multi-threading
- 상수
- for문
- 접근제어지시자
- OPP개념
- 컴파일
- function
- 메서드 오버로딩
- Java데이터 타입
- 인텔리제이 한글 깨짐 해결법
- IntelliJ IDEA
- 집합관계
- 연관관계
- 형 변환
- 시스템 환경 변수 편집
- 메서드
- Thread
- Java
- 생성자
- break문
- java변수
Archives
- Today
- Total
최원종의 개발 블로그
V16-3 Board 상세보기 유료/무료 구분 본문


BoardController⬇️
- 유료 게시글일때 구매여부 포함해서 상세보기 조회
- 자바코드에서 논리 연산실행 (머스태치는 연산 불가)
더보기
// 게시글 상세보기 화면 요청
// http://localhost:8080/board/1
@GetMapping("/board/{id}")
public String detailPage(@PathVariable(name = "id") Integer id, Model model, HttpSession session) {
// 게시글 상세보기는 로그인 하지 않은 사용자도 들어올 수 있음
User sessionUser = (User) session.getAttribute("sessionUser");
Integer sessionUserId = sessionUser != null ? sessionUser.getId() : null;
// 추가 로직 - 유료 게시글이라면 구매 여부 포함해서 상세 보기 조회
BoardResponse.DetailDTO detailDTO = boardService.게시글상세조회(id, sessionUserId);
// 유료 게시글 경우 - 본인이 작성할 글이라면 보여 주어야 함
// 소유자 여부
boolean isOwner = detailDTO.checkIsOwner(sessionUserId);
// 머스태치 는 AND/OR 같은 논리 연산을 화면에서 못함. 즉, 자바 코드에서 미리 연산해서
boolean canRead = !detailDTO.getPremium() || detailDTO.getPurchased() || isOwner;
List<ReplyResponse.ListDTO> replyList = replyService.댓글목록조회(id, sessionUserId);
// view 에 데이터 전달 - canRead -> true, false
model.addAttribute("board", detailDTO);
model.addAttribute("canRead", canRead);
model.addAttribute("checkIsOwner", detailDTO.checkIsOwner(sessionUserId));
model.addAttribute("replyList", replyList);
return "board/detail";
}
BoardResponse 추가⬇️
private Boolean premium; // 유료 게시글 여부
private Boolean purchased; // 현재 로그인 사용자의 구매 여부
. . .
public DetailDTO(Board board, Boolean isPurchased) {
this(board);
this.purchased = isPurchased;
}
더보기
package com.tenco.blog.board;
import com.tenco.blog._core.util.MyDateUtil;
import lombok.Data;
import org.springframework.data.domain.Page;
import java.util.ArrayList;
import java.util.List;
/**
* 게시글 응답 DTO
* <p>
* Open Session In View 가 false 일 때
* 트랜잭션이 종료 되는 시점에 LAZY 로딩이 불가능하다.
* Service 단에서 필요한 데이터를 모두 조회 또는 일부로 호출(트리거)해서 응답 DTO 변환해서 반환
* 엔티티를 직접 반환하지 않고(Controller,View) 서비스단에서 DTO 내려줄 예정(결합도 감소)
*/
public class BoardResponse {
@Data
public static class DetailDTO {
private Integer id; // board PK
private String title;
private String content;
private String username;
private Integer userId; // user PK
private boolean isOwner;
private Boolean premium; // 유료 게시글 여부
private Boolean purchased; // 현재 로그인 사용자의 구매 여부
public DetailDTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.premium = board.getPremium();
if(board.getUser() != null) {
this.username = board.getUser().getUsername();
this.userId = board.getUser().getId();
}
}
public DetailDTO(Board board, Boolean isPurchased) {
this(board);
this.purchased = isPurchased;
}
// 소유자 확인
public boolean checkIsOwner(Integer sessionUserId) {
if(sessionUserId == null) {
return false;
}
if (sessionUserId.equals(this.userId)) {
return true;
} else {
return false;
}
}
} // end of DetailDTO inner class
} // end of outer class
BoardService 수정 ⬇️
private final PurchaseService purchaseService;
. . .
// 구매 여부 확인 추가 (로그인 사용자가 있을 때만 의미 있음, 비 로그인 시 null)
boolean purchased = purchaseService.구매여부확인(sessionUserId, id);
return new BoardResponse.DetailDTO(boardEntity, purchased);
더보기
private final PurchaseService purchaseService;
/**
* 게시글 상세 조회
* @param id (Board PK)
* @return DetailDTO 처리 (OSIV 대응)
*/
public BoardResponse.DetailDTO 게시글상세조회(Integer id, Integer sessionUserId) {
Board boardEntity = boardRepository.findByIdJoinUser(id).orElseThrow(() -> {
log.warn("게시글 조회 실패 - ID: {}", id);
return new Exception404("해당하는 게시글을 찾을 수 없습니다");
});
// 구매 여부 확인 추가 (로그인 사용자가 있을 때만 의미 있음, 비 로그인 시 null)
boolean purchased = purchaseService.구매여부확인(sessionUserId, id);
return new BoardResponse.DetailDTO(boardEntity, purchased);
}
/**
* 게시글 상세 화면 요청(인가 처리)
*
* @param id (Board PK)
* @param sessionUser (로그인한 사용자 정보)
* @return BoardResponse.DetailDTO
*/
public BoardResponse.DetailDTO 게시글상세화면및인가처리(Integer id, User sessionUser) {
log.info("게시글 상세 화면 및 인가 확인");
BoardResponse.DetailDTO detailDTO = 게시글상세조회(id, sessionUser.getId());
if(!detailDTO.getUserId().equals(sessionUser.getId())) {
throw new Exception403("권한없음");
}
log.info("게시글 수정 조회 완료 - 제목: {}, 작성자: {}",
detailDTO.getTitle(), detailDTO.getUsername());
return detailDTO;
}
detail.mustache 코드
더보기
{{> layout/header}}
<div class="container p-5">
<!-- 수정삭제버튼 섹션 -->
{{#checkIsOwner}}
<div class="d-flex justify-content-end">
<a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{board.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/checkIsOwner}}
<!-- 작성자 정보 표시 -->
<div class="d-flex justify-content-end">
<b>작성자</b> : {{board.username}}
</div>
<!-- 게시글 내용 섹션 -->
<div>
<h2><b>{{board.title}}</b> {{#board.premium}} <span class="badge bg-warning"> 유료 </span> {{/board.premium}} </h2>
<hr />
<div class="m-4 p-2">
{{#canRead}}
{{board.content}}
{{/canRead}}
{{^canRead}}
<!-- 로그인 시 - 이미 구매했다면 보여야 함 -->
{{#sessionUser}}
<form action="/board/{{board.id}}/purchase" method="post"
onsubmit="return confirm('구매하시겠습니까?');">
<button type="submit" class="btn btn-primary">구매하기(500P)</button>
</form>
{{/sessionUser}}
{{^sessionUser}}
<a href="/login-form" class="btn btn-outline-primary">로그인 후 구매가능</a>
{{/sessionUser}}
<!-- 비 로그인 시 - 로그인 요청 -->
{{/canRead}}
</div>
</div>
<!-- 댓글 섹션 시작 -->
<div class="card mt-3">
{{^sessionUser}}
<div class="card-body">
<p class="text-muted text-center p-2">댓글을 작성하려면 <a href="/login-form">로그인</a>이 필요합니다</p>
</div>
{{/sessionUser}}
{{#sessionUser}}
<div class="card-body">
<form action="/reply/save" method="post">
<input type="hidden" name="boardId" value="{{board.id}}" />
<textarea name="comment" class="form-control"></textarea>
<div class="d-flex justify-content-end mt-2">
<button type="submit" class="btn btn-outline-primary">댓글 등록</button>
</div>
</form>
</div>
{{/sessionUser}}
{{^replyList}}
<p class="text-muted text-center p-2">현재 작성된 댓글이 없습니다</p>
{{/replyList}}
{{#replyList}}
<div class="list-group">
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-1" >
<div class="px-2 py-1 bg-primary text-white rounded"> {{username}} </div>
<small class="text-muted">{{createdAt}}</small>
</div>
<div>{{comment}}</div>
</div>
{{#isOwner}}
<form action="/reply/{{id}}/delete" method="post">
<input type="hidden" name="boardId" value="{{board.id}}" >
<button type="submit" class="btn btn-danger btn-sm">🗑️ 삭제</button>
</form>
{{/isOwner}}
</div>
</div>
{{/replyList}}
</div>
<!-- 댓글 섹션 종료 -->
</div>
{{> layout/footer}}
update-form.mustache ( 주석 해제)
더보기
<div class="mb-3">
<!-- board.premium 컬럼 추가시 주석 해제 -->
<input type="checkbox" id="premium" name="premium" class="form-check-input"
{{#board.premium}} checked {{/board.premium}}>
<label for="premium" class="form-check-label">유료 게시글 등록(열람시 500포인트 차감 됨)</label>
</div>
'Spring boot 입문 > Spring boot 입문 -1' 카테고리의 다른 글
| V17 PG사 연결 - 포트원 연동하기 (0) | 2026.06.25 |
|---|---|
| V16-4 유료 게시글 구매하기 (0) | 2026.06.25 |
| V16-2 포인트 시스템의 개념과 유료 게시글 (0) | 2026.06.25 |
| V16-1 포인트 시스템(유료 게시글 기능 추가) (0) | 2026.06.23 |
| V15-4 이메일 인증 후 회원가입 로직 변경 (0) | 2026.06.23 |