반응형

나는 코로나 시국에 여자친구를 만나려구 핀란드에 가게 되었다.
이전 보다 가기가 힘들었지만 틈틈히 대사관 모니터링을 통해
3가지 제출 서류
1) 코로나 음성확인서 pcr
2) 백신 확인서
3) 완치 확인서

위 세가지 서류중 2가지 가 필요했으나

Green List로 한국이 설정되어
3가지 서류중 한가지 만 가져가면 되었다.




그로 인해 나는 백신을 3차까지 맞았다는 증명서를 챙겼다.
체크인 서류를 확인 한다.

그 후 게이트에서 대기 후 약 15분 정도 지연이 된거같다.

이때 나는 상당히 긴장이 되었는데
핀란드는 상당히 많이 가보았지만

원래 8시간 비행기인데 우크라이나 때문에 경로가 변경 되어
14시간으로 비행기가 돌아간다
무려 약 비행시간이 2배나 늘었다.


마지막 전 친구가 보내준 최후의 커피를 먹고 비행기에 탄다


밤 10시 비행기라 바로 자려고 했는데..



기내식에 핀란드맥주중에 좋아하는 산델스 맥주가 나왔다.

이건 먹어야된다.

기내식은 제육볶음에 밥 조금 이렇게 나온다.

상당히 괜찮았다. 대신 out비행기 에서 기내식은 기대하면 안된다. 핀란드 식으로 나오기 떄문에
한국인 입맛에는 안맞을 수가 있다 ㅠ
나는 한번도 맞은 적이 없다.


자 보아라 경이로운 14시간 ..

밥을 먹고 다행인지 모르겠으나 코로나시국에는 항상
자리가 많아 3열을 혼자쓴다.

그래서 나는 가로로 누워 잔다.
하지만 기체가 흔들릴때마다 잠에서 깨고 비행기에서는 에어컨을 틀기 때문에 처음에는 덥더가 점점 추워진다.

일단 자자 해서 12시 부터 8시까지 푹잣다
정말 놀라웠다. 일부러 몸을 좀 피곤하게 만든것도 있지만
맥주의 힘인가 정말 푹자서 다행이다 자고 일어나니 약 4시간 정도 남아있던거 같다.

4시간 동안 나는 유튜브 + 넷플릭스에서 다운한 여러가지 콘텐츠를 보며 시간을 보냈다 .


맞다 곧 착륙 할 시간이다.

도착하니 핀란드 시간으로 새벽 6시 까마득 했다.
이때 여자친구가 데리러 오기 때문에 입국심사 등을 빠르게 끝내려고 했다.

도착 후 넘나 친절하게 한국어로 도착만 따라가면 된다.

그리고 여태까지 핀란드에서 수많은 입국 심사를 했는데 이번이 제일 심했다
무려 20분 정도 당한듯 ;;

앞에서 기다리는 사람이나 당하는 사람이나 핀란드 사람 일처리는 알아주어야 한다.
질문 하나 하고 타이핑 질문하나 하고 타이핑

나같은 경우에는 일단 아웃티켓이 3개월 뒤이기 때문에  질문 목록은 다음과 같다.

1) 어디에 가는가?
- 여자친구 집에 간다.
2) 여자친구 친구 집은 어디인가?
- 000 에 간다
3) 얼마나 핀란드에 머무나?
- 3개월 정도
4) 아웃티켓 있냐?
- ㅇㅇ 바우처 보여드림
-5) 여자친구랑 만난기간은? (이때 부터 기분이 좀 나쁘기 시작함)
-n년정도 만났다.
6). 집주소는 아는가?


아 집주소..
맞다 나는 집주소를 모른다.

집주소 모른다니까 ㅇㅋㅇㅋ 하고 그냥 보내줄주 알았는데
뒤에 승무원 있으니까 일단 너 옆으로 가고 승무원 먼저 해줄게 이러면서 한 10분 기달렸다.
이건 매너니까 .. ;; 승무원이 왕이지 꼬우면 내가 승무원 해야지

여자친구랑 통홰 후 집주소를 받고
주소를 보여주고 약 10가지 넘는 질문을 받고 범죄자 취급 당하며 .. 나는 입국 스탬프를
받을 수 있엇다.
나는 여태 핀란드만 in out 티켓이 약 5개가 넘는다;; 이게 제일 의외다…



그 후 기차를 타고 여자친구 손을 잡고 간다~~

핀란드 1일차 끝

반응형
반응형

우선 앞서 말하지만 나는 6년정도 회계 + 인사 부서에서 나이대비 괜찮은 연봉을 받으며 지내다 회의감에 절여져
개발자로 직무전환을 하려고 했다.

그 첫번째 단계인 국비지원 학원이 금일로써 끝낫다.
이번 처음 프로젝트를 하면서 많은 부분을 배워나갔고 생각하게 되었다.

나는 4월 1일 수료이지만 나는 3월 31일 여자친구를 보러 핀란드에 와 있다.

우선 첫 개발자로써 프로젝트라 좀 설렛던거 같다.

처음 회의는 5명의 팀끼리 학원 밑 카페에서 주제와 Wireframes를 제작 하게 되었다.

와이어프레임을 제작하고 나는 그것을 토대로 간략하게 사이트를 만들어 보았다.

약 2주간 프론트엔드 기간으로 잡고 5명의 팀원에서

팀원1 : 메인페이지 제작
팀원2 : 유저 회원가입등 제작
팀원3,4 : 어드민 페이지제작
팀원 5: 게시판제작

처음 말이 부트스트랩을 쓰냐 안쓰냐고 말이 있엇지만
부트는 쓰지 않기로 하고 나름 자부심을 느끼며 작업을 하려고 했다.

하지만 나중에 안 사실로는 부트를 쓰는게 맞았다고 본다.
확실히 전문가가 만튼 툴로 만든 사이트는 디자인에 디자도 모르는 팀원들이 감당하기에는 너무나 높은 산인듯 했다.
퀄리티 차이가 너무 심하였다.
하지만 개발자로서 처음 프로젝트 이며 내 사이트를 손수 만들어보고 싶다는 생각이 우선이 었다.

오마주사이트 없이 그냥 에타라는 사이트를 비교해가며 디자인을 해왔는데 현실감이 떨어졌엇다 .

우여곡절끝에 처음생각한 프론트 화면디자인 2주 걸리는 시간이 생각보다 절약되어 약 10일 정도에 완료 되었다.

그 뒤 서버 및 데이터베이스 세팅을 하였는데

생각보다 데이터베이스 세팅이 너무 단조롭게 진행되었다.

우리가 생각한 기능은 다음과같다.

1) 다중게시판  :  커뮤니티 이다 보니 다채로운 카테고리가 존재해야함.
2) 댓글 : 커뮤니티 소통의 기본
3) 쪽지 , 스크랩 기본 커뮤니티에 준하는 기능들 좋아요 싫어요 등
4) 관리자페이지

그리고 내가 맡은 부분은 게시판 댓글 어드민페이지 그 외 팀원들이 막히는 부분이 있으면 도와주었다.
나도 팀원들에게 많은 도움을 받았다.

그 후 나는 기존에 보여줄 것이 게시판 밖에 없어 고민 하던차에
어드민 페이지에 힘들 실어 주기로했다.

발표는 내가 맡기로 하였지만 발표시간과 출국시간의 차이로 아쉽게 발표는 하지 못했다.

어드민 페이지에서는 모든 정보를 한번에 볼수 있으며 여러가지 정보를 관리자가 한페이지에 관리했으면 한다.
내가 어드민페이지에서 가장 충실하게 만든 부분이라고 할수있다 .

기능은 다음과 같다 .
메인 페이지 : 가입회원 및 게시글이 계속 순차적으로 올라오면 원페이지에서 확인가능
회원 관리 : 모든 회원을 관리하며 가입 승인 거절 추방 대기 4가지로 관리를 한다. 그리고 이페이지에서 모달로 게시글 댓글을 볼수 있다. 데이터테이블을 활용하여 페이징등을 진행하였다.
게시판 관리 : 국내취업 해외취업 … 삭제게시판 등 약 6가지 카테고리에 중분류로 관리가 되며 데이터테이블로 소트가 가능하다. 그리고 글에 접근및 이페이지에서 삭제가 가능하다.
데이터 일별 조회 : 날자 to 날자를 선택하면 그 해당하는 날자에 대해 모든 게시판의 갯수가 나오며 구글차트로 데이터 시각화를 제공한다.

이렇게 4가지로 나누게 된다.

100퍼센트 구현을 했는데  역시나 기능상으로 아쉬운것없지만 항상 아쉬운 부분은 내생각에는 역시
디자인이다. 유튜브에서 내가 자주 보는 개발자 유튜브에서는 일단 기능이 다 좋아도 디자인이 좋아야 즉 보기가 좋아야 눈길이 간다는 것이었다.

왜? 만들때는 알지 못하였을까

이번 핀란드에서 포트폴리오가 많으면 2개 적으면 1개 정도 나올 거 같은데
일단 한번 디자인부터 싹 혼자 다시 만들어보면서 감각을 키워야 겠다.

최종 발표를 짐싸면서 줌으로 들었는데

결과적으로는 후련했다.

이렇게 나의 첫번째 프로젝트가 끝나고 다음 프로젝트는 개인 프로젝트 1개 ~2개 정도 준비 후에
핀란드에서 나가 한국에 출국하게 되면 면접보러 다녀야겠다.

반응형
반응형

국비학원을 다니는데 계속 확진자가 나왔다.

하지만 프로젝트 떄문에 계속 학원에 나가게 되었는데

어는날 코로나에 확진이 되었다... ㅠㅠ 

 

처음에 확진될 때 코로나 키트 옅은 두줄이 뜬다.

그리고 3월 15일날 빠르게 병원으로 달려가서 코로나 검사를 받았다. 

그랫더니 빠르게 2줄이 뜨고 나는 코로나 확진자가 되어 7일간 자가격리를 했다. 

 

그리고 가족들도 코로나 검사를 했지만 모두 음성이 나왔다. 

정말 다행이다. 

 

병원에서 5일치 약을 받고 2일차 까지는 별다른 증상이 없었다 ..

 

그리고 3일차부터 목이 쉬기 시작하여 목에 대한 통증이 말도 못할 정도로 심하였다. 

 

거의 갑상선암 수술받은 이후.. 정도 인거같다. 

 

나는 중증환자로 분류되어 하루에 2번 의료진에게 전화가 왔엇다. 

코로나 약은 받지 않았다. 

 

4일차에 코로나 검사를 또 해보았다.

계속 두줄... 목이 아프고 밥도 따로먹고 

일주일간 씻지도 못했다. 

계속 코딩만 한듯. . 

 

밖에 나가고 싶지만 못나가서 정말 답답했다. 

하지만 내일 자가격리가 끝나 너무 다행이다. 

 

오늘 코로나 검사를 했는데 한줄이 떳다. 키트값만 한 5만원 넘게 들어간듯.. 너무 비싸다 .. 

 

요약

코로나 확진 3월 15일 키트 검사비 + 5일치 약값 해서 9,000원 

3월 16일 ~ 3월 18일 까지 목 통증 및 심한 기침

3월 19일 부터 서서히 나아짐

3월 21일 코로나 검사 완전 한줄 

3월 22일 코로나 격리 해제!!

 

이제 완전히 면역가 된 것인가. 

 

나는 3차까지 맞았다. 

 

그나마 3차까지 맞아서 데미지가 별로 없는듯하다. 

 

다른 친구들은 거의 죽을 뻔했다고 하지만 나는 별로.. 목 통증이 좀 심했긴 했다. 

이제 핀란드에 갈때까지 약 10일 남았는데

정말 지금 걸려서 정말 다행이다!!

반응형
반응형

오늘은 게시판 별 카테고리(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

반응형
반응형


저번주에 오랜만에 떡볶이가 당겨 신당동 떡볶이타운에 다녀왔습니다.
평소에 마복림이나 종점떡볶이등 어렸을 때 많이 다녀왔지만
최근 몇년이사에 다녀온 적이 없어 예전에 단골 이었던 아이러브떡볶이집을 소개 해볼까 합니다.

- 개인적인 주관으로 작성된 포스팅입니다.-



신당동 떡볶이 떡볶이 골목


골목의 자세한 역사는 모르지만 적어도 나의 추억이 담겨있는 곳이기도 하다.
친구와 쌈짓돈을 뭉쳐 먹던 떡볶이
정말 맛있게 먹었엇다.
예전 생각이나 가족들과 한번 방문을 해보았다.

신당동 떡볶이 골목을 알리는

정말 방문하자마자 저 간판이 있는데 저것을 보고 와 정말 하나도 변하지 않았구나를 느꼇다.



수많은 호객행위를 뒤로하고 목적지 였던 아이러브에 도착 정말 수많은 차들과 손님들이 있엇다.
정말 코로나 시즌이 맞는건자 싶었다.



가격표

우리는 4명에서 순대 1인분 닭발 1인분 기본 떡볶이 3인분을 시켰다.


떡볶이

야끼만두왜 쫄면을 추가했다.
생각보다 비주얼도 괜찮고 얼른 먹고싶었다.

정신없이 먹어서 끓고 난 사진은 없디..

순대 이건 비추천한다.
비싸기만하고..


국물 닭발인데 맛있긴 하다. 하지만 7000원의 값은 하지 않는것같다.

양이 정말 적다 달발 한 12개 정도 들은 거같다.

그렇게 추천하고 싶지는 않다.


3인 기준으로 저렴하게 먹었다.
31500원이면 1인당 8000원 꼴이니 일반 백반집정도의 가격이면
됬엇다. 하지만


닭발과 순대를빼고 차라리 떡볶이에 들어가는 것을 추가 하자.



볶음밥 사진을 추가 하고 싶었지만

없다..
이미 먹고 난 뒤였다..

가격 5/5
맛 4/5
양 3/5
재방문의사 3/5

반응형

+ Recent posts