반응형

국비학원 프로젝트 이후 핀란드에 넘어와 

무슨 프로젝트를 할까 고민하던 차에 이전 조별 프로젝트에서 인상깊게 보았던

앨범형 게시판에 의거 하여 레시피 사이트를 제작해보려고 한다. 

 

메인페이지에 헤더 및 배너 부분이다. 

약 5개의 카테고리로 구분될 것이며

비건레시피 비건뉴스 비건이라? 비건수다방(채팅) 레시피자랑하기

로 구분되어 게시판1개 관리자게시판 1개로 구분된다. 

 

 

아래 비건 뉴스는 카드 뉴스 형식으로써 각종 뉴스를 제공하려고 한다.

비건 뉴스 카테고리와 접목해서 게시판을 올릴때마다 이곳에서는 3개만 보여지도록 하려고 한다. 

 

이것을 누르면 비건에대해서 보여주려고 한다. 

 

푸터 부분 

이렇게 해서 메인페이지에 대한 기초 사항은 끝낫고

이제 로그인, 회원가입, 게시판, 인포, 채팅창 등등 뷰 만 찍어내고 

 

빨리 스프링에 들어가야겠다 !! 

반응형
반응형

이번 시간에는 이전에 프로젝트 때 작업 했던 

위 와같은 댓글이 달리도록 한번 작성해보자 

 

그럼 Let's GO 

 


1.  댓글 테이블 제작 및 vo 클래스 제작

 

데이터 베이스 같은경우 오라클을 사용하였다. 

 

import java.sql.Timestamp;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class CommentVO {
	
	private int com_no;
	private String com_writer;
	private String com_content;
	private int com_like;
	private int com_hate;
	private String re_com;
	private int com_type;
	private Timestamp com_regdate;
	private int com_bno;
}

 

위 와같이 코드가 작성되고 컬럼명은 VO와 동일하게 제작했댜.

 


서비스와 매퍼 객체를 만들어보자 

 

public interface ICommentService {
	
	
	void CommentRegist(CommentVO vo); //댓글 등록
	List<CommentVO> getList(int com_bno); //목록 요청
	int getTotal(int com_bno); //댓글 개수
	List<CommentVO> myRecord(String writer);
	void commentDelete(CommentVO vo);
	
	List<CommentVO> getComList(String com_wrtier); //어드민용 회원 댓글 불러오긔

}

인터페이스 서비스와 매퍼객체 똑같이 만들어준다. 

 

그 뒤 서비스를 포함한 클래스를 만들어준다. 

 

package com.community.shy.board.JobComment.service;

import java.util.List;

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

import com.community.shy.board.JobComment.mapper.ICommentMapper;
import com.community.shy.board.command.CommentVO;
@Service
public class CommentService implements ICommentService {

	@Autowired
	private ICommentMapper mapper;
	
	@Override
	public void CommentRegist(CommentVO vo) {
		mapper.CommentRegist(vo);

	}

	@Override
	public List<CommentVO> getList(int com_bno) {
		return mapper.getList(com_bno);
	}

	@Override
	public int getTotal(int com_bno) {
		// TODO Auto-generated method stub
		return mapper.getTotal(com_bno);
	}

	@Override
	public List<CommentVO> myRecord(String writer) {
		return mapper.myRecord(writer);
	}

	@Override
	public void commentDelete(CommentVO vo) {
		mapper.commentDelete(vo);
		
	}
	
	@Override
	public List<CommentVO> getComList(String com_wrtier) {
		return mapper.getComList(com_wrtier);
	}

}

오늘 필요한 메소드는 CommentRegist 와 getList , getTotal 까지 이다. 

 

 


컨트롤러 및 마이바티스에 들어가는 매퍼를 제작해보자 

 

매퍼 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="com.community.shy.board.JobComment.mapper.ICommentMapper">
  	
  	<insert id="CommentRegist">
  		insert into job_comment(com_no,com_writer,com_content,com_like,com_hate,re_com,com_type,com_bno)
		values (COMMENT_SEQ.nextval ,#{com_writer},#{com_content},0,0,1,1,#{com_bno})
  	</insert>
  	
  	<select id="getTotal" resultType="int">
  		select count(*) from job_comment where com_bno = #{com_bno}
  	</select>
  	
  	<select id="getList" resultType="com.community.shy.board.command.CommentVO">
  		select * from job_comment where com_bno = #{com_bno}
  	</select>
  	
  	<select id="myRecord" resultType="com.community.shy.board.command.CommentVO">
		SELECT * FROM job_comment
		WHERE com_writer = #{writer}
	</select>
	
	<delete id="commentDelete">
		DELETE FROM job_comment
		WHERE com_writer = #{com_writer} AND com_content = #{com_content}
	</delete>
	
	<select id="getComList" resultType="com.community.shy.board.command.CommentVO">
        select * from job_comment where com_writer = #{com_writer}
     </select>
	
  </mapper>
@RestController
@RequestMapping("/Comment")
public class CommentController {
	
	@Autowired
	private ICommentService service;
	
	@PostMapping("/InsertComment")
	public String InsertComment(@RequestBody CommentVO vo,HttpSession session) {
		System.out.println("댓글 등록 통신 성공");
		if(session.getAttribute("login") == null) {
			return "fail";
		} else {
			System.out.println("로긘함. 스크랩 진행");
			
			service.CommentRegist(vo);
			System.out.println("댓글 등록 서비스 성공");
			return "InsertSuccess";
		}
	}

	@GetMapping("/CommentList/{com_bno}")
	public Map<String, Object> getList(@PathVariable int com_bno, Model model) {
		System.out.println("댓글 목록 컨트롤러 동작");
		List<CommentVO> list = service.getList(com_bno);
		int total = service.getTotal(com_bno);
		
		ModelAndView view = new ModelAndView();
		System.out.println("댓글 갯수 " + service.getTotal(com_bno));
		view.setViewName("/board/JBoardList");
		Map<String, Object> map = new HashMap<>();
		map.put("list", list);
		map.put("total", total);
		
		return map;
	}

컨트롤러 같은경우에 ajax로 동작시키려고 한다.

그렇기 떄문에 RestControolaer를 활용하자 

 

그 후 세션 객체를 활용 하여 로그인 검증 한번 해주고

댓글 등록에 대한 로직이 돌아가 도록 서비스 객체에서 댓글 등록 인서트문 을 돌려준다. 

 

이제 jsp를 보자! :) 

 

 

                <div class="comment-box">
                    
   		                 <div class="comment-count">댓글 <span id="count">0</span></div>

   		                 	   <!-- <span class="c-icon"><i class="fa-solid fa-user"></i>  -->
   		                 <div class="comment-name">
	                        <span class="anonym">작성자 : 
	                    	    <input type="text" class="form-control" id="com_writer" placeholder="이름" name ="com_writer" value='${login.userId}' readonly  style="width: 100px; border:none;">
	                        </span>
	                      </div>   
	                        	
	                        <!-- </span> -->
                     <!--<img src="/익명.jpg" width ="50px" alt="My Image"><!-->
                    <div class="comment-sbox">
                        <textarea class="comment-input" id="com_content" cols="80" rows="2" name="com_content" ></textarea>
                        <!-- <span class="com-function-btn" type="hidden">
                            
                            <a href="#"><i class="fa-solid fa-pen-to-square"></i></a>
                            <a href="#"><i class="fa-solid fa-trash-can"></i></a>
                         </span> -->
                    </div>
                    	<div class="regBtn">
                    		<button id="Comment_regist"> 댓글등록</button>
                    	 </div>

작성하면 대략 

이런 화면을 가진 창이 나타내게 된다. 

 

그 후 댓글 등록을 누르게 되면  다른 div 박스 안에 계속 list가 채워 지는 형식으로 진행 할 것이다. 

 

<div class="comment_Box" style="border:1px solid gray;"> <!-- 댓글이 들어갈 박스 -->

	                </div>

이제 ajax 를 작성해보자 

 

$('#Comment_regist').click(function() {
			
   			//Json으로 전달할 파라미터 변수선언
   			const com_bno = ${board_no};
   			const com_writer = $('#com_writer').val();
   			const com_content = $('#com_content').val();
   			
   			console.log(com_bno);
   			console.log(com_writer);
   			console.log(com_content);
   		
   			if(com_writer == ''){
   				alert('로그인 후 이용해주세요');
   				return;
   			}else if(com_content == '') {
   				alert('내용을 입력하세요');
   			}
   			
   			$.ajax({
   				type:'post',
   				url:'<c:url value="/Comment/InsertComment"/>',
   				data: JSON.stringify(
   					{
   						"com_bno":com_bno,
   						"com_writer":com_writer,
   						"com_content":com_content
   					}		
   				),
   				contentType: 'application/json',
   				success:function(data){
   					console.log('통신성공' + data);
   					if(data === 'InsertSuccess') {
   						alert('댓글 등록이 완료되었습니다.');
   						console.log('댓글 등록 완료');
   						$('#com_writer').val(com_writer);
   	   					$('#com_content').val('');
   	   					getList();
   					} else {
   						alert('로그인 이후 이용해주시기 바랍니다.');
   						console.log('댓글 등록 실패');
   					}
   				},
   				error:function(){
   					alert('통신실패');
   				}
   			});// 댓글 비동기 끝
   			
		});// 댓글등록 이벤트 끝
		
		getList();

우선 Json으로 전달할 파라미터를 const로 변수 선언을 해준다. 

이것이 이제 컨트롤러로 들어가 매퍼를 거쳐 디비에 저장될 값이다. 

 

그후 검증을 통해 빈값 체크 및 로그인을 하지 않았을 경우 등을 걸러준다. 

그 후 ajax를 통해 입력 받은 데이터를 컨트롤러로 넘겨준다. 

 

이때 ajax를 통해 getList라는 메소드가 ㄴ발동 되게 되는데 이것은 리스트를 불러오는 것이다 

작성된 글을 실시간으로 비동기 식으로 보여주어  페이지 로딩없이 바로 보여준다. 

 

getList는 getJson으로 해보자

 

ajax와 getJson의 차이점은 

async 옵션은 기본적으로 true 가 default 인데, false 로 하면 백그라운드로 진행하는게 아니라 순차적으로 진행되어  제대로된 .ajax 를 사용하는게 아닐지 모르지만 화면을 보는 관점에서는 더자연스럽게 느껴진다.

 

     또한, 백그라운드로 진행하면 안되는 작업을 할 때는 async옵션을 false로 주어야 한다.

 

 

		function getList() {
			
			const com_bno = ${board_no};
			const com_writer = $('#com_writer').val();
   			const com_content = $('#com_content').val();
   			/* const com_no = ${com}; */
			$.getJSON(
				"<c:url value='/Comment/CommentList/'/>"+com_bno,
				function(data) {
					if(data.total > 0){
						var list = data.list;
						
						var comment_html = "<div>";
						
						$('#count').html(data.total);
						for(i = 0;i < list.length;i++){
							var content = list[i].com_content;
							var writer = list[i].com_writer;
							comment_html += "<div><span id='com_writer'><strong>" + writer + "</strong></span><br/>";
							comment_html += "<span id='com-content'>" + content + "</span><br>";
							if(writer === $("#com_writer").val()){
								 comment_html += "<span id='delete' style='cursor:pointer;' data-id ="+content+">[삭제]</span><br></div><hr>";
								 
							}
							else{
								comment_html += "</div><hr>";
							}
						}
						
						$(".comment_Box").html(comment_html);
						
						
					}
					else{
						var comment_html = "<div>등록된 댓글이 없습니다.</div>";
						$(".comment_Box").html(comment_html);
					}
			
				
				}
				);//getJson



getJson도 우선 글을 불러오기 위해 글 번호를 받는다. 

그 뒤에 컨트롤러에 접근하기위해 글 번호를 묻혀 보낸다. 

이떄 데이터 크기가 0코다 클경우로 설정하여 

 

댓글이 0개 일시 등록된 댓글이 없다고 표기해준다. 

 

그 후 컨트롤러에서 뿌려진 정보를 data 반복문을 통해 

이 글 번호에 등록된 댓글을 계속 뿌려주고 

이때 계속 += div asdasd .</div>

이렇게 계속 html 코드가 이어지도록 반복문을 작성한뒤 

 

제이쿼리 html() 로 붙여 넣어주면 댓글 작성이 끝나게 된다. 

 

https://github.com/MoonSeokHyun/JobJob_community

 

GitHub - MoonSeokHyun/JobJob_community

Contribute to MoonSeokHyun/JobJob_community development by creating an account on GitHub.

github.com

 

 

반응형
반응형

오늘은 게시판 별 카테고리(type)을 주어 다중 게시판을 만들어 보고자 한다. 

 

1. Sql 테이블 생성

 

테이블 생성

 

다중게시판을 위해 Board_type으로 게시판을 나눈다. 

 

2. VO객체 생성

 

public class BoardVO {
	private int board_no;
	private String board_title;
	private String board_writer;
	private String board_content;
	private int board_hit;
	private int board_like;
	private int board_hate;
	private String board_img;
	private String board_img_path;
	private int board_type;
	private Timestamp board_regdate;
}

vo 객체 생성 뒤 페이징 및 검색을 위한 객체 생성

 

@Getter
@Setter
public class PageVO {

	//사용자가 선택한 페이지 정보를 담을 변수.
	private int pageNum;
	private int countPerPage;
	
	//검색에 필요한 데이터를 변수로 선언.
	private String keyword;
	private String condition;
	
	public PageVO() {
		this.pageNum = 1;
		this.countPerPage = 5;
	}
	
}

페이징을 위한 다음 이전 등 버튼 개수를 위한 클래스 제작

 

@Setter
@Getter
@ToString
public class PageCreator {
	
	private PageVO paging;
	private int articleTotalCount;
	private int endPage;
	private int beginPage;
	private boolean prev;
	private boolean next;
	
	private final int buttonNum = 5;
	
	
	private void calcDataOfPage() {
		
		endPage = (int) (Math.ceil(paging.getPageNum() / (double) buttonNum) * buttonNum);
		
		beginPage = (endPage - buttonNum) + 1;
		
		prev = (beginPage == 1) ? false : true;
		
		next = articleTotalCount <= (endPage * paging.getCountPerPage()) ? false : true;
		
		if(!next) {
			endPage = (int) Math.ceil(articleTotalCount / (double) paging.getCountPerPage()); 
		}
		
	}
	
	//컨트롤러가 총 게시물의 개수를 PageCreator에게 전달한 직후에 
	//바로 페이징 버튼 알고리즘이 돌아갈 수 있도록 setter를 커스텀.
	public void setArticleTotalCount(int articleTotalCount) {
		this.articleTotalCount = articleTotalCount;
		calcDataOfPage();
	}

}

 

jsp (view) 제작

 

<body>
	<header id="header">
		<%@ include file="../include/header.jsp"%>
	</header>



	<section>
		<div class="body-box">
			<div class="row">

				<div class="titlebox">
					<c:choose>
						<c:when test="${type == 1}">
							<h2>사기업게시판</h2>
						</c:when>
						<c:when test="${type == 2}">
							<h2>공기업게시판</h2>
						</c:when>
						<c:when test="${type == 3}">
							<h2>아시아게시판</h2>
						</c:when>
						<c:when test="${type == 4}">
							<h2>유럽게시판</h2>
						</c:when>
						<c:when test="${type == 5}">
							<h2>남미게시판</h2>
						</c:when>
						<c:when test="${type == 6}">
							<h2>북미게시판</h2>
						</c:when>
						<c:when test="${type == 7}">
							<h2>아프리카게시판</h2>
						</c:when>
						<c:when test="${type == 8}">
							<h2>국가자격증게시판</h2>
						</c:when>
						<c:when test="${type == 9}">
							<h2>민간자격증게시판</h2>
						</c:when>
						<c:when test="${type == 10}">
							<h2>어학게시판</h2>
						</c:when>
						<c:when test="${type == 11}">
							<h2>자유게시판</h2>
						</c:when>
						<c:when test="${type == 12}">
							<h2>취뽀게시판</h2>
						</c:when>
						<c:when test="${type == 13}">
							<h2>취업게시판</h2>
						</c:when>
						<c:when test="${type == 14}">
							<h2>자격증게시판</h2>
						</c:when>
						<c:when test="${type == 15}">
							<h2>자소서게시판</h2>
						</c:when>



					</c:choose>
				</div>
				<hr>



				<div class="search-wrap">

					<div id="search_box">
						<form action=<c:url value="/board/JBoardList"/>>
							<input type="hidden" name="board_type" value="${type}" />
							<div class="search-wrap clearfix">
								<select class="form-control search-select" name="condition">
									<option value="board_title"
										${pc.paging.condition == 'board_title' ? 'selected' : ''}>제목</option>
									<option value="board_content"
										${pc.paging.condition == 'board_content' ? 'selected' : ''}>내용</option>
									<option value="board_writer"
										${pc.paging.condition == 'board_writer' ? 'selected' : ''}>작성자</option>
								</select> <input type="text" name="keyword"
									class="form-control search-input" value="${pc.paging.keyword}">
								<button type="submit" class="btn btn-info search-btn">검색</button>
							</div>
						</form>
					</div>
				</div>
				<table>
					<colgroup>

						<col class="no">
						<col class="title">
						<col class="writer">
						<col class="date">
						<col class="count">
						<col class="like">
					</colgroup>
					<thead>
						<tr>

							<th>번호</th>
							<th>제목</th>
							<th>작성자</th>
							<th>작성일</th>
							<th>조회</th>
							<th>추천</th>
						</tr>
					</thead>
					<tbody>

						<c:forEach var="vo" items="${boardList}">
							<tr>
								<td>${vo.board_no}</td>
								<td><a href="<c:url value ='/board/JBoardDetail?board_no=${vo.board_no}&board_type=${type}'/>">${vo.board_title} (${count})</a></td>
								<td><a href="#" id="modal-writer1">${vo.board_writer}</a></td>
								<td>${vo.board_regdate}</td>
								<td>${vo.board_hit}</td>
								<td>${vo.board_like}</td>
							</tr>
						</c:forEach>



					</tbody>
				</table>
				<a href=<c:url value="/board/JBoardWrite?board_type=${type}"/> class="myButton">글쓰기</a>



				<form action=<c:url value="/board/JBoardList?board_type=${type}"/>
					name="pageForm">
					<div class="text-center clearfix">
						<hr>
						<ul class="pagination" id="pagination">
							<c:if test="${pc.prev}">
								<li><a href="#" data-pageNum="${pc.beginPage-1}">이전</a></li>
							</c:if>

							<c:forEach var="num" begin="${pc.beginPage}" end="${pc.endPage}">
								<li class="${pc.paging.pageNum == num ? 'active' : ''}"><a
									href="#" data-pageNum="${num}">${num}</a></li>
							</c:forEach>

							<c:if test="${pc.next}">
								<li><a href="#" data-pageNum="${pc.endPage+1}">다음</a></li>
							</c:if>
						</ul>

						<!-- 페이지 관련 버튼을 클릭 시 같이 숨겨서 보낼 값 -->
						<input type="hidden" name="board_type" value="${type}">
						 <input type="hidden" name="pageNum" value="${pc.paging.pageNum}">
						<input type="hidden" name="countPerPage"value="${pc.paging.countPerPage}">
						<input type="hidden" name="keyword" value="${pc.paging.keyword}"> 
						<input type="hidden" name="condition" value="${pc.paging.condition}">


					</div>
				</form>
			</div>
		</div>
		</div>

		<!--쪽지모달-->


		<div class="modal_wrap">


			<div class="modal-contnet">
				<div class="mini-title">작성자님에게 보내는 쪽지</div>
				<textarea class="modal-txtcontent" rows="4" cols=25"></textarea>
				<button class="send-modalBtn">
					<a href="#">보내기</a>
				</button>
				<button class="close-modalBtn">
					<a href="#">닫기</a>
				</button>
			</div>


		</div>



	</section>


	<footer id="footer">
		<%@ include file="../include/footer.jsp"%>
	</footer>
</body>

 

tittle_box에서 jstl로 게시판을 구분해준다. 

이건 헤더에서도 마찬가지로 구분해준다. 

 

대략 요론 페이지가 나온다.

sql문을 만들자!

 

  		<sql id="search">
		<if test="paging.condition == 'board_title'">
			and board_title LIKE '%'||#{paging.keyword}||'%'
		</if>
		<if test="paging.condition == 'board_content'">
			and board_content LIKE '%'||#{paging.keyword}||'%'
		</if>
		<if test="paging.condition == 'board_writer'">
			and board_writer LIKE '%'||#{paging.keyword}||'%'
		</if> 

	</sql>	
    
    <select id="getJBoard" resultType="com.community.shy.board.command.BoardVO" >
		
		select * from
		(
		select rownum as rn , tbl. * from
		(
		SELECT * FROM job_board 
		where board_type = #{board_type}
		<include refid="search" />
		ORDER BY Board_no DESC
		)tbl
		)
		<![CDATA[
		WHERE rn > (#{paging.pageNum ,jdbcType=VARCHAR} - 1) * #{paging.countPerPage,jdbcType=VARCHAR}
		AND rn <= #{paging.pageNum,jdbcType=VARCHAR} * #{paging.countPerPage,jdbcType=VARCHAR}
		]]>
		
	</select>

search라는 이름으로 검색을 할수 있게 해주었다. 

where절에 board_type으로 구분해준다. 

view에서 파라미터 값으로 type을 전달해 줄것이다. 

이떄 sql문 board_type으로 구분을 해준다. 

 

그 뒤 페이징을 통해 처음에 구성해준 countparpage를 통해 몇개의 열이 화면에 보여줄지를 결정하게 된다. 

 

컨트롤러 구성

@Controller
@RequestMapping("/board")
public class BoardController {

	@Autowired
	private IBoardService service;

	
	@GetMapping("/JBoardList")
	public String BoardList(int board_type , PageVO paging ,Model model) {
		System.out.println("리스트 페이지 입니다. ");
		
		System.out.println("검색어: " + paging.getKeyword());
		System.out.println("검색 조건: " + paging.getCondition());
		
		PageCreator pc = new PageCreator();
		System.out.println(paging);
		pc.setPaging(paging);
		pc.setArticleTotalCount(service.getJTotal(paging, board_type));
		
		model.addAttribute("boardList", service.getJBoard(board_type, paging));
		model.addAttribute("type", board_type);
		model.addAttribute("pc", pc);
		System.out.println(pc);
		
		return "/board/JBoardList";
		
				
	}

컨트롤러에서는 board_type과 page에 대한 정보를 받고 model객체로 감싸 view에 뿌려준다. 

 

@Service
public class BoardService implements IBoardService {
	
	@Autowired
	private IBoardMapper mapper; 
	
	@Override
	public List<BoardVO> getJBoard(int board_type, PageVO paging) {
		List<BoardVO> list = new ArrayList<BoardVO>();
		
		Map<String, Object> data = new HashMap<>();
		data.put("board_type", board_type);
		data.put("paging", paging);
		
		return mapper.getJBoard(data);
	}

서비스 구분 2개의 파라미터를 전달을 해주기위해 map으로 포장해준다. 

 

인터페이스 메퍼에서는 map 타입으로 전달 받는다. 

public interface IBoardMapper {
//	게시판 구분 
	List<BoardVO> getJBoard(Map<String, Object> data)

그 후 

이렇게 더미데이터를 몇개 넣어주면

 

대략 요론 페이지가 나온다.

페이징을 통한 여러 게시판이 완성이 된다!

 

https://github.com/MoonSeokHyun/JobJob_community

 

GitHub - MoonSeokHyun/JobJob_community

Contribute to MoonSeokHyun/JobJob_community development by creating an account on GitHub.

github.com

 

반응형
반응형

현재 개발하고 있는 프로젝트임. 

 

나는 게시판쪽이랑 댓글 쪽을 배정받아 작업을 이어나감. 

 

이렇게 5개의 카테고리가 있으며 총 14개의 게시판을 짜야함

이럴 경우에 테이블에서 type을 주어 게시판 분리를 시켜줬음 .

 

그리고 게시판 메인에 게시판 리스트는 

type 1,2를 묶어 보여주고 , 3,4,5,6 이런식으로 묶어서 실시간으로 올라온 글을 보여주게함. 

 

아직 다듬어 지지는 않았지만 게시판임 

하지만 단점이 번호가 1씩 안올라간다는거임 테이블 컬럼을 하나 추가하여 적용했어야하는데 나의 큰 실수임.

 

 

댓글 같은 경우 아직 폼을 만들지 않아 카운터랑 밑에 쓴글만 보이게 함 

나중에 디자인할 예정

 

이외에도 상세보기 수정 등이 있지만 너무 간단하여 나중에 한번에 업데이트할 예정임.

반응형
반응형

오랜만에 포스팅이다..

 

요즘 프로젝트 및 해외상황 및 기타 등등 잡무로 너무나 바쁜 하루를 보내고 있다.

주말에 매일 써야지 써야지 하면서 넘긴 하루들을 반성하며 

오늘은 꼭 써야지라고 하고 드디어 내 블로그를 켰다. 

 

저번에 만들었던 상세보기 모달까지 만들었으며

이번에는 삭제까지 만들려고 한다. 

 

우선 js를 작성해주고 ajax를 통하여 서버와 통신한다. 

 


Js 작성 

 

      $('#contentDiv').on('click', '.link-inner a', function(e) {
         e.preventDefault();
         
         const bno = $(this).attr('href');
         
         $.ajax({
            type: "post",
            url: "<c:url value='/snsBoard/delete' />",
            data: bno,
            contentType: 'application/json',
            success: function(result) {
               if(result === 'noAuth') {
                  alert('권한이 없습니다.');
               } else if(result === 'fail') {
                  alert('삭제에 실패했습니다. 관리자에게 문의하세요.');
               } else {
                  alert('게시물이 정상적으로 삭제되었습니다.');
                  getList(1, true); //삭제가 반영된 글 목록을 새롭게 보여줘야 하기 때문에 str을 초기화. 
               }
            },
            error: function() {
               alert('삭제에 실패했습니다. 다시 시도하세요.');
            }
         });
         
      });

 

이제 통신할 컨트롤러를 만들어 준다. 

 

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

 

 

우선 삭제할 글번호와 로그인한 세션의 정보를 가져온다. 

그 후 vo 객체에서 get으로 불러 준 뒤 

userVo의 세션 정보와 매핑시킨디ㅏ. 

그 후 조건문으로 로그인을 하지않은 경우 에러코드를 ajax쪽으로 보낸다,

 

그후 

		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드

파일객체를 생성자로 불러와 지워 지고 있는 게시물의 파일을 지목한다., 

그후 리턴값으로 fail or success를 넣는다. 

 

그 뒤 Service문을 작성한다. 

 

게시물의 정보를 가져올 GetDetail() 과 

삭제를 담당하게 될 getDelete를  작성한다. 

 

		//파일 객체를 생성해서 지워지고 있는 게시물의 파일을 지목
		File file = new File(vo.getUploadpath()+"\\"+vo.getFilename());
		System.out.println("파일 삭제완료");
		return file.delete() ? "Success" : "fail"; // 파일 삭제 메소드
        
        	@Override
	public void delete(int bno) {
		mapper.delete(bno);

	}

 

mapper.xml을 작성한다. 

 

	<delete id="delete">
		delete from snsboard
		where bno = #{bno}
	</delete>
      //삭제 처리
      //삭제하기 링크를 클릭했을 때 이벤트를 발생 시켜서
      //비동기 방식으로 삭제를 진행해 주세요. (삭제 버튼은 한 화면에 여러개 겠죠?)
      //서버쪽에서 권한을 확인 해 주세요. (작성자와 로그인 중인 사용자의 id를 비교해서)
      //일치하지 않으면 문자열 "noAuth" 리턴, 성공하면 "Success" 리턴.
      // url: /snsBoard/delete, method: post

삭제처리를 총 종합해보면 위와 같다. 

 

 

그 후 작동 테스트를 해보면 

 

로그인 하지 않을 경우 권한없음 메시지 출력

삭제는.. 로그인 부분이 고장난거같다 추 후 확인을 해보아야 할거같다.

Otl...

반응형
반응형

저번에 리스트 만들기 에서 좀 막힌 부분은 좀 더 공부가 필요해 보인다. 

이번에는 상세보기 모달을 작성해 보려고 한다. 

 


1. 모달창 JSP 제작

.modal-body {
      padding: 0px;
   }
   
   .modal-content>.row {
      margin: 0px;
   }
   
   .modal-body>.modal-img {
      padding: 0px;
   }
   
   .modal-body>.modal-con {
      padding: 15px;
   }
   
   .modal-inner {
      position: relative;
   }
   
   .modal-inner .profile {
      position: absolute; /*부모기준으로 위치지정 릴레이티브*/
      top: 0px;
      left: 0px;
   }
   
   .modal-inner .title {
      padding-left: 50px;
   }
   
   .modal-inner p {
      margin: 0px;
   }

 

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

클릭했을때 작동 될 모달창

 

리스트에 그림을 클릭했을때 모달을 열어준다!

 

 

2. JS 작성

 

$('#contentDiv').on('click', '.image-inner a', function(e) {
         e.preventDefault();
         
         //글 번호 얻어오기
         const bno = $(this).attr('href');
         
         $.getJSON(
            "<c:url value='/snsBoard/getDetail/' />" + bno,
            function(data) {
               console.log(data);
               
               const img = 'display?fileLoca=' + data.fileloca + '&fileName=' + data.filename;
               $('#snsImg').attr('src', img); //이미지 경로 처리
               $('#snsWriter').html(data.writer); //작성자 처리
               $('#snsRegdate').html(timeStamp(data.regdate)); //날짜 처리
               $('#snsContent').html(data.content); //내용 처리
               $('#snsModal').modal('show'); //모달 열기
            }
         );
         
      });

클릭이벤트함수를 설정한뒤

E의 이벤트를 preventDefault()함수로 기능을 상실시킨다. 

 

그뒤 번 번호를 가져온 후 

 

getJson 방식으로 컨트롤러와 통신하여 DB의 데이터를 가져온다. 

 

 

컨트롤러 제작

 

@GetMapping("/getDetail/{bno}")
	@ResponseBody
	public SnsBoardVO getDetail(@PathVariable int bno) {
		return service.getDetail(bno);
	}

getJson에서 요청된 파라미터의 값을

@pathVariable의 값으로 받는다 .

이때 변수의 값이 동일하다면 pathVariable의 annotation을 생략해도 된다. 

방식은 모든 정보를 담고 있는 객체인 VO 객체로 리턴한다. 

 

 

서비스 및 매퍼 작성

 

 

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

 

리턴한 매퍼를 작성해주자!

 

	<select id="getDetail" resultType="com.spring.myweb.command.SnsBoardVO">
		select * from snsboard
		where bno = #{bno}
	</select>

 

이제 getJson에서 요청된 정보를 컨트롤러에서 파라미터를 받아 서비스로 리턴 후 매퍼에서 주어 

매퍼에서 쿼리문을 작성해 가져온 데이터를 js 모달에 뿌려주면 된다.

 

         $.getJSON(
            "<c:url value='/snsBoard/getDetail/' />" + bno,
            function(data) {
               console.log(data);
               
               const img = 'display?fileLoca=' + data.fileloca + '&fileName=' + data.filename;
               $('#snsImg').attr('src', img); //이미지 경로 처리
               $('#snsWriter').html(data.writer); //작성자 처리
               $('#snsRegdate').html(timeStamp(data.regdate)); //날짜 처리
               $('#snsContent').html(data.content); //내용 처리
               $('#snsModal').modal('show'); //모달 열기
            }
         );
         
      });

이런식으로 Jqurey .html 및 attr 함수로 추가해주었다. 

 


동작확인

 

모달창이 아주 잘 뜨는걸 확인 할 수 있다. 

 

 

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

반응형
반응형

Insert까지 진행했엇다. 

이제는 리스트를 불러오도록 해보자!

 

 


Js 작업

 

       let str = '';
      let page = 1;
      getList(1, true);
 
 function getList(page, reset) {
         if(reset === true) {
            str = ''; //화면 리셋 여부가 true라면 str변수를 초기화.
         }
         
         $.getJSON(
            '<c:url value="/snsBoard/getList?pageNum=' + page + '" />',
            function(list) {
               console.log(list);
               
               for(let i=0; i<list.length; i++) {
                  str += "<div class='title-inner'>";
                        str += "<div class='profile'>";
                        str += "<img src='../resources/img/profile.png' >";    
                        str += "</div>";
                        str += "<div class='title'>";
                        str += "<p>"+ list[i].writer +"</p>";
                        str += "<small>"+ timeStamp(list[i].regdate) +"</small> &nbsp;&nbsp;";
                        
                        //파일다운로드
                        str += "<a href='download?fileLoca=" + list[i].fileloca +  "&fileName=" + list[i].filename + "'>이미지 다운로드</a>";
                        //파일다운로드끝
                        
                        str += "</div>";
                        str += "<div class='content-inner'>"
                        str += "<p>"+ (list[i].content === null ? '' : list[i].content) +"</p>";
                        str += "</div>";
                        /* 이미지 영역 */
                        str += "<div class='image-inner'>";
                        str += "<a href='" + list[i].bno + "'>"; ///////추가
                        str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";
                        str += "</a>" ////////추가
                        str += "</div>";
                        str += "<div class='like-inner'>";                   
                        str += "<img src='../resources/img/like.png'><span>522</span>";                  
                        str += "</div>";
                        str += "<div class='link-inner'>";                     
                        str += "<a href='##'><i class='glyphicon glyphicon-thumbs-up'></i>좋아요</a>";
                        str += "<a href='##'><i class='glyphicon glyphicon-comment'></i>댓글달기</a>";
                        str += "<a href='" + list[i].bno + "'><i class='glyphicon glyphicon-remove'></i>삭제하기</a>";
                        str += "</div>";
                        $('#contentDiv').html(str);
               }
               
            }
               
         ); //end getJSON
         
      } //end getList()

우선 변수 str과 page를 만들어준다. 

나는 무한스크롤을 진행을 하려고 페이지는 1로 두었다. 

 

함수를 만들어 reset을 변수로 둔다.

reset의 역활은 화면 여부가 리셋되는 것을 의미 한다. 

false가 오면 실패 true가 오면 성공이다. 

 

true값이 오면 

str을 초기화 해주는 역활을 한다. 

 

그다음 

 

(1) $.getJSON(url,callback)

1) 자바에서 static 메서드와 유사--> jQuery에서는 전역메서드라 불린다.

2) 첫번째 매개변수로 JSON 파일을 로드한다. 

3) 두번째 매개변수(콜백함수)에서 JSON 파일을 이용하여 로드된 데이터를 처리한다. 콜백함수는 로드된 데이터를 인자로 넘겨받는다.(JSON 데이터를 참조하기 위해 data 변수를 사용하고 있다.)

 

참고하자

을 활용 하여 

컨트롤러와 통신을 한다. url을 넣어주고

콜백으로 

for문을 돌려 str에 html을 add 시켜준다. 

그 후 컨트롤러와 통신을 하여 불러온 list의 값으로 

작성자 파일이름, 파일경로 등등을 불러와 준다.

그 후 str이 완료가 되었으면

 

J-Qurry 함수 html()로 전달을 해준다. 

 

그 후 reset 값이 true로 바뀌면 str의 값은 ''으로 초기화가 된다.

 

 

VO 제작

@Getter
@Setter
public class PageVO {
	
	//사용자가 선택한 페이지 정보를 담을 변수.
	private int pageNum;
	private int countPerPage;
	
	//검색에 필요한 데이터를 변수로 선언.
	private String keyword;
	private String condition;
	
	public PageVO() {
		this.pageNum = 1;
		this.countPerPage = 10;
	}

}

 

컨트롤러 제작

	@GetMapping("/getList")
	@ResponseBody
	public List<SnsBoardVO> getList(PageVO paging) {
		paging.setCountPerPage(3);
		return service.getList(paging);
	}

 

rest방식으로 통신을 진행한다. 

리스트타입으로 snsVO 타입으로 받고 

페이지의 정보를 리턴해야 하기 때문에 paging을 변수로 설정한다. 

 

 

 

서비스 제작

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

 

매퍼 제작

 

   <select id="getList" resultType="com.spring.myweb.command.SnsBoardVO">
          SELECT * FROM snsboard
            ORDER BY bno DESC
    
      
   </select>

js 추가 작성

 

 $.ajax({
            url: '<c:url value="/snsBoard/upload" />',
            type: 'post',
            data: formData, //폼데이터 객체를 넘깁니다.
            contentType: false, //ajax 방식에서 파일을 넘길때는 반드시 false로 처리 -> "multipart/form-data"로 선언됨.
            processData: false, //폼 데이터를 &변수=값&변수=값... 형식으로 변경되는 것을 막는 요소.
            success: function(result) {
               if(result === 'success') {
                  $('#file').val(''); //파일선택지 비우기
                  $('#content').val(''); //글 영역 비우기
                  $('.fileDiv').css('display', 'none'); //미리보기 감추기
                  getList(1, true); //글 목록을 호출
               } else {
                  alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
               }
            },
            error: function(request, status, error) {
               console.log('code: ' + request + '\n' + 'message: ' + request.responseText + '\n' + 'error: ' + error);
               alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
            }
            
         }); //end ajax

저번에 insert 에서 만들어 두었는데 

마지막 부분에 getList()를 추가해주어

등록하기 버튼을 눌렀을 때 getList가 작동될 수 있게 해준다.

 

그 후 

 

  for(let i=0; i<list.length; i++) {
                  str += "<div class='title-inner'>";
                        str += "<div class='profile'>";
                        str += "<img src='../resources/img/profile.png' >";    
                        str += "</div>";
                        str += "<div class='title'>";
                        str += "<p>"+ list[i].writer +"</p>";
                        str += "<small>"+ timeStamp(list[i].regdate) +"</small> &nbsp;&nbsp;";
                        
                        //파일다운로드
                        str += "<a href='download?fileLoca=" + list[i].fileloca +  "&fileName=" + list[i].filename + "'>이미지 다운로드</a>";
                        //파일다운로드끝
                        
                        str += "</div>";
                        str += "<div class='content-inner'>"
                        str += "<p>"+ (list[i].content === null ? '' : list[i].content) +"</p>";
                        str += "</div>";
                        /* 이미지 영역 */
                        str += "<div class='image-inner'>";
                        str += "<a href='" + list[i].bno + "'>"; ///////추가
                        str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";
                        str += "</a>" ////////추가
                        str += "</div>";
                        str += "<div class='like-inner'>";                   
                        str += "<img src='../resources/img/like.png'><span>522</span>";                  
                        str += "</div>";
                        str += "<div class='link-inner'>";                     
                        str += "<a href='##'><i class='glyphicon glyphicon-thumbs-up'></i>좋아요</a>";
                        str += "<a href='##'><i class='glyphicon glyphicon-comment'></i>댓글달기</a>";
                        str += "<a href='" + list[i].bno + "'><i class='glyphicon glyphicon-remove'></i>삭제하기</a>";
                        str += "</div>";
                        $('#contentDiv').html(str);
               }

 

그 후 snsList에서 등록된  이미지를 저장 후 불러 와줄 컨트롤러를 작성한다.

 

str += "<img src='display?fileLoca=" + list[i].fileloca + "&fileName=" + list[i].filename +"'>";

이부분에서 컨트롤러를 부른다. 

 

fileLoca = 파일경로

fileName = 파일이름 

	//게시글의 이미지 파일 전송 요청
	//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;
		
	}

이부분은 좀더 공부가 필요해 보인다 . 

 

3편에서 계속...

반응형
반응형


(1)에서는 기본적인 프로젝트의 기초틀을 만들었다. 

이번에는 insert를 만들어 보려고한다! 

 

자 만들어 보자 

 


1. Jsp 검증 로직 구현

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

 

   $(function() {
      
      //등록하기 버튼 클릭 이벤트
      $('#uploadBtn').click(function() {
         regist();
      });
      
      //등록을 담당하는 함수
      function regist() {
         //세션에서 현재 로그인 중인 사용자 정보(아이디)를 얻어오자
         const user_id = '${sessionScope.login.userId}';
         //자바스크립트의 파일 확장자 체크 검색.
         let file = $('#file').val();
         
         console.log(user_id);
         console.log(file);
         //.을 제거한 확장자만 얻어낸 후 그것을 소문자로 일괄 변경
         file = file.slice(file.indexOf('.') + 1).toLowerCase();
         console.log(file);
         if(file !== 'jpg' && file !== 'png' && file !== 'jpeg' && file !== 'bmp') {
            alert('이미지 파일(jpg, png, jpeg, bmp)만 등록이 가능합니다.');
            $('#file').val('');
            return;
         } else if(user_id === '') { //세션 데이터가 없다 -> 로그인 x
            alert('로그인이 필요한 서비스입니다.');
            return;
         }

세션데이터가 없을경우 글 등록을 하지 못하게 하였다.

그리고 얻어온 세션 아이디로 글을 올린 사람에 대한 정보를 쿼리문으로 넘겨 줄 것이다.

 

우선 file이라는 변수로 file의 값을 가져온다. 

그 후 파일 업로드가 끝나면 slice 함수로 .png 등 확장자를 잘라내어 

tolowerCase로 소문자로 변경해준다.

 

그 뒤 이미지 파일만 올릴 수 있게

if문 으로 검증 로직을 구현 해준다. 

 

즉 로그인 안됨 = 파일을 올릴 수없음

로그인 함 = 이미지 파일만 올릴 수있음

이 두가지 관문을 거쳐야 파일을 올릴 수있게 처리를 하였다.

 

 

미리보기 기능

 

      function readURL(input) {
           if (input.files && input.files[0]) {
              
               var reader = new FileReader(); //비동기처리를 위한 파읽을 읽는 자바스크립트 객체
               //readAsDataURL 메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할 (MDN참조)
              reader.readAsDataURL(input.files[0]); 
               //파일업로드시 화면에 숨겨져있는 클래스fileDiv를 보이게한다
               $(".fileDiv").css("display", "block");
               
               reader.onload = function(event) { //읽기 동작이 성공적으로 완료 되었을 때 실행되는 익명함수
                   $('#fileImg').attr("src", event.target.result); 
                   console.log(event.target)//event.target은 이벤트로 선택된 요소를 의미
              }
           }
       }
      $("#file").change(function() {
           readURL(this); //this는 #file자신 태그를 의미
           
       });

1) 이미지가 1개인 경우

미리보기 이미지가 표시될 이미지 태그를 생성하고, input file 태그를 생성하고, 자바스크립트의 FileReader()를 통해 이미지가 로딩되면 이미지 태그의 src 속성이 교체되도록 합니다.

 

 

 

 

2. 컨트롤러 제작

 

	@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("/snsList") void 처리로 페이지 이동을 할 수 있게 처리 

 

로그인 세션을 가져와  로그인한 아이디의 값을 가져온다. 

 

날자별로 폴더를 생성해서 관리를 하려고한다.

자바 객체인 SimpleDateFormat을 활용하여

날자를 생성해주고

Date date = new Date(); 하면 오늘 날자가 생성된다.

fileloca라는 변수에 저장해 둔다 (20220215) 이런식으로 나옴

 

저장할 폴더의 경로를 선택해준다.

 

String uploadPath = 실제사용할경로 + fileloca

 

그 후 자바 FIle객체를 사용하여 

 

          exist() - File 객체가 참조하는 파일 또는 디렉토리가 실제로 존재하면 true를, 그렇지

                            않으면 false를 리턴한다.

 

메소드를 활용하여 파일이 한번 생성 된 뒤 중복 생성을 방지한다.


String fileRealName = file.getOriginalFilename();로 파일 고유의 이름을 추출

 

UUID uuid = UUID.randomUUID();
String uuids = uuid.toString().replaceAll("-", "");

로 랜덤한 이름을 추출 생성 해준뒤 

 

자바스크립트와 같이 확장자를 추출한다.(substring 활용)

String fileExtension = fileRealName.substring(fileRealName.indexOf("."), fileRealName.length());

 

그 후 이름을 변경한 파일의 이름을 변수 명으로 담는다.

String fileName = uuids + fileExtension;

 

업로드한 파일을 서버 컴퓨터의 지정한 경로 내에 실제로 저장.
File saveFile = new File(uploadPath + "\\" + fileName);
file.transferTo(saveFile);

MultipartFile 의 transferTo() 라는 메소드를 사용해서 원하는 위치에 저장해줍니다

//DB에 insert 작업을 진행.
SnsBoardVO snsVO = new SnsBoardVO(0, writer, uploadPath, fileLoca, fileName, fileRealName, content, null);
service.insert(snsVO);

 

 

 

2-1 서비스제작

 

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

VO값을 컨트롤러로 보내준다.

 

2-2 Mapper 제작

 

 

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

원래 나중에 resultmap을 써야하지만 나는 변수명과 컬럼값을 다 맞춰 벼렸다.. 

다음부터는 원칙을 지키자

sql은 snake_case!!

jaca CamelCase!!

 

3. 비동기 통신(ajax)

 

const formData = new FormData();
         const data = $('#file');
         
         console.log('폼 데이터: ' + formData);
         console.log('data: ' + data);
         console.log(data[0]);
         console.log(data[0].files); //파일 태그에 담긴 파일 정보를 확인하는 키값.
         console.log(data[0].files[0]);
         
         //data[index] -> 파일 업로드 버튼이 여러 개 존재할 경우 요소의 인덱스를 지목해서 가져오는 법.
         //우리는 요소를 id로 취득했기 때문에 하나만 찍히지만, class이름 같은거로 지목하면 여러개가 취득되겠죠?
         //files[index] -> 파일이 여러개 전송되는 경우, 몇 번째 파일인지를 지목.
         //우리는 multiple 속성을 주지 않았기 때문에 0번 인덱스 밖에 없는 겁니다.
         
         //FormData 객체에 사용자가 업로드한 파일의 정보들이 들어있는 객체를 전달.
         formData.append('file', data[0].files[0]);
         //content(글 내용) 값을 얻어와서 폼 데이터에 추가
         const content = $('#content').val();
         formData.append('content', content);
         
         //비동기 방식으로 파일 업로드 및 게시글 등록을 진행.
         $.ajax({
            url: '<c:url value="/snsBoard/upload" />',
            type: 'post',
            data: formData, //폼데이터 객체를 넘깁니다.
            contentType: false, //ajax 방식에서 파일을 넘길때는 반드시 false로 처리 -> "multipart/form-data"로 선언됨.
            processData: false, //폼 데이터를 &변수=값&변수=값... 형식으로 변경되는 것을 막는 요소.
            success: function(result) {
               if(result === 'success') {
                  $('#file').val(''); //파일선택지 비우기
                  $('#content').val(''); //글 영역 비우기
                  $('.fileDiv').css('display', 'none'); //미리보기 감추기
                  getList(1, true); //글 목록을 호출
               } else {
                  alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
               }
            },
            error: function(request, status, error) {
               console.log('code: ' + request + '\n' + 'message: ' + request.responseText + '\n' + 'error: ' + error);
               alert('업로드에 실패했습니다. 관리자에게 문의해 주세요.');
            }
            
         }); //end ajax
               
      } //end regist()

폼데이터를 활용하여 컨트롤러에게 비동기 통신으로 전달한다.

 

FromData란 ajax로 폼 전송을 가능하게 해주는 FormData 객체입니다.

보통은 Ajax로 폼(form 태그) 전송을 할 일이 거의 없습니다.

주로 JSON 구조로 "KEY-VALUE" (키와 값) 구조로 데이터를 전송합니다.

 

하지만,

form전송이 필요한 경우가 있는데, 이미지를 ajax로 업로드할 때 필요합니다.

이미지는 base64, buffer, 2진 data 형식으로 서버로 전송해도 됩니다.

 

하지만 추천 드리는 방법은 input[type=file]을 사용해 form(폼)을 통해서 업로드를 하는 것 입니다.

보통, form을 제출하면 action 속성에 의해 지정한 페이지로 이동하면서 데이터를 전송합니다.

ajax 반대로 제출 버튼을 누르면 기본 폼 동작은 e.preventDefault()  로 멈추고, 페이지 전환 없이 데이터를 전송합니다.

 

페이지 전환 없이 폼 데이터를 제출 하고 싶을 때 바로 FormData 객체를 사용합니다.

기존에 스캔 설정이랑은 해서 바로 테스트를 해보면 될 듯하다. 

 

 


4. 동작 확인

로그인 세션값 불러오기 및 화면
이미지 미리보기 구현
컨트롤러 정상 동작 확인

 

sql 업로드 확인

학원에서 배운내용을 정리를 하는거지만 정말 어렵다.

나중에 다시 코드리뷰를 해보아야겠다. 

 

 

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

반응형
반응형

페이스북이나 인스타그램 같은 간단한 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

반응형
반응형

오늘은 홈페이지에서 필수적인 파일 업로드이다

 

파일업로드는 커뮤니티 쇼핑몰.. 등등 모든 사이트에 다 들어가는 거같다.

안들어가는 사이트 있으면 알려주실뿐~~?? 😎

 

그래서 스프링이나 다른 언어를 공부할때 많은 라이브러리를 사용을 하는데

메이픈 레포지토리에서 다운 받을 수 있는 

Apache Commons FileUpload 

Apache Commons IO

 

이 2가지를 이용하여 파일 업로드를 구현을 해보자

 

시작🤩

 


 

1. 준비물 세팅

 

https://mvnrepository.com/

위 사이트에서 

 

Apache Commons FileUpload 

Apache Commons IO

 

2 가지를 코드를 카피한다.

 

참고로 

 

Apache Commons FileUpload 는 서블릿 3.0이상 부터 사용이 가능하다.

 

 

		<!-- 서블릿 3.0이상 사용 가능한 파일 업로드 api-->
		<dependency>
		    <groupId>commons-fileupload</groupId>
		    <artifactId>commons-fileupload</artifactId>
		    <version>1.3.1</version>
		</dependency>
		
		<!-- commons-io -->
		<dependency>
		    <groupId>commons-io</groupId>
		    <artifactId>commons-io</artifactId>
		    <version>2.4</version>
		</dependency>

 

레거시 프로젝트 pom.xml에서 넣어주고 꼭 메이븐 업데이트(alt + F5)

를 눌러서 꼬옥! 업데이트를 해주자

 

1-2 객체 등록 및 빈(Bean) 등록

 

Web-INF  > config(Spring) > Servlet-config(이름다를 수 있음!)

위 경로에 객체 및 빈 등록을 해주자 

 

<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
      
      <beans:property name="maxUploadSize" value="10485760" />
      <beans:property name="defaultEncoding" value="utf-8" />
   
   </beans:bean>

이렇게 빈등록을 해주자 

 

반드시 id를 multipartResolver로 선언

 

 

 최대 업로드 가능한 바이트 크기(바이트 단위), -1은 제한이 없음을 의미 -
      <beans:property name="maxUploadSize" value="10485760" />
      
       업로드 요청을 변환할 때 사용할 문자 인코딩 방식 
      <beans:property name="defaultEncoding" value="utf-8" />

 

이 두가지는 꼭 기억해주자 

 


2. 본격적인 코딩😋

 

2-1 JSP 

 

	<!-- 파일 업로드에서는 enctype(인코딩타입)을 multipart/form-data로 반드시 설정 -->
	<form action="upload_ok" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" name="file">
		<input type="submit" value="전송">
	</form>
	
	<br><hr><br>
	
	<!-- 파일 두개이상 붙히는거 -->
	<form action="upload_ok2" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" multiple="multiple" name="files"> 
		<input type="submit" value="전송">
	</form>

연습이기 떄문에 디자인은 생략한다

 

이때

🤓

중요한게 form태그안에 

enctype="multipart/form-data를 입력 해주어야한다.

 

참조 코딩의 시작 TCP School

 

2-1 Class VO 등 제작


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UploadVO {
	private String nmae;
	private MultipartFile file;
}

 

@Getter
@Setter
@ToString
public class MultiUploadVO {
	private List<UploadVO> list;
}

 

파일을 올릴때 한가지면 VO만 작성해도 되지만

2개 이상일 시는 LIst 타입으로 받아준다.

 

2-2 ConTroller 제작

 

//업로드로 가는 메소드
	@GetMapping("/upload")
	public void form() {}
	
	@PostMapping("/upload_ok")
	public String upload(@RequestParam("file") MultipartFile file) {
		String fileRealName = file.getOriginalFilename(); //파일명을 얻어낼 수 있는 메서드!
		long size = file.getSize(); //파일 사이즈
		
		System.out.println("파일명 : "  + fileRealName);
		System.out.println("용량크기(byte) : " + size);
		//서버에 저장할 파일이름 fileextension으로 .jsp이런식의  확장자 명을 구함
		String fileExtension = fileRealName.substring(fileRealName.lastIndexOf("."),fileRealName.length());
		String uploadFolder = "C:\\test\\upload";
		
		
		/*
		  파일 업로드시 파일명이 동일한 파일이 이미 존재할 수도 있고 사용자가 
		  업로드 하는 파일명이 언어 이외의 언어로 되어있을 수 있습니다. 
		  타인어를 지원하지 않는 환경에서는 정산 동작이 되지 않습니다.(리눅스가 대표적인 예시)
		  고유한 랜던 문자를 통해 db와 서버에 저장할 파일명을 새롭게 만들어 준다.
		 */
		
		UUID uuid = UUID.randomUUID();
		System.out.println(uuid.toString());
		String[] uuids = uuid.toString().split("-");
		
		String uniqueName = uuids[0];
		System.out.println("생성된 고유문자열" + uniqueName);
		System.out.println("확장자명" + fileExtension);
		
		
		
		// File saveFile = new File(uploadFolder+"\\"+fileRealName); uuid 적용 전
		
		File saveFile = new File(uploadFolder+"\\"+uniqueName + fileExtension);  // 적용 후
		try {
			file.transferTo(saveFile); // 실제 파일 저장메서드(filewriter 작업을 손쉽게 한방에 처리해준다.)
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "fileupload/upload_ok";
	}

우선 Jsp에서 파일의 name = file로 지정해주었기 떄문에 

@RequestParam("file")로 가져온다. 

 

다음에 getOriginalFilename()로 파일을 업로드하면서 업로드한 파일명을 얻어낸다.

그 후 size를 알고 싶으면 file size를 붙혀 알아낸다. 

여기서 int를 붙히지않는다 용량이 커질 수도 있기 떄문에 

int값을 못 받을 수 있다.

 

물론 int로 받고 빈등록에서 용량을 조절해도 된다 

 

이때 나는 지정한 fileRealName(가져온 파일의 이름)에 확장자를 얻고 싶다.

fileRealName에서 subString을 사용하여  파일명을 조각낸다.

그 후 indexof로 (.)을 지정한 뒤 fileRealName.length()로 하면

예를 들면 이미지 파일일 경우

xxx.jpg 의 jpg를 따오게 된다. 

그래서 fileExtension에는 확장자가 들어가 있다. 

 

String uploadFolde= "C:\\test\\upload";을 지정해 주고 

위에 경로와 동일한 폴더를 생성해준다. 

이곳으로 업로드한 파일을 보낼것이다

 

 

		UUID uuid = UUID.randomUUID();
		System.out.println(uuid.toString());
		String[] uuids = uuid.toString().split("-");
		
		String uniqueName = uuids[0];
		System.out.println("생성된 고유문자열" + uniqueName);
		System.out.println("확장자명" + fileExtension);

그리고 컨트롤러에 왠 UUID 일 수가 있는데

컴퓨터는 동일한 파일명을 저장할 수 없다. 

이렇게 UUID를 생성하여 랜덤한 파일명의 이름으로 저장해준다.

 

File saveFile = new File(uploadFolder+"\\"+uniqueName + fileExtension);  
		try {
			file.transferTo(saveFile); // 실제 파일 저장메서드(filewriter 작업을 손쉽게 한방에 처리해준다.)
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "fileupload/upload_ok";
	}

그리고 saveFile의 이름으로 아까 생성한 경로에 파일이 들어갈 수 있도록 생성해 준뒤

file.transferTo(saveFile);로 파일을 전송해준다. 

 

이때 파일전달에 예외를 던지고 있어 예외처리를 해준다. 

 


파일이 2개 이상 업로드 될 시🤡 

위의 경우는 1가지일 때이다.

 

 

VO는 동일하다.

 

 

 

Jsp 파일이 달라지게 되는데.

 

	<form action="upload_ok2" method="post" enctype="multipart/form-data">
		파일 선택 : <input type="file" multiple="multiple" name="files"> 
		<input type="submit" value="전송">
	</form>

 

 

multiple="multiple"을 써주게 되면

 

이렇게 바뀌게 된다🤓 

 

 

컨트롤러에서 추가된 부분을 찾아보자!

 

@PostMapping("/upload_ok2")
	public String upload2(MultipartHttpServletRequest files) {
		

		//서버에서 저장 할 경로
		String uploadFolder = "C:\\test\\upload";
		List<MultipartFile> list = files.getFiles("files");
		for(int i = 0; i<list.size(); i++) {
			String fileRealName = list.get(i).getOriginalFilename();
			long size = list.get(i).getSize();
			
			System.out.println("파일명 :" + fileRealName);
			System.out.println("사이즈" + size);
			
			File saveFile = new File(uploadFolder + "\\" + fileRealName);
			try {
				list.get(i).transferTo(saveFile);
			} catch (IllegalStateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	
		return "fileupload/upload_ok";
	}

기본적인 형식을 똑같으나.

MultipartHttpServletRequest : 다중파일을 받아 올 수 있다. 

 먼저 와 같이 서버에 저장할 경로를 지정해 준뒤 

List로 파일을 받아오자

List<MultipartFile> list = files.getFiles("files") 파일 여러개라 리스트로받고
반복문을 돌린다. 하나씩 뺴줘야하니까.

 

그 후 반복문 중간에 UUID를 넣어 중복을 막아주면 된다(선택사항)

 

 


3.동작확인

 

파일 등록 후 전송

 

잉 오류 떳다.... ㅇ ㅏ 뭐지 하다가

곰곰히 영문을 보니 아까 빈으로 등록했던 파일의 용량보다 커서 예외처리가 되었다.

그러면 적은 파일을 넣어보자.

 

 

드디어 성공 

자 이제 아까 확인용 println을 보자.

 

아주 잘 가져오는것을 확인 할 수 있다.

uuid로 임의로 변경된 이름으로 저장이 되었다.

 

성공 !!!!

 

 

이렇게 오늘은 파일 업로드를 알아 보았는데

나중에 현업으로 가면 많이 쓰일 것 같다

생각보다 어렵지 않아서 좋당

 

코딩을 배우시는분들 화이팅!!

 

 

 

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

 

MoonSeokHyun - Overview

http://mls0000.dothome.co.kr/. MoonSeokHyun has 7 repositories available. Follow their code on GitHub.

github.com

그럼 이만 !

 

반응형

+ Recent posts