게시글 등록
write.jsp
<body>
<h2>게시판 작성</h2>
<div id='board-write-container'>
<form action="${ path }/board/write" method="POST" enctype="multipart/form-data">
<table id='tbl-board'>
<tr>
<th>제목</th>
<td><input type="text" name="title" id="title"></td>
</tr>
<tr>
<th>작성자</th>
<td><input type="text" name="writerId" value="${ board.writerId }" readonly></td>
</tr>
<tr>
<th>첨부파일</th>
<td><input type="file" name="upfile"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" cols="50" rows="15" ></textarea></td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="등록">
<input type="reset" value="취소">
</th>
</tr>
</table>
</form>
</div>
</body>
servlet-context.xml
<exclude-mapping path="/board/write"/>
- 로그인 하지 않은 사람은 url로접근하지 못하도록 인터셉터에 매핑한다.
BoardController
@GetMapping("/write")
public String write() {
log.info("게시글 작성 페이지 요청");
return "board/write";
}
- jsp 페이지가 연결되도록 Controller 에서 get 요청을 한다.
- 클래스 상단에 @RequestMapping("/board") 설정하면 mapping 할 때 자동으로 앞에 "/board"를 표시해준다.
- 추가로, post 요청을 처리하는 메소드를 만든다. (GetMapping만 하면 405 에러 발생)
pom.xml
<!-- 파일 업로드 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
- 파일 업로드를 위한 라이브러리 등록
- maven-repository > 검색 키워드 : commons-io / commons-fileupload
multipart-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
p:maxUploadSize="10485760"
/>
</beans>
- 파일 업로드 시 사용할 MultipartResolver 빈 등록 → Spring Bean Definition file 등록
- p:maxUploadSize: 최대 업로드 파일 크기를 지정한다. (10MB)
- 해당 xml 사용하기 위해 root-context에 import 한다.
MultipartFileUtil
@Slf4j
public class MultipartFileUtil {
public static String save(MultipartFile upfile, String location) {
String renamedFileName = null;
String originalFileName = upfile.getOriginalFilename();
log.info("Upfile Name : {}", originalFileName);
log.info("location : {}", location);
// location이 실제로 존재하지 않으면 폴더를 생성하는 로직
File folder = new File(location);
if(!folder.exists()) {
folder.mkdirs();
}
renamedFileName =
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssSSS")) +
originalFileName.substring(originalFileName.lastIndexOf("."));
try {
// 업로드한 파일 데이터를 지정한 파일에 저장한다.
upfile.transferTo(new File(location + "/" + renamedFileName));
} catch (IllegalStateException | IOException e) {
log.error("파일 전송 에러");
e.printStackTrace();
}
return renamedFileName;
}
public static void delete(String location) {
log.info("location : {}", location);
File file = new File(location);
if(file.exists()) {
file.delete();
}
}
}
BoardController
@PostMapping("/write")
public ModelAndView write(
ModelAndView model,
@ModelAttribute Board board,
@RequestParam("upfile") MultipartFile upfile,
@SessionAttribute("loginMember") Member loginMember){
int result = 0;
log.info("Upfile is Empty : {}", upfile.isEmpty());
log.info("Upfile Name : {}", upfile.getOriginalFilename());
// 1. 파일을 업로드 했는지 확인 후 파일을 저장
if(upfile != null && !upfile.isEmpty()) {
// 파일을 저장하는 로직 작성
String location = null;
String renamedFileName = null;
try {
location = resourceLoader.getResource("resources/upload/board").getFile().getPath();
renamedFileName = MultipartFileUtil.save(upfile, location);
} catch (IOException e) {
e.printStackTrace();
}
if(renamedFileName != null) {
board.setOriginalFileName(upfile.getOriginalFilename());
board.setRenamedFileName(renamedFileName);
}
}
// 2. 작성한 게시글 데이터를 데이터 베이스에 저장
board.setWriterNo(loginMember.getNo());
result = service.save(board);
if(result > 0) {
model.addObject("msg", "게시글 등록 성공");
model.addObject("location", "/board/view?no=" + board.getNo());
} else {
model.addObject("msg", "게시글 등록 실패");
model.addObject("location", "/board/write");
}
model.setViewName("common/msg");
return model;
}
- ModelAndView : jsp에서 넘어오는 값과 View Page를 같이 저장한다.
- @RequestParam : 사용자가 요청시 전달하는 값을 Handler(Controller)의 매개변수로 1:1 맵핑할 때 사용되며, upfile 정보를 가져온다.
- @ModelAttribute : 사용자가 요청시 전달하는 값을 오브젝트 형태로 매핑해준다.
- @SessionAttribute : 컨트롤러 밖에서 만들어 준 세션 데이터에 접근할 때 사용하며, 로그인 세션에 저장된 "loginMember"을 가져와 매개변수 loginMember에 담아준다.
log.info("Upfile is Empty : {}", upfile.isEmpty());
- 파일을 업로드하지 않으면 true, 파일을 업로드하면 false
log.info("Upfile Name : {}", upfile.getOriginalFilename());
- 파일을 업로드하지 않으면 빈문자열, 파일을 업로드하면 "파일명"
- ResourceLoader를 사용하기 위해 추가한다.
- ResourceLoader : 리소스를 읽어오는 기능을 제공하는 인터페이스다.
1. 파일을 업로드 했는지 확인 후 파일을 저장
- getResource: Resource 객체를 반환한다.
- MultipartFileUtil의 save 메서드를 통해 매개변수 upfile과 location을 넘겨주고 리턴값을 renamedFileName 에 저장한다.
- 파일명을 재설정하여 renamedFileName 매개 변수에 저장하고 이를 retrun한다.
2. 작성한 게시글 데이터를 데이터 베이스에 저장
setViewName()
- 뷰의 이름 설정
addObject()
- 데이터 전달
BoardService
public interface BoardService {
int save(Board board);
}
BoardServiceImpl
@Service
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardMapper mapper;
@Override
@Transactional
public int save(Board board) {
int result = 0;
if(board.getNo() != 0) {
// update
} else {
// insert
result = mapper.insertBoard(board);
}
return result;
}
}
board-mapper.xml
<insert id="insertBoard" parameterType="Board"
useGeneratedKeys="true" keyProperty="no" keyColumn="NO">
INSERT INTO BOARD (
NO,
WRITER_NO,
TITLE,
CONTENT,
ORIGINAL_FILENAME,
RENAMED_FILENAME,
READCOUNT,
STATUS,
CREATE_DATE,
MODIFY_DATE,
TYPE
)
VALUES(
SEQ_BOARD_NO.NEXTVAL,
#{writerNo},
#{title},
#{content},
#{originalFileName},
#{renamedFileName},
DEFAULT,
DEFAULT,
DEFAULT,
DEFAULT,
DEFAULT
)
</insert>
BoardMapper
@Mapper
public interface BoardMapper {
int insertBoard(Board board);
}
게시글에 등록된 파일 다운로드
view.jsp
<body>
<tr>
<th>첨부파일</th>
<td>
<c:if test="${ empty board.originalFileName }">
<span>-</span>
</c:if>
<c:if test="${ not empty board.originalFileName }">
<img src="${ path }/resources/images/file.png" width="20px" height="20px">
<a href="javascript:" id="fileDown">
<span>${ board.originalFileName }</span>
</a>
<%--
<br><a href="${ path }/resources/upload/board/${board.renamedFileName}"
download="${ board.originalFileName }">파일 다운</a>
--%>
</c:if>
</td>
</tr>
<script>
$(document).ready(() => {
$("#fileDown").on("click", () => {
location.assign("${ path }/board/fileDown?oname=${ board.originalFileName }&rname=${ board.renamedFileName }");
});
});
</script>
</body>
- view.jsp 일부분
BoardController
@GetMapping("/fileDown")
public ResponseEntity<Resource> fileDown(
@RequestHeader("user-agent") String userAgent,
@RequestParam String oname, @RequestParam String rname) {
Resource resource = null;
String downFileName = null;
log.info("oname : {}, rname : {}", oname, rname);
log.info("{}", userAgent);
try {
// 클라이언트로 전송할 파일의 경로와 파일명을 가져온다.
resource = resourceLoader.getResource("resources/upload/board/" + rname);
// 브라우저별 인코딩 처리
if(userAgent.indexOf("MSIE") != -1 || userAgent.indexOf("Trident") != -1) {
downFileName = URLEncoder.encode(oname, "UTF-8").replaceAll("\\+", "%20");
} else {
downFileName = new String(oname.getBytes("UTF-8"), "ISO-8859-1");
}
// 응답 메시지 작성 (html X, file O)
return ResponseEntity.ok()
// application/octet-stream 모든 종류의 2진 데이터를 뜻한다.
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
// 파일 링크를 클릭했을 때 다운로드 화면이 출력되게 처리하는 부분
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + downFileName)
.body(resource);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
게시글 수정
view.jsp
<%--글작성자/관리자인경우 수정삭제 가능 --%>
<tr>
<th colspan="2">
<c:if test="${ not empty loginMember && loginMember.id == board.writerId }">
<button type="button"
onclick="location.href='${ path }/board/update?no=${ board.no }'">수정</button>
<button type="button" id="btnDelete">삭제</button>
</c:if>
<button type="button" onclick="location.href='${ path }/board/list'">목록으로</button>
</th>
</tr>
- view.jsp 일부분
BoardController
@GetMapping("/update")
public ModelAndView update(
ModelAndView model,
@RequestParam int no,
@SessionAttribute("loginMember") Member loginMember) {
Board board = null;
board = service.findBoardByNo(no);
if(board.getWriterId().equals(loginMember.getId())) {
model.addObject("board", board);
model.setViewName("board/update");
} else {
model.addObject("msg", "잘못된 접근입니다.");
model.addObject("location", "/board/list");
model.setViewName("common/msg");
}
return model;
}
- update.jsp 로 연결되는 controller 설정한다.
@RequestMapping(value = "/update", method = { RequestMethod.POST })
public ModelAndView update(
ModelAndView model,
@ModelAttribute Board board,
@RequestParam("upfile") MultipartFile upfile,
@SessionAttribute("loginMember") Member loginMember) {
int result = 0;
String location = null;
String renamedFileName = null;
if (service.findBoardByNo(board.getNo()).getWriterId().equals(loginMember.getId())) {
if (upfile != null && !upfile.isEmpty()) {
try {
location = resourceLoader.getResource("resources/upload/board").getFile().getPath();
// 기존에 업로드 된 파일이 있음
if (board.getRenamedFileName() != null) {
// 이전에 업로드 된 첨부 파일이 존재하면 삭제
MultipartFileUtil.delete(location + "/" + board.getRenamedFileName());
}
renamedFileName = MultipartFileUtil.save(upfile, location);
if (renamedFileName != null) {
board.setOriginalFileName(upfile.getOriginalFilename());
board.setRenamedFileName(renamedFileName);
}
} catch (IOException e) {
e.printStackTrace();
}
}
result = service.save(board);
if (result > 0) {
model.addObject("msg", "수정 완");
model.addObject("location", "/board/view?no=" + board.getNo());
} else {
model.addObject("msg", "다시 수정하쇼");
model.addObject("location", "/board/update?no=" + board.getNo());
}
} else {
model.addObject("msg", "잘못 접근");
model.addObject("location", "/board/list");
}
model.setViewName("common/msg");
return model;
}
BoardService
public interface BoardService {
int save(Board board);
}
BoardServiceImpl
@Override
@Transactional
public int save(Board board) {
int result = 0;
if(board.getNo() != 0) {
// update
result = mapper.updateBoard(board);
} else {
// insert
result = mapper.insertBoard(board);
}
return result;
}
board-mapper.xml
<update id="updateBoard" parameterType="Board">
UPDATE BOARD
<trim prefix="SET" suffixOverrides=",">
<if test="title != null">
TITLE = #{title},
</if>
<if test="content != null">
CONTENT = #{content},
</if>
<if test="originalFileName != null">
ORIGINAL_FILENAME = #{originalFileName},
</if>
<if test="renamedFileName != null">
RENAMED_FILENAME = #{renamedFileName},
</if>
MODIFY_DATE = SYSDATE
</trim>
WHERE NO = #{no}
</update>
BoardMapper
@Mapper
public interface BoardMapper {
int updateBoard(Board board);
int updateStatus(@Param("no") int no, @Param("status") String status);
}
'Spring > Spring MVC2' 카테고리의 다른 글
Board - 전체 게시글 / 상세 페이지 / 게시글 삭제 (1) | 2022.10.23 |
---|---|
기본 설정 ③ servlet-context.xml (0) | 2022.09.02 |
기본 설정 ② root-context.xml (0) | 2022.09.02 |
기본 설정 ① web.xml (0) | 2022.09.02 |