21.03.02 Untact프로젝트(관리자 메인화면 구현 ~ 첨부파일 업로드 및 리스팅시 섬네일 노출까지)

2021. 3. 2. 22:16JAVA/Spring & Vue APP 프로젝트(백엔드)

#NOTE

<URL vs URI>
URI는 인터넷 상의 자원을 식별하기 위한 문자열의 구성쯤으로 해석 될 수 있겠다. 
URI의 한 형태인 URL은 인터넷 상의 자원 위치를 나타낸다. 
URL는 URI의 한 형태로, 바꿔 말하면 URI는 URL을 포함 하는 개념이다.
<테일윈드 container의 역할>
container: @media, max-width, min-width가 걸려있음
즉, 반응형이 적용되어있는 것
그냥 con이 container의 줄임말
<select class="py-2 select-board-id">
	<option value="1">공지사항</option>
	<option value="2">자유게시판</option>
</select>
			
	<script>
		// select의 옵션의 value를 .val(param.boardId)값으로 바꾼다
		$('.section-1 .select-board-id').val(param.boardId);
			
		$('.section-1 .select-board-id').change(function() {
				//change() : 뭔가 바뀔때마다 실행되는 함수
				//location.href : 현재 페이지의 URL
			location.href = '?boardId=' + this.value;
		});
	</script>


<change()>
바뀔때마다 실행되는 함수

<selected="selected">
select에서 디폴트로 선택되어 있는 옵션이라는 의미
<autofocus="autofocus">
<첨부파일 기능>
일반 jsp에서 첨부파일 기능을 구현하려면 
많은 로직이 필요함
스프링에선 보다 적은 로직으로 구현이 가능함

input type="file"을 하려면
form 전송방식은 무조건 POST여야 한다.
또한, form 속성에 enctype="multipart/form-data" 추가해야 함

#주요 소스코드

<관리자 게시물 리스트 페이지>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<%@ include file="../part/mainLayoutHead.jspf"%>

<section class="section-1">
	<div class="bg-white shadow-md rounded container mx-auto p-8 mt-8">
		<div class="flex items-center">
			<select class="py-2 select-board-id">
				<option value="1">공지사항</option>
				<option value="2">자유게시판</option>
			</select>
			
			<script>
			// select의 옵션의 value를 .val(param.boardId)값으로 바꾼다
			$('.section-1 .select-board-id').val(param.boardId);
			
			$('.section-1 .select-board-id').change(function() {
				//change() : 뭔가 바뀔때마다 실행되는 함수
				//location.href : 현재 페이지의 URL
				location.href = '?boardId=' + this.value;
			});
			</script>
			
			<div class="flex-grow"></div>
			<a href="add?boardId=${param.boardId}" class="btn-primary bg-blue-500 hover:bg-blue-dark text-white font-bold py-2 px-4 rounded">글쓰기</a>
			
		</div>
		<div>
			<c:forEach items="${articles}" var="article">
				<div class="flex justify-between items-center mt-10">
					<span class="font-light text-gray-600">${article.regDate}</span>
					<a href="list?boardId=${article.boardId}" class="px-2 py-1 bg-gray-600 text-gray-100 font-bold rounded hover:bg-gray-500">${article.extra__boardName}</a>
				</div>
				<div class="mt-2">
					<a href="detail?id=${article.id}" class="text-2xl text-gray-700 font-bold hover:underline">${article.title}</a>
					<p class="mt-2 text-gray-600">${article.body}</p>
					<div>
						<c:if test="${article.extra__thumbImg != null}">
							<img src="${article.extra__thumbImg}" alt="" />
						</c:if>
					</div>
				</div>
				<div class="flex justify-between items-center mt-4">
					<a href="detail?id=${article.id}" class="text-blue-500 hover:underline">자세히 보기</a>
					
					<div>
						<a href="detail?id=${article.id}" class="flex items-center">
							<img src="https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=crop&amp;w=731&amp;q=80" alt="avatar" class="mx-4 w-10 h-10 object-cover rounded-full">
							<h1 class="text-gray-700 font-bold hover:underline">${article.extra__writer}</h1>
						</a>
					</div>
				</div>
			</c:forEach>
		</div>
	</div>
</section>

<%@ include file="../part/mainLayoutFoot.jspf"%> 

<genFile 테이블>

# 파일 테이블 추가
CREATE TABLE genFile (
  id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, # 번호
  regDate DATETIME DEFAULT NULL, # 작성날짜
  updateDate DATETIME DEFAULT NULL, # 갱신날짜
  delDate DATETIME DEFAULT NULL, # 삭제날짜
  delStatus TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, # 삭제상태(0:미삭제,1:삭제)
  relTypeCode CHAR(50) NOT NULL, # 관련 데이터 타입(article, member)
  relId INT(10) UNSIGNED NOT NULL, # 관련 데이터 번호
  originFileName VARCHAR(100) NOT NULL, # 업로드 당시의 파일이름
  fileExt CHAR(10) NOT NULL, # 확장자
  typeCode CHAR(20) NOT NULL, # 종류코드 (common)
  type2Code CHAR(20) NOT NULL, # 종류2코드 (attatchment)
  fileSize INT(10) UNSIGNED NOT NULL, # 파일의 사이즈(byte)
  fileExtTypeCode CHAR(10) NOT NULL, # 파일규격코드(img, video)
  fileExtType2Code CHAR(10) NOT NULL, # 파일규격2코드(jpg, mp4)
  fileNo SMALLINT(2) UNSIGNED NOT NULL, # 파일번호 (1)
  fileDir CHAR(20) NOT NULL, # 파일이 저장되는 폴더명
  PRIMARY KEY (id),
  KEY relId (relId,relTypeCode,typeCode,type2Code,fileNo)
); 

<글쓰기 시 첨부파일 업로드>

<AdmArticleController.java>

@RequestMapping("/adm/article/doAdd")
@ResponseBody
public ResultData doAdd(@RequestParam Map<String, Object> param, HttpServletRequest req, MultipartRequest multipartRequest) {

	................................[생략]
		
		ResultData addArticleRd = articleService.addArticle(param);
		
		// addArticleRd map의 body에서 key값이 id인 것을 가져와라
		int newArticleId = (int) addArticleRd.getBody().get("id");
		
		//MultipartRequest : 첨부파일 기능 관련 요청
		Map<String, MultipartFile> fileMap = multipartRequest.getFileMap(); //MultipartRequest로 들어온 map 정보를 가져오기
				
		
		//fileMap.keySet() : file__article__0__common__attachment__1
		for (String fileInputName : fileMap.keySet()) {
			//fileInputName : file__article__0__common__attachment__1
			MultipartFile multipartFile = fileMap.get(fileInputName);
			
			if(multipartFile.isEmpty() == false) {
				//저장할 파일관련 정보를 넘김
				genFileService.save(multipartFile, newArticleId);
			}
			
		}

		return addArticleRd;
	}
    
 --------------------------------------------------------------------------------
 <GenFileService.java>
 
 public ResultData save(MultipartFile multipartFile, int relId) {
		String fileInputName = multipartFile.getName();
		//'file__article__0__common__attachment__1'를 "__" 기준으로 쪼갠다.
		//  0       1	  2    3          4      5
		String[] fileInputNameBits = fileInputName.split("__");

		if (fileInputNameBits[0].equals("file") == false) {
			return new ResultData("F-1", "파라미터명이 올바르지 않습니다.");
		}
		
		// getSize() : 파일 사이즈를 가져오는 명령어
		int fileSize = (int) multipartFile.getSize();

		// 파일 사이즈가 0이거나 0보다 작으면
		if (fileSize <= 0) {
			return new ResultData("F-2", "파일이 업로드 되지 않았습니다.");
		}

		String relTypeCode = fileInputNameBits[1];
		String typeCode = fileInputNameBits[3];
		String type2Code = fileInputNameBits[4];
		int fileNo = Integer.parseInt(fileInputNameBits[5]);
		String originFileName = multipartFile.getOriginalFilename();
		String fileExtTypeCode = Util.getFileExtTypeCodeFromFileName(multipartFile.getOriginalFilename());
		String fileExtType2Code = Util.getFileExtType2CodeFromFileName(multipartFile.getOriginalFilename());
		String fileExt = Util.getFileExtFromFileName(multipartFile.getOriginalFilename()).toLowerCase();

		if (fileExt.equals("jpeg")) {
			fileExt = "jpg";
		} else if (fileExt.equals("htm")) {
			fileExt = "html";
		}

		String fileDir = Util.getNowYearMonthDateStr();

		ResultData saveMetaRd = saveMeta(relTypeCode, relId, typeCode, type2Code, fileNo, originFileName,
				fileExtTypeCode, fileExtType2Code, fileExt, fileSize, fileDir);
		int newGenFileId = (int) saveMetaRd.getBody().get("id");

		// 새 파일이 저장될 폴더(io파일) 객체 생성
		String targetDirPath = genFileDirPath + "/" + relTypeCode + "/" + fileDir;
		java.io.File targetDir = new java.io.File(targetDirPath);

		// 새 파일이 저장될 폴더가 존재하지 않는다면 생성
		if (targetDir.exists() == false) {
			targetDir.mkdirs();
		}

		String targetFileName = newGenFileId + "." + fileExt;
		String targetFilePath = targetDirPath + "/" + targetFileName;

		// 파일 생성(업로드된 파일을 지정된 경로로 옮김)
		try {
			multipartFile.transferTo(new File(targetFilePath));
		} catch (IllegalStateException | IOException e) {
			return new ResultData("F-3", "파일저장에 실패하였습니다.");
		}

		return new ResultData("S-1", "파일이 생성되었습니다.", "id", newGenFileId, "fileRealPath", targetFilePath, "fileName", targetFileName);
	}
 

<게시물 리스팅 시 첨부파일1 섬네일 노출>

<AdmArticleController.java>

@RequestMapping("/adm/article/list")
//@ResponseBody
public String showList(HttpServletRequest req, @RequestParam(defaultValue = "1") int boardId, String searchKeywordType, String searchKeyword, @RequestParam(defaultValue = "1") int page) {

..................................[생략]

	/* 각 article에 달려있는 첨부파일 섬네일 가져오기 시작 */
	for ( Article article : articles ) {
											//String relTypeCode, int relId, String typeCode, String type2Code, int fileNo
			GenFile genFile = genFileService.getGenFile("article", article.getId(), "common", "attachment", 1);

			if ( genFile != null ) {
				//img의 url을 가져오기
				article.setExtra__thumbImg(genFile.getForPrintUrl());
			}
	}
	/* 각 article에 달려있는 첨부파일 섬네일 가져오기 끝 */