1. 카테고리 등록
카테고리 등록 버튼을 누르면 어디로 페이지를 넘겨줘야 할까?
카테고리를 여러개 만들 수도 있으니까
/s/category/writeForm으로 리다이렉트 해주면 되겠다.
근데 그냥 페이지를 넘겨주기만 하면 카테고리가 잘 등록되었는지 알 수 없으니까
자바스크립트를 리턴해주면 좋겠다.
@PostMapping("/s/category")
public @ResponseBody String write() {
return Script.href("/s/category/writeForm", "카테고리 등록 완료");
}
자바스크립트 locatino.href 로 페이지를 이동시켜주거나
history.back 같이 뒤로 가기 해주는 코드는 자주 사용되므로
util 폴더에 메서드로 만들어주자.
파일만 돌려주면 될때는 그냥 리턴해주고,
메시지를 띄우면서 돌려주고 싶을 때는 스크립트를 리턴해주자!
package site.metacoding.blogv3.util;
public class Script {
public static String href(String url, String msg) {
StringBuffer sb = new StringBuffer();
sb.append("<script>");
sb.append("alert('" + msg + "');");
sb.append("location.href = '" + url + "';");
sb.append("</script>");
return sb.toString();
}
public static String back(String msg) {
StringBuffer sb = new StringBuffer();
sb.append("<script>");
sb.append("alert('" + msg + "');");
sb.append("history.back();");
sb.append("</script>");
return sb.toString();
}
}
2. 세션 불러오기
카테고리의 title만 받으면되지만
컨트롤러에서 null 체크, 공백 체크 등등
체크해야 할 게 많아지니까 dto 만들어주자.
dto에서 toEntity를 완성하기 위해서는 유저를 주입해줘야 하니까
컨트롤러에서 세션을 불러와 넣어줄 것이다.
세션을 불러오는데 3가지 방법이 있다.
package site.metacoding.blogv3.web;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import lombok.RequiredArgsConstructor;
import site.metacoding.blogv3.config.auth.LoginUser;
import site.metacoding.blogv3.domain.category.Category;
import site.metacoding.blogv3.domain.user.User;
import site.metacoding.blogv3.service.CategoryService;
import site.metacoding.blogv3.util.Script;
import site.metacoding.blogv3.util.UtilValid;
import site.metacoding.blogv3.web.dto.category.CategoryWriteReqDto;
@RequiredArgsConstructor
@Controller
public class CategoryController {
private final CategoryService categoryService;
private final HttpSession session;
@GetMapping("/s/category/writeForm")
public String writeForm() {
return "/category/writeForm";
}
@PostMapping("/s/category")
public @ResponseBody String write(
@Valid CategoryWriteReqDto categoryWriteReqDto,
BindingResult bindingResult,
@AuthenticationPrincipal LoginUser loginUser) {
UtilValid.요청에러처리(bindingResult); // 유효성 검사 끝!!
// 1. HttpSession에서 get
// User principal = (User) session.getAttribute("principal"); 머스태치에서 쓰려고 담은 세션
// 2. Authentication.getPrincipal(); 시큐리티에서 가져온 것
User principal = loginUser.getUser();
// 3. getPrincipal() 캐스팅한 후 오브젝트 꺼내주기
// LoginUser loginU = (LoginUser)
// SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// User principal = loginU.getUser();
// 서비스에 기능 호출 (Category 모델에 세션(유저) 담아서!!)
Category category = categoryWriteReqDto.toEntity(principal);
categoryService.카테고리등록(category);
return Script.href("/s/category/writeForm", "카테고리 등록 완료");
}
}
① 세션에 저장해놓은 principal 찾기
// 1. HttpSession에서 get
private final HttpSession session; // DI
...
User principal = (User) session.getAttribute("principal");
② AuthenticationPrincipal이 제공해주는 getUser( ) 찾기
getUser( )를 찾기 위해서는 @AuthenticationPrincipal 어노테이션을 달아서 LoginUser를 찾을 수 있다.
// 2. Authentication.getPrincipal(); 시큐리티에서 가져온 것
User principal = loginUser.getUser();
③ SecurityContextHolder에서 타고 타고 들어가서 찾기
// 3. getPrincipal() 캐스팅한 후 오브젝트 꺼내주기
LoginUser loginU = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User principal = loginU.getUser();
AuthenticationPrincipal 쓰면 HttpSession DI 할 필요도 없이 세션에 접근이 가능하다.
제일 편한 이 방법 사용할 것이다.
CategoryWriteDto
package site.metacoding.blogv3.web.dto.category;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import site.metacoding.blogv3.domain.category.Category;
import site.metacoding.blogv3.domain.user.User;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CategoryWriteReqDto {
@Size(min = 1, max = 60)
@NotBlank
private String title;
public Category toEntity(User principal) {
Category category = new Category();
category.setTitle(title);
category.setUser(principal);
return category;
}
}
3. 카테고리와 게시글 담아가기
로그인 상관없이 내 블로그로 들어오면 혹은 다른 사람 블로그로 들어가면
localhost:8080/user/{id}/post 주소로 접근하게 되는데
해당 유저(블로그 주인)의 블로그를 갈 때
그 사람의 카테고리와 게시글 목록도 가져와야 한다.
이때 컨트롤러에서 카테고리 서비스와 포스트 서비스에 2번 요청하는 것은 비효율적이다.
해당 페이지의 핵심은 포스트를 가지고 오는 것이기 때문에
게시글을 가지고 오는 서비스에서 같이 가지고 오자.
여기서 문제가 있다.
리턴을 어떤 타입으로 해줘야 할까?
카테고리 목록과 게시글 목록을 함께 리턴해줄 수 있는 타입이 없다.
응답의 dto를 만들어주자.
PostRespDto ▼
package site.metacoding.blogv3.web.dto.post;
import java.util.List;
import org.springframework.data.domain.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import site.metacoding.blogv3.domain.category.Category;
import site.metacoding.blogv3.domain.post.Post;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PostRespDto {
private Page<Post> posts;
private List<Category> categorys;
private Integer userId;
}
PostService ▼
public PostRespDto 게시글목록보기(Integer userId) {
List<Post> postsEntity = postRepository.findByUserId(userId);
List<Category> categorysEntity = categoryRepository.findByUserId(userId);
PostRespDto postRespDto = new PostRespDto(
postsEntity,
categorysEntity,
userId);
return postRespDto;
}
public PostRespDto 게시글카테고리별보기(Integer userId, Integer categoryId) {
List<Post> postsEntity = postRepository.findByUserIdAndCategoryId(userId, categoryId);
List<Category> categorysEntity = categoryRepository.findByUserId(userId);
PostRespDto postRespDto = new PostRespDto(
postsEntity,
categorysEntity,
userId);
return postRespDto;
}
PostRepository ▼
@Query(value = "SELECT * FROM post WHERE userId = :userId AND categoryId = :categoryId ORDER BY id DESC", nativeQuery = true)
List<Post> findByUserIdAndCategoryId(@Param("userId") Integer userId, @Param("categoryId") Integer categoryId);
PostController ▼
// CategoryService 사용하지 말고 PostService 사용하세요.
// category, post글 다 같이 가지고 가야 하기 때문임!!
@GetMapping("/user/{id}/post")
public String postList(@PathVariable Integer id, Integer categoryId, Model model) {
PostRespDto postRespDto = null;
if (categoryId == null) {
postRespDto = postService.게시글목록보기(id);
} else {
postRespDto = postService.게시글카테고리별보기(id, categoryId);
}
model.addAttribute("postRespDto", postRespDto);
return "/post/list";
}
뷰에 잘 뿌려주기만 하면 끝!
/post/list.mustache ▼
{{#postRespDto.posts.content}}
<div class="my_post_list_item">
<div class="my_post_list_item_left">
{{#thumnail}}
<img src="/upload/{{thumnail}}" width="100%" height="100%">
{{/thumnail}}
{{^thumnail}}
<div class="my_fakeimg"> </div>
{{/thumnail}}
</div>
<div class="my_post_list_item_right my_ellipsis">
<div class="my_text_title my_ellipsis">{{title}}
</div>
<div class="my_text_body_sm">{{formatCreateDate}}</div>
<div class="my_mt_md_1">
<a href="/post/{{id}}" class="my_success_btn">Read More</a>
</div>
</div>
</div>
{{/postRespDto.posts.content}}
/layout/post-header.mustache ▼
{{#postRespDto.categorys}}
<li><a class="drawer-menu-item" href="/user/{{user.id}}/post?categoryId={{id}}">{{title}}</a></li>
{{/postRespDto.categorys}}
[출처]
https://cafe.naver.com/metacoding
메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9