
$(function(){
/**
* 비밀번호 변경 체크
*/
$('#passwd').keyup(function(){
if($('#confirm_passwd').val()!=''&& $('#confirm_passwd').val() != $(this).val()){
$('#message_password').text('비밀번호 불일치').css('color', 'red');
} else if($('#confirm_passwd').val()!=''&& $('#confirm_passwd').val() == $(this).val()){
$('#message_password').text('비밀번호 일치').css('color', 'black');
}
});
$('#confirm_passwd').keyup(function(){
if($('#passwd').val()!=''&& $('#passwd').val() != $(this).val()){
$('#message_password').text('비밀번호 불일치').css('color', 'red');
} else if($('#passwd').val()!=''&& $('#passwd').val() == $(this).val()){
$('#message_password').text('비밀번호 일치').css('color', 'black');
}
});
$('#member_change').submit(function(){
if($('#now_passwd').val().trim()==''){
alert('현재 비밀번호를 입력하세요.');
$('#now_passwd').val('').focus();
return false;
}
if($('#passwd').val().trim()==''){
alert('새 비밀번호를 입력하세요.');
$('#passwd').val('').focus();
return false;
}
if($('#confirm_passwd').val().trim()==''){
alert('새 비밀번호 확인을 입력하세요.');
$('#confirm_passwd').val('').focus();
return false;
}
if($('#passwd').val() != $('#confirm_passwd').val()){
$('#message_password').text('비밀번호 불일치').css('color', 'red');
return false;
}
});
});
수정할 부분 있음
@Pattern(regexp = "^[0-9a-zA-Z]+$")
private String captcha_chars;
Pattern.captcha_chars = 캡챠 문자는 필수
invalidCaptcha = 캡챠 문자 불일치
변경사항 반영하기
@GetMapping("/member/getCaptcha")
public String getCaptcha(Model model, HttpSession session) {
// 내 정보
String clientId = "HNtvT0vO0xB9fEP5czzq";
String clientSecret = "853al0auHW";
String code = "0"; // 키 발급시 0, 캡챠 이미지 비교시 1로 세팅
String key_apiURL="https://openapi.naver.com/v1/captcha/nkey?code=" + code;
Map<String,String> requestHeaders= new HashMap<String, String>();
requestHeaders.put("X-Naver-Client-Id", clientId);
requestHeaders.put("X-Naver-Client-Secret", clientSecret);
String responseBody = CaptchaUtil.get(key_apiURL, requestHeaders);
log.debug("<< responseBody >> : " + responseBody);
JSONObject jObject = new JSONObject(responseBody);
try {
// key -> https://openapi.naver.com/v1/captcha/nkey 얘를 호출해서 받은 key 값을 의미
String key = jObject.getString("key");
// 캡챠 이미지와 동일하게 전송해야 하기 때문에 세션에 보관
session.setAttribute("captcha_key", key);
String apiURL = "https://openapi.naver.com/v1/captcha/ncaptcha.bin?key=" + key;
// byte 배열에 image 넣어주기 (불러온 정보들)
byte[] response_byte = CaptchaUtil.getCaptchaImage(apiURL, requestHeaders);
model.addAttribute("imageFile",response_byte);
model.addAttribute("filename","captcha.jpg");
} catch (Exception e) {
log.error(e.toString());
} finally {
}
return "imageView";
}
@PostMapping("/member/changePassword")
public String submitChangePassword(@Valid MemberVO memberVO,BindingResult result, HttpSession session, Model model, HttpServletRequest request) {
log.debug("<< 비밀번호 변경 처리 >> : " + memberVO);
// 유효성 체크 결과 오류 있다면 폼 호출하기
if(result.hasFieldErrors("now_passwd") || result.hasFieldErrors("passwd") || result.hasFieldErrors("captcha_chars")) {
return formChangePassword();
}
// 캡챠 문자 체크 시작
// 캡챠 이미지 비교시 1로 세팅
String code = "1";
// 캡챠 키 발급시 받은 키 값을 session에 저장했기 때문에 불러오기 가능
String key=(String)session.getAttribute("captcha_key");
// 사용자가 입력한 캡챠 이미지 글자값
String value = memberVO.getCaptcha_chars();
// code, key, value 값을 모두 넣어준다
String key_apiURL="https://openapi.naver.com/v1/captcha/nkey?code="+code+"&key="+ key+"&value="+value;
Map<String,String> requestHeaders= new HashMap<String, String>();
requestHeaders.put("X-Naver-Client-Id", "개인 코드 넣기");
requestHeaders.put("X-Naver-Client-Secret", "개인 코드 넣기");
// 값을 전달받기 // apiURL, requestHeaders의 값을 전달
String responseBody = CaptchaUtil.get(key_apiURL, requestHeaders);
log.debug("<< 캡챠 결과 >> : " + responseBody);
// json object로 변환 시켜준다
JSONObject jObject = new JSONObject(responseBody);
// 캡챠의 결과를 불리언 값으로 받는다
boolean captcha_result = jObject.getBoolean("result");
// 만약 일치하지 않았다면 invalidCaptcha 에러 코드를 생성한다.
if(!captcha_result) {
result.rejectValue("captcha_chars", "invalidCaptcha");
}
// 캡챠 문자 체크 종료
MemberVO user = (MemberVO)session.getAttribute("user");
memberVO.setMem_num(user.getMem_num());
MemberVO db_member = memberService.selectMember(memberVO.getMem_num());
// form 에서 전송한 현재 비번이랑 db에서 읽어온 비번이랑 일치 여부 체크하기
if(!db_member.getPasswd().equals(memberVO.getNow_passwd())) {
result.rejectValue("now_passwd", "invalidPassword");
return formChangePassword();
}
// 비밀번호 수정
memberService.updatePassword(memberVO);
// 설정되어있는 자동 로그인 기능 해제한다()
memberService.deleteAu_id(memberVO.getMem_num());
// UI 문구 처리
model.addAttribute("message","비밀번호 변경이 완료되었습니다. 재접속시 설정되어있는 자동로그인 기능이 해제됩니다.");
model.addAttribute("url", request.getContextPath()+"/member/myPage");
return "common/resultAlert";
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<script>
alert('${message}');
location.href = '${url}';
</script>
게시판 테이블 생성
create table spboard(
board_num number not null,
category char(1) not null,
title varchar2(90) not null,
content clob not null,
hit number(8) default 0 not null,
reg_date date default sysdate not null,
modify_date date,
filename varchar2(400),
ip varchar2(40) not null,
mem_num number not null,
constraint spboard_pk primary key (board_num),
constraint spboard_fk foreign key (mem_num) references spmember(mem_num)
);
create sequence spboard_seq;
package kr.spring.board.vo;
import java.sql.Date;
import org.springframework.web.multipart.MultipartFile;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class BoardVO {
private Long board_num;
private String category;
private String title;
private String content;
private int hit;
private Date reg_date;
private Date modify_date;
private String filename;
private String ip;
private Long mem_num;
private MultipartFile upload;
// 조인을 이용해서 가져올 데이터
private String id;
private String nick_name;
private int re_cnt; // 댓글 개수
private int fav_cnt; // 좋아요 개수
}
package kr.spring.board.dao;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import kr.spring.board.vo.BoardVO;
@Mapper
public interface BoardMapper {
// 글
public List<BoardVO> selectList(Map<String, Object> map);
public Integer selectRowCount(Map<String, Object> map); // integer, int 상관 없음
public void insertBoard(BoardVO board);
public BoardVO selectBoard(Long board_num);
public void updateHit(Long board_num);
public void updateBoard(BoardVO board);
public void deleteBoard(Long board_num);
public void deleteFile(Long board_num);
// 글 좋아요
// 댓글
// 댓글 좋아요
// 대댓글
}
글 작성하는 SQL 작성하기
<!-- interface명칭과 xml의 명칭이 동일해야 한다 -->
<mapper namespace="kr.spring.board.dao.BoardMapper">
<!-- 글 작성하기 -->
<insert id="insertBoard" parameterType="boardVO">
INSERT INTO
spboard(board_num, category,title,content,reg_date, filename,ip,mem_num)
VALUES(spboard_seq.nextval,#{category},#{title},#{content},SYSDATE,#{filename},#{ip},#{mem_num})
</insert>
</mapper>
package kr.spring.board.service;
import java.util.List;
import java.util.Map;
import kr.spring.board.vo.BoardVO;
public interface BoardService {
// 글
public List<BoardVO> selectList(Map<String, Object> map);
public Integer selectRowCount(Map<String, Object> map);
public void insertBoard(BoardVO board);
public BoardVO selectBoard(Long board_num);
public void updateHit(Long board_num);
public void updateBoard(BoardVO board);
public void deleteBoard(Long board_num);
public void deleteFile(Long board_num);
}
package kr.spring.board.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kr.spring.board.dao.BoardMapper;
import kr.spring.board.vo.BoardVO;
@Service
@Transactional
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardMapper boardMapper;
@Override
public List<BoardVO> selectList(Map<String, Object> map) {
return null;
}
@Override
public Integer selectRowCount(Map<String, Object> map) {
return null;
}
@Override
public void insertBoard(BoardVO board) {
}
@Override
public BoardVO selectBoard(Long board_num) {
return null;
}
@Override
public void updateHit(Long board_num) {
}
@Override
public void updateBoard(BoardVO board) {
}
@Override
public void deleteBoard(Long board_num) {
}
@Override
public void deleteFile(Long board_num) {
}
}
@GetMapping("/board/write")
public String form() {
return "boardWrite";
}
@PostMapping("/board/write")
public String submitInsert(@Valid BoardVO boardVO, BindingResult result, HttpSession session, Model model,
HttpServletRequest request) throws IllegalStateException, IOException {
log.debug("<< 게시판 글 저장 >> : " + boardVO);
if(result.hasErrors()) {
return form();
}
// 회원 번호 세팅하기
MemberVO user = (MemberVO)session.getAttribute("user");
boardVO.setMem_num(user.getMem_num());
// IP 세팅하기
boardVO.setIp(request.getRemoteAddr());
// 파일 업로드하기
boardVO.setFilename(FileUtil.createFile(request, boardVO.getUpload()));
boardService.insertBoard(boardVO);
// UI 문구 처리
model.addAttribute("accessTitle","글 작성");
model.addAttribute("accessMsg","글 작성이 완료되었습니다");
model.addAttribute("accessBtn","목록으로");
model.addAttribute("accessUrl", request.getContextPath()+"/board/list");
return "common/resultView";
@GetMapping("/board/list")
public String getList(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "1") int order, @RequestParam(defaultValue = "") String category,
String keyfield, String keyword, Model model) {
log.debug("<< 게시판 목록 - category >> : " + category);
log.debug("<< 게시판 목록 - order >> : " + order);
return "boardList";
}
package kr.spring.board.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import kr.spring.board.service.BoardService;
import kr.spring.board.vo.BoardVO;
import kr.spring.member.vo.MemberVO;
import kr.spring.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class BoardController {
@Autowired
public BoardService boardService;
// 자바빈 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
/* ==============
* 게시판 글 작성
* ==============
* */
// 게시판 등록 폼 호출
@GetMapping("/board/write")
public String form() {
return "boardWrite";
}
// 게시판 등록하기
@PostMapping("/board/write")
public String submitInsert(@Valid BoardVO boardVO, BindingResult result, HttpSession session, Model model,
HttpServletRequest request) throws IllegalStateException, IOException {
log.debug("<< 게시판 글 저장 >> : " + boardVO);
if(result.hasErrors()) {
return form();
}
// 회원 번호 세팅하기
MemberVO user = (MemberVO)session.getAttribute("user");
boardVO.setMem_num(user.getMem_num());
// IP 세팅하기
boardVO.setIp(request.getRemoteAddr());
// 파일 업로드하기
boardVO.setFilename(FileUtil.createFile(request, boardVO.getUpload()));
boardService.insertBoard(boardVO);
// UI 문구 처리
model.addAttribute("accessTitle","글 작성");
model.addAttribute("accessMsg","글 작성이 완료되었습니다");
model.addAttribute("accessBtn","목록으로");
model.addAttribute("accessUrl", request.getContextPath()+"/board/list");
return "common/resultView";
}
/* =============
* 게시판 목록
* =============
* */
@GetMapping("/board/list")
public String getList(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "1") int order, @RequestParam(defaultValue = "") String category,
String keyfield, String keyword, Model model) {
log.debug("<< 게시판 목록 - category >> : " + category);
log.debug("<< 게시판 목록 - order >> : " + order);
return "boardList";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!-- Board List -->
<definition name="boardList" extends="main">
<put-attribute name="title" value="Board List"/>
<put-attribute name="css" value="/WEB-INF/views/board/boardCSS.jsp" />
<put-attribute name="body" value="/WEB-INF/views/board/boardList.jsp" />
</definition>
</tiles-definitions>
Board XML 설정 추가해주기
@Bean
public TilesConfigurer tilesConfigurer() {
final TilesConfigurer configurer = new TilesConfigurer();
// XML 설정 파일 경로 지정 -> 배열이기 때문에 다양한 파일 지정 가능함
configurer.setDefinitions(new String[] {
"/WEB-INF/tiles-def/main.xml",
"/WEB-INF/tiles-def/member.xml",
"/WEB-INF/tiles-def/board.xml"
});
configurer.setCheckRefresh(true);
return configurer;
}
package kr.spring.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FileUtil {
// 업로드 상대 경로 명시하기
private static final String UPLOAD_PATH = "/upload";
// 파일 업로드 처리하기
public static String createFile(HttpServletRequest request, MultipartFile file) throws IllegalStateException, IOException{
// 컨텍스트 루트상의 절대 경로 구하기
String path = request.getServletContext().getRealPath(UPLOAD_PATH);
String filename = null;
if(file != null && !file.isEmpty()) {
// 파일 명이 중복되지 않도록 파일명 변경하기
// 원래 파일명을 보존하지 않는 경우
// . 까지 포함된 확장자를 가져오는것임
filename = UUID.randomUUID()+file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
// 원래 파일명을 보존하는 경우
//filename = UUID.randomUUID()+"_"+file.getOriginalFilename();
file.transferTo(new File(path + "/" + filename));
}
return filename;
}
// 파일 삭제하기
public static void removeFile(HttpServletRequest request, String filename){
if(filename != null) {
// 컨텍스트 루트상의 절대 경로 구하기
String path = request.getServletContext().getRealPath(UPLOAD_PATH);
File file = new File(path + "/" + filename);
if(file.exists()) file.delete();
}
}
// 지정한 경로의 파일을 읽어들여 byte 배열로 변환해준다
public static byte[] getBytes(String path) {
FileInputStream fis = null;
byte[] readbyte = null;
try {
fis = new FileInputStream(path);
readbyte = new byte[fis.available()];
fis.read(readbyte);
} catch (Exception e) {
log.error(e.toString());
} finally {
if(fis != null) try {fis.close();}catch (IOException e) {}
}
return readbyte;
}
}
# 게시판
NotBlank.title = 제목은 필수 항목입니다.
NotEmpty.content = 내용은 필수 항목입니다.
Size.category = 분류를 선택하세요.
tomcat:
max-http-form-post-size: 200MB #톰캣에서 허용하는 파일 업로드 사이즈
servlet:
multipart:
max-file-size: 50MB #파일 한 개당 최대 사이즈
max-request-size: 200MB #요청당 최대 파일 크기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<!-- Board List 시작 -->
<div class="page-main">
<h2>게시판 목록</h2>
<div class="align-right">
<c:if test="${!empty user}">
<input type="button" value="게시글 등록" onclick="location.href='write'">
</c:if>
</div>
</div>
<!-- Board List 종료 -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<link rel="stylesheet" href="${pageContext.request.contextPath }/css/board.css">
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 글 등록 시작 -->
<div class="page-main">
<h2>글쓰기</h2>
<form:form action="write" id="board_register" enctype="multipart/form-data" modelAttribute="boardVO">
<ul>
<li>
<form:label path="category">분류</form:label>
<form:select path="category">
<option disabled="disabled">선택하세요</option>
<form:option value="1">자바</form:option>
<form:option value="2">데이터베이스</form:option>
<form:option value="3">자바스크립트</form:option>
<form:option value="4">기타</form:option>
</form:select>
<form:errors element="div" path="category" cssClass="error-color-reg"/>
</li>
<li>
<form:label path="title">제목</form:label>
<form:input path="title" />
<form:errors element="div" path="title" cssClass="error-color-reg"/>
</li>
<li>
<form:label path="content">내용</form:label>
<form:textarea path="content"/>
<form:errors element="div" path="content" cssClass="error-color-reg"/>
</li>
<li>
<form:label path="upload">파일 등록</form:label>
<input type="file" id="upload" name="upload">
<form:errors element="div" path="upload" cssClass="error-color-reg"/>
</li>
</ul>
<div class="align-center">
<form:button class="default-btn">전송</form:button>
<input type="button" value="목록" class="default-btn" onclick="location.href='list'">
</div>
</form:form>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.7.1.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/member.register.js"></script>
</div>
<!-- 글 등록 종료 -->