반응형
기능
- 글 작성
- 전체 글 보기
- 글 보기
- 글 수정
- 글 삭제
- 글 수정 및 삭제 시에는 비밀번호가 일치해야 함
프로젝트 구조 및 설명
- domain : 비지니스 도메인 객체
- content 객체 생성
- repository : DB와 연결되는 부분
- DB를 따로 연결하지 않았기 때문에 Map을 사용해 데이터 저장
- 프로젝트 재실행 시 데이터 초기화
- service : 핵심 비지니스 로직
- controller : 웹 MVC에서의 컨트롤러 역할
- url mapping, view return, servcie와 연결
구현 코드
사용 라이브러리
- spring-boot-starter-web
- spring-boot-starter-thymeleaf
- spring-boot-devtools
- lombok
- spring-boot-starter-test
domain > Content
- Content 객체
- Content(게시글)에는 각각 id, title, writer, updateDate, texts, password가 필요
@Data
public class Content {
private int id;
private String title;
private String texts;
private String writer;
private String password;
private String updateDate;
}
repository > ContentRepository
- Repository는 보통 DB와 연결되는 부분인데 이 프로젝트에서는 DB를 사용하지 않음
- contents라는 Map을 생성 후 사용
- contents에 저장, 수정, 삭제, 조회하는 기능 담당 (CURD)
@Repository
public class ContentRepository {
private static Map<Integer, Content> contents = new HashMap<>();
private static int sequence = 0;
public void save(Content content) {
content.setId(++sequence);
contents.put(content.getId(), content);
}
public void edit(int id, Content content) {
contents.put(id, content);
}
public void delete(int id) {
contents.remove(id);
}
public List<Content> findAll() {
return new ArrayList<>(contents.values());
}
public Content findById(int id) {
return contents.get(id);
}
}
service > ContentService
- 핵심 비지니스 로직
- Controller, Repository와 연결되어 데이터 송/수신
@Service
@RequiredArgsConstructor
public class ContentService {
private final ContentRepository contentRepository;
/**
* 글 작성
*/
public void writeContent(Content content) {
contentRepository.save(content);
}
/**
* 글 수정
*/
public void editContent(int id, String texts, String password) {
Content content = contentRepository.findById(id);
if(!content.getPassword().equals(password)) {
return;
}
content.setTexts(texts);
LocalDateTime now = LocalDateTime.now();
String formattedDate = now.format( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") );
content.setUpdateDate(formattedDate);
contentRepository.edit(id, content);
}
/**
* 글 삭제
*/
public void deleteContent(int id, String password) {
Content content = contentRepository.findById(id);
if(!content.getPassword().equals(password)) {
return;
}
contentRepository.delete(id);
}
/**
* 전체 글 조회
*/
public List<Content> getAllContents() {
return contentRepository.findAll();
}
/**
* Id로 글 조회
*/
public Content getContent(int id) {
return contentRepository.findById(id);
}
}
controller > ContentController
- home 화면을 포함한 모든 화면들을 mapping 하는 controller
- View, Service와 연결되어 데이터 송/수신
@Controller
@RequiredArgsConstructor
public class ContentController {
private final ContentService contentService;
// 홈 화면
@GetMapping(value = {"", "/"})
public String home(Model model) {
model.addAttribute("contents", contentService.getAllContents());
return "home";
}
// 글 쓰기 화면
@GetMapping("/content/write")
public String writePage() {
return "write-page";
}
// 글 쓰기
@PostMapping("/content/write")
public String writeContent(Content content) {
LocalDateTime now = LocalDateTime.now();
String formattedDate = now.format( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") );
content.setUpdateDate( formattedDate );
contentService.writeContent(content);
return "redirect:/";
}
// 글 보기 화면
@GetMapping("/content/{id}")
public String showContent(@PathVariable int id, Model model) {
model.addAttribute("content", contentService.getContent(id));
return "content-page";
}
// 글 수정
@PostMapping("/content/{id}")
public String editContent(@PathVariable int id, Content content) {
contentService.editContent(id, content.getTexts(), content.getPassword());
return "redirect:/";
}
// 글 삭제
@PostMapping("/content/delete/{id}")
public String deleteContent(@PathVariable int id, Content content) {
contentService.deleteContent(id, content.getPassword());
return "redirect:/";
}
}
css > style.css
nav {
text-align: center;
font-size: x-large;
font-weight: bold;
background-color: orange;
padding-top: 25px;
height: 50px;
}
button {
height: 50px;
width: 100px;
font-size: large;
}
.form-group {
padding: 20px;
margin-bottom: 30px;
border-width: medium;
border-color: black;
border-style: solid;
width: 800px;
}
textarea {
width: 800px;
height: 300px;
}
html > home.html
- 홈 화면이자 글 전체 보기 화면
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/style.css">
<title>게시판</title>
<nav>홈 화면</nav>
</head>
<body>
<div>
<br/>
<div align="right" style="margin-right: 100px">
<button onclick="location.href='./content/write'">
글 작성
</button>
</div>
</div>
<br/>
<div>
<table border="1" width="1100" align="center">
<thead>
<tr>
<th width="50">#</th>
<th width="550">제목</th>
<th width="200">작성자</th>
<th width="200">최근 수정일</th>
<th width="200">글 보기/수정/삭제</th>
</tr>
</thead>
<tbody align="center">
<tr th:each="content: ${contents}">
<td th:text="${content.id}"></td>
<td th:text="${content.title}"></td>
<td th:text="${content.writer}"></td>
<td th:text="${content.updateDate}"></td>
<td> <button th:onclick="|location.href='@{/content/}'+${content.id}|" style="height:40px;">보기</button> </td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
html > write-page
- 글 쓰기 화면
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/style.css">
<title>게시판</title>
<nav>글 쓰기 화면</nav>
</head>
<body>
<div>
<br/>
<div align="center">
<form action="/content/write" method="post">
<div class="form-group" align="left">
<label for="title">글 제목 : </label>
<input type="text" id="title" name="title" placeholder="제목을 입력하세요" width="400px">
<br/><br/>
<label for="writer">작성자 : </label>
<input type="text" id="writer" name="writer" placeholder="작성자를 입력하세요" width="200px">
<br/><br/>
<label for="password">비밀번호 : </label>
<input type="password" id="password" name="password" width="200px">
<br/><br/>
<label for="texts">글 내용 : </label>
<br/><br/>
<textarea id="texts" name="texts"></textarea>
</div>
<button type="submit">제출</button>
</form>
</div>
</div>
</body>
</html>
html > content-page
- 글 보기 화면
- 글 내용 수정 기능
- 글 삭제 기능
- 수정, 삭제 시 비밀번호 확인 창에 입력한 비밀번호가 일치해야 작동
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/style.css">
<title>게시판</title>
<nav>글 보기 화면</nav>
</head>
<body>
<div>
<br/>
<div align="center">
<form th:action="@{/content/{id} (id=${content.id})}" th:method="post">
<div class="form-group" align="left">
<div th:text="'글 제목 : ' + ${content.title}"></div>
<br/>
<div th:text="'작성자 : ' + ${content.writer}"></div>
<br/>
<label for="password">비밀번호 확인 : </label>
<input type="password" id="password" name="password" width="200px">
<br/><br/>
<label for="texts">글 내용 : </label>
<br/><br/>
<textarea id="texts" name="texts">[[${content.texts}]]</textarea>
<br/>
</div>
<button type="submit" style="margin-right: 50px">수정</button>
<button th:formaction="@{/content/delete/{id} (id=${content.id})}"
type="submit">삭제</button>
</form>
</div>
</div>
</body>
</html>
결과
- 홈 화면
- 글 쓰기 화면
- 글 작성 시 홈 화면에 글이 추가된 것을 확인할 수 있음
- 글 보기 화면
- 비밀번호가 일치하면 수정, 삭제 기능 수행
반응형
'Spring Boot > 프로젝트' 카테고리의 다른 글
[Spring Boot] 게시판 만들기 5 - 댓글, 좋아요, 파일 업로드 관련 기능 (0) | 2023.04.17 |
---|---|
[Spring Boot] 게시판 만들기 4 - 게시판 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 3 - 유저 기능 (0) | 2023.04.17 |
[Spring Boot] 게시판 만들기 2 - 라이브러리 설치, ERD, Entity 생성 (1) | 2023.04.17 |
[Spring Boot] 게시판 만들기 1 - 설계 & 결과 (2) | 2023.04.16 |