gwooden_코린이
스프링 부트 추천기능 구현해보기 본문
728x90
/* 추천 기능 구현 */
@GetMapping("/vote/{id}")
public String questionVote(@PathVariable("id") Integer id, Principal principal) {
//id에 해당하는 레코드에 voter컬럼부분에다가 추천자 정보를 넣어줌
Question question = questionService.getQuestion(id);
SiteUser siteUser = userService.getUser(principal.getName());
//question서비스에서 메서드 불러오기
questionService.vote(question, siteUser);
return "redirect:/question/detail/" + id;
}
question : 컨트롤
//추천 기능 메서드
public void vote(Question question, SiteUser siteUser) {
//question에 voter가져와서 siteuser 정보고 같이 추가로 넣어줌
question.getVoter().add(siteUser);
questionRepository.save(question);
}
question : 서비스
Question question = questionService.getQuestion(id);
새로운 question 객체에
질문코드에 해당하는 데이터를 넣어줌
기존 데이터들이 question에 저장
SiteUser siteUser = userService.getUser(principal.getName());
추천인만 추가
question.getVoter().add(d라는 사람의 정보)
questionService.vote(question, siteUser);
save(question)
@ManyToMany //한 사람이 여러 게시글에 투표를 할 수 있다보니 ManyToMan를 사용
Set<SiteUser> voter; //set 컬렉션으로 중복처리, 어떤 유저가 투표했는지 SiteUser 정보 가져오기
Question : VO
<a href="javascript:void(0);" class="recommend btn btn-sm btn-outline-secondary"
th:data-uri="@{|/question/vote/${question.id}|}">
추천
<span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></span>
</a>
추천 기능 : view
package com.example.sb.answer;
import java.time.LocalDateTime;
import java.util.Set;
import com.example.sb.question.Question;
import com.example.sb.user.SiteUser;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; //답변 코드
@Column(columnDefinition = "TEXT")
private String content; //답변 내용
private LocalDateTime createDate; //답변 작성일
@ManyToOne
private Question question; //답변을 달아준 질문
@ManyToOne
private SiteUser author; //답변을 작성한 작성자 정보
private LocalDateTime modifyDate; //수정날짜
@ManyToMany //한 사람이 여러 게시글에 투표를 할 수 있다보니 ManyToMan를 사용
Set<SiteUser> voter; //set 컬렉션으로 중복처리, 어떤 유저가 투표했는지 SiteUser 정보 가져오기
}
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<!-- 질문 -->
<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${question.content}"></div>
<div class="d-flex justify-content-end">
<div th:if="${question.modifyDate != null}" class="badge bg-light text-dark p-2 text-start">
<div>수정일자</div>
<div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<div class="badge bg-light text-dark p-2 text-start">
<div th:if="${question.author != null}" th:text="${question.author.username}"></div>
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
<!--질문 수정/삭제 버튼-->
<!--#lists.size(vo) : 자동으로 개수를 계산해줌-->
<div class="mb3">
<a href="javascript:void(0);" class="recommend btn btn-sm btn-outline-secondary"
th:data-uri="@{|/question/vote/${question.id}|}">
추천
<span class="badge rounded-pill bg-success" th:text="${#lists.size(question.voter)}"></span>
</a>
<a th:href="@{|/question/modify/${question.id}|}" class="btn btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${question.author != null and question.author.username == #authentication.getPrincipal().getUsername()}">
수정
</a>
<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}"
class="btn btn-outline-secondary delete" sec:authorize="isAuthenticated()"
th:if="${question.author != null and question.author.username == #authentication.getPrincipal().getUsername()}">
삭제
</a>
</div>
</div>
</div>
<!-- 답변의 갯수 표시 -->
<h5 class="border-bottom my-3 py-2"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<!-- 답변 반복 시작 -->
<div class="card my-3" th:each="answer : ${question.answerList}">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
<div class="d-flex justify-content-end">
<div th:if="${answer.modifyDate != null}" class="badge bg-light text-dark p-2 text-start">
<div>수정일자</div>
<div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<div class="badge bg-light text-dark p-2 text-start">
<div th:if="${answer.author != null}" th:text="${answer.author.username}"></div>
<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd hh:mm')}"></div>
</div>
</div>
<!--답변 수정/삭제 버튼-->
<div class="mb3">
<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${answer.author != null and answer.author.username == #authentication.getPrincipal().getUsername()}">
수정
</a>
<a href="javascript:void(0);" th:data-uri="@{|/answer/delete/${answer.id}|}"
class="btn btn-outline-secondary delete" sec:authorize="isAuthenticated()"
th:if="${answer.author != null and answer.author.username == #authentication.getPrincipal().getUsername()}">
삭제
</a>
</div>
</div>
</div>
<!-- 답변 반복 끝 -->
<!-- 답변 작성 -->
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<textarea name="content" id="content" rows="10" class="form-control"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
</div>
</html>
const btns = document.querySelectorAll('.delete');
//delete 클래스가 여러개 있을 경우 반복문을 사용해줘야 버튼들이 기능을 한다.
btns.forEach(function(btn) {
btn.addEventListener('click', function() {
if(confirm('정말로 삭제하시겠습니까?')) {
location.href = this.dataset.uri;
}
});
});
const recommendBtns = document.querySelectorAll('.recommend');
recommendBtns.forEach(function(btn) {
btn.addEventListener('click', function() {
if(confirm('추천하시겠습니까?')) {
location.href = this.dataset.uri;
}
});
});
package com.example.sb.user;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Setter
@Getter
public class SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
private String email;
}
더보기
package com.example.sb.answer;
import java.time.LocalDateTime;
import java.util.Set;
import com.example.sb.question.Question;
import com.example.sb.user.SiteUser;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; //답변 코드
@Column(columnDefinition = "TEXT")
private String content; //답변 내용
private LocalDateTime createDate; //답변 작성일
@ManyToOne
private Question question; //답변을 달아준 질문
@ManyToOne
private SiteUser author; //답변을 작성한 작성자 정보
private LocalDateTime modifyDate; //수정날짜
@ManyToMany //한 사람이 여러 게시글에 투표를 할 수 있다보니 ManyToMan를 사용
Set<SiteUser> voter; //set 컬렉션으로 중복처리, 어떤 유저가 투표했는지 SiteUser 정보 가져오기
}
package com.example.sb.question;
import java.security.Principal;
import org.springframework.data.domain.Page;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.sb.answer.AnswerForm;
import com.example.sb.user.SiteUser;
import com.example.sb.user.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@RequestMapping("/question")//중복 되는 주소를 리퀘스맵핑으로 처리
@RequiredArgsConstructor
@Controller
public class QuestionController {
private final QuestionService questionService;
private final UserService userService;
//question테이블에 전체레코드를 출력해주는 메서드
// @GetMapping("/list")
// public String list(Model model) { //model은 자바스크립트로 따지면 이벤트같은 기능, request역할을 해줌
// //질문 목록을 뽑아내는 코드
// List<Question> questionList = questionService.getList();
// model.addAttribute("questionList", questionList);
//
// return "question_list";
// }
/* 등록한 질문 리스트 페이지로 이동 */
@GetMapping("/list")
public String list(Model model, @RequestParam(value="page", defaultValue = "0") int page) {
Page<Question> paging = questionService.getList(page);
model.addAttribute("paging", paging);
// return "test";
return "question_list";
}
/* 질문 상세 보기 */
@GetMapping(value = "/detail/{id}")
public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm) {
//id에 해당하는 레코드를 가져와야 함
Question q = questionService.getQuestion(id);
//모델에 추가
model.addAttribute("question", q);
return "question_detail";
}
/* 질문 등록 페이지 이동 */
@PreAuthorize("isAuthenticated()") //메서드가 호출되기전에 권한이 있는지 검사를 먼저한 후 그다음 진행
@GetMapping("/create")
public String questionCreate(QuestionForm questionForm) {
return "question_form";
}
/* 질문 등록 */
@PostMapping("/create")
public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal) {
SiteUser siteUser = userService.getUser(principal.getName());
if(bindingResult.hasErrors()) {
return "question_form";
}
//입력한 질문제목과 내용을 Question 테이블에 추가 하는 코드
questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser);
return"redirect:/question/list";
}
/* 수정 페이지로 이동 */
@GetMapping("/modify/{id}")
public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id) {
Question question = questionService.getQuestion(id);
questionForm.setSubject(question.getSubject());
questionForm.setContent(question.getContent());
return "question_form";
}
/* 수정 기능 */
@PostMapping("/modify/{id}")
public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult
, @PathVariable("id") Integer id) {
Question question = questionService.getQuestion(id); //원본코드
if(bindingResult.hasErrors()) {
return "question_form";
}
//원본에서 제목과 내용만 수정해서 테이블에 update
questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
return "redirect:/question/detail/" + id;
}
/* 삭제 기능 구현 중*/
@GetMapping("/delete/{id}")
public String questionDelete(@PathVariable("id") Integer id ) {
Question question = questionService.getQuestion(id);
questionService.delete(question);
// questionService.delete(id); QuestionService에서 deleteByid를 이용했을 경우 이 코드만으로도 가능하다
return "redirect:/";
}
/* 추천 기능 구현 */
@GetMapping("/vote/{id}")
public String questionVote(@PathVariable("id") Integer id, Principal principal) {
//id에 해당하는 레코드에 voter컬럼부분에다가 추천자 정보를 넣어줌
Question question = questionService.getQuestion(id);
SiteUser siteUser = userService.getUser(principal.getName());
//question서비스에서 메서드 불러오기
questionService.vote(question, siteUser);
return "redirect:/question/detail/" + id;
}
}
package com.example.sb.question;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import com.example.sb.DataNotFoundException;
import com.example.sb.user.SiteUser;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class QuestionService {
private final QuestionRepository questionRepository;
// question테이블에 모든 레코드를 가져오는 메서드
// public List<Question> getList() {
// return questionRepository.findAll();
// }
//question테이블에 레코드를 페이지별로 가져오는 메서드
public Page<Question> getList(int page) {
List<Sort.Order> sort = new ArrayList<>();
//내림차순
sort.add(Sort.Order.desc("createDate"));
Pageable pageable = PageRequest.of(page, 10, Sort.by(sort));
return questionRepository.findAll(pageable);
}
//question테이블에서 전달받은 id에 해당하는 레코드를 가져오는 메서드
public Question getQuestion(Integer id) {
Optional<Question> q = questionRepository.findById(id);
if(q.isPresent()) {
return q.get();
} else {
// 검색도니 결과가 없을때 처리할 코드 - 에러페이지
throw new DataNotFoundException("해당 질문이 존재하지 않습니다.");
}
}
//질문 추가 메서드
public void create(String subject, String content, SiteUser author) {
Question quest = new Question();
quest.setSubject(subject);
quest.setContent(content);
quest.setCreateDate(LocalDateTime.now());
quest.setAuthor(author);
questionRepository.save(quest);
}
//수정 기능 메서드
//question 원본 레코드, subject 수정할 제목, content 수정할 내용
public void modify(Question question, String subject, String content) {
question.setSubject(subject);
question.setContent(content);
question.setModifyDate(LocalDateTime.now());
questionRepository.save(question);
}
//삭제 메서드 (기능 구현중)
public void delete(Question question) {
//자바스크리브에서 요청을 날려서 직접 찾아서 삭제 처리
questionRepository.delete(question);
}
/*
* id값을 jpa를 통해 알아서 찾게 하고 그 다음 delete를 하기 때문에 위 로직과 비교 했을때 아래 로직이 좀 더 느리다.
* 단, 컨트롤러에서 코드가 최소화가 된다.
*/
// public void delete(Integer id) {
// questionRepository.deleteById(id);
// }
//추천 기능 메서드
public void vote(Question question, SiteUser siteUser) {
//question에 voter가져와서 siteuser 정보고 같이 추가로 넣어줌
question.getVoter().add(siteUser);
questionRepository.save(question);
}
}
728x90
'스프링 부트' 카테고리의 다른 글
스프링 부트 RestAPI (0) | 2023.02.17 |
---|---|
스프링 부트 시큐리티 + H2-CONSOLE (0) | 2023.02.15 |
스프링 부트 유효성 검사 (0) | 2023.02.15 |
스프링 부트 타임리프 사용해보기 (0) | 2023.02.14 |
스프링 부트 DAO Respository (0) | 2023.02.13 |
Comments