2024. 1. 23. 15:51ㆍProject/ShoppingMall
🟢 NoticeRepository
▪️ 조회수 증가
noticeView 메소드는 noticeId를 식별자로 사용하여 NoticeEntity 인스턴스의 noticeView 필드를 업데이트
noticeView 메소드 호출 시 noticeId 값을 전달
@Repository
public interface NoticeRepository extends JpaRepository<NoticeEntity, Integer> {
//조회수 증가
@Modifying
@Query("UPDATE NoticeEntity n SET n.noticeView = n.noticeView+1 WHERE n.noticeId = :noticeId")
void noticeView (@Param("noticeId")Integer noticeId);
}
▪️ 이전 다음 페이지로 이동
//이전 페이지 (현재 페이지보다 작은 noticeId)
//NoticeEntity의 noticeId 속성 중 최대값
@Query("SELECT MAX(n.noticeId) FROM NoticeEntity n WHERE n.noticeId < :currentNoticeId")
Integer findPreviousNoticeId(@Param("currentNoticeId") Integer currentNoticeId);
//다음 페이지 (현재 페이지보다 큰 noticeId)
//NoticeEntity의 noticeId 속성 중 최소값
//COALESCE 함수는 MIN 함수의 결과가 NULL일 경우에 대비하여 -1을 반환
@Query("SELECT COALESCE(MIN(n.noticeId), -1) FROM NoticeEntity n WHERE n.noticeId > :currentNoticeId")
Integer findNextNoticeId(@Param("currentNoticeId") Integer currentNoticeId);
🟢 Notice Service
▪️ 공지사항 목록 (검색+페이징처리) / 상세보기 + 조회수
@RequiredArgsConstructor: 롬복(Lombok) 어노테이션
클래스에 선언된 final 필드를 가지고 생성자를 자동으로 생성
NoticeRepository와 ModelMapper에 대한 생성자가 자동으로 생성
@Service
@RequiredArgsConstructor
@Transactional
public class NoticeService {
private final NoticeRepository noticeRepository;
private final ModelMapper modelMapper = new ModelMapper();
//공지목록 (회원)
public Page<NoticeDTO> userNoticeList(String type, String keyword, String noticeRole, Pageable page) throws Exception {
//페이지네이션 설정 (현재 페이지 번호, 페이지당 아이템 수)
int currentpage = page.getPageNumber()-1;
int blockLimit = 10;
//Pageable 객체 생성
//noticeId를 기준으로 내림차순으로 정렬
Pageable pageable = PageRequest.of(currentpage, blockLimit, Sort.by(Sort.Direction.DESC, "noticeId"));
//검색 조건에 따른 공지사항 목록 조회
Page<NoticeEntity> noticeEntityPage;
if("tc".equals(type) && keyword != null && !keyword.isEmpty()) {
noticeEntityPage = noticeRepository.findByNoticeTitleOrNoticeContent(keyword, pageable);
} else if("r".equals(type) && noticeRole != null && !noticeRole.isEmpty()) {
noticeEntityPage = noticeRepository.findByNoticeRole(NoticeRole.valueOf(noticeRole), pageable);
} else {
noticeEntityPage = noticeRepository.findAll(pageable);
}
//Entity를 DTO로 변환 및 페이징된 결과 반환
//검색된 공지사항 목록(noticeEntityPage)을 DTO로 변환
List<NoticeDTO> noticeDTOS = noticeEntityPage.stream()
.map(noticeEntity -> modelMapper.map(noticeEntity, NoticeDTO.class))
.collect(Collectors.toList());
//PageImpl을 사용하여 페이징된 목록을 생성
return new PageImpl<>(noticeDTOS, pageable, noticeEntityPage.getTotalElements());
}
//공지상세 (회원)
public NoticeDTO userNoticeDetail(Integer noticeId) throws Exception {
Optional<NoticeEntity> noticeEntity = noticeRepository.findById(noticeId);
NoticeDTO noticeDTO = modelMapper.map(noticeEntity, NoticeDTO.class);
return noticeDTO;
}
}
▪️ 조회 수 증가
특별한 반환값이 없으며, 조회수를 증가시키기 위한 부수적인 작업을 수행 → void
//조회수 증가
public void noticeView(Integer noticeId) throws Exception {
noticeRepository.noticeView(noticeId);
}
▪️ 공지사항 이전 글, 다음 글로 이동
이전과 다음 게시글의 ID를 찾아 반환 → Integer
//이전버튼
public Integer findPreviousNoticeId(Integer currentNoticeId) {
return noticeRepository.findPreviousNoticeId(currentNoticeId);
}
//다음버튼
public Integer findNextNoticeId(Integer currentNoticeId) {
return noticeRepository.findNextNoticeId(currentNoticeId);
}
🟢 Notice Controller
▪️ 공지사항 목록 + 상세페이지(댓글 목록 포함)
@RequestParam 어노테이션: 웹 요청 파라미터를 컨트롤러 메서드의 매개변수에 바인딩
URL에서 type, keyword, noticeRole 파라미터의 값을 가져옴
defaultValue : 이러한 파라미터가 요청에 포함되어 있지 않은 경우에 기본값을 제공
Model 객체 : Spring MVC에서 컨트롤러에서 뷰로 데이터를 전달하는 데 사용
model.addAttribute 메서드 : 모델에 속성을 추가하는 데 사용
@Controller
@Transactional
@RequiredArgsConstructor
public class NoticeController {
private final NoticeService noticeService;
private final CommentService commentService;
//회원
//공지목록
@GetMapping("/notice_list")
public String usernoticeList(@RequestParam(value = "type", defaultValue = "") String type,
@RequestParam(value = "keyword", defaultValue = "") String keyword,
@RequestParam(value = "noticeRole", defaultValue = "") String noticeRole,
@PageableDefault(page = 1) Pageable pageable,
Model model) throws Exception {
Page<NoticeDTO> noticeDTOS = noticeService.userNoticeList(type, keyword, noticeRole, pageable);
int blockLimit = 5;
int startPage = (((int)(Math.ceil((double) pageable.getPageNumber() / blockLimit))) - 1) * blockLimit + 1;
int endPage = Math.min(startPage + blockLimit - 1, noticeDTOS.getTotalPages());
int prevPage = noticeDTOS.getNumber();
int currentPage = noticeDTOS.getNumber() + 1;
int nextPage = noticeDTOS.getNumber() + 2;
int lastPage = noticeDTOS.getTotalPages();
model.addAttribute("noticeDTOS", noticeDTOS);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
model.addAttribute("prevPage", prevPage);
model.addAttribute("currentPage", currentPage);
model.addAttribute("nextPage", nextPage);
model.addAttribute("lastPage", lastPage);
model.addAttribute("type", type);
model.addAttribute("keyword", keyword);
return "/notice/notice_list";
}
//공지상세
@GetMapping("/notice_detail")
public String usernoticedetail(Integer noticeId, Model model) throws Exception {
//게시글 불러오기
NoticeDTO noticeDTO = noticeService.userNoticeDetail(noticeId);
//댓글 불러오기
List<CommentDTO> commentDTOS = commentService.list(noticeId);
//이전, 다음 게시글 이동
Integer prevNoticeId = noticeService.findPreviousNoticeId(noticeId);
Integer nextNoticeId = noticeService.findNextNoticeId(noticeId);
noticeService.noticeView(noticeId);
model.addAttribute("noticeDTO", noticeDTO);
model.addAttribute("commentDTOS", commentDTOS);
model.addAttribute("prevNoticeId",prevNoticeId);
model.addAttribute("nextNoticeId",nextNoticeId);
return "/notice/notice_detail";
}
}
Model 객체는 Spring MVC에서 컨트롤러에서 뷰로 데이터를 전달하는 데 사용
🟢 Notice List
▪️ 공지사항 목록 + 다중검색 + 페이지네이션
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout}">
<head>
<meta charset="UTF-8">
<title>Notice List</title>
</head>
<body>
<div layout:fragment="content">
<div class="main-wrapper wrapper-2">
<div class="breadcrumb-area breadcrumb-padding-6">
<div class="container">
<div class="breadcrumb-content text-center">
<div class="breadcrumb-title" data-aos="fade-up" data-aos-delay="200">
<h2>Notice</h2>
</div>
<ul data-aos="fade-up" data-aos-delay="300">
<li>
<a href="/notice_list">Notice</a>
</li>
<li>
<a href="/qnalist">Q&A </a>
</li>
</ul>
</div>
</div>
</div>
<div class="wishlist-area bg-white pb-130">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-5"></div>
<div class="col-lg-7 ">
<form th:action="@{/notice_list}" method="get" id="searchForm">
<input type="hidden" name="page" value="1">
<div class="input-group mb-3 mt-5">
<!-- 대분류 셀렉트 리스트 -->
<select class="form-select" name="type" id="searchType">
<option value="" th:selected="${type == ''}">== 선택 ==</option>
<option value="tc" th:selected="${type == 'tc'}">제목+내용</option>
<option value="r" th:selected="${type == 'r'}">공지유형</option>
</select>
<!-- 검색창 -->
<input type="text" class="form-control" name="keyword" id="keyword" th:value="${keyword}" style="display: none;"></input>
<!-- 중분류(공지유형) 셀렉트 리스트 -->
<select class="form-select" name="noticeRole" id="noticeRole" style="display: none;">
<option value="" th:selected="${noticeRole == ''}">== 선택 ==</option>
<option value="DELIVERY" th:selected="${noticeRole == 'DELIVERY'}">배송공지</option>
<option value="PRODUCT" th:selected="${noticeRole == 'LIVING'}">상품공지</option>
<option value="TIP" th:selected="${noticeRole == 'TIP'}">청소꿀팁</option>
<option value="EVENT" th:selected="${noticeRole == 'EVENT'}">이벤트</option>
<option value="POINT" th:selected="${noticeRole == 'POINT'}">적립금</option>
<option value="ETC" th:selected="${noticeRole == 'ETC'}">기타</option>
</select>
<!-- 검색 버튼 -->
<button type="submit" class="btn btn-primary admin-dart" name="searchButton">검색</button>
<!-- 리셋 버튼 -->
<button type="button" class="btn btn-light admin-light " name="searchButton" th:onclick="|location.href='@{/notice_list}'|">다시</button>
</div>
</form>
<!-- JavaScript 코드 -->
<script>
document.getElementById('searchType').addEventListener('change', function () {
resetSearchForm();
});
function resetSearchForm() {
var selectedSearchType = document.getElementById('searchType').value;
var selectedNoticeRole = document.getElementById('noticeRole').value;
applyStylesAfterSearch(selectedSearchType, selectedNoticeRole);
toggleElementDisplay('keyword', selectedSearchType === 'tc');
}
function applyStylesAfterSearch(searchType, noticeRole) {
toggleElementDisplay('noticeRole', searchType === 'r');
// 검색 타입이 제목+내용일 때 keyword 입력 필드를 보이게 설정
toggleElementDisplay('keyword', searchType === 'tc');
if (searchType === 'r') {
var noticeRoleElement = document.getElementById('noticeRole');
if (noticeRoleElement.value === '') {
noticeRoleElement.value = localStorage.getItem('selectedNoticeRole') || noticeRole;
}
}
}
function toggleElementDisplay(elementId, condition) {
var element = document.getElementById(elementId);
element.style.display = condition ? 'block' : 'none';
}
// 페이지 로드 시 초기 스타일 적용
applyStylesAfterSearch(
document.getElementById('searchType').value,
document.getElementById('noticeRole').value
);
// 페이지 로드 시 localStorage에서 이전 선택 값을 가져와 적용
window.onload = function () {
applyStylesAfterSearch(
document.getElementById('searchType').value,
document.getElementById('noticeRole').value,
);
}
// 페이지 언로드 시 localStorage에 현재 선택 값을 저장
window.onbeforeunload = function () {
localStorage.setItem('selectedNoticeRole', document.getElementById('noticeRole').value);
}
</script>
</div>
</div>
</div>
<div class="row mb-20"></div>
<div class="container">
<div class="row">
<div class="col-12">
<div class="wishlist-table-content">
<div class="table-content">
<table>
<thead>
<tr>
<th class="width-price"> 번호 </th>
<th class="width-price"> 유형 </th>
<th class="width-price"> 제목 </th>
<th class="width-price"> 작성자 </th>
<th class="width-price"> 작성날짜 </th>
<th class="width-price"> 조회수 </th>
</tr>
</thead>
<tbody>
<tr th:each="data:${noticeDTOS}">
<td class="product-name">
<h5 th:text="${data.noticeId}"></h5>
</td>
<td th:text="${data.noticeRole.description}"></td>
<td>
<a th:href="@{/notice_detail(noticeId=${data.noticeId})}" th:text="${data.noticeTitle}"></a>
</td>
<td class="product-name">
<h5 th:text="${data.noticeWriter}"></h5>
</td>
<td class="product-name">
<h5 th:text="${#temporals.format(data.reDate, 'yyyy-MM-dd')}"></h5>
</td>
<td class="product-name">
<h5 th:text="${data.noticeView}"></h5>
</td>
</tr>
</tbody>
</table>
<!-- 페이지 번호 추가 -->
<div class="pagination-style text-center mt-30" th:if="${lastPage > 1}">
<ul>
<li th:unless="${startPage == 1}">
<a th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=1)}">처음</a>
</li>
<li th:unless="${currentPage == 1}">
<a th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=${prevPage})}"><</a>
</li>
<li th:each="page: ${#numbers.sequence(startPage, endPage)}">
<a th:if="${currentPage != page}" th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=${page})}">[[${page}]]</a>
<a th:if="${currentPage == page}" href="#" class="active">[[${page}]]</a>
</li>
<li th:unless="${currentPage == lastPage}">
<a th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=${nextPage})}">></a>
</li>
<li th:unless="${endPage == lastPage}">
<a th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=${lastPage})}">끝</a>
</li>
</ul>
</div>
<!-- 페이지 번호 추가 끝 -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main JS -->
<script src="assets/js/main.js"></script>
</div>
</body>
</html>
🟢 Notice Detail
▪️ 공지사항 상세보기 + 이전/다음 글로 이동 + 조회수 증가 + 댓글 기능
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout}">
<head>
<meta charset="UTF-8">
<title>Notice Detail</title>
</head>
<body>
<div layout:fragment="content">
<div class="main-wrapper wrapper-2">
<div class="breadcrumb-area breadcrumb-padding-6">
<div class="container">
<div class="breadcrumb-content text-center">
<div class="breadcrumb-title" data-aos="fade-up" data-aos-delay="200">
<h2>Notice</h2>
</div>
<ul data-aos="fade-up" data-aos-delay="300">
<li>
<a href="/notice_list">Notice</a>
</li>
<li>
<a href="/qnalist">Q&A </a>
</li>
</ul>
</div>
</div>
</div>
<div class="notice-area bg-white pb-130">
<div class="container">
<div class="notice-info-wrap">
<table class="table notice-table">
<tbody>
<input type="hidden" name="noticeView" th:value="${noticeDTO.noticeView}">
<tr>
<th scope="row" class="notice-padding">제목</th>
<td id="noticeTitle" name="noticeTitle" th:text="${noticeDTO.noticeTitle}"></td>
</tr>
<tr>
<th scope="row" class="notice-padding">작성자</th>
<td class="notice-info">
<input type="text" id="noticeWriter" name="noticeWriter" th:value="${noticeDTO.noticeWriter}" readonly>
</td>
</tr>
<tr>
<th scope="row" class="notice-padding">공지유형</th>
<td class="notice-info">
<input type="text" id="noticeRole" name="noticeRole" th:value="${noticeDTO.noticeRole.description}" readonly>
</td>
</tr>
<tr>
<th scope="row" class="notice-padding">공지내용</th>
<td class="notice-info">
<textarea rows="15" th:text="${noticeDTO.noticeContent}" name="noticeContent" readonly></textarea>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-lg-9"></div>
<div class="col-lg-3">
<button type="button" class="btn btn-primary admin-primary " name="searchButton"
th:onclick="|location.href='@{/notice_list}'|">목록</button>
<button type="button" class="btn btn-primary admin-primary " name="searchButton"
th:if="${prevNoticeId != null}" th:onclick="|location.href='@{/notice_detail(noticeId=${prevNoticeId})}'|">이전</button>
<button type="button" class="btn btn-primary admin-primary " name="searchButton"
th:if="${nextNoticeId != null && nextNoticeId != -1}" th:onclick="|location.href='@{/notice_detail(noticeId=${nextNoticeId})}'|">다음</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
'Project > ShoppingMall' 카테고리의 다른 글
[Admin_공지사항] 다중 이미지 삽입 / 이미지 테이블 조인 / 공지사항 등록 / 공지사항 목록 (4) (1) | 2024.01.26 |
---|---|
[User_공지사항] 댓글 등록, 목록, 수정 / 댓글 좋아요 싫어요 (3) (0) | 2024.01.25 |
[Admin_공지사항] enum 공지사항 유형 / 조회수 증가 / 다중 검색 기능(1) (0) | 2024.01.20 |
[User_상품진열] enum 카테고리별로 진열 / 상품 상세페이지 / 할인율 계산 / 자동합산 / 연관상품 (0) | 2024.01.20 |
[Admin_상품등록] 다중 이미지 삽입 / 이미지-상품 테이블 조인 / enum 카테고리, 판매 상태(2) (0) | 2024.01.19 |