일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 깃허브 블로그
- MSI
- 무결성 제약조건
- 오류모음
- 오류
- 설정
- 오라클
- Ajax
- 백준문제풀이
- for문
- while
- ORA-01407
- 이클립스단축기
- 이클립스
- jsp
- spring
- 환경설정
- Oracle
- 티스토리 블로그
- 전화번호부
- 자바
- 별 찍기
- 파워서플라이
- 스프링
- 인터페이스
- ORA-02292
- 백준
- 순환문
- 공부
- 백준문제
- Today
- Total
danDevlog
Spring - 15 (첨부파일) 본문
첨부파일이 자동으로 저장될 폴더를 생성해준다
C:\upload\temp
web.xml에 첨부파일 설정 추가 (servlet 태그안에)
<!-- 첨부파일 설정 값 -->
<multipart-config>
<location>c:\\upload\\temp</location>
<max-file-size>20971520</max-file-size>
<!-- byte 기본 단위 1024 , 1024 1개 파일의
최대 업로드 용량은 20MB -->
<max-request-size>41943040</max-request-size>
<!-- 동시에 업로드 하는 파일들의 총 크기 -->
<file-size-threshold>20971520</file-size-threshold>
<!-- 스트림으로 처리할 수 있는 최대 크기 초과하면
임시파일로 저장후 업로드 -->
</multipart-config>
servlet-context.xml에 코드를 추가한다.
<beans:bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver" />
<!-- xml 작성시도 ctrl+space 자동 완성 지원함.
StandardServletM 까지만 쓰고 자동완성. -->
데이터베이스에 테이블을 추가해준다.
create table tbl_attach(
uuid varchar2(100) not null, -- 중복되지 않는 구분값
uploadPath varchar2(200) not null, -- 파일 저장 위치
fileName varchar2(100) not null, -- 파일명
filetype char(1) default 'I', -- 파일타입
bno number(10,0) -- 게시물번호(게시물당 여러개 첨부파일)
);
alter table tbl_attach add constraint pk_attach
primary key (uuid); -- 기본키 설정
alter table tbl_attach add constraint fk_board_attach
foreign key (bno)
references tbl_board(bno); -- 외래키 설정
commit;
domain패키지에 값 객체 BoardAttachVO 를 추가한다.
import lombok.Data;
@Data
public class BoardAttachVO {
private String uuid;
private String uploadPath;
private String fileName;
private boolean fileType;
private Long bno;
}
그리고 BoardVO 클래스에 코드를 추가한다.
private List<BoardAttachVO> attachList; // 첨부 파일 목록(부모 자식 관계)
BoardAttachMapper 인터페이스를 생성해준다.
import java.util.List;
import kr.icia.domain.BoardAttachVO;
public interface BoardAttachMapper {
public void insert(BoardAttachVO vo); // 첨부파일 등록
public void delete(String uuid); // 첨부파일 삭제
public List<BoardAttachVO> findByBno(Long bno); // 첨부파일 목록
public void deleteAll(Long bno);
// 첨부파일 여러개 한꺼번에 삭제
public List<BoardAttachVO> getOldFiles();
// 111.zip 커뮤니티를 사용하는 사용자들은
// 중복 파일명을 사용할 수도 있음.
// 시스템은 동일 파일명에 대해서 내부적으로 저장하는 다른 이름을 가짐.
}
BoardAttachMapper.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="com.review.mapper.BoardAttachMapper">
<insert id="insert">
insert into tbl_attach (uuid, uploadpath, filename,
filetype, bno) values (#{uuid},#{uploadPath},#{fileName}
,#{fileType},#{bno})
</insert>
<delete id="delete">
delete from tbl_attach where uuid=#{uuid}
</delete>
<select id="findByBno" resultType="com.review.domain.BoardAttachVO">
select * from tbl_attach where
bno=#{bno}
</select>
</mapper>
register.jsp 파일에 첨부파일 항목을 만들어준다.
<!-- 첨부파일 처리 시작 -->
<br />
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<div class="form-group uploadDiv">
파일 첨부: <input type="file" name="uploadFile" multiple>
</div>
<div class="uploadResult">
<ul></ul>
</div>
</div>
</div>
</div>
</div>
<!-- 첨부파일 처리 끝 -->
input태그의 multiple 속성은 input 요소에 사용자가 둘 이상의 값을 입력할 수 있음을 명시한다.
domain패키지에 AttachFileDto 라는 데이터 전송 객체를 만들어준다.
import lombok.Data;
@Data
public class AttachFileDTO {
// 첨부파일 1개에 대한 처리.
// BoardAttachVO 는 어느 게시물의 첨부파일인지 처리.
// 첨부파일 정보는 디비에 저장, 첨부파일 2진 데이터는 서버의 특정 폴더에 저장,
// 특정 폴더에 파일을 저장하기 위한 클래스.
private String fileName;
private String uploadPath;
private String uuid;
private boolean image;
}
UploadController를 생성해준다.
package kr.icia.controller;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import kr.icia.domain.AttachFileDTO;
import lombok.extern.log4j.Log4j;
import kr.icia.domain.AttachFileDTO;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class UploadController {
String uploadFolder = "c:\\upload";
// opg.srpingframework.htpp.MediaType
@PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody // 응답을 json 형태로 리턴.
public ResponseEntity<List<AttachFileDTO>> uploadAjaxPost(MultipartFile[] uploadFile) {
// rest 방식 으로 ajax 처리
// 파일을 받고 json 값을 리턴.
List<AttachFileDTO> list = new ArrayList<>();
// 여러개 파일 저장을 위한 객체 배열 타입 선언
String uploadFolder = "c:\\upload";
String uploadFolderPath = getFolder();
File uploadPath = new File(uploadFolder, uploadFolderPath);
// 예) c:\\upload\\2022\05\\27에 파일 저장 예정
if (uploadPath.exists() == false) {
uploadPath.mkdirs();
// 경로에 폴더들이 생성되어 있지 않다면, 폴더 생성
}
return new ResponseEntity<>(list, HttpStatus.OK);
}
private String getFolder() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String str = sdf.format(date);
return str.replace("-", File.separator);
// 파일 검색 시간을 줄이기 위해서,
// 폴더 1개에 모두 저장하는 것이 아니라,
// 년월일로 구분하여 폴더를 생성하고 그곳에 파일을 저장.
// File.separator : 폴더 구분자르 운영체제에 맞춰서 변경.
// 2022-04-27
// 2022/04/27 결과적으로 날짜별로 파일 저장.
}
}
register.jsp에 스크립트를 추가해준다.
<script>
$(document).ready(function (e) {
var formObj = $("form[role='form']");
$("button[type='submit']").on("click", function (e) {
e.preventDefault();
console.log("submit clicked");
});
var regex = new RegExp("(.*?)\.(exe|sh|zip|alz)$");
// 정규표현식. 일부 파일의 업로드 제한.
//
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/%EC%A0%95%EA%B7%9C%EC%8
B % 9D
// https://regexper.com/
var maxSize = 5242880; // 5MB
function checkExtension(fileName, fileSize) {
if (fileSize >= maxSize) {
alert("파일 크기 초과");
return false;
}
if (regex.test(fileName)) {
alert("해당 종류의 파일은 업로드 불가.");
return false;
}
return true;
}
$("input[type='file']").change(function (e) {
var formData = new FormData();
var inputFile = $("input[name='uploadFile']");
var files = inputFile[0].files;
// 등록하려는 파일 요소들을 배열 형태로 리턴.
for (var i = 0; i < files.length; i++) {
if (!checkExtension(files[i].name
, files[i].size)) {
return false;
}
formData.append("uploadFile", files[i]);
}
$.ajax({
url: '/uploadAjaxAction',
processData: false,
contentType: false,
data: formData,
type: 'POST',
dataType: 'json',
success: function (result) {
console.log(result);
}
});
});
});
</script>
change() 메소드는 해당하는 요소의 value에 변화가 생길 경우 이를 감지하여 등록된 callback 함수를 동작시킨다.
해당 코드는 input, textarea, select 태그에 동작한다.
파일첨부로 파일이 들어온다면 change함수가 작동하여 파일들을 배열에 담고 순환하면서 각각의 파일들에 대한
파일크기 및 형식을 체크한다.
지금까지 했다면 업로드시 지정한 경로에 폴더는 생성하지만, 파일을 업로드 하지는 않는다.
추가 코드 구현이 필요하다.
UploadController에 코드를 추가한다.
package kr.icia.controller;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import kr.icia.domain.AttachFileDTO;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class UploadController {
String uploadFolder = "c:\\upload";
// opg.srpingframework.htpp.MediaType
@PostMapping(value = "/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody // 응답을 json 형태로 리턴.
public ResponseEntity<List<AttachFileDTO>> uploadAjaxPost(MultipartFile[] uploadFile) {
// rest 방식 으로 ajax 처리
// 파일을 받고 json 값을 리턴.
List<AttachFileDTO> list = new ArrayList<>();
// 여러개 파일 저장을 위한 객체 배열 타입 선언
String uploadFolder = "c:\\upload";
String uploadFolderPath = getFolder();
File uploadPath = new File(uploadFolder, uploadFolderPath);
// 예) c:\\upload\\2022\05\\27에 파일 저장 예정
if (uploadPath.exists() == false) {
uploadPath.mkdirs();
// 경로에 폴더들이 생성되어 있지 않다면, 폴더 생성
}
// 실제 바이너리 파일의 전송 처리 시작.
// 파일은 1개 일수도 있고, 여러개 일수도 있음
for (MultipartFile multipartFile : uploadFile) {
AttachFileDTO attachDTO = new AttachFileDTO();
String uploadFileName = multipartFile.getOriginalFilename();
// 파일의 원래 이름 저장.
// 인터넷 익스플로러 경우, 예외 처리
uploadFileName = uploadFileName.substring(
uploadFileName.lastIndexOf("\\")+1);
// 파일명에 포함된 경로들은 제외하고 실제 파일명만 저장.
attachDTO.setFileName(uploadFileName); // 파일 이름 저장.
UUID uuid = UUID.randomUUID();
// universal unique identifier, 범용 고유 식별자.
// 파일의 중복을 회피.
uploadFileName = uuid.toString() + "_" + uploadFileName;
// 예 ) uuid_일일일.txt
try {
File saveFile = new File(uploadPath, uploadFileName);
// 어느 경로에 어느 파일명으로 저장할지 설정.
multipartFile.transferTo(saveFile);
// 서버에 파일 저장.
attachDTO.setUuid(uuid.toString());
attachDTO.setUploadPath(uploadFolderPath);
list.add(attachDTO);
// 업로드된 파일 정보를 객체 배열에 담아서 리턴.
} catch (Exception e) {
e.printStackTrace();
}
}
// 실제 바이너리 파일의 전송 처리 끝
return new ResponseEntity<>(list, HttpStatus.OK);
}
private String getFolder() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String str = sdf.format(date);
return str.replace("-", File.separator);
// 파일 검색 시간을 줄이기 위해서,
// 폴더 1개에 모두 저장하는 것이 아니라,
// 년월일로 구분하여 폴더를 생성하고 그곳에 파일을 저장.
// File.separator : 폴더 구분자르 운영체제에 맞춰서 변경.
// 2022-04-27
// 2022/04/27 결과적으로 날짜별로 파일 저장.
}
}
향상된 for문을 사용하여 배열의 수만큼 순환한다.
Dto 데이터 전송객체를 생성하고, getOriginalFilename으로 파일의 이름을 알아낸뒤 uploadFilename에 저장한다.
substring : 문자열자르기
lastIndexOf : 문자열에서 탐색하는 문자열이 마지막으로 등장하는 위치에 대한 index 값을 반환
즉, 파일이름이 ~~~~\\example.txt 라는 파일이 있다면, \\에 해당하는 index위치를 lastIndexOf로 반환하고,
거기에 + 1을 더한 e 부터 반환되어 example.txt 라는 파일이름을 얻게된다.
첨부 파일의 목록을 화면에 표시되게 구현한다.
register.jsp 의 input[type='file']" 부터 수정한다
$("input[type='file']")
.change(
function(e) {
// 첨부파일 정보를 변경 한다면,
var formData = new FormData();// 스크립트의 폼 객체.
// 폼에 담고 있지만, 기존과 동일한 항목과 값의 형태로 처리.
var inputFile = $("input[name='uploadFile']");// 화면의 파일 요소를 변수에 할당.
var files = inputFile[0].files;
// .files 를 이용하면 배열로 파일 값들을 리턴.
for (var i = 0; i < files.length; i++) {
if (!checkExtension(
files[i].name,
files[i].size)) {
return false;
}
formData.append("uploadFile",
files[i]);
// 확장자와 크기가 지정한 규격에 맞다면, 폼에 해당 파일 정보를 추가.
}
$.ajax({
url : '/uploadAjaxAction',
processData : false,
contentType : false,
data : formData, // 실제 2진 데이터 전송이 아니고, 파일관련 정보만 전송.
type : 'post',// 첨부파일 처리는 get 방식은 불가.
dataType : 'json',
success : function(result) {
console.log(result);
showUploadResult(result);
}
});
});// end_upload_change
// 첨부파일 목록 시작.
function showUploadResult(uploadResultArr) {
if (!uploadResultArr || uploadResultArr.length == 0) {
// json 처리 결과가 없다면 함수 종료.
return;
}
var uploadUL = $(".uploadResult ul");
var str = "";
// each 구문은 전달된 배열의 길이 만큼,
// each 이후의 함수를 반복 처리.
// https://api.jquery.com/jQuery.each/#jQuery-each-array-callback
$(uploadResultArr).each(function(i, obj) {
var fileCallPath = encodeURIComponent(obj.uploadPath
+ "/" + obj.uuid + "_" + obj.fileName);
// encodeURIComponent :
// uri 로 전달되는 특수문자의 치환.
// & ?
var fileLink = fileCallPath.replace(new RegExp(/\\/g), "/");
// 전달되는 값들 중에서 역슬러시를 찾아서 슬러시로 변경.
str += "<li data-path='";
str += obj.uploadPath+"' data-uuid='";
str += obj.uuid+"' data-filename='";
str += obj.fileName+"' data-type='";
str += obj.image+"'><div>";
str += "<img src='/resources/img/attach.png' width='20' height='20'>";
str += "<span>" + obj.fileName + "</span> ";
str += "<b data-file='"+fileCallPath;
str += "' data-type='file'>[x]</b>";
str += "</div></li>";
});
uploadUL.append(str);
}// end_showUploadResult
ajax 부분에 showUploadResult(result)를 추가했고 이에 대한 함수를 구현하였다.
첨부된 파일들의 이미지 처리를 해준다 (임시로 파일 옆에 이미지가 뜨도록 아무이미지 상관없음)
첨부파일의 [x]를 눌렀을 때, 화면에서 해당 파일을 지우고, 서버의 upload 폴더에서도 파일을 삭제하도록 처리한다.
UploadController 에 삭제처리를 구현한다
@PostMapping("/deleteFile")
@ResponseBody
public ResponseEntity<String> deleteFile(String fileName, String type){
log.info("deleteFile: " + fileName);
File file;
try {
file=new File("c:\\upload\\" + URLDecoder.decode(fileName,"UTF-8"));
// 한글의 경우, 페이지 전환시 변경됨.
// 알맞는 문자 포맷으로 해석해서 읽어 들여야 함.
file.delete();
// 파일 삭제.
} catch(UnsupportedEncodingException e) {
e.printStackTrace();
return new ResponseEntity<> (HttpStatus.NOT_FOUND);
}
return new ResponseEntity<String>("deleted", HttpStatus.OK);
// return null;
}
resister.jsp에 삭제 스크립트를 추가한다.
// 첨부파일 목록에서 삭제 처리 이벤트 시작
$(".uploadResult").on("click","b",function(e){
console.log("delete file");
var targetFile = $(this).data("file");
var type = $(this).data("type");
var targetLi = $(this).closest("li");
$.ajax({
url : '/deleteFile',
data : {
fileName : targetFile,
type :type
},
dataType : 'text',
type : 'POST',
success : function(result){
alert(result);
targetLi.remove();
}
})
});
// 첨부파일 목록에서 삭제 처리 이벤트 끝.
화면 상에서 첨부파일의 등록과 삭제가 잘 이루어 진다면 성공이다.
'Spring 게시판 만들기' 카테고리의 다른 글
Spring - 17 (첨부파일-3) (0) | 2022.04.29 |
---|---|
Spring - 16 (첨부파일 - 2) (0) | 2022.04.29 |
Spring - 14 (댓글 페이징) (0) | 2022.04.26 |
Spring - 13 (댓글 기능 - 페이징) (0) | 2022.04.25 |
Spring - 12 (댓글 기능 -3 View) (4) | 2022.04.25 |