Java/Spring

Spring 기초(login) - AWS 풀스택 과정 72일차

awspspgh 2024. 11. 6. 16:03
목차
1. login
2. 404 error

 

1. login

▣ header

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>header.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</head>
<body>
<!-- <div class="container-md"> -->
	<h1>
		My Spring Project!!  
	</h1>
<!-- </div> -->

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Spring</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="/board/list">게시판 보기</a>
        </li>
        <sec:authorize access="isAnonymous()">
	        <li class="nav-item">
	          <a class="nav-link" href="/user/register">회원가입</a>
	        </li>
	        <li class="nav-item">
	          <a class="nav-link" href="/user/login">로그인</a>
	        </li>
        </sec:authorize>
        <sec:authorize access="isAuthenticated()">
        <!-- 인증 객체가 만들어져 있는 상태 -->
        <!-- 인증된 객체 가져오기 => 현재 로그인 정보는 : principal -->
        <sec:authentication property="principal.uvo.email" var="authEmail"/>
        <sec:authentication property="principal.uvo.nickName" var="authNick"/>
        <sec:authentication property="principal.uvo.authList" var="auths"/>
	        <li class="nav-item">
	          <a class="nav-link" href="/board/register">게시판 글쓰기</a>
	        </li>
	        <li class="nav-item">
	          <a class="nav-link" href="/user/modify">회원정보수정 ${authNick}(${authEmail})</a>
	        </li>
	        
	        <c:if test="${auths.stream().anyMatch(authVO -> authVO.auth.equals('ROLE_ADMIN')).get() }">
	        <li class="nav-item">
	          <a class="nav-link text-danger" href="/user/list">회원리스트(ADMIN)</a>
	        </li>
	        </c:if>
	        <li class="nav-item">
	          <a class="nav-link" href="/user/logout">로그아웃</a>
	        </li>
        </sec:authorize>
        </ul>
    </div>
  </div>
</nav>

 

UserController.java

package com.ezen.spring.controller;

import java.security.Principal;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.ezen.spring.domain.UserVO;
import com.ezen.spring.service.UserService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/user/**")
@Controller
public class UserController {
	
	private final UserService usv;
	private final BCryptPasswordEncoder bcEncoder;
	
	// mapping 경로와 jsp의 경로가 같으면 void 처리 가능.
	
	@GetMapping("/register")
	public void register() {}
	
	@PostMapping("/register")
	public String register(UserVO uvo) {
		log.info(">>>> User VO >> {}", uvo);
		// encode : 암호화 
		uvo.setPwd(bcEncoder.encode(uvo.getPwd()));
		//log.info(">>>> User VO >> {}", uvo);
		int isOk = usv.register(uvo);
		return "redirect:/";
	}
	
	@GetMapping("/login")
	public void login() {}
	
	@PostMapping("/login")
	public String login(HttpServletRequest request, RedirectAttributes re) {
		// 실제 로그인은 Security의 필터에서 가져감. 
		// 로그인 실패시 다시 로그인 페이지로 돌아와 오류 메시지를 전송
		// 재 로그인을 유도
		log.info(">>> errMsg >> {}", request.getAttribute("errMsg").toString());
		
		re.addAttribute("email", request.getAttribute("email"));
		re.addAttribute("errMsg", request.getAttribute("errMsg"));
		return "redirect:/user/login";
	}
	
	@GetMapping("/list")
	public void list(Model m) {
		List<UserVO> userList = usv.getList();
		m.addAttribute("userList", userList);
	}
	
	@GetMapping("/modify")
	public void modify() {}
	
	@PostMapping("/modify")
	public String modify(UserVO uvo, HttpServletRequest request, HttpServletResponse response, 
			RedirectAttributes re) {
		log.info(">>>> modify uvo >> {}", uvo);
		int isOk = 0;
		if(uvo.getPwd().isEmpty() || uvo.getPwd().length() == 0) {
			//비번 없이 업데이트 진행
			isOk = usv.modifyPwdEmpty(uvo);
		}else {
			// 비번 암호화 후 업데이트 진행
			uvo.setPwd(bcEncoder.encode(uvo.getPwd()));
			isOk = usv.modify(uvo);
		}
		//로그아웃 => index로 돌아가기
		logout(request, response);
		if(isOk > 0) {
			re.addFlashAttribute("modify_msg", "ok");			
		}else {
			re.addFlashAttribute("modify_msg", "fail");	
		}
		return "redirect:/";
	}
	
	@GetMapping("/remove")
	public String remove(HttpServletRequest request, HttpServletResponse response, 
			Principal principal, RedirectAttributes re) {
		log.info(principal.toString());
		String email = principal.getName();
		int isOk = usv.remove(email);
		if(isOk > 0) {
			re.addFlashAttribute("remove_msg", "ok");			
		}else {
			re.addFlashAttribute("remove_msg", "fail");	
		}
		logout(request, response);
		return "redirect:/";
	}
	
	private void logout(HttpServletRequest request, HttpServletResponse response) {
		// 내가 로그인 한 시큐리티의 authentication 객체
		Authentication authentication = 
				SecurityContextHolder.getContext().getAuthentication();
		new SecurityContextLogoutHandler()
			.logout(request, response, authentication);
	}

}

 

 UserService

package com.ezen.spring.service;

import java.util.List;

import com.ezen.spring.domain.UserVO;

public interface UserService {

	int register(UserVO uvo);

	List<UserVO> getList();

	int modifyPwdEmpty(UserVO uvo);

	int modify(UserVO uvo);

	int remove(String email);

}

 

 UserServiceImpl.java

package com.ezen.spring.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ezen.spring.dao.UserDAO;
import com.ezen.spring.domain.UserVO;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements UserService {

	private final UserDAO udao;

	@Transactional
	@Override
	public int register(UserVO uvo) {
		// TODO Auto-generated method stub
		int isOk = udao.insert(uvo);
		return udao.insertAuthInit(uvo.getEmail());
	}

	@Transactional
	@Override
	public List<UserVO> getList() {
		List<UserVO> userList = udao.getList();
		for(UserVO uvo : userList) {
			uvo.setAuthList(udao.selectAuths(uvo.getEmail()));
		}
		return userList;
	}

	@Override
	public int modifyPwdEmpty(UserVO uvo) {
		// TODO Auto-generated method stub
		return udao.modifyPwdEmpty(uvo);
	}

	@Override
	public int modify(UserVO uvo) {
		// TODO Auto-generated method stub
		return udao.modify(uvo);
	}

	@Transactional
	@Override
	public int remove(String email) {
		int isOk = udao.removeAuth(email);
		return udao.remove(email);
	}
}

 

 UserDAO.java

package com.ezen.spring.dao;

import java.util.List;

import com.ezen.spring.domain.AuthVO;
import com.ezen.spring.domain.UserVO;

public interface UserDAO {

	int insert(UserVO uvo);

	int insertAuthInit(String email);

	UserVO selectEmail(String username);

	List<AuthVO> selectAuths(String username);

	int updateLastLogin(String authEmail);

	List<UserVO> getList();

	int modifyPwdEmpty(UserVO uvo);

	int modify(UserVO uvo);

	int remove(String email);

	int removeAuth(String email);

}

 

 userMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
<mapper namespace="com.ezen.spring.dao.UserDAO">

	<insert id="insert">
		insert into user(email, pwd, nickName)
		values(#{email}, #{pwd}, #{nickName})
	</insert>
	<!-- 회원가입 후 권한 부여 --> 
	<insert id="insertAuthInit">
		insert into auth(email, auth)
		values(#{email}, 'ROLE_USER')
	</insert>
	<select id="selectEmail" resultType="com.ezen.spring.domain.UserVO">
		select * from user where email = #{email}
	</select>
	<select id="selectAuths" resultType="com.ezen.spring.domain.AuthVO">
		select * from auth where email = #{email}
	</select>
	<update id="updateLastLogin">
		update user set lastLogin = now() 
		where email = #{email}
	</update>
	
	<select id="getList" resultType="com.ezen.spring.domain.UserVO">
		select * from user order by reg_date desc
	</select>
	<update id="modifyPwdEmpty">
		update user set nickName = #{nickName}
		where email = #{email}
	</update>
	<update id="modify">
		update user set nickName = #{nickName}, pwd = #{pwd}
		where email = #{email}
	</update>
	<delete id="remove">
		delete from user where email = #{email}
	</delete>
	<delete id="removeAuth">
		delete from auth where email = #{email}
	</delete>
	
</mapper>

 

 board/register.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>


<jsp:include page="../layout/header.jsp" />
<div class="container-md">
<h1>Board Register Page...</h1>
<sec:authentication property="principal.uvo.nickName" var="authNick"/>
<hr>
<!-- enctype : multipart/form-data -->
<form action="/board/insert" method="post" enctype="multipart/form-data" >
	<div class="mb-3">
	  <label for="t" class="form-label">title</label>
	  <input type="text" class="form-control" id="t" name="title" placeholder="title...">
	</div>
	<div class="mb-3">
	  <label for="w" class="form-label">writer</label>
	  <input type="text" class="form-control" id="w" name="writer" value="${authNick }" readonly="readonly">
	</div>
	<div class="mb-3">
	  <label for="c" class="form-label">content</label>
	  <textarea class="form-control" id="c" rows="3" name="content"></textarea>
	</div>
	
	<!-- 첨부파일 입력 라인 추가 -->
	<div class="mb-3">
	  <label for="file" class="form-label"></label>
	  <input type="file" class="form-control" id="file" name="files" 
	  		multiple="multiple" style="display:none">
	  <button type="button" class="btn btn-info" id="trigger">FileUpload...</button>
	</div>
	
	<!-- 첨부파일 표시 라인 추가 -->
	<div class="mb-3" id="fileZone">
	</div>
	<button type="submit" class="btn btn-primary" id="regBtn">register</button>
</form>
</div>

<script type="text/javascript" src="/resources/js/boardRegister.js"></script>

<jsp:include page="../layout/footer.jsp" />

 

 board/detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>

<jsp:include page="../layout/header.jsp" />
<div class="container-md">
<h1>Board Detail Page...</h1>
<hr>
<!-- request 객체로 온 값은 ${bdto} -->
<!-- c:set은 값을 저장하는 용도 -->
<c:set value="${bdto.bvo }" var="bvo"></c:set>

	<div class="mb-3">
	  <label for="n" class="form-label">no.</label>
	  <input type="text" class="form-control" id="n" value="${bvo.bno }" readonly>
	</div>
	<div class="mb-3">
	  <label for="t" class="form-label">title</label>
	  <input type="text" class="form-control" id="t" value="${bvo.title }" readonly>
	</div>
	<div class="mb-3">
	  <label for="w" class="form-label">writer</label>
	  <input type="text" class="form-control" id="w" value="${bvo.writer }" readonly>
	  <span class="badge text-bg-info">${bvo.regDate }</span>
	</div>
	<div class="mb-3">
	  <label for="c" class="form-label">content</label>
	  <textarea class="form-control" id="c" rows="3" readonly>${bvo.content }</textarea>
	</div>
	
	<!-- file upload 표시라인 -->
	<c:set value="${bdto.flist }" var="flist"></c:set>
	<div class="mb-3">
		<ul class="list-group list-group-flush">
			<!-- 파일의 개수만큼 li를 반복하여 파일 표시 타입이 1인경우만 그림을 표시 -->
		  	<c:forEach items="${flist }" var="fvo">
		  		 <li class="list-group-item">
		  		 	<c:choose>
		  		 		<c:when test="${fvo.fileType > 0 }">
		  		 			<div>
		  		 				<img alt="" src="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}">
		  		 			</div>
		  		 		</c:when>
		  		 		<c:otherwise>
		  		 			<!-- 일반파일 : 아이콘 하나 가져와서 다운로드 가능하게 생성 -->
		  		 			<a href="/upload/${fvo.saveDir }/${fvo.uuid}_${fvo.fileName}" download="${fvo.fileName}">
		  		 				<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-file-earmark-arrow-down-fill" viewBox="0 0 16 16">
								  <path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0M9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1m-1 4v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 11.293V7.5a.5.5 0 0 1 1 0"/>
								</svg>
		  		 			</a>
		  		 		</c:otherwise>
		  		 	</c:choose>
		  		 	<div class="fw-bold">${fvo.fileName }</div>
		  		 	<span class="badge text-bg-primary rounded-pill">${fvo.regDate } / ${fvo.fileSize }Bytes</span>
		  		 </li>
		  	</c:forEach>
		</ul>
	</div>
	
	
	<a href="/board/modify?bno=${bvo.bno }"><button type="button" class="btn btn-success">modify</button></a>
	<a href="/board/delete?bno=${bvo.bno }"><button type="button" class="btn btn-danger">delete</button></a>
	<br>
	<hr>
	

	<!-- comment line -->
	<!-- comment post -->
	 <sec:authorize access="isAuthenticated()">
	 <sec:authentication property="principal.uvo.nickName" var="authNick"/>
	<div class="input-group mb-3">
	  <span class="input-group-text" id="cmtWriter">${authNick }</span>
	  <input type="text" id="cmtText" class="form-control" placeholder="Add Comment..." aria-label="Username" aria-describedby="basic-addon1">
	  <button type="button" id="cmtAddBtn" class="btn btn-secondary">post</button>
	</div>
	<c:set value="${authNick }" var="nick"/>
	 </sec:authorize>
	
	<!-- comment print -->
	<ul class="list-group list-group-flush" id="cmtListArea">
	  <li class="list-group-item">
	  	<div class="ms-2 me-auto">
	  		<div class="fw-bold">writer</div>
		 Content
	  	</div>
		<span class="badge text-bg-primary rounded-pill">regDate</span>
	  </li>
	</ul>
	
	<!-- 댓글 더보기 버튼 : 더 표시하고자 하는 댓글이 없으면 사라지게함. -->
	<div>
		<button type="button" id="moreBtn" data-page="1" class="btn btn-dark" style="visibility:hidden">MORE + </button>
	</div>
	
	<!-- modal line -->
	<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
	  <div class="modal-dialog">
	    <div class="modal-content">
	      <div class="modal-header">
	        <h1 class="modal-title fs-5" id="cmtWriterMod">${authNick }</h1>
	        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
	      </div>
	      <div class="modal-body">
	        <input type="text" class="form-control" id="cmtTextMod">
	      </div>
	      <div class="modal-footer">
	        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
	        <button type="button" id="cmtModBtn" class="btn btn-primary">changes</button>
	      </div>
	    </div>
	  </div>
	</div>
	
	
<script type="text/javascript">
let bnoVal = `<c:out value="${bvo.bno}" />`;
console.log(bnoVal);
let authNick = `<c:out value="${nick }" />`;
console.log(authNick);
</script>	

	
<script type="text/javascript" src="/resources/js/boardDetailComment.js"></script>

<script type="text/javascript">
	spreadCommendList(bnoVal);
</script>
	
</div>
<jsp:include page="../layout/footer.jsp" />

 

 boardDetailComment.js

console.log("boardDetailComment.js in");
console.log("vscode 연동 성공");

document.getElementById('cmtAddBtn').addEventListener('click',()=>{
   const cmtText = document.getElementById('cmtText');
   const cmtWriter = document.getElementById('cmtWriter');

   if(cmtText.value == null || cmtText.value == ''){
       alert('댓글을 입력해주세요.');
       cmtText.focus();
       return false;
   }
   let cmtData={
       bno: bnoVal,
       writer : cmtWriter.innerText,
       content: cmtText.value
   }
   console.log(cmtData);
   postCommentToServer(cmtData).then(result =>{
       if(result == '1'){
           alert("댓글 등록 성공");
           cmtText.value="";
           //댓글 뿌리기
           spreadCommendList(bnoVal);
       }
   })

});
console.log(authNick);
function spreadCommendList(bno, page=1){
   getCommentListFromServer(bno, page).then(result =>{
       console.log("ph>",result);
       // 댓글 뿌리기
       const ul = document.getElementById('cmtListArea');
       if(result.cmtList.length > 0){
           if(page == 1){
               ul.innerHTML=""; // 반복 전에 기존 샘플 버리기 (더보기 버튼에 의한 누적 불가능)
           }
           for(let cvo of result.cmtList){
               let li = `<li class="list-group-item" data-cno=${cvo.cno}>`;
               li += `<div class="ms-2 me-auto">`;
               li += `<div class="fw-bold">${cvo.writer}</div>`;
               li += `${cvo.content}`;
               li += `</div>`;
               li += `<span class="badge text-bg-primary rounded-pill">${cvo.regDate}</span>`;
               //수정 삭제 버튼 추가
               if(cvo.writer == authNick){
                   li += `<button type="button" data-cno=${cvo.cno} class="btn btn-outline-warning btn-sm mod" data-bs-toggle="modal" data-bs-target="#myModal">%</button>`;
                   li += `<button type="button" data-cno=${cvo.cno} class="btn btn-outline-danger btn-sm del">X</button>`;
               }
               li += `</li>`;
               ul.innerHTML += li;
           }
           //더보기 버튼 숨김여부 체크 코드
           let moreBtn = document.getElementById('moreBtn');
           // 더보기 버튼이 표시되는 조건
           // result = ph > pgvo > pageNo = 1 / realEndPage = 3
           // 현재 페이지가 전체 페이지보다 작으면 표시
           if(result.pgvo.pageNo < result.realEndPage){
               // style.visibility = "hidden" : 숨김  / 'visible' : 표시
               moreBtn.style.visibility = 'visible' ; //버튼 표시
               moreBtn.dataset.page = page+1; // 1 페이지 증가
           }else{
               // 현재 페이지가 전체보다 작지 않다면.. 같거나 크다면...
               moreBtn.style.visibility = 'hidden'; //숨김
           }
   
       }else{
           ul.innerHTML = `<li class="list-group-item">Comment List Empty</li>`;
       }
   })
}

//수정 삭제
document.addEventListener('click',(e)=>{
   // console.log(e.target);
   //내가 클릭한 버튼의 객체를 모달창으로 전달
   if(e.target.classList.contains('mod')){
       // 내가 클릭한 버튼의 li 가져오기
       //closest : 가장 가까운(나를 포함한) 태그 (부모태그)
       let li = e.target.closest('li');
       //nextSibling : 한 부모 안에서의 다음 형제 값 찾기
       let cmtText = li.querySelector('.fw-bold').nextSibling;
       console.log(cmtText);
       document.getElementById('cmtTextMod').value = cmtText.nodeValue;
       let cno = li.dataset.cno;
       let cmtWriter = li.querySelector('.fw-bold').innerText;
       document.getElementById('cmtWriterMod').innerHTML = `no.${cno}  <b>${cmtWriter}</b>`;  
       //cmtModBtn => cno 값을 dataset으로 달기
       document.getElementById('cmtModBtn').setAttribute("data-cno",cno);
   }
   if(e.target.id == 'cmtModBtn'){
       let cmtData = {
           cno: e.target.dataset.cno,
           content: document.getElementById('cmtTextMod').value
       };
       console.log(cmtData);
       updateCommentToServer(cmtData).then(result =>{
           if(result == '1'){
               alert("댓글 수정 성공");
           }else{
               alert("댓글 수정 실패");
           }
           //모달창 닫기 : btn-close 라는 객체를 클릭해라
           document.querySelector('.btn-close').click();
           //댓글 뿌리기
           spreadCommendList(bnoVal);
       })
       
   }
   if(e.target.classList.contains('del')){
       // cno 만 있으면 됨. 
       let li = e.target.closest('li');
       let cno = li.dataset.cno;
       removeCommentToServer(cno).then(result =>{
           if(result == '1'){
               alert("댓글 삭제 성공");
           }else{
               alert("댓글 삭제 실패");
           }
            //댓글 뿌리기
            spreadCommendList(bnoVal);
       })
   }
   if(e.target.id == 'moreBtn'){
       let page = parseInt(e.target.dataset.page);
       spreadCommendList(bnoVal, page);
   }

})


async function updateCommentToServer(cmtData) {
   try {
       const url = "/comment/modify";
       const config={
           method:'put',
           headers:{
               'Content-Type' : 'application/json; charset=utf-8'
           },
           body: JSON.stringify(cmtData)
       };
       const resp = await fetch(url, config);
       const result = await resp.text();  //isOk
       return result;
   } catch (error) {
       console.log(error);
   }
}

//delete 메서드 사용
async function removeCommentToServer(cno) {
   try {
       const url = "/comment/"+cno+"/"+bnoVal;
       const config = {
           method : 'delete'
       }
       const resp = await fetch(url, config);
       // const resp = await fetch("/comment/"+cno, {method:'delete'});
       const result = await resp.text();  //isOk
       return result;
   } catch (error) {
       console.log(error);
   }
}

//get은 생략가능.
async function getCommentListFromServer(bno, page) {
   try {
       const resp = await fetch("/comment/"+bno+"/"+page);
       const result = await resp.json();
       return result;
   } catch (error) {
       console.log(error);
   }
}

async function postCommentToServer(cmtData) {
   try {
       const url = "/comment/post";
       const config = {
           method: "post",
           headers:{
               'Content-Type':'application/json; charset=utf-8'
           },
           body: JSON.stringify(cmtData)
       };
       const resp = await fetch(url, config);
       console.log(resp);
       const result = await resp.text();  // isOk
       return result;
   } catch (error) {
       console.log(error);
   }
}

 

 user/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<jsp:include page="../layout/header.jsp" />
<div class="container-md">
<h1>User List Page...</h1>
<hr>
<div class="row row-cols-1 row-cols-md-2 g-4">
<c:forEach items="${userList }" var="uvo">
  <div class="col">
<div class="card border-dark mb-3" style="max-width: 540px;">
  <div class="row g-0">
    <div class="col-md-4">
      <img src="/resources/image/human.png" class="img-fluid rounded-start" alt="...">
    </div>
    <div class="col-md-8">
      <div class="card-body">
        <h5 class="card-title">${uvo.nickName }</h5>
        <p class="card-text">${uvo.email } (${uvo.regDate }) </p>
        <p class="card-text"><small class="text-body-secondary">Last login ${uvo.lastLogin } ago</small></p>
         <c:forEach items="${uvo.authList }" var="auths">
	    	 <span class="badge rounded-pill text-bg-success">${auths.auth }</span>
	     </c:forEach>
      </div>
    </div>
  </div>
</div>
</div>
</c:forEach>
</div>
</div>


<%-- <div class="card" style="width: 18rem;">
  <img src="/resources/image/image.jpg" class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">${uvo.email }(${uvo.nickName })</h5>
    <p class="card-text">가입일:${uvo.regDate }  </p>
     <p class="card-text">최근로그인:${uvo.lastLogin }</p>
     <c:forEach items="${uvo.authList }" var="auths">
     <span class="badge rounded-pill text-bg-success">${auths.auth }</span>
     </c:forEach>
     <br>
    <a href="/" class="btn btn-primary">Go somewhere</a>
  </div>
</div> --%>
<jsp:include page="../layout/footer.jsp" />

 

 user/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<jsp:include page="../layout/header.jsp" />
<div class="container-md">
<h1>User List Page...</h1>
<hr>
<div class="row row-cols-1 row-cols-md-2 g-4">
<c:forEach items="${userList }" var="uvo">
  <div class="col">
<div class="card border-dark mb-3" style="max-width: 540px;">
  <div class="row g-0">
    <div class="col-md-4">
      <img src="/resources/image/human.png" class="img-fluid rounded-start" alt="...">
    </div>
    <div class="col-md-8">
      <div class="card-body">
        <h5 class="card-title">${uvo.nickName }</h5>
        <p class="card-text">${uvo.email } (${uvo.regDate }) </p>
        <p class="card-text"><small class="text-body-secondary">Last login ${uvo.lastLogin } ago</small></p>
         <c:forEach items="${uvo.authList }" var="auths">
	    	 <span class="badge rounded-pill text-bg-success">${auths.auth }</span>
	     </c:forEach>
      </div>
    </div>
  </div>
</div>
</div>
</c:forEach>
</div>
</div>


<%-- <div class="card" style="width: 18rem;">
  <img src="/resources/image/image.jpg" class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">${uvo.email }(${uvo.nickName })</h5>
    <p class="card-text">가입일:${uvo.regDate }  </p>
     <p class="card-text">최근로그인:${uvo.lastLogin }</p>
     <c:forEach items="${uvo.authList }" var="auths">
     <span class="badge rounded-pill text-bg-success">${auths.auth }</span>
     </c:forEach>
     <br>
    <a href="/" class="btn btn-primary">Go somewhere</a>
  </div>
</div> --%>
<jsp:include page="../layout/footer.jsp" />

 

 user/modify.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<jsp:include page="../layout/header.jsp" />
<div class="container-md">
<h1>User Modify Page...</h1>
<hr>
<sec:authentication property="principal.uvo" var="uvo"/>
<form action="/user/modify" method="post">
 <div class="col">
<div class="card border-dark mb-3" style="max-width: 540px;">
  <div class="row g-0">
    <div class="col-md-4">
      <img src="/resources/image/human.png" class="img-fluid rounded-start" alt="...">
    </div>
    <div class="col-md-8">
      <div class="card-body">
       <input type="hidden" name="email" value="${uvo.email }">
        <h5 class="card-title"> 
			  <input type="text" name="nickName" class="form-control"  value="${uvo.nickName }">
			  <input type="text" name="pwd" class="form-control" placeholder="password...">      
        </h5>
        <p class="card-text">${uvo.email } (${uvo.regDate }) </p>
        <p class="card-text"><small class="text-body-secondary">Last login ${uvo.lastLogin } ago</small></p>
         <c:forEach items="${uvo.authList }" var="auths">
	    	 <span class="badge rounded-pill text-bg-success">${auths.auth }</span>
	     </c:forEach>
	     <button type="submit" class="btn btn-primary btn-sm">modify</button>
	     <a href="/user/remove"><button type="button" class="btn btn-danger btn-sm">remove</button></a>
      </div>
    </div>
  </div>
</div>
</div>
</form>
</div>
<jsp:include page="../layout/footer.jsp" />

 

▷ 출력

회원 리스트, 회원 정보 수정

 

2. 404 error

 custom404.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1> Custom 404 Page... </h1>

<h3><c:out value="${exception }"/></h3>
</body>
</html>

 

 ServletConfiguration.java

package com.ezen.spring.config;

import org.springframework.context.annotation.Bean;

//import javax.servlet.MultipartConfigElement;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableScheduling
@EnableWebMvc
@ComponentScan(basePackages = {"com.ezen.spring.controller","com.ezen.spring.service","com.ezen.spring.handler","com.ezen.spring.security","com.ezen.spring.exception"})
public class ServletConfiguration implements WebMvcConfigurer{

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		// resources 경로 설정 (css, js, img, font)
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
		// 나중에 파일 업로드 경로도 추가 예정
		registry.addResourceHandler("/upload/**").addResourceLocations("file:///D:\\_myProject\\_java\\_fileUpload\\");
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		// view를 jsp(jstl 포함)로 어떻게 보여줄지 설정
		// view 경로 설정
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setPrefix("/WEB-INF/views/");
		viewResolver.setSuffix(".jsp");
		// 화면에 뷰를 구성할 때 jstl에 대한 사용을 하는 jsp를 구성하겠다.
		viewResolver.setViewClass(JstlView.class);
		
		registry.viewResolver(viewResolver);
	}

	// 나중에 파일 업로드 리졸버도 추가 예정.
	// 빈 이름이 multipartResolver이어야 에러가 안 남
	@Bean(name = "multipartResolver")
	public MultipartResolver getMultipartResolver( ) {
		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
		return multipartResolver;
	}
}

 

 WebConfig.java

package com.ezen.spring.config;

import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{

	@Override
	protected Class<?>[] getRootConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] {RootConfig.class, SecurityConfig.class};
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] {ServletConfiguration.class};
	}

	@Override
	protected String[] getServletMappings() {
		// TODO Auto-generated method stub
		return new String[] {"/"};
	}

	// encoding filter 설정
	@Override
	protected Filter[] getServletFilters() {
		// TODO Auto-generated method stub
//		CharacterEncodingFilter encoding = new CharacterEncodingFilter("UTF-8", true);
		CharacterEncodingFilter encoding = new CharacterEncodingFilter();
		encoding.setEncoding("UTF-8");
		encoding.setForceEncoding(true); // 외부로 나가는 데이터 인코딩 여부
		
		return new Filter[] {encoding};
	}

	// 사용자 지정 설정이 필요한 경우 사용. (파일업로드)
	@Override
	protected void customizeRegistration(Dynamic registration) {
		// 사용자 지정 익셉션 처리 설정
		registration.setInitParameter("throwExceptionIfNoHandlerFound","true");
		// 파일 업로드 설정 (위치 설정)
		String uploadLocation = "D:\\_myProject\\_java\\_fileUpload";
		int maxFileSize = 1024*1024*20; // 20MB
		int maxReqSize = maxFileSize * 3;
		int fileSizeThreshold = maxFileSize;
		
		MultipartConfigElement multipartConfig = new MultipartConfigElement(uploadLocation, maxFileSize, maxReqSize, fileSizeThreshold);
		
		registration.setMultipartConfig(multipartConfig);
	}	
	
}

 

 CommonExceptionAdvice.java

package com.ezen.spring.exception;

import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@ControllerAdvice
public class CommonExceptionAdvice {

	@ExceptionHandler(NoHandlerFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public String handler404(NoHandlerFoundException ex, Model m) {
		log.info(">>> exception >> {}", ex.getMessage());
		m.addAttribute("exception", ex.getMessage());
		return "custom404";
	}
	
//	@ExceptionHandler(Exception.class)
//	public String exception(Exception ex, Model m) {
//		return 새로 페이지 만들어서 연결
//	}
	
}

 

▷ 출력

404 error시 나오는 화면을 꾸밀 수 있음

 

출처 : https://blog.naver.com/momonocha/223649642099