danDevlog

Spring - 16 (첨부파일 - 2) 본문

Spring 게시판 만들기

Spring - 16 (첨부파일 - 2)

단데기이 2022. 4. 29. 09:54
728x90

이제 데이터 베이스에 저장할 수 있도록 구현할 차례이다.

 

 

register.jsp의 전송 클릭시 첨부파일 정보를 함께 보낼 수 있게한다. (document.readt 바로 아랫부분에 추가하였다.)

 

BoardController 에서 첨부파일 정보 전송 확인한다.

@PostMapping("/register")
	public String register(BoardVO board, RedirectAttributes rttr) {
		// @Controller 어노테이션이 붙고,
		// 컴포넌트 스캔에 패키지가 지정되어 있다면,
		// 매개변수 인자들은 스프링이 자동으로 생성 할당 함.
		log.info("register : " + board);
		service.register(board);
		// Controller에서는 첨부파일을 처리하지 않고,
		// 서비스 계층에서 처리할 예정 이다.
		// 그러므로, 첨부파일 정보에 bno가 널인 것은 상관 없음.
		
		if(board.getAttachList() != null) {
			// 첨부파일이 있다면,
			board.getAttachList().forEach(attach -> log.info(attach));
			// 첨부파일의 각 요소를 로그로 출력.
		}
		
		rttr.addFlashAttribute("result",board.getBno());
		// 리다이렉트 시키면서 1회용 값을 전달.
		
		return "redirect:/board/list";

 

로그만 확인하고싶다면 service.register 부분을 주석처리하면된다.

 

BoardServiceImp에 코드 추가

@Log4j // lombok 로그 이용.
@Service // 이 클래스가 서비스 계층을 맡는다고 알림.
@AllArgsConstructor // 모든 매개변수에 대한 생성자 생성.
public class BoardServiceImp implements BoardService {
	@Setter(onMethod_ = @Autowired)
	private BoardMapper mapper;
	
	@Setter(onMethod_ = @Autowired)
	private BoardAttachMapper attachMapper;
	
	@Transactional
	@Override
	public void register(BoardVO board) {
		log.info("register......" + board);
		mapper.insertSelectKey(board);
		// 게시물은 부모, 첨부파일은 자식의 개념
		
		if(board.getAttachList() == null ||
				board.getAttachList().size() <= 0) {
			// 첨부파일 객체가 널이거나 첨부파일 객체의 크기가 0 이라하면 메소드 중지.
			return;
		}
		
		board.getAttachList().forEach(attach->{
			attach.setBno(board.getBno()); // 게시물 번호를 할당하고
			attachMapper.insert(attach); // 첨부파일 정보 디비에 등록.
		});
	}

게시물 등록시 첨부파일이 등록되고, tbl_attach, tbl_board 에 등록 내용 확인.

 

 

BoardService에서 게시물 읽기시 첨부파일의 정보가 보이도록한다.

public List<BoardAttachVO> getAttachList(Long bno);
	// 게시물의 정보를 가지고 오면서, 첨부파일의 정보도 포함.
	// 게시물 읽기 시에 첨부파일 목록을 표시하여 다운로드 가능하도록 처리.

BoardServiceImp에 메소드를 구현한다.

	@Override
	public List<BoardAttachVO> getAttachList(Long bno) {
		log.info("get Attach list by bno : " + bno);
		return attachMapper.findByBno(bno);
		// 게시물 번호를 전달하고,
		// 게시물 번호와 일치하는 첨부파일을 모두 리턴.
	}

 

BoardController에 첨부파일 목록을 호출할 메소드를 추가한다.

// 글 내용을 읽으면서 ajax 호출로 정부파일 목록 표서드.
	@GetMapping(value = "/getAttachList", produces = MediaType.APPLICATION_JSON_VALUE)
	@ResponseBody
	public ResponseEntity<List<BoardAttachVO>> getAttachList(Long bno) {
		log.info("getAttachList: " + bno);
		
		return new ResponseEntity<>(service.getAttachList(bno), HttpStatus.OK);
	}

 

get.jsp에 화면과 스크립트를 구현한다.

<!-- 첨부파일 시작 -->
<br/>
<div class="row">
   <div class="col-lg-12">
      <div class="panel panel-default">
         <div class="panel-heading">첨부파일</div>
         <div class="panel-body">
            <div class="uploadResult">
               <ul></ul>
            </div>
         </div>
      </div>
   </div>
</div>
<!-- 첨부파일 끝 -->
      // 첨부파일 목록 표시.(익명 즉시 실행 함수)
      (function(){
         var bno='<c:out value="${board.bno}"/>';
         // 화면상에 공유된 bno 값 가져와 사용 준비.
         $.getJSON("/board/getAttachList"
               ,{bno:bno}, function(arr){
                  console.log(arr);
                  
                  var str="";
                  $(arr).each(function(i,attach){
                     str+="<li data-path='";
                     str+=attach.uploadPath+"' data-uuid='";
                     str+=attach.uuid+"' data-filename='";
                     str+=attach.fileName+"' data-type='";
                     str+=attach.fileType+"'><div>";
                     str+="<img src='/resources/img/dan.jpg' width='20' height='20'> ";
                     str+="<span>"+attach.fileName+"</span><br/> ";
                     
                     str+="</div></li>";
                  });
                  $(".uploadResult ul").html(str);
               });
      })();// 첨부파일 목록 표시.(익명 즉시 실행 함수) 끝

 

 

UploadController에 파일 다운로드 메소드를 구현한다.

import org.springframework.core.io.Resource;

// 파일 다운로드에 대한 메소드 처리 시작.
	// 처음에 작성하지만, 차후에는 복붙 / 일종의 상용구 형태.
	@GetMapping(value = "/download",
			produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
	@ResponseBody
	public ResponseEntity<Resource> downloadFile(
			@RequestHeader("User-Agent") String userAgent, String fileName){
		// 서버에 접속한 브라우저의 정보는
		// 헤더의 User-Agent를 보면 알 수 있음.
		Resource resource = new FileSystemResource("c:\\upload\\" + fileName);
		// 파일을 리소스(자원: 가공 처리를 위한 중간 단계]로 변경.
		log.info("resource: " + resource);
		if(resource.exists() == false) {
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		}
		String resourceName = resource.getFilename();
		// 리소스에서 파일명을 찾아서 할당.
		String resourceOriginalName
		= resourceName.substring(resourceName.indexOf("_") + 1);
		// uuid를 제외한 파일명 만들기.
		HttpHeaders headers = new HttpHeaders();
		// 웹 브라우저별 특성 처리
		try {
			String downloadName = null;
			if (userAgent.contains("Trident")) {
				log.info("IE browser");
				downloadName = URLEncoder.encode(resourceOriginalName
						, "UTF-8").replaceAll("\\", " ");
			} else if (userAgent.contains("Edge")) {
				log.info("Edge browser");
				downloadName = URLEncoder.encode(resourceOriginalName, "UTF-8");
			} else {
				log.info("chrome browser");
				downloadName = new String(resourceOriginalName.getBytes("UTF-8")
						, "ISO-8859-1");
			}
			log.info("downloadName: " + downloadName);
			headers.add("Content-disposition", "attachment; filename=" + downloadName);
			// 헤더에 파일 다운로드 정보 추가.
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return new ResponseEntity<Resource>(resource, headers, HttpStatus.OK);
	}


get.jsp에 다운로드 스크립트를 작성한다.

	// 첨부파일 다운로드 이벤트 시작
      $(".uploadResult").on("click", "li", function(e){
    	  console.log("download file");
    	  var liObj = $(this);
    	  var path = encodeURIComponent(liObj.data("path")
    			  +"/"+liObj.data("uuid")+"_"
    			  +liObj.data("filename"));
    	  self.location="/download?fileName="+path;
      });
      // 다운 끝

encodeURIComponent() 함수는 URI의 특정한 문자를 UTF-8로 인코딩해 하나, 둘, 셋, 혹은 네 개의 연속된 이스케이프 문자로 나타낸다. (두 개의 대리 문자로 이루어진 문자만 이스케이프 문자 네 개로 변환된다.)

 

 

게시물 삭제시 첨부파일도 함께 삭제 처리하도록 수정

BoardAttachMapper.xml에 deleteAll을 추가한다.

	 	<delete id="deleteAll">
	 		delete tbl_attach where bno=#{bno}
	 	</delete>

 

BoardServiceImp 수정

	@Transactional
	@Override
	public boolean remove(Long bno) {
		log.info("remove......" + bno);
		replyMapper.deleteAll(bno);
		attachMapper.deleteAll(bno); // 게시물 삭제시 해당 게시물의 첨부파일 모두 삭제.
		
		return (mapper.delete(bno)) ==1 ;
	}

만약 deleteAll 메서드가 안만들어져있다면 모두 만들어준다.

ReplyMapper 인터페이스에 deleteAll 메소드 선언부를 추가한다.

	public int deleteAll(Long bno);

ReplyMapper.xml 에 덧글 모두 삭제에 대한 쿼리문을 구현한다

<delete id="deleteAll">
  	delete from tbl_reply where bno=#{bno}
 </delete>

BoardServiceImpl에 replyMapper 인스턴스 만들어준다

	@Setter(onMethod_ = @Autowired)
	private ReplyMapper replyMapper; // 덧글

 

 

Criteria에 메소드 추가 (향후 작업을 쉽게 하도록 메소드 변형

import org.springframework.web.util.UriComponentsBuilder;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Criteria {
	// Criteria : 페이징 처리 기준을 갖는 밸류 오브젝트
	private int pageNum; // 현재 페이지 번호.
	private int amount; // 페이지당 게시물수
	private String keyword; // 검색 키워드
	private String type; // 검색 타입
	private String[] typeArr;

	public Criteria() {
		this(1, 10); // 아래쪽 전달값 2개 생성자 호출.
	}

	public Criteria(int pageNum, int amount) {
		this.pageNum = pageNum;
		this.amount = amount;
	}

	public void setType(String type) {
		this.type = type;
		this.typeArr = type.split("");
	}

	public String[] getTypeArr() {
		// 검색 타입 배열 가져오기.
		return type == null ? new String[] {} : type.split("");
		// 검색타입이 널이라면 비여있는 문자열 배열을 만들고,
		// 그렇지 않다면, 검색타입을 한글자씩 잘라서 문자열 배열로 만듦.
		// t(제목), w(작성자), c(내용)
	}

	public String getListLink() {
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("").queryParam("pageNum", this.pageNum)
				.queryParam("amount", this.getAmount()).queryParam("type", this.getType())
				.queryParam("keyword", this.getKeyword());
		return builder.toUriString();
		// 기존의 get 방식으로 전달하던 page,amount,type,keyword
		// 즉, 현재 페이지, 페이지당 게시물수, 검색타입, 검색어
		// 를 주소창에 get 방식으로 붙여서 보냈는데,
		// 일일이 값을 호출해서 처리하는 것이 아니라,
		// getListLink 메소드로 한꺼번에 처리하도록 변경.
	}
}

UriComponentsBuilder는 여러 개의 파라미터들을 연결하여 URL 형태로 만들어 주는 기능을 가지고 있다.

즉 Controller 단에서 addAttrivute로 하나 하나 속성을 지정해주지 않아도 이 class를 이용하면 손쉽고 간단하게

파라미터를 전달할 수 있다.

 

BoardController 수정

	
	@PostMapping("/modify")
	public String modify(BoardVO board, RedirectAttributes rttr,
			@ModelAttribute("cri") Criteria cri) {
		log.info("modify:" + board);
		if(service.modify(board)) {
			rttr.addFlashAttribute("result", "성공");
		}
		
//		rttr.addAttribute("pageNum",cri.getPageNum());
//		rttr.addAttribute("amount",cri.getAmount());
//		rttr.addAttribute("keyword",cri.getKeyword());
//		rttr.addAttribute("type",cri.getType());
		
		return "redirect:/board/list" + cri.getListLink();
	}
	
	@PostMapping("/remove")
	public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr,
			@ModelAttribute("cri") Criteria cri) {
		log.info("삭제" + bno);
		if (service.remove(bno)) {
			rttr.addFlashAttribute("result", "성공");
		}
		
//		rttr.addAttribute("pageNum",cri.getPageNum());
//		rttr.addAttribute("amount",cri.getAmount());
//		rttr.addAttribute("keyword",cri.getKeyword());
//		rttr.addAttribute("type",cri.getType());
		return "redirect:/board/list" + cri.getListLink();
	}

기존에 addAttribute로 일일이 속성을 지정해줘야했던것을 주석처리하고, 

return에 cri.getListLink() 메서드로 한번에 보내줄 수 있다.

또한 @ModelAttrivbute어노테이션을 이용하여 값을 알아서 "바인딩" OR "주입" 시켜준다.

 

 

BoardController 에서 게시물당 첨부된 파일을 찾아서 디스크에서 삭제해준다.

	// 첨부파일 삭제 처리 메소드.
	private void deleteFiles(List<BoardAttachVO> attachList) {
		if (attachList == null || attachList.size() == 0) {
			return;
		}
		log.info("delete attach file....");
		log.info(attachList);
		
		attachList.forEach(attach -> {
			try {
				Path file = Paths.get(
						"c:\\upload\\"+attach.getUploadPath()+"\\"
						+ attach.getUuid() + "_" + attach.getFileName());
				Files.deleteIfExists(file);
				// 해당 파일이 존재한다면 삭제 처리.
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}

 

BoardServiceImp 에 삭제 메서드를 추가해준다.

	@Transactional
	@Override
	public boolean remove(Long bno) {
		log.info("remove......" + bno);
		replyMapper.deleteAll(bno);
		attachMapper.deleteAll(bno); // 게시물 삭제시 해당 게시물의 첨부파일 모두 삭제.
		
		return (mapper.delete(bno)) ==1 ;
	}

게시믈 삭제시 화면에서 게시물이 사라지고, 첨부파일 저장 공간에 게시물과 관련된 파일이 삭제되고,

tbl_attach, tbl_reply에 방금 삭제한 데이터가 사라진 것을 확인할 수 있다.

'Spring 게시판 만들기' 카테고리의 다른 글

Spring - 18 (스프링 시큐리티)  (0) 2022.04.29
Spring - 17 (첨부파일-3)  (0) 2022.04.29
Spring - 15 (첨부파일)  (0) 2022.04.26
Spring - 14 (댓글 페이징)  (0) 2022.04.26
Spring - 13 (댓글 기능 - 페이징)  (0) 2022.04.25
Comments