I T H

[스프링프로젝트연습 18] 스프링 프로젝트 구현 - 관리자페이지: 이미지 업로드 본문

Spring Basic

[스프링프로젝트연습 18] 스프링 프로젝트 구현 - 관리자페이지: 이미지 업로드

thdev 2024. 1. 22. 14:49
관리자 페이지용으로 단일 이미지 업로드 테스트를 위한 페이지를 구현하고자 한다.

[ 테이블 생성 ]

프로필 정보를 저장하기 위한 데이터베이스 테이블을 1개 생성한다.

CREATE TABLE TBL_PROFILE_INFO (
  PROFILE_ID varchar(10) NOT NULL PRIMARY KEY,
  PROFILE_NAME varchar(20) NOT NULL,
  PROFILE_JOB varchar(100) NOT NULL,
  PROFILE_EMAIL varchar(100) DEFAULT NULL,
  PROFILE_IMG varchar(100) DEFAULT NULL
)

 

[ Dependency 추가 ]

파일 업로드를 위한 라이브러리를 참조하기 위해 pom.xml 파일에 아래 내용을 추가한다.

참고 > https://commons.apache.org/proper/commons-fileupload/

 

FileUpload – Home

Commons FileUpload The Commons FileUpload package makes it easy to add robust, high-performance, file upload capability to your servlets and web applications. FileUpload parses HTTP requests which conform to RFC 1867, "Form-based File Upload in HTML". That

commons.apache.org

 

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.3.1</version>
		</dependency>

 

[ JavaConfig 설정 ]

-  kr.co.values.init 패키지 아래에 클래스 파일 내용을 수정한다.

-  multipartResolver 빈 등록 

package kr.co.values.init;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

/**
 * servlet-context.xml 파일을 대신하는 클래스 
 * @ComponentScan 을 통하여 컨트롤러 클래스가 존재하는 패키지를 자동 스캔한다.
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "kr.co.values.web" } )
public class ServletConfig implements WebMvcConfigurer {
	private final int MAX_SIZE = 10 * 1024 * 1024;
	
	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		InternalResourceViewResolver bean = new InternalResourceViewResolver();
		bean.setViewClass(JstlView.class);
		bean.setPrefix("/WEB-INF/views/");
		bean.setSuffix(".jsp");
		registry.viewResolver(bean);
	}

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
	}
	
	@Bean
	public MultipartResolver multipartResolver() {
	    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
	    multipartResolver.setMaxUploadSize(MAX_SIZE); // 10MB
	    multipartResolver.setMaxUploadSizePerFile(MAX_SIZE); // 10MB
	    multipartResolver.setMaxInMemorySize(0);
        return multipartResolver;
    }
  }

 

다음으로는 관리자용 페이지를 하나 생성한다. JSP~컨트롤러까지 admin 이라는 이름으로 한 세트를 작성한다.

 

[ JSP ]

webapp/WEB-INF/views/main/admin.jsp 파일 생성한다.

파일 1개에 대한 이미지 업로드를 진행하기 위해 file 태그가 포함된 구문을 작성한다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
</head>
<body>
	<p>ADMIN PAGE</p>
	<label>프로필 이미지</label>
	<input id="file" name="file" type="file" style="padding: 5pt;">
	<label>프로필 이름</label>
	<input type="text" id="profileName">
	<label>프로필 직업</label>
	<input type="text" id="profileJob">
	<label>프로필 이메일</label>
	<input type="text" id="profileEmail">
	<button id="btnRegister">프로필 등록</button>
    
	<script src="/resources/ajax.js"></script>
	<script src="/resources/admin.js"></script>
</body>
</html>

 

[ 이벤트 처리 ]

resources / admin.js 내에 스크립트를 작성한다.

스크립트 내용은 아래와 같다.

- 프로필 정보를 저장할 때 이미지를 등록한 경우에는 이미지(파일) 업로드를 먼저 진행한다.

- 반대로 이미지가 등록되지 않은 경우에는 정보 업데이트만 진행한다.

$(function(){
	
	$("#btnRegister").on("click", function(){
		
		var profileName = $("#profileName").val();
		var profileJob = $("#profileJob").val();
		var profileEmail = $("#profileEmail").val();
		
		if(profileName == null || profileName == ""){
			alert("프로필 이름을 입력하세요.");
			return;
		}
		
		obj = {
			profileName: profileName,
			profileJob: profileJob,
			profileEmail: profileEmail,
			profileImg: ""
		}
		
		// 프로필 저장 시에는 아래와 같은 순서로 처리한다.
		// 1. 프로필 이미지를 포함하여 등록 버튼을 누른 경우에는 이미지를 먼저 업로드하고 (업로트 컨트롤러 호출) 저장된 이미지명을 전달받은 후 
		// 전달받은 이미지명을 파라미터에 포함한 뒤프로필 정보를 저장한다. 
		// 서버사이드에서는 프로필 정보가 이미 저장되어 있는지 체크 후 insert 혹은 update로 프로플 정보를 저장하게 된다.
		
		var fileImg = $("#file").val();
		
		if(fileImg == null || fileImg == ""){ //이미지가 포함되지 않은경우
			dataSaveAjax("/admin/updateProfile.do", obj, "POST");
		} else { //이미지 포함된 경우
			var ext = $("#file").val().split('.').pop().toLowerCase(); // ex) ~.txt -> txt
			if($.inArray(ext, ['gif', 'png', 'jpg', 'jpeg'] == -1)){
				alert('gif, png, jpg, jpeg 파일만 업로드 할수 있습니다.');
				return false;
			}
			
			var formData = new FormData();
			formData.append("file", $("#file")[0].files[0]); //파일 한개
			formData.append("params", JSON.stringify(obj)); 
			formData.append("enctype", "multipart/form-data");
			
			$.ajax({
				url: "/admin/profileUpload.do",
				type: "POST",
				processData: false, //file전송시 필수
				contentType: false, //file전송시 필수
				data: formData,
				dataType: "json",
				success: function(data){
					// 이미지 등록이 완료되면 서버 사이드에서 전달해주는 src 키에 매핑된 파일명 값을 obj 파라미터에 담은 후 
					// 프로필 정보를 저장하는 컨트롤러를 호출한다
					console.log("data", data);
					obj.profileImg = data.src;
					dataSaveAjax("/admin/updateProfile.do", obj, "POST");
				}
			});
		}

	})
})

 

[ 컨트롤러 구현 ]

kr\co\values\web\AdminController.java 파일을 생성하여 아래 내용을 입력한다.

<참고>

transferTo() : 파일을 저장한다

getOriginalFilename() : 파일 이름을 String 값으로 반환한다

getSize() : 파일 크기를 반환한다

getInputStream() : 파일에 대한 입력 스트림을 얻어온다

package kr.co.values.web;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileUploadException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import kr.co.values.service.AdminService;


@Controller
public class AdminController {
	
	@Autowired
	private AdminService adminService;
	
	
    @RequestMapping("/admin/main.do")
    public String main() {
    	
        return "main/admin";
    }
    
    // 프로필 정보 저장
    @RequestMapping("/admin/updateProfile.do")
    @ResponseBody
    public Map<String, Object> updateProfile(@RequestBody Map<String, Object> params) {
    	
    	adminService.updateProfile(params);
    	Map<String, Object> result = new HashMap<String, Object>();
    	result.put("result", true);
    	
        return result;
    }
    
    /**
	 * 업로드 (프로필 이미지 파일 업로드)
	 * for Servlet2.5 Multipart FileUpload
	 * @throws FileUploadException 
	 */
	@RequestMapping("/admin/profileUpload.do")
	@ResponseBody
	public Map<String, Object> profileUpload(@RequestParam("file") MultipartFile part, HttpServletRequest request) throws FileUploadException {
		
		ServletContext context = request.getSession().getServletContext();
		String path = context.getRealPath("/resources/images/profile");

		System.out.println("path : " + path);
		String fileName = part.getOriginalFilename();
		
		System.out.println("fileName : " + fileName);

		InputStream in = null;

		SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
		Date time = new Date();
		String forTime = format.format(time);
		
		int pos = fileName.lastIndexOf(".");
		String ext = fileName.substring(pos + 1);

		try {
			File f = new File(path + "/" + forTime + "." + ext);
			part.transferTo(f);

		} catch (Exception e) {

			throw new FileUploadException(); // 파일업로드를 실패하였습니다.
		} finally {
			if (in != null)
				try {
					in.close();
				} catch (IOException e) {
				}
		}
		
		Map<String, Object> result = new HashMap<String, Object>();
		result.put("src", forTime + "." + ext); // 현재시간으로 변경된 파일명을 화면 뷰로 전달해준다. 이 파일명을 데이터베이스 테이블에 저장하게 된다.

		return result;
    }
    

}

 

[ 서비스 구현 ]

kr\co\values\service\AdminService.java 파일을 생성한다.

package kr.co.values.service;

import java.util.Map;

public interface AdminService {
	
	// 프로필 정보 저장
	void updateProfile(Map<String, Object> map);
	
}

 

서비스 단에서 이미 저장된 프로필 정보가 있는 경우를 체크하여 없으면 insert를 진행하고

반대라면 update를 진행하게 되도록 로직을 구현한다.

 

kr\co\values\service\AdminServiceImpl.java 파일을 생성한다.

package kr.co.values.service;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import kr.co.values.persistence.AdminMapper;

@Service
public class AdminServiceImpl implements AdminService {

	@Autowired
	private AdminMapper adminMapper;

	@Override
	public void updateProfile(Map<String, Object> map) {
		
		// 1. 프로필 정보를 저장하기 전에 프로필 테이블에 이미 저장된 정보가 있는지 체크한다.
		List<Map<String, Object>> list = adminMapper.searchProfile(map);
		
		if(list.size() > 0) { // 정보가 있는 경우에는 업데이트로 프로필 정보를 변경
			map.put("profileId", list.get(0).get("PROFILE_ID"));
			adminMapper.updateProfile(map);
		}
		else { // 정보가 없는 경우에는 인서트로 프로필 정보를 최초 저장 
			adminMapper.insertProfile(map);
		}
	}

	
}

 

[ 매퍼 구현 ]

kr\co\values\persistence\AdminMapper.java 파일을 생성한다.

package kr.co.values.persistence;

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

public interface AdminMapper {
	
	// 프로필 저장 정보 조회 
	public List<Map<String, Object>> searchProfile(Map<String, Object> map);
	
	// 프로필 정보 저장 
	public void insertProfile(Map<String, Object> map);
	
	// 프로필 정보 업데이트 
	public void updateProfile(Map<String, Object> map);
}

 

kr\co\values\persistence\AdminMapper.xml 파일을 생성한다.

<?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="kr.co.values.persistence.AdminMapper"> 
	
	<!-- 프로필 정보 조회  -->
	<select id="searchProfile" parameterType="hashmap" resultType="hashmap">
		SELECT *
		  FROM TBL_PROFILE_INFO
	 	 WHERE 1=1
	</select>
	
	<!-- 프로필 최초 저장 -->
	<insert id="insertProfile" parameterType="hashmap">
		INSERT INTO TBL_PROFILE_INFO (
			  PROFILE_ID
			, PROFILE_NAME
			, PROFILE_JOB
			, PROFILE_EMAIL
			, PROFILE_IMG
		)
		VALUES (
			  'profile' /* 아이디는 고정하여 사용 */
			, #{profileName}
			, #{profileJob}
			, #{profileEmail}
			, #{profileImg}
		)
	</insert>
	
	<!-- 프로필 정보 업데이트 -->
	<update id="updateProfile" parameterType="hashmap">
		UPDATE TBL_PROFILE_INFO 
		   SET PROFILE_NAME = #{profileName}
			 , PROFILE_JOB = #{profileJob}
			 , PROFILE_EMAIL = #{profileEmail}
			 , PROFILE_IMG = #{profileImg}
		 WHERE PROFILE_ID = #{profileId}
	</update>
	
</mapper>

 

[ 실행 결과 ] - /admin/main.do 페이지로 접속 후 기능 테스트