Files
hexagonal/src/main/java/myblog/blog/article/controller/ArticleController.java
jinia91 1ed441d98a 21.12.08 캐시 기능 추가 및 리팩토링
__기본기능__
 v 권한 구분 관리자 권한 구현
 v 소셜로그인 기능
 v 블로그 기본 CRUD
 v 연관 카테고리 글 목록
 v 썸네일업로드시 이미지서버에 선업로드 후 URL반환
 v 댓글과 대댓글 구현
 v 메인화면은 무한스크롤 구현
 v 카테고리 화면에서는 페이징박스로 페이징구현
 v 토스트 에디터 사용
 v 토스트 에디터로 작성된 메인 컨텐츠 파싱해서 SSR으로 html출력
 v 쿠키로 이미 읽은 글인지 체크해서 조회수 중복 방지
 v 게시물 조회수 순위별 조회
 v 최근 게시물 조회
 v 최근 코멘트 노출
 v 블로그 태그별 검색과 태그 보이기
 v 일반 검색기능
 v 썸네일 링크로도 추가 가능
 v 카테고리 목록 편집기 개발
 v 비밀댓글 기능
 v 글 포스팅시 자동 커밋 푸시
 v 글 1분단위 자동저장
 v 공유하기 기능
 v reCache 사용하여 레이아웃용 조회값들 캐싱
 v seo 최적화
  v 자동 메타태그 작성
  v rss피드
 v 1차 백엔드 코드 리팩토링

__현재 일정__
  - robot.txt
  - 사이트맵.xml
  - 에러 제어
  - 프론트엔드 코드 리팩토링
  - 디버깅
  - 무중단 배포

__ 고려중__
 - 테스트 코드작성
 - aop 도입
___
나중에 개발해볼 기능
 - toc
 - 이메일 구독기능
 - 새로운 글 토스트 알람 보내기
2021-12-08 12:40:39 +09:00

370 lines
13 KiB
Java

package myblog.blog.article.controller;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.article.dto.*;
import myblog.blog.article.service.ArticleService;
import myblog.blog.article.service.TempArticleService;
import myblog.blog.category.dto.CategoryForView;
import myblog.blog.category.dto.CategoryNormalDto;
import myblog.blog.category.service.CategoryService;
import myblog.blog.comment.dto.CommentDtoForLayout;
import myblog.blog.comment.service.CommentService;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.dto.MemberDto;
import myblog.blog.tags.dto.TagsDto;
import myblog.blog.tags.service.TagsService;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jsoup.Jsoup;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
@Controller
@RequiredArgsConstructor
public class ArticleController {
private final ArticleService articleService;
private final TagsService tagsService;
private final CategoryService categoryService;
private final CommentService commentService;
private final TempArticleService tempArticleService;
private final ModelMapper modelMapper;
private final Parser parser;
private final HtmlRenderer htmlRenderer;
/*
- 아티클 작성 폼 조회
*/
@GetMapping("article/write")
public String writeArticleForm(Model model) {
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", new ArticleForm());
return "article/articleWriteForm";
}
/*
- 아티클 작성 post 요청
*/
@PostMapping("article/write")
@Transactional
public String writeArticle(@Validated ArticleForm articleForm,
Errors errors,
@AuthenticationPrincipal PrincipalDetails principal,
Model model) {
if (errors.hasErrors()) {
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", new ArticleForm());
return "article/articleWriteForm";
}
Long articleId = articleService.writeArticle(articleForm, principal.getMember());
// articleService.pushArticleToGithub(article);
tempArticleService.deleteTemp();
return "redirect:/article/view?articleId=" + articleId;
}
/*
- 아티클 수정 폼 조회
*/
@GetMapping("/article/edit")
public String updateArticle(@RequestParam Long articleId,
Model model) {
// 기존 아티클 DTO 전처리
Article article = articleService.readArticle(articleId);
ArticleDtoForEdit articleDto = modelMapper.map(article, ArticleDtoForEdit.class);
articleDto.setArticleTagList(article.getArticleTagLists()
.stream()
.map(articleTag -> articleTag.getTags().getName())
.collect(Collectors.toList()));
//
modelsForArticleForm(model);
modelsForLayout(model);
model.addAttribute("articleDto", articleDto);
return "article/articleEditForm";
}
/*
- 아티클 수정 요청
*/
@PostMapping("/article/edit")
@Transactional
public String editArticle(@RequestParam Long articleId,
@ModelAttribute ArticleForm articleForm) {
articleService.editArticle(articleId, articleForm);
return "redirect:/article/view?articleId=" + articleId;
}
/*
- 아티클 삭제 요청
*/
@PostMapping("/article/delete")
@Transactional
public String deleteArticle(@RequestParam Long articleId) {
articleService.deleteArticle(articleId);
return "redirect:/";
}
/*
- 카테고리별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list")
public String getArticlesListByCategory(@RequestParam String category,
@RequestParam Integer tier,
@RequestParam Integer page,
Model model) {
// DTO 매핑 전처리
CategoryForView categoryForView = modelsForLayout(model);
PagingBoxDto pagingBoxDto = PagingBoxDto
.createOf(page, getTotalArticleCntByCategory(category, categoryForView));
Slice<ArticleDtoForMain> articleList = articleService.getArticlesByCategory(category, tier, pagingBoxDto.getCurPageNum())
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
//
model.addAttribute("pagingBox", pagingBoxDto);
model.addAttribute("articleList", articleList);
return "article/articleList";
}
/*
- 태그별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list/tag/")
public String getArticlesListByTag(@RequestParam Integer page,
@RequestParam String tagName,
Model model) {
// DTO 매핑 전처리
Page<ArticleDtoForMain> articleList =
articleService.getArticlesByTag(tagName, page)
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, articleList.getTotalPages());
modelsForLayout(model);
//
model.addAttribute("articleList", articleList);
model.addAttribute("pagingBox", pagingBoxDto);
return "article/articleListByTag";
}
/*
- 검색어별 게시물 조회하기
*/
@Transactional
@GetMapping("article/list/search/")
public String getArticlesListByKeyword(@RequestParam Integer page,
@RequestParam String keyword,
Model model) {
// DTO 매핑 전처리
Page<ArticleDtoForMain> articleList =
articleService.getArticlesByKeyword(keyword, page)
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, articleList.getTotalPages());
modelsForLayout(model);
//
model.addAttribute("articleList", articleList);
model.addAttribute("pagingBox", pagingBoxDto);
return "article/articleListByKeyword";
}
/*
- 아티클 상세 조회
1. 로그인여부 검토
2. 게시물 상세조회에 필요한 Dto 전처리
3. 메타태그 작성위한 Dto 전처리
4. Dto 담기
5. 조회수 증가 검토
*/
@GetMapping("/article/view")
public String readArticle(@RequestParam Long articleId,
@AuthenticationPrincipal PrincipalDetails principal,
@CookieValue(required = false, name = "view") String cookie,
HttpServletResponse response,
Model model) {
// 1. 로그인 여부에 따라 뷰단에 회원정보 출력 여부 결정
if (principal != null) {
model.addAttribute("member", modelMapper.map(principal.getMember(), MemberDto.class));
} else {
model.addAttribute("member", null);
}
/*
DTO 매핑 전처리
2. 게시물 상세조회용
*/
Article article = articleService.readArticle(articleId);
ArticleDtoForDetail articleDtoForDetail =
modelMapper.map(article, ArticleDtoForDetail.class);
List<String> tags = article.getArticleTagLists()
.stream()
.map(tag -> tag.getTags().getName())
.collect(Collectors.toList());
articleDtoForDetail.setTags(tags);
articleDtoForDetail.setContent(htmlRenderer.render(parser.parse(article.getContent())));
List<ArticleDtoByCategory> articleTitlesSortByCategory =
articleService
.getArticlesByCategoryForDetailView(article.getCategory())
.stream()
.map(article1 -> modelMapper.map(article1, ArticleDtoByCategory.class))
.collect(Collectors.toList());
// 3. 메타 태그용 Dto 전처리
StringBuilder metaTags = new StringBuilder();
for (String tag : tags) {
metaTags.append(tag).append(", ");
}
String substringContents = null;
if(articleDtoForDetail.getContent().length()>200) {
substringContents = articleDtoForDetail.getContent().substring(0, 200);
}
else substringContents = articleDtoForDetail.getContent();
// 4. 모델 담기
modelsForLayout(model);
model.addAttribute("article", articleDtoForDetail);
model.addAttribute("metaTags",metaTags);
model.addAttribute("metaContents",Jsoup.parse(substringContents).text());
model.addAttribute("articlesSortBycategory", articleTitlesSortByCategory);
// 5. 조회수 증가 검토
addHitWithCookie(article, cookie, response);
return "article/articleView";
}
/*
- 아티클 폼에 필요한 모델 담기
*/
private void modelsForArticleForm(Model model) {
List<CategoryNormalDto> categoryForForm =
categoryService
.findCategoryByTier(2)
.stream()
.map(category -> modelMapper.map(category, CategoryNormalDto.class))
.collect(Collectors.toList());
model.addAttribute("categoryInput", categoryForForm);
List<TagsDto> tagsForForm =
tagsService
.findAllTags()
.stream()
.map(tag -> new TagsDto(tag.getName()))
.collect(Collectors.toList());
model.addAttribute("tagsInput", tagsForForm);
}
/*
- 레이아웃에 필요한 모델 담기
*/
private CategoryForView modelsForLayout(Model model) {
CategoryForView categoryForView = categoryService.getCategoryForView();
model.addAttribute("category", categoryForView);
List<CommentDtoForLayout> comments = commentService.recentCommentList();
model.addAttribute("commentsList", comments);
return categoryForView;
}
/*
- 쿠키 추가 검토
*/
private void addHitWithCookie(Article article, String cookie, HttpServletResponse response) {
Long articleId = article.getId();
if (cookie == null) {
Cookie viewCookie = new Cookie("view", articleId + "/");
viewCookie.setComment("게시물 조회 확인용");
viewCookie.setMaxAge(60 * 60);
article.addHit();
response.addCookie(viewCookie);
} else {
boolean isRead = false;
String[] viewCookieList = cookie.split("/");
for (String alreadyRead : viewCookieList) {
if (alreadyRead.equals(String.valueOf(articleId))) {
isRead = true;
break;
}
}
if (!isRead) {
cookie += articleId + "/";
article.addHit();
}
response.addCookie(new Cookie("view", cookie));
}
}
/*
- 카테고리별 아티클 갯수 구하기
*/
private int getTotalArticleCntByCategory(String category, CategoryForView categorys) {
if (categorys.getTitle().equals(category)) {
return categorys.getCount();
} else {
for (CategoryForView categoryCnt :
categorys.getCategoryTCountList()) {
if (categoryCnt.getTitle().equals(category))
return categoryCnt.getCount();
for (CategoryForView categoryCntSub : categoryCnt.getCategoryTCountList()) {
if (categoryCntSub.getTitle().equals(category))
return categoryCntSub.getCount();
}
}
}
throw new IllegalArgumentException("카테고리별 아티클 수 에러");
}
}