이제 기능을 구현할 차례입니다.
첫 메인페이지에는 질문 리스트와 페이징처리를 할것입니다.
질문리스트를 구현하기에 앞서 testclass를 통해 질문을 여러개 저장하려고합니다.
@Test
void questionCreateTest(){
Random random = new Random(System.nanoTime());
for (int i=0; i<200; i++){
String subject=String.format((int)(random.nextInt(10000))+"번 문제 해결 팁 좀 알려주세요");
String content=String.format((int)(random.nextInt(10000))+"코드 로직에 무슨 문제가 있는지 확인해주세요,,,문제를 잘모르겠어요.나온예시는 일단 다 맞는데..틀렸다고 나와서 고민이 커졌어요ㅠㅠ");
this.questionService.createQuestion(subject,content);
}
}
random.nextInt()함수를 사용하면 0에서 1사이의 소수가 랜덤으로 생성됩니다.
(int)random.nextInt(10000)을 통해 0에서 10000사이의 정수를 생성하고
String.format을 사용하여 문자열으로 변환시켜서 subject객체에 넣어줍니다.
총 200개의 게시물을 생성해줍니다.
페이징 처리를 위해 QustionRepository에 메소드를 추가해줍니다.
Pageable한 객체를 모두찾아 Page<Question>을 반환해줍니다.
package com.example.quenswer.question;
import org.springframework.data.domain.Page; #추가
import org.springframework.data.domain.Pageable; #추가
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface QuestionRepository extends JpaRepository<Question,Integer> {
List<Question> findBySubjectLike(String subject);
Page<Question> findAll(Pageable pageable); #추가
}
이제 서비스클래스를 만들어줍니다.
@Service애너테이션으로 서비스객체를 스프링에 등록합니다.
페이지를 반환하기 위해 질문리스트를 최신순으로 내림차순 정렬합니다.
이후 요청한 페이지를 PageRequest.of(요청 페이지번호,한페이지에 몇개의 질문을 보여줄건지,정렬방법)를 통해 pageable객체에 담습니다.
@Service
@RequiredArgsConstructor
public class QuestionService {
private final QuestionRepository questionRepository;
public Page<Question> getList(int page){
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("createDate"));
Pageable pageable = PageRequest.of(page, 6,Sort.by(sorts));
return this.questionRepository.findAll(pageable); //pageable객체가 Page<Question>타입임
}
}
Controller를 생성해줍니다.
@Controller애너테이션을 통해 Controller로 등록합니다.
private final로 service클래스를 선언하고,final이 붙은 속성을 포함하는 생성자를 자동으로 생성해주는 @RequiredArgsConstructor애너테이션을 추가합니다.
그리고 QuestionnController에서 사용하는 모든 url은 "/question"이 앞에 붙으므로 @RequestMapping("/question")애너테이션을 추가해줍니다.
redirect:<URL>:리다이렉트는 완전히 새로운 URL로 요청이 됩니다.
forward:<URL>:포워드는 기존 요청 값들이 유지된 상태로 URL이 전환됩니다.
@Controller
@RequiredArgsConstructor //final이 붙은 속성을 포함하는 생성자를 자동으로 생성
@RequestMapping("/question")
public class QuestionController {
private final QuestionRepository questionRepository;
private final QuestionService questionService;
@GetMapping("/")
public String index(){
return "redirect:/question/list";
}
@GetMapping("/list")
public String QuestionList(Model model,@RequestParam(value="page", defaultValue="0") int page){
Page<Question> Pq=questionService.getList(page);
model.addAttribute("paging",Pq);
return "question_list";
}
}
QuestionList메서드는 question_List.html을 찾아 클라이언트로 보내줍니다.
매개변수로는 model객체와 url의 page값(기본적으로 0)이 사용됩니다.
변수 Pq에 서비스에서 구현한 getList메서드를 통해 page값을 가진 Page<Question>타입의 객체를 담고,
model객체에 Pq를 paging이라는 변수로 추가해줍니다.
그러면 main/resorces/templates폴더안에 question_list화면을 만들어봅시다.
그전에 공통되는 layout부터 만들것입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
crossorigin="anonymous"></script>
<meta charset="UTF-8">
</head>
<body>
<header th:replace="~{header::headerFragment}"></header>
<th:block layout:fragment="content"></th:block>
</body>
</html>
부트스트랩을 사용할것이기때문에 부트스트랩파일을 link태그와 script태그로 가져왔습니다.
그리고 body안에 header를 넣을 것인데 헤더는 코드길이가 길어서 header.html로 생성하여 넣어줬습니다.
레이아웃 안에 삽입될 내용은 th:block태그 사이에 들어갈것입니다.
header.html은 아래와같습니다.
<header class="p-3 text-bg-dark" th:fragment="headerFragment">
<div class="container">
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
<svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap">
<use xlink:href="#bootstrap"></use>
</svg>
</a>
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a href="/question/" class="nav-link px-2 text-white">Home</a></li>
<li><a href="#" class="nav-link px-2 text-white">Features</a></li>
<li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
<li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
<li><a href="#" class="nav-link px-2 text-white">About</a></li>
</ul>
<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
<input type="search" class="form-control form-control-dark text-bg-dark" placeholder="Search..."
aria-label="Search">
</form>
<div class="text-end">
<button type="button" class="btn btn-outline-light me-2">Login</button>
<button type="button" class="btn btn-warning" th:onclick="|location.href='@{/member/signup}'|">Sign-up</button>
</div>
</div>
</div>
</header>
이제 본격적으로 question_list.html을 작성할것입니다.
이전에 모델객체에 paging을 넘겨받았습니다.
<div class="col" th:each="question:${paging}">를 통해 paging안에 있는 객체들을 question변수로 접근할것입니다.
각각 question변수를 통해 subject와 content를 보여주고,th:onclick을 통해 카드객체를 클릭하면 해당 아이디에 해당하는 질문 화면으로 넘어가게 했습니다.
question객체의 answerList의 길이를 사용하여 질문갯수도 표시하였습니다.
<!DOCTYPE html>
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
<link rel="stylesheet" type="text/css" th:href="@{/questionList.css}">
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">quenswer</h1>
<p class="col-md-8 fs-4">한국형 stackoverflow</p>
<button class="btn btn-primary btn-lg" type="button" th:onclick="|location.href='@{/question/create}'|">질문하기</button>
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4" id="posting">
<div class="col" th:each="question:${paging}">
<div class="card h-100" id=th:text="${question.id}">
<div class="card-body" th:onclick="|location.href='@{/question/details/{id}(id=${question.id})}'|">
<h5 class="card-title" th:text="${question.subject}"></h5>
<p class="card-text" th:text="${question.content}"></p>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start" >
<div th:if="${#lists.size(question.answerList) > 0}"
th:text="${#lists.size(question.answerList)}"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 페이징처리 시작 -->
<div th:if="${!paging.isEmpty()}">
<ul class="pagination justify-content-center">
<li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
<a class="page-link"
th:href="@{|?page=${paging.number-1}|}">
<span>이전</span>
</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:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
</li>
<li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
<a class="page-link" th:href="@{|?page=${paging.number+1}|}">
<span>다음</span>
</a>
</li>
</ul>
</div>
<!-- 페이징처리 끝 -->
</div>
</html>
questionList.css는 아래와 같습니다.
제목과 내용이 긴경우 박스밖으로 넘칤수있기에 제목(card-title)은 한줄,내용(p)는 3줄만 보이게하였고 나머지는 안보이게 overflow:hidden을 설정하였습니다.
.card-body{
height:170px;
}
.card-title{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.pagination{
margin-top: 10px;
}
p{
display:-webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:3;
overflow:hidden;
}
page가 0(default)일때의 화면입니다.
page가 5일때의 화면입니다.
'웹개발' 카테고리의 다른 글
quenswer(프로젝트)-spring security(로그인,로그아웃) (0) | 2024.02.04 |
---|---|
quenswer(프로젝트)-spring security(회원가입) (0) | 2024.02.04 |
quenswer(프로젝트)-Junit Test(Answer) (0) | 2023.12.29 |
quenswer(프로젝트)-Junit Test(Question) (0) | 2023.12.29 |
quenswer(프로젝트)-entity설계 (4) | 2023.12.29 |