JAVA 웹개발 과정 국비 62일차 정리

62일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. 게시판 만들기 이어서..

1) 자잘한 이동 버그 수정

  • 게시글 목록으로 이동할 때, 현재 페이지 값이 없다면 1로 만들기
  • 게시글 수정하기 버튼을 누르면 에러-> 수정한 페이지로 돌아가게 수정
    • cpage값을 넘겨서 받아옴
  • 게시글 삭제하기 버튼을 누르면 에러 -> 수정한 페이지로 돌아가게 수정.
    • cpage값을 넘겨서 받아옴
  • 게시글을 수정한 뒤, 목록으로 버튼을 2번 눌러야 게시판 목록으로 돌아가는 버그 수정.
    • <a href="jacascript:history.back()">으로 태그를 감싸주니 수정하기 -> 수정완료 두 단계가 되어버려서 목록으로 버튼을 2번 눌러야 결국 이전 페이지로 가게 된다.
    • a태그 대신 js로 location.href="/toBoard.board?cpage="+${cpage}; 위치를 직접 줘서 해결.

2. 소스 코드

Board

2. File 업로드

1) 클라이언트 코드

index 페이지에 form태그와 type=file인 input태그를 만들어준다.
name 값을 설정해줘야 submit할 때 서버로 값이 넘어간다.
파일은 get방식으로 넘기기에는 너무 크기 때문에 post방식으로 전달한다.
파일의 내용은 binary data이기 때문에 서버에서 파일을 받을 때 request.getParameter로는 받을 수 없다.
복잡한 데이터를 submit할 때는 enctype="multipart/form-data"를 적어줘야 request를 만들 때 body에서 string값은 string값 대로 분류하고, 파일 data는 파일 data 대로 분류를 해준다.
즉, 이 코드를 적어주지 않으면 string 값과 파일 data값이 뒤섞여서 전달되게 된다.

파일이 submit 대상으로 포함된다면?

  1. method는 post 방식이여야만 한다.
  2. enctype=”multipart/form-data” 적어주기
<form action="/upload.file" method="post">
	<input type="file" name="file"><br>
	<input type="submit">
</form>

2) 서버 코드 - 1

① 기본 코드 생성

필요없는 코드를 삭제하고, 어노테이션을 *.file으로 수정한다.
컨트롤러 기본 코드들을 추가하고 /upload.file을 받을 수 있게 if문을 만든다.

@WebServlet("*.file")
public class FileController extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf8");
		String uri = request.getRequestURI();
		String ctx = request.getContextPath();
		String cmd = uri.substring(ctx.length());
		System.out.println(cmd);
		
		if(cmd.equals("/upload.file")){
			// 파일 업로드 기능
		}
	}

② 값을 받아오려면?

클라이언트에서 multipart/form-data 로 인코딩되어 값이 넘어오기 때문에, 서버에서 기존 request로는 그 값을 받을 수 없다.
따라서 인코딩 된 값을 분석해주는 라이브러리를 이용하여 값은 받아와야 한다.
가장 유명한 라이브러리로 apache file-upload가 있지만, 이 라이브러리는 spring과 같이 써야 편하기 때문에 지금은 cos로 사용하기로 한다.

③ 라이브러리 적용

maven repository에서 받았던 cos는 낮은 버전이여서 구글에 cos.jar를 검색하여 servlets.com/cos 주소를 가지는 공식 홈페이지에 들어가서 cos-20.08.zip을 다운받았다.
압축을 풀고 WEB-INF폴더에 lib 폴더에 복붙해준다.

④ 톰캣의 홈 디렉토리와 배포

톰캣은 가동하려면 홈 디렉토리가 필요하다. 홈 디렉토리가 있어야 다른 파일들의 위치를 알 수 있기 때문이다.
이클립스의 프로젝트 폴더를 홈 디렉토리로 착각하지 않도록 주의한다.
이클립스에서 ctrl + f11로 실행하는 순간 톰캣은 서버를 가용하는데 필요한 파일들을 추려서 복사를 한다. 그 곳을 홈으로 잡고 톰캣은 가동을 한다.
코드는 수정했는데 브라우저에서는 반영이 안되었을 때 이러한 복사과정에서의 버그가 일어난 것은 아닌지 의심할 수 있어야 한다.
이런 상황에서는 이클립스 서버에 우클릭을 해서 Clean을 눌러서 복사되어있는 내용을 다 지워버리고 다시 복사를 받으면 해결이 가능하다.
바로 이 복사하는 과정을 배포(Deployment)라고 한다.

아래와 같이 코드를 실행해주면 홈 디렉토리 경로가 출력된다.
나의 경우는 아래와 같이 나왔다.

System.out.println(request.getServletContext().getRealPath(""));
E:\2021_09_웹응용과정\workspace_backend\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\Day_03_File\

⑤ 파일의 경로

홈 디렉토리가 있다는 것은 클라이언트가 파일을 저장하거나 다운받을 때 프로젝트 파일에 접근하는 것이 아니라 톰캣의 홈 디렉토리에 접근한다는 것을 의미한다.

savePath 변수는 클라이언트가 파일을 업로드했을 때 파일을 저장할 위치를 받는다.
위에서 언급한대로 프로젝트 파일에 직접적으로 클라이언트가 접근하는 것이 아니기 때문에 savePath의 위치를 c:/abc/ 이렇게 지정해줄 수는 없다.
따라서 savePath의 경로도 톰캣의 홈 디렉토리 안에 files 폴더를 하나 만들어서 지정해준다.
files 폴더가 없다면 만들어주는 if문도 추가한다.

String savePath = request.getServletContext().getRealPath("files");
File filePath = new File(savePath);

if(!filePath.exists()) {filePath.mkdir();}

⑥ 라이브러리 사용

COS 라이브러리의 MultipartRequest 객체는 컨트롤러에 있는 기본 request를 라이브러리로 가져온 MultipartRequest로 업그레이드 시킨다
또한 동시에 업로드 된 파일을 지정 경로에 저장하는 기능도 수행한다.
첫 번째 파라미터로 어떤 객체인지 (어떤 객체 안에 있는 파일인지), 두 번째 파라미터로 파일의 저장 위치,
세 번째 파라미터로 파일의 최대 사이즈(byte기준), 네 번째 파라미터로 인코딩,
마지막 파라미터는 동일한 파일을 올렸을 경우 덮어씌워지지 않도록 이름을 자동으로 바꿔주는 기능(COS에서 제공)을 넣어준다.

int maxSize = 1024*1024*10; // 10MB
MultipartRequest multi = new MultipartRequest(request, savePath, maxSize, "UTF8", new DefaultFileRenamePolicy());

3) DTO, DAO 생성

① File 테이블에 필요한 속성들

업로드 당시의 원래 파일 이름 oriName과 클라이언트에게 보여지는 파일 이름이 다를 수도 있기 때문에(중복된 파일 이름이 존재할 수 있기 때문) oriName은 DB에 저장한다.
또한 파일을 다운로드할 때 서버쪽에 저장 된 이름 sysName으로 파일을 내보내야 하기 때문에 sysName도 필요하다.

예를 들면, abc.jpg 파일을 업로드를 했다. 그러나 이미 서버에 abc.jpg 파일이 존재한다면, 서버에서는 새로운 abc.jpgabc(1).jpg로 저장해야 한다. (중복되면 덮어씌워지므로)
그러나 클라이언트 입장에서는 abc.jpg 를 업로드했는데, 첨부로 보여지는 이름이 abc(1).jpg이면 이상하게 느껴질 것이다.
따라서 서버는 abc(1).jpg로 파일을 저장하되, 클라이언트에게 보여줄 때는 abc.jpg로 보여줘야 한다.

  • Original File Name
    • 업로드 당시의 원래 파일 이름
    • 위의 예에서는 abc.jpg 를 나타낸다.
    • DB에 저장 필요
  • System File Name
    • 서버에 저장되는 실제 파일 이름
    • 위의 예에서는 abc(1).jpg를 나타낸다.
    • DB에 저장 필요

기본적으로 식별자로 seq번호가 필요할 것이다.
또한, 예를 들면 어떤 게시글에 올라온 파일인지를 알아야하는 것처럼 파일의 parent seq를 알아야한다. 이 네개의 정보는 필수적으로 들어가 있어야 하는 것이다.

Optional로는 아래와 같은 정보들이 있다.

  • 저장 경로
  • 업로드 날짜, 시간
  • 파일 사이즈

이 정보들을 토대로 files 테이블을 생성한다.

CREATE TABLE files(
    seq NUMBER PRIMARY KEY,
    oriName VARCHAR(100) NOT NULL,
    sysName VARCHAR(100) NOT NULL,
    --parentSeq NUMBER REFERENCES board(seq)
    parentSeq NUMBER NOT NULL
);
CREATE SEQUENCE files_seq START WITH 1 INCREMENT BY 1 NOCACHE NOMAXVALUE;

② FileDAO, FileDTO 생성

DAO와 DTO를 생성해준다.

4) 서버 코드 - 2

DAO와 DTO도 만들어줬으니 file을 DB에 저장해보자.
예외는 try-catch로 전가한다.

String sysName = multi.getFilesystemName("file");
String oriName = multi.getOriginalFileName("file");
dao.insert(new FileDTO(0,oriName,sysName,0));

3. File 목록 출력과 다운로드

업로드한 파일 목록 출력은 기존 방법들과 다르지 않다.
다만 주의할 점은 AJAX를 이용할 때는 서버쪽에 한글 인코딩 코드 response.setContentType("text/html;charset=utf8");를 별도로 적어줘야한다.

컨트롤러를 거쳐 다운로드를 하는 방법은 다음 수업때 배울 예정이므로, 이번에는 다이렉트로 링크를 클릭하여 다운받는 간단한 방법을 배웠다.

<!-- 클라이언트 -->
<button id="fileList">파일 명단 보기</button>
<br>
<fieldset>
	<legend>파일 목록</legend>
</fieldset>
<script>
	$("#fileList").on("click",function(){
		$.ajax({
			url:"/list.file",
			dataType:"json"
		}).done(function(resp){
			for(let i=0;i<resp.length;i++){
				let div = $("<div>");
				let anker = $("<a>");
				// 다이렉트로 다운
				anker.attr("href","/files/"+resp[i].sysName);
				anker.text(resp[i].oriName);
				div.append(anker);
				$("legend").after(div);
			}
		})
	})
</script>
// 서버
else if(cmd.equals("/list.file")) {
	List<FileDTO> list = dao.selectAll();
	Gson g = new Gson();
	String result = g.toJson(list);
	// ajax는 한글 처리하는 코드를 따로 써줘야함.
	response.setContentType("text/html;charset=utf8");
	response.getWriter().append(result);
}

댓글남기기