Spring_boot/Project

[Spring] MVC를 이용해 블로그 만들기

달의요정루나 2022. 7. 31. 00:00

프로젝트 생성하기

프로젝트를 생성합니다.

dependencies를 설정합니다.

Spring Boot DevTools, Lombok, Thymeleaf, Spring Web이 선택됩니다.

프로젝트가 생성되면 pom.xml로 들어갑니다.
아래에 Dependencies로 들어갑니다.

Overview 옆에 있는 Dependencies로 들어갑니다.

Dependencies에서 Add...를 누른다.
새로운 Dependency를 추가한다.

javax.validation를 추가한다.

추가된 모습이다.

- src/main/java 코딩문

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 코딩문

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