본문 바로가기
스프링(Spring)/게시판 만들기

[Spring 게시판] 등록(첨부파일, Validation)

by una⭐ 2020. 3. 12.

1. Controller(흐름제어)

 

BoardController

 

@Controller
public class BoardController {

    // 게시글 작성 폼
    @RequestMapping(value = "/write", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String write(Model model, String current) throws Exception {

    logger.debug("Controller: write() 입니다.");
    model.addAttribute("vo", new BoardVO());
    model.addAttribute("current", current);

    return "write";
    }

    // 게시글 작성 처리
    @RequestMapping(value = "/writeProc2", method = RequestMethod.POST)
    public ModelAndView writeProc2(@ModelAttribute("vo")BoardVO vo, BindingResult bindingResult, String current, MultipartHttpServletRequest request) throws Exception {

    logger.debug("Controller: writeProc2() 입니다.");

    Criteria cri = new Criteria();
    ModelAndView mv = new ModelAndView();
    int intValue = 0;

    WriteValidator validator = new WriteValidator();
    validator.validate(vo,bindingResult);

    if (bindingResult.hasErrors()) {
    System.out.println("hasErrors");

    List<FieldError> errors = bindingResult.getFieldErrors();

    for (FieldError error : errors ) {
    System.out.println (error.getField() + " 는 " + error.getDefaultMessage());

    if(error.getDefaultMessage().toString().equals("제목을 입력해주세요.")) {
    System.out.println(error.getDefaultMessage());
    intValue = 1;
    break;
    } else if(error.getDefaultMessage().toString().equals("내용을 입력해주세요.")) {
    System.out.println(error.getDefaultMessage());
    intValue = 2; 
    break;
    }
    else if(error.getDefaultMessage().toString().equals("제목의 첫글자는 영문 또는 한글이어야합니다.")) {
    System.out.println(error.getDefaultMessage());
    intValue = 3; 
    break;
    }
    else if(error.getDefaultMessage().toString().equals("제목의 길이는 200자를 넘을 수 없습니다.")) {
    intValue = 4;
    break;
    }else if(error.getDefaultMessage().toString().equals("내용의 길이는 300자를 넘을 수 없습니다.")) {
    intValue = 5; 
    break;
    }
    }

    }else {
    int titlego = boardService.checkTitle(vo);	

    //댓글허용 체크박스 체크 안할경우 값 set해주기
    if(vo.getComment_yn() == null || vo.getComment_yn().equals("")) {
    vo.setComment_yn("y");
    }

    if(titlego > 0) { //중복시 		//기존 : titlego == 1
    intValue = 6;
    }else if(titlego == 0){
    vo.setCategory(current);
    intValue = boardService.writeProc(vo);		//성공 시 intValue는 7
    }
    }

    mv.setViewName("write");
    mv.addObject("write", vo);
    mv.addObject("current", current);
    mv.addObject("perPageNum", cri.getPerPageNum());
    mv.addObject("intValue", intValue);

    return mv;
    }
    
    //파일 insert
    @ResponseBody
    @RequestMapping(value = "/insertFile", produces="application/json")
    public void insertFile(FileVO fileVO, MultipartHttpServletRequest request) throws Exception {
    logger.debug("Controller: insertFile() 입니다.");
    logger.debug(fileVO.toString());
    System.out.println(String.valueOf(fileVO.getBseq()));
    boardService.insertFile(fileVO);
    }
    
    
  
}

 

2. Service(비즈니스 로직, DB 연동 이외의 작업)

 

BoardService

 

public interface BoardService {
	
	//게시글 작성
	public int writeProc(BoardVO vo) throws Exception;
    
    	//게시글 등록 중복체크
	public int checkTitle(BoardVO vo) throws Exception;
    
    	//파일 저장
	public FileVO saveFile(MultipartFile multipartFile, FileVO fileVO) throws Exception;

	//file data insert
	public void insertFile(FileVO fileVO) throws Exception;
    
}

 

BoardServiceImpl

 

@Service("BoardService")
public class BoardServiceImpl implements BoardService {

	@Autowired
	BoardDao boardDao;
	
	@Autowired
	@Qualifier("fileUtils")
    private FileUtils fileUtils;

	//게시글 목록, 검색, 페이징
	@Override
	public List<BoardVO> listAllSearch(Criteria cri) throws Exception {
		// TODO Auto-generated method stub
		
		System.out.println("Service: listAll(option, keyword) 입니다.");
	      
	    return boardDao.listAllSearch(cri);
	}
	
	//페이징 전체 목록 개수 
	@Override
	public int countPostListSearch(Criteria cri) throws Exception {
		// TODO Auto-generated method stub
		    
		return boardDao.countPostListSearch(cri);
	}
	
	//게시글 작성
	@Override
	public int writeProc(BoardVO vo) throws Exception { //, HttpServletRequest request
		// TODO Auto-generated method stub
		
		int boardSeq = 0;
		
		FileVO fileVO = new FileVO();
		
		//파일이 있다면 서버에 저장
		if(vo.getP_file().getSize() != 0){
			System.out.println("BoardServiceImple====saveFile");
			fileVO = saveFile(vo.getP_file(), fileVO);
			
			//db에 insert할 originalFileName을 boardVO에 set
			//vo.setFile(vo.getP_file().getOriginalFilename());		
		}else{
			vo.setFile(null);	
		}
		
		System.out.println("BoardServiceImple=============originalFileName : "+ fileVO.getP_file());
		
		//게시글 insert
		boardDao.writeProcInsert(vo);
		
		// 방금 작성한 글의 번호 출력 : keyProperty 속성
		boardSeq = vo.getBoardSeq();
		System.out.println("BoardServiceImple=============boardSeq : "+ boardSeq);
		
		//file 정보 insert
		if(fileVO.getF_path() != "") {		//파일 저장한 적 있으면, 
			fileVO.setBseq(boardSeq);
			boardDao.insertFile(fileVO);
		}
		
		return 7;		//return 필요 없음. 
	}
    
    //파일첨부 정보 출력 
	private void testFile(FileVO fileVO) {
		/*MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest)request;
		Iterator<String> iterator = multipartHttpServletRequest.getFileNames();
		MultipartFile multipartFile = null;
		
		while(iterator.hasNext()){ 
			multipartFile = multipartHttpServletRequest.getFile(iterator.next()); 
			if(fileVO. == false){ */
				System.out.println("------------- file start -------------"); 
				System.out.println("bSeq : "+String.valueOf(fileVO.getBseq())); 
				System.out.println("name : "+fileVO.getP_file()); 
				//System.out.println("filename : "+multipartFile.getOriginalFilename()); 
				System.out.println("storedFileName : "+fileVO.getF_stored_file_name()); 
				System.out.println("size : "+fileVO.getF_size()); 
				System.out.println("-------------- file end --------------\n"); 
			/*} 
		}*/
	}
    
    //게시글 등록 중복체크
	@Override
	public int checkTitle(BoardVO vo) throws Exception {
		int result = boardDao.checkTitle(vo);
		return result;
	}
    
    //첨부파일 저장
	@Override
	public FileVO saveFile(MultipartFile multipartFile, FileVO fileVO) throws Exception{
		
		FileVO fileList = fileUtils.parseInsertFileInfo(multipartFile, fileVO); 
		
		return fileList;
	}
	
	//file insert
	@Override
	public void insertFile(FileVO fileVO) throws Exception{

			//첨부파일 정보 출력
			testFile(fileVO);
			//첨부파일 정보 db에 insert
			boardDao.insertFile(fileVO); 
	}
    
}    

 

 

3. Model(비즈니스 로직, DB 연동 작업)

 

FileVO(파일 데이터 저장 클래스)

 

package com.ncomz.sample.dto;

public class FileVO {
	private int f_seq;
	private String b_seq; //b_seq. 파일 업로드에 사용
	private int bseq; //table insert용
	private String f_stored_file_name="";
	private String p_file=""; //original file name
	private long f_size;
	private String f_path="";
	public int getF_seq() {
		return f_seq;
	}
	public void setF_seq(int f_seq) {
		this.f_seq = f_seq;
	}
	public String getB_seq() {
		return b_seq;
	}
	public void setB_seq(String b_seq) {
		this.b_seq = b_seq;
	}
	public String getF_stored_file_name() {
		return f_stored_file_name;
	}
	public void setF_stored_file_name(String f_stored_file_name) {
		this.f_stored_file_name = f_stored_file_name;
	}
	public String getP_file() {
		return p_file;
	}
	public void setP_file(String p_file) {
		this.p_file = p_file;
	}
	public long getF_size() {
		return f_size;
	}
	public void setF_size(long f_size) {
		this.f_size = f_size;
	}
	public String getF_path() {
		return f_path;
	}
	public void setF_path(String f_path) {
		this.f_path = f_path;
	}
	public int getBseq() {
		return bseq;
	}
	public void setBseq(int bseq) {
		this.bseq = bseq;
	}
	@Override
	public String toString() {
		return "FileVO [f_seq=" + f_seq + ", b_seq=" + b_seq + ", bseq=" + bseq + ", f_stored_file_name="
				+ f_stored_file_name + ", p_file=" + p_file + ", f_size=" + f_size + ", f_path=" + f_path + "]";
	}

}

 

BoardDao

 

public interface BoardDao {

	public int writeProc(BoardVO vo) throws Exception;
	public void writeProcInsert(BoardVO vo) throws Exception;
    public int checkTitle(BoardVO vo) throws Exception;
	
	public void insertFile(FileVO fileVO) throws Exception;
	public void updateFile(FileVO fileVO) throws Exception;
 
 }

 

BoardDaoImpl

 

@Repository
public class BoardDaoImpl implements BoardDao {

	SqlSession sqlSession;
	
	//게시글 작성
	@Override
	public int writeProc(BoardVO vo) throws Exception {
		// TODO Auto-generated method stub
		return sqlSession.selectOne("com.ncomz.sample.dao.BoardDao.writeProc", vo);
	}
	
	@Override
	public void writeProcInsert(BoardVO vo) throws Exception {
		// TODO Auto-generated method stub

		sqlSession.insert("com.ncomz.sample.dao.BoardDao.writeProcInsert", vo);
	}
	
	//게시글 작성- 첨부파일 db등록
	@Override
	public void insertFile(FileVO fileVO) throws Exception {
		sqlSession.insert("com.ncomz.sample.dao.BoardDao.insertFile", fileVO);
	}
    
    //게시글 등록 중복체크
	@Override
	public int checkTitle(BoardVO vo) throws Exception {
		 int result = sqlSession.selectOne("checkTitle", vo); 
		 return result; 
	}
    
}

 

 

BoardDao.xml(SQL문)

 

<?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="com.ncomz.sample.dao.BoardDao">

	<!-- 게시글 작성 -->
   <select id="writeProc" resultType="int" parameterType="com.ncomz.sample.dto.BoardVO">
      SELECT
         count(*) AS count
      FROM
         TB_JMJ_BOARD
      WHERE
         title = #{title}
   </select>
   
   <insert id="writeProcInsert"  parameterType="com.ncomz.sample.dto.BoardVO" useGeneratedKeys="true" keyProperty="boardSeq">
      INSERT INTO TB_JMJ_BOARD(
         title,
         content,
         category,
         register_date,
         hit,
         comment_yn,
         delete_yn,
         userSeq
      ) VALUES (
         #{title},
         #{content},
         #{category},
         now(),
         0,
         #{comment_yn},
         'n',
         #{userSeq}
      )
   </insert>
   
   <!-- 게시글 등록-첨부파일 등록 -->
   <insert id="insertFile">
      INSERT INTO TB_JMJ_FILE(
         boardSeq,
         NAME,
         SIZE,
         PATH,
         STORED_FILE_NAME
      ) VALUES (
         #{bseq},
         #{p_file},
         #{f_size},
         #{f_path},
         #{f_stored_file_name}
      )
   </insert>
   
   <!-- 게시글 등록 중복체크 -->
   <select id="checkTitle" resultType="int">
   
      SELECT 
         COUNT(*) 
      FROM 
         TB_JMJ_BOARD
      WHERE 
         title = #{title} 
         
   </select>

</mapper>

 

4. View(화면)

 

Write.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 prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%
	request.setCharacterEncoding("utf-8");
	response.setContentType("text/html;charset=UTF-8");

	int sessionSeq = (Integer) session.getAttribute("sessionSeq");
	String sessionName = (String) session.getAttribute("sessionName");
%>
<html>
<head>
<title>목록</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<script
	src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
<script type="text/javascript">
	$(document).ready(function() {

		//탭 메뉴 색 변경
		if ('${current}' == 'dance') {

			$('#currentDashboard').removeClass('current');

			$('#currentDance').css({

				"background-color" : "#2baae1",
				"color" : "#222"

			});

		} else if ('${current}' == 'movie') {

			$('#currentDashboard').removeClass('current');

			$('#currentMovie').css({

				"background-color" : "#2baae1",
				"color" : "#222"

			});

		}
		
		if ('${intValue}' == '1') {
			alert("제목은 필수입니다.");
			return;
		} else if ('${intValue}' == '2') {
			alert("내용은 필수입니다.");
			return;
		} else if ('${intValue}' == '3') {
			alert("제목의 첫글자는 영문 또는 한글이어야합니다.");
			return;
		} else if ('${intValue}' == '4') {
			alert("제목의 길이는 200자를 넘을 수 없습니다.");
			return;
		} else if ('${intValue}' == '5') {
			alert("내용의 길이는 300자를 넘을 수 없습니다.");
			return;
		} else if ('${intValue}' == '6') {
			alert("제목이 중복되었습니다.");
			return;
		} else if ('${intValue}' == '7') {
			alert("등록되었습니다.");
			location.href = "list?current=${current}";
		}

	});

	function CK() {

		var checkComment = document.getElementById("checkComment").checked;

		if (checkComment) {

			document.getElementById("comment_yn").value = 'y';

		} else {

			document.getElementById("comment_yn").value = 'n';

		}

	}
	
</script>
<style type="text/css">
	.container {
		padding-top: 10px;
	}
	
	table {
		font-family: "Lato", "sans-serif";
		font-size: 12px;
		width: 60%;
		border-collapse: collapse;
		margin-top: 10px;
		table-layout: fixed;
	}
	
	th {
		width: 100px;
		height: 25px;
		text-align: center;
		background-color: #2baae1;
		color: white;
	}
	
	td {
		height: 25px;
	}
	
	textarea {
		width: 100%;
	}
	
	input[type="text"], input[type="password"] {
		background-color: transparent;
		border: 0 solid black;
		text-align: center;
	}
</style>
</head>
<body>

<jsp:include page="/WEB-INF/views/header.jsp" flush="false" />

<form:form method="post" modelAttribute="vo" name="writeProc" id="writeProc" action="writeProc2" enctype="multipart/form-data">
	<div align="center" class="container">
		<table border="1">
			<tr>
				<th>* 제목 </th>
				<td>
					<form:input class="inputText" name="title" id="title" placeholder="제목을 입력하세요." value="${write.title}" path="title" maxlength="100" />
				</td>
				<th>댓글 사용 여부</th>
				<td>
					<input type="hidden" name="comment_yn" id="comment_yn" />
					<input type="checkbox" name="checkComment" id="checkComment" checked onclick="CK()" />
				</td>
			</tr>
			<tr>
				<th>* 작성자</th>
				<td>
					<%=sessionName%>  
					<input type="hidden" name="userSeq" id="userSeq" value="<%=sessionSeq%>">
				</td>
				<th>등록일</th>
				<td>
					<jsp:useBean id="now" class="java.util.Date" /> 
					<fmt:formatDate value="${now}" pattern="yyyy/MM/dd a hh:mm:ss" var="today" /> 
					<c:out value="${today}" />
				</td>
			</tr>
			<tr>
				<td colspan="4">
					<form:textarea name="content" id="content" rows="12" cols="80" placeholder="* 내용을 입력하세요. " path="content"></form:textarea>
			</tr>
			<tr>
				<th>첨부파일</th>
				<td colspan="3">
					<!-- 첨부파일 객체 추가, 처리 추가 --> 
					<input type="file" name="p_file" id="p_file" placeholder="첨부파일" accept=".gif, .jpeg, .jpg, .png">
					<input type="hidden" name="checkTitle" id="checkTitle" />
				</td>
		</table>
	</div>
	<table align="center">
		<tr align="right">
			<td>
				<input type="submit" value="확인" /> 
				<input type="hidden" name="current" value="${current}"> 
				<input type="button" value="취소" onclick="location.href='/list?current=${current}'">
			</td>
		</tr>
	</table>
</form:form>

</body>
</html>

 

5. Validation

 

WriteValidator

 

public class WriteValidator implements Validator {

	@Override
	public boolean supports(Class<?> arg0) {
		// 검증할 객체의 클래스 타입 정보
		return BoardVO.class.isAssignableFrom(arg0);
	}

	@Override
	public void validate(Object obj, Errors errors) {
		
		BoardVO vo = (BoardVO)obj;
		
		String inputTitle = vo.getTitle();
		if(inputTitle == null || inputTitle.trim().isEmpty() ) {
        
			ValidationUtils.rejectIfEmpty(errors, "title", "titleRequired", "제목을 입력해주세요.");
			
		}else {
			
			//첫글자 영문, 한글 체크
			boolean Eng = false;
			
			final char firstChar = inputTitle.charAt(0);
			
			//유니코드 값으로 돌려줌
			final int firstCharCode = inputTitle.codePointAt(0);
			
			if (isEng(firstChar) == true || iskor(firstCharCode) == true ) {
				
				 	Eng = true;
				 	
			} else {
				
				errors.rejectValue("title", "titleFirstCharisEngORKor" , "제목의 첫글자는 영문 또는 한글이어야합니다.");
				
			}
			
		}
		if(inputTitle.length() > 200) {
			
			//제목 길이 체크
			errors.rejectValue("title", "titleLengthMax", "제목의 길이는 200자를 넘을 수 없습니다.");
			
		}

		String inputContent = vo.getContent();
		if(inputContent == null || inputContent.trim().isEmpty() ) {
			
			//내용 null 체크
			ValidationUtils.rejectIfEmpty(errors, "content", "contentRequired","내용을 입력해주세요.");
			
		}
		if(inputContent.length() > 300) {
			
			//내용 길이 체크
			errors.rejectValue("content", "contentLengthMax", "내용의 길이는 300자를 넘을 수 없습니다.");
			
		}
		
	}//validate
	
	public static boolean isEng(char firstChar) {
		
		return (firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z');
		
	}
	
	 public static boolean iskor(int firstCharCode) {
		
		 return (firstCharCode > 0x3130 && firstCharCode < 0x318F) || (firstCharCode>= 0xAC00 && firstCharCode <= 0xD7A3) ;		 
		 
	 }
	 	
}

 

6. 환경설정

 

root-context.xml

 

<!-- 첨부파일 - MultipartResolver 설정 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <property name="maxUploadSize" value="100000000" />
  <property name="maxInMemorySize" value="100000000" />
  <property name="defaultEncoding" value="utf-8" />
</bean>

 

pom.xml

 

<!-- 첨부파일 업로드 -->
<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.0.1</version>
</dependency>
<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.2.2</version>
</dependency> 

 

web.xml

 

<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.PNG</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.JPEG</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.jpeg</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.JPG</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.GIF</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.gif</url-pattern>
</servlet-mapping>

댓글