spring에서 이미지 업로드 하는 방법

jeongwoo·2022년 6월 25일
0

Java 프로젝트

목록 보기
8/9
post-thumbnail

코드

Quest.java

src/main/java/songjeongwoo.godgamez.domain에 Quest 도메인 추가

package songjeongwoo.godgamez.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Quest {
	private int qstId;
	private Class cls;
	private String qstName;
	private int difficulty;
	private String qstContent;
	private String qstImg;
}

questMap.xml

src/main/resources/songjeongwoo/godgamez/dao/map/questMap.xml

  • 퀘스트 추가
<insert id='insertQuest'>
  insert into quests(quest_id, class_id, quest_name, difficulty, quest_content, image_id)
  values(qsts_qstId_seq.nextval, #{cls.clsId}, #{qstName}, #{difficulty}, #{qstContent}, #{qstImg})
</insert>
  • 퀘스트 상세조회
<select id='selectQuest' resultMap='questMap'>
  <include refid="selectAdminQsts"/>
  where quest_id = #{qstId}
</select>

<sql id="selectAdminQsts">
  select q.*, c.*
  from quests q join classes c
  on q.class_id = c.class_id
</sql>
  • 퀘스트 수정
<update id='updateQuest'>
  update quests
  set quest_name = #{qstName}, difficulty = #{difficulty}, quest_content = #{qstContent}
  where quest_id = #{qstId}
</update>

이외 persistence와 service계층은 생략

app.properties

src/main/resources/songjeongwoo/godgamez/config/app.properties에 questAttachDir=/res/quest 경로를 추가해준다.

  • 해당 경로는 뒤에 나오겠지만 톰켓 설치 시 deploy할 폴더로 지정해주었던 경로이다.
questAttachDir=/res/quest

AttachController.java

src/main/java/songjeongwoo.godgamez.web에서 AttachController.java 파일을 만들고 코드를 작성한다.

  • fileName은 수정까지 생각했을 때 해당 객체의 시퀀스인 qstId로 저장되게 하였다.
    • 수정할 경우 덮어쓰기가 된다.
package songjeongwoo.godgamez.web;

import java.io.File;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping
public class AttachController {
	@Value("${attachDir}")
	private String attachDir;
	
	@Value("${questAttachDir}")
	private String questAttachDir;
	
	@PostMapping("/quest/attach")
	@ResponseBody
	public void questAttach(MultipartFile attachFile, HttpServletRequest request) {
		String dir = request.getServletContext().getRealPath(questAttachDir);
		String fileName = request.getParameter("curQstId") + ".jpg";
		
		save(dir + "/" + fileName, attachFile);
	}
	
	private void save(String fileName, MultipartFile attachFile) {
		try {
			attachFile.transferTo(new File(fileName));
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
    
    //퀘스트 삭제 시 해당 퀘스트 이미지 삭제
	@DeleteMapping("/quest/attach/del")
	public void questAttachDel(@RequestBody int qstId, HttpServletRequest request) {
		String dir = request.getServletContext().getRealPath(questAttachDir);
		System.out.println(qstId);
		System.out.println(dir + "/" + qstId + ".jpg");
		
		File file = new File(dir + "/" + qstId + ".jpg");
		if(file.exists()) {
			file.delete();
			System.out.println("del");			
		}
		
	}
}

.jsp에 이미지 업로드를 위한 js작성

JS

  • 퀘스트 추가
//퀘스트 추가
function addProcQst() {
	let newQstName = $('#addFixProcModal #qstName').val()
	let newDifficulty = $('input[type="radio"]:checked').val()
	let newQstContent = $('#addFixProcModal #qstContent').val()
	let clsId = $('#addFixProcModal #clsId').val()
	let newClsName = $('#addFixProcModal #cls').val().split('>')[2].replace(" ", "")
	let questDataIn = {}
	$.ajax({
		url: '/godgamez/class/search',
		method: 'post',
		data: JSON.stringify({clsName: newClsName}),
		contentType: 'application/json'
	}).done(clss => {
		let clsObj = clss[0]
		questDataIn = {
			qstId: 0,
			cls: clsObj,
			qstName: newQstName,
			difficulty: newDifficulty,
			qstContent: newQstContent,
			qstImg: clsId + '_' + newQstName //DB에 저장될 네이밍(클래스당 여러 개 퀘스트가 있을 수 있기때문에 이렇게 지음)
		}
		$.ajax({
			url: '/godgamez/quest/add',
			method: 'post',
			data: JSON.stringify(questDataIn),
			contentType: 'application/json'
		}).done(result => {
			if(result) {
				let questId
				$.ajax({
					url: '/godgamez/quest/qstId',
					async: false, //ajax는 비동기방식, false로 쓰면 동기방식이 되기때문에 //전역변수에 ajax통신 값을 저장해서 블록 밖에서도 쓸 수 있게 해준다.
				}).done(qstId => {
					questId = qstId
				})
				$('#curQstId').val(questId)
				
				//퀘스트 추가 시 이미지파일 추가 
				let form = $('#addFixQstImg')[0] //form DOM element
				let formData = new FormData(form)
				$.ajax({
					url: '/godgamez/quest/attach',
					method: 'post',
					data: formData,
					contentType: false, //false로 둘 경우, multipart/form-data로 전송됨
					processData: false //false로 둘 경우, formData를 String으로 변환하지 않음
				})
				modal('퀘스트', '추가', '성공')
				$('#doneBBtn').click(() => {
					window.location.reload()
				})
			} else modal('퀘스트', '추가', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.')
		}).fail(() => modal('퀘스트', '추가', '실패10', '퀘스트명, 클래스, 난이도, 퀘스트 내용을 입력했는지 확인하세요.'))
	})
}
  • 퀘스트 상세조회
//퀘스트 상세 조회
let qstId // 퀘스트 수정 시 사용
function qstDetail() {
	//$(event.currentTarget) //클릭한 요소 읽기(js)
	qstId = $(event.currentTarget).closest('.qstDetail').children('.qstId').text()
	$.ajax({
		url: `/godgamez/quest/list/\${qstId}`
	}).done(qst => {
		let qstName = qst.qstName
		let mainCtg = qst.cls.mainCtg
		let subCtg = qst.cls.subCtg
		let clsName = qst.cls.clsName
		let difficulty = qst.difficulty
		let qstContent = qst.qstContent
		if(!$('#qstChk:checked').length) {
			$('.modal-title').html()
			$('.modal-title').html('<b>퀘스트 상세조회 및 수정</b>')
			$('#addFixProcModal #qstName').val(qstName)
			$('#addFixProcModal #cls').siblings().remove()
			$('#addFixProcModal #cls').attr('class', 'text-center border-0')
			$('#addFixProcModal #cls').val(`\${mainCtg} > \${subCtg} > \${clsName}`)
			switch(difficulty) {
			case 1: $('#one').prop('checked', true); break;
			case 2: $('#two').prop('checked', true); break;
			case 3: $('#three').prop('checked', true); break;
			case 4: $('#four').prop('checked', true); break;
			case 5: $('#five').prop('checked', true);
			}
			$('#addFixProcModal #qstContent').val(qstContent)
			$('.modal-footer #chngBtn').text("수정")
			$('.modal-footer #chngBtn').attr('onclick', 'fixProcQst()')
			$('#addFixProcModal').modal()
			//퀘스트 이미지 불러오기
			$('#msg').html("100MB 이하의 JPG, PNG파일을 제출하세요.")
			$('#previewImg').attr('src', '/godgamez/res/quest/' + qst.qstId + '.jpg')
			//$('#previewImg').attr('src', '/godgamez/res/quest/' + qst.cls.clsId + '_' + qstName + '.jpg')
		} else modal('퀘스트', '상세조회', '실패10', '체크박스 해제 후 다시 시도하세요.')
	}).fail(() => modal('퀘스트', '상세조회', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.'))
}
  • 퀘스트 수정
//퀘스트 수정
function fixProcQst() {
	let newQstName = $('#addFixProcModal #qstName').val()
	let newDifficulty = $('input[type="radio"]:checked').val()
	let newQstContent = $('#addFixProcModal #qstContent').val()
	let clsId = $('#addFixProcModal #clsId').val()
	questDataIn = {
		qstId: qstId,
		qstName: newQstName,
		difficulty: newDifficulty,
		qstContent: newQstContent,
		qstImg: clsId + '_' + newQstName
	}
	$.ajax({
		url: '/godgamez/quest/fix',
		method: 'put',
		data: JSON.stringify(questDataIn),
		contentType: 'application/json'
	}).done(result => {
		if(result) {
			if($('#addFixQstImg img').attr('src') == readerResult) { //cf) 322번째 줄 코드
				$('#curQstId').val(qstId.trim())
				// 퀘스트 수정 시 이미지파일 덮어쓰기
				let form = $('#addFixQstImg')[0] //form DOM element
				let formData = new FormData(form)
				$.ajax({
					url: '/godgamez/quest/attach',
					method: 'post',
					data: formData,
					contentType: false, //false로 둘 경우, multipart/form-data로 전송됨
					processData: false //false로 둘 경우, formData를 String으로 변환하지 않음
				})
				modal('퀘스트', '수정', '성공')
				$('#doneBBtn').click(() => window.location.reload())
			}
			modal('퀘스트', '수정', '성공')
			$('#doneBBtn').click(() => window.location.reload())
		} else modal('퀘스트', '삭제', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.')
	}).fail(() => modal('퀘스트', '삭제', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.'))
}
  • 퀘스트 삭제
//퀘스트 삭제 - script.js 함수명 공통
function delB() {	
	let actpdQstCnt = $('#qstChk:checked').closest('.qstDetail').children('.actpdQstCnt').text().trim();
	let qstId = $('#qstChk:checked').closest('.qstDetail').children('.qstId').text().trim();
	if(actpdQstCnt == 0 || actpdQstCnt == 'undefined') {
		$.ajax({
			url: `/godgamez/quest/del/\${qstId}`,
			method: 'delete'
		}).done(result => {
			if(result) {
				modal('퀘스트', '삭제', '성공')
				$('#doneBBtn').click(() => window.location.reload())
				
				//해당 퀘스트 이미지 삭제
				$.ajax({
					url: '/godgamez/quest/attach/del',
					method: 'delete',
					data: JSON.stringify(qstId),
					contentType: 'application/json'
				}).done(() => {
					console.log('del')
				})
				
			} else modal('퀘스트', '삭제', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.')
		}).fail(() => modal('퀘스트', '삭제', '실패10', '알 수 없는 이유로 해당 작업에 실패했습니다.'))
	} else if(actpdQstCnt > 0) modal('퀘스트', '삭제', '실패10', '현재 해당 퀘스트를 수행 중인 회원이 존재합니다.')
	else modal('퀘스트', '삭제', '실패10', '체크박스를 1개만 선택하세요.')
}

cf) modal코드

<div id='addFixProcModal' class='modal fade' tabindex='-2'>
	<div class='modal-dialog'>
		<div class='modal-content'>
			<div class='modal-header'>
				<h6 class='modal-title'></h6>
				<button type='button' class='close' data-dismiss='modal'>&times;</button>
			</div>
			<form class='modal-body' id='addFixQstImg'>
				<div class='contatiner ml-3 mb-3' id='modalTable'>
					<div class='row'>
						<div class='col'>
							<table>
								<tbody>
									<tr>
										<th></th>
										<td><input type='hidden' id='curQstId' name='curQstId'/></td>
									</tr>
									<tr>
										<th>퀘스트명</th>
										<td>
											<input type='text' placeholder='2글자 이상의 한글로 입력하세요.'
												class='text-center form-control' id='qstName' name='qstName'/>
										</td>
									</tr>
									<tr>
										<th>클래스</th>
										<td>
											<input type='text' id='clsId' hidden='true'/>
											<div id='div' class='text-center form-control'>
												<input type='text' class='border-0' placeholder='1개의 클래스를 선택하세요' id='cls' readonly/>
												<button type='button' class='btn border float-right' id='srchClsBtn'>
													<i class="fas fa-search float-center"></i>
												</button>
											</div>
										</td>
									</tr>
									<tr height='5rem'></tr>
									<tr>
										<th>난이도</th>
										<td>
											<input type='radio' id='one' name='difficulty' class='ml-3' value='1'/>1
											<input type='radio' id='two' name='difficulty' class='ml-3' value='2'/>2
											<input type='radio' id='three' name='difficulty' class='ml-3' value='3'/>3
											<input type='radio' id='four' name='difficulty' class='ml-3' value='4'/>4
											<input type='radio' id='five' name='difficulty' class='ml-3' value='5'/>5
										</td>
									</tr>
									<tr height='5rem'></tr>
									<tr>
										<th>퀘스트 내용</th>
										<td>
											<input type='text' placeholder='50글자 이하의 한글로 입력하세요.'
												class='text-center form-control' id='qstContent'/>
										</td>
									</tr>
									<tr height='5rem'></tr>
									<tr>
										<th rowspan='5'>이미지</th>
										<td>
											<span class='text-muted' id='msg'>
												100MB 이하의 JPG, PNG파일을 제출하세요.
											</span>
										</td>
									</tr>
									<tr height='5rem'></tr>
									<tr>
										<td>
											<input type='file' name='attachFile' id='fileBtn' accept='.jpg, .png'/>
										</td>
									</tr>	
									<tr height='5rem'></tr>
									<tr>
										<td>
											<img id='previewImg' class='w-100 m-0' alt='이미지가 존재하지 않습니다.'/>
										</td>
									</tr>											
								</tbody>
							</table>
						</div>
					</div>
				</div>
				<div class='modal-footer justify-content-center'>
					<button type='button' class='btn btn-outline-secondary' onClick='modal("퀘스트", "추가", "중단")'>취소</button>
					<button type='button' id='chngBtn' class='btn btn-outline-secondary' onClick='addProcQst()'>추가</button>
				</div>
			</form>
		</div>
	</div>
</div>

이미지로 보는 동작원리(?)

퀘스트 추가

퀘스트 상세조회 및 수정

  • C:\DEV\GodGamezDeploy\wtpwebapps\godgamez\res\quest

퀘스트 삭제

  • C:\DEV\GodGamezDeploy\wtpwebapps\godgamez\res\quest
    • 1~4번 퀘스트는 oracle sql에서 이미지 없이 넣은 데이터이기때문에 로컬에 이미지 파일 존재X

0개의 댓글