본문 바로가기

Java 웹 개발

21.10.29 - 웹 개발 입문 55일차

홈페이지 - 파일업로드


Multipart 요청 방식일 경우의 수신 코드

( 지금은 1. cos.jar 사용 / 2. apache-commons-fileupload 스프링때 사용)


1. cos.jar 라이브러리를 이용하여 파일 업로드 및 저장을 수행
- 장점 : 편하고 코드가 비교적 간단하다.
- 단점 : 파일을 같은 이름으로 1개만 수신할 수 있다.

2. apache-commons-fileupload 라이브러리를 이용하여 파일 업로드 및 저장을 수행
- 장점 : 같은 이름으로 다중 선택하여 전송되는 파일을 처리할 수 있다.
- 단점 : 코드가 상대적으로 복잡하다.

 

http://www.servlets.com/cos/ 에서 cos-20.08.zip 다운로드 한다

-> cos.jar를 WEB-INF -> lib에 등록한다

 

[파일 업로드 페이지]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h2>POST방식 업로드 테스트(2) - 멀티파트 방식</h2>
<form action="test14.jsp" method="post" enctype="multipart/form-data">
	<input type="file" name="attach" accept=".jpg, .png, .gif">
	<input type="submit" value="업로드">
</form>

 

 

[ 파일 업로드 처리 페이지 ]

cos.jar에서는 MultipartRequest라는 클래스를 제공하며 이를 통해 multipart 요청을 처리
= multipart/form-data 방식에서는 99.999% 확률로 "파일"이 전송된다.
= 따라서 파일의 처리(저장)와 관련된 값들을 설정해야 요청을 수신할 수 있다.
= (1) 해석할 요청 객체 : request
= (2) 저장될 위치 : savePath
= (3) 허용할 업로드 제한크기(byte) : maxSize
= (4) 해석할 Encoding 방식 : encoding
= (5) [선택] 작명정책 객체 : policy

<%@page import="java.io.File"%>
<%@page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy"%>
<%@page import="com.oreilly.servlet.MultipartRequest"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h3>attach = <%=attach%></h3>

<%
	String savePath = "c:/upload"; // 경로는 자유롭게 지정가능
	int maxSize = 1 * 1024 * 1024;//1MB
	String encoding = "UTF-8";
	DefaultFileRenamePolicy policy = new DefaultFileRenamePolicy();
	
	//요청 해석 및 파일 저장
	MultipartRequest mRequest = 
			new MultipartRequest(request, savePath, maxSize, encoding, policy);
	
	//데이터베이스에 저장하기 위하여 정보를 추출
	String uploadName = mRequest.getOriginalFileName("attach");//원래이름
	String saveName = mRequest.getFilesystemName("attach");//저장된이름
	String contentType = mRequest.getContentType("attach");//파일의 MIME-TYPE
	
	File target = mRequest.getFile("attach");//저장된 파일의 객체 반환(없으면 null)
	long fileSize = target == null ? 0L : target.length();
%>

<h3>uploadName = <%=uploadName%></h3>
<h3>saveName = <%=saveName%></h3>
<h3>contentType = <%=contentType%></h3>
<h3>target = <%=target%></h3>
<h3>fileSize = <%=fileSize%></h3>

 

[ 파일 선택후 업로드 ]

[ 업로드 후 처리 페이지 ]

[ 저장 위치에 업로드 완료 ]

 


[ 게시판 - 파일 업로드 구현하기 ]

 

- SQL board_file 테이블 만들기

create table board_file(
board_file_no number primary key,
board_no references board(board_no) on delete cascade,
board_file_uploadname varchar2(256) not null,
board_file_savename varchar2(256) not null unique,
board_file_size number,
board_file_type varchar2(256)
);

create sequence board_file_seq;

 

- BoardFileDto 만들기

package home.beans;

public class BoardFileDto {
	private int boardFileNo;
	private int boardNo;
	private String boardFileUploadname;
	private String boardFileSavename;
	private String boardFileType;
	private long boardFileSize;
	public BoardFileDto() {
		super();
	}
	public int getBoardFileNo() {
		return boardFileNo;
	}
	public void setBoardFileNo(int boardFileNo) {
		this.boardFileNo = boardFileNo;
	}
	public int getBoardNo() {
		return boardNo;
	}
	public void setBoardNo(int boardNo) {
		this.boardNo = boardNo;
	}
	public String getBoardFileUploadname() {
		return boardFileUploadname;
	}
	public void setBoardFileUploadname(String boardFileUploadname) {
		this.boardFileUploadname = boardFileUploadname;
	}
	public String getBoardFileSavename() {
		return boardFileSavename;
	}
	public void setBoardFileSavename(String boardFileSavename) {
		this.boardFileSavename = boardFileSavename;
	}
	public String getBoardFileType() {
		return boardFileType;
	}
	public void setBoardFileType(String boardFileType) {
		this.boardFileType = boardFileType;
	}
	public long getBoardFileSize() {
		return boardFileSize;
	}
	public void setBoardFileSize(long boardFileSize) {
		this.boardFileSize = boardFileSize;
	}
}

 

 

- BoardFileDao 파일 정보 저장 기능 만들기

package home.beans;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class BoardFileDao {

	public void insert(BoardFileDto boardFileDto) throws Exception {
		Connection con = JdbcUtils.connect2();

		String sql = "insert into board_file values(board_file_seq.nextval, ?, ?, ?, ?, ?)";
		PreparedStatement ps = con.prepareStatement(sql);
		ps.setInt(1, boardFileDto.getBoardNo());
		ps.setString(2, boardFileDto.getBoardFileUploadname());
		ps.setString(3, boardFileDto.getBoardFileSavename());
		ps.setLong(4, boardFileDto.getBoardFileSize());
		ps.setString(5, boardFileDto.getBoardFileType());
		ps.execute();

		con.close();
	}

}

 

 

- 게시글 등록 처리 (BoardWriteServlet.java) 수정 하기

[ 파일 업로드가 적용되기 전의 게시글 등록 서블릿 ]

package home.servlet.board;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import home.beans.BoardDao;
import home.beans.BoardDto;

@WebServlet(urlPatterns = "/board/write.txt")
public class BoardWriteServlet extends HttpServlet{
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//입력 : BoardDto(boardTitle, boardContent)
			//req.setCharacterEncoding("UTF-8");//EncodingFilter에서 처리
			BoardDto boardDto = new BoardDto();
			boardDto.setBoardTitle(req.getParameter("boardTitle"));
			boardDto.setBoardContent(req.getParameter("boardContent"));

			//아이디는 세션에서 수집하여 추가
			boardDto.setBoardWriter((String)req.getSession().getAttribute("ses"));
			
			//새글인지 답글인지 판정 : 파라미터에 boardSuperno가 있으면 답글이다
			boolean answer = req.getParameter("boardSuperno") != null;
			//답글일 경우에는 boardSuperno를 저장한다.
			if(answer) {
				boardDto.setBoardSuperno(Integer.parseInt(req.getParameter("boardSuperno")));
			}

			//처리
			//(1) 일반적인 등록
			//BoardDao boardDao = new BoardDao();
			//boardDao.write(boardDto);

			//(2) 번호를 알아내면서 등록
			BoardDao boardDao = new BoardDao();
			int boardNo = boardDao.getSequence();//시퀀스 번호 생성
			boardDto.setBoardNo(boardNo);//게시글 데이터에 생성된 번호 추가
			//답글일 경우 등록될 글의 정보를 상위글 정보를 기준으로 계산해야 한다.
			if(answer) {
				//1. 상위글의 모든 정보를 불러온다
				BoardDto parentDto = boardDao.get(boardDto.getBoardSuperno());
				//2. 상위글의 정보를 토대로 등록될 글의 정보를 계산한다
				//= 그룹은 동일하게 유지하고 차수는 1 증가시켜서 설정한다
				boardDto.setBoardGroupno(parentDto.getBoardGroupno());
				boardDto.setBoardDepth(parentDto.getBoardDepth() + 1);
				boardDao.writeAnswer(boardDto);//게시글 등록(답글)
			}else{
				boardDao.write2(boardDto);//게시글 등록(새글)
			}

			//출력
			//(1) list.jsp로 리다이렉트
			//resp.sendRedirect("list.jsp");

			//(2) detail.jsp로 리다이렉트
			resp.sendRedirect("detail.jsp?boardNo="+boardNo);
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);
		}
	}
}

 

 

 

[ 파일 업로드를 적용한 서블릿 ]

 = 사용자의 요청 방식이 multipart/form-data 방식으로 변경되었다.
 = request를 기존의 형태로 수신할 수 없으며, MultipartRequest(cos.jar)를 이용하여 수신하도록 수정
 = 저장위치는 C:/upload/board
 = 용량제한은 10MB
 = 인코딩은 UTF-8
 = 기본 파일 이름 재정의 객체를 적용
 = req.getParameter()는 mRequest.getParameter()로 바꿔서 사용
 = 파일 관련된 명령만 따로 존재
 = 파일업로드명 : mRequest.getOriginalFileName()
 = 저장된 파일명 : mRequest.getFileSystemName();
 = 파일 유형 : mRequest.getContentType()
 = 파일 반환 : mRequest.getFile()

 

+ 파일이 없는 경우에 대한 조건 추가

package home.servlet.board;

import java.io.File;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import home.beans.BoardDao;
import home.beans.BoardDto;
import home.beans.BoardFileDao;
import home.beans.BoardFileDto;

@WebServlet(urlPatterns = "/board/write.txt")
public class BoardWriteServlet extends HttpServlet{
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//multipart/form-data를 해석하기 위한 객체 생성
			String savePath = "C:/upload/board";
			int maxSize = 10 * 1024 * 1024;
			String encoding = "UTF-8";
			DefaultFileRenamePolicy policy = new DefaultFileRenamePolicy();
			MultipartRequest mRequest = 
						new MultipartRequest(req, savePath, maxSize, encoding, policy);
		
			//입력 : BoardDto(boardTitle, boardContent)

			BoardDto boardDto = new BoardDto();
			boardDto.setBoardTitle(mRequest.getParameter("boardTitle"));
			boardDto.setBoardContent(mRequest.getParameter("boardContent"));

			//아이디는 세션에서 수집하여 추가
			boardDto.setBoardWriter((String)req.getSession().getAttribute("ses"));
			
			//새글인지 답글인지 판정 : 파라미터에 boardSuperno가 있으면 답글이다
			boolean answer = mRequest.getParameter("boardSuperno") != null;
			//답글일 경우에는 boardSuperno를 저장한다.
			if(answer) {
				boardDto.setBoardSuperno(Integer.parseInt(mRequest.getParameter("boardSuperno")));
			}

			//(2) 번호를 알아내면서 등록
			BoardDao boardDao = new BoardDao();
			int boardNo = boardDao.getSequence();//시퀀스 번호 생성
			boardDto.setBoardNo(boardNo);//게시글 데이터에 생성된 번호 추가
			//답글일 경우 등록될 글의 정보를 상위글 정보를 기준으로 계산해야 한다.
			if(answer) {
				//1. 상위글의 모든 정보를 불러온다
				BoardDto parentDto = boardDao.get(boardDto.getBoardSuperno());
				//2. 상위글의 정보를 토대로 등록될 글의 정보를 계산한다
				//= 그룹은 동일하게 유지하고 차수는 1 증가시켜서 설정한다
				boardDto.setBoardGroupno(parentDto.getBoardGroupno());
				boardDto.setBoardDepth(parentDto.getBoardDepth() + 1);
				boardDao.writeAnswer(boardDto);//게시글 등록(답글)
			}else{
				boardDao.write2(boardDto);//게시글 등록(새글)
			}

			/**
			 * 게시글 등록을 모두 마친 뒤에 파일 정보를 데이터베이스에 저장하도록 설정
			 * 파일이 존재할 경우만 실행해야 한다.
			 */
			if(mRequest.getFile("attach") != null) {//파일이 attach란 이름으로 올라왔다면
				BoardFileDto boardFileDto = new BoardFileDto();
				boardFileDto.setBoardNo(boardNo);//게시글 번호
				boardFileDto.setBoardFileSavename(mRequest.getFilesystemName("attach"));//실제저장된이름
				boardFileDto.setBoardFileUploadname(mRequest.getOriginalFileName("attach"));//사용자가올린이름
				boardFileDto.setBoardFileType(mRequest.getContentType("attach"));//파일 유형
				File target = mRequest.getFile("attach");
				boardFileDto.setBoardFileSize(target == null ? 0L : target.length());

				BoardFileDao boardFileDao = new BoardFileDao();
				boardFileDao.insert(boardFileDto);
			}
			
			//출력
			//(2) detail.jsp로 리다이렉트
			resp.sendRedirect("detail.jsp?boardNo="+boardNo);
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);
		}
	}
}

 

- 게시글 등록 입력 (write.jsp)  수정 하기

[ 수정 전 코드 ]

<form action="write.txt" method="post">

[ 수정 후 코드]

<form action="write.txt" method="post" enctype="multipart/form-data">

[ 하단 에 추가 코드 ]

-> 첨부 파일 부분만 추가

	<tbody>
		<tr>
			<th>제목</th>
			<td><input type="text" name="boardTitle" required></td>
		</tr>
		<tr>
			<th>내용</th>
			<td>
				<textarea name="boardContent" required rows="10" cols="60"></textarea>
			</td>
		</tr>
		
				<!-- 첨부파일 -->
		<tr>
			<th>첨부</th>
			<td>
				<input type="file" name="attach">
			</td>
		</tr>
		
	</tbody>

 

[ 게시글 파일 업로드 처리 구현 완료 ]

 

 


 

[ 회원 - 프로필 사진 구현하기 ]

 

- SQL member_profile 테이블 만들기

create table member_profile(
member_profile_no number primary key,
member_id references member(member_id) on delete cascade,
member_profile_uploadname varchar2(256) not null,
member_profile_savename varchar2(256) not null unique,
member_profile_size number,
member_profile_type varchar2(256)
);

create sequence member_profile_seq;

 

- MemberProfileDto 만들기

package home.beans;

public class MemberProfileDto {
	private int memberProfileNo;
	private String memberId;
	private String memberProfileSavename;
	private String memberProfileUploadname;
	private String memberProfileType;
	private long memberProfileSize;
	public MemberProfileDto() {
		super();
	}
	public int getMemberProfileNo() {
		return memberProfileNo;
	}
	public void setMemberProfileNo(int memberProfileNo) {
		this.memberProfileNo = memberProfileNo;
	}
	public String getMemberId() {
		return memberId;
	}
	public void setMemberId(String memberId) {
		this.memberId = memberId;
	}
	public String getMemberProfileSavename() {
		return memberProfileSavename;
	}
	public void setMemberProfileSavename(String memberProfileSavename) {
		this.memberProfileSavename = memberProfileSavename;
	}
	public String getMemberProfileUploadname() {
		return memberProfileUploadname;
	}
	public void setMemberProfileUploadname(String memberProfileUploadname) {
		this.memberProfileUploadname = memberProfileUploadname;
	}
	public String getMemberProfileType() {
		return memberProfileType;
	}
	public void setMemberProfileType(String memberProfileType) {
		this.memberProfileType = memberProfileType;
	}
	public long getMemberProfileSize() {
		return memberProfileSize;
	}
	public void setMemberProfileSize(long memberProfileSize) {
		this.memberProfileSize = memberProfileSize;
	}
}

 

- MemberProfileDao 프로필 정보 등록 만들기

package home.beans;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class MemberProfileDao {
	public void insert(MemberProfileDto memberProfileDto) throws Exception {
		Connection con = JdbcUtils.connect2();

		String sql = "insert into member_profile values(member_profile_seq.nextval, ?, ?, ?, ?, ?)";
		PreparedStatement ps = con.prepareStatement(sql);
		ps.setString(1, memberProfileDto.getMemberId());
		ps.setString(2, memberProfileDto.getMemberProfileUploadname());
		ps.setString(3, memberProfileDto.getMemberProfileSavename());
		ps.setLong(4, memberProfileDto.getMemberProfileSize());
		ps.setString(5, memberProfileDto.getMemberProfileType());
		ps.execute();

		con.close();
	}
}

 

- 회원 가입 처리 (MemberJoinServlet.java) 수정 하기

[ 회원 가입 처리 서블릿 (기존 방식) ]

package home.servlet.member;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import home.beans.MemberDao;
import home.beans.MemberDto;

//= 입력창의 전송형태에 따라 메소드를 선택할 수 있다.
//= service()는 모든 방식을 수용하는 메소드
//= doGet()은 GET 방식만 수용하는 메소드
//= doPost()는 POST 방식만 수용하는 메소드
@WebServlet(urlPatterns = "/member/join.txt")
public class MemberJoinServlet extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//입력 : MemberDto
			//= 문제점 : POST 방식으로 전송될 경우 유니코드가 ASCII코드로 변경되어 전송된다.
			//= 해결책 : 수신한 데이터를 원래의 인코딩방식으로 변환하도록 지시한다.
			//= 주의 : 반드시 수신 전에 복원명령을 작성해야 한다.
			req.setCharacterEncoding("UTF-8");
			MemberDto memberDto = new MemberDto();
			memberDto.setMemberId(req.getParameter("memberId"));
			memberDto.setMemberPw(req.getParameter("memberPw"));
			memberDto.setMemberNick(req.getParameter("memberNick"));
			memberDto.setMemberBirth(req.getParameter("memberBirth"));
			memberDto.setMemberEmail(req.getParameter("memberEmail"));
			memberDto.setMemberPhone(req.getParameter("memberPhone"));

			//처리 : MemberDao
			MemberDao memberDao = new MemberDao();
			memberDao.join(memberDto);

			//출력 : 리다이렉트
			resp.sendRedirect("join_success.jsp");
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);//사용자에게 500번 상태값을 전송(500은 Internal server error)
		}
	}
}

 

[ 수정 후 회원 가입 처리 서블릿 ]

package home.servlet.member;

import java.io.File;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import home.beans.MemberDao;
import home.beans.MemberDto;
import home.beans.MemberProfileDao;
import home.beans.MemberProfileDto;

//= 입력창의 전송형태에 따라 메소드를 선택할 수 있다.
//= service()는 모든 방식을 수용하는 메소드
//= doGet()은 GET 방식만 수용하는 메소드
//= doPost()는 POST 방식만 수용하는 메소드
@WebServlet(urlPatterns = "/member/join.txt")
public class MemberJoinServlet extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//MultiPart 요청 처리 준비
			String savePath = "C:/upload";
			int maxSize = 5 * 1024 * 1024;
			String encoding = "UTF-8";
			DefaultFileRenamePolicy policy = new DefaultFileRenamePolicy();
			MultipartRequest mRequest = 
						new MultipartRequest(req, savePath, maxSize, encoding, policy);
			
			MemberDto memberDto = new MemberDto();
			memberDto.setMemberId(mRequest.getParameter("memberId"));
			memberDto.setMemberPw(mRequest.getParameter("memberPw"));
			memberDto.setMemberNick(mRequest.getParameter("memberNick"));
			memberDto.setMemberBirth(mRequest.getParameter("memberBirth"));
			memberDto.setMemberEmail(mRequest.getParameter("memberEmail"));
			memberDto.setMemberPhone(mRequest.getParameter("memberPhone"));

			//처리 : MemberDao
			MemberDao memberDao = new MemberDao();
			memberDao.join(memberDto);
			
			//프로필이 있다면 등록 처리
			if(mRequest.getFile("attach") != null) {//파일이 attach란 이름으로 올라왔다면
				MemberProfileDto memberProfileDto = new MemberProfileDto();
				memberProfileDto.setMemberId(memberDto.getMemberId());//회원 아이디
				memberProfileDto.setMemberProfileSavename(mRequest.getFilesystemName("attach"));//실제저장된이름
				memberProfileDto.setMemberProfileUploadname(mRequest.getOriginalFileName("attach"));//사용자가올린이름
				memberProfileDto.setMemberProfileType(mRequest.getContentType("attach"));//파일 유형
				memberProfileDto.setMemberProfileSize(mRequest.getFile("attach").length());

				MemberProfileDao memberProfileDao = new MemberProfileDao();
				memberProfileDao.insert(memberProfileDto);
			}
			
			//출력 : 리다이렉트
			resp.sendRedirect("join_success.jsp");
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);//사용자에게 500번 상태값을 전송(500은 Internal server error)
		}
	}
}

 

 

- 회원 가입 메인 페이지 (join.jsp) 수정 하기

[ 수정 전 코드 ]

<form action="join.txt" method="post">

[ 수정 후 코드]

<form action="join.txt" method="post" enctype="multipart/form-data">

[ 하단 에 추가 코드 ]

<tr>
	<th>프로필</th>
	<td>
		<input type="file" name="attach" accept="image/*">
	</td>
</tr>

 

[ 회원 프로필 사진 처리 구현 완료 ]

 


홈페이지 - 파일다운로드


[ 파일 다운로드 예제 ]

목표 : C:/upload(또는 다른 경로) 에 있는 test.txt라는 파일을 접속 시 바로 다운로드 하도록 페이지 구성

구조 : [test.txt] ---(입력)---> [서블릿] ---(출력)---> [사용자]
준비물 :
1. test.txt에 대한 파일 객체
2. test.txt에 대한 입력 스트림(FileInputStream)
3. 읽은 내용을 사용자에게 전달하기 위한 도구(resp)

package web09.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/download.txt")

public class FileDownlodServlet extends HttpServlet{
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		File target = new File("c:/upload", "test.txt");
		FileInputStream in = new FileInputStream(target);
		byte[] buffer = new byte[1024]; //1024배수 기준
		
		//[주의] 서버는 사용자에게 지금부터 보내는 내용이 "다운로드"를 위한 것임을 알려줘야 할 의무가 있다. 
		//= 추가적으로 "헤더" 설정을 해줘야 한다.
		//= 헤더는 사용자가 받게 될 내용에 대한 정보
		//= 보낼 내용의 유형, 크기, 이름, 방식, 설명, ...
		//= resp.setHeader("이름", "값"); 형태로 설정한다.
		//= header는 String밖에 설정할 수 없다.
		
		resp.setHeader("Content-Type", "application/octet-stream");
		//resp.setContentType("application/octet-stream");

		resp.setHeader("Content-Disposition", "attachment; filename=test.txt");

		resp.setHeader("Content-Encoding", "UTF-8");

		resp.setHeader("Content-Length", String.valueOf(target.length()));
		//resp.setContentLength((int)target.length());

		while(true) {
			int size = in.read(buffer);//size개 만큼 읽어오는 코드
			if(size == -1) break;
			resp.getOutputStream().write(buffer, 0, size);//size개만큼 내보내는 코드
		}

		in.close();
	}
}

 

 


[ 게시판 - 파일 다운로드 구현하기 ]

 

- 게시판 파일 다운로드 처리 (BoardFileDownloadServlet.java) 만들기

package home.servlet.board;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import home.beans.BoardFileDao;
import home.beans.BoardFileDto;

@WebServlet(urlPatterns = "/board/file/download.txt")
public class BoardFileDownloadServlet extends HttpServlet{
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//입력 : 단일조회 항목(int boardFileNo)
			//= 이 서블릿은 어떠한 경우에도 파일을 1개만 출력한다. 절대로 여러개를 동시에 출력할 수 없다. 
			int boardFileNo = Integer.parseInt(req.getParameter("boardFileNo"));

			//처리
			//1. board_file 단일조회
			BoardFileDao boardFileDao = new BoardFileDao();
			BoardFileDto boardFileDto = boardFileDao.get(boardFileNo);

			//2. 파일 정보 설정
			File dir = new File("C:/upload/board");
			File target = new File(dir, boardFileDto.getBoardFileSavename());
			InputStream in = new FileInputStream(target);
			byte[] buffer = new byte[8192];

			//3. 헤더 설정
			//[주의] 파일명에는 띄어쓰기나 일부 특수문자, 한글 등의 유니코드가 포함될 수 있다.
			//(1) 따옴표를 이용하여 띄어쓰기 등을 무시하도록 처리
			//(2) 인코딩을 통해서 파일명에 존재하는 유니코드를 변환 처리(URLEncoder)
			String uploadName = URLEncoder.encode(boardFileDto.getBoardFileUploadname(), "UTF-8");
			uploadName = uploadName.replace("+", "%20");

			resp.setHeader("Content-Type", "application/octet-stream");
			resp.setHeader("Content-Disposition", "attachment; filename=\""+uploadName+"\"");
			resp.setHeader("Content-Encoding", "UTF-8");
			resp.setHeader("Content-Length", String.valueOf(boardFileDto.getBoardFileSize()));

			//출력 : 파일 출력
			//4. 데이터 출력(다운로드)
			while(true) {
				int size = in.read(buffer);
				if(size == -1) break;
				resp.getOutputStream().write(buffer, 0, size);
			}

			in.close();
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);
		}
	}
}

 

- BoardFileDao 게시글에 해당하는 파일목록 조회 기능 만들기 

public List<BoardFileDto> find(int boardNo) throws Exception {
	Connection con = JdbcUtils.connect2();

	String sql = "select * from board_file where board_no = ? order by board_file_no asc";
	PreparedStatement ps = con.prepareStatement(sql);
	ps.setInt(1, boardNo);
	ResultSet rs = ps.executeQuery();

	List<BoardFileDto> list = new ArrayList<>();
	while(rs.next()) {
		BoardFileDto boardFileDto = new BoardFileDto();

		//copy
		boardFileDto.setBoardFileNo(rs.getInt("board_file_no"));
		boardFileDto.setBoardNo(rs.getInt("board_no"));
		boardFileDto.setBoardFileSavename(rs.getString("board_file_savename"));
		boardFileDto.setBoardFileUploadname(rs.getString("board_file_uploadname"));
		boardFileDto.setBoardFileType(rs.getString("board_file_type"));
		boardFileDto.setBoardFileSize(rs.getLong("board_file_size"));

		list.add(boardFileDto);
	}

	con.close();

	return list;
}

 

- 게시판 상세보기 (detail.jsp) 추가하기

[ 추가 전 코드 ]

boolean owner = boardDto.getBoardWriter().equals(memberId);
%>

<%
	//현재 게시글에 대한 댓글을 조회
	ReplyDao replyDao = new ReplyDao();
	List<ReplyDto> replyList = replyDao.list(boardNo);
%>

<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>

 

[ 추가 코드 ]

boolean owner = boardDto.getBoardWriter().equals(memberId);
%>

<%
	//현재 게시글에 대한 댓글을 조회
	ReplyDao replyDao = new ReplyDao();
	List<ReplyDto> replyList = replyDao.list(boardNo);
%>

<%
	//현재 게시글에 대한 파일정보를 조회
	BoardFileDao boardFileDao = new BoardFileDao();
	List<BoardFileDto> boardFileList = boardFileDao.find(boardNo);//파일이 여러 개일 경우
	//BoardFileDto boardFileDto = boardFileDao.find2(boardNo);//파일이 한 개일 경우
%>

<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>

 

[ 하단에 추가 코드 ]

<%-- 첨부파일이 있다면 첨부파일을 다운받을 수 있는 링크를 제공 --%>
<%if(!boardFileList.isEmpty()){ %>
	<%for(BoardFileDto boardFileDto : boardFileList){ %>
		<h6>
			<%=boardFileDto.getBoardFileUploadname() %>
			(<%=boardFileDto.getBoardFileSize()%> bytes)
			<a href="file/download.txt?boardFileNo=<%=boardFileDto.getBoardFileNo()%>">
				다운로드
			</a>
		</h6>
	<%} %>
<%} %>

 

[ 게시판 파일 다운로드 구현 완료 ]

 


[ 마이페이지 기능 추가 하기 ]

- 프로필 이미지 추가되도록 설정
- 내가 작성한 게시글을 표시하도록 설정 

 


[ 프로필 이미지 추가되도록 설정 ]

- MemberProfileDao 프로필 이미지 조회 기능 만들기 1

public MemberProfileDto get(int memberProfileNo) throws Exception {
	Connection con = JdbcUtils.connect2();

	String sql = "select * from member_profile where member_profile_no = ?";
	PreparedStatement ps = con.prepareStatement(sql);
	ps.setInt(1, memberProfileNo);
	ResultSet rs = ps.executeQuery();

	MemberProfileDto memberProfileDto;
	if(rs.next()) {
		memberProfileDto = new MemberProfileDto();
		memberProfileDto.setMemberProfileNo(rs.getInt("member_profile_no"));
		memberProfileDto.setMemberId(rs.getString("member_id"));
		memberProfileDto.setMemberProfileUploadname(rs.getString("member_profile_uploadname"));
		memberProfileDto.setMemberProfileSavename(rs.getString("member_profile_savename"));
		memberProfileDto.setMemberProfileSize(rs.getLong("member_profile_size"));
		memberProfileDto.setMemberProfileType(rs.getString("member_profile_type"));
	}
	else {
		memberProfileDto = null;
	}

	con.close();

	return memberProfileDto;
}

 

- MemberProfileDao 프로필 이미지 조회 기능 만들기 2

public MemberProfileDto get(String memberId) throws Exception {
	Connection con = JdbcUtils.connect2();

	String sql = "select * from member_profile where member_id = ?";
	PreparedStatement ps = con.prepareStatement(sql);
	ps.setString(1, memberId);
	ResultSet rs = ps.executeQuery();

	MemberProfileDto memberProfileDto;
	if(rs.next()) {
		memberProfileDto = new MemberProfileDto();
		memberProfileDto.setMemberProfileNo(rs.getInt("member_profile_no"));
		memberProfileDto.setMemberId(rs.getString("member_id"));
		memberProfileDto.setMemberProfileUploadname(rs.getString("member_profile_uploadname"));
		memberProfileDto.setMemberProfileSavename(rs.getString("member_profile_savename"));
		memberProfileDto.setMemberProfileSize(rs.getLong("member_profile_size"));
		memberProfileDto.setMemberProfileType(rs.getString("member_profile_type"));
	}
	else {
		memberProfileDto = null;
	}

	con.close();

	return memberProfileDto;
}

 

- 프로필 이미지 보기 처리 (MemberProfileServlet.java) 만들기

package home.servlet.member;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import home.beans.MemberProfileDao;
import home.beans.MemberProfileDto;

@WebServlet(urlPatterns = "/member/profile.txt")
public class MemberProfileServlet extends HttpServlet{
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//입력
			int memberProfileNo = Integer.parseInt(req.getParameter("memberProfileNo"));

			//처리
			MemberProfileDao memberProfileDao = new MemberProfileDao();
			MemberProfileDto memberProfileDto = memberProfileDao.get(memberProfileNo);

			File dir = new File("C:/upload/member");
			File target = new File(dir, memberProfileDto.getMemberProfileSavename());
			InputStream in = new FileInputStream(target);
			byte[] buffer = new byte[8192];

			String encodeFilename = URLEncoder.encode(memberProfileDto.getMemberProfileUploadname(), "UTF-8");
			encodeFilename = encodeFilename.replace("+", "%20");

			resp.setHeader("Content-Type", "application/octet-stream");
			resp.setHeader("Content-Disposition", "attachment; filename=\""+encodeFilename+"\"");
			resp.setHeader("Content-Encoding", "UTF-8");
			resp.setHeader("Content-Length", String.valueOf(memberProfileDto.getMemberProfileSize()));

			while(true) {
				int size = in.read(buffer);
				if(size == -1) break;
				resp.getOutputStream().write(buffer, 0, size);
			}

			in.close();
		}
		catch(Exception e) {
			e.printStackTrace();
			resp.sendError(500);
		}
	}
}

 

- 내 정보 보기 (mypage.jsp) 코드 추가

 

[ 상단에 코드 추가 ]

<%
	//프로필 이미지 조회 : 아이디로 해도됨(1-1관계)
	MemberProfileDao memberProfileDao = new MemberProfileDao();
	MemberProfileDto memberProfileDto = memberProfileDao.get(memberId);
%>

 

[ 상단에 코드 추가 ]

<%if(memberProfileDto == null){ %>
<img src="https://via.placeholder.com?text=User" width="150" height="150">
<%}else{ %>
<img src="profile.txt?memberProfileNo=<%=memberProfileDto.getMemberProfileNo()%>" width="150" height="150">
<%} %>

 

[ 마이페이지 프로필 사진 구현 완료 ]

 


[ 내가 작성한 게시글을 표시하도록 설정  ]

- BoardDao 일치 검색 기능 만들기 

public List<BoardDto> searchEquals(String column, String keyword) throws Exception {
	Connection con = JdbcUtils.connect2();

	String sql = "select * from board where #1 = ? order by board_no desc";
	sql = sql.replace("#1", column);//정적 바인딩
	PreparedStatement ps = con.prepareStatement(sql);
	ps.setString(1, keyword);
	ResultSet rs = ps.executeQuery();

	List<BoardDto> list = new ArrayList<>();
	while(rs.next()) {
		BoardDto boardDto = new BoardDto();

		boardDto.setBoardNo(rs.getInt("board_no"));
		boardDto.setBoardWriter(rs.getString("board_writer"));
		boardDto.setBoardTitle(rs.getString("board_title"));
		boardDto.setBoardContent(rs.getString("board_content"));
		boardDto.setBoardTime(rs.getDate("board_time"));
		boardDto.setBoardRead(rs.getInt("board_read"));
		boardDto.setBoardReply(rs.getInt("board_reply"));
		boardDto.setBoardSuperno(rs.getInt("board_superno"));
		boardDto.setBoardGroupno(rs.getInt("board_groupno"));
		boardDto.setBoardDepth(rs.getInt("board_depth"));

		list.add(boardDto);
	}

	con.close();

	return list;
	}
}

 

 

- 내 정보 보기 (mypage.jsp) 코드 추가

 

[ 상단에 코드 추가 ]

<%
	//내가 작성한 글 보여주기
	BoardDao boardDao = new BoardDao();
	List<BoardDto> myBoardList = boardDao.searchEquals("board_writer", memberId);
%>

 

[ 하단에 코드 추가 ]

<hr>

<h2>내가 작성한 게시글</h2>

<table border="1" width="90%">
	<thead>
		<tr>
			<th>번호</th>
			<th width="60%">제목</th>
			<th>작성일</th>
			<th>조회수</th>
		</tr>
	</thead>
	<tbody align="center">
		<%for(BoardDto boardDto : myBoardList){ %>
		<tr>
			<td><%=boardDto.getBoardNo()%></td>
			<td align="left">

				<%-- 
					게시글의 제목을 출력하기 전에 차수에 따라 띄어쓰기를 진행한다
					띄어쓰기는 HTML 특수문자인 &nbsp; 을 사용한다.
					답변글에는 reply icon을 추가로 출력한다. 
				--%>
				<%
					//if(boardDto.getBoardDepth() > 0){
					if(boardDto.hasDepth()){ 
				%>
					<%for(int i=0; i < boardDto.getBoardDepth(); i++){ %>
					&nbsp;&nbsp;&nbsp;&nbsp;
					<%} %>

					<img src="<%=request.getContextPath()%>/resource/image/reply.png" width="15" height="15">
				<%} %>

				<a href="<%=request.getContextPath()%>/board/detail.jsp?boardNo=<%=boardDto.getBoardNo()%>">
					<%=boardDto.getBoardTitle()%>
				</a>

				<!-- 제목 뒤에 댓글 개수를 출력한다 -->
				<%
				//if(boardDto.getBoardReply() > 0){
				if(boardDto.isReplyExist()){
				%>
					[<%=boardDto.getBoardReply()%>]
				<%} %>
			</td>
			<td><%=boardDto.getBoardTime()%></td>
			<td><%=boardDto.getBoardRead()%></td>
		</tr>
		<%} %>
	</tbody>
</table>

 

[ 마이페이지 내가 작성한 게시글 구현 완료 ]


[write.jsp] 전체 코드 

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%-- 입력 : 답글일 경우에는 boardSuperno라는 값이 전달된다. --%>
<%
	String boardSuperno = request.getParameter("boardSuperno");
%>

<%-- 처리 --%>
<%
	boolean answer = boardSuperno != null;
	String title = answer ? "답글 작성" : "새글 작성";
%>

<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>

<h2><%=title%></h2>

<form action="write.txt" method="post" enctype="multipart/form-data">

<%-- 답글일 경우에는 반드시 "상위글번호(boardSuperno)" 를 처리페이지로 전송해야 한다 --%>
<%if(answer){ %>
<input type="hidden" name="boardSuperno" value="<%=boardSuperno%>">
<%} %>

<table border="0">
	<tbody>
		<tr>
			<th>제목</th>
			<td><input type="text" name="boardTitle" required></td>
		</tr>
		<tr>
			<th>내용</th>
			<td>
				<textarea name="boardContent" required rows="10" cols="60"></textarea>
			</td>
		</tr>
		
				<!-- 첨부파일 -->
		<tr>
			<th>첨부</th>
			<td>
				<input type="file" name="attach">
			</td>
		</tr>
		
	</tbody>
	<tfoot>
		<tr>
			<td colspan="2" align="right">
				<input type="submit" value="등록">
			</td>
		</tr>
	</tfoot>
</table>

</form>

<jsp:include page="/template/footer.jsp"></jsp:include>

[join.jsp] 전체 코드

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<jsp:include page="/template/header.jsp"></jsp:include>

<h1>회원가입</h1>

<form action="join.txt" method="post" enctype="multipart/form-data">
	
	<table border="0">
		<tbody>
			<tr>
				<th>아이디</th>
				<td>
					<input type="text" name="memberId" required>
				</td>
			</tr>
			<tr>
				<th>비밀번호</th>
				<td>
					<input type="password" name="memberPw" required>
				</td>
			</tr>
			<tr>
				<th>닉네임</th>
				<td>
					<input type="text" name="memberNick" required>
				</td>
			</tr>
			<tr>
				<th>생년월일</th>
				<td>
					<input type="date" name="memberBirth" required>
				</td>
			</tr>
			<tr>
				<th>이메일</th>
				<td>
					<input type="email" name="memberEmail">
				</td>
			</tr>
			<tr>
				<th>전화번호</th>
				<td>
					<input type="tel" name="memberPhone">
				</td>
			</tr>
			<tr>

		<tr>
			<th>프로필</th>
			<td>
				<input type="file" name="attach" accept="image/*">
			</td>
		</tr>
		
				<!-- 
					colspan을 사용하면 셀 1개를 특정 칸 수만큼 늘릴 수 있다.
					rowspan을 사용하면 셀 1개를 특정 줄 수만큼 늘릴 수 있다.
					두 개를 동시에 사용하진 않는다 
				-->
				<td colspan="2" align="right">
					<input type="submit" value="가입">
				</td>
			</tr>
		</tbody>
	</table>
</form>

<jsp:include page="/template/footer.jsp"></jsp:include>

[mypage.jsp] 전체 코드 

더보기
<%@page import="home.beans.MemberProfileDto"%>
<%@page import="home.beans.MemberProfileDao"%>
<%@page import="home.beans.BoardDto"%>
<%@page import="home.beans.BoardDao"%>
<%@page import="home.beans.TotalHistoryDto"%>
<%@page import="home.beans.TotalHistoryDao"%>
<%@page import="java.text.DecimalFormat"%>
<%@page import="java.text.Format"%>
<%@page import="home.beans.HistoryDto"%>
<%@page import="home.beans.HistoryDao"%>
<%@page import="java.util.List"%>

<%@page import="home.beans.MemberDto"%>
<%@page import="home.beans.MemberDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%-- 입력 : 현재 로그인한 회원ID - String memberId --%>
<%
	String memberId = (String) session.getAttribute("ses");
%>

<%-- 처리 : 회원정보(MemberDto) --%>
<%
	MemberDao memberDao = new MemberDao();
	MemberDto memberDto = memberDao.get(memberId);

// 아래와 같이 조회하면 취소 여부를 알 수 없다.
//	HistoryDao historyDao = new HistoryDao();
//	List<HistoryDto> historyList = historyDao.findByMemberId(memberId);

// 새롭게 만든 뷰(total_history)를 이용하여 포인트 이력을 조회
	TotalHistoryDao historyDao = new TotalHistoryDao();
	List<TotalHistoryDto> historyList = historyDao.findByMemberId(memberId);
%>

<%
	//내가 작성한 글 보여주기
	BoardDao boardDao = new BoardDao();
	List<BoardDto> myBoardList = boardDao.searchEquals("board_writer", memberId);
%> 

<%
	//프로필 이미지 조회 : 아이디로 해도됨(1-1관계)
	MemberProfileDao memberProfileDao = new MemberProfileDao();
	MemberProfileDto memberProfileDto = memberProfileDao.get(memberId);
%>

<%-- 출력 --%>   
<jsp:include page="/template/header.jsp"></jsp:include>

<h2>회원 상세 정보</h2>

<%if(memberProfileDto == null){ %>
<img src="https://via.placeholder.com?text=User" width="150" height="150">
<%}else{ %>
<img src="profile.txt?memberProfileNo=<%=memberProfileDto.getMemberProfileNo()%>" width="150" height="150">
<%} %>

<table border="1" width="300">
	<tbody>
		<tr>
			<th width="25%">아이디</th>
			<td><%=memberDto.getMemberId()%></td>
		</tr>
		<tr>
			<th>닉네임</th>
			<td><%=memberDto.getMemberNick()%></td>
		</tr>
		<tr>
			<th>생년월일</th>
			<td><%=memberDto.getMemberBirthDay()%></td>
		</tr>	
		<tr>
			<th>이메일</th>
			<td><%=memberDto.getMemberEmailString()%></td>
		</tr>
		<tr>
			<th>전화번호</th>
			<td><%=memberDto.getMemberPhoneString()%></td>
		</tr>		
		<tr>
			<th>가입일시</th>
			<td><%=memberDto.getMemberJoin()%></td>
		</tr>
		<tr>
			<th>포인트</th>
			<td><%=memberDto.getMemberPoint()%></td>
		</tr>
		<tr>
			<th>등급</th>
			<td><%=memberDto.getMemberGrade()%></td>
		</tr>							
	</tbody>
</table>

<h3><a href="password.jsp">비밀번호 변경</a></h3>
<h3><a href="edit.jsp">개인정보 변경</a></h3>
<h3><a href="check.jsp">회원 탈퇴</a></h3>

<hr>

<!-- 포인트 내역 출력 -->
<h2>포인트 상세 내역</h2>

<table border="1" width="500">
	<thead>
		<tr>
			<th>일시</th>
			<th>금액</th>
			<th>메모</th>
			<th>cancel</th>
			<th>취소</th>
		</tr>
	</thead>
	<tbody>
		<%Format f = new DecimalFormat("#,##0"); %>
		<%for(TotalHistoryDto historyDto : historyList) { %>
		<tr>
			<td align="center"><%=historyDto.getHistoryTime()%></td>
			<td align="right"><%=f.format(historyDto.getHistoryAmount())%></td>
			<td align="left"><%=historyDto.getHistoryMemo()%></td>
			<td><%=historyDto.getCancel()%></td>
			<td>
				<%if(historyDto.available()){ %>
<%-- 				<a href="../point/cancel.txt?historyNo=<%=historyDto.getHistoryNo()%>">취소</a> 상대경로 --%>
				<a href="<%=request.getContextPath()%>/point/cancel.txt?historyNo=<%=historyDto.getHistoryNo()%>">취소</a>
				<%} %>
			</td>
		</tr>
		<%} %>
	</tbody>
</table>

<hr>

<h2>내가 작성한 게시글</h2>

<table border="1" width="90%">
	<thead>
		<tr>
			<th>번호</th>
			<th width="60%">제목</th>
			<th>작성일</th>
			<th>조회수</th>
		</tr>
	</thead>
	<tbody align="center">
		<%for(BoardDto boardDto : myBoardList){ %>
		<tr>
			<td><%=boardDto.getBoardNo()%></td>
			<td align="left">

				<%-- 
					게시글의 제목을 출력하기 전에 차수에 따라 띄어쓰기를 진행한다
					띄어쓰기는 HTML 특수문자인 &nbsp; 을 사용한다.
					답변글에는 reply icon을 추가로 출력한다. 
				--%>
				<%
					//if(boardDto.getBoardDepth() > 0){
					if(boardDto.hasDepth()){ 
				%>
					<%for(int i=0; i < boardDto.getBoardDepth(); i++){ %>
					&nbsp;&nbsp;&nbsp;&nbsp;
					<%} %>

					<img src="<%=request.getContextPath()%>/resource/image/reply.png" width="15" height="15">
				<%} %>

				<a href="<%=request.getContextPath()%>/board/detail.jsp?boardNo=<%=boardDto.getBoardNo()%>">
					<%=boardDto.getBoardTitle()%>
				</a>

				<!-- 제목 뒤에 댓글 개수를 출력한다 -->
				<%
				//if(boardDto.getBoardReply() > 0){
				if(boardDto.isReplyExist()){
				%>
					[<%=boardDto.getBoardReply()%>]
				<%} %>
			</td>
			<td><%=boardDto.getBoardTime()%></td>
			<td><%=boardDto.getBoardRead()%></td>
		</tr>
		<%} %>
	</tbody>
</table>

<jsp:include page="/template/footer.jsp"></jsp:include>

[detail.jsp] 전체 코드 

더보기
<%@page import="home.beans.BoardFileDto"%>
<%@page import="home.beans.BoardFileDao"%>
<%@page import="home.beans.ReplyDto"%>
<%@page import="java.util.List"%>
<%@page import="home.beans.ReplyDao"%>
<%@page import="java.util.HashSet"%>
<%@page import="java.util.Set"%>
<%@page import="home.beans.BoardDto"%>
<%@page import="home.beans.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<%-- 번외 : 수정 버튼을 눌렀을 때 처리되도록 구현하는 스크립트(나중에 배움) --%>
<script src="https://code.jquery.com/jquery-latest.js"></script>
<script>
	$(function(){
		$(".view-row").find(".edit-btn").click(function(){
			$(this).parents("tr.view-row").hide();
			$(this).parents("tr.view-row").next("tr.edit-row").show();
		});
		
		$(".edit-row").find(".edit-cancel-btn").click(function(){
			$(this).parents("tr.edit-row").hide();
			$(this).parents("tr.edit-row").prev("tr.view-row").show();
		});
		
		$(".edit-row").hide();
	});
</script>     
    
<%-- 입력 : 게시글번호(boardNo) --%>
<%
	int boardNo = Integer.parseInt(request.getParameter("boardNo"));
%>

<%-- 처리 --%>
<%
String memberId = (String)session.getAttribute("ses"); 
BoardDao boardDao = new BoardDao();

//1. boardViewedNo 라는 이름의 저장소를 세션에서 꺼내어 본다.
Set<Integer> boardViewedNo = (Set<Integer>)session.getAttribute("boardViewedNo");

//2. boardViewedNo 가 null 이면 "처음 글을 읽는 상태"임을 말하므로 저장소를 신규로 생성
if(boardViewedNo == null){
boardViewedNo = new HashSet<>();
//System.out.println("처음으로 글을 읽기 시작했습니다(저장소 생성)");
}

//3. 현재 글 번호를 저장소에 추가해본다
//3-1. 추가가 된다면 이 글은 처음 읽는 글
//3-2. 추가가 안된다면 이 글은 두 번 이상 읽은 글
if (boardViewedNo.add(boardNo)) {//처음 읽은 글인 경우
boardDao.readUp(boardNo, memberId);//조회수 증가(남에 글일때만)
//System.out.println("이 글은 처음 읽는 글입니다");
} else {
//System.out.println("이 글은 읽은 적이 있습니다");
}

//System.out.println("저장소 : " + boardViewedNo);

//4. 저장소 갱신
session.setAttribute("boardViewedNo", boardViewedNo);

BoardDto boardDto = boardDao.get(boardNo);//단일조회

//본인 글인지 아닌지를 판정하는 변수
//boolean owner = 세션의ses 값과 게시글 작성자가 같은가?;

boolean owner = boardDto.getBoardWriter().equals(memberId);
%>

<%
	//현재 게시글에 대한 댓글을 조회
	ReplyDao replyDao = new ReplyDao();
	List<ReplyDto> replyList = replyDao.list(boardNo);
%>

<%
	//현재 게시글에 대한 파일정보를 조회
	BoardFileDao boardFileDao = new BoardFileDao();
	List<BoardFileDto> boardFileList = boardFileDao.find(boardNo);//파일이 여러 개일 경우
	//BoardFileDto boardFileDto = boardFileDao.find2(boardNo);//파일이 한 개일 경우
%>

<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>

<h2><%=boardDto.getBoardNo()%>번 게시글</h2>

<table border="1" width="80%">
	<tbody>
		<tr>
			<td>
				<h3><%=boardDto.getBoardTitle()%></h3>
			</td>
		</tr>
		<tr>
			<td>
				등록일 : <%=boardDto.getBoardTime()%>
				|
				작성자 : <%=boardDto.getBoardWriter()%>
				|
				조회수 : <%=boardDto.getBoardRead()%>
			</td>
		</tr>
		<!-- 답답해 보이지 않도록 기본높이를 부여 -->
		<tr height="250" valign="top">
			<td>
				<pre><%=boardDto.getBoardContent()%></pre>
			</td>		
		</tr>
		<tr>
			<td align="right">
				<a href="write.jsp">글쓰기</a>
				<a href="write.jsp?boardSuperno=<%=boardDto.getBoardNo()%>">답글쓰기</a>
				<a href="list.jsp">목록보기</a>
				<%if(owner){ %>
				<a href="edit.jsp?boardNo=<%=boardDto.getBoardNo()%>">수정하기</a>
				<%-- 				<a href="delete.txt?boardNo=<%=boardNo%>">삭제하기</a> --%>
				<a href="delete.txt?boardNo=<%=boardDto.getBoardNo()%>">삭제하기</a>
				<%} %>
			</td>
		</tr>
	</tbody>
</table>

<%-- 댓글 작성, 목록 영역 --%>
<form action="./reply/insert.txt" method="post">
	<input type="hidden" name="boardNo" value="<%=boardDto.getBoardNo()%>">
	<table border="0" width="80%">
		<tbody>
			<tr>
				<td>
					<textarea name="replyContent" required rows="4" cols="80"></textarea>
				</td>
				<td>
					<input type="submit" value="댓글작성">
				</td>
			</tr>
		</tbody>
	</table>
</form>

<%if(replyList.isEmpty()){ %>
	<!-- 댓글이 없을 경우에 표시할 테이블 -->
	<table border="1" width="80%">
		<tbody>
			<tr><th>작성된 댓글이 없습니다</th></tr>
		</tbody>
	</table>
<%}else{ %>
	<!-- 댓글이 있을 경우에 표시할 테이블 -->
	<table border="1" width="80%">
		<tbody>
			<%for(ReplyDto replyDto : replyList){ %>
			<%
				//본인 댓글인지 판정 : 세션의 회원아이디와 댓글의 작성자를 비교
				//작성자 댓글인지 판정 : 게시글 작성자와 댓글의 작성자를 비교
				boolean myReply = memberId.equals(replyDto.getReplyWriter());
				boolean ownerReply = boardDto.getBoardWriter().equals(replyDto.getReplyWriter());
			%>
			<tr class="view-row">
				<td width="30%">
					<%=replyDto.getReplyWriter()%>
					<%-- 게시글 작성자의 댓글에는 표시 --%>
					<%if(ownerReply){ %>
						(작성자)
					<%} %>
					<br>
					(<%=replyDto.getReplyFullTime()%>)
				</td>
				<td>
					<pre><%=replyDto.getReplyContent()%></pre>
				</td>
				<td width="15%">
				<%-- 현재 사용자가 작성한 글에만 수정, 삭제를 표시 --%>
				<%if(myReply){ %>
				<a class="edit-btn">수정</a> |
				<a href="reply/delete.txt?boardNo=<%=replyDto.getBoardNo()%>&replyNo=<%=replyDto.getReplyNo()%>">삭제</a>
				<%} %>
				</td>
			</tr>
				<%-- 본인 글일 경우 수정을 위한 공간을 추가적으로 생성 --%>
				<%if(myReply){ %>
				<tr class="edit-row">
					<td colspan="3">
						<form action="reply/edit.txt" method="post">
							<input type="hidden" name="replyNo" value="<%=replyDto.getReplyNo()%>">
							<input type="hidden" name="boardNo" value="<%=replyDto.getBoardNo()%>">
							<textarea name="replyContent" required rows="4" cols="80"><%=replyDto.getReplyContent()%></textarea>
							<input type="submit" value="수정">
							<a class="edit-cancel-btn">취소</a>
						</form>
					</td>
				</tr>
				<%} %>
			<%} %>
		</tbody>
	</table>
<%} %>

<%-- 첨부파일이 있다면 첨부파일을 다운받을 수 있는 링크를 제공 --%>
<%if(!boardFileList.isEmpty()){ %>
	<%for(BoardFileDto boardFileDto : boardFileList){ %>
		<h6>
			<%=boardFileDto.getBoardFileUploadname() %>
			(<%=boardFileDto.getBoardFileSize()%> bytes)
			<a href="file/download.txt?boardFileNo=<%=boardFileDto.getBoardFileNo()%>">
				다운로드
			</a>

<%-- 			<img src="file/download.txt?boardFileNo=<%=boardFileDto.getBoardFileNo()%>" width="50" height="50"> --%>
		</h6>
	<%} %>
<%} %>
<jsp:include page="/template/footer.jsp"></jsp:include>