TIL 0628

먼지·2024년 6월 28일

Today I Learned

목록 보기
87/89
post-thumbnail

resources > static > js

member.password.js

$(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;
		}
	});

});

kr.spring.member.vo

Member VO

수정할 부분 있음

@Pattern(regexp = "^[0-9a-zA-Z]+$")
private String captcha_chars;

resources > messages

validation Properties

Pattern.captcha_chars = 캡챠 문자는 필수
invalidCaptcha = 캡챠 문자 불일치

kr.spring.member.controller

Member Controller

캡챠 이미지 호출

변경사항 반영하기

	@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";
	}

WEB-INF > views > common

resultAlert.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<script>
    alert('${message}');
    location.href = '${url}';
</script>

webapp > sql

table.sql

게시판 테이블 생성

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;

kr.spring.board.vo

Board VO

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; // 좋아요 개수
}
 

kr.spring.board.dao

Board Mapper

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);
	// 글 좋아요
	
	// 댓글
	
	// 댓글 좋아요
	
	// 대댓글
}

Board Mapper.xml

글 작성하는 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>

kr.spring.board.service

Board Service

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);
}

Board Service Impl

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) {
		
	}
	
	
}

kr.spring.board.controller

Board Controller

게시판 등록 폼 호출

	@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";
	}
}

WEB-INF > tiles-def

board.xml

<?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>

kr.spring.config

AppConfig

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;
	}

kr.spring.util

File Util

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;
	}
}

resources > messages

validation.properties

# 게시판
NotBlank.title = 제목은 필수 항목입니다.
NotEmpty.content = 내용은 필수 항목입니다.
Size.category = 분류를 선택하세요.

resources

Application.yml

tomcat:
    max-http-form-post-size: 200MB #톰캣에서 허용하는 파일 업로드 사이즈
    
servlet:
    multipart:
      max-file-size: 50MB #파일 한 개당 최대 사이즈
      max-request-size: 200MB #요청당 최대 파일 크기

WEB-INF > views > board

boardList.jsp

<%@ 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 종료 -->

boardCSS.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<link rel="stylesheet" href="${pageContext.request.contextPath }/css/board.css">

boardWrite.jsp

<%@ 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>
<!-- 글 등록 종료 -->
profile
Lucky Things🍀

0개의 댓글