Spring boot

2024.08.14 Bank App 만들기 파일 업로드 - 2단계(ResourceHandler 사용) -29

정훈5 2024. 8. 14. 09:52

 

학습 목표

1. WebMvcConfig 에 addResourceHandlers 코드 추가 하기

2. header.jsp 파일 수정

3. 초기 파라메터를 이용해서 경로를 수정해 보기

 

현재 우리 사이트 이미지를 보여줘야 하는 상황 확인 하기

* 로그인 하지 않으면 기본 이미지를 보여 주어야 한다.

* 로그인 후 - 회원 가입 시 이미지를 등록한 사용자 - 회원 가입 시 이미지를 등록하지 않은 사용자

 

WebMvcConfigurer 인터페이스Spring MVC 설정을 맞춤화할 수 있는 메서드를 제공합니다.

개발자는 이 인터페이스를 구현하여 기본 제공되는 스프링 부트의 자동 구성을 수정하거나 추가적인 설정을 할 수 있다.

 

  1. ViewResolver
    뷰 리졸버
    컨트롤러에서 반환된 뷰 이름을 바탕으로 실제 뷰의 위치를 찾아내고 렌더링하는 역할을 합니다.
    예를 들어, JSP 파일이나 Thymeleaf 템플릿 등의 실제 경로를 결정합니다.

  2. addResourceHandlers
    정적 리소스(예: CSS, JavaScript, 이미지 파일 등)핸들링을 위한 경로를 추가하는 데 사용됩니다.
    이를 통해 정적 리소스에 대한 매핑을 정의하고 리소스 캐싱 정책을 설정할 수 있습니다.

  3. setApplicationContext
    스프링 컨텍스트에 접근하거나 커스텀 리소스 로딩 등의 설정을 할 때 사용됩니다.
    ApplicationContextAware 인터페이스를 통해 애플리케이션 컨텍스트를 설정하고 관리합니다.

  4. Template Engine
    템플릿 엔진 설정은 주로 addViewControllers 메서드와 함께 사용되며, 특정 URL 요청에 대해 템플릿 엔진을 통한 뷰를 렌더링하는 방법을 정의합니다.

  5. Interceptor Registration
    인터셉터는 요청 처리의 전/후 또는 요청 처리 중에 특정 로직을 실행할 수 있게 해주는 컴포넌트입니다. addInterceptors 메서드를 통해 인터셉터등록하고 설정할 수 있습니다.

 

WebMvcConfig 코드 추가

더보기

WebMvcConfig

// 코드 추가
	// C:\Lightshot/a.png <-- 서버 컴퓨터상에 실체 이미지 경로지만 
	// 프로젝트 상에서 (클라이언트가 HTML 소스로 보이는 경로는) /images/uploads./**
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		// 1. 
		registry.addResourceHandler("/images/uploads/**")
		.addResourceLocations("file:\\C:\\work_spring\\upload/");
	}

 

Spring MVC의 WebMvcConfigurer 인터페이스를 구현하는 클래스의 addResourceHandlers 메서드를 오버라이드하여,

웹 애플리케이션에서 정적 리소스를 제공하는 방법을 정의합니다.  여기서 정의된 설정은 애플리케이션의 /images/uploads/ URL 패턴으로 요청되는 모든 리소스를 처리하는 방법을 지정합니다.

WebMvcConfig 전체코드

더보기

WebMvcConfig 전체코드

package com.tenco.bank.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.tenco.bank.handler.AuthInterceptor;

import lombok.RequiredArgsConstructor;

@Configuration // 하나 이상의 bean을 등록
@RequiredArgsConstructor // 생성자 대신에 사용할 수 있음
public class WebMvcConfig implements WebMvcConfigurer {
	// implements WebMvcConfigurer : 설정 파일로 사용할 수 있다. 
	
	@Autowired // DI 
	private final AuthInterceptor authInterceptor; // final 사용하면 @Autowired 사용못함
	
	// @RequiredArgsConstructor // 생성자 대신에 사용할 수 있음
	
	// 우리가 만들어 놓은 AuthInterceptor를 등록해야 함.
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authInterceptor)
				.addPathPatterns("/account/**") // /account 뒤에 있는 모든 디렉토리 파일을 의미한다.
				.addPathPatterns("/auth/**");
			
	} // end of addInterceptors()
	
	// 코드 추가
	// C:\Lightshot/a.png <-- 서버 컴퓨터상에 실체 이미지 경로지만 
	// 프로젝트 상에서 (클라이언트가 HTML 소스로 보이는 경로는) /images/uploads./**
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		// 1. 
		registry.addResourceHandler("/images/uploads/**")
		.addResourceLocations("file:\\C:\\work_spring\\upload/");
	}
	
	
	
	
	@Bean // IOC 대상 (싱글톤 처리)
	PasswordEncoder passwordEncoder() { // 회원가입 기능에 DI 해야한다. --> @bean 사용
		return new BCryptPasswordEncoder(); // 회원가입할 때 비밀번호 암호화
	}
	
	
}

 

User.java

더보기

User 추가

	// 이미지 파일명 작업?
	public String setUpUserImage() {
		
	
		// 만약에 업로드 파일이름이 없다면 기본값 https://picsum.photos/id/1/350  아니라면
		// 내 서버 컴퓨터 경로에 있는 /images/uploads/ + uploadFileName 찾아라
		return uploadFileName == null ? "https://picsum.photos/id/1/350": "/images/uploads/" + uploadFileName;
	}

 

User.java 전체부분

더보기

User.java 전체부분

package com.tenco.bank.repository.model;

import java.sql.Timestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class User {
	
	private Integer id;
	private String username;
	private String password;
	private String fullname;
	
	private String originFileName; // 파일 업로드 관련 
	private String uploadFileName; // 파일 업로드 관련 
	
	private Timestamp createdAt;
	
	// 이미지 파일명 작업?
	public String setUpUserImage() {
		
	
		// 만약에 업로드 파일이름이 없다면 기본값 https://picsum.photos/id/1/350  아니라면
		// 내 서버 컴퓨터 경로에 있는 /images/uploads/ + uploadFileName 찾아라
		return uploadFileName == null ? "https://picsum.photos/id/1/350": "/images/uploads/" + uploadFileName;
	}
}

 

header.jsp - JSTL 태그 활용

더보기

header.jsp 수정부분

<h2>About Me</h2>
						<h5>Photo of me:</h5>
						<!--  start of 코드 수정 -->
						<!--  1. 로그인 유무 확인 -->
						<c:choose>
							<c:when test="${principal != null}">
							<%-- 이미지 --%>
								<img class="m--profile" alt="" src="${principal.setUpUserImage()}"> 
							</c:when>
							<c:otherwise>
								<div class="m--profile"></div>	
							</c:otherwise>
						</c:choose>
						<!-- end of 코드 수정 -->

 

 

header.jsp  전체부분

더보기

header.jsp  전체부분

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>  <!-- Core 라이브러리 -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <!-- Core 라이브러리 -->

<!DOCTYPE html>
<html lang="en">
<head>
<title>myBank</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/css/common.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>

</head>
<body>

	<div class="m--flex-container">
		<div class="m-container">
			<div class="jumbotron text-center m--banner-img" style="margin-bottom: 0">
				<h1>My Bank</h1>
				<p>마이바이티스를 활용한 스프링 부트 앱 만들어보기</p>
			</div>



			<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
				<a class="navbar-brand" href="/index">홈</a>
				<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
					<span class="navbar-toggler-icon"></span>
				</button>
				<div class="collapse navbar-collapse" id="collapsibleNavbar">
					<ul class="navbar-nav">
						
						<c:choose>
							<c:when test="${principal != null}">
								<%-- 사용자가 로그인 상태  --%>
								<li class="nav-item"><a class="nav-link" href="/user/logout">로그아웃</a></li>
							</c:when>
							<c:otherwise>
									<%-- 사용자가 로그인 안된 상태  --%>
								<li class="nav-item"><a class="nav-link" href="/user/sign-in">로그인</a></li>
								<li class="nav-item"><a class="nav-link" href="/user/sign-up">회원가입</a></li>
							</c:otherwise>	
						</c:choose>
						
					</ul>
				</div>
			</nav>

			<div class="container" style="margin-top: 30px">
				<div class="row">
					<div class="col-sm-4">
						<h2>About Me</h2>
						<h5>Photo of me:</h5>
						<!--  start of 코드 수정 -->
						<!--  1. 로그인 유무 확인 -->
						<c:choose>
							<c:when test="${principal != null}">
							<%-- 이미지 --%>
								<img class="m--profile" alt="" src="${principal.setUpUserImage()}"> 
							</c:when>
							<c:otherwise>
								<div class="m--profile"></div>	
							</c:otherwise>
						</c:choose>
						<!-- end of 코드 수정 -->
						<p>코린이 개발을 위한 뱅크 앱</p>
						<h3>서비스 목록</h3>
						<p>계좌목록, 생성, 입금, 출금, 이체 페이지를 활용할 수 있어요</p>
						<ul class="nav nav-pills flex-column">
							<li class="nav-item"><a class="nav-link" href="/account/list">나의계좌목록</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/save">신규계좌생성</a></li>
					<%--	<li class="nav-item"><a class="nav-link" href="/account/withdrawl">출금하기</a></li>  --%>
							<li class="nav-item"><a class="nav-link" href="/account/withdrawal">출금하기</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/deposit">입금하기</a></li>
							<li class="nav-item"><a class="nav-link" href="/account/transfer">이체하기</a></li>
						</ul>
						<hr class="d-sm-none">
					</div>

					<!-- end of header.jsp  -->

로그인 했을 시 자신이 등록했던 이미지가 출력된다.

css 에서 m--profile 스타일에 있는 url 이미지 때문에 겹쳐진다.

 


 

초기 파라미터를 이용해서 경로를 수정해 보기

appliaction.yml 파일에 코드 추가

더보기
logging:
  level:
    org.apache.ibatis: DEBUG #MyBatis 로깅 레벨을 DEBUG로 설정하여 실행되는 SQL 쿼리와 내부 로깅 정보를 콘솔에 출력합니다.

    
# 초기 파라메터 설정 
file:
  upload-dir: C:\\work_spring\\upload/

 

UserService.java

더보기

UserService.java

/**
	 * 서버 운영체제에 파일 업로드 기능
	 * 
	 * @param mFile
	 */
	private String[] uploadFile(MultipartFile mFile) {
		// 파일 업로드 구현
		
		// 방어적 코드
		if(mFile.getSize() > Define.MAX_FILE_SIZE) {
			throw new DataDeliveryException("파일 크기는 20MB 이상 클 수 없습니다.", HttpStatus.BAD_REQUEST);
		}
		
		// 서버 컴퓨터에 파일을 넣을 디렉토리가 있는지 검사 
		//	String saveDirectory = uploadDir; // C:\\work_spring\\upload/
		//	System.out.println("saveDirectory : " + saveDirectory);
		//	String saveDirectory = Define.UPLOAD_FILE_DERECTORY; // C:\\work_spring\\upload/
		// 사전기반 지식 - 윈도우, 리눅스
		//	File directory = new File(saveDirectory);
		//	if(!directory.exists()) {
		// 존재하면 true 반환 존재하지 않으면 false 반환
		//		directory.mkdirs();
		//	}
		
		// 코드 수정
		// File - getAbsolutePath() :  파일 시스템의 절대 경로를 나타냅니다.
		// (리눅스 또는 MacOS)에 맞춰서 절대 경로가 생성을 시킬 수 있다.  
		String saveDirectory = (uploadDir);
		System.out.println("saveDirectory : " +  saveDirectory);
		
		
		// 파일 이름 생성(중복 이름 예방)
		String uploadFileName = UUID.randomUUID() + "_" + mFile.getOriginalFilename();
		// 파일 전체경로 + 새로 생성한 파일명
	//	String uploadPath = saveDirectory + uploadFileName;
		String uploadPath = saveDirectory + File.separator + uploadFileName; // 리눅스나 이런데서 사용
		File destination = new File(uploadPath); 
		
		// 반드시 수행 
		try {
			mFile.transferTo(destination); // transferTo 옮기는 작업
		} catch (IllegalStateException | IOException e) {
			e.printStackTrace(); 
			throw new DataDeliveryException("파일 업로드 중에 오류가 발생했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
		}
		
		return new String[] {mFile.getOriginalFilename(), uploadFileName}; // 리턴과 동시에 초기화 작업  
	}