이 투표글이 마감됐는지 아닌지 정보를 담고 있는 속성을 추가했다. 속성명은 dead(boolean).
스타트 부트 스트랩 템플릿을 이용해서 메인 페이지를 만들었다.
스타트 부트스트랩
메인 내용의 3가지 컨텐츠들은 가장 투표량이 많은 즉, 인기 탑 쓰리 투표들에 대한 정보들을 보여주도록 했다.
@Query(value="select b.*, "
+ "sum(i.count) c from board as b left join voteitem as i "
+ "on b.bno=i.board_num "
+ "group by i.board_num order by c desc limit 3", nativeQuery = true)
List<Board> findTop3();
@Override //인기 글 조회
public List<BoardDTO> mostPopluar() {
List<Board> list = boardRepository.findTop3();
List<BoardDTO> result = new ArrayList<>();
for(Board board : list) {
result.add(entityToDto(board, 2));
}
return result;
}
투표 항목 테이블에서 count 속성을 그룹으로 나눠 총 합을 구한뒤 내림차순으로 정렬해 상단 3개만 보이도록 쿼리를 짜야하는데 JPQL
로는 짜기가 힘들어 보여서 native queury
로 만들었다.
@Controller
@Log4j2
@RequiredArgsConstructor
public class MainController {
private final BoardService boardService;
@RequestMapping("/")
public String home(Model model) {
log.info("mainpage 요청");
List<BoardDTO> list = boardService.mostPopluar();
model.addAttribute("list", list);
return "index";
}
}
컨트롤러에서는 쿼리 결과를 Model에 담아서 보내주기만 하면 된다.
@Controller
@RequestMapping("/member")
@Log4j2
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private boolean loginStat(HttpSession session) {
Object loginSession = session.getAttribute("userLogin");
if(loginSession != null && loginSession.toString().equals("login")) return true;
return false;
}
//회원가입 폼 요청
@GetMapping("/signup")
public void signup() {
log.info("singup form 요청");
}
//회원가입 처리
@PostMapping("/signup")
public String signupPro(MemberDTO dto) {
log.info(dto);
String email = memberService.insertMember(dto);
if(email == null) {
return null;
}
return "/main";
}
//로그인 화면 요청
@GetMapping("/login")
public String login(HttpSession session) {
log.info("login 화면 요청");
if(loginStat(session)) return "redirect:/main";
return "/member/login";
}
//로그인 처리
@PostMapping("/login")
public String loginPro(MemberDTO dto, HttpSession session, Model model) {
int result = memberService.memberLogin(dto);
if(result == 1) { //로그인 성공 시
session.setAttribute("userLogin", "login");
session.setAttribute("userId", dto.getEmail());
return "/main";
}
model.addAttribute("loginCheck", 0);
return "/member/login";
}
//마이 페이지 요청
@GetMapping("/mypage")
public void mypage(MemberDTO dto, HttpSession session, Model model) {
log.info("mypage 요청");
if(loginStat(session)) {
MemberDTO findMember = memberService.getMember(dto);
if(findMember == null) {
return;
}
model.addAttribute("memberDTO", dto);
}
}
//회원 정보 수정 폼 요청
@GetMapping("/modify")
public void modify(MemberDTO dto, HttpSession session, Model model) {
log.info("회원정보 수정 폼 요청");
if(loginStat(session)) {
model.addAttribute("memberDTO", dto);
}
}
//회원 정보 수정 처리
@Transactional
@PutMapping("/modify")
public void modifyPro(MemberDTO dto) {
log.info("회원정보 수정 처리");
memberService.updateMember(dto);
//return "/member/mypage";
}
//회원 탈퇴 처리
@Transactional
@DeleteMapping("/modify")
public String deletePro(MemberDTO dto, HttpSession session) {
session.removeAttribute("userLogin");
session.removeAttribute("userId");
memberService.deleteMember(dto);
return "/main";
}
}
스프링 시큐리티는 사용하지 않았기에 사용자의 로그인 여부를 확인하는 loginStat()
함수를 하나 만들었다. 단순히 세션에 로그인 정보가 있는 판단하는 함수이다. 마이페이지 같은 주소처럼 로그인 여부가 필요한 주소 요청시에 사용한다.
@Controller
@RequestMapping("board")
@RequiredArgsConstructor
@Log4j2
public class BoardController {
public final BoardService boardService;
public final ItemService itemService;
private boolean loginStat(HttpSession session) {
Object loginSession = session.getAttribute("userLogin");
if(loginSession != null && loginSession.toString().equals("login")) return true;
return false;
}
@GetMapping("/register")
public void boardRegister(HttpSession session, Model model) {
log.info("게시글 등록 폼 요청");
if(loginStat(session)) {
String email = (String)session.getAttribute("userId");
model.addAttribute("email", email);
}
return;
}
@PostMapping("/register")
public String boardRegister(BoardDTO boardDTO, HttpServletRequest request) {
log.info(boardDTO);
String[] items = request.getParameterValues("item");
List<VoteItemDTO> list = new ArrayList<>();
for(String item : items) {
VoteItemDTO dto = VoteItemDTO.builder().item(item).build();
list.add(dto);
}
boardService.registerBoard(boardDTO, list);
return "/board/list";
}
@GetMapping("/list")
public String boardPage(@RequestParam(required = false) PageRequestBoardDTO dto, Model model) {
log.info(dto);
if(dto == null) {
dto = new PageRequestBoardDTO();
}
PageResponseBoardDTO response = boardService.getList(dto);
model.addAttribute("list", response.getBoardList());
return "board/list";
}
@GetMapping("/vote")
public void vote(BoardDTO dto, Model model) {
log.info("투표글 상세 조회");
BoardDTO result = boardService.getBoard(dto);
model.addAttribute("result", result);
}
@GetMapping("/modify")
public void modify(BoardDTO boardDTO, Model model) {
List<Long> inoList = new ArrayList<>();
for(VoteItemDTO itemDTO : boardDTO.getVoteItem()) {
inoList.add(itemDTO.getIno());
}
int canMod = itemService.canModify(inoList);
model.addAttribute("canMod", canMod);
model.addAttribute("boardDTO", boardDTO);
}
@Transactional
@PutMapping("/modify")
public String modifyPro(BoardDTO boardDTO, HttpServletRequest request) {
String[] inos = request.getParameterValues("ino");
String[] items = request.getParameterValues("item");
List<VoteItemDTO> itemList = new ArrayList<>();
for(int i=0; i<items.length; i++) {
try {
VoteItemDTO itemDTO = VoteItemDTO.builder().ino(Long.parseLong(inos[i]))
.item(items[i])
.board_num(boardDTO.getBno()).build();
itemList.add(itemDTO);
} catch (ArrayIndexOutOfBoundsException e) {
log.info("new item add");
VoteItemDTO itemDTO = VoteItemDTO.builder()
.item(items[i])
.board_num(boardDTO.getBno()).build();
itemList.add(itemDTO);
}
}
itemService.itemUpdate(itemList);
boardService.updateBoard(boardDTO);
return "/board/list";
}
@Transactional
@DeleteMapping("/modify")
public String deletePro(BoardDTO boardDTO) {
boardService.deleteBoard(boardDTO);
return "/board/list";
}
}
투표 글에는 단순히 글에 대한 데이터(제목, 설명 등)와 투표 내용에 대한 데이터(투표 항목, 득표수)가 동시에 사용되고 하나의 글에 여러개의 투표 항목이 들어가는 1:N 관계이다. 그로 인해 글에 대한 데이터는 BoardDTO를 파라미터로 써서 쉽게 body message를 받을 수 있지만 투표 항목은 List로 받아야 하기 때문에 HttpServletRequest
의 getParameterValues
로 받아오는 작업이 필요했다.
View 부분은 스프링 부트를 이용해서 타임리프를 사용하기로 했다. 타임리프 사용을 위해서는 먼저 설정이 필요하다.
build.gradle
에 아래의 의존성을 주입한다.
implementation group: 'org.thymeleaf', name: 'thymeleaf'
application.properties
에 타임리프 상세 설정을 한다.
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
타임리프 파일의 위치는 아래 사진처럼 src/main/resources 의 templates 디렉토리이다.
https://github.com/JINJAEHO/vote
기존 레포지토리가 말썽이라 새로운 곳으로 옮김