반응형

페이스북이나 인스타그램 같은 간단한 Ajax와 파일 업로드 기능을 추가하여 

간단한 게시판을 만들어 보자 😁😁

 

순서

1. 테이블제작

2. VO객체 제작

3. 인터페이스 제작

4. 서비스 제작

5. 매퍼제작 

6. 마이바티스 스캔설정

7.mapper.xml 제작

8. 컨트롤러 제작

9. jsp 만들기

 

위 순서는 바뀌어도 된다. 내가 편하기 때문에 이순서를 지양한다.


1. 테이블 제작

 

우선 오라클을 사용하기 때문에 sqld로 테이블을 제작해준다.

 

CREATE TABLE snsboard(
    bno NUMBER(10,0) PRIMARY KEY,
    writer VARCHAR2(50) NOT NULL,
    uploadpath VARCHAR2(100) NOT NULL,
    fileloca VARCHAR2(100) NOT NULL,
    filename VARCHAR2(50) NOT NULL,
    filerealname VARCHAR2(50) NOT NULL,
    content VARCHAR2(2000),
    regdate DATE DEFAULT sysdate
);

CREATE SEQUENCE snsboard_seq
    START WITH 1
    INCREMENT BY 1
    MAXVALUE 1000
    NOCYCLE
    NOCACHE;

bno은 게시물의 번호로로 시퀀스 처리를 해준다. 

 


VO(모델) 제작

 

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SnsBoardVO {

	private int bno;
	private String writer;
	private String uploadpath;
	private String fileloca;
	private String filename;
	private String filerealname;
	private String content;
	private Timestamp regdate;
	
}

lombok을 이용하여 간단하게 구현한다.

 

@NoArgsConstructor 파라미터가 없는 기본 생성자를 생성
@AllArgsConstructor 모든 필드 값을 파라미터로 받는 생성자를 만듦

🤓 참고해 주자 🤓

 

인터페이스 제작(기능)

 

package com.spring.myweb.snsboard.service;

import java.util.List;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.util.PageVO;

public interface ISnsBoardService {

	//등록
	void insert(SnsBoardVO vo);
	
	//목록
	List<SnsBoardVO> getList(PageVO paging);
	
	//상세
	SnsBoardVO getDetail(int bno);
	
	//삭제
	void delete(int bno);

	
}

 

간단하게 등록 목록 상세 삭제 정도로 만들어 주려고한다.

이제 인터페이스를 활용하게 기능이 들어간 클래스를 만들어 준다.

 

서비스 제작(기능)

 

package com.spring.myweb.snsboard.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.snsboard.mapper.ISnsBoardMapper;
import com.spring.myweb.util.PageVO;

@Service
public class SnsBoardService implements ISnsBoardService {

	@Autowired
	private ISnsBoardMapper mapper;
	
	@Override
	public void insert(SnsBoardVO vo) {
		mapper.insert(vo);
	}

	@Override
	public List<SnsBoardVO> getList(PageVO paging) {
		return mapper.getList(paging);
	}

	@Override
	public SnsBoardVO getDetail(int bno) {
		// TODO Auto-generated method stub
		return mapper.getDetail(bno);
	}

	@Override
	public void delete(int bno) {
		mapper.delete(bno);

	}

}

이렇게 리턴 값들과 나중에 Mapper에게 줄 값들을 정리해준다.

이때 아노테이션을 빼먹으면 오류가 나니 확인 해주자

 

 

Mapper 제작 (쿼리문)

 

 

package com.spring.myweb.snsboard.mapper;

import java.util.List;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.util.PageVO;

public interface ISnsBoardMapper {
	
	//등록
	void insert(SnsBoardVO vo);
	
	//목록
	List<SnsBoardVO> getList(PageVO paging);
	
	//상세
	SnsBoardVO getDetail(int bno);
	
	//삭제
	void delete(int bno);
	
}

인터페이스와 같은 형식으로 진행해 주어도 무리 없을듯 하다. 

 

Mapper.xml  (쿼리문)

 

매퍼를 만들기 전에 꼭 마이바티스 스캔 설정을 해주어야한다. 

 

<!-- 위의 구현체 xml을 빈으로 등록하기 위해서는 타입이 필요하니까
		인터페이스를 자동으로 스캔해서 빈으로 등록하기 위한 설정.
	 -->
	<mybatis-spring:scan base-package="com.spring.myweb.freeboard.mapper"/>
	<mybatis-spring:scan base-package="com.spring.myweb.reply.mapper"/> 
	<mybatis-spring:scan base-package="com.spring.myweb.user.mapper"/>
	<mybatis-spring:scan base-package="com.spring.myweb.snsboard.mapper"/>

 

<?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.spring.myweb.snsboard.mapper.ISnsBoardMapper">

	<!-- 등록하기 -->
	<insert id="insert">
		INSERT INTO snsboard
		(bno, writer, uploadpath, fileloca, filename, filerealname, content)
		VALUES
		(snsboard_seq.NEXTVAL, #{writer}, #{uploadpath}, #{fileloca}, #{filename}, #{filerealname}, #{content})
	</insert>
	
	<!-- 전체 목록 

	<select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
		SELECT * FROM snsboard
		ORDER BY bno DESC
	</select>
	 -->
	
	<!-- 상세보기 -->
	<select id="getDetail" resultType="com.spring.myweb.command.SnsBoardVO">
		select * from snsboard
		where bno = #{bno}
	</select>
	
	<!-- 삭제 -->
	<delete id="delete">
		delete from snsboard
		where bno = #{bno}
	</delete>
	
	<!-- 페이징 전체목록 -->
<!-- 전체 목록 -->
   <select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
      
      SELECT * FROM
         (
         SELECT ROWNUM AS rn, tbl.* FROM
            (
            SELECT * FROM snsboard
            ORDER BY bno DESC
            ) tbl
         )
      <![CDATA[
      WHERE rn > (#{pageNum} - 1) * #{countPerPage}
      AND rn <= #{pageNum} * #{countPerPage}
      ]]>
      
   </select>
</mapper>

원래 만들고 junit으로 테스트를 해주어야하지만 생략하려고 한다.

 

 

컨트롤러 제작

 

 

package com.spring.myweb.controller;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.spring.myweb.command.SnsBoardVO;
import com.spring.myweb.command.UserVO;
import com.spring.myweb.snsboard.service.ISnsBoardService;
import com.spring.myweb.util.PageVO;

@Controller
@RequestMapping("/snsBoard")
public class SnsBoardController {
	
	@Autowired
	private ISnsBoardService service;
	
	@GetMapping("/snsList")
	public void snsList() {}
	
	@PostMapping("/upload")
	@ResponseBody
	public String upload(MultipartFile file, String content, 
						 HttpSession session) {
		
		try {
			
			String writer = ((UserVO)session.getAttribute("login")).getUserId();
			
			//날짜별로 폴더를 생성해서 파일을 관리
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
			Date date = new Date();
			String fileLoca = sdf.format(date);
			
			//저장할 폴더 경로
			String uploadPath = "C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca;
			
			File folder = new File(uploadPath);
			if(!folder.exists()) {
				folder.mkdir(); //폴더가 존재하지 않는다면 생성해라.
			}
			
			String fileRealName = file.getOriginalFilename();
			
			//파일명을 고유한 랜덤 문자로 생성.
			UUID uuid = UUID.randomUUID();
			String uuids = uuid.toString().replaceAll("-", "");
			
			//확장자를 추출합니다.
			String fileExtension = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());
			
			System.out.println("저장할 폴더 경로: " + uploadPath);
			System.out.println("실제 파일명: " + fileRealName);
			System.out.println("폴더명: " + fileLoca);
			System.out.println("확장자: " + fileExtension);
			System.out.println("고유랜덤문자: " + uuids);
			
			String fileName = uuids + fileExtension;
			System.out.println("변경해서 저장할 파일명: " + fileName);
			
			//업로드한 파일을 서버 컴퓨터의 지정한 경로 내에 실제로 저장.
			File saveFile = new File(uploadPath + "\\" + fileName);
			file.transferTo(saveFile);
			
			//DB에 insert 작업을 진행.
			SnsBoardVO snsVO = new SnsBoardVO(0, writer, uploadPath, fileLoca, fileName, fileRealName, content, null);
			service.insert(snsVO);
			
			
			return "success";
			
		} catch (Exception e) {
			System.out.println("업로드 중 에러 발생: " + e.getMessage());
			return "fail"; //에러가 났을 시에는 실패 키워드를 반환.
		}
		
	}
	
	//비동기 통신 후 가져올 목록
	@GetMapping("/getList")
	@ResponseBody
	public List<SnsBoardVO> getList(PageVO paging) {
		paging.setCountPerPage(3);
		return service.getList(paging);
	}
	
	//게시글의 이미지 파일 전송 요청
	//ResponseEntity: 응답으로 변환될 정보를 모두 담은 요소들을 객체로 만들어서 반환해 줍니다.
	@GetMapping("/display")
	public ResponseEntity<byte[]> getFile(String fileLoca, String fileName) {
		
		System.out.println("fileName: " + fileName);
		System.out.println("fileLoca: " + fileLoca);
		
		File file = new File("C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
		System.out.println(file);
		
		ResponseEntity<byte[]> result = null;
		
		try {
			
			HttpHeaders headers = new HttpHeaders();
			//probeContentType: 파라미터로 전달받은 파일의 타입을 문자열로 변환해 주는 메서드.
			//사용자에게 보여주고자 하는 데이터가 어떤 파일인지를 검사해서 응답 상태 코드를 다르게 리턴할 수도 있습니다.
			headers.add("Content-Type", Files.probeContentType(file.toPath()));
//			headers.add("merong", "hello");
			
			//ResponseEntity<>(응답 객체에 담을 내용, 헤더에 담을 내용, 상태 메세지);
			//FileCopyUtils: 파일 및 스트림 데이터 복사를 위한 간단한 유틸리티 메서드의 집합체.
			//file객체 안에 있는 내용을 복사해서 byte배열로 변환해서 바디에 담아 화면에 전달.
			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), headers, HttpStatus.OK);

		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
		return result;
		
	}
	// 상세보기 처리
	@GetMapping("/getDetail/{bno}")
	@ResponseBody
	public SnsBoardVO getDetail(@PathVariable int bno) {
		return service.getDetail(bno);
	}

	//삭제처리
	@PostMapping("/delete")
	@ResponseBody
	public String delete(@RequestBody int bno, HttpSession session) {
		System.out.println("삭제 글 번호 : " + bno);
		SnsBoardVO vo = service.getDetail(bno);
		UserVO user = (UserVO) session.getAttribute("login");
		
		//로그인을 하지 않았거나, 작성자와 현재 로그인한 id가 일치하지 않는다면
		if(user == null || !user.getUserId().equals(vo.getWriter()) ) {
			return "noAuth";
		}
		service.delete(bno);
		
		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드
	}
	
	//다운로드 비동기처리(화면에서 a태그를 클릭시download요청이 들어올 수 있도록 처리)
	@GetMapping("/download")
	@ResponseBody
	public ResponseEntity<byte[]> download(String fileLoca, String fileName ){
		System.out.println("fileName: " + fileName);
		System.out.println("fileLoca: " + fileLoca);
		File file = new File("C:\\Users\\mls00\\OneDrive\\바탕 화면\\upload\\" + fileLoca + "\\" + fileName);
		
		
		ResponseEntity<byte[]> result = null;
		try {
			// 응답하는 본문을 브라우저가 어떻게 표시해야 할 지 알려주는 헤더 정보를 추가합니다.
			// inline이라느 헤더 정보를 줄 경우 웹 페이지 화면에 포시가 되고, attachment인 경우 다운로드를 제공한다. 
			
			//request객체의 getHeader("User-Agent") -> 단어를 뽑아서 확인
	         //ie: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko  
	         //chrome: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
			
			//파일명한글처리(Chrome browser) 크롬
	         //header.add("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1") );
	         //파일명한글처리(Edge) 엣지 
	         //header.add("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
	         //파일명한글처리(Trident) IE
	         //Header.add("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", " "));
			
			HttpHeaders header = new HttpHeaders();
			header.add("content-Disposition", "attachment; filename="+ fileName);
			
			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header,HttpStatus.OK);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
		
	}
	
	
}

스프링에서는 아노테이션이 중요하다. 

@Controller
@RequestMapping("/snsBoard")

컨트롤러라는 것을 등록 해주고 

기본 url을 매핑해준다.

 

 

@Autowired
private ISnsBoardService service;

서비스를 참조한다고 변수를 등록해준뒤 빈등록을 해준다. 

 

 


JSP

</head>
<body>
   <%@ include file="../include/header.jsp" %>
   <section>
      <div class="container">
         <div class="row">
            <aside class="col-sm-2">
               <div class="aside-inner">
                  <div class="menu1">
                     <c:choose>
                        <c:when test="${login != null}">
                           <p>
                              <img src="../resources/img/profile.png">&nbsp;&nbsp;${login.userName}님
                           </p>
                           <ul>
                              <li>내정보</li>
                              <li>내쿠폰</li>
                              <li>내 좋아요 게시물</li>
                           </ul>
                        </c:when>
                        <c:otherwise>
                           <button type="button" class="btn btn-info" onclick="location.href='<c:url value="/user/userLogin" />'">로그인</button>
                        </c:otherwise>
                     </c:choose>
                  </div>
                  <div class="menu2">
                     <p>둘러보기</p>
                     <ul>
                        <li>기부 캠페인</li>
                        <li>페이지</li>
                        <li>그룹</li>
                        <li>이벤트</li>
                        <li>친구리스트</li>
                     </ul>
                  </div>
               </div>
            </aside>
            <div class="col-xs-12 col-sm-8 section-inner">
               <h4>게시물 만들기</h4>
               <!-- 파일 업로드 폼입니다 -->
               <div class="fileDiv">
                  <img id="fileImg" src="<c:url value='/img/img_ready.png' />" />
               </div>
               <div class="reply-content">
                  <textarea class="form-control" rows="3" name="content"
                     id="content" placeholder="무슨 생각을 하고 계신가요?"></textarea>
                  <div class="reply-group">
                     <div class="filebox pull-left">
                        <label for="file">이미지업로드</label> 
                        <input type="file" name="file" id="file">
                     </div>
                     <button type="button" class="right btn btn-info" id="uploadBtn">등록하기</button>
                  </div>
               </div>


               <!-- 파일 업로드 폼 끝 -->
               
               <div id="contentDiv">
               <div class="title-inner">
                  <!--제목영역-->
                  <div class="profile">
                     <img src="../resources/img/profile.png">
                  </div>
                  <div class="title">
                     <p>테스트</p>
                     <small>21시간</small>
                  </div>
               </div>
               <div class="content-inner">
                  <!--내용영역-->
                  <p>삶이 우리를 끝없이 시험하기에 고어텍스는 한계를 테스트합니다</p>
               </div>
               <div class="image-inner">
                  <!-- 이미지영역 -->
                  <img src="../resources/img/facebook.jpg">
                  
               </div>
               <div class="like-inner">
                  <!--좋아요-->
                  <img src="../resources/img/like.png"> <span>522</span>
               </div>
               <div class="link-inner">
                  <a href="##"><i class="glyphicon glyphicon-thumbs-up"></i>좋아요</a>
                  <a href="##"><i class="glyphicon glyphicon-comment"></i>댓글달기</a> 
                  <a href="##"><i class="glyphicon glyphicon-remove"></i>삭제하기</a>
               </div>
               </div>
            </div>
            <!--우측 어사이드-->
            <aside class="col-sm-2">
               <div class="aside-inner">
                  <div class="menu1">
                     <p>목록</p>
                     <ul>
                        <li>목록1</li>
                        <li>목록2</li>
                        <li>목록3</li>
                        <li>목록4</li>
                        <li>목록5</li>
                     </ul>
                  </div>
               </div>
            </aside>
         </div>
      </div>
   </section>
   <%@ include file="../include/footer.jsp" %>
   
   <!-- 모달 -->
   <div class="modal fade" id="snsModal" role="dialog">
         <div class="modal-dialog modal-lg">
         <div class="modal-content">
            <div class="modal-body row">
               <div class="modal-img col-sm-8 col-xs-6" >
                  <img src="../resources/img/img_ready.png" id="snsImg" width="100%">
               </div>
               <div class="modal-con col-sm-4 col-xs-6">
                  <div class="modal-inner">
                  <div class="profile">
                     <img src="../resources/img/profile.png">
                  </div>
                  <div class="title">
                     <p id="snsWriter">테스트</p>
                     <small id="snsRegdate">21시간전</small>
                  </div>
                  <div class="content-inner">
                     <p id="snsContent">다람쥐 헌 쳇바퀴에 타고파. sns Content 내용입니다.</p>
                  </div>
                  <div class="link-inner">
                     <a href="##"><i class="glyphicon glyphicon-thumbs-up"></i>좋아요</a>
                     <a href="##"><i class="glyphicon glyphicon-comment"></i>댓글달기</a> 
                     <a href="##"><i class="glyphicon glyphicon-share-alt"></i>공유하기</a>
                  </div>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>

html을 만들어 준뒤 이제 본격적으로 기능구현을 시작하면 된다. 

 

😋

 

주인장 git : https://github.com/MoonSeokHyun

반응형

+ Recent posts