2023. 11. 9. 20:12ㆍSpringboot
1. Requirements
- Spring boot version 2.4.5
2. Stacks
Springboot
Mybatis
- Loombook
- Spring Data JPA
- Spring Boot DevTools
- Spring Security
- Spring Web
- Spring boot version 2.4.5
3. Code
1. profile.jsp
- localhost:8080/user/{principalId}으로 접속했을 때 나오는 첫 페이지
- 해당 밑에 있는 코드는 인증된 정보에 접근하는 방법이며 필자는 모든 jsp파일의 header.jsp에
적어줌으로써 다른 jsp파일은 안 적혀있습니다.
- 해당 페이지는 Springboot package com.pjh.company.config부분에 void configure(HttpSecurity http)에
http.authorizeRequest(). antMatchers("/")를 통해 로그인이 되지 않으면 접근 불가능하게 설계되어 있습니다.
이에 defaultFileUploadPicture.jpg는 직접 설정해줘야 하며 src/main/resources/static/images 폴더 밑에 넣어줘야 합니다.
- 해당 밑에 있는 코드는 인증된 정보에 접근하는 방법이며 필자는 모든 jsp파일의 header.jsp에
적어줌으로써 다른 jsp파일은 안 적혀있습니다.
- 임시 틀이며 추후 추가 예정
- <!--유저이미지-->에 해당하는 것을 클릭 시 모달이 나옴
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="../layout/header.jsp"%>
<!--프로필 섹션-->
<section class="profile">
<!--유저정보 컨테이너-->
<div class="profileContainer">
<!--유저이미지-->
<div class="profile-left">
<div class="profile-img-wrap story-border"
onclick="popup('.modal-image')">
<form id="userProfileImageForm" >
<input type="file" name="profileImageFile" style="display: none;"
id="userProfileImageInput" />
</form>
<img class="profile-image" src="/upload/${dto.user.profileImageUrl}"
onerror="this.src='/images/profilePicture.jpeg'" id="userProfileImage" />
</div>
</div>
<!--유저이미지end-->
<!--유저정보 및 사진등록 구독하기-->
<div class="profile-right">
<div class="name-group">
<h2>${dto.user.name}</h2>
<c:choose>
<c:when test="${dto.pageOwnerState}">
<button class="cta" onclick="location.href='/image/upload'">사진, 파일 등록</button>
</c:when>
<c:otherwise>
<c:choose>
<c:when test = "${dto.subscribeState}">
<button class="cta blue" onclick="toggleSubscribe(${dto.user.id},this)">구독취소</button>
</c:when>
<c:otherwise>
<button class="cta" onclick="toggleSubscribe(${dto.user.id},this)">구독하기</button>
</c:otherwise>
</c:choose>
</c:otherwise>
</c:choose>
<!--사진, 파일 등록-->
<button class="modi" onclick="popup('.modal-info')">
<i class="fas fa-cog"></i>
</button>
<!--톱니바퀴-->
</div>
<div class="subscribe">
<ul>
<li><a href=""> 게시물<span>${dto.imageCount}</span></a></li>
<li><a href="javascript:subscribeInfoModalOpen(${dto.user.id});"> 구독정보<span>${dto.subscribeCount}</span></a></li>
</ul>
</div>
<div class="state">
<h4>${dto.user.bio}</h4>
<h4>${dto.user.website}</h4>
</div>
</div>
<!--유저정보 및 사진등록 구독하기-->
</div>
</section>
<!-- 아직 안만듦 -->
<!--popular.jsp 사용하기-->
<!--게시물컨섹션-->
<section id="tab-content">
<!--게시물컨컨테이너-->
<div class="profileContainer">
<!--그냥 감싸는 div (지우면이미지커짐)-->
<div id="tab-1-content" class="tab-content-item show">
<!--게시물컨 그리드배열-->
<div class="tab-1-content-inner">
<!--아이템들-->
<c:forEach var="image" items="${dto.user.images}">
<div class="img-box">
<a href="/user/${principal.user.id}/file">
<img src="/upload/${image.postImageUrl}" onerror="this.src='/images/defaultFIleUploadPicture.png'"/>
</a>
<div class="comment">
<a href="/user/${principal.user.id}/file" class=""> <i class="fas fa-heart"></i><span>${image.likeCount}</span>
</a>
</div>
</div>
</c:forEach>
<!--아이템들end-->
</div>
</div>
</div>
</section>
<!--로그아웃, 회원정보변경 모달-->
<div class="modal-info" onclick="modalInfo()">
<div class="modal">
<button onclick="location.href='/user/1/update'">회원정보 변경</button>
<button onclick="location.href='/logout'">로그아웃</button>
<button onclick="closePopup('.modal-info')">취소</button>
</div>
</div>
<!--로그아웃, 회원정보변경 모달 end-->
<!--프로필사진 바꾸기 모달-->
<div class="modal-image" onclick="modalImage()">
<div class="modal">
<p>프로필 사진 바꾸기</p>
<button onclick="profileImageUpload(${dto.user.id}, ${principal.user.id})">사진 업로드</button>
<button onclick="closePopup('.modal-image')">취소</button>
</div>
</div>
<!--프로필사진 바꾸기 모달end-->
<div class="modal-subscribe">
<div class="subscribe">
<div class="subscribe-header">
<span>구독정보</span>
<button onclick="modalClose()">
<i class="fas fa-times"></i>
</button>
</div>
<div class = "subscribe-list" id="subscribeModalList">
<!--해당 유저의 구독리스트 모달 -->
</div>
</div>
</div>
<script src="/js/profile.js"></script>
<%@ include file="../layout/footer.jsp"%>
2. profile.js
- let principalId = ${"#principalId"}.val()을 통해 인증된 사용자의 아이디를 저장합니다.
- function storyLoad() : 해당 함수는 page에 따른 스토리 이미지를 가져오는 함수입니다.(이미지가 3개면 3번 반복)
/**
(1) 유저 프로파일 페이지 구독하기, 구독취소
(2) 구독자 정보 모달 보기
(3) 유저 프로필 사진 변경
(4) 사용자 정보 메뉴 열기 닫기
(5) 사용자 정보(회원정보, 로그아웃, 닫기) 모달 -> 스프링 회원수정
(6) 사용자 프로파일 이미지 메뉴(사진업로드, 취소) 모달 -> 스프링 이미지 업로드(1-2)
(7) 구독자 정보 모달 닫기
*/
// (3) 유저 프로파일 사진 변경 (완)
function profileImageUpload(pageUserId, principalId) {
// ${dto.user.id} pageUserId 페이지 접속 아이디
// ${principal.user.id} principalId 로그인한 아이디
console.log("pageUserId : ",pageUserId);
console.log("principalId : ",principalId);
if(pageUserId != principalId){
alert("해당 페이지의 주인이 아닙니다.");
return;
}
$("#userProfileImageInput").click();
$("#userProfileImageInput").on("change", (e) => {
// 값이 변경될 때마다
let f = e.target.files[0];
// f의 files[0]의 value를 변경하고
if (!f.type.match("image.*")) {
alert("이미지를 등록해야 합니다.");
return;
}
let profileImageForm = $("#userProfileImageForm")[0];
// 서버에 이미지를 전송
let formData = new FormData(profileImageForm);
// FormData 객체를 이용하면 form 태그의 필드와 그 값을 나타내는 일련의 k/v 쌍을 담을 수 있다.
// 필수 : default -> x-www-form-urlencoded이기에
// principalId에 해당하는 profileImage의 Url만 자신이 건드릴 수 있음
// 필수 : contentType을 false로 줬을때 QueryString으로 자동 설정 해제
//console.log("지금 서버에 보낼 데이터 : ",formData);
$.ajax({
type:"put",
url:`/api/user/${principalId}/profileImageUrl`,
data: formData,
contentType:false,
processData:false,
enctype : "multipart/form-data",
dataType: "json"
}).done(res=>{
console.log("응답 받았다.");
let reader = new FileReader();
reader.onload = (e) => {
$("#userProfileImage").attr("src", e.target.result);
// $('#userProfileImage').src = "______"을 가져옴
}
// $().attr()
reader.readAsDataURL(f); // 이 코드 실행시 reader.onload 실행됨.
}).fail(error=>{
console.log("오류",error);
});
});
}
// (4) 사용자 정보 메뉴 열기 닫기
function popup(obj) {
$(obj).css("display", "flex");
}
function closePopup(obj) {
$(obj).css("display", "none");
}
// (5) 사용자 정보(회원정보, 로그아웃, 닫기) 모달
function modalInfo() {
$(".modal-info").css("display", "none");
}
// (6) 사용자 프로파일 이미지 메뉴(사진업로드, 취소) 모달
function modalImage() {
$(".modal-image").css("display", "none");
}
// (7) 구독자 정보 모달 닫기, 임시 틀 아직 안만듦
function modalClose() {
$(".modal-subscribe").css("display", "none");
location.reload();
}
3. profile.css
@import "header.css";
@import "footer.css";
/* Fonts */
@font-face {
font-family: is;
src: url(../fonts/billabong.ttf);
}
.profileContainer {
display: flex;
justify-content: space-between;
align-items: center;
width: 935px;
margin: 0 auto;
height: 100%;
}
/* Profile */
.profile {
height: 170px;
display: flex;
margin: 54px 0 44px;
}
.profile > .profileContainer {
padding: 30px 20px 0;
}
.profile .profile-left,
.profile .profile-right {
height: 100%;
}
.profile .profile-left {
flex-basis: 40%;
display: flex;
justify-content: center;
}
.profile .profile-right {
flex-basis: 60%;
}
.profile .profile-left .profile-img-wrap {
width: 162px;
height: 162px;
padding: 6px;
border-radius: 50%;
position: relative;
cursor: pointer;
}
.profile .profile-left .profile-img-wrap img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.profile .profile-right .name-group {
display: flex;
margin-bottom: 20px;
}
.profile .profile-right .name-group h2 {
font-weight: normal;
font-family: "Montserrat", sans-serif;
margin-right: 30px;
}
.profile .profile-right .name-group .cta {
margin-right: 10px;
padding: 5px 9px;
border: 1px solid rgba(var(--ca6, 219, 219, 219), 1);
border-radius: 4px;
color: #262626;
font-weight: bold;
background: transparent;
}
.profile .profile-right .name-group .modi {
border: none;
background: none;
}
.profile .profile-right .name-group .modi i {
font-size: 20px;
padding: 8px;
}
.profile .profile-right .subscribe {
height: 28px;
margin-bottom: 5px;
}
.profile .profile-right .subscribe ul {
display: flex;
}
.profile .profile-right .subscribe ul li {
margin-right: 18px;
}
.profile .profile-right .subscribe ul li a {
font-size: 16px;
color: #262626;
}
.profile .profile-right .subscribe ul li a span {
font-weight: bold;
margin-left: 4px;
}
.profile .profile-right .state h4 {
margin-bottom: 2px;
}
/* story */
.story {
height: 130px;
margin-bottom: 44px;
}
.story .profileContainer {
padding: 0 48px;
display: flex;
justify-content: flex-start;
}
.story .profileContainer .story-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 24px;
cursor: pointer;
}
.story .profileContainer .story-item .stroy-img {
width: 87px;
height: 87px;
padding: 3px;
border: 1px solid gainsboro;
border-radius: 50%;
}
.story .profileContainer .story-item .stroy-img img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.story .profileContainer .story-item .story-name {
padding-top: 10px;
}
.story .profileContainer .story-item .story-name p {
font-size: 14px;
font-weight: bold;
}
/* Tab-controller */
.gallery .profileContainer .controller {
height: 20px;
width: 100%;
border-top: 1px solid #dbdbdb;
}
.gallery .profileContainer .controller .con-list .tab-item {
margin-right: 60px;
height: 100%;
line-height: 53px;
}
.gallery .profileContainer .controller .con-list {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.gallery .profileContainer .controller .con-list a {
color: rgba(var(--f52, 142, 142, 142), 1);
display: inline-block;
height: 100%;
}
.gallery .profileContainer .controller .con-list i {
font-size: 8px;
margin-right: 6px;
vertical-align: 1px;
}
.gallery .profileContainer .controller .con-list span {
font-size: 12px;
}
/* Tab-content 1*/
#tab-content #tab-1-content {
width: 100%;
margin-bottom: 30px;
}
#tab-content #tab-1-content .tab-1-content-inner {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 15px;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box {
position: relative;
height: 293px;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box img {
width: 100%;
height: 100%;
object-fit: cover;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box .comment .img-box button{
position: absolute;
left: 50%;
top: 50%;
width: 100%;
height: 100%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
opacity: 0;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box:hover .comment {
background: rgba(0, 0, 0, 0.6);
opacity: 1;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box .comment a,#tab-content #tab-1-content .tab-1-content-inner .img-box .comment a span{
color: #fff;
}
#tab-content #tab-1-content .tab-1-content-inner .img-box .comment a i {
margin-right: 10px;
}
/* Sub2 */
.popular {
width: 100%;
}
.popular .profileContainer {
flex-direction: column;
}
.popular .profileContainer .popular-gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 30px;
margin-bottom: 30px;
}
.popular .profileContainer .popular-gallery.popular-top .p-img-box {
width: 100%;
min-height: 292px;
}
.popular .profileContainer .popular-gallery.popular-top .p-img-box:nth-child(2) {
grid-column: 2/4;
grid-row: 1/3;
}
.popular .profileContainer .popular-gallery .p-img-box {
width: 292px;
min-height: 292px;
}
.popular .profileContainer .popular-gallery .p-img-box img {
display: block;
width: 100%;
height: 100%;
}
/* Event */
#tab-1-content,
#tab-2-content,
#tab-3-content,
#tab-5-content {
display: none;
}
.show {
display: block !important;
}
.tab-item {
border-top: 1px solid transparent;
}
.tab-border {
border-top: 1px solid #000;
}
.tab-border a i,
.tab-border a span {
color: #000;
font-weight: bold;
}
.profile-image {
border: 2px solid #8a3ab8;
}
.modal-image {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: none;
align-items: center;
justify-content: center;
}
.modal-image .modal {
width: 400px;
background-color: #fff;
border-radius: 10px;
display: flex;
flex-direction: column;
}
.modal-image .modal button {
border: 0;
background-color: transparent;
height: 48px;
cursor: pointer;
}
.modal-image .modal p {
line-height: 50px;
text-align: center;
border-bottom: 1px solid #ddd;
font-weight: bold;
color: #222;
font-size: 18px;
}
.modal-image .modal button:nth-child(2) {
font-weight: 600;
color: #0095f6;
}
.modal-image .modal button:not(:last-child) {
border-bottom: 1px solid #dbdbdb;
}
.modal-subscribe {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: none;
align-items: center;
justify-content: center;
}
.modal-subscribe .subscribe {
width: 600px;
height: 500px;
background-color: #fff;
border-radius: 10px;
display: flex;
flex-direction: column;
}
.modal-subscribe .subscribe .subscribe-header {
text-align: center;
line-height: 50px;
font-weight: bold;
border-bottom: 1px solid #ddd;
position: relative;
}
.modal-subscribe .subscribe .subscribe-header button {
position: absolute;
right: 15px;
top: 15px;
background-color: transparent;
border: 0;
}
.modal-subscribe .subscribe .subscribe-header button i {
font-size: 20px;
}
.modal-subscribe .subscribe .subscribe-list {
overflow: hidden;
overflow-y: auto;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item {
display: flex;
height: 70px;
padding: 10px;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__img {
width: 80px;
text-align: center;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__img {
width: 80px;
text-align: center;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__img img {
width: 50px;
height: 50px;
border-radius: 50%;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__text {
padding-top: 5px;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__text h2 {
font-size: 16px;
line-height: 50px;
}
.modal-subscribe .subscribe .subscribe-list .subscribe__item .subscribe__btn {
margin-left: auto;
display: flex;
align-items: center;
}
.cta {
margin-right: 10px;
padding: 10px 10px;
border: 1px solid rgba(var(--ca6, 219, 219, 219), 1);
border-radius: 4px;
color: #262626;
font-weight: bold;
background: transparent;
}
.cta.blue {
background: #0095f6 !important;
color: #fff !important;
}
.modal-info {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: none;
align-items: center;
justify-content: center;
}
.modal-info .modal {
width: 400px;
background-color: #fff;
border-radius: 10px;
display: flex;
flex-direction: column;
}
.modal-info .modal button {
border: 0;
background-color: transparent;
height: 48px;
cursor: pointer;
}
.modal-info .modal button:not(:last-child) {
border-bottom: 1px solid #dbdbdb;
}
/* Animation */
@keyframes loading {
100% {
stroke: #cd476b;
transform: rotate(200deg);
}
}
/* Media */
@media screen and (max-width: 935px) {
.profileContainer {
width: 100%;
}
}
4. application.yml
- springboot를 실행할 때 기본적으로 필요한 요소를 적음
- datasource는 mariadb를 직접 지정해야 사용할 수 있음
- 변경점 없음
server:
port: 8080(자신이 7070으로 설정했다면 localhost:7070으로 들어가야함)
servlet:
context-path: /
encoding:
charset: utf-8
enabled: true
spring:
mvc:
view:
prefix: (src/main밑에 있는 폴더명에 저장)
suffix: .jsp
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:(mybatis 연결포트)/(자신이 연결한 데이터베이스 이름)?serverTimezone=Asia/Seoul
username: (자신이 만든 아이디)
password: (자신이 만든 비밀번호)
jpa:
open-in-view: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
servlet:
multipart:
enabled: true
max-file-size: 2MB
security:
user:
name: test
password: 1234
file:
path: (자신이 직접 설정하는 경로, 해당 스프링 파일이 있는 경로에 직접 지정하는 것이 좋음)
5. UserController.java
- 해당 개인의 image를 관리하는 것이기에 UserController에 넣어서 사용합니다.
- @Controller : 파일 리턴 컨트롤러
- @AuthenticalPrincipalDetails : 세션의 존재하는 유저(아이디, 비밀번호)를 알고 있는 객체
- @PathVariable : URI의 일부를 템플릿 변수로 사용하여 컨트롤러 메서드의 파라미터에 값을 전달하는 데 사용됩니다.
package com.pjh.comapny.web;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.pjh.comapny.config.auth.PrincipalDetails;
import com.pjh.comapny.domain.user.User;
import com.pjh.comapny.service.UserService;
import com.pjh.comapny.web.dto.user.UserProfileDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@GetMapping("/user/{pageUserId}")
public String profile(@PathVariable int pageUserId,
Model model,
@AuthenticationPrincipal PrincipalDetails principalDetails) {
UserProfileDto dto = userService.회원프로필(pageUserId,principalDetails.getUser().getId());
//userService.회원프로필(id);
model.addAttribute("dto", dto);
// view에 key, value 형태의 데이터를 전송
// controller -> service -> repository -> service -> controller
// 해당 데이터를 저장 및 새로운 값 전달 후, 다시 view에 dto형태를 전달
// "dto" = dto {pageOwnerCount = 1, ...}
return "/user/profile";
}
}
6. UserService.java
- @Service 비즈니스 로직을 구현을 위해 생성하는 service 어노테이션입니다.
- @Transactional DB에 트랜잭션(일의 최소 단위) 처리를 springboot에서 구현한 형태로 수행할 수 있게 합니다.
- @Value : application.yml에 해당하는 file:path부분의 값을 불러옵니다.
package com.pjh.comapny.service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.pjh.comapny.config.auth.PrincipalDetails;
import com.pjh.comapny.domain.user.User;
import com.pjh.comapny.domain.user.UserRepository;
import com.pjh.comapny.domain.user.subscribe.SubScribeRepository;
import com.pjh.comapny.handler.ex.CustomApiException;
import com.pjh.comapny.handler.ex.CustomException;
import com.pjh.comapny.handler.ex.CustomValidationApiException;
import com.pjh.comapny.web.dto.user.UserProfileDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Value("${file.path}")
private String uploadFolder;
@Transactional
public User 회원프로필사진변경(int principalId, MultipartFile profileImageFile) {
UUID uuid = UUID.randomUUID();
// uuid 업로드한 사진이 중복된 경로를 가지지 않게 하기 위해서
// 네트워크 상에서 고유성이 보장되는 id를 만들기 위한 표준 규약
// 유일성이 보장되는 암호
String imageFileName = uuid+"_"+profileImageFile.getOriginalFilename();
// 실제 파일 네임이 올라감 Ex) 1.JPG가 그대로 String imageFileName으로 저장
System.out.println("이미지 파일이름 : "+imageFileName);
Path imageFilePath = Paths.get(uploadFolder+imageFileName);
try {
Files.write(imageFilePath, profileImageFile.getBytes());
}catch(Exception e) {
e.printStackTrace();
}
User userEntity = userRepository.findById(principalId)
.orElseThrow(()->{
throw new CustomApiException("유저를 찾을 수 없습니다.");
});
userEntity.setProfileImageUrl(imageFileName);
// 찾아서 업데이트
return userEntity;
}// 더티 체킹으로 업데이트
}
7. UserRepository
- DB와 직접 맞닿아 있는 인터페이스입니다.
- DB에 처리할 SQL문을 직접 사용할 수 있고 extends 하여 JpaRepository의 crud형태의 기본적인 연산이 지원됩니다.
package com.pjh.comapny.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
// 어노테이션이 없어도 jpaRepository상속을 받으면 IoC 등록이 자동으로 됨.
public interface UserRepository extends JpaRepository<User, Integer>{
// JPA query method 사용
User findByUsername(String username);
}
8.WebMvcConfig.java
- web에 대한 설정 파일
- @Value("${}")에 해당하는 파라미터는 UserService.java와 동일해야 하며,
application.yml의 파일경로가 존재해야 하고 경로 안에 해당 폴더가 있어야 함.
- jsp파일에서 /upload/**로 시작하는 img src문이 있다면 함수 호출이 됨.
- setCachePeriod(60*10*6) 1시간 캐시
package com.pjh.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{ // web설정파일
@Value("${file.path}")
private String uploadFolder;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry
.addResourceHandler("/upload/**") // jsp페이지에서 /upload/** 패턴이 나오면 함수 호출
.addResourceLocations("file:///"+uploadFolder)
.setCachePeriod(60*10*6) // 1시간 cache
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
// 이거 없으면 이미지 onerror화면 출력
}
'Springboot' 카테고리의 다른 글
Spring HTTP 요청과 응답 (0) | 2023.11.13 |
---|---|
Spring boot의 인증 방식 (0) | 2023.11.10 |
스프링 이미지 업로드(2) (0) | 2023.11.07 |
스프링 메모장 (1) | 2023.11.01 |
스프링 이미지 업로드(1) (1) | 2023.10.30 |