https://greatps1215.tistory.com/5
https://extracold.tistory.com/40
위 두 사이트를 참조로 진행
폴더 : 152.스프링_멀티파일 업로드
pom.xml
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
<dependency>
<groupId>org.lazyluke</groupId>
<artifactId>log4jdbc-remix</artifactId>
<version>0.2.7</version>
</dependency>
jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<style type="text/css">
#request_file { display: none;}
.my_button {
display: inline-block;
width: 200px;
text-align: center;
padding: 10px;
background-color: #006BCC;
color: #fff !important;
text-decoration: none;
border-radius: 5px;
}
.my_button:hover{color: #fff;}
.imgs_wrap {
border: none;
margin-top: 30px;
margin-bottom: 30px;
padding-top: 10px;
padding-bottom: 10px;
}
.imgs_wrap img {
max-width: auto;
margin-left: 10px;
margin-right: 10px;
max-height: 120px;
padding: 5px;
}
.col-xs-5ths,
.col-sm-5ths,
.col-md-5ths,
.col-lg-5ths {
position: relative;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
.col-xs-5ths {
width: 20%;
float: left;
}
@media (min-width: 768px) {
.col-sm-5ths {
width: 20%;
float: left;
}
}
@media (min-width: 992px) {
.col-md-5ths {
width: 20%;
float: left;
}
}
@media (min-width: 1200px) {
.col-lg-5ths {
width: 20%;
float: left;
}
}
</style>
<div id="myModal1" class="modal modal-child" data-backdrop-limit="1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true" data-modal-parent="#myModal">
<div class="modal-dialog modal-lg">
<div class="modal-content" style="height: 800px;">
<!-- body -->
<div class="modal-body" style="padding: 50px; padding-top: 40px; width: 440px; height: 400px;">
<!-- 부모 모달 -->
<button type="button" class="close" data-dismiss="modal"
id="close1" aria-label="Close"
style="position: relative; bottom: 320px; left: 770px;">
<span class="ion-android-close" aria-hidden="true" style="color: #000; cursor: pointer;"></span>
</button>
<div class="row justify-content-center mt-0">
<div
class="col-11 col-sm-9 col-md-7 col-lg-6 text-center p-0 mt-3 mb-2"
style="width: 500px; padding-left: 10px;">
<div class="card px-0 pt-4 pb-0 mt-3 mb-3" style="width: 700px; position: relative; right: 140px; border: none;">
<h3 id="top"></h3>
<div class="row" style="height: 500px">
<div class="col-md-12 mx-0">
<form id="msform" method="post" enctype="multipart/form-data">
<!-- progressbar -->
<ul id="progressbar">
<li class="active" id="account"><strong>건물 유형</strong></li>
<li id="personal"><strong>수리 유형</strong></li>
<li id="payment"><strong>첨부 사진</strong></li>
<li id="confirm"><strong>간단 요청</strong></li>
</ul>
<!-- fieldsets -->
<fieldset>
<div class="form-card " style="height: 450px;">
<!-- 여기 라디오 버튼 : form -->
<b>* 선택해주세요.</b><br> <br>
<div class="row">
<div class="col-8 col-sm-8">
<input type="radio" name="building_type" value="1" id="structure1" checked="checked">
<span>아파트</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="building_type" value="2" id="structure1">
<span>단독주택</span>
</div>
<div class="col-8 col-sm-8">
<input type="radio" name="building_type" value="3" id="structure1">
<span>빌라/연립주택</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="building_type" value="4" id="structure1">
<span>빌딩/상가</span>
</div>
<div class="col-8 col-sm-8">
<input type="radio" name="building_type" value="5" id="structure1">
<span>기타</span>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-12">
<textarea rows="4" cols="20" maxlength="50" style="border: 1px solid lightgray;" id="building_text" name="building_text"></textarea>
</div>
</div>
<b class="page_number">1/4</b>
</div>
<!-- 버튼 -->
<input type="button" name="next" class="next action-button" value="다음" id="next" />
</fieldset>
<fieldset style="display: none;">
<div class="form-card " style="height: 450px;">
<!-- 여기 라디오 버튼 : form -->
<b>* 선택해주세요.</b><br> <br>
<div class="row">
<div class="col-8 col-sm-8">
<input type="radio" name="repair_type" value="1" id="structure1" checked="checked">
<span>전자제품 수리</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="repair_type" value="2" id="structure1" >
<span>가구수리</span>
</div>
<div class="col-8 col-sm-8">
<input type="radio" name="repair_type" value="3" id="structure1" >
<span>열쇠/도어락 수리</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="repair_type" value="4" id="structure1" >
<span>전기 배선 수리</span>
</div>
<div class="col-8 col-sm-8">
<input type="radio" name="repair_type" value="5" id="structure1">
<span>방충망 및 방범창 수리</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="repair_type" value="6" id="structure1">
<span>문 수리</span>
</div>
<div class="col-8 col-sm-8">
<input type="radio" name="repair_type" value="7" id="structure1">
<span>수도 관련 수리</span>
</div>
<div class="col-4 col-sm-4">
<input type="radio" name="repair_type" value="8" id="structure1">
<span>기타</span>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-12">
<textarea rows="4" cols="20" maxlength="50" style="border: 1px solid lightgray;" id=repair_text name="repair_text"></textarea>
</div>
</div>
<b class="page_number">2/4</b>
</div>
<!-- 버튼 -->
<input type="button" name="previous" class="previous action-button-previous" value="이전" />
<input type="button" name="next" class="next action-button" value="다음" />
</fieldset>
<fieldset style="display: none;">
<div class="form-card" style="height: 400px;">
<!-- 사진을 첨부해주세요.: form -->
<span style="font-size: 15px; color: lightgray;">*최대 10장</span>
<div>
<div class="imgs_wrap row" style="height: 210px;width: 600px;border: none;top: -20px;position: relative;">
<img id="img" style="display: none;"/>
</div>
</div>
<div class="row">
<div class="input_wrap col-12 col-sm-12 text-center">
<a href="javascript:" onclick="fileUploadAction();" class="my_button">파일 업로드</a>
<input type="file" id="request_file" name="request_file" multiple="true" />
</div>
</div>
<b class="page_number">3/4</b>
</div>
<!-- 버튼 -->
<input type="button" name="previous" class="previous action-button-previous" value="이전" />
<input type="button" name="next" class="next action-button" value="다음" />
</fieldset>
<fieldset style="display: none;">
<div class="form-card">
<b>* 공식적인 요청 외 전문가에게 무리한 요구시 요청이 거절될 수 있습니다.</b>
<textarea rows="10" cols="20" maxlength="70" style="border: 1px solid lightgray;" id="simple_req_text" name="simple_req_text" placeholder="내용을 입력하세요."></textarea>
<b class="page_number">4/4</b>
</div>
<input type="button" class="previous action-button-previous" value="이전" />
<input type="button" name="finish" id="finish" class="action-button" value="요청" />
<input type="hidden" name="expert_id" value="${param.expert}" >
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- modal-body -->
</div>
<!-- .modal -->
</div>
<!-- .modal-dialog -->
</div>
<script type="text/javascript">
// 이미지 정보들을 담을 배열
var sel_files = [];
$(document).ready(function() {
$("#request_file").on("change", handleImgFileSelect);
$("#finish").on("click", function(event){
submitAction();
});
});
function fileUploadAction() {
$("#request_file").trigger('click');
}
function handleImgFileSelect(e) {
// 이미지 정보들을 초기화
sel_files = [];
$(".imgs_wrap").empty();
var files = e.target.files;
if (files.length >= 10) {
alert("최대 10장만 업로드 가능합니다.");
return;
}
var filesArr = Array.prototype.slice.call(files);
var index = 0;
filesArr.forEach(function(f) {
if (!f.type.match("image.*")) {
alert("확장자는 이미지 확장자만 가능합니다.");
return;
}
sel_files.push(f);
var reader = new FileReader();
reader.onload = function(e) {
var html = "<div class='col-md-5ths col-xs-6 upload_image' data-file='"+f.name+"' ><a href=\"javascript:void(0);\" onclick=\"deleteImageAction("
+ index
+ ")\" id=\"img_id_"
+ index
+ "\"><img src=\"" + e.target.result + "\" data-file='"+f.name+"' class='request_img_file' title='Click to remove'></a></div>";
$(".imgs_wrap").append(html);
index++;
$(".removeButton").on("click", function(){
$(this).closest('div').remove();
document.getElementById("file").value = "";
});
}
reader.readAsDataURL(f);
});
}
function deleteImageAction(index) {
sel_files.splice(index, 1);
delete sel_files[index];
var img_id = "#img_id_" + index;
$(img_id).val("");
$(img_id).parent("div").remove();
console.log("삭제 후 배열 값: ");
console.dir(sel_files);
}
function cleanInputs(fileEle, oriName){
var fileData=$("#request_file").val();
console.log(" fileData : 파일 ");
console.dir(fileData);
}
function fileUploadAction() {
$("#request_file").trigger('click');
}
function submitAction() {
console.log("업로드 파일 갯수 : " + sel_files.length);
if (sel_files.length < 1) {
//alert("한개이상의 파일을 선택해주세요.");
return;
}
var formData = new FormData($('#msform')[0]);
//삭제 처리시 실질 적으로 업로드할 이미지명 과 비교할 데이터
$(".upload_image").each(function(){
console.log($(this).attr("data-file"));
formData.append('realFiles',$(this).attr("data-file"));
});
formData.append("image_count", sel_files.length);
$.ajax({
type : "POST",
enctype : 'multipart/form-data',
url : '/Request.Ajax',
data : formData,
processData : false,
contentType : false,
cache : false,
success : function(result) {
var re=$.trim(result);
if(re=="success"){
alert("등록 처리 되었습니다.");
location.reload();
// 썸네일 출력 ex
//http://localhost:8080/resources/resources/Requestupload/2020-7-19/thumb_rq202071981184285.jpg
}else if(re=="error"){
alert("등록에 실패 하였습니다.");
}
},
error : function(e) {
}
});
}
</script>
Controller
@ResponseBody
@RequestMapping(value = "/Request.Ajax", method = RequestMethod.POST)
public String request_ajax(@RequestParam("realFiles") List realFiles , MultipartHttpServletRequest request, @RequestParam Map paramMap) throws Exception {
for(String realImage : realFiles) {
logger.info("realFiles : "+realImage);
}
String userId=(String)request.getSession().getAttribute("user_id");
String expertId=(String)request.getSession().getAttribute("expert_id");
/** writer_type 1:사용자 로그인, 2:전문가 로그인 , 3:로그인 안한 유저 */
int writerType=0;
if(userId!=null) {
logger.info("사용자 로그인 아이디 : " +userId);
writerType=1;
paramMap.put("writer", userId);
}else if(expertId!=null) {
logger.info("전문가 로그인 아이디 : " +expertId);
writerType=2;
paramMap.put("writer", expertId);
}
paramMap.put("writer_type", writerType);
int result=expertservice.requestAjax(realFiles, request, paramMap);
return result!=5? "success": "error";
}
@Service
@Override
@Transactional
public int requestAjax(List realFiles , MultipartHttpServletRequest request, Map paramMap) {
logger.info("\n\n requestAjax 요청 파라미터 값 reqMap : " + paramMap.toString());
//1.Request 테이블에 데이터 등록
exdao.insertRequest(paramMap);
logger.info(" 시퀀스 반환 값 : " + paramMap.get("request_no"));
int result = 0;
List fileList = new ArrayList();
/** 파일이 존재할 경우 **/
if (realFiles!=null && request.getFiles("request_file").get(0).getSize() != 0) {
fileList = request.getFiles("request_file");
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
String saveFolder = request.getSession().getServletContext().getRealPath("resources") + File.separator+"Requestupload" + File.separator;
String homedir = saveFolder + year + "-" + month + "-" + date;
File path1 = new File(homedir);
if (!(path1.exists())) {
path1.mkdir();
}
for(String realImage : realFiles) {
for (MultipartFile mf : fileList) {
String originalFilename = mf.getOriginalFilename();
if(realImage.equals(originalFilename)) {
Random r = new Random();
int random = r.nextInt(100000000);
int index = originalFilename.lastIndexOf(".");
String fileExtension = originalFilename.substring(index + 1);
String realFileName = "rq" + year + month + date + random + "." + fileExtension;
String dateFolder = year + "-" + month + "-" + date;
String finalFile = saveFolder + dateFolder + File.separator + realFileName;
String fileDBName = "/" + dateFolder + "/" + realFileName;
try {
// 파일생성
mf.transferTo(new File(finalFile));
String fileName = fileDBName;
String fileOriginal = originalFilename;
String fileThumbName = "/"+ makeThumbnail(saveFolder, fileDBName, originalFilename, dateFolder, realFileName);
//paramMap.put("request_no", requestNo);
paramMap.put("file_name", fileName);
paramMap.put("file_original", fileOriginal);
paramMap.put("file_thumb_name", fileThumbName);
System.out.println("\n\n파일이름 . 위치 = " + index);
System.out.println("원본 파일 명 = " + originalFilename);
System.out.println("이미지 확장자 = " + fileExtension);
System.out.println("새로운 파일명 = " + realFileName);
System.out.println("최종 파일 저장 위치및 파일 명 " + finalFile);
System.out.println("DB에 저장될 파일 내용 = " + fileDBName);
System.out.println("DB에 저장될 썸네일 내용 = " + fileThumbName);
System.out.println("데이터 베이스 등록 : file_name : " + fileName + " , file_original :" + fileOriginal+ " , file_thumb_name : " + fileThumbName);
result=exdao.insertRequestFileData(paramMap);
} catch (Exception e) {
e.printStackTrace();
result = 5;
}
}
}
}
}
return result;
}
/** 250 x 150 크기의 썸네일을 생성 */
private String makeThumbnail(String saveFolder, String fileDBName, String originalFileName, String dateFolder,
String refileName) throws Exception {
int index = originalFileName.lastIndexOf(".");
String fileExt = originalFileName.substring(index + 1);
// 저장된 원본파일로부터 BufferedImage 객체를 생성
BufferedImage srcImg = ImageIO.read(new File(saveFolder + fileDBName));
// 썸네일의 너비와 높이
int dw = 250, dh = 150;
// 원본 이미지의 너비와 높이
int ow = srcImg.getWidth();
int oh = srcImg.getHeight();
// 원본 너비를 기준으로 하여 썸네일의 비율로 높이를 계산
int nw = ow;
int nh = (ow * dh) / dw;
// 계산된 높이가 원본보다 높다면 crop이 안되므로 원본 높이를 기준으로 썸네일의 비율로 너비를 계산
if (nh > oh) {
nw = (oh * dw) / dh;
nh = oh;
}
// 계산된 크기로 원본이미지를 가운데에서 crop
BufferedImage cropImg = Scalr.crop(srcImg, (ow - nw) / 2, (oh - nh) / 2, nw, nh);
// 1.crop된 이미지로 썸네일을 생성
// BufferedImage destImg = Scalr.resize(cropImg, dw, dh);
// 2.원본 이미지의 비율을 유지하면서 높이를 150px
BufferedImage destImg = Scalr.resize(srcImg, Scalr.Method.AUTOMATIC, Scalr.Mode.FIT_TO_HEIGHT, 150);
// 3.원본 이미지의 비율을 유지하면서 너비를 250px
// BufferedImage destImg = Scalr.resize(srcImg, Scalr.Method.AUTOMATIC,
// Scalr.Mode.FIT_TO_WIDTH, 250);
// thumb_ 붙여 썸네일을 저장
String thumbName = saveFolder + dateFolder + File.separator + "thumb_" + refileName;
File thumbFile = new File(thumbName);
ImageIO.write(destImg, fileExt.toUpperCase(), thumbFile);
return dateFolder + "/" + "thumb_" + refileName;
}
mybatis
SELECT request_seq.CURRVAL FROM DUAL
INSERT INTO REQUEST
(REQUEST_NO, EXPERT_ID, WRITER, WRITER_TYPE, BUILDING_TYPE, BUILDING_TEXT, REPAIR_TYPE, REPAIR_TEXT, SIMPLE_REQ_TEXT, REQUEST_DATE)
VALUES(request_seq.NEXTVAL, #{expert_id}, #{writer}, #{writer_type}, #{building_type}, #{building_text}, #{repair_type}, #{repair_text}, #{simple_req_text}, SYSDATE)
INSERT INTO REQUEST_FILE
(FILE_NO, REQUEST_NO, FILE_NAME, FILE_ORIGINAL, FILE_THUMB_NAME, REQUEST_FILE_DATE)
VALUES (request_file_seq.NEXTVAL, #{request_no}, #{file_name}, #{file_original}, #{file_thumb_name}, SYSDATE)
멀티파일 업로드 후에 특정 파일 만 제거처리가 제대로 동작하지 않는다. 특정 파일을 제거를 한후 전송을 해도
멀티파일 업로드 한 것 전체가 업로드 전송처리 되어 진다. 따라서
멀티파일 업로드 후 자바스크립트에서 삭제 처리가 어려워서 다음과 같이
미리보기에서 삭제 된 것들의 대상으로 업로드 파일명 따로 추출후 서버단으로 보내는 작업을 거쳤으며
//삭제 처리시 실질 적으로 업로드할 이미지명 과 비교할 데이터
$(".upload_image").each(function(){
console.log($(this).attr("data-file"));
formData.append('realFiles',$(this).attr("data-file"));
});
서버단에서 다음과 같이 실질 적으로 비교 처리 하는 과정을 거쳤다.
for(String realImage : realFiles) {
for (MultipartFile mf : fileList) {
String originalFilename = mf.getOriginalFilename();
if(realImage.equals(originalFilename)) {

















댓글 ( 5)
댓글 남기기