스프링 부트로 진행하던 프로젝트를 스프링 기반으로 바꾸어서 구현해보았습니다.
아무래도 MVC 패턴에 익숙해지기 위해서는 스프링 기반으로 먼저 하는 것도 좋을 듯 싶어서
먼저하고 나중에 부트도 해보려 합니다.
스터디개설, 포인트가감 기능은 추후에 구현하려합니다.
이전에 H2 데이터베이스를 마이바티스를 활용하여 오라클과 연동했습니다.
https://github.com/kingof7/Spring-Board-Project
1. 구현 기능
1) 로그인 (부트스트랩 Modal 기능)
2) 회원가입
3) 게시판 (글, 댓글 작성,수정,삭제, 파일첨부 기능)
4) 회원정보
2. 구현 기술
1) 언어: Java, HTML
2) 프레임워크: 스프링
3) 서버: Tomcat
4) 빌드툴: Maven
5) 라이브러리: ojdbc, mybatis, jstl, servlet, spring-security 등
6) DB: 오라클 (Oracle 18c, SQL Developer)
3. 화면
1) 메인페이지 (home.jsp)
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page language="java" pageEncoding="UTF-8"
contentType="text/html; charset=UTF-8"%>
<html>
<head>
<title>스터디 게시판에 오신 걸 환영합니다.</title>
<!-- Required meta tags -->
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
<!-- css는 resources파일안에 -->
<link rel="stylesheet" href="/resources/index.css"/>
<!-- 합쳐지고 최소화된 최신 CSS -->
<!-- 부가적인 테마 -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script
src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<script type="text/javascript">
$(document).ready(function() {
$("#logoutBtn").on("click", function() {
location.href = "member/logout";
})
$("#registerBtn").on("click", function() {
location.href = "member/register";
})
$("#memberUpdateBtn").on("click", function() {
location.href = "member/memberUpdateView";
})
})
</script>
<body style="margin: 0px auto; width: 80%;">
<header>
<div id="title">
<span> 당신에게 필요한 모임 - StudyM</span>
</div>
<div id="menu">
<a href="/"><span style="margin-left: 50px; color: green;"><b>스터디</b></span><span
style="color: red;"><b>엠</b></span></a>
<ul>
<li style="margin-left: 20px;"><a href="#">공지사항</a></li>
<li><a href="/board/list">스터디게시판</a></li>
<li style="float: right; margin-right: 30px;"><a href="/member/list.do">스터디원</a></li>
<li style="float: right;">|</li>
<li style="float: right;"><a href="/member/register">회원가입</a></li>
<li style="float: right;">|</li>
<div class="container">
<c:if test="${member == null}">
<li style="float: right; padding: 10px; color: #0056b3;"
type='button' data-toggle="modal" data-target="#popUpWindow">로그인</li>
</c:if>
<div class="modal fade" id="popUpWindow">
<div class="modal-dialog">
<div class="modal-content">
<!-- header -->
<div class="modal-header"
style="display: block; text-align: center;">
<h3 class="modal-title">로그인</h3>
</div>
<!-- body -->
<div class="modal-header">
<form role="form" name='homeForm' method="post"
action="/member/login">
<c:if test="${member == null}">
<div class="form-group">
<input type="text" id="userId" name="userId"
class="form-control" placeholder="Id" style="width: 465px;" />
<input type="password" id="userPass" name="userPass"
class="form-control" placeholder="Password" />
</div>
<!-- footer -->
<div class="modal-footer">
<button class="btn btn-primary btn-block">Log In</button>
</div>
</c:if>
</form>
</div>
</div>
</div>
</div>
<c:if test="${msg == false}">
<c:if test="${member == null}">
<p style="color: red;">로그인 실패! 아이디와 비밀번호 확인해주세요.</p>
</c:if>
</c:if>
<c:if test="${member != null}">
<ul style="line-height: 2.8; list-style-type: none; margin: 0px;">
<li style="padding-right: 10px; float: right; text-decoration: none;"><a href="/member/memberUpdateView" id="memberUpdateBtn" >회원정보수정</a></li>
<li style="padding-right: 20px; float: right;">|</li>
<li style="padding-right: 20px; float: right; text-decoration: none;"><a href="#" id="logoutBtn">로그아웃</a></li>
<li style="padding-right: 20px; float: right;">|</li>
<li style="padding-right: 20px; float: right; text-decoration: none;"><a>${member.userId}님 환영합니다.</a></li>
</ul>
</c:if>
</div>
</ul>
</div>
<section>
<div id="content">
<img
src="https://search.pstatic.net/common/?src=http%3A%2F%2Fldb.phinf.naver.net%2F20190719_42%2F1563527498327ep72X_JPEG%2FXRE0BVqaCxDVC-5dIzVSi2vk.jpg&type=b400" />
<div class="real">
<div
style="padding: 10px; background-color: #fecc84; text-align: center;">실시간
Best 게시글</div>
<div
style="padding-top: 10px; padding-left: 10px; background-color: white; height: 100px;">
<img src="../img/rank1.png" style="width: 20px; height: 20px;" />
<span>(서울 신촌,강남)롯데 인적성 스터디 모집합니다!</span>
</div>
</div>
<div class="real">
<div
style="padding: 10px; background-color: #fecc84; text-align: center;">접속중
회원: 0명</div>
<div
style="padding-top: 10px; padding-left: 10px; background-color: white; height: 100px; overflow-y: scroll;">
</div>
</div>
<div class="real">
<div
style="padding: 10px; background-color: #fecc84; text-align: center;">접속중
회원: 0명</div>
<div
style="padding-top: 10px; padding-left: 10px; background-color: white; height: 100px; overflow-y: scroll;">
</div>
</div>
<div class="real">
<div
style="padding: 10px; background-color: #fecc84; text-align: center;">접속중
회원: 0명</div>
<div
style="padding-top: 10px; padding-left: 10px; background-color: white; height: 100px; overflow-y: scroll;">
</div>
</div>
</div>
</section>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
</body>
</html>
2) 로그인 화면 (MemberController.java)
package kr.co.controller;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import kr.co.service.MemberService;
import kr.co.vo.MemberVO;
import kr.co.vo.SearchCriteria;
@Controller
@RequestMapping("/member/*")
public class MemberController {
private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
@Inject
MemberService service;
@Inject
BCryptPasswordEncoder pwdEncoder;
// 회원가입 get
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void getRegister() throws Exception {
logger.info("get register");
}
// 회원가입 post
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String postRegister(MemberVO vo) throws Exception {
logger.info("post register");
int result = service.idChk(vo);
try {
if (result == 1) {
return "/member/register";
} else if (result == 0) {
String inputPass = vo.getUserPass();
String pwd = pwdEncoder.encode(inputPass);
vo.setUserPass(pwd);
service.register(vo);
}
// 요기에서~ 입력된 아이디가 존재한다면 -> 다시 회원가입 페이지로 돌아가기
// 존재하지 않는다면 -> register
} catch (Exception e) {
throw new RuntimeException();
}
return "redirect:/";
}
// 로그인 post
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception {
logger.info("post login");
session.getAttribute("member");
MemberVO login = service.login(vo);
boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
if (login != null && pwdMatch == true) {
session.setAttribute("member", login);
} else {
session.setAttribute("member", null);
rttr.addFlashAttribute("msg", false);
}
return "redirect:/";
}
// 로그아웃 post
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) throws Exception {
session.invalidate();
return "redirect:/";
}
// 회원정보 수정 get
@RequestMapping(value = "/memberUpdateView", method = RequestMethod.GET)
public String registerUpdateView() throws Exception {
return "member/memberUpdateView";
}
// 회원정보 수정 post
@RequestMapping(value = "/memberUpdate", method = RequestMethod.POST)
public String registerUpdate(MemberVO vo, HttpSession session) throws Exception {
/*
* MemberVO login = service.login(vo);
*
* boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
* if(pwdMatch) { service.memberUpdate(vo); session.invalidate(); }else { return
* "member/memberUpdateView"; }
*/
service.memberUpdate(vo);
session.invalidate();
return "redirect:/";
}
// 회원 탈퇴 get
@RequestMapping(value = "/memberDeleteView", method = RequestMethod.GET)
public String memberDeleteView() throws Exception {
return "member/memberDeleteView";
}
// 회원 조회
@RequestMapping(value = "/list.do")
public String memberList(Model model) throws Exception{
List<MemberVO> list = service.memberList();
model.addAttribute("list", list);
return "member/memberView";
}
// 회원 탈퇴 post
@RequestMapping(value = "/memberDelete", method = RequestMethod.POST)
public String memberDelete(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception {
service.memberDelete(vo);
session.invalidate();
return "redirect:/";
}
// 패스워드 체크
@ResponseBody
@RequestMapping(value = "/passChk", method = RequestMethod.POST)
public boolean passChk(MemberVO vo) throws Exception {
MemberVO login = service.login(vo);
boolean pwdChk = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
return pwdChk;
}
// 아이디 중복 체크
@ResponseBody
@RequestMapping(value = "/idChk", method = RequestMethod.POST)
public int idChk(MemberVO vo) throws Exception {
int result = service.idChk(vo);
return result;
}
}
3) 회원가입 화면
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>회원가입</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
// 취소
$(".cencle").on("click", function(){
location.href = "/";
})
$("#submit").on("click", function(){
if($("#userId").val()==""){
alert("아이디를 입력해주세요.");
$("#userId").focus();
return false;
}
if($("#userPass").val()==""){
alert("비밀번호를 입력해주세요.");
$("#userPass").focus();
return false;
}
if($("#userName").val()==""){
alert("성명을 입력해주세요.");
$("#userName").focus();
return false;
}
var idChkVal = $("#idChk").val();
if(idChkVal == "N"){
alert("중복확인 버튼을 눌러주세요.");
}else if(idChkVal == "Y"){
$("#regForm").submit();
}
});
})
function fn_idChk(){
$.ajax({
url : "/member/idChk",
type : "post",
dataType : "json",
data : {"userId" : $("#userId").val()},
success : function(data){
if(data == 1){
alert("중복된 아이디입니다.");
}else if(data == 0){
$("#idChk").attr("value", "Y");
alert("사용가능한 아이디입니다.");
}
}
})
}
</script>
<body>
<section id="container">
<form action="/member/register" method="post" id="regForm">
<div class="form-group has-feedback">
<label class="control-label" for="userId">아이디</label>
<input class="form-control" type="text" id="userId" name="userId" />
<button class="idChk" type="button" id="idChk" onclick="fn_idChk();" value="N">중복확인</button>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userPass">패스워드</label>
<input class="form-control" type="password" id="userPass" name="userPass" />
</div>
<div class="form-group has-feedback">
<label class="control-label" for="userName">성명</label>
<input class="form-control" type="text" id="userName" name="userName" />
</div>
</form>
<div class="form-group has-feedback">
<button class="btn btn-success" type="button" id="submit">회원가입</button>
<button class="cencle btn btn-danger" type="button">취소</button>
</div>
</section>
</body>
</html>
4) 회원정보 페이지
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page language="java" pageEncoding="UTF-8"
contentType="text/html; charset=UTF-8"%>
<html>
<head>
<title>스터디 게시판에 오신 걸 환영합니다.</title>
<!-- Required meta tags -->
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
<!-- css는 resources파일안에 -->
<link rel="stylesheet" href="/resources/index.css" />
<!-- 합쳐지고 최소화된 최신 CSS -->
<!-- 부가적인 테마 -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script
src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<script type="text/javascript">
$(document).ready(function() {
$("#logoutBtn").on("click", function() {
location.href = "member/logout";
})
$("#registerBtn").on("click", function() {
location.href = "member/register";
})
$("#memberUpdateBtn").on("click", function() {
location.href = "member/memberUpdateView";
})
})
</script>
<body style="margin: 0px auto; width: 80%;">
<header>
<div id="title">
<span> 당신에게 필요한 모임 - StudyM</span>
</div>
<div id="menu">
<a href="/"><span style="margin-left: 50px; color: green;"><b>스터디</b></span><span
style="color: red;"><b>엠</b></span></a>
<ul>
<li style="margin-left: 20px;"><a href="#">공지사항</a></li>
<li><a href="/board/list">스터디게시판</a></li>
<li style="float: right; margin-right: 30px;"><a
href="/member/studymember">스터디원</a></li>
<li style="float: right;">|</li>
<li style="float: right;"><a href="/member/register">회원가입</a></li>
<li style="float: right;">|</li>
<div class="container">
<c:if test="${member == null}">
<li style="float: right; padding: 10px; color: #0056b3;"
type='button' data-toggle="modal" data-target="#popUpWindow">로그인</li>
</c:if>
<div class="modal fade" id="popUpWindow">
<div class="modal-dialog">
<div class="modal-content">
<!-- header -->
<div class="modal-header"
style="display: block; text-align: center;">
<h3 class="modal-title">로그인</h3>
</div>
<!-- body -->
<div class="modal-header">
<form role="form" name='homeForm' method="post"
action="/member/login">
<c:if test="${member == null}">
<div class="form-group">
<input type="text" id="userId" name="userId"
class="form-control" placeholder="Id" style="width: 465px;" />
<input type="password" id="userPass" name="userPass"
class="form-control" placeholder="Password" />
</div>
<!-- footer -->
<div class="modal-footer">
<button class="btn btn-primary btn-block">Log In</button>
</div>
</c:if>
</form>
</div>
</div>
</div>
</div>
<c:if test="${msg == false}">
<c:if test="${member == null}">
<p style="color: red;">로그인 실패! 아이디와 비밀번호 확인해주세요.</p>
</c:if>
</c:if>
<c:if test="${member != null}">
<ul style="line-height: 2.8; list-style-type: none; margin: 0px;">
<li
style="padding-right: 10px; float: right; text-decoration: none;"><a
href="/member/memberUpdateView" id="memberUpdateBtn">회원정보수정</a></li>
<li style="padding-right: 20px; float: right;">|</li>
<li
style="padding-right: 20px; float: right; text-decoration: none;"><a
href="#" id="logoutBtn">로그아웃</a></li>
<li style="padding-right: 20px; float: right;">|</li>
<li
style="padding-right: 20px; float: right; text-decoration: none;"><a>${member.userId}님
환영합니다.</a></li>
</ul>
</c:if>
</div>
</ul>
</div>
<section>
<div id="content">
<section id="container">
<form role="form" method="get">
<table style="" 1" width:700px;" class="table table-hover">
<thead>
<tr>
<th>아이디</th>
<th>이름</th>
</tr>
</thead>
<c:forEach var="row" items="${list}">
<tr>
<td>${row.userId}</td>
<td>${row.userName}</td>
<td>${row.regDate}</td>
</tr>
</c:forEach>
</table>
</div>
</section>
</div>
</section>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
</body>
</html>
5) 게시판 화면
- writeView
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<body>
<div class="container">
<header>
<h1> 스터디게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<section id="container">
<form role="form" method="get">
<table class="table table-hover">
<thead>
<tr><th>번호</th><th>제목</th><th>작성자</th><th>등록일</th><th>조회수</th>
</thead>
<c:forEach items="${list}" var = "list">
<tr>
<td><c:out value="${list.bno}" /></td>
<td>
<a href="/board/readView?bno=${list.bno}&page=${scri.page}&perPageNum=${scri.perPageNum}&searchType=${scri.searchType}&keyword=${scri.keyword}"><c:out value="${list.title}" /></a>
</td>
<td><c:out value="${list.writer}" /></td>
<td><fmt:formatDate value="${list.regdate}" pattern="yyyy-MM-dd"/></td>
<td><c:out value="${list.hit}" /></td>
</tr>
</c:forEach>
</table>
<div class="search row">
<div class="col-xs-2 col-sm-2">
<select name="searchType" class="form-control">
<option value="n"<c:out value="${scri.searchType == null ? 'selected' : ''}"/>>-----</option>
<option value="t"<c:out value="${scri.searchType eq 't' ? 'selected' : ''}"/>>제목</option>
<option value="c"<c:out value="${scri.searchType eq 'c' ? 'selected' : ''}"/>>내용</option>
<option value="w"<c:out value="${scri.searchType eq 'w' ? 'selected' : ''}"/>>작성자</option>
<option value="tc"<c:out value="${scri.searchType eq 'tc' ? 'selected' : ''}"/>>제목+내용</option>
</select>
</div>
<div class="col-xs-10 col-sm-10">
<div class="input-group">
<input type="text" name="keyword" id="keywordInput" value="${scri.keyword}" class="form-control"/>
<span class="input-group-btn">
<button id="searchBtn" type="button" class="btn btn-default">검색</button>
</span>
</div>
</div>
<script>
$(function(){
$('#searchBtn').click(function() {
self.location = "list" + '${pageMaker.makeQuery(1)}' + "&searchType=" + $("select option:selected").val() + "&keyword=" + encodeURIComponent($('#keywordInput').val());
});
});
</script>
</div>
<div class="col-md-offset-3">
<ul class="pagination">
<c:if test="${pageMaker.prev}">
<li><a href="list${pageMaker.makeSearch(pageMaker.startPage - 1)}">이전</a></li>
</c:if>
<c:forEach begin="${pageMaker.startPage}" end="${pageMaker.endPage}" var="idx">
<li <c:out value="${pageMaker.cri.page == idx ? 'class=info' : ''}" />>
<a href="list${pageMaker.makeSearch(idx)}">${idx}</a></li>
</c:forEach>
<c:if test="${pageMaker.next && pageMaker.endPage > 0}">
<li><a href="list${pageMaker.makeSearch(pageMaker.endPage + 1)}">다음</a></li>
</c:if>
</ul>
</div>
</form>
</section>
</div>
</body>
</html>
6) 게시글 검색 기능
7) 게시글, 댓글 작성기능, 파일첨부 기능
- updateView
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form[name='writeForm']");
$(".write_btn").on("click", function(){
if(fn_valiChk()){
return false;
}
formObj.attr("action", "/board/write");
formObj.attr("method", "post");
formObj.submit();
});
fn_addFile();
})
function fn_valiChk(){
var regForm = $("form[name='writeForm'] .chk").length;
for(var i = 0; i<regForm; i++){
if($(".chk").eq(i).val() == "" || $(".chk").eq(i).val() == null){
alert($(".chk").eq(i).attr("title"));
return true;
}
}
}
function fn_addFile(){
var fileIndex = 1;
//$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"<button type='button' style='float:right;' id='fileAddBtn'>"+"추가"+"</button></div>");
$(".fileAdd_btn").on("click", function(){
$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"</button>"+"<button type='button' style='float:right;' id='fileDelBtn'>"+"삭제"+"</button></div>");
});
$(document).on("click","#fileDelBtn", function(){
$(this).parent().remove();
});
}
</script>
<body>
<div id="root">
<header>
<h1> 게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<hr />
<section id="container">
<form name="writeForm" method="post" action="/board/write" enctype="multipart/form-data">
<table>
<tbody>
<c:if test="${member.userId != null}">
<tr>
<td>
<label for="title">제목</label><input type="text" id="title" name="title" class="chk" title="제목을 입력하세요."/>
</td>
</tr>
<tr>
<td>
<label for="content">내용</label><textarea id="content" name="content" class="chk" title="내용을 입력하세요."></textarea>
</td>
</tr>
<tr>
<td>
<label for="writer">작성자</label><input type="text" id="writer" name="writer" class="chk" title="작성자를 입력하세요." value="${member.userId}" />
</td>
</tr>
<tr>
<tr>
<td id="fileIndex">
</td>
</tr>
<tr>
<td>
<button class="write_btn" type="submit">작성</button>
<button class="fileAdd_btn" type="button">파일추가</button>
</td>
</tr>
</c:if>
<c:if test="${member.userId == null}">
<p>로그인 후에 작성하실 수 있습니다.</p>
</c:if>
</tbody>
</table>
</form>
</section>
<hr />
</div>
</body>
</html>
- replyUpdateView
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form[name='updateForm']");
$(document).on("click","#fileDel", function(){
$(this).parent().remove();
})
fn_addFile();
$(".cancel_btn").on("click", function(){
event.preventDefault();
location.href = "/board/readView?bno=${update.bno}"
+ "&page=${scri.page}"
+ "&perPageNum=${scri.perPageNum}"
+ "&searchType=${scri.searchType}"
+ "&keyword=${scri.keyword}";
})
$(".update_btn").on("click", function(){
if(fn_valiChk()){
return false;
}
formObj.attr("action", "/board/update");
formObj.attr("method", "post");
formObj.submit();
})
})
function fn_valiChk(){
var updateForm = $("form[name='updateForm'] .chk").length;
for(var i = 0; i<updateForm; i++){
if($(".chk").eq(i).val() == "" || $(".chk").eq(i).val() == null){
alert($(".chk").eq(i).attr("title"));
return true;
}
}
}
function fn_addFile(){
var fileIndex = 1;
//$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"<button type='button' style='float:right;' id='fileAddBtn'>"+"추가"+"</button></div>");
$(".fileAdd_btn").on("click", function(){
$("#fileIndex").append("<div><input type='file' style='float:left;' name='file_"+(fileIndex++)+"'>"+"</button>"+"<button type='button' style='float:right;' id='fileDelBtn'>"+"삭제"+"</button></div>");
});
$(document).on("click","#fileDelBtn", function(){
$(this).parent().remove();
});
}
var fileNoArry = new Array();
var fileNameArry = new Array();
function fn_del(value, name){
fileNoArry.push(value);
fileNameArry.push(name);
$("#fileNoDel").attr("value", fileNoArry);
$("#fileNameDel").attr("value", fileNameArry);
}
</script>
<body>
<div id="root">
<header>
<h1> 게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<hr />
<section id="container">
<form name="updateForm" role="form" method="post" action="/board/update" enctype="multipart/form-data">
<input type="hidden" name="bno" value="${update.bno}" readonly="readonly"/>
<input type="hidden" id="page" name="page" value="${scri.page}">
<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}">
<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}">
<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}">
<input type="hidden" id="fileNoDel" name="fileNoDel[]" value="">
<input type="hidden" id="fileNameDel" name="fileNameDel[]" value="">
<table>
<tbody>
<tr>
<td>
<label for="title">제목</label><input type="text" id="title" name="title" value="${update.title}" class="chk" title="제목을 입력하세요."/>
</td>
</tr>
<tr>
<td>
<label for="content">내용</label><textarea id="content" name="content" class="chk" title="내용을 입력하세요."><c:out value="${update.content}" /></textarea>
</td>
</tr>
<tr>
<td>
<label for="writer">작성자</label><input type="text" id="writer" name="writer" value="${update.writer}" readonly="readonly"/>
</td>
</tr>
<tr>
<td>
<label for="regdate">작성날짜</label>
<fmt:formatDate value="${update.regdate}" pattern="yyyy-MM-dd"/>
</td>
</tr>
<tr>
<td id="fileIndex">
<c:forEach var="file" items="${file}" varStatus="var">
<div>
<input type="hidden" id="FILE_NO" name="FILE_NO_${var.index}" value="${file.FILE_NO }">
<input type="hidden" id="FILE_NAME" name="FILE_NAME" value="FILE_NO_${var.index}">
<a href="#" id="fileName" onclick="return false;">${file.ORG_FILE_NAME}</a>(${file.FILE_SIZE}kb)
<button id="fileDel" onclick="fn_del('${file.FILE_NO}','FILE_NO_${var.index}');" type="button">삭제</button><br>
</div>
</c:forEach>
</td>
</tr>
</tbody>
</table>
<div>
<button type="button" class="update_btn">저장</button>
<button type="button" class="cancel_btn">취소</button>
<button type="button" class="fileAdd_btn">파일추가</button>
</div>
</form>
</section>
<hr />
</div>
</body>
</html>
- replyDeleteView
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form[name='updateForm']");
$(".cancel_btn").on("click", function(){
location.href = "/board/readView?bno=${replyUpdate.bno}"
+ "&page=${scri.page}"
+ "&perPageNum=${scri.perPageNum}"
+ "&searchType=${scri.searchType}"
+ "&keyword=${scri.keyword}";
})
})
</script>
<body>
<div id="root">
<header>
<h1> 게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<hr />
<section id="container">
<form name="updateForm" role="form" method="post" action="/board/replyUpdate">
<input type="hidden" name="bno" value="${replyUpdate.bno}" readonly="readonly"/>
<input type="hidden" id="rno" name="rno" value="${replyUpdate.rno}" />
<input type="hidden" id="page" name="page" value="${scri.page}">
<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}">
<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}">
<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}">
<table>
<tbody>
<tr>
<td>
<label for="content">댓글 내용</label><input type="text" id="content" name="content" value="${replyUpdate.content}"/>
</td>
</tr>
</tbody>
</table>
<div>
<button type="submit" class="update_btn">저장</button>
<button type="button" class="cancel_btn">취소</button>
</div>
</form>
</section>
<hr />
</div>
</body>
</html>
- replyDeleteView
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form[name='updateForm']");
$(".cancel_btn").on("click", function(){
location.href = "/board/readView?bno=${replyDelete.bno}"
+ "&page=${scri.page}"
+ "&perPageNum=${scri.perPageNum}"
+ "&searchType=${scri.searchType}"
+ "&keyword=${scri.keyword}";
})
})
</script>
<body>
<div id="root">
<header>
<h1> 게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<hr />
<section id="container">
<form name="updateForm" role="form" method="post" action="/board/replyDelete">
<input type="hidden" name="bno" value="${replyDelete.bno}" readonly="readonly"/>
<input type="hidden" id="rno" name="rno" value="${replyDelete.rno}" />
<input type="hidden" id="page" name="page" value="${scri.page}">
<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}">
<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}">
<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}">
<div>
<p>삭제 하시겠습니까?</p>
<button type="submit" class="delete_btn">예 삭제합니다.</button>
<button type="button" class="cancel_btn">아니오. 삭제하지 않습니다.</button>
</div>
</form>
</section>
<hr />
</div>
</body>
</html>
- readView
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<title>게시판</title>
</head>
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form[name='readForm']");
// 수정
$(".update_btn").on("click", function(){
formObj.attr("action", "/board/updateView");
formObj.attr("method", "get");
formObj.submit();
})
// 삭제
$(".delete_btn").on("click", function(){
var deleteYN = confirm("삭제하시겠습니까?");
if(deleteYN == true){
formObj.attr("action", "/board/delete");
formObj.attr("method", "post");
formObj.submit();
}
})
// 목록
$(".list_btn").on("click", function(){
location.href = "/board/list?page=${scri.page}"
+"&perPageNum=${scri.perPageNum}"
+"&searchType=${scri.searchType}&keyword=${scri.keyword}";
})
$(".replyWriteBtn").on("click", function(){
var formObj = $("form[name='replyForm']");
formObj.attr("action", "/board/replyWrite");
formObj.submit();
});
//댓글 수정 View
$(".replyUpdateBtn").on("click", function(){
location.href = "/board/replyUpdateView?bno=${read.bno}"
+ "&page=${scri.page}"
+ "&perPageNum=${scri.perPageNum}"
+ "&searchType=${scri.searchType}"
+ "&keyword=${scri.keyword}"
+ "&rno="+$(this).attr("data-rno");
});
//댓글 삭제 View
$(".replyDeleteBtn").on("click", function(){
location.href = "/board/replyDeleteView?bno=${read.bno}"
+ "&page=${scri.page}"
+ "&perPageNum=${scri.perPageNum}"
+ "&searchType=${scri.searchType}"
+ "&keyword=${scri.keyword}"
+ "&rno="+$(this).attr("data-rno");
});
})
function fn_fileDown(fileNo){
var formObj = $("form[name='readForm']");
$("#FILE_NO").attr("value", fileNo);
formObj.attr("action", "/board/fileDown");
formObj.submit();
}
</script>
<body>
<div class="container">
<header>
<h1> 게시판</h1>
</header>
<hr />
<div>
<%@include file="nav.jsp" %>
</div>
<section id="container">
<form name="readForm" role="form" method="post">
<input type="hidden" id="bno" name="bno" value="${read.bno}" />
<input type="hidden" id="page" name="page" value="${scri.page}">
<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}">
<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}">
<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}">
<input type="hidden" id="FILE_NO" name="FILE_NO" value="">
</form>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">제목</label>
<input type="text" id="title" name="title" class="form-control" value="${read.title}" readonly="readonly" />
</div>
<div class="form-group">
<label for="content" class="col-sm-2 control-label">내용</label>
<textarea id="content" name="content" class="form-control" readonly="readonly"><c:out value="${read.content}" /></textarea>
</div>
<div class="form-group">
<label for="writer" class="col-sm-2 control-label">작성자</label>
<input type="text" id="writer" name="writer" class="form-control" value="${read.writer}" readonly="readonly"/>
</div>
<div class="form-group">
<label for="regdate" class="col-sm-2 control-label">작성날짜</label>
<fmt:formatDate value="${read.regdate}" pattern="yyyy-MM-dd" />
</div>
<hr>
<span>파일 목록</span>
<div class="form-group" style="border: 1px solid #dbdbdb;">
<c:forEach var="file" items="${file}">
<a href="#" onclick="fn_fileDown('${file.FILE_NO}'); return false;">${file.ORG_FILE_NAME}</a>(${file.FILE_SIZE}kb)<br>
</c:forEach>
</div>
<hr>
<div>
<button type="button" class="update_btn btn btn-warning">수정</button>
<button type="button" class="delete_btn btn btn-danger">삭제</button>
<button type="button" class="list_btn btn btn-primary">목록</button>
</div>
<!-- 댓글 -->
<div id="reply">
<ol class="replyList">
<c:forEach items="${replyList}" var="replyList">
<li>
<p>
작성자 : ${replyList.writer}<br />
작성 날짜 : <fmt:formatDate value="${replyList.regdate}" pattern="yyyy-MM-dd" />
</p>
<p>${replyList.content}</p>
<div>
<button type="button" class="replyUpdateBtn btn btn-warning" data-rno="${replyList.rno}">수정</button>
<button type="button" class="replyDeleteBtn btn btn-danger" data-rno="${replyList.rno}">삭제</button>
</div>
</li>
</c:forEach>
</ol>
</div>
<form name="replyForm" method="post" class="form-horizontal">
<input type="hidden" id="bno" name="bno" value="${read.bno}" />
<input type="hidden" id="page" name="page" value="${scri.page}">
<input type="hidden" id="perPageNum" name="perPageNum" value="${scri.perPageNum}">
<input type="hidden" id="searchType" name="searchType" value="${scri.searchType}">
<input type="hidden" id="keyword" name="keyword" value="${scri.keyword}">
<div class="form-group">
<label for="writer" class="col-sm-2 control-label">댓글 작성자</label>
<div class="col-sm-10">
<input type="text" id="writer" name="writer" class="form-control" />
</div>
</div>
<div class="form-group">
<label for="content" class="col-sm-2 control-label">댓글 내용</label>
<div class="col-sm-10">
<input type="text" id="content" name="content" class="form-control"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="button" class="replyWriteBtn btn btn-success">작성</button>
</div>
</div>
</form>
</section>
<hr />
</div>
</body>
</html>
- nav
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<style type="text/css">
li {list-style: none; display:inline; padding: 6px;}
</style>
<ul>
<li><a href="/">메인으로</a></li>
<li><a href="/board/list">목록</a></li>
<li><a href="/board/writeView">글 작성</a></li>
<li>
<c:if test="${member != null}"><a href="/member/logout">로그아웃</a></c:if>
<c:if test="${member == null}"><a href="/">로그인</a></c:if>
</li>
<li>
<c:if test="${member != null}">
<label>${member.userId}님 안녕하세요.</label>
</c:if>
</li>
</ul>
4. 컨트롤러
1) HomeController.java
package kr.co.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
}
2) BoardController.java
package kr.co.controller;
import java.io.File;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import kr.co.service.BoardService;
import kr.co.service.ReplyService;
import kr.co.vo.BoardVO;
import kr.co.vo.PageMaker;
import kr.co.vo.ReplyVO;
import kr.co.vo.SearchCriteria;
@Controller
@RequestMapping("/board/*")
public class BoardController {
private static final Logger logger = LoggerFactory.getLogger(BoardController.class);
@Inject
BoardService service;
@Inject
ReplyService replyService;
// 게시판 글 작성 화면
@RequestMapping(value = "/writeView", method = RequestMethod.GET) // board뒤에 붙는 url
public void writeView() throws Exception {
logger.info("writeView");
}
// 게시판 글 작성
@RequestMapping(value = "/write", method = RequestMethod.POST)
public String write(BoardVO boardVO, MultipartHttpServletRequest mpRequest) throws Exception {
logger.info("write");
service.write(boardVO, mpRequest);
return "redirect:/board/list";
}
// 게시판 목록 조회
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model, @ModelAttribute("scri") SearchCriteria scri) throws Exception {
logger.info("list");
model.addAttribute("list", service.list(scri));
PageMaker pageMaker = new PageMaker();
pageMaker.setCri(scri);
pageMaker.setTotalCount(service.listCount(scri));
model.addAttribute("pageMaker", pageMaker);
return "board/list";
}
// 게시판 조회
@RequestMapping(value = "/readView", method = RequestMethod.GET)
public String read(BoardVO boardVO, @ModelAttribute("scri") SearchCriteria scri, Model model) throws Exception {
logger.info("read");
model.addAttribute("read", service.read(boardVO.getBno()));
model.addAttribute("scri", scri);
List<ReplyVO> replyList = replyService.readReply(boardVO.getBno());
model.addAttribute("replyList", replyList);
List<Map<String, Object>> fileList = service.selectFileList(boardVO.getBno());
model.addAttribute("file", fileList);
return "board/readView";
}
// 게시판 수정뷰
@RequestMapping(value = "/updateView", method = RequestMethod.GET)
public String updateView(BoardVO boardVO, @ModelAttribute("scri") SearchCriteria scri, Model model)
throws Exception {
logger.info("updateView");
model.addAttribute("update", service.read(boardVO.getBno()));
model.addAttribute("scri", scri);
List<Map<String, Object>> fileList = service.selectFileList(boardVO.getBno());
model.addAttribute("file", fileList);
return "board/updateView";
}
// 게시판 수정
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String update(BoardVO boardVO, @ModelAttribute("scri") SearchCriteria scri, RedirectAttributes rttr,
@RequestParam(value = "fileNoDel[]") String[] files,
@RequestParam(value = "fileNameDel[]") String[] fileNames, MultipartHttpServletRequest mpRequest)
throws Exception {
logger.info("update");
service.update(boardVO, files, fileNames, mpRequest);
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/list";
}
// 게시판 삭제
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public String delete(BoardVO boardVO, @ModelAttribute("scri") SearchCriteria scri, RedirectAttributes rttr)
throws Exception {
logger.info("delete");
service.delete(boardVO.getBno());
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/list";
}
// 댓글 작성
@RequestMapping(value = "/replyWrite", method = RequestMethod.POST)
public String replyWrite(ReplyVO vo, SearchCriteria scri, RedirectAttributes rttr) throws Exception {
logger.info("reply Write");
replyService.writeReply(vo);
rttr.addAttribute("bno", vo.getBno());
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/readView";
}
// 댓글 수정 GET
@RequestMapping(value = "/replyUpdateView", method = RequestMethod.GET)
public String replyUpdateView(ReplyVO vo, SearchCriteria scri, Model model) throws Exception {
logger.info("reply Write");
model.addAttribute("replyUpdate", replyService.selectReply(vo.getRno()));
model.addAttribute("scri", scri);
return "board/replyUpdateView";
}
// 댓글 수정 POST
@RequestMapping(value = "/replyUpdate", method = RequestMethod.POST)
public String replyUpdate(ReplyVO vo, SearchCriteria scri, RedirectAttributes rttr) throws Exception {
logger.info("reply Write");
replyService.updateReply(vo);
rttr.addAttribute("bno", vo.getBno());
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/readView";
}
// 댓글 삭제 GET
@RequestMapping(value = "/replyDeleteView", method = RequestMethod.GET)
public String replyDeleteView(ReplyVO vo, SearchCriteria scri, Model model) throws Exception {
logger.info("reply Write");
model.addAttribute("replyDelete", replyService.selectReply(vo.getRno()));
model.addAttribute("scri", scri);
return "board/replyDeleteView";
}
// 댓글 삭제
@RequestMapping(value = "/replyDelete", method = RequestMethod.POST)
public String replyDelete(ReplyVO vo, SearchCriteria scri, RedirectAttributes rttr) throws Exception {
logger.info("reply Write");
replyService.deleteReply(vo);
rttr.addAttribute("bno", vo.getBno());
rttr.addAttribute("page", scri.getPage());
rttr.addAttribute("perPageNum", scri.getPerPageNum());
rttr.addAttribute("searchType", scri.getSearchType());
rttr.addAttribute("keyword", scri.getKeyword());
return "redirect:/board/readView";
}
@RequestMapping(value = "/fileDown")
public void fileDown(@RequestParam Map<String, Object> map, HttpServletResponse response) throws Exception {
Map<String, Object> resultMap = service.selectFileInfo(map);
String storedFileName = (String) resultMap.get("STORED_FILE_NAME");
String originalFileName = (String) resultMap.get("ORG_FILE_NAME");
// 파일을 저장했던 위치에서 첨부파일을 읽어 byte[]형식으로 변환한다.
byte fileByte[] = org.apache.commons.io.FileUtils
.readFileToByteArray(new File("C:\\mp\\file\\" + storedFileName));
response.setContentType("application/octet-stream");
response.setContentLength(fileByte.length);
response.setHeader("Content-Disposition",
"attachment; fileName=\"" + URLEncoder.encode(originalFileName, "UTF-8") + "\";");
response.getOutputStream().write(fileByte);
response.getOutputStream().flush();
response.getOutputStream().close();
}
}
3) MemberController.java
package kr.co.controller;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import kr.co.service.MemberService;
import kr.co.vo.MemberVO;
import kr.co.vo.SearchCriteria;
@Controller
@RequestMapping("/member/*")
public class MemberController {
private static final Logger logger = LoggerFactory.getLogger(MemberController.class);
@Inject
MemberService service;
@Inject
BCryptPasswordEncoder pwdEncoder;
// 회원가입 get
@RequestMapping(value = "/register", method = RequestMethod.GET)
public void getRegister() throws Exception {
logger.info("get register");
}
// 회원가입 post
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String postRegister(MemberVO vo) throws Exception {
logger.info("post register");
int result = service.idChk(vo);
try {
if (result == 1) {
return "/member/register";
} else if (result == 0) {
String inputPass = vo.getUserPass();
String pwd = pwdEncoder.encode(inputPass);
vo.setUserPass(pwd);
service.register(vo);
}
// 요기에서~ 입력된 아이디가 존재한다면 -> 다시 회원가입 페이지로 돌아가기
// 존재하지 않는다면 -> register
} catch (Exception e) {
throw new RuntimeException();
}
return "redirect:/";
}
// 로그인 post
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception {
logger.info("post login");
session.getAttribute("member");
MemberVO login = service.login(vo);
boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
if (login != null && pwdMatch == true) {
session.setAttribute("member", login);
} else {
session.setAttribute("member", null);
rttr.addFlashAttribute("msg", false);
}
return "redirect:/";
}
// 로그아웃 post
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) throws Exception {
session.invalidate();
return "redirect:/";
}
// 회원정보 수정 get
@RequestMapping(value = "/memberUpdateView", method = RequestMethod.GET)
public String registerUpdateView() throws Exception {
return "member/memberUpdateView";
}
// 회원정보 수정 post
@RequestMapping(value = "/memberUpdate", method = RequestMethod.POST)
public String registerUpdate(MemberVO vo, HttpSession session) throws Exception {
/*
* MemberVO login = service.login(vo);
*
* boolean pwdMatch = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
* if(pwdMatch) { service.memberUpdate(vo); session.invalidate(); }else { return
* "member/memberUpdateView"; }
*/
service.memberUpdate(vo);
session.invalidate();
return "redirect:/";
}
// 회원 탈퇴 get
@RequestMapping(value = "/memberDeleteView", method = RequestMethod.GET)
public String memberDeleteView() throws Exception {
return "member/memberDeleteView";
}
// 회원 조회
@RequestMapping(value = "/list.do")
public String memberList(Model model) throws Exception{
List<MemberVO> list = service.memberList();
model.addAttribute("list", list);
return "member/memberView";
}
// 회원 탈퇴 post
@RequestMapping(value = "/memberDelete", method = RequestMethod.POST)
public String memberDelete(MemberVO vo, HttpSession session, RedirectAttributes rttr) throws Exception {
service.memberDelete(vo);
session.invalidate();
return "redirect:/";
}
// 패스워드 체크
@ResponseBody
@RequestMapping(value = "/passChk", method = RequestMethod.POST)
public boolean passChk(MemberVO vo) throws Exception {
MemberVO login = service.login(vo);
boolean pwdChk = pwdEncoder.matches(vo.getUserPass(), login.getUserPass());
return pwdChk;
}
// 아이디 중복 체크
@ResponseBody
@RequestMapping(value = "/idChk", method = RequestMethod.POST)
public int idChk(MemberVO vo) throws Exception {
int result = service.idChk(vo);
return result;
}
}
5. 매퍼
1) boardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="boardMapper">
<!-- 게시판 글 작성 -->
<insert id="insert" parameterType="kr.co.vo.BoardVO" useGeneratedKeys="true" keyProperty="bno">
<selectKey keyProperty="bno" resultType="int" order="BEFORE">
SELECT MP_BOARD_SEQ.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO MP_BOARD( BNO
, TITLE
, CONTENT
, WRITER )
VALUES( #{bno}
, #{title}
, #{content}
, #{writer} )
</insert>
<select id="listPage" resultType="kr.co.vo.BoardVO" parameterType="kr.co.vo.SearchCriteria">
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT
FROM (
SELECT BNO,
TITLE,
CONTENT,
WRITER,
REGDATE,
HIT,
ROW_NUMBER() OVER(ORDER BY BNO DESC) AS RNUM
FROM MP_BOARD
WHERE 1=1
<include refid="search"></include>
) MP
WHERE RNUM BETWEEN #{rowStart} AND #{rowEnd}
ORDER BY BNO DESC
</select>
<select id="listCount" parameterType="kr.co.vo.SearchCriteria" resultType="int">
SELECT COUNT(BNO)
FROM MP_BOARD
WHERE 1=1
<include refid="search"></include>
AND BNO > 0
</select>
<sql id="search">
<if test="searchType != null">
<if test="searchType == 't'.toString()">AND TITLE LIKE '%' || #{keyword} || '%'</if>
<if test="searchType == 'c'.toString()">AND CONTENT LIKE '%' || #{keyword} || '%'</if>
<if test="searchType == 'w'.toString()">AND WRITER LIKE '%' || #{keyword} || '%'</if>
<if test="searchType == 'tc'.toString()">AND (TITLE LIKE '%' || #{keyword} || '%') or (CONTENT LIKE '%' || #{keyword} || '%')</if>
</if>
</sql>
<select id="read" parameterType="int" resultType="kr.co.vo.BoardVO">
SELECT BNO
, TITLE
, CONTENT
, WRITER
, REGDATE
FROM MP_BOARD
WHERE BNO = #{bno}
</select>
<update id="update" parameterType="kr.co.vo.BoardVO">
UPDATE MP_BOARD
SET TITLE = #{title},
CONTENT = #{content}
WHERE BNO = #{bno}
</update>
<delete id="delete" parameterType="int">
DELETE
FROM MP_BOARD
WHERE BNO = #{bno}
</delete>
<!-- 첨부파일 업로드 -->
<insert id="insertFile" parameterType="hashMap">
INSERT INTO MP_FILE(
FILE_NO,
BNO,
ORG_FILE_NAME,
STORED_FILE_NAME,
FILE_SIZE
)VALUES(
SEQ_MP_FILE_NO.NEXTVAL,
#{BNO},
#{ORG_FILE_NAME},
#{STORED_FILE_NAME},
#{FILE_SIZE}
)
</insert>
<!-- 첨부파일 조회 -->
<select id="selectFileList" parameterType="int" resultType="hashMap">
SELECT FILE_NO,
ORG_FILE_NAME,
ROUND(FILE_SIZE/1024,1) AS FILE_SIZE,
DEL_GB
FROM MP_FILE
WHERE BNO = #{BNO}
AND DEL_GB = 'N'
ORDER BY FILE_NO ASC
</select>
<!-- 첨부파일 다운 -->
<select id="selectFileInfo" parameterType="hashMap" resultType="hashMap">
SELECT
STORED_FILE_NAME,
ORG_FILE_NAME
FROM MP_FILE
WHERE FILE_NO = #{FILE_NO}
</select>
<!-- 첨부파일 수정 -->
<update id="updateFile" parameterType="hashMap">
UPDATE MP_FILE SET
DEL_GB = 'Y'
WHERE FILE_NO = #{FILE_NO}
</update>
<!-- 게시판 조회수 -->
<update id="boardHit" parameterType="int">
UPDATE MP_BOARD SET
HIT = HIT+1
WHERE BNO = #{bno}
</update>
</mapper>
2) memberMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="memberMapper">
<!-- 회원가입 -->
<insert id="register">
INSERT INTO MP_MEMBER( USERID
, USERPASS
, USERNAME )
VALUES( #{userId}
, #{userPass}
, #{userName})
</insert>
<!-- 로그인 -->
<select id="login" resultType="kr.co.vo.MemberVO">
SELECT USERID, USERPASS, USERNAME
FROM MP_MEMBER
WHERE USERID = #{userId}
<!-- AND USERPASS = #{userPass} -->
</select>
<!-- 회원정보 수정 -->
<update id="memberUpdate">
UPDATE MP_MEMBER SET
<!-- USERPASS = #{userPass}, -->
USERNAME = #{userName}
WHERE USERID = #{userId}
</update>
<!-- 회원탈퇴 -->
<delete id="memberDelete">
DELETE FROM MP_MEMBER
WHERE USERID = #{userId}
<!-- AND USERPASS = #{userPass} -->
</delete>
<!-- 회원조회 -->
<select id="memberList" resultType="kr.co.vo.MemberVO">
SELECT USERID as userId
, USERNAME as userName
, REGDATE as regDate
FROM MP_MEMBER
ORDER BY regDate
</select>
<!-- 패스워드 체크 -->
<select id="passChk" resultType="int">
SELECT COUNT(*) FROM MP_MEMBER
WHERE USERID = #{userId}
AND USERPASS = #{userPass}
</select>
<!-- 아이디 중복 체크 -->
<select id="idChk" resultType="int">
SELECT COUNT(*) FROM MP_MEMBER
WHERE USERID = #{userId}
</select>
</mapper>
3) replyMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="replyMapper">
<!-- 댓글 조회 -->
<select id="readReply" resultType="kr.co.vo.ReplyVO">
SELECT
RNO, CONTENT, WRITER, REGDATE
FROM MP_REPLY
WHERE BNO = #{bno}
</select>
<!-- 댓글 작성 -->
<insert id="writeReply">
INSERT INTO MP_REPLY(
BNO
, RNO
, CONTENT
, WRITER
)
VALUES( #{bno}
, MP_REPLY_SEQ.NEXTVAL
, #{content}
, #{writer} )
</insert>
<!-- 댓글 수정 -->
<update id="updateReply" parameterType="kr.co.vo.ReplyVO">
UPDATE MP_REPLY SET CONTENT = #{content}
WHERE RNO = #{rno}
</update>
<!-- 댓글 삭제 -->
<delete id="deleteReply" parameterType="kr.co.vo.ReplyVO">
DELETE FROM MP_REPLY
WHERE RNO = #{rno}
</delete>
<!-- 선택된 댓글 조회 -->
<select id="selectReply" resultType="kr.co.vo.ReplyVO">
SELECT
BNO
, RNO
, CONTENT
, WRITER
, REGDATE
FROM MP_REPLY
WHERE RNO = #{rno}
</select>
</mapper>
'웹 프로젝트 > 웹 프로젝트' 카테고리의 다른 글
스터디 관리 사이트 (5) - 스터디 개설, 조회, 가입 기능 추가 (1) | 2020.06.28 |
---|---|
스터디 관리 사이트 프로젝트 (3) (0) | 2020.06.15 |
스터디 관리 사이트 프로젝트 (2) (0) | 2020.06.15 |
스터디 관리 사이트 제작 프로젝트 (1) (0) | 2020.06.15 |