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)
댓글 남기기