Boot_exam
75953 ワード
package com.example.controller;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import com.example.entity.Board;
import com.example.entity.BoardListProjection;
import com.example.repository.BoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping(value = "/board")
public class BoardController {
@Autowired
BoardRepository bRepository;
@Autowired
ResourceLoader resLoader;
// 수정 GET
@GetMapping(value = "/update")
public String updateGET(
Model model,
@RequestParam(name = "no") long no) {
Board board = bRepository.findById(no).orElse(null);
model.addAttribute("board", board);
return "board/update";
}
// 수정 POST
@PostMapping(value = "/updateaction")
public String updateActionPOST(@ModelAttribute Board board) {
System.out.println("=== board.getNo() === " + board.getNo());
Board oldBoard = bRepository.findById(board.getNo()).orElse(null);
board.setWriter(oldBoard.getWriter());
board.setHit(oldBoard.getHit());
board.setRegdate(oldBoard.getRegdate());
if (board.getImagesize() <= 0) {
board.setImage(oldBoard.getImage());
board.setImagename(oldBoard.getImagename());
board.setImagesize(oldBoard.getImagesize());
board.setImagetype(oldBoard.getImagetype());
}
Board retBoard = bRepository.save(board);
// System.out.println("=== retBoard === " + retBoard.toString());
return "redirect:/board/selectone?no=" + board.getNo();
}
// 글쓰기 GET
@GetMapping(value = "/insert")
public String insertGET(Model model) {
return "board/insert";
}
// 글쓰기 POST
@PostMapping(value = "/insertaction")
public String insertActionPOST(
Model model,
@ModelAttribute Board board,
@RequestParam(name = "timage") MultipartFile image) {
try {
System.out.println("=== image === " + image.getOriginalFilename());
// 4개항목 추가 (이미지)
if (image.getSize() > 0) {
System.out.println("=== 이미지 첨부 됨 ===");
board.setImage(image.getBytes());
board.setImagename(image.getOriginalFilename());
board.setImagesize(image.getSize());
board.setImagetype(image.getContentType());
} else {
System.out.println("=== 이미지 첨부 안됨 ===");
}
bRepository.save(board);
model.addAttribute("msg", "게시물을 등록했습니다");
model.addAttribute("url", "/board/selectlist");
return "layout/alert";
// return "redirect:/board/selectlist";
// return "redirect:/board/insert";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/board/insert";
}
}
// 전체목록 + 검색 + 페이지네이션
@GetMapping(value = "/selectlist")
public String selectListGET(
Model model,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "txt", defaultValue = "") String txt) {
try {
PageRequest pageable = PageRequest.of((page - 1), 15);
List<BoardListProjection> list = bRepository.findByTitleContainsOrderByRegdateDesc(txt, pageable);
// 페이지네이션을 위한 개수 가져오기 해보기
long total = bRepository.countByTitleContains(txt);
long pages = (total - 1) / 15 + 1;
model.addAttribute("pages", pages);
model.addAttribute("list", list);
return "board/selectlist";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/board/selectlist";
}
}
// 게시글 한개 조회
@GetMapping(value = "/selectone")
public String selectoneGET(
Model model,
HttpServletRequest request,
@RequestParam(name = "no") long no) {
try {
Board board = bRepository.findById(no).orElse(null);
// 이미지 url 정보 추가 후 전송
board.setImageUrl(request.getContextPath() + "/board/image?no=" + no);
model.addAttribute("board", board);
// 이전글
BoardListProjection boardPrev = bRepository.findFirstByNoLessThanOrderByNoDesc(no);
if (boardPrev != null) {
// long prev = boardPrev.getNo();
model.addAttribute("prev", boardPrev.getNo());
} else {
model.addAttribute("prev", 0L);
}
// 다음글
BoardListProjection boardNext = bRepository.findFirstByNoGreaterThanOrderByNoAsc(no);
if (boardNext != null) {
model.addAttribute("next", boardNext.getNo());
} else {
model.addAttribute("next", 0L);
}
// System.out.println("=== model === " + model);
return "board/selectone";
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 이미지조회
// http://127.0.0.1:9090/ROOT/board/image?no=7
@GetMapping(value = "/image")
public ResponseEntity<byte[]> imageGET(
@RequestParam(name = "no") long no) throws IOException {
Board board = bRepository.findById(no).orElse(null);
// 이미지명, 크기, 타입, 데이터
// System.out.println(board.getImagetype());
// System.out.println(board.getImagesize());
// System.out.println("board => "+board);
if (board.getImagesize() > 0) { // 첨부한 파일이 존재
// headers에 파일 타입 추가
HttpHeaders headers = new HttpHeaders();
if (board.getImagetype().equals("image/png")) {
headers.setContentType(MediaType.IMAGE_PNG);
} else if (board.getImagetype().equals("image/jpeg")) {
headers.setContentType(MediaType.IMAGE_JPEG);
} else if (board.getImagetype().equals("image/gif")) {
headers.setContentType(MediaType.IMAGE_GIF);
}
// 이미지 byte[], headers, Httpstatus.Ok
ResponseEntity<byte[]> response = new ResponseEntity<>(board.getImage(), headers, HttpStatus.OK);
return response;
} else {
InputStream is = resLoader.getResource("classpath:/static/img/default.jpg").getInputStream();
// System.out.println("InputStream is => "+is);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
ResponseEntity<byte[]> response = new ResponseEntity<>(is.readAllBytes(), headers, HttpStatus.OK);
return response;
}
}
}
package com.example.controller;
import java.util.HashMap;
import java.util.Map;
import com.example.entity.Board;
import com.example.repository.BoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/restboard")
public class BoardRestController {
@Autowired
BoardRepository bRepository;
// 삭제
@RequestMapping(value = "/delete", method = { RequestMethod.GET }, consumes = {
MediaType.ALL_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE })
public Map<String, Object> boardDeleteGET(@RequestParam(name = "no") long no) {
Map<String, Object> map = new HashMap<>();
try {
bRepository.deleteById(no);
map.put("status", 200);
// map.put("board", board);
} catch (Exception e) {
e.printStackTrace();
map.put("status", 0);
}
return map;
}
// 조회수 1증가 rest로 만들고, 호출함
// 127.0.0.1:9090/ROOT/restboard/updatehit?no=6
@RequestMapping(value = "/updatehit", method = { RequestMethod.PUT }, consumes = {
MediaType.ALL_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE })
public Map<String, Object> boardUpdateHit1PUT(@RequestParam(name = "no") long no) {
Map<String, Object> map = new HashMap<>();
try {
Board board = bRepository.findById(no).orElse(null);
board.setHit(board.getHit() + 2L);
bRepository.save(board);
map.put("status", 200);
// map.put("board", board);
} catch (Exception e) {
map.put("status", 0);
}
return map;
}
}
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
// 홈화면
@GetMapping(value = { "/", "/home" })
public String homeGET() {
return "home";
}
}
package com.example.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
@Entity
@Data
@Table(name = "BOARD")
@SequenceGenerator(name = "SEQ_BOARD", sequenceName = "SEQ_BOARD_NO", allocationSize = 1, initialValue = 1)
public class Board {
// 글번호 no, 글제목 title, 내용 content, 작성자 writer, 이미지 image, 조회수 hit, 등록일 regdate
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_BOARD") // 시퀀스 적용
private Long no;
@Column(length = 200)
private String title;
@Lob
private String content;
@Column(length = 50)
private String writer;
@Lob
private byte[] image;
@Column(length = 200)
private String imagename;
private long imagesize;
@Column(length = 50)
private String imagetype;
private long hit = 10L;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
@CreationTimestamp // CURRENT_DATE
private Date regdate;
// DB의 테이블에 생성되지 않고, 매핑도 안됨, 임시
@Transient
private String imageUrl;
}
package com.example.entity;
import java.util.Date;
public interface BoardListProjection {
// 글번호
Long getNo();
// 제목
String getTitle();
// 작성자
String getWriter();
// 조회수
long getHit();
// 등록일
Date getRegdate();
}
package com.example.repository;
import java.util.List;
import com.example.entity.Board;
import com.example.entity.BoardListProjection;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {
// 제목검색어 + 최신글부터 내림차순
List<BoardListProjection> findByTitleContainsOrderByRegdateDesc(String title, Pageable pageable);
// 페이지네이셔내을 위한 게시글 개수 (검색어 포함)
long countByTitleContains(String title);
// projection 목록 조회
// Witer 검색
List<BoardListProjection> findByWriterContainsOrderByNoDesc(String writer, Pageable pageable);
// 페이지네이셔내을 위한 게시글 개수 (검색어 포함)
long countByWriterContainsOrderByNoDesc(String writer);
// 이전글
// 작은것 중에서 가장 큰것
BoardListProjection findFirstByNoLessThanOrderByNoDesc(long no);
// 다음글
// 현재 글 번호보다 큰것을 오름차순정렬해서 가장 처음 것
// ex) 3번보다 큰것 => 4, 5, 7, 8, 9, 10(오름차순 정렬) => 4(제일 처음것)
BoardListProjection findFirstByNoGreaterThanOrderByNoAsc(long no);
}
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시판글쓰기</title>
</head>
<body>
<div style="padding:20px">
<h3>글쓰기</h3>
<hr />
<form th:action="@{/board/insertaction}" method="post"
enctype="multipart/form-data">
제목 : <input type="text" name="title" /><br />
내용 : <textarea rows="6" name="content"></textarea><br />
작성자 : <input type="text" name="writer" /><br />
이미지 : <input type="file" name="timage" /><br />
<input type="submit" value="글쓰기" />
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시판목록</title>
</head>
<body>
<div style="padding:20px">
<h3>게시판목록</h3>
<hr />
<form th:action="@{/board/selectlist}" method="get">
검색 : <input type="text" name="txt" />
<input type="submit" value="검색" />
</form>
<hr />
<a th:href="@{/board/insert}">글쓰기</a>
<table border="1">
<tr>
<th>글번호</th>
<th>글제목</th>
<th>작성자</th>
<th>조회수</th>
<th>등록일</th>
</tr>
<tr th:each="tmp : ${list}">
<td th:text="${tmp.no}"></td>
<td>
<a href="#"
th:onclick="|javascript:updateHit('${tmp.no}')|"
th:text="${tmp.title}"></a>
</td>
<td th:text="${tmp.writer}"></td>
<td th:text="${tmp.hit}"></td>
<td th:text="${tmp.regdate}"></td>
</tr>
</table>
<th:block th:each="i : ${#numbers.sequence(1, pages)}">
<a th:href="@{/board/selectlist(page=${i}, txt=${param.txt})}"
th:text="${i}"></a>
</th:block>
</div>
<script>
function updateHit(no){
const xhr = new XMLHttpRequest(); // ex) axios와 같은 것
const url = '/ROOT/restboard/updatehit?no=' + no;
xhr.open("PUT", url, true);
xhr.responseType="json";
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8;');
xhr.onload = function(e) {
console.log(e.target);
if(e.target.response.status === 200) {
// 2. 다음 페이지 이동
location.href="/ROOT/board/selectone?no=" + no;
}
}
xhr.send();
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>게시판내용</title>
</head>
<body>
<div style="padding:20px">
<h3>게시판내용</h3>
<hr />
<p th:text="${board.no}" />
<p th:text="${board.title}" />
<img th:src="${board.imageurl}" style="width:50px" />
<hr />
<a th:href="@{/board/update(no=${board.no})}">
<button>수정</button>
</a>
<button th:onclick="|javascript:handleDelete('${board.no}')|">삭제</button>
<a th:if="${prev}" th:href="@{/board/selectone(no=${prev})}">
<button>이전글</button>
</a>
<a th:if="${next}" th:href="@{/board/selectone(no=${next})}">
<button>다음글</button>
</a>
<a th:href="@{/board/selectlist}"><button>목록</button></a>
</div>
</body>
</html>
<script>
function handleDelete(no) {
if(confirm('삭제할까요?')){
console.log("=== no === "+ no);
const xhr = new XMLHttpRequest();
console.log("=== xhr === " + xhr);
const url = "/ROOT/restboard/delete?no="+no;
console.log("=== url === " + url);
xhr.open("GET", url, true);
xhr.responseType = "json";
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8;');
xhr.onload = function(e) {
console.log("=== e.target === " , e.target);
if(e.target.response.status === 200){
// 2. 해당 페이지로 이동
location.href = "/ROOT/board/selectlist";
}
}
// 호출해야 onload가 반응
xhr.send();
// GET으로 삭제처리 주소창을 바꿈
// location.href="/ROOT/board/delete?no="+no;
}
}
</script>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>board/update</title>
</head>
<body>
<div style="padding: 20px">
<h3>글수정</h3>
<hr />
<!-- '@'의 역할 => context-path를 자동으로 잡아줌 -->
<form th:action="@{/board/updateaction}" method="post" enctype="multipart/form-data" >
글번호 : <input type="text" name="no" th:value="${board.no}" hidden /><br />
글제목 : <input type="text" name="title" th:value="${board.title}" /><br />
글내용: <textarea rows="6" cols="" name="content" th:text="${board.content}"></textarea><br />
<input type="submit" value="글수정" />
</form>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script th:inline="javascript">
/*<![CDATA[*/
const msg = [[${msg}]];
alert(msg); //알림표시
window.location.replace( [[${#httpServletRequest.getContextPath()}]] + "" + [[${url}]] ); //이동하는 페이지
/*]]*/
</script>
</head>
</html>
Reference
この問題について(Boot_exam), 我々は、より多くの情報をここで見つけました https://velog.io/@gegus1220/Bootexamテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol