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}">← 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 →</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과 설정하는 것이 좀 다르고, 추가 기능이 있다.
'국비교육(22-23)' 카테고리의 다른 글
58일차(3)/Spring Boot(4) : Boot 에서 DB연동, MyBatis 사용 (0) | 2022.12.29 |
---|---|
58일차(2)/Spring Boot(3) : Spring web project 세팅 / Spring Boot 설정 작성법 (0) | 2022.12.29 |
57일차(2)/Spring(21) : Transaction, DataAccessException 활용 예제 (1) | 2022.12.28 |
57일차(1)/Spring(20) : 게시판 댓글 기능 코드 리뷰 (0) | 2022.12.27 |
56일차(3)/Spring(19) : 게시판 댓글 기능 구현 (0) | 2022.12.27 |