프로젝트를 생성합니다.
Spring Boot DevTools, Lombok, Thymeleaf, Spring Web이 선택됩니다.
Overview 옆에 있는 Dependencies로 들어갑니다.
javax.validation를 추가한다.
- src/main/java 코딩문
1. com.aaa.blog.model
package com.aaa.blog.model;
import java.util.Date;
import javax.validation.constraints.NotNull;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Tag {
private Long id;
@NotNull(message="글을 입력하세요")
private String body;
@DateTimeFormat(pattern="yyyy-MM-dd hh:mm:ss")
private Date createdDate;
private Post post;
private User user;
}
Tag.java
package com.aaa.blog.model;
import java.util.Collection;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Post {
private Long id;
private String title;
private String body;
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private Date createdDate;
private User user;
private Collection<Tag> tags;
}
Post.java
package com.aaa.blog.model;
public enum UserRole {
ADMIN, USER;
}
UserRole.java
package com.aaa.blog.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String password;
private String email;
private String fullname;
private UserRole role;
}
User.java
2. com.aaa.blog.service
package com.aaa.blog.service;
import java.util.List;
import java.util.Optional;
import com.aaa.blog.model.Post;
public interface PostService {
List<Post> findAllOrderedById();
List<Post> findAllOrderedById(int page);
Optional<Post> findById(Long id);
Post create(Post post);
Post edit(Post post);
void deleteById(Long id);
}
PostService.java
package com.aaa.blog.service;
import java.util.List;
import com.aaa.blog.model.Tag;
public interface TagService {
Tag save(Tag tag);
List<Tag> getTags();
}
TagService.java
package com.aaa.blog.service;
import java.util.List;
import java.util.Optional;
import com.aaa.blog.model.User;
public interface UserService {
List<User> findAll();
Optional<User> findById(Long id);
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
User register(User user);
boolean authenticate(String username, String password);
}
UserService.java
package com.aaa.blog.service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.aaa.blog.model.Post;
@Service
public class PostServiceTestImpl implements PostService {
private TagService tagService;
private UserService userService;
private List<Post> posts = new ArrayList<Post>();
@Autowired
public PostServiceTestImpl(UserService userService, TagService tagService) {
this.userService=userService;
this.tagService=tagService;
posts.add(new Post(1L, "첫번째 포스트입니다.", "첫번째 포스트의 내용입니다.", new Date(),userService.findById(1L).get(),tagService.getTags()));
posts.add(new Post(2L, "두번째 포스트입니다.", "두번째 포스트의 내용입니다.", new Date(),userService.findById(1L).get(),tagService.getTags()));
posts.add(new Post(3L, "세번째 포스트입니다.", "세번째 포스트의 내용입니다.", new Date(),userService.findById(1L).get(),tagService.getTags()));
posts.add(new Post(4L, "네번째 포스트입니다.", "네번째 포스트의 내용입니다.", new Date(),userService.findById(2L).get(),tagService.getTags()));
posts.add(new Post(5L, "다섯번째 포스트입니다.", "다섯번째 포스트의 내용입니다.", new Date(),userService.findById(2L).get(),tagService.getTags()));
posts.add(new Post(6L, "여섯번째 포스트입니다.", "여섯번째 포스트의 내용입니다.", new Date(),userService.findById(3L).get(),tagService.getTags()));
posts.add(new Post(7L, "일곱번째 포스트입니다.", "일곱번째 포스트의 내용입니다.", new Date(),userService.findById(3L).get(),tagService.getTags()));
}
@Override
public List<Post> findAllOrderedById() {
// TODO Auto-generated method stub
return posts.stream().sorted(Comparator.comparing(Post::getId)).collect(Collectors.toList());
}
@Override
public List<Post> findAllOrderedById(int page) {
// TODO Auto-generated method stub
return posts.stream().sorted(Comparator.comparing(Post::getId)).limit(page).collect(Collectors.toList());
}
@Override
public Optional<Post> findById(Long id) {
// TODO Auto-generated method stub
return posts.stream().filter(p->p.getId().equals(id)).findAny();
}
@Override
public Post create(Post post) {
// TODO Auto-generated method stub
post.setId(this.posts.stream().count()+1);
post.setCreatedDate(new Date());
this.posts.add(post);
return post;
}
@Override
public Post edit(Post post) {
// TODO Auto-generated method stub
for (int i = 0; i < this.posts.size(); i++) {
if (this.posts.get(i).getId() == post.getId()) {
this.posts.set(i, post);
return post;
}
}
return null;
}
@Override
public void deleteById(Long id) {
// TODO Auto-generated method stub
for (int i = 0; i < this.posts.size(); i++) {
if (this.posts.get(i).getId() == id) {
this.posts.remove(i);
return;
}
}
}
}
PostServiceTestImpl.java
package com.aaa.blog.service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.aaa.blog.model.Tag;
@Service
public class TagServiceTestImpl implements TagService {
private UserService userService;
private List<Tag> tags = new ArrayList<Tag>();
@Autowired
public TagServiceTestImpl(UserService userService) {
this.userService=userService;
tags.add(new Tag(1L,"태그 1", new Date(), null, userService.findById(1L).get()));
tags.add(new Tag(2L,"태그 2", new Date(), null, userService.findById(2L).get()));
tags.add(new Tag(3L,"태그 3", new Date(), null, userService.findById(3L).get()));
}
@Override
public Tag save(Tag tag) {
// TODO Auto-generated method stub
tag.setCreatedDate(new Date());
this.tags.add(tag);
return tag;
}
@Override
public List<Tag> getTags() {
// TODO Auto-generated method stub
return this.tags;
}
}
TagServiceTestImpl.java
package com.aaa.blog.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.aaa.blog.model.User;
import com.aaa.blog.model.UserRole;
@Service
public class UserServiceImpl implements UserService {
private List<User> users = new ArrayList<>() {{
add(new User(1L, "aupres","password","aaa@bbb.com","홍길동",UserRole.ADMIN));
add(new User(2L, "ana","password","bbb@ccc.com","심청이",UserRole.USER));
add(new User(3L, "julian","password","ccc@sss.com","임꺽정",UserRole.USER));
}};
@Override
public List<User> findAll() {
// TODO Auto-generated method stub
return users.stream().collect(Collectors.toList());
}
@Override
public Optional<User> findById(Long id) {
// TODO Auto-generated method stub
return users.stream().filter(u->u.getId().equals(id)).findAny();
}
@Override
public Optional<User> findByUsername(String username) {
// TODO Auto-generated method stub
return users.stream().filter(u->u.getUsername().equals(username)).findAny();
}
@Override
public Optional<User> findByEmail(String email) {
// TODO Auto-generated method stub
return users.stream().filter(u->u.getEmail().equals(email)).findAny();
}
@Override
public User register(User user) {
// TODO Auto-generated method stub
user.setId(this.users.stream().count());
user.setRole(UserRole.USER);
users.add(user);
return user;
}
@Override
public boolean authenticate(String username, String password) {
// TODO Auto-generated method stub
Predicate<User> loginFilter = u->u.getUsername().equals(username) && u.getPassword().equals(password);
return users.stream().anyMatch(loginFilter);
}
}
UserServiceImpl.java
3. com.aaa.blog.form
package com.aaa.blog.form;
import javax.validation.constraints.NotNull;
import lombok.Data;
@Data
public class LoginForm {
@NotNull
private String username;
@NotNull
private String password;
}
LoginForm.java
package com.aaa.blog.form;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class PostForm {
@NotNull
@Size(min=2, max=30, message="2자 이상 30자 이하로 입력하여주십시요")
private String title;
@NotNull
@Size(min=5, max=500, message="5장 이상 500자 이하로 작성해 주세요")
private String body;
}
PostForm.java
package com.aaa.blog.form;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class RegisterForm {
@NotBlank(message="유효한 유저명을 입력하세요.")
@Size(min=2,max=30,message="아이디는 2자 이상 30자 이하입니다.")
private String username;
@NotBlank(message="유효한 암호를 입력하세요.")
@Size(min=5, message="최소 5글자이어야 합니다.")
private String password;
@Size(max=50, message="50자 이내로 작성하세요")
private String fullname;
@NotEmpty(message="유효한 이메일을 입력하세요")
private String email;
}
RegisterForm.java
4. com.aaa.blog.controller
package com.aaa.blog.controller;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.aaa.blog.form.PostForm;
import com.aaa.blog.model.Post;
import com.aaa.blog.model.User;
import com.aaa.blog.service.PostService;
import com.aaa.blog.service.TagService;
import com.aaa.blog.service.UserService;
@Controller
public class PostController {
@Autowired
private UserService userService;
@Autowired
private PostService postService;
@Autowired
private TagService tagService;
@RequestMapping(value="/posts/create", method=RequestMethod.GET)
public String newPost(PostForm postForm) {
return "/posts/create";
}
@RequestMapping(value="/posts/create", method=RequestMethod.POST)
public String newPost(@Valid PostForm postForm, BindingResult bindingResult, HttpSession session) {
if(bindingResult.hasErrors()) {
bindingResult.rejectValue("body", "create.body", "포스트 생성 시 오류가 발생했습니다.");
return "/posts/create";
}
Post post = new Post();
post.setId(postService.findAllOrderedById().stream().map(Post::getId).max(Comparator.naturalOrder()).orElse(Long.MIN_VALUE)+1);
post.setTitle(postForm.getTitle());
post.setBody(postForm.getBody());
post.setCreatedDate(new Date());
post.setUser(userService.findByUsername((String)session.getAttribute("username")).get());
Post p = postService.create(post);
if(p==null) {
return "/errors/default";
}
return "redirect:/posts";
}
@RequestMapping(value="/posts/view/{id}",method=RequestMethod.GET)
public String viewPostwithId(@PathVariable("id") Long id, Model model) {
Optional<Post> optionalPost = postService.findById(id);
if (optionalPost.isPresent()) {
model.addAttribute("post", optionalPost.get());
return "/posts/view";
}
return "/errors/404";
}
@RequestMapping(value="/posts/viewUser/{username}", method = RequestMethod.GET)
public String viewPostwithUsername(@PathVariable("username") String username, Model model) {
Optional<User> optionalUser = userService.findByUsername(username);
if(optionalUser.isPresent()) {
User user = optionalUser.get();
List<Post> posts = postService.findAllOrderedById();
model.addAttribute("posts", posts);
model.addAttribute("user", user);
return "/posts";
} else {
return "/errors/404";
}
}
@RequestMapping(value="/posts/edit/{id}", method = RequestMethod.GET)
public String editPost(@PathVariable("id") Long id, PostForm postForm) {
Optional<Post> optionalPost = postService.findById(id);
if(optionalPost.isPresent()) {
postForm.setTitle(optionalPost.get().getTitle());
postForm.setBody(optionalPost.get().getBody());
return "/posts/create";
}
return "/errors/404";
}
@RequestMapping(value="/posts/edit/{id}", method = RequestMethod.POST)
public String editPost(@PathVariable("id") Long id, @Valid PostForm postForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
bindingResult.rejectValue("body", "edit.body", "포스트 수정 시 오류발생.");
return "/posts/create";
}
Optional<Post> optionalPost = postService.findById(id);
if(optionalPost.isPresent()) {
Optional<User> optionalUser = userService.findById(optionalPost.get().getUser().getId());
if (optionalUser.isPresent()) {
Post post = optionalPost.get();
post.setTitle(postForm.getTitle());
post.setBody(postForm.getBody());
post.setCreatedDate(new Date());
post.setUser(optionalUser.get());
post.setTags(tagService.getTags());
Post p = postService.edit(post);
if (p==null) {
return "/posts/create";
}
}
return "redirect:/posts";
}
return "/errors/404";
}
@RequestMapping(value="/posts/delete/{id}", method=RequestMethod.GET)
public String delete(@PathVariable("id") Long id) {
Optional<Post> optionalPost = postService.findById(id);
if(optionalPost.isPresent()) {
postService.deleteById(id);
}
return "redirect:/posts";
}
public String index(Model model) {
List<Post> posts = postService.findAllOrderedById();
model.addAttribute("posts", posts);
return "/index";
}
}
PostController.java
package com.aaa.blog.controller;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.aaa.blog.form.LoginForm;
import com.aaa.blog.form.RegisterForm;
import com.aaa.blog.model.User;
import com.aaa.blog.model.UserRole;
import com.aaa.blog.service.UserService;
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String index(Model model) {
List<User> users = this.userService.findAll();
model.addAttribute("users", users);
return "/users/view";
}
@RequestMapping(value = "/users/login", method = RequestMethod.GET)
public String login(LoginForm loginForm) {
return "/users/login";
}
@RequestMapping(value = "/users/login", method = RequestMethod.POST)
public String login(@Valid LoginForm loginForm, BindingResult bindingResult, HttpSession session) {
if(!userService.authenticate(loginForm.getUsername(), loginForm.getPassword())) {
bindingResult.rejectValue("username", "login.username", "유저이름이나 암호가 틀립니다.");
return "/users/login";
}
session.setAttribute("username", loginForm.getUsername());
return "redirect:/index";
}
@RequestMapping(value="/users/register", method=RequestMethod.GET)
public String register(RegisterForm registerForm) {
return "/users/register";
}
@RequestMapping(value="/users/register", method=RequestMethod.POST)
public String register(@Valid RegisterForm registerForm, BindingResult bindingResult) {
Optional<User> optionalUser = this.userService.findByUsername(registerForm.getUsername());
if(optionalUser.isPresent()) {
bindingResult.rejectValue("username", "register.username", "User가 이미 존재.");
}
Optional<User> optionalEmail = this.userService.findByEmail(registerForm.getEmail());
if(optionalEmail.isPresent()) {
bindingResult.rejectValue("email", "register.email", "이메일이 이미 존재.");
}
if(!bindingResult.hasErrors()) {
User user = new User();
user.setUsername(registerForm.getUsername());
user.setPassword(registerForm.getPassword());
user.setEmail(registerForm.getEmail());
user.setFullname(registerForm.getFullname());
user.setRole(UserRole.USER);
this.userService.register(user);
return "redirect:/index";
}
return "/users/register";
}
@RequestMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/index";
}
}
UserController.java
package com.aaa.blog.controller;
import java.util.Comparator;
import java.util.Date;
import java.util.Optional;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.aaa.blog.model.Post;
import com.aaa.blog.model.Tag;
import com.aaa.blog.service.PostService;
import com.aaa.blog.service.TagService;
import com.aaa.blog.service.UserService;
@Controller
public class TagController {
@Autowired
private UserService userService;
@Autowired
private PostService postService;
@Autowired
private TagService tagService;
@RequestMapping(value="/tags/create", method=RequestMethod.POST)
public String newTag(@Valid Tag tag, BindingResult bindingResult, HttpSession session) {
if(bindingResult.hasErrors()) {
bindingResult.rejectValue("body", "tag.body", "댓글 생성시 오류 발생.");
return "/tags/create";
}
if(session.getAttribute("username")==null) {
bindingResult.rejectValue("body", "tag.username", "로그인 하세요.");
return "/error";
}
tag.setId(tagService.getTags().stream().map(Tag::getId).max(Comparator.naturalOrder()).orElse(Long.MIN_VALUE)+1);
tag.setUser(userService.findByUsername((String)session.getAttribute("username")).get());
tag.setCreatedDate(new Date());
tagService.save(tag);
return "redirect:/posts/view/"+tag.getPost().getId();
}
@RequestMapping(value="/tags/view/{id}", method = RequestMethod.GET)
public String postTagwithId(@PathVariable Long id, Model model) {
Optional<Post> optionalPost = postService.findById(id);
if(optionalPost.isPresent()) {
Tag tag = new Tag();
tag.setPost(optionalPost.get());
tag.setCreatedDate(new Date());
model.addAttribute("tag", tag);
return "/tags/create";
} else {
return "/error";
}
}
}
TagController.java
package com.aaa.blog.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.aaa.blog.model.Post;
import com.aaa.blog.service.PostService;
@Controller
public class BlogController {
@Autowired
private PostService postService;
@GetMapping(value= {"/", "/index"})
public String blog(@RequestParam(defaultValue = "20") int page, Model model) {
List<Post> posts = postService.findAllOrderedById(page);
model.addAttribute("posts", posts);
return "/index";
}
}
BlogController.java
package com.aaa.blog.controller;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class BlogErrorController implements ErrorController {
//@Override
public String getErrorPath() {
return "/errors";
}
@RequestMapping("/errors")
public String errorHandle(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode.equals(HttpStatus.BAD_REQUEST.value())) {
return "/errors/400";
} else if(statusCode.equals(HttpStatus.NOT_FOUND.value())) {
return "/errors/404";
} else if(statusCode.equals(HttpStatus.FORBIDDEN.value())) {
return "/errors/403";
} else if(statusCode.equals(HttpStatus.INTERNAL_SERVER_ERROR.value())) {
return "/errors/500";
}
}
return "errors/default";
}
}
BlogErrorController.java
- src/main/resources 코딩문
1. css
@charset "UTF-8";
/** 헤더 메뉴에 대한 css 값 */
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: black;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li div{
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
/** User 테이블에 대한에 대한 css 값 */
#user_table {
display: table;
width: 80%;
margin: auto;
}
.user_row {
display: table-row;
}
.user_cell {
display: table-cell;
padding: 3px;
border-bottom: 1px solid #DDD;
}
.user_col1 {
width: 20%;
}
.user_col2 {
width: 20%;
}
.user_col3 {
width: 40%;
}
.user_col4 {
width: 20%;
}
/** Post 테이블에 대한에 대한 css 값 */
#post_table {
display: table;
width: 80%;
margin: auto;
}
.post_row {
display: table-row;
}
.post_cell {
display: table-cell;
padding: 3px;
border-bottom: 1px solid #DDD;
}
.post_col1 {
width: 7%;
text-align: center;
}
.post_col2 {
width: 8%;
text-align: center;
}
.post_col3 {
width: 50%;
}
.post_col4 {
width: 10%;
text-align: center;
}
.post_col5 {
width: 5%;
text-align: center;
}
.post_col6 {
width: 10%;
text-align: center;
}
.disabled {
color: currentColor;
cursor: not-allowed;
opacity: 0.5;
text-decoration: none;
}
main.css
2. errors
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>Http 400 오류</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<div class="text-center">
<div><h2>HTTP 400 오류. 요청값이 정확하지 않습니다.</h2></div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
400.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>Http 403 오류</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<div class="text-center">
<div><h2> HTTP 403 오류. 본 페이지에 대한 권한이 없습니다.</h2></div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
403.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>Http 404 오류</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<div class="text-center">
<div><h2> HTTP 404 오류. 요청하신 페이지가 없습니다.</h2></div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
404.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>Http 500 오류</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<div class="text-center">
<div><h2> HTTP 500 오류. 시스템 내부 오류입니다.</h2></div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
500.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>Http 오류</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<div class="text-center">
<div><h2>시스템에 치명적인 오류가 발생 하였습니다. 잠시후 다시 연결해주십시요</h2></div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
default.html
3. posts
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>포스트 수정</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br>
<div class="container">
<h1>블로그 수정 화면입니다. 글을 수정주세요.</h1>
<form autocomplete="off" method="post" th:object="${postForm}">
<div class="form-group"><label for="title">제목</label></div>
<input id="title" type="text" name="title" class="form-control input-lg" placeholder="Title" th:field="*{title}" th:value="*{title}"/>
<div class="form-group"><label for="body" th:value="*{title}">내용</label></div>
<textarea name="body" class="form-control input-lg" rows="20" width="200" placeholder="Body" th:value="*{body}">[[*{body}]]</textarea>
<div class="form-group">
<input type="submit" value="확인" />
<a href="index.html" th:href="@{/posts}">취소</a>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('title')}">
<b th:text="${#fields.detailedErrors('title')}"></b>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('body')}">
<b th:text="${#fields.detailedErrors('body')}"></b>
</div>
</form>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
create.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>포스트 보기</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<form method="post" th:object="${postForm}">
<div class="jumbotron">
<h1 class="title" th:text="${post.title}">글 제목</h1>
<div class="date">
<i>표시</i>
<span th:text="${#dates.format(post.createdDate, 'dd-MMM-yyyy')}">02-May2019</span>
<span th:if="${post.user}" th:remove="tag">
<i>by</i>
<span th:text="${post.user.fullname != null ? post.user.fullname : post.user.username}">작성자</span>
</span>
</div>
<h3>Comments:</h3>
<div class="card card-body bg-light mb-2" th:each="tag : ${post.tags}">
<h3 th:text="${tag.body}">댓글</h3>
<div th:text="'Created: ' + ${#dates.format(tag.createdDate, 'dd-MMM-yyyy')} + ' by '">
생성자
</div>
<div><a th:text="${tag.user.username}">사용자</a>
</div>
</div>
<div class="row">
<a th:href="@{'/tags/view/{id}'(id=${post.id})}">
<button type="button" th:text="Tag">댓글</button>
</a>
</div>
</form>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
view.html
4. tags
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>댓글 입력</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<h1>댓글 입력 화면입니다. 댓글을 입력하세요.</h1>
<div class="container">
<form autocomplete="off" method="post" th:action="@{/tags/create}" th:object="${tag}">
<div class="form-group">
<textarea name="body" rows="20" width="200" class="form-control input-lg" placeholder="Body" th:value="*{body}">[[*{body}]]</textarea>
<input type="hidden" th:field="*{post}"/>
<input type="hidden" th:field="*{user}"/>
<div>
<input type="submit" value="수정" />
<a href="index.html" th:href="@{/posts}">취소</a>
</div>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('body')}">
<b th:text="${#fields.detailedErrors('body')}"></b>
</div>
</form>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
create.html
5. users
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>로그인 화면</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<h1>로그인 화면입니다.</h1>
<form method="post" th:object="${loginForm}">
<div><label for="username">아이디</label></div>
<input id="username" type="text" name="username" class="input-lg" th:value="*{username}"/>
<div><label for="password">비밀번호</label></div>
<input id="password" type="password" name="password" class="input-lg" th:value="*{password}" />
<div class="form-group">
<input type="submit" value="로그인" />
<a href="index.html" th:href="@{/posts}">취소</a>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('username')}">
<b th:text="${#fields.detailedErrors('username')}">
</div>
</form>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>사용자 등록</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div class="container">
<h1>등록 화면입니다.</h1>
<form method="post" th:object="${registerForm}">
<div><label for="username">아이디</label></div>
<input id="username" type="text" name="username" th:field="*{username}" th:value="*{username}" />
<div><label for="password" th:value="*{username}">비밀번호</label></div>
<input id="password" type="password" name="password" th:field="*{password}" th:value="*{password}" />
<div><label for="fullname" th:value="*{username}">이름</label></div>
<input id="fullname" type="text" name="fullname" th:field="*{fullname}" th:value="*{fullname}" />
<div><label for="email" th:value="*{username}">이메일</label></div>
<input id="email" type="text" name="email" th:field="*{email}" th:value="*{email}" />
<div class="form-group">
<input type="submit" value="등록" />
<a href="index.html" th:href="@{/}">취소</a>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('username')}">
<b th:text="${#fields.detailedErrors('username')}"></b>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('password')}">
<b th:text="${#fields.detailedErrors('password')}"></b>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('fullname')}">
<b th:text="${#fields.detailedErrors('fullname')}"></b>
</div>
<div class="form-group alert alert-danger" th:if="${#fields.hasErrors('email')}">
<b th:text="${#fields.detailedErrors('email')}"></b>
</div>
</form>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
register.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>사용자 보기</title>
<div th:replace="/layout :: header-css"/>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div id="user_table">
<div class="user_row">
<div class="user_cell user_col1">아이디</div>
<div class="user_cell user_col2">이메일</div>
<div class="user_cell user_col3">사용자 이름</div>
<div class="user_cell user_col4">권한</div>
</div>
<div class="user_row" th:each="user : ${users}">
<div class="user_cell user_col1" th:text="${user.username}">사용자 아이디</div>
<div class="user_cell user_col2" th:text="${user.email}">사용자 이메일</div>
<div class="user_cell user_col3" th:text="${user.fullname}">사용자 이름</div>
<div class="user_cell user_col4" th:text="${user.role}">사용자 권한</div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
view.html
6. index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<div th:replace="/layout :: header-css"/>
<title>블로그 전체글 내용보기</title>
</head>
<body>
<div th:replace="/layout :: site-header"></div><br><br>
<div id="post_table">
<div class="post_row">
<div class="post_cell post_col1">포스트 아이디</div>
<div class="post_cell post_col2">포스트 이름</div>
<div class="post_cell post_col3">포스트 내용</div>
<div class="post_cell post_col4">날짜</div>
<div class="post_cell post_col5">작성자</div>
<div class="post_cell post_col6">포스트 처리</div>
</div>
<div class="post_row" th:each="post : ${posts}">
<div class="post_cell post_col1" th:text="${post.id}">포스트 아이디</div>
<div class="post_cell post_col2" th:text="${post.title}">제목</div>
<div class="post_cell post_col3" th:text="${post.body}">내용</div>
<div class="post_cell post_col4" th:text="${post.createdDate}">작성날짜</div>
<div class="post_cell post_col5" th:text="${post.user.username}">작성자 아이디</div>
<div class="post_cell post_col6">
<div th:if="${session.username}">
<a href="posts/view.html" th:href="@{posts/view/__${post.id}__}">
<img src="../public/icon/view.png" th:src="@{/icon/view.png}" width="25px" height="25px"/></a>
<a href="posts/view.html" th:href="@{posts/edit/__${post.id}__}">
<img src="../public/icon/edit.png" th:src="@{/icon/edit.png}" width="25px" height="25px"/></a>
<a href="posts/view.html" th:href="@{posts/delete/__${post.id}__}">
<img src="../public/icon/delete.png" th:src="@{/icon/delete.png}" width="25ps" height="25px"/></a>
</div>
<div th:unless="${session.username}">
<a class="disabled" href="#">
<img src="../public/icon/view.png" th:src="@{/icon/view.png}" width="25px" height="25px"/></a>
<a class="disabled" href="#">
<img src="../public/icon/edit.png" th:src="@{/icon/edit.png}" width="25px" height="25px"/></a>
<a class="disabled" href="#">
<img src="../public/icon/delete.png" th:src="@{/icon/delete.png}" width="25ps" height="25px"/></a>
</div>
</div>
</div>
</div>
<div th:replace="/layout :: footer"></div>
</body>
</html>
7. layout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<!-- fragment 에서 사용할 jQuery 와 bootstrap, 그리고 사용자 정의된 css 를 불러옵니다. -->
<div th:fragment="header-css">
<meta charset="UTF-8">
<title>Aaa Blog System</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
<link href="https://maxcdn.bootstrapcdn.com/fontawesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 블로그에서 사용할 cascading style sheets 를 불러옵니다. -->
<link rel="stylesheet" href="../public/css/main.css" th:href="@{/css/main.css}" />
</div>
</head>
<body>
<!-- header 템플릿 입니다. -->
<div th:fragment="site-header">
<nav id="header">
<ul>
<li>
<a class="navbar-brand" th:href="@{/index}">
<img src="../public/icon/logo.png" th:src="@{/icon/logo.png}" width="180px" height="60px"/>
</a>
</li>
<!-- session 에 username 이 존재하면 하위링크를 표시합니다. -->
<div th:if="${session.username}">
<li><a th:href="@{/users}">사용자 보기</a></li>
<li><a th:href="@{/posts/create}">새로운 글</a></li>
<li><a th:href="@{/logout}">퇴장</a></li>
<li style="float:right">
<div th:text="${session.username}"></div>
</li>
</div>
<!-- session 에 username 이 존재하지 않으면 하위 링크를 표시합니다. -->
<div th:unless="${session.username}">
<li><a th:href="@{/users/register}">등록</a></li>
<li><a th:href="@{/users/login}">로그인</a></li>
<li style="float:right"><div>로그인안됨</div></li>
</div>
</ul>
</nav>
</div>
<!-- footer 템플릿 입니다. -->
<div th:fragment="footer">
<p class="text-center">© 2021 Joseph Hwang 블로그</p>
</div>
</body>
</html>
8. application.properties
server.port = 8080
server.error.whitelabel.enabled=false
server.error.path=/errors
결과물
코드문 출처: Joesph Hwang
https://github.com/Julian-Hwang/spring_boot_project/tree/main/Spring-Blog-Jpa
GitHub - Julian-Hwang/spring_boot_project
Contribute to Julian-Hwang/spring_boot_project development by creating an account on GitHub.
github.com
'Spring_boot > Project' 카테고리의 다른 글
[Spring] REST 웹 서비스 (0) | 2022.08.13 |
---|---|
[Spring] JPA로 블로그하고 데이터를 액세스 하기 (0) | 2022.08.07 |
[Spring] Spring MVC와 Web Security 통합 (0) | 2021.08.29 |
[Spring] 자바 환경 변수 설정하기 (0) | 2021.08.26 |
[Spring] 웹 보안 (0) | 2021.08.26 |