🥇 VS-Code [ jsp ]
📺 JSP 본문 코드
< form action = "profile" method = "POST" name = "myPageFrm" id = "profileFrm" >
< div class = "profile-image-area" >
< img src = "/resources/images/user.png" id = "profileImage" >
</ div >
< span id = "deleteImage" > x </ span >
< div class = "profile-btn-area" >
< label for = "imageInput" > 이미지 선택 </ label >
< input type = "file" name = "profileImage" id = "imageInput" accept = "image/*" >
< button > 변경하기 </ button >
</ div >
< div class = "myPage-row" >
< label > 이메일 </ label >
< span > 로그인 회원 이메일 </ span >
</ div >
< div class = "myPage-row" >
< label > 가입일 </ label >
< span > 로그인 회원 가입일 </ span >
</ div >
</ form >
📺 기본 브라우저화면
🥈 VS-Code [ js ]
📺 JS 코드 ( 추가, 변경, 삭제 )
// 프로필 이미지 추가 / 변경 / 삭제
const profileImage = document . getElementById ( "profileImage" ); // img 태그
const deleteImage = document . getElementById ( "deleteImage" ); // x 버튼
const imageInput = document . getElementById ( "imageInput" ); // input태그
let initCheck ; // 초기 프로필 이미지 상태를 저장하는 변수
// false == 기본 이미지, true == 이전 업로드 이미지
let deleteCheck = - 1 ;
// 프로필 이미지가 새로 업로드 되거나 삭제 되었음을 나타내는 변수
// -1 == 초기값, 0 == 프로필 삭제(x 버튼), 1 == 새 이미지 업로드
let originalImage ; // 초기 프로필 이미지 파일 경로 저장
if ( imageInput != null ){ // 화면에 imageInput이 있을 경우
// 프로필 이미지가 출력되는 img태그의 src 속성을 저장
originalImage = profileImage . getAttribute ( "src" );
if ( originalImage == "/resources/images/user.png" ){
// 기본 이미지인 경우
initCheck = false ;
} else {
initCheck = true ;
}
// 회원 프로필 화면 진입 시
// 현재 회원의 프로필 이미지 상태를확인
imageInput . addEventListener ( "change" , e => {
// 2MB로 최대 크기 제한
const maxSize = 1 * 1024 * 1024 * 2 ; // 파일 최대 크기 지정(바이트 단위)
console . log ( e . target ); // input
console . log ( e . target . value ); // 업로드된 파일 경로
console . log ( e . target . files ); // 업로드된 파일의 정보가 담긴 배열
const file = e . target . files [ 0 ]; // 업로드한 파일의 정보가 담긴 객체
// 파일을 한번 선택한 후 취소했을 때
if ( file == undefined ){
console . log ( "파일 선택이 취소됨" );
deleteCheck = - 1 ; // 취소 == 파일 없음 == 초기상태
// 취소 시 기존 프로필 이미지로 변경
profileImage . setAttribute ( "src" , originalImage );
return ;
}
if ( file . size > maxSize ){ // 선택된 파일의 크기가 최대 크기를 초과한 경우
alert ( "2MB 이하의 이미지를 선택해주세요" );
imageInput . value = "" ;
// input type="file" 태그에 대입할수 있는 value는 ""(빈칸) 뿐이다!
deleteCheck = 1 ; // 취소 == 파일 없음 == 초기상태
// 기존 프로필 이미지로 변경
profileImage . setAttribute ( "src" , originalImage );
return ;
}
// JS에서 파일을 읽는 객체
// - 파일을 읽고 클라이언트 컴퓨터에 파일을 저장할 수 있음
const reader = new FileReader ();
reader . readAsDataURL ( file );
// 매개변수에 작성된 파일을 읽어서 저장 후
// 파일을 나타내는 URL을 result 속성으로 얻어올 수 있게 함
// 다 읽었을 때
reader . onload = e => {
//console.log(e.target);
//console.log(e.target.result); // 읽은 파일의 URL
const url = e . target . result ;
// 프로필이미지(img) 태그에 src 속성으로 추가
profileImage . setAttribute ( "src" , url );
deleteCheck = 1 ;
}
});
}
* Key Point !
- chang 이벤트 : 값이 변했을때 적용되는 이벤트 ( input type = "file", "checkbox", "radio"에 많이 사용 )
- text / number 형식 사용가능 -> input값 입력 후 포커스를 잃었을때 이전 값과 다르면 chang 이벤트 발생
📺 console.log() 결과 값
- console.log(e.target) : ↓ 첫번째 값
- console.log(e.target.value) : ↓ 두번째 값
- console.log(e.target.files) : ↓ 세번째 값
-> * files는 무조건 배열 형식으로 나오기 때문에 무조건 [0]으로 사용해야한다
- reader.onload = e => { } console.log() 결과값
- console.log(e.target) : ↓ 첫번째 값
- console.log(e.target.result) : ↓ 두번째 값 URL
📺 JS 코드 ( x 버튼 클릭 )
// x 버튼 클릭 시
deleteImage . addEventListener ( "click" , e => {
// 프로필 이미지 기본이미지 변경
profileImage . setAttribute ( "src" , "/resources/images/user.png" );
imageInput . value = "" ; // input type="file"의 value 삭제
deleteCheck = 0 ;
});
📺 JS 코드 ( 조건을 걸어 form sumit 제어 )
document . getElementById ( "profileFrm" ). addEventListener ( "submit" , e => {
// let initCheck;
// 초기 프로필 이미지 상태를 저장하는 변수
// false == 기본 이미지, true == 이전 업로드 이미지
// let deleteCheck = -1;
// 프로필 이미지가 새로 업로드 되거나 삭제 되었음을 나타내는 변수
// -1 == 초기값, 0 == 프로필 삭제(x 버튼), 1 == 새 이미지 업로드
let flag = true ;
// 프로필 이미지가 없다 -> 있다
if ( ! initCheck && deleteCheck == 1 ) flag = false ;
// 이전 프로필 이미지가 있다 -> 삭제
if ( initCheck && deleteCheck == 0 ) flag = false ;
// 이전 프로필 이미지가 없다 -> 새 이미지
if ( initCheck && deleteCheck == 1 ) flag = false ;
if ( flag ){ // flag == true -> 제출하면 안되는 경우
e . preventDefault (); // form 기본 이벤트 제거
alert ( "이미지 변경 후 클릭하세요" );
}
});
🥉 Spring
📺 Spring와 연결을 위한 jsp 코드
- 파일 제출시 무조건 Post 방식 enctype 속성 추가
- enctype : from 태그 데이터가 서버로 제출될 때 인코딩 되는 방법을 지정 ( Post방식 일때만 사용 가능 )
- application/x-www-form-urlencoded : 모든 문자를 서버로 전송하기 전에 인코딩 ( form 태그 기본값 )
- multipart/form-data : 모든 문자를 인코딩 하지 않음 ( 원본 데이터가 유지되어 이미지, 파일등을 서버로 전송할 수있음 )
< form action = "profile" method = "POST" name = "myPageFrm" id = "profileFrm" enctype = "multipart/form-data" >
< div class = "profile-image-area" >
< c:if test = " ${ empty loginMember . profileImage } " >
< img src = "/resources/images/user.png" id = "profileImage" >
</ c:if >
< c:if test = " ${ !empty loginMember . profileImage } " >
< img src = " ${ loginMember . profileImage } " id = "profileImage" >
</ c:if >
</ div >
< span id = "deleteImage" > x </ span >
< div class = "profile-btn-area" >
< label for = "imageInput" > 이미지 선택 </ label >
< input type = "file" name = "profileImage" id = "imageInput" accept = "image/*" >
< button > 변경하기 </ button >
</ div >
< div class = "myPage-row" >
< label > 이메일 </ label >
< span > ${ loginMember . memberEmail } </ span >
</ div >
< div class = "myPage-row" >
< label > 가입일 </ label >
< span > ${ loginMember . enrollDate } </ span >
</ div >
</ form >
📺 root-context.xml에 multipartResolver bean을 객체로 추가
📺 MyPageController
- MultipartFile : input type= "file" 로 제출된 파일을 저장한 객체
- [ MuitipartFile ] 이 제공하는 메소드 ( transferTo(), getOriginalFileName() , getSize() )
1) transferTo() : 파일을 지정된 경로에 저장 ( 메모리 -> HDD/SSD )
2) getOriginalFileName() : 파일 원본명
3) getSize() : 파일 크기
📺 MyPageService
📺 MyPageServiceImpl
- throws IllegalStateException, IOException 로 예외처리를 해주지 않으면
profileImage.transferTo(new File(filePath + rename)); <- 이부분에서 에러메세지가 뜬다.
// 프로필 이미지 수정 서비스
@Transactional(rollbackFor = {Exception.class})
@Override
public int updateProfile(MultipartFile profileImage, String webPath, String filePath, Member loginMember) throws IllegalStateException, IOException {
// 프로필 이미지 변경 실패 대비
String temp = loginMember.getProfileImage(); // 이전 이미지 저장
// 업로드된 이미지가 있을 경우 <-> 없는 경우 ( x버튼 )
String rename = null; // 변경 이름 저장 변수
if(profileImage.getSize() >0 ) { // 업로드된 이미지가 있을 경우
// 1) 파일 이름 변경
rename = fileRename(profileImage.getOriginalFilename());
// 2) 바뀐 이름 loginMember에 세팅
loginMember.setProfileImage(webPath + rename);
// /resources/images/member/ + 20230824114510_ 12345.jpg
} else { // 업로드된 이미지가 없을 경우
loginMember.setProfileImage(null);
// 세션 이미지를 null 변경해서 삭제
}
// 프로필 이미지 수정 DAO 메소드 호출
int result = dao.updateProfileImage(loginMember);
if(result > 0) { // 성공
// 새 이미지가 업로드 된 경우
if(rename != null ) {
profileImage.transferTo(new File(filePath + rename));
}
} else { // 실패
// 이전 이미지로 프로필 다시 세팅
loginMember.setProfileImage(temp);
}
return result;
}
📺 MyPageServiceImpl
- 파일명 변경 메소드 생성
- reutrn date + str + ext : 현재시간 + "_" 랜덤한 5자리 정수 + .확장자
- lastIndexOf() : 원본 문자열의 뒤에서부터 탐색하여 처음으로 나오는 ( ) 안에 문자열 리턴 <-> IndexOf()
📺 MyPageDAO
📺 myPage-mapper.xml