yahayaha

31. 첨부파일 등록하기 본문

spring/프로젝트

31. 첨부파일 등록하기

yaha 2024. 2. 24. 01:27

첨부파일이 게시물과 합쳐지면 게시물과 첨부파일의 관계를 저장하는 테이블의 설계를 먼저 해야함.

 

게시물의 첨부파일은 각각 고유한 UUID를 사지고 있기 때문에 별도의 PK를 지정할 필요가 없음.

 

게시물을 등록할 때 첨부파일 테이블도 insert 작업이 진행되어야 하므로 트랜잭션 처리가 필요함.

 

첨부파일 보관 테이블을 설계해야함. 

 

create table tbl_attach(
    uuid varchar2(100) not null,
    uploadPath varchar2(2000) not null,
    fileName varchar2(100) not null,
    filetype char(1) default 'I',
    bno number(10,0)
); 

alter table tbl_attach add constraint pk_attach primary key (uuid);

alter table tbl_attach add constraint fk_board_attach foreign key (bno)
references tbl_board(bno);

 

 

테이블 생성 후 boardAttachVO 클래스를 설계.

 

import lombok.Data;

@Data
public class BoardAttachVO {

    private String uuid;
    private String uploadPath;
    private String fileName;
    private boolean fileType;

    private Long bno;
}

 

BoardVO는 등록 시 한 번에 BoardAttachVO를 처리할 수 있도록 List<BoardAttachVO> 추가해줌.

@Data
public class BoardVO {

	private Long bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private Date updateDate;
	
	//댓글 개수의 대한 변수.
	private int replyCnt;
	
	private List<BoardAttachVO> attacList;
}

 

 

그리고 첨부파일을 처리를 위한 Mapper 처리를 해줘야함. 첨부파일 정보를 데이터베이스를 이용해서 보관하기에 인터페이스와 XML도 처리 해줘야함.

 

// 인터페이스
import java.util.List;

import org.zerock.domain.BoardAttachVO;

public interface BoardAttachMapper {
	
	public void insert(BoardAttachVO attvo);
	
	public void delete(String uuid);
	
	public List<BoardAttachVO> findByBno(Long bno);

}



//AttchMapper.xml

<mapper namespace="org.zerock.mapper.BoardAttachMapper">

	<insert id="insert">
		insert into tbl_attach (uuid, uploadpath, filename, filetype, bno)
		values ( #{uuid}, #{uploadpath}, #{filename}, #{filetype}, #{bno} )
	</insert>
	
	<delete id="delete">
		delete from tbl_attach where uuid = #{uuid}
	</delete>
	
	<select id="findByBno" resultType="org.zerock.domain.BoardAttachVO">
		select * from tbl_attach where bno = #{bno}
	</select>
	

</mapper>

 

첨부파일 자체의 처리는 Ajax로 끝낸 상태. 게시물의 등록시점에는 현재 시간에 업로드된 파일들에 정보를 등록하려는 게시물의 정보와 같이 전송해서 처리야함.

 

게시물의 등록 버튼을 클릭했을 때 현재 서버에 업로드된 파일의 정보를 input으로 만들어서 전송.

 

먼저 register.jsp에 첨부파일을 추가할 수 있도록 수정.

 

<!-- 파일 업로드 부분 -->
<style>
.uploadResult{
    width:100%;
    background: gray;
}
.uploadResult ul{
    display: flex;
    flex-flow: row;
    justify-content: center;
    align-items: center;
}
.uploadResult ul li{
    list-style: none;
    padding: 10px;
}
.uploadResult ul li img{
    width: 20px;
}
</style>
<div class="row">
    <div class="col-lg-12">
        <div class="panel panel-default">
            <div class="panel-heading">
                File Attach
            </div>
            <!-- /.panel-heading -->
            <div class="panel-body">
                <div class="form-group uploadDiv">
                     <input type="file" name="uploadFile" multiple>
                 </div>

                 <div class="uploadResult">
                    <ul>

                    </ul>
                 </div>
            </div>
            <!-- /.panel-body -->
        </div>
        <!-- /.panel -->
    </div>
    <!-- /.col-lg-12 -->
</div>

 

div 안에 <div class="form-group uploadDiv"> 등 추가해서 파일 업로드한 결과를 처리할 수 있도록함.

 

 

 

복잡한 부분은 파을 선택 또는 submit Button을 클릭했을때 javascript 처리.

 

먼저 submit Button을 클릭했을 때 첨부파일 관련 처리가 가능하도록 기본 동작 제한을 걸어줌.

 

<script>
$(document).ready(function(e){
    var formObj = $("form[role='form]']").on("click", function(e){
        e.preventDefault();

        console.log("submit clicked");


    });
    var regex = new RegExp("(.*?)\.(exe|sh|zip|aiz)$");

    var maxSize = 5242880; //5MB

    function checkExtension(fileName, fileSize){
        if(fileSize >= maxSize){
            alert("파일 사이즈 초과");
            return false;
        }

        if(regex.test(fileName)){
            alert("해당 종류의 파일은 업로드 불가능합니다.");
            return false;
        }
        return true;
    }

    $("input[type='file']").change(function(e){
        var formData = new FormData();

        var inputFile = $("input[name='uploadFile']");

        var files = inputFile[0].files;

        for(var i = 0; i < files.length; i++){

            if(!checkExtension(files[i].name, files[i].size)){
                return false;
            }
            formData.append("uploadFile", files[i]);
        }

        $.ajax({
            url: '/uploadAjaxAction',
            processData: false,
            contentType: false,data:
            formData,type: 'POST',
            dataType:'json',
            success:function(result){
                console.log(result);
            }
        }); //$.ajax
    });
});
</script>

 

첨부된 파일의 처리는 기존과 동일. 하지만 썸네일이나 파일 아이콘을 보여주는 부분을 아직 처리가 안된 상태.

 

먼저 콘솔창을 이용해서 업로드가 정상적인 처리가 되는지 확인.

 

 

업로드된 결과를 화면에 썸네일을 만들어서 처리하는 부분은 별도의 showUploadResult 함수를 제작.

 

function showUploadResult(uploadResultArr){
    if(!uploadResultArr || uploadResultArr.length == 0){
        return;
    }
    var uploadUL = $(".uploadResult ul");

    var str = "";

    $(uploadResultArr).each(function(i, obj){
        if(obj.image){

            var fileCallPath = encodeURIComponent(obj.uploadPath + "/s_"+obj.uuid+"_"+obj.fileName);
            str += "<li><div>";
            str += "<span> " + obj.fileName+"</span>";
            str += "<button type='button' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>"
            str += "<img src='/display?fileName="+fileCallPath+"'>";
            str += "</div>";
            str += "</li>";

        }else{
            var fileCallPath = encodeURIComponent(obj.uploadPath + "/s_"+obj.uuid+"_"+obj.fileName);

            var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");

            str += "<li><div>";
            str += "<span> " + obj.fileName+"</span>";
            str += "<button type='button' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>"
            str += "<img src='/resources/img/attach.png'></a>";
            str += "</div>";
            str += "</li>";
        }
    });
    uploadUL.append(str);
}

$.ajax({
    ...............
    success:function(result){
        console.log(result);
        showUploadResult(result); //업로드 결과 처리
    }
}); //$.ajax

 

추가 후 파일을 업로드 해보면 썸네일이 나오는걸 확인 가능함.

 

 

첨부파일을 올렸으면 변경도 가능하도록 해야함.

 

첨부파일 변경은 사실상 저기 쪼만한 X 표시 모양을 클릭했을때 이벤트가 실행되도록 함.

 

먼저 저 버튼을 클릭하면 삭제된다는 로그를 한번 출력해봄.

 

$(".uploadResult").on("click", "button", function(e){
    console.log("삭제함.");
});

로그가 잘 찍힘.

 

삭제를 위해서 업로드된 파일의 경로와 UUID가 포함된 파일 이름이 필요함.

 

button 태그에 data-file과 data-type 정보를 추가를 해줘야함.

 

function showUploadResult(uploadResultArr){
    if(!uploadResultArr || uploadResultArr.length == 0){
        return;
    }
    var uploadUL = $(".uploadResult ul");

    var str = "";

    $(uploadResultArr).each(function(i, obj){
        if(obj.image){

            var fileCallPath = encodeURIComponent(obj.uploadPath + "/s_"+obj.uuid+"_"+obj.fileName);
            str += "<li><div>";
            str += "<span> " + obj.fileName+"</span>";
            str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='image' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>"
            str += "<img src='/display?fileName="+fileCallPath+"'>";
            str += "</div>";
            str += "</li>";

        }else{
            var fileCallPath = encodeURIComponent(obj.uploadPath + "/s_"+obj.uuid+"_"+obj.fileName);

            var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");

            str += "<li><div>";
            str += "<span> " + obj.fileName+"</span>";
            str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='file' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>"
            str += "<img src='/resources/img/attach.png'></a>";
            str += "</div>";
            str += "</li>";
        }
    });
    uploadUL.append(str);
}

 

그리고 x를 클릭하면 서버에서 삭제하도록 이벤트 처리.

 

$(".uploadResult").on("click", "button", function(e){
    console.log("삭제함.");

    var targetFile = $(this).data("file");
    var type = $(this).data("type");

    var targetLi = $(this).closest("li");

    // 첨부파일 삭제 ajax
    $.ajax({
        url: '/deleteFile',
        data: {fileName: targetFile, type: type},
        dataType: 'text',
        type: 'POST',
            success: function(result){
                alert(result);
                targetLi.remove()
            }
    }); //$.ajax
}); // 삭제 이벤트 끝

 

그리고 브라우저에서 첨부파일을 삭제하면 업로드된 파일도 같이 삭제가 실행됨.

 

 

 

이제 게시물을 등록했으면 첨부파일도 데이터베이스에 처리를 해줘야함.

 

게시물이 등록될 때 첨부파일과 관련된 자료를 같이 전송하고, 이를 데이터베이스에 등록해야함.

 

게이물의 등록은 form 태그를 통해서 이루어짐. 이미 업로드된 첨부파일의 정보는 별도의 input type=hidden 태그를 생성해서 처리.

 

첨부파일 정보를 태그로 생성할 때 첨부파일과 관련된 정보를 추가해야함.

 

function showUploadResult(uploadResultArr){
    if(!uploadResultArr || uploadResultArr.length == 0){
        return;
    }
    var uploadUL = $(".uploadResult ul");

    var str = "";

    $(uploadResultArr).each(function(i, obj){
        if(obj.image){
          var fileCallPath =  encodeURIComponent( obj.uploadPath+ "/s_"+obj.uuid +"_"+obj.fileName);
        str += "<li data-path='"+obj.uploadPath+"'";
        str +=" data-uuid='"+obj.uuid+"'data-filename='"+obj.fileName+"' data-type='"+obj.image+"'"
        str +" ><div>";
        str += "<span> "+ obj.fileName+"</span>";
        str += "<button type='button' data-file=\'"+fileCallPath+"\' "
        str += "data-type='image' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
        str += "<a><img src='/display?fileName="+fileCallPath+"'></a>";
        str += "</div>";
        str +"</li>";
        }else{
          var fileCallPath =  encodeURIComponent( obj.uploadPath+"/"+ obj.uuid +"_"+obj.fileName);            
          var fileLink = fileCallPath.replace(new RegExp(/\\/g),"/");

          str += "<li data-path='"+obj.uploadPath+"'";
          str +=" data-uuid='"+obj.uuid+"'data-filename='"+obj.fileName+"' data-type='"+obj.image+"'"
          str +" ><div>";
          str += "<span> "+ obj.fileName+"</span>";
          str += "<button type='button' data-file=\'"+fileCallPath+"\' data-type='file' class='btn btn-warning btn-circle'><i class='fa fa-times'></i></button><br>";
          str += "<a><img src='/resources/img/attach.png'></a>";
          str += "</div>";
          str +"</li>";
        } 
    });
    uploadUL.append(str);
}

 

업로드된 정보는 JSON으로 처리되는게 확인 가능함.

 

 

이제 변환된 정보는 BoardVO로 수집을함.

 

@Data
public class BoardVO {

	private Long bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private Date updateDate;
	
	//댓글 개수의 대한 변수.
	private int replyCnt;
	
	private List<BoardAttachVO> attacList;
}

 

BoardVO에 attacList라는 변수로 첨부파일의 정보를 수집을 함.

 

input type=hidden의 name은 attacList[인덱스번호]같은 이름을 사용해야함.

 

JSP화면에서는 javascrip를 사용해서 기존 form 태그를 전송하는 부분을 수정.

 

//form 태그에 id추가해주기.
<form action="/board/register" method="post" id='formObj'>

$(document).ready(function(e){

var formObj = $("#formObj");

$("button[type='submit']").on("click", function(e) {
    e.preventDefault();

    console.log("전송 클릭");

    var str = "";

    $(".uploadResult ul li").each(function (i, obj) {
        var jobj = $(obj);

        console.dir(jobj);
        str += "<input type='hidden' name='attachList[" + i + "].fileName' value='"+jobj.data("filename") + "'>";
        str += "<input type='hidden' name='attachList[" + i + "].uuid' value='"+jobj.data("uuid") + "'>";
        str += "<input type='hidden' name='attachList[" + i + "].uploadPath' value='"+jobj.data("path") + "'>";
        str += "<input type='hidden' name='attachList[" + i + "].fileType' value='"+jobj.data("type") + "'>";
    });
    formObj.append(str).submit();
});

 

게시물 등록을 선택하면 이미 업로드된 항목들을 내부적으로 input type=hidden 태그들로 만들어서 form 태그가 submit 될 때 같이 전송.

 

파라미터를 수집하는 BoardController는 별도 처리 없이 전송되는 데이터가 수집되는지 확인 해야함.

 

@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {

    log.info("확인 테스트 ======");

    log.info("register : " + board);

    if(board.getAttacList() != null) {
        board.getAttacList().forEach(attach -> log.info(attach));
    }
    log.info("확인 테스트 ======");
//		log.info("board : " + board);
//		
//		Long bno = service.register(board);
//		
//		log.info("BNO : " + bno);
//		rttr.addFlashAttribute("result", bno);

    return "redirect:/board/list";
}

 

이제 새로운 게시글을 등록

 

 

BoardMapper와 BoardAttachMapper는 이미 작성을 한 상태.

 

남은 작업은 BoardServiceImpl에서 두 개의 Mapper 인터페이스 타입을 주입 후 호출을 하면됨.

 

2개의 Mapper를 주입 받아야함 자동주입 대신 Setter 메서드를 이용.

public class BoardServiceImpl implements BoardService{

	@Setter(onMethod_ = @Autowired)
	private BoardMapper mapper;
	
	@Setter(onMethod_ = @Autowired)
	private BoardAttachMapper attachMapper;

 

게시물의 등록 작업은 tbl_board 테이블과 tbl_attach 테이블 양쪽 모두가 insert가 진행되어야함. 그래서 트랜잭션 처리가 필요함.

 

오라클은 시퀀스를 이용해서 nextval와 currval을 이용해서 처리하지만, MyBatis의 selectKey를 이용해서 별도의 currval을 매번 호출할 필요가 없음.

 

@Transactional
@Override
public void register(BoardVO board) {

    log.info("register....." + board);

    mapper.insertSelectkey(board);

    if(board.getAttachList() == null || board.getAttachList().size() <= 0 ) {
        return;
    }

    board.getAttachList().forEach(attach -> {
        attach.setBno(board.getBno());
        attachMapper.insert(attach);
    });
}

 

그리고 Controller에 주석을 풀어줌.

 

@PostMapping("/register")
public String register(BoardVO board, RedirectAttributes rttr) {

  log.info("=====================================");	

  log.info("register: " + board);

  if (board.getAttachList() != null) {

      board.getAttachList().forEach(attach -> log.info(attach));
  }

  log.info("=====================================");

  service.register(board);
   rttr.addFlashAttribute("result", board.getBno());

  return "redirect:/board/list";
}

 

 

이제 ServiceImpl의 register는 트랜잭션 하에서 tbl_board에 먼저 게시물을 등록하고, 각 첨부파일은 생성된 게시물 번호를 세팅한 후 tbl_attach 테이블에 데이터를 추가함.

 

그리고 첨부파일을 등록하고 데이터베이스를 보면 추가된걸 확인이 가능함.

 

 

 

 

 

이렇게 첨부파일은 끝.

 

------------------------------------ ------------------------------------ ------------------------------------ ------------------------------------

 

와 이거하는데 오타랑 오류가 많아서 개오래걸렸음.