[포트폴리오 페이지]_18단계_게시판 심화_(파일 업로드 1편)

2022. 4. 29. 10:46[Spring]_/[Spring]_포트폴리오 페이지 만들기

728x90
반응형

해당 게시글은 업로드 1편 , 다운로드 2편으로 구성되어 있습니다.

 

[환경]

 

개발툴 : IntelliJ

DB : oracle

프레임워크 : spring , mybatis

사용 언어 : ES6, Java , Html5 , CSS


완성 화면]

 

input type="file" 생성
해당 버튼 클릭시 파일 선택하는 창이 뜸
한가지 or 다중 파일 선택시 하단에 썸네일, 이름 , 삭제버튼 생성
삭제버튼 클릭시 해당 row 삭제와 동시에 전달하는 데이터도 삭제
등록후 해당 게시물 조회시
정상적으로 파일 이름과 함께 다운로드가 되는 모습


 

개발 목표 : SPA 로 구현한 게시판에서 글 작성시 파일 업로드 기능 추가,

파일 업로드시 Mulitple 속성 부여하여 여러개의 파일 업로드 하도록 구현파일 다운로드시 글 작성자가 저장한 파일 명 그대로 다운로드하도록 구현, 한국어 인코딩 추가

 


1] 구성

유저가 파일을 업로드 할 때

 

- > Input type=file 태그를 활용하여 FormData 형태의 구조로 Controller에 전달

-> Controller에서는 전달받은 데이터를 핸들링하는 Service 호출

-> Service에서 해당 데이터를 매핑하여 반환

-> Controller에서 반환된 데이터를 가지고 DB 입력하는 Service 호출

-> 호출받은 Service가 해당 데이터를 DB에 입력

-> 호출 끝

 

유저가 파일을 다운로드 할 때

 

-> 목록에서 row 선택

-> 선택시 상세화면이 열리면서 해당 게시글의 filename과 filerealname을 DB에서 전달받음

-> 첨부파일의 a태그 선택

-> 매핑되어있는 url 로 Download Controller 호출과 동시에 HttpServletRequest 로 filename과 filerealname 값 전달

-> 전달받은 데이터로 서버에 저장된 filename을 찾고 filerealname의 이름으로 파일 다운로드 반환

-> 다운로드 끝

 


2] DB 구성 변경 , Model 변경

 

이랬던 DB 베이스 구조를

filename : UUID 로 작성된 무작위 숫자로 서버에 저장될 파일명과 동일한 이름

filerealname : 유저가 게시글 작성시 등록한 파일의 이름

을 저장하도록 컬럼을 추가한다

alter table board add filerealname varchar(500);
alter table board modify filename VARCHAR(500);

 

데이터베이스가 바뀌었으니 BoardVO도 바뀌어야 합니다.

추가한 2개의 값을 선언하고 Getter Setter 와 toString을 생성합니다.

 

//게시물 파일
private String filename;
private String filerealname;

 

※Getter,Setter,toString 생략


3] boardregister.jsp 등록부분

 

3-1] 파일 업로드를 위한 프론트 input type="file" 를 사용

 

내용이 길어져서 포스팅으로 대체

https://yn971106.tistory.com/99

 

[Input_type="file"]_업로드 구현을 위한 프론트 부분

포스팅 목표] 파일 업로드 방식 중 많이 사용하는 을 사용해 여러파일을 등록 가능하게 하며, 등록된 파일의 썸네일 표출 , line 별 삭제기능 구현 방식 설명 완료화면] HTML ] 제목 : 내용 : 파일첨

yn971106.tistory.com

 

 

3-2] FormData 를 fetch 로 전송

regibtn.onclick = function () {
    let myformdata = document.getElementById('formdata');
    let formData = new FormData(myformdata);

    let filelist = document.getElementById("ex_file").files;

    let sendList = new Array();

    for (let i = 0; i < filelist.length; i++) {
        let fullname = filelist[i].name
        let str = fullname.split('.');
        let ext = str[1];

        let data = new Object();
        data.filename = fullname;
        data.ext = ext;
        sendList.push(data);
    }
    let jsonData = JSON.stringify(sendList);

    formData.append("files", jsonData);
    formData.append("writer", writer);
    formData.append("date", date)

    fetch("/board/register.do", {
        method: 'POST',
        body: formData
    }).then(res => {
        $common.deletehistory("/regi/loader.do");

        let url = 'menu/menu1.do'
        $('#main_content').load(url, function () {

        });
    });


}

 

프론트 준비가 끝났다면 등록버튼 이벤트 클릭시에 입력된 정보들을 formData에 담아서 Controller에 전달하면 됩니다.

 

formData를 선언하고

let myformdata = document.getElementById('formdata');
let formData = new FormData(myformdata);

 

지금 선택된 파일들은 fileList 객체로 반환되어 있습니다. 이를 평범한 배열안에 넣기위해 for문을 돌며 내용을 배열에 집어넣습니다.

let filelist = document.getElementById("ex_file").files;

let sendList = new Array();

for (let i = 0; i < filelist.length; i++) {
    let fullname = filelist[i].name
    let str = fullname.split('.');
    let ext = str[1];

    let data = new Object();
    data.filename = fullname;
    data.ext = ext;
    sendList.push(data);
}

 

해당 배열을 Json 객체로 반환 후 formData에 파일, 작성자, 일자를 기입

formData 안에는 내용과, 제목이 포함되어 있음

let jsonData = JSON.stringify(sendList);

formData.append("files", jsonData);
formData.append("writer", writer);
formData.append("date", date)

이를 해당 Controller에 전달하고

통신 완료시 다시 메뉴로 되돌아가는 기능 설정

단 여기서 formData를 전달하게 되는데

보통 fetch 사용시 header 라는 부분을 집어넣게 됩니다.

formData 를 전달 시에는 적어주지 않아도 저절로 선택되어 전송되게 되니, 헤더 부분은 뺴고 전달합니다.

fetch("/board/register.do", {
    method: 'POST',
    body: formData
}).then(res => {
    $common.deletehistory("/regi/loader.do");

    let url = 'menu/menu1.do'
    $('#main_content').load(url, function () {

    });
});

4] 데이터 통신, 저장부분

 

4-1] Controller

@RequestMapping("/board/register.do")
@ResponseBody
public String boardWrite(MultipartHttpServletRequest req) throws Exception {
    System.out.println("게시글 등록 컨트롤러 시작");

    Map<String,String> result = boardService.createfileList(req);

    BoardVO boarddata = new BoardVO();
    int cnt = boardService.serchbno() + 1;

    boarddata.setBno(cnt);
    boarddata.setTitle(result.get("title"));
    boarddata.setContent(result.get("content"));
    boarddata.setWriter(result.get("writer"));
    boarddata.setRegdate(result.get("date"));
    boarddata.setViewcnt(0);
    boarddata.setFilename(result.get("filelist"));
    boarddata.setFilerealname(result.get("filerealname"));

    boardService.boardWrite(boarddata);

    return "ok";
}

해당 컨트롤러에서 받고 난뒤 받은 정보를 파일리스트를 만드는 서비스에 전달합니다.

그 결과값은 Map 형식으로 Key, value로 값을 꺼내와서 boardVo 객체에 Set 시킵니다.

그리고 이 객체를 게시글 작성 service로 전달합니다.

 

4-2] createfileList service 소스코드

@Override
public Map<String, String> createfileList(MultipartHttpServletRequest req) throws IOException {

    System.out.println("--게시글 등록을 위한 데이터 변환 처리 시작--");

    System.out.println("req = " + req);
    String files = req.getParameter("files");
    System.out.println("files = " + files);
    String title = req.getParameter("subject");
    System.out.println("title = " + title);
    String content = req.getParameter("content");
    System.out.println("content = " + content);
    String writer = req.getParameter("writer");
    System.out.println("writer = " + writer);
    String date = req.getParameter("date");
    System.out.println("date = " + date);

    List<MultipartFile> file = req.getFiles("filename");
    System.out.println("file =" + file);
    String filename = "";

    ArrayList<String> filenamelist = new ArrayList<String>();
    ArrayList<String> filerealnamelist = new ArrayList<String>();
    Map<String, String> result = new HashMap<String,String>();

    // file 첨부여부 확인
    if (!file.get(0).getOriginalFilename().isEmpty()) {

        System.out.println("비어있지 않음");

        for (int i = 0; i < file.size(); i++) {

            //원래이름 저장
            String originname = file.get(i).getOriginalFilename();
            System.out.println("originname = " + originname);

            //확장자
            String ext = FilenameUtils.getExtension(originname);
            System.out.println("ext = " + ext);
            //중복방지를 위한 UUID 설정
            UUID uuid = UUID.randomUUID();
            //저장되는 파일이름
            filename = uuid + "." + ext;

            file.get(i).transferTo(new File("C:\\filetest\\" + filename));


            filenamelist.add(filename);
            filerealnamelist.add(originname);

            System.out.println("filenamelist = " + filenamelist);
            System.out.println("filenamelist.toString() = " + filenamelist.toString());

        }
    }

    result.put("title",title);
    result.put("content",content);
    result.put("writer",writer);
    result.put("date",date);
    result.put("filelist",filenamelist.toString());
    result.put("filerealname",filerealnamelist.toString());


    return result;
}

해당 서비스를 만들기 위해서는 service에 인터페이스를 먼저 만드셔야 합니다.

 

해당 서비스는 간단히 요약하자면,

request 받은 데이터들을 각각의 파라미터값으로 매핑한 뒤에 데이터베이스에 입력하기 위한 String 형태로 변환하는 기능을 가지고 있습니다.

 

formData 형식의 file 데이터 접근시

List<MultipartFile> file = req.getFiles("filename");
System.out.println("file =" + file);
String filename = "";

위와같은 방식으로 접근합니다.

 

아래의 코드가 업로드의 핵심 부분입니다.

if (!file.get(0).getOriginalFilename().isEmpty()) {

    System.out.println("비어있지 않음");

    for (int i = 0; i < file.size(); i++) {

        //원래이름 저장
        String originname = file.get(i).getOriginalFilename();
        System.out.println("originname = " + originname);

        //확장자
        String ext = FilenameUtils.getExtension(originname);
        System.out.println("ext = " + ext);
        //중복방지를 위한 UUID 설정
        UUID uuid = UUID.randomUUID();
        //저장되는 파일이름
        filename = uuid + "." + ext;

        file.get(i).transferTo(new File("C:\\filetest\\" + filename));


        filenamelist.add(filename);
        filerealnamelist.add(originname);

        System.out.println("filenamelist = " + filenamelist);
        System.out.println("filenamelist.toString() = " + filenamelist.toString());

    }
}

우선 파일이 있는지 없는지 확인합니다.

없을경우 지나치고 있을경우 수행하게 됩니다.

 

List 구조의 MulipartFile 의 경우 크기를 size를 통해서 판별하고 그 리스트의 크기만큼 반복문을 수행합니다.

String originname = file.get(i).getOriginalFilename();

해당 리스트에 get(인덱스 번호)에 접근하고 getOriginalFilename 함수로 원본의 파일 이름을 받아옵니다.

 

확장자는 getExtension 함수를 사용합니다.

String ext = FilenameUtils.getExtension(originname);

 

중복 방지를위해 UUID를 사용하고 해당 아이디와 확장자 이름을 붙여서 저장합니다.

저장하는 방법은 transferTo 함수를 이용해서

원하는 위치 + 파일 이름 명으로 저장합니다.

//중복방지를 위한 UUID 설정
UUID uuid = UUID.randomUUID();
//저장되는 파일이름
filename = uuid + "." + ext;

file.get(i).transferTo(new File("C:\\filetest\\" + filename));

서버에 지정된 위치에 저장 이후 데이터 베이스에 입력하기 위해

UUID 로 변경된 이름과 원본 이름를 반환할 list 에 add 합니다.

 

4-3] boardWrite 서비스

 

이전 포스팅에서 다뤘기 때문에 설명 생략

다른건 변한게 없고, 쿼리만 조금 수정되었습니다.

Mapper.xml

<insert id="boardWrite">
    insert into board (bno, title, content, writer, filename, filerealname)
    values (#{bno}, #{title}, #{content}, #{writer}, #{filename}, #{filerealname})
</insert>

컬럼이 추가되었으니, 쿼리도 2개 추가해줍니다.

 

 

데이터 입력후 등록버튼클릭시

정상적으로 값이 들어가는 것을 확인 할 수 있습니다.

 

다음에는 업로드 된 파일을 다운로드하는 방법을 포스팅 해보겠습니다.

감사합니다.

728x90
반응형