국비교육(22-23)

58일차(1)/Spring(22) : 갤러리 게시판 구현

서리/Seori 2022. 12. 29. 00:39

58일차(1)/Spring(22) : 갤러리 게시판 구현

 

 

- home.jsp에 갤러리 목록보기 링크 추가

 

 

- gallery 관련 기능을 만들기

- 업로드 폼에서 사진의 제목, 사진을 선택해서 upload하면 

  gallery 목록에서 어떤 제목으로, 어떤 사진을, 누가, 언제 업로드했는지 목록이 보이도록!

- 해당 사진을 클릭하면 해당사진 원본보기(자세히보기)로 이동

 

 

- users의 updateform을 참고해서 만들기

- 프로필사진 업로드하는 기능과 같다!

 

table.sql

-- 이미지 갤러리를 만들기 위한 테이블 
CREATE TABLE board_gallery(
	num NUMBER PRIMARY KEY,
    writer VARCHAR2(100),
    caption VARCHAR2(100), -- 이미지에 대한 설명
    imagePath VARCHAR2(100), -- 업로드된 이미지의 경로  ex) /upload/xxx.jpg
    regdate DATE -- 이미지 업로드 날짜 
);

CREATE SEQUENCE board_gallery_seq;

 

- 캡션: 이미지 설명

- imagePath : 경로 ex) /resources/upload/xxx.jpg

 

- 이런 경로가 들어있는 칼럼이다.

 


 

 

* GitHub : 링크

- 업로드, 삭제(본인만), 페이징 처리, 검색까지 완료

- 이렇게 전체 기능은 한번 완성하고.. 코드를 다시 수정 ㅎㅎ

 

 

- CSS 일부 추가 (마우스를 올리면 hover효과 일어나도록)

 


 

com.sy.spring04.gallery.dto / dao / controller / service 패키지생성

 

GalleryDto

package com.sy.spring04.gallery.dto;

import org.springframework.web.multipart.MultipartFile;

public class GalleryDto {
	private int num;
	private String writer;
	private String caption;
	private String imagePath;
	private String regdate;
	private int startRowNum;
	private int endRowNum;
	private int prevNum; //이전글의 글번호
	private int nextNum; //다음글의 글번호
	private MultipartFile image;	//이미지 파일 업로드 처리를 위한 필드

	//디폴트 생성자 
	public GalleryDto() {}

	public GalleryDto(int num, String writer, String caption, String imagePath, String regdate, int startRowNum,
			int endRowNum, int prevNum, int nextNum, MultipartFile image) {
		super();
		this.num = num;
		this.writer = writer;
		this.caption = caption;
		this.imagePath = imagePath;
		this.regdate = regdate;
		this.startRowNum = startRowNum;
		this.endRowNum = endRowNum;
		this.prevNum = prevNum;
		this.nextNum = nextNum;
		this.image = image;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getWriter() {
		return writer;
	}

	public void setWriter(String writer) {
		this.writer = writer;
	}

	public String getCaption() {
		return caption;
	}

	public void setCaption(String caption) {
		this.caption = caption;
	}

	public String getImagePath() {
		return imagePath;
	}

	public void setImagePath(String imagePath) {
		this.imagePath = imagePath;
	}

	public String getRegdate() {
		return regdate;
	}

	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}

	public int getStartRowNum() {
		return startRowNum;
	}

	public void setStartRowNum(int startRowNum) {
		this.startRowNum = startRowNum;
	}

	public int getEndRowNum() {
		return endRowNum;
	}

	public void setEndRowNum(int endRowNum) {
		this.endRowNum = endRowNum;
	}

	public int getPrevNum() {
		return prevNum;
	}

	public void setPrevNum(int prevNum) {
		this.prevNum = prevNum;
	}

	public int getNextNum() {
		return nextNum;
	}

	public void setNextNum(int nextNum) {
		this.nextNum = nextNum;
	}

	public MultipartFile getImage() {
		return image;
	}

	public void setImage(MultipartFile image) {
		this.image = image;
	}

}

 

GalleryDao

package com.sy.spring04.gallery.dao;

import java.util.List;

import com.sy.spring04.gallery.dto.GalleryDto;

public interface GalleryDao {
	//gallery 리스트 가져오기
	public List<GalleryDto> getList(GalleryDto dto);
	//모든 ROW 의 개수
	public int getCount();
	//갤러리에 사진 저장하기
	public void insert(GalleryDto dto);
	//pk num 에 해당하는 DB 에서 gallery 게시글 하나의 data 가져오기
	public GalleryDto getData(int num);

}

 

GalleryDaoImpl

package com.sy.spring04.gallery.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.sy.spring04.gallery.dto.GalleryDto;

@Repository
public class GalleryDaoImpl implements GalleryDao {

	@Autowired
	private SqlSession session;

	/*
	 * Mapper's namespace : gallery
	 * sql's id : getList
	 * parameterType : GalleryDto
	 * resultType : GalleryDto
	 */
	//gallery 의 모든 리스트 가져오기
	@Override
	public List<GalleryDto> getList(GalleryDto dto) {

		return session.selectList("gallery.getList", dto);
	}

	/*
	 * Mapper's namespace : gallery
	 * sql's id : getCount
	 * resultType : int
	 */
	//row 의 총 개수 구하기
	@Override
	public int getCount() {
		return session.selectOne("gallery.getCount");
	}

	/*
	 * Mapper's namespace : gallery
	 * sql's id : insert
	 * parameterType : GalleryDto
	 */
	@Override
	public void insert(GalleryDto dto) {
		session.insert("gallery.insert", dto);
	}

	/*
	 * Mapper's namespace : gallery
	 * sql's id : getData
	 * parameterType : int
	 * resultType : GalleryDto
	 */
	@Override
	public GalleryDto getData(int num) {
		return session.selectOne("gallery.getData", num);
	}

}

 

GalleryMapper 추가

<?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="gallery">
	<select id="getList" parameterType="galleryDto" resultType="galleryDto">
		SELECT *
		FROM	
			(SELECT result1.*, ROWNUM as rnum
			FROM
				(SELECT num, writer, caption, imagePath, regdate
				FROM board_gallery
				ORDER BY num DESC) result1)
		WHERE rnum BETWEEN #{startRowNum} AND #{endRowNum}
	</select>
	<select id="getCount" resultType="int">
		SELECT NVL(MAX(ROWNUM), 0)
		FROM board_gallery
	</select>
	<insert id="insert" parameterType="galleryDto">
		INSERT INTO board_gallery
		(num, writer, caption, imagePath, regdate)
		VALUES(board_gallery_seq.NEXTVAL, #{writer}, #{caption}, #{imagePath}, SYSDATE)
	</insert>
	<select id="getData" parameterType="int" resultType="galleryDto">
		SELECT *
		FROM
			(SELECT num, writer, caption, imagePath, regdate, 
				LAG(num, 1, 0) OVER (ORDER BY num DESC) AS prevNum,
				LEAD(num, 1, 0) OVER (ORDER BY num DESC) AS nextNum
			FROM board_gallery
			ORDER BY num DESC)
		WHERE num = #{num}
	</select>
</mapper>

 

GalleryService

package com.sy.spring04.gallery.service;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.ModelAndView;

import com.sy.spring04.gallery.dto.GalleryDto;

public interface GalleryService {
	//갤러리의 list 가져오기
	public void getList(HttpServletRequest request);
	//갤러리에 사진 upload & DB 저장하기
	public void saveImage(GalleryDto dto, HttpServletRequest request);
	//갤러리에 사진 저장하기 - ajax
	public Map<String, Object> uploadAjaxImage(GalleryDto dto, HttpServletRequest request);
	//갤러리에 사진 저장하기 - db에만 저장(upload 작업은 이미 완료)
	public void insert(GalleryDto dto, HttpServletRequest request);
	//갤러리 detail 페이지에 필요한 data를 ModelAndView 에 저장
	public void getDetail(ModelAndView mView, int num);
}

 

GalleryServiceImpl

package com.sy.spring04.gallery.service;

import java.io.File;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import com.sy.spring04.gallery.dao.GalleryDao;
import com.sy.spring04.gallery.dto.GalleryDto;

@Service
public class GalleryServiceImpl implements GalleryService {
	@Autowired
	private GalleryDao dao;

	//갤러리 이미지 list
	public void getList(HttpServletRequest request) {
		//한 페이지에 몇개씩 표시할 것인지
		final int PAGE_ROW_COUNT=8;
		//하단 페이지를 몇개씩 표시할 것인지
		final int PAGE_DISPLAY_COUNT=5;

		//보여줄 페이지의 번호를 일단 1이라고 초기값 지정
		int pageNum=1;
		//페이지 번호가 파라미터로 전달되는지 읽어와 본다.
		String strPageNum = request.getParameter("pageNum");
		//만일 페이지 번호가 파라미터로 넘어 온다면
		if(strPageNum != null){
			//숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
			pageNum=Integer.parseInt(strPageNum);
		}

		//보여줄 페이지의 시작 ROWNUM
		int startRowNum = 1 + (pageNum-1) * PAGE_ROW_COUNT;
		//보여줄 페이지의 끝 ROWNUM
		int endRowNum = pageNum * PAGE_ROW_COUNT;

		//startRowNum 과 endRowNum  을 GalleryDto 객체에 담고
		GalleryDto dto = new GalleryDto();
		dto.setStartRowNum(startRowNum);
		dto.setEndRowNum(endRowNum);

		//GalleryDao 객체를 이용해서 회원 목록을 얻어온다.
		List<GalleryDto> list = dao.getList(dto);

		//하단 시작 페이지 번호 
		int startPageNum = 1 + ((pageNum-1)/PAGE_DISPLAY_COUNT) * PAGE_DISPLAY_COUNT;
		//하단 끝 페이지 번호
		int endPageNum = startPageNum + PAGE_DISPLAY_COUNT - 1;

		//전체 row 의 갯수
		int totalRow = dao.getCount();
		//전체 페이지의 갯수 구하기
		int totalPageCount = (int)Math.ceil(totalRow / (double)PAGE_ROW_COUNT);
		//끝 페이지 번호가 이미 전체 페이지 갯수보다 크게 계산되었다면 잘못된 값이다.
		if(endPageNum > totalPageCount){
			endPageNum = totalPageCount; //보정해 준다. 
		}

		//request 영역에 담아주기
		request.setAttribute("list", list);	//gallery list
		request.setAttribute("startPageNum", startPageNum);	//시작 페이지 번호
		request.setAttribute("endPageNum", endPageNum);	//끝 페이지 번호
		request.setAttribute("pageNum", pageNum);	//현재 페이지 번호
		request.setAttribute("totalPageCount", totalPageCount);	//모든 페이지 count

	}

	//이미지 추가 - 이미지 업로드 & db 저장
	public void saveImage(GalleryDto dto, HttpServletRequest request) {
		//업로드된 파일의 정보를 가지고 있는 MultipartFile 객체의 참조값을 얻어오기
		MultipartFile image = dto.getImage();
		//원본 파일명 -> 저장할 파일 이름 만들기위해서 사용됨
		String orgFileName = image.getOriginalFilename();
		//파일 크기 -> 다운로드가 없으므로, 여기서는 필요 없다.
		long fileSize = image.getSize();

		// webapp/upload 폴더 까지의 실제 경로(서버의 파일 시스템 상에서의 경로)
		String realPath = request.getServletContext().getRealPath("/resources/upload");
		//db 에 저장할 저장할 파일의 상세 경로
		String filePath = realPath + File.separator;
		//디렉토리를 만들 파일 객체 생성
		File upload = new File(filePath);
		if(!upload.exists()) {
			//만약 디렉토리가 존재하지X
			upload.mkdir();//폴더 생성
		}
		//저장할 파일의 이름을 구성한다. -> 우리가 직접 구성해줘야한다.
		String saveFileName = System.currentTimeMillis() + orgFileName;

		try {
			//upload 폴더에 파일을 저장한다.
			image.transferTo(new File(filePath + saveFileName));
			System.out.println();	//임시 출력
		}catch(Exception e) {
			e.printStackTrace();
		}

		//dto 에 업로드된 파일의 정보를 담는다.
		//-> parameer 로 넘어온 dto 에는 caption, image 가 들어 있었다.
		//-> 추가할 것 : writer(id), imagePath 만 추가로 담아주면 된다.
		//-> num, regdate : db 에 추가하면서 자동으로 들어감
		String id = (String)request.getSession().getAttribute("id");
		dto.setWriter(id);
		//gallery 는 사진 다운 기능이 없다. -> orgFileName, saveFileName, fileSize 저장할 필요X
		//imagePath 만 저장해주면 됨
		dto.setImagePath("/resources/upload/" + saveFileName);

		//GalleryDao 를 이용해서 DB 에 저장하기
		dao.insert(dto);
	}

	//이미지 ajax upload
	public Map<String, Object> uploadAjaxImage(GalleryDto dto, HttpServletRequest request){
		//업로드된 파일의 정보를 가지고 있는 MultipartFile 객체의 참조값을 얻어오기
		MultipartFile image = dto.getImage();
		//원본 파일명 -> 저장할 파일 이름 만들기위해서 사용됨
		String orgFileName = image.getOriginalFilename();
		//파일 크기
		long fileSize = image.getSize();

		// webapp/upload 폴더 까지의 실제 경로(서버의 파일 시스템 상에서의 경로)
		String realPath = request.getServletContext().getRealPath("/resources/upload");
		//db 에 저장할 저장할 파일의 상세 경로
		String filePath = realPath + File.separator;
		//디렉토리를 만들 파일 객체 생성
		File upload = new File(filePath);
		if(!upload.exists()) {
			//만약 디렉토리가 존재하지X
			upload.mkdir();//폴더 생성
		}
		//저장할 파일의 이름을 구성한다. -> 우리가 직접 구성해줘야한다.
		String saveFileName = System.currentTimeMillis() + orgFileName;

		try {
			//upload 폴더에 파일을 저장한다.
			image.transferTo(new File(filePath + saveFileName));
			System.out.println();	//임시 출력
		}catch(Exception e) {
			e.printStackTrace();
		}

		String imagePath = "/resources/upload/" + saveFileName;

		//ajax upload 를 위한 imagePath return
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("imagePath", imagePath);

		return map;
	}

	@Override
	public void insert(GalleryDto dto, HttpServletRequest request) {
		//dto : caption, imagePath 가지고 있다.
		//dto 에 writer(id) 추가
		dto.setWriter((String)request.getSession().getAttribute("id"));

		//GalleryDao 를 이용해서 DB 에 저장하기
		dao.insert(dto);

	}

	//갤러리 detail 페이지에 필요한 data를 ModelAndView 에 저장
	@Override
	public void getDetail(ModelAndView mView, int num) {
		//dao 로 해당 게시글 num 에 해당하는 데이터(dto)를 가져온다.
		GalleryDto dto = dao.getData(num);
		//ModelAndView 에 가져온 GalleryDto 를 담는다.
		mView.addObject("dto", dto);
	}
}

 

GalleryController

package com.sy.spring04.gallery.controller;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.sy.spring04.gallery.dto.GalleryDto;
import com.sy.spring04.gallery.service.GalleryService;

@Controller
public class GalleryController {

	@Autowired
	private GalleryService service;

	//gallery list 페이지로 이동
	@RequestMapping(value = "/gallery/list")
	public String getList(HttpServletRequest request) {
		//view 페이지에 사용될 데이터는 request 영역에 담는다.
		service.getList(request);

		return "gallery/list";
	}

	//gallery 사진 업로드 form 페이지로 이동
	@RequestMapping(value = "/gallery/uploadform")
	public String uploadForm() {

		return "gallery/uploadform";
	}

	//gallery 사진 업로드 & DB 저장
	@RequestMapping(value = "/gallery/upload")
	public String upload(GalleryDto dto, HttpServletRequest request) {
		//form 에서 dto 로 데이터 받아옴
		//dto : caption, MultipartFile image 를 가지고 있다.
		//request :  imagePath 만드는데 사용, session 영역의 id 가져오는데 사용
		service.saveImage(dto, request);

		return "gallery/upload";
	}

	//gallery 사진 업로드 form - ajax form
	@RequestMapping(value = "/gallery/ajax_form")
	public String ajaxForm() {

		return "gallery/ajax_form";
	}

	//gallery 사진 업로드 - ajax
	//json 으로 return 할 것
	@RequestMapping(value = "/gallery/ajax_upload")
	@ResponseBody
	public Map<String, Object> ajaxUpload(GalleryDto dto, HttpServletRequest request){		
		//form 에서 dto 로 데이터 받아옴
		//dto : MultipartFile image 를 가지고 있다.
		//request : imagePath 만드는데 사용, session 영역의 id 가져오는데 사용
		//return : { "imagePath" : "/upload/123456img_name.png" } 형식의 JSON 응답
		return service.uploadAjaxImage(dto, request);
	}

	//imagePath 구성 X -> dto 로 imagePath 를 받아서 DB 에 저장하기
	@RequestMapping("/gallery/insert")
	public String insert(GalleryDto dto, HttpServletRequest request) {
		//dto : caption, imagePath 가지고 있다.
		//request : dto 에 writer(id) 추가
		service.insert(dto, request);

		return "gallery/upload";
	}

	//gallery 게시글의 num 이 parameter get 방식으로 넘어온다.
	//detail 페이지
	@RequestMapping(value = "/gallery/detail", method = RequestMethod.GET)
	public ModelAndView detail(ModelAndView mView, int num) {
		//갤러리 detail 페이지에 필요한 data를 num 으로 가져와, ModelAndView 에 저장
		service.getDetail(mView, num);
		mView.setViewName("gallery/detail");

		return mView;
	}

}

 


 

View page

list.jsp

<%@page import="com.sy.spring04.gallery.dto.GalleryDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/gallery/list.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<style>
   /* card 이미지 부모요소의 높이 지정 */
   .img-wrapper{
      height: 250px;
      /* transform 을 적용할대 0.3s 동안 순차적으로 적용하기 */
      transition: transform 0.3s ease-out;
   }
   /* .img-wrapper 에 마우스가 hover 되었을때 적용할 css */
   .img-wrapper:hover{
      /* 원본 크기의 1.1 배로 확대 시키기*/
      transform: scale(1.1);
   }
   
   .card .card-text{
      /* 한줄만 text 가 나오고  한줄 넘는 길이에 대해서는 ... 처리 하는 css */
      display:block;
      white-space : nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
   }
   	.img-wrapper img{
	   	width: 100%;
	   	height: 100%;
	   	/* fill | contain | cover | scale-down | none(default) */
	   	/*	
	   		cover - 부모의 크기에 맞게 키운 후, 자른다. 비율은 일정하게 증가한다. 
	   		contain - 안잘린다. 대신 빈 공간이 남을 수 있다.
	   		fill - 부모의 크기에 딱 맞게, 비율 관계 없이 맞춘다.(이미지가 일그러질 수 있다.)
	   		scale-down - 가로, 세로 중에 큰 것을 부모의 크기에 맞춘 상태까지만 커지거나 작아지고, 비율은 일정하다.
	   	*/
		object-fit: contain;	
   	}
</style>
</head>
<body>
<div class="container">
   	<a href="${pageContext.request.contextPath}/gallery/uploadform">사진 업로드 하러 가기</a><br/>
   	<a href="${pageContext.request.contextPath}/gallery/ajax_form">사진 업로드 하러 가기2</a>
   	<h1>갤러리 목록 입니다.</h1>
   	<div class="row">
		<c:forEach var="tmp" items="${list }">
			<div class="col-6 col-md-4 col-lg-3">
         		<div class="card mb-3">
            		<a href="${pageContext.request.contextPath}/gallery/detail?num=${tmp.num}">
	               		<div class="img-wrapper">
	                  		<img class="card-img-top" src="${pageContext.request.contextPath }${tmp.imagePath}" />
	               		</div>
            		</a>
            		<div class="card-body">
               			<p class="card-text">${tmp.caption}</p>
               			<p class="card-text">by <strong>${tmp.writer}</strong></p>
               			<p><small>${tmp.regdate}</small></p>
            		</div>
         		</div>
      		</div>
		</c:forEach>
   	</div>
   	<nav>
	<ul class="pagination justify-content-center">
		<c:choose>
			<c:when test="${startPageNum ne 1 }">
				<li class="page-item">
               		<a class="page-link" href="${pageContext.request.contextPath}/gallery/list?pageNum=${startPageNum - 1}">Prev</a>
            	</li>
			</c:when>
			<c:otherwise>
				<li class="page-item disabled">
               		<a class="page-link" href="javascript:">Prev</a>
            	</li>
			</c:otherwise>
		</c:choose>
		<c:forEach var="i" begin="${startPageNum }" end="${endPageNum }">
			<c:choose>
				<c:when test="${i eq pageNum }">
					<li class="page-item active">
                  		<a class="page-link" href="${pageContext.request.contextPath}/gallery/list?pageNum=${i}">${i }</a>
               		</li>
				</c:when>
				<c:otherwise>
					<li class="page-item">
                  		<a class="page-link" href="${pageContext.request.contextPath}/gallery/list?pageNum=${i}">${i}</a>
               		</li>
				</c:otherwise>
			</c:choose>
		</c:forEach>
		<c:choose>
			<c:when test="${endPageNum lt totalPageCount }">
				<li class="page-item">
               		<a class="page-link" href="${pageContext.request.contextPath}/gallery/list?pageNum=${endPageNum + 1}">Next</a>
            	</li>
			</c:when>
			<c:otherwise>
				<li class="page-item disabled">
               		<a class="page-link" href="javascript:">Next</a>
            	</li>
			</c:otherwise>
		</c:choose>
      </ul>
   </nav>   
</div>
<%-- <script>
   // card 이미지의 부모 요소를 선택해서 imgLiquid  동작(jquery plugin 동작) 하기 
   $(".img-wrapper").imgLiquid();
</script> --%>
</body>
</html>

 

uploadform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/gallery/uploadform.jsp</title>
</head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
	<div class="container">
	   	<h1>이미지 업로드 폼</h1>
	   	<form action="${pageContext.request.contextPath}/gallery/upload" method="post" enctype="multipart/form-data">
	      	<div>
	         	<label for="caption">설명</label>
	         	<input type="text" name="caption" id="caption"/>
	      	</div>
	      	<div>
	         	<label for="image">이미지</label>
	         	<input type="file" name="image" id="image"
	            	accept=".jpg, .jpeg, .png, .JPG, .JPEG"/>
	      	</div>
	      	<button type="submit">업로드</button>
	   	</form>
	</div>
</body>
</html>

 

upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/gallery/upload.jsp</title>
</head>
<body>
	<script>
		alert("사진 업로드에 성공했습니다.");
		location.href = "${pageContext.request.contextPath}/gallery/list";
	</script>
</body>
</html>

 

ajax_form.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/gallery/ajax_form.jsp</title>
</head>
<body>
	<div class="container">
   		<form action="${pageContext.request.contextPath}/gallery/insert" method="post" id="insertForm">
      		<input type="hidden" name="imagePath" id="imagePath"/>
      		<div>
         		<label for="caption">설명</label>
         		<input type="text" name="caption" id="caption"/>
      		</div>
   		</form>
   		<form action="${pageContext.request.contextPath}/gallery/ajax_upload" method="post" id="ajaxForm" enctype="multipart/form-data">
      		<div>
         		<label for="image">이미지</label>
         		<input type="file" name="image" id="image" 
            		accept=".jpg, .jpeg, .png, .JPG, .JPEG"/>
      		</div>
   		</form>
   		<button id="submitBtn">등록</button>
   		<div class="img-wrapper">
      		<img />
   		</div>
	</div>

	<script src="${pageContext.request.contextPath}/resources/js/sy_util.js"></script>
	<script>
		//이미지를 선택했을 때, 실행할 함수 등록
		document.querySelector("#image").addEventListener("change", function(){
			//id 가 ajaxForm 인 form 을 ajax 전송 시킨다.
			const form = document.querySelector("#ajaxForm");
			//util 함수를 이용해서 ajax 전송
			ajaxFormPromise(form)
			.then(function(response){
				return response.json();
			})
			.then(function(data){
				//data : {imagePath:"/upload/xxx.jpg"} 형식의 obj
				console.log(data);
				//이미지 경로에 context Path 추가하기
				const path = "${pageContext.request.contextPath}" + data.imagePath;
				//img 태그에 경로 추가
				document.querySelector(".img-wrapper img").setAttribute("src", path);
				//위의 form 의 input hidden 요소에 value 로 넣어서 db 에 저장
				document.querySelector("#imagePath").value = data.imagePath;
			});
		});
		
		document.querySelector("#submitBtn").addEventListener("click", function(){
			document.querySelector("#insertForm").submit();
		});
	</script>
</body>
</html>

 

detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>  
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/gallery/detail.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<div class="container">
   <nav>
      <ul class="breadcrumb">
         <li class="breadcrumb-item">
            <a href="${pageContext.request.contextPath }/">Home</a>
         </li>
         <li class="breadcrumb-item">
            <a href="${pageContext.request.contextPath }/gallery/list.do">겔러리 목록</a>
         </li>
         <li class="breadcrumb-item active">상세보기</li>
      </ul>
   </nav>
   <div class="card mb-3">
      <img class="card-img-top" src="${pageContext.request.contextPath}${dto.imagePath}"/>
      <div class="card-body">
         <p class="card-text">${dto.caption}</p>
         <p class="card-text">by <strong>${dto.writer}</strong></p>
         <p><small>${dto.regdate}</small></p>
      </div>
   </div>
   <nav>
      <ul class="pagination justify-content-center">
         <c:choose>
         	<c:when test="${dto.prevNum ne 0 }">
         		<li class="page-item mr-3">
               		<a class="page-link" href="${pageContext.request.contextPath}/gallery/detail.do?num=${dto.prevNum}">&larr; Prev</a>
            	</li>
         	</c:when>
         	<c:otherwise>
         		<li class="page-item disabled mr-3">
               		<a class="page-link" href="javascript:">Prev</a>
            	</li>
         	</c:otherwise>
         </c:choose>
         <c:choose>
         	<c:when test="${dto.nextNum ne 0 }">
         		<li class="page-item">
               		<a class="page-link" href="${pageContext.request.contextPath}/gallery/detail.do?num=${dto.nextNum}">Next &rarr;</a>
            	</li>
         	</c:when>
         	<c:otherwise>
         		<li class="page-item disabled">
               		<a class="page-link" href="javascript:">Next</a>
            	</li>
         	</c:otherwise>
         </c:choose>         
      </ul>
   </nav>      
</div>
</body>
</html>

 


 

 

- 마우스를 올리면 사진이 천천히 확대되도록 처리했다.(hover)

- 한 페이지에 게시물은 8개씩 나타난다.

 

 

- 자세히보기(detail) 페이지로 들어가면 prev / next 사용해서 이동가능

- detail 페이지의 상단에는 breadcrumb 사용

 

- 업로드는 2가지 방법 사용!

1) 파일 선택-업로드-업로드 버튼을 누르면 올린게 보인다.(리스트로 이동)

  input type="file"로 만든 것.

2) ajax를 이용하는 폼. 파일을 선택하면 페이지 새로고침 없이 한번 이미지를 보여주고,

 그 뒤 업로드 버튼을 누르면 업로드되도록 한 것. 프로필이미지 등록과 같은 원리!

 

- spring에서 이런 식으로 파일을 복사해서 넣을 수도 있다.

- 패키지를 만들어서 해당 파일을 집어넣으면 된다.

 

 

- 갤러리 리스트 (getList) mapper

- 정렬-rowNum붙이기-원하는 내용 select

 

- 페이지처리 (getData) mapper

- 이전 사진의 번호, 다음 사진의 번호를 같이 SELECT할수있도록 LEAD, LAG 사용

 

- dto는 이렇게. table을 바탕으로

 

- input type="file" 로 작성해주어야 한다.

- input name값, label for값과 MultipartFile 타입의 필드명을 같게!

 

- 이 input에 들어온 값은 각각 저 필드에 담기게 된다.

 

 

GalleryController

- 선언된 dto에 caption과 image가 담긴다.

- Service에 dto와 request를 함께 전달.

 

Service SaveImage

- dto와 request에 담겨있는 정보를 활용해서 파일명과 경로 만들어내기

- transferTo()를 사용해서 DB에 저장

- 로그인된 아이디를 담아서 전달하고,

 setImagePath로 저장된 이미지의 경로를 전달한다.

 

 

- 로그인해야만 업로드할 수 있도록, Interceptor에 이렇게 만들기.

- 로그인하지 않으면 업로드할 수 없다.

 

 

- list에는 부트스트랩 로딩하고, 

container의 div안에 "row" 출력

 

 

- 반복문 돌면서 칼럼을 여러개 출력한 것이다.(파란색)

- 칼럼의 크기는 그때그때 다르다. 반응형으로 적용된다.

- (작은 폭에서는) 6/12 사이즈, (보통 폭에서는) 4/12사이즈, (large 디바이스 이상에서는) 3/12 크기를 가지게 하겠다는 뜻

 

- 칼럼 안에는 부트스트랩 card를 집어넣었다.

- card는 img-wrapper와 card-body가 들어 있다.

- img-wrapper 안에는 img-card-top이 있고, 이 이미지에 링크를 걸어두었다.

 

-  card 구조는 부트스트랩의 설명 참조

 

- 이 카드를 반복해서 출력한 것이다.

좁을때는 칼럼하나가 6/12 크기 , 좀 크면 4/12, 더크면 3/12

 

- 브라우저 폭이 넓을 때, 카드의 폭은 전체의 3/12씩을 사용하게 되어 있다.

- 부트스트랩으로 쉽게 반응형 적용 가능!

 

 

- 카드가 가운데 들어온 이유! 이 클래스를 부여했기 때문에.

 

 

** 갤러리의 이미지를 출력하는 방식

 

 

- 어떤 이미지는 좌우가 비고, 어떤 이미지는 위아래가 빈다.

- 이미지 크기를 고정으로 맞춰둔 것이 아니라서 그렇다.

- 폭도 그때그때 변한다.

 

- object-fit 이라는 속성을 사용한다. none 이외에 4가지 종류가 있다.

 

- cover : 비율이 일정하게 증가하고, 이미지는 일부 잘린다. (cover도 많이 사용한다.)

- contain : 이미지를 자르지 않고, 가로/세로의 최대 크기로만 표시한다. 빈 공간이 남는다.

- fill : 무조건 꽉 채운다. 비율이 망가지고, 강제로 틀의 크기에 맞추는 것이다.

- scale-down : 비율은 일정하게, 크기는 부모의 크기에 맞춰진다.

 

- div에 커서가 올라갔을때 이미지를 1.1배 확대하겠다는 것

- 시간여유를 가지고 확대되는 것은 transition 설정을 넣었기 때문이다.

- transition을 0.3초 ease-out 으로 넣어두었다. \

→ 종합하면, 커서를 올리면 자연스럽게 0.3초동안 사진이 커지도록 한다.

 

 

- 설명이 길어도 옆으로 삐져나오지 않도록 설정했다.

- 캡션을 적는 위치에 설명이 많이 길다면 어떻게 할지 처리한 것.

 

- 내용이 길면 이렇게 설명을 더 이상 나오지 않게 하는 기능도 css로 적용할 수 있다.

 

 

- 이 css를 적용하면 너무 긴 컨텐츠는 뒷부분이 생략되어 ... 처리가 된다.

 

- 상단에 "breadcrumb" 이라는 클래스를 부여해서 BreadCrumb을 적용했다.

 

- 카드 하나를 출력하는 div

- 폭을 100% 차지하고 있기 때문에 큰 이미지도 출력할 수 있다.

 

- 파일업로드 기능은 프로필이미지 등록 기능과 동일! 잘 알아두기.

 

 

 

- 내 갤러리도 이런 형태로 적용했다.

 

- Spring Legacy Project는 여기서 끝!

- Spring Boot 는 Spring과 설정하는 것이 좀 다르고, 추가 기능이 있다.