[User_공지사항] 게시글 목록 / 상세보기 / 조회수 / 이전 다음 페이지 이동 (2)

2024. 1. 23. 15:51Project/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 필드를 가지고 생성자를 자동으로 생성

                                                  NoticeRepositoryModelMapper에 대한 생성자가 자동으로 생성

@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})}">&lt;</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>&nbsp;
                                        </li>

                                        <li th:unless="${currentPage == lastPage}">
                                            <a th:href="@{/notice_list(type=${type}, keyword=${keyword}, page=${nextPage})}">&gt;</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>

 

반응형