스프링 유저 프로필 사진

2023. 11. 9. 20:12Springboot

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