목차 | |
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시 나오는 화면을 꾸밀 수 있음
'Java > Spring' 카테고리의 다른 글
Spring 기초(security) - AWS 풀스택 과정 71일차 (0) | 2024.11.05 |
---|---|
Spring 기초(scheduler) - AWS 풀스택 과정 70일차 (0) | 2024.11.04 |
Spring 기초(file) - AWS 풀스택 과정 69일차 (0) | 2024.11.01 |
Spring 기초(comment) - AWS 풀스택 과정 68일차 (0) | 2024.10.31 |
Spring 기초(paging, search) - AWS 풀스택 과정 67일차 (0) | 2024.10.30 |