헥사고날 아키텍쳐로 리아키텍쳐링 마무리, DDD를 적용하여 두꺼운 도메인, 얇은 서비스로 리팩토링

This commit is contained in:
jinia91
2022-03-27 00:30:18 +09:00
parent 527edda336
commit 76cd25e11f
63 changed files with 425 additions and 370 deletions

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.adapter.incomming.web;
package myblog.blog.article.adapter.incomming;
import lombok.RequiredArgsConstructor;
@@ -6,17 +6,16 @@ import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.article.application.port.incomming.TempArticleUseCase;
import myblog.blog.article.application.port.incomming.ArticleQueriesUseCase;
import myblog.blog.article.application.port.incomming.TagsQueriesUseCase;
import myblog.blog.article.application.port.request.ArticleCreateRequest;
import myblog.blog.article.application.port.request.ArticleEditRequest;
import myblog.blog.article.application.port.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.response.ArticleResponseForEdit;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.dto.MemberVo;
import myblog.blog.article.application.port.incomming.request.ArticleCreateRequest;
import myblog.blog.article.application.port.incomming.request.ArticleEditRequest;
import myblog.blog.category.appliacation.port.incomming.response.CategoryViewForLayout;
import myblog.blog.article.application.port.incomming.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForEdit;
import myblog.blog.member.application.port.incomming.response.PrincipalDetails;
import myblog.blog.shared.queries.LayoutRenderingQueries;
import org.jsoup.Jsoup;
@@ -209,7 +208,7 @@ public class ArticleController {
Model model) {
// 1. 로그인 여부에 따라 뷰단에 회원정보 출력 여부 결정
if (principal != null) {
model.addAttribute("member", MemberVo.from(principal.getMember()));
model.addAttribute("member", principal.getMember());
} else {
model.addAttribute("member", null);
}

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.adapter.incomming.web;
package myblog.blog.article.adapter.incomming;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,8 +1,8 @@
package myblog.blog.article.adapter.incomming.web;
package myblog.blog.article.adapter.incomming;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.ArticleQueriesUseCase;
import myblog.blog.shared.queries.LayoutRenderingQueries;
import org.jsoup.Jsoup;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.adapter.incomming.web;
package myblog.blog.article.adapter.incomming;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,9 +1,9 @@
package myblog.blog.article.adapter.incomming.web;
package myblog.blog.article.adapter.incomming;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.TempArticle;
import myblog.blog.article.application.TempArticleService;
import myblog.blog.article.application.port.response.TempArticleResponse;
import myblog.blog.article.application.port.incomming.response.TempArticleResponse;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;

View File

@@ -2,17 +2,17 @@ package myblog.blog.article.application;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.ArticleQueriesUseCase;
import myblog.blog.article.application.port.outgoing.ArticleRepositoryPort;
import myblog.blog.article.domain.Article;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.domain.Category;
import myblog.blog.article.application.port.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.response.ArticleResponseForEdit;
import myblog.blog.article.application.port.incomming.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForEdit;
import myblog.blog.category.appliacation.CategoryService;
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
@@ -27,7 +27,7 @@ import java.util.stream.Collectors;
public class ArticleQueries implements ArticleQueriesUseCase {
private final ArticleRepositoryPort articleRepositoryPort;
private final CategoryService categoryService;
private final CategoryUseCase categoryUseCase;
private final ModelMapper modelMapper;
/*
@@ -130,7 +130,7 @@ public class ArticleQueries implements ArticleQueriesUseCase {
*/
@Override
public List<ArticleResponseByCategory> getArticlesByCategoryForDetailView(String categoryName){
Category category = categoryService.findCategory(categoryName);
Category category = categoryUseCase.findCategory(categoryName);
return articleRepositoryPort.findTop6ByCategoryOrderByIdDesc(category)
.stream()
.map(article -> modelMapper.map(article, ArticleResponseByCategory.class))

View File

@@ -2,10 +2,12 @@ package myblog.blog.article.application;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.request.ArticleCreateRequest;
import myblog.blog.article.application.port.request.ArticleEditRequest;
import myblog.blog.article.application.port.incomming.request.ArticleCreateRequest;
import myblog.blog.article.application.port.incomming.request.ArticleEditRequest;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.article.application.port.incomming.TagUseCase;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.member.application.port.incomming.MemberUseCase;
import myblog.blog.article.application.port.outgoing.ArticleBackupRepositoryPort;
import myblog.blog.article.application.port.outgoing.ArticleRepositoryPort;
@@ -13,8 +15,6 @@ import myblog.blog.article.domain.Article;
import myblog.blog.category.domain.Category;
import myblog.blog.member.doamin.Member;
import myblog.blog.category.appliacation.CategoryService;
import myblog.blog.member.service.Oauth2MemberService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
@@ -28,16 +28,16 @@ import java.util.List;
public class ArticleService implements ArticleUseCase {
private final TagUseCase tagUseCase;
private final CategoryService categoryService;
private final Oauth2MemberService memberService;
private final CategoryUseCase categoryUseCase;
private final MemberUseCase memberUseCase;
private final ArticleRepositoryPort articleRepositoryPort;
private final ArticleBackupRepositoryPort articleBackupRepositoryPort;
@Override
@CacheEvict(value = {"layoutCaching", "layoutRecentArticleCaching","seoCaching"}, allEntries = true)
public Long writeArticle(ArticleCreateRequest articleCreateRequest) {
Member writer = memberService.findById(articleCreateRequest.getMemberId());
Category category = categoryService.findCategory(articleCreateRequest.getCategory());
Member writer = memberUseCase.findById(articleCreateRequest.getMemberId());
Category category = categoryUseCase.findCategory(articleCreateRequest.getCategory());
Article newArticle = new Article(articleCreateRequest.getTitle(),
articleCreateRequest.getContent(),
articleCreateRequest.getToc(),
@@ -54,7 +54,7 @@ public class ArticleService implements ArticleUseCase {
public void editArticle(ArticleEditRequest articleEditRequest) {
Article article = articleRepositoryPort.findById(articleEditRequest.getArticleId())
.orElseThrow(() -> new IllegalArgumentException("NotFoundArticleException"));
Category category = categoryService.findCategory(articleEditRequest.getCategoryName());
Category category = categoryUseCase.findCategory(articleEditRequest.getCategoryName());
tagUseCase.deleteAllTagsWith(article);
tagUseCase.createNewTagsAndArticleTagList(articleEditRequest.getTags(), article);
article.edit(articleEditRequest.getContent(),

View File

@@ -5,7 +5,7 @@ import myblog.blog.article.application.port.outgoing.TagRepositoryPort;
import myblog.blog.article.domain.Tags;
import myblog.blog.article.application.port.incomming.TagsQueriesUseCase;
import myblog.blog.shared.utils.MapperUtils;
import myblog.blog.article.application.port.response.TagsResponse;
import myblog.blog.article.application.port.incomming.response.TagsResponse;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -1,9 +1,9 @@
package myblog.blog.article.application.port.incomming;
import myblog.blog.article.application.port.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.response.ArticleResponseForEdit;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForCardBox;
import myblog.blog.article.application.port.incomming.response.ArticleResponseByCategory;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForDetail;
import myblog.blog.article.application.port.incomming.response.ArticleResponseForEdit;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;

View File

@@ -1,7 +1,7 @@
package myblog.blog.article.application.port.incomming;
import myblog.blog.article.application.port.request.ArticleCreateRequest;
import myblog.blog.article.application.port.request.ArticleEditRequest;
import myblog.blog.article.application.port.incomming.request.ArticleCreateRequest;
import myblog.blog.article.application.port.incomming.request.ArticleEditRequest;
import myblog.blog.article.domain.Article;
import java.util.List;

View File

@@ -1,6 +1,6 @@
package myblog.blog.article.application.port.incomming;
import myblog.blog.article.application.port.response.TagsResponse;
import myblog.blog.article.application.port.incomming.response.TagsResponse;
import java.util.List;

View File

@@ -1,8 +1,8 @@
package myblog.blog.article.application.port.request;
package myblog.blog.article.application.port.incomming.request;
import lombok.AllArgsConstructor;
import lombok.Getter;
import myblog.blog.article.adapter.incomming.web.ArticleForm;
import myblog.blog.article.adapter.incomming.ArticleForm;
@Getter
@AllArgsConstructor

View File

@@ -1,8 +1,8 @@
package myblog.blog.article.application.port.request;
package myblog.blog.article.application.port.incomming.request;
import lombok.AllArgsConstructor;
import lombok.Getter;
import myblog.blog.article.adapter.incomming.web.ArticleForm;
import myblog.blog.article.adapter.incomming.ArticleForm;
@Getter
@AllArgsConstructor

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.response;
package myblog.blog.article.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -3,7 +3,7 @@ package myblog.blog.base.config;
import lombok.RequiredArgsConstructor;
import myblog.blog.shared.exception.LoginFailHandler;
import myblog.blog.member.doamin.Role;
import myblog.blog.member.service.Oauth2MemberService;
import myblog.blog.member.application.Oauth2MemberService;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

View File

@@ -3,9 +3,9 @@ package myblog.blog.category.adapter.imcomming;
import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.category.appliacation.port.incomming.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import myblog.blog.comment.application.port.incomming.response.CommentDtoForLayout;
import myblog.blog.comment.application.CommentService;
import org.springframework.stereotype.Controller;
@@ -27,13 +27,13 @@ public class CategoryController {
- 카테고리 수정폼 조회
*/
@GetMapping("/category/edit")
public String editCategoryForm(Model model) {
String editCategoryForm(Model model) {
List<CategorySimpleDto> categoryList = categoryUseCase.getCategorytCountList();
List<CategorySimpleDto> copyList = new ArrayList<>(List.copyOf(categoryList));
copyList.remove(0);
CategoryViewForLayout categoryViewForLayout = CategoryViewForLayout.from(categoryList);
List<CommentDtoForLayout> comments = commentService.recentCommentList();
List<CommentDtoForLayout> comments = commentService.recentCommentListForLayout();
model.addAttribute("categoryForEdit", copyList);
model.addAttribute("category", categoryViewForLayout);
@@ -45,8 +45,7 @@ public class CategoryController {
- 카테고리 수정 요청
*/
@PostMapping("/category/edit")
public @ResponseBody
String editCategory(@RequestBody List<CategorySimpleDto> categoryList, Errors errors) {
@ResponseBody String editCategory(@RequestBody List<CategorySimpleDto> categoryList, Errors errors) {
// List DTO 검증을 위한 커스텀 validator
categorylistValidator.validate(categoryList, errors);
categoryUseCase.changeCategory(categoryList);

View File

@@ -2,7 +2,7 @@ package myblog.blog.category.adapter.outgoing.persistence;
import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.outgoing.CategoryRepositoryPort;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import myblog.blog.category.domain.Category;
import org.springframework.stereotype.Component;

View File

@@ -1,6 +1,6 @@
package myblog.blog.category.adapter.outgoing.persistence;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

View File

@@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor;
import myblog.blog.category.domain.Category;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.outgoing.CategoryRepositoryPort;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.incomming.response.CategoryViewForLayout;
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.CacheEvict;

View File

@@ -1,7 +1,7 @@
package myblog.blog.category.appliacation.port.incomming;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.incomming.response.CategoryViewForLayout;
import myblog.blog.category.domain.Category;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package myblog.blog.category.appliacation.port.response;
package myblog.blog.category.appliacation.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.category.appliacation.port.response;
package myblog.blog.category.appliacation.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,6 +1,6 @@
package myblog.blog.category.appliacation.port.outgoing;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.category.appliacation.port.incomming.response.CategorySimpleDto;
import myblog.blog.category.domain.Category;
import java.util.List;

View File

@@ -1,13 +1,12 @@
package myblog.blog.comment.adapter.incomming;
import myblog.blog.comment.application.port.incomming.CommentUseCase;
import myblog.blog.comment.application.port.incomming.CommentDto;
import myblog.blog.comment.application.port.incomming.response.CommentDto;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.doamin.Member;
import myblog.blog.member.application.port.incomming.response.PrincipalDetails;
import lombok.RequiredArgsConstructor;
import myblog.blog.member.application.port.incomming.response.MemberVo;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
@@ -24,7 +23,7 @@ public class CommentController {
- 아티클 조회시 아티클에 달린 댓글들 전체 조회
*/
@GetMapping("/comment/list/{articleId}")
public List<CommentDto> getCommentList(@PathVariable Long articleId){
List<CommentDto> getCommentList(@PathVariable Long articleId){
return commentUseCase.getCommentList(articleId);
}
@@ -32,7 +31,7 @@ public class CommentController {
- 댓글 작성 요청
*/
@PostMapping("/comment/write")
public List<CommentDto> getCommentList(@RequestParam Long articleId,
List<CommentDto> getCommentList(@RequestParam Long articleId,
@RequestParam(required = false) Long parentId,
@Validated @RequestBody CommentForm commentForm, Errors errors,
@AuthenticationPrincipal PrincipalDetails principal){
@@ -40,13 +39,13 @@ public class CommentController {
throw new InvalidCommentRequestException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage());
}
Member member = principal.getMember();
MemberVo member = principal.getMember();
// 부모 댓글인지 자식댓글인지 분기로 저장
if(parentId != null){
commentUseCase.saveCComment(commentForm.getContent(), commentForm.isSecret(), member, articleId, parentId);
commentUseCase.saveCComment(commentForm.getContent(), commentForm.isSecret(), member.getId(), articleId, parentId);
}
else {
commentUseCase.savePComment(commentForm.getContent(), commentForm.isSecret(), member, articleId);
commentUseCase.savePComment(commentForm.getContent(), commentForm.isSecret(), member.getId(), articleId);
}
return commentUseCase.getCommentList(articleId);
@@ -56,7 +55,7 @@ public class CommentController {
- 댓글 삭제 요청
*/
@PostMapping("/comment/delete")
public List<CommentDto> deleteComment(@RequestParam Long articleId,
List<CommentDto> deleteComment(@RequestParam Long articleId,
@RequestParam Long commentId) {
commentUseCase.deleteComment(commentId);
return commentUseCase.getCommentList(articleId);

View File

@@ -2,8 +2,8 @@ package myblog.blog.comment.application;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.comment.application.port.incomming.CommentUseCase;
import myblog.blog.comment.application.port.incomming.CommentDto;
import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.application.port.incomming.response.CommentDto;
import myblog.blog.comment.application.port.incomming.response.CommentDtoForLayout;
import myblog.blog.comment.application.port.outgoing.CommentRepositoryPort;
import myblog.blog.comment.domain.Comment;
@@ -11,6 +11,7 @@ import myblog.blog.article.domain.Article;
import myblog.blog.member.doamin.Member;
import lombok.RequiredArgsConstructor;
import myblog.blog.member.application.port.incomming.MemberUseCase;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@@ -23,6 +24,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class CommentService implements CommentUseCase {
private final ArticleUseCase articleUseCase;
private final MemberUseCase memberUseCase;
private final CommentRepositoryPort commentRepositoryPort;
/*
@@ -38,8 +40,8 @@ public class CommentService implements CommentUseCase {
*/
@CacheEvict(value = "layoutRecentCommentCaching", allEntries = true)
@Override
public void savePComment(String content, boolean secret, Member member, Long articleId){
public void savePComment(String content, boolean secret, Long memberId, Long articleId){
Member member = memberUseCase.findById(memberId);
Article article = articleUseCase.getArticle(articleId);
Comment comment = Comment.builder()
@@ -60,8 +62,8 @@ public class CommentService implements CommentUseCase {
*/
@CacheEvict(value = "layoutRecentCommentCaching", allEntries = true)
@Override
public void saveCComment(String content, boolean secret, Member member, Long articleId, Long parentId) {
public void saveCComment(String content, boolean secret, Long memberId, Long articleId, Long parentId) {
Member member = memberUseCase.findById(memberId);
Article article = articleUseCase.getArticle(articleId);
Comment pComment = commentRepositoryPort.findById(parentId)
.orElseThrow(() -> new IllegalArgumentException("NotfoundParentCommentException"));
@@ -97,7 +99,7 @@ public class CommentService implements CommentUseCase {
*/
@Cacheable(value = "layoutRecentCommentCaching", key = "0")
@Override
public List<CommentDtoForLayout> recentCommentList(){
public List<CommentDtoForLayout> recentCommentListForLayout(){
return commentRepositoryPort.findTop5ByOrderByIdDesc()
.stream()
.map(comment ->

View File

@@ -1,13 +1,14 @@
package myblog.blog.comment.application.port.incomming;
import myblog.blog.member.doamin.Member;
import myblog.blog.comment.application.port.incomming.response.CommentDto;
import myblog.blog.comment.application.port.incomming.response.CommentDtoForLayout;
import java.util.List;
public interface CommentUseCase {
List<CommentDto> getCommentList(Long articleId);
void savePComment(String content, boolean secret, Member member, Long articleId);
void saveCComment(String content, boolean secret, Member member, Long articleId, Long parentId);
void savePComment(String content, boolean secret, Long memberId, Long articleId);
void saveCComment(String content, boolean secret, Long memberId, Long articleId, Long parentId);
void deleteComment(Long commentId);
List<CommentDtoForLayout> recentCommentList();
List<CommentDtoForLayout> recentCommentListForLayout();
}

View File

@@ -1,4 +1,4 @@
package myblog.blog.comment.application.port.incomming;
package myblog.blog.comment.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,4 +1,4 @@
package myblog.blog.comment.application.port.incomming;
package myblog.blog.comment.application.port.incomming.response;
import lombok.Getter;
import lombok.Setter;

View File

@@ -1,10 +1,9 @@
package myblog.blog.imgupload.adapter.incomming;
import myblog.blog.imgupload.service.port.incomming.ImgUploadUseCase;
import myblog.blog.imgupload.application.port.incomming.ImgUploadUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@RestController
@RequiredArgsConstructor
@@ -13,8 +12,7 @@ public class UploadImgController {
private final ImgUploadUseCase imgUploadUseCase;
@PostMapping("/article/uploadImg")
public @ResponseBody
String imgUpload(@ModelAttribute UploadImgForm uploadImgForm) throws IOException {
String imgUpload(@ModelAttribute UploadImgForm uploadImgForm){
return imgUploadUseCase.storeImg(uploadImgForm.getImg());
}
}

View File

@@ -4,7 +4,7 @@ import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import lombok.RequiredArgsConstructor;
import myblog.blog.imgupload.domain.ImageFile;
import myblog.blog.imgupload.service.port.outgoing.ImgUploadStrategyPort;
import myblog.blog.imgupload.application.port.outgoing.ImgUploadStrategyPort;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

View File

@@ -1,16 +1,13 @@
package myblog.blog.imgupload.service;
package myblog.blog.imgupload.application;
import lombok.RequiredArgsConstructor;
import myblog.blog.imgupload.domain.ImageFile;
import myblog.blog.imgupload.service.port.incomming.ImgUploadUseCase;
import myblog.blog.imgupload.service.port.outgoing.ImgUploadStrategyPort;
import myblog.blog.imgupload.application.port.incomming.ImgUploadUseCase;
import myblog.blog.imgupload.application.port.outgoing.ImgUploadStrategyPort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Service
@Transactional
@RequiredArgsConstructor

View File

@@ -1,9 +1,7 @@
package myblog.blog.imgupload.service.port.incomming;
package myblog.blog.imgupload.application.port.incomming;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface ImgUploadUseCase {
String storeImg(MultipartFile img);
}

View File

@@ -1,4 +1,4 @@
package myblog.blog.imgupload.service.port.outgoing;
package myblog.blog.imgupload.application.port.outgoing;
import myblog.blog.imgupload.domain.ImageFile;

View File

@@ -0,0 +1,26 @@
package myblog.blog.member.adapter.incomming;
import lombok.RequiredArgsConstructor;
import myblog.blog.shared.queries.LayoutRenderingQueries;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequiredArgsConstructor
public class MemberController {
private final LayoutRenderingQueries layoutRenderingQueries;
/*
- 회원 로그인 폼 조회
*/
@GetMapping("/login")
String loginFrom(@RequestParam(value = "error",required = false) String error, Model model){
if(error!=null&&error.equals("duplicatedEmail")){
model.addAttribute("errMsg","이미 가입된 이메일입니다.");
}
layoutRenderingQueries.AddLayoutTo(model);
return "login";
}
}

View File

@@ -1,10 +1,9 @@
package myblog.blog.member.repository;
package myblog.blog.member.adapter.outgoing.repository;
import myblog.blog.member.doamin.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
public interface MemberRepository extends JpaRepository<Member, Long> {
public interface JpaMemberRepository extends JpaRepository<Member, Long> {
/*
- Id로 유저 찾기
*/

View File

@@ -0,0 +1,32 @@
package myblog.blog.member.adapter.outgoing.repository;
import myblog.blog.member.application.port.outgoing.MemberRepositoryPort;
import myblog.blog.member.doamin.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class MemberRepositoryAdapter implements MemberRepositoryPort {
private final JpaMemberRepository jpaMemberRepository;
@Override
public Member findByEmail(String email) {
return jpaMemberRepository.findByEmail(email);
}
@Override
public void save(Member member) {
jpaMemberRepository.save(member);
}
@Override
public Optional<Member> findById(Long memberId) {
return jpaMemberRepository.findById(memberId);
}
@Override
public Member findByUserId(String providerId) {
return jpaMemberRepository.findByUserId(providerId);
}
}

View File

@@ -0,0 +1,38 @@
package myblog.blog.member.application;
@Deprecated
public class BuildAdminAccountHelper {
// 앱 구동시 ADMIN 계정 Insert
// @Value("${admin.username}")
// private String adminUsername;
// @Value("${admin.picUrl}")
// private String adminPicUrl;
// @Value("${admin.email}")
// private String adminEmail;
// @Value("${admin.providerId}")
// private String adminProviderId;
// @Value("${admin.provider}")
// private String adminProvider;
/*
- 앱 구동시 ADMIN 계정 INSERT
*/
// @PostConstruct
// public void insertAdmin(){
//
// Member admin = memberRepository.findByEmail(adminEmail);
// if(admin == null){
// admin = Member.builder()
// .username(adminUsername+"#"+adminProviderId.substring(0,5))
// .email(adminEmail)
// .picUrl(adminPicUrl)
// .userId(adminProviderId)
// .providerId(adminProviderId)
// .provider(adminProvider)
// .role(Role.ADMIN)
// .build();
//
// memberRepository.save(admin);
// }
// }
}

View File

@@ -1,13 +1,14 @@
package myblog.blog.member.service;
package myblog.blog.member.application;
import myblog.blog.member.application.port.incomming.MemberUseCase;
import myblog.blog.member.application.port.incomming.response.PrincipalDetails;
import myblog.blog.member.application.port.incomming.response.userinfo.Oauth2UserInfo;
import myblog.blog.member.application.port.outgoing.MemberRepositoryPort;
import lombok.RequiredArgsConstructor;
import myblog.blog.member.auth.userinfo.Oauth2UserInfo;
import myblog.blog.member.auth.UserInfoFactory;
import myblog.blog.member.repository.MemberRepository;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.doamin.Member;
import myblog.blog.member.doamin.Role;
import org.springframework.beans.factory.annotation.Value;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -15,42 +16,24 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
@Service
@Transactional
@RequiredArgsConstructor
public class Oauth2MemberService extends DefaultOAuth2UserService {
public class Oauth2MemberService extends DefaultOAuth2UserService implements MemberUseCase {
private final MemberRepository memberRepository;
private final MemberRepositoryPort memberRepositoryPort;
private final UserInfoFactory userInfoFactory;
// 구동시 ADMIN 계정 Insert
@Value("${admin.username}")
private String adminUsername;
@Value("${admin.picUrl}")
private String adminPicUrl;
@Value("${admin.email}")
private String adminEmail;
@Value("${admin.providerId}")
private String adminProviderId;
@Value("${admin.provider}")
private String adminProvider;
/*
- OAuth2 인증 로그인
*/
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Oauth2UserInfo userInfo =
userInfoFactory.makeOauth2UserInfoOf(userRequest, oAuth2User);
Member member = getOrJoinMember(userInfo);
return new PrincipalDetails(member, userInfo.getAttributes());
}
@@ -58,16 +41,12 @@ public class Oauth2MemberService extends DefaultOAuth2UserService {
- 회원가입 or 로그인 로직
*/
private Member getOrJoinMember(Oauth2UserInfo userInfo) {
//DB에서 조회해서 존재시 로그인처리, 미존재시 가입처리
Member member = memberRepository.findByUserId(userInfo.getProviderId());
Member member = memberRepositoryPort.findByUserId(userInfo.getProviderId());
if(member == null) {
//Email 중복검증
if(memberRepository.findByEmail(userInfo.getEmail()) != null)
if(memberRepositoryPort.findByEmail(userInfo.getEmail()) != null)
throw new OAuth2AuthenticationException("duplicateEmail");
member = Member.builder()
.username(userInfo.getUserName())
.picUrl(userInfo.getPicture())
@@ -77,43 +56,18 @@ public class Oauth2MemberService extends DefaultOAuth2UserService {
.provider(userInfo.getProvider())
.role(Role.USER)
.build();
memberRepository.save(member);
memberRepositoryPort.save(member);
}
// 유저 네임 변경시 더티체킹으로 유저네임 변경
if(!member.getUsername().equals(userInfo.getUserName())){
member.changeUsername(userInfo.getUserName());
}
return member;
}
/*
- 구동시 ADMIN 계정 INSERT
*/
// @PostConstruct
public void insertAdmin(){
Member admin = memberRepository.findByEmail(adminEmail);
if(admin == null){
admin = Member.builder()
.username(adminUsername+"#"+adminProviderId.substring(0,5))
.email(adminEmail)
.picUrl(adminPicUrl)
.userId(adminProviderId)
.providerId(adminProviderId)
.provider(adminProvider)
.role(Role.ADMIN)
.build();
memberRepository.save(admin);
}
}
@Override
public Member findById(Long memberId) {
return memberRepository.findById(memberId)
return memberRepositoryPort.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("NotFoundMemberException"));
}
}

View File

@@ -1,6 +1,9 @@
package myblog.blog.member.auth;
package myblog.blog.member.application;
import myblog.blog.member.auth.userinfo.*;
import myblog.blog.member.application.port.incomming.response.userinfo.GoogleUserInfo;
import myblog.blog.member.application.port.incomming.response.userinfo.NaverUserInfo;
import myblog.blog.member.application.port.incomming.response.userinfo.Oauth2UserInfo;
import myblog.blog.member.application.port.incomming.response.userinfo.ProviderType;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;

View File

@@ -0,0 +1,7 @@
package myblog.blog.member.application.port.incomming;
import myblog.blog.member.doamin.Member;
public interface MemberUseCase {
Member findById(Long memberId);
}

View File

@@ -1,8 +1,7 @@
package myblog.blog.member.dto;
package myblog.blog.member.application.port.incomming.response;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import myblog.blog.member.doamin.Member;
/*

View File

@@ -1,6 +1,7 @@
package myblog.blog.member.auth;
package myblog.blog.member.application.port.incomming.response;
import myblog.blog.member.doamin.Member;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -16,12 +17,14 @@ public class PrincipalDetails implements OAuth2User {
private final Map<String, Object> attributes;
private final Member member;
private final MemberVo memberVo;
public PrincipalDetails(Member member, Map<String, Object> attributes) {
this.member = member;
this.attributes = attributes;
this.memberVo = MemberVo.from(member);
}
public Member getMember(){return member;}
public MemberVo getMember(){return memberVo;}
// 타임리프 뷰단처리를 위한 편의 메소드
public Long getMemberId(){

View File

@@ -1,4 +1,4 @@
package myblog.blog.member.auth.userinfo;
package myblog.blog.member.application.port.incomming.response.userinfo;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Map;

View File

@@ -1,7 +1,6 @@
package myblog.blog.member.auth.userinfo;
package myblog.blog.member.application.port.incomming.response.userinfo;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package myblog.blog.member.auth.userinfo;
package myblog.blog.member.application.port.incomming.response.userinfo;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package myblog.blog.member.auth.userinfo;
package myblog.blog.member.application.port.incomming.response.userinfo;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

View File

@@ -0,0 +1,12 @@
package myblog.blog.member.application.port.outgoing;
import myblog.blog.member.doamin.Member;
import java.util.Optional;
public interface MemberRepositoryPort {
Member findByEmail(String email);
void save(Member member);
Optional<Member> findById(Long memberId);
Member findByUserId(String providerId);
}

View File

@@ -1,44 +0,0 @@
package myblog.blog.member.controller;
import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.CategoryService;
import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.application.CommentService;
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 java.util.List;
@Controller
@RequiredArgsConstructor
public class MemberController {
private final CategoryService categoryService;
private final CommentService commentService;
/*
- 회원 로그인 폼 조회
*/
@GetMapping("/login")
public String loginFrom(@RequestParam(value = "error",required = false) String error, Model model){
// 가입 실패시 에러 메시지 처리
if(error!=null&&error.equals("duplicatedEmail")){
model.addAttribute("errMsg","이미 가입된 이메일입니다.");
}
// 레이아웃 DTO 전처리
CategoryViewForLayout categoryViewForLayout = categoryService.getCategoryViewForLayout();
List<CommentDtoForLayout> comments = commentService.recentCommentList();
//
model.addAttribute("category", categoryViewForLayout);
model.addAttribute("commentsList", comments);
return "login";
}
}

View File

@@ -1,22 +1,21 @@
package myblog.blog.seo.adapter.incomming;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import myblog.blog.article.application.port.incomming.RssUseCase;
import myblog.blog.seo.application.port.incomming.RssUseCase;
/*
- rss 피드 발행 요청
- rss 발행 비용을 감안해 캐시처리
- 발행빈도가 아직 예측되지않으므로 캐시 만료를 12시간으로 설정
*/
@Controller
@RestController
@RequiredArgsConstructor
public class RssController {
private final RssUseCase rssUseCase;
@GetMapping(value = "/rss",produces = "application/xml;charset=utf-8")
public @ResponseBody String rssFeed() {
String rssFeed() {
return rssUseCase.getRssFeed();
}
}

View File

@@ -1,22 +1,21 @@
package myblog.blog.seo.adapter.incomming;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import myblog.blog.article.application.port.incomming.SiteMapUseCase;
import myblog.blog.seo.application.port.incomming.SiteMapUseCase;
/*
- siteMap.xml 요청
- 작성 비용을 감안해 캐시처리
- 조회 빈도가 아직 예측되지않으므로 캐시 만료를 12시간으로 설정
*/
@Controller
@RestController
@RequiredArgsConstructor
public class SiteMapController {
private final SiteMapUseCase siteMapUseCase;
@GetMapping(value = "/sitemap",produces = "application/xml;charset=utf-8")
public @ResponseBody String getSiteMap() {
String getSiteMap() {
return siteMapUseCase.getSiteMap();
}
}

View File

@@ -1,21 +1,18 @@
package myblog.blog.seo.application;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.article.application.port.incomming.RssUseCase;
import myblog.blog.seo.application.port.incomming.RssUseCase;
import myblog.blog.article.domain.Article;
import myblog.blog.seo.domain.RssFeed;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import myblog.blog.article.domain.Article;
import org.jdom2.*;
import org.jdom2.output.*;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import static myblog.blog.shared.utils.MarkdownUtils.*;
/*
- rss 서비스 로직
1. jdom2 사용하여 xml파일 작성
@@ -27,65 +24,14 @@ import static myblog.blog.shared.utils.MarkdownUtils.*;
@RequiredArgsConstructor
public class RssService implements RssUseCase {
static final String ITEM_ROOT = "https://www.jiniaslog.co.kr/article/view?articleId=";
private final ArticleUseCase articleUseCase;
@Override
@Cacheable(value = "seoCaching", key = "0")
public String getRssFeed() {
List<Article> articles = articleUseCase.getTotalArticle();
Document doc = makeRssFeedDocumentFrom(articles);
XMLOutputter xmlOutputter = getXmlOutputter();
return xmlOutputter.outputString(doc);
}
private Document makeRssFeedDocumentFrom(List<Article> articles) {
Document doc = new Document();
Element rss = buildRssRoot();
doc.setRootElement(rss);
Element channel = buildChannel();
rss.addContent(channel);
addArticleItemsToChannel(articles, channel, new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH));
return doc;
}
private Element buildRssRoot() {
Element rss = new Element("rss");
rss.setAttribute(new Attribute("version", "2.0"));
return rss;
}
private Element buildChannel() {
Element channel = new Element("channel");
channel.addContent(new Element("title").addContent(new CDATA("Jinia's LOG")));
channel.addContent(new Element("link").setText("https://www.jiniaslog.co.kr"));
channel.addContent(new Element("description").addContent(new CDATA("비전공 출신, 개발자 지망생의 공부 내용을 기록하는 블로그입니다.")));
return channel;
}
private void addArticleItemsToChannel(List<Article> articles, Element channel, SimpleDateFormat dateFormat) {
for (Article article : articles) {
Element item = createItemTo(article, dateFormat);
channel.addContent(item);
}
}
private Element createItemTo(Article article, SimpleDateFormat dateFormat) {
Element item = new Element("item");
item
.addContent(new Element("title").addContent(new CDATA(article.getTitle())))
.addContent(new Element("link").setText(ITEM_ROOT + article.getId()))
.addContent(new Element("description").addContent(new CDATA(getHtmlRenderer().render(getParser().parse(article.getContent())))))
.addContent(new Element("pubDate").setText(dateFormat.format(Timestamp.valueOf(article.getCreatedDate()))))
.addContent(new Element("guid").setText(ITEM_ROOT + article.getId()));
return item;
}
private XMLOutputter getXmlOutputter() {
Format format = Format.getPrettyFormat();
format.setEncoding("UTF-8");
format.setLineSeparator("\r\n");
return new XMLOutputter(format);
RssFeed rssFeed = RssFeed.from(articles);
XMLOutputter xmlOutputter = XMLOutPutterBuildHelper.getXmlOutputter();
return xmlOutputter.outputString(rssFeed.getRssDoc());
}
}

View File

@@ -1,20 +1,18 @@
package myblog.blog.seo.application;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.article.application.port.incomming.SiteMapUseCase;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.seo.application.port.incomming.SiteMapUseCase;
import myblog.blog.article.domain.Article;
import myblog.blog.category.domain.Category;
import myblog.blog.seo.domain.SiteMap;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.category.domain.Category;
import myblog.blog.category.appliacation.CategoryService;
import org.jdom2.*;
import org.jdom2.output.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
@@ -22,68 +20,16 @@ import java.util.*;
@RequiredArgsConstructor
public class SiteMapService implements SiteMapUseCase {
static final String NAMESPACE = "http://www.sitemaps.org/schemas/sitemap/0.9";
static final String ROOT = "https://www.jiniaslog.co.kr";
static final String CATEGORYPRE = "/article/list?";
static final String CATEGORYPRO = "&page=1";
static final String ARTICLEPREV = "/article/view?articleId=";
private final ArticleUseCase articleUseCase;
private final CategoryService categoryService;
private final CategoryUseCase categoryUseCase;
@Override
@Cacheable(value = "seoCaching", key = "1")
public String getSiteMap(){
List<Article> articles = articleUseCase.getTotalArticle();
List<Category> allCategories = categoryService.getAllCategories();
Document doc = makeSiteMapDocument(articles, allCategories);
XMLOutputter xmlOutputter = getXmlOutputter();
return xmlOutputter.outputString(doc);
}
private Document makeSiteMapDocument(List<Article> articles, List<Category> allCategories) {
Document doc = new Document();
Element siteMap = new Element("urlset", NAMESPACE);
doc.setRootElement(siteMap);
Element main = createMainElement();
siteMap.addContent(main);
addCategoryUrlsToSiteMap(allCategories, siteMap);
addArticleUrlToSiteMap(articles, siteMap);
return doc;
}
private Element createMainElement() {
Element main = new Element("url",NAMESPACE);
main.addContent(new Element("loc",NAMESPACE).setText(ROOT));
main.addContent(new Element("lastmod",NAMESPACE).setText(
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd",Locale.ENGLISH))));
main.addContent(new Element("priority",NAMESPACE).setText("1.0"));
return main;
}
private void addArticleUrlToSiteMap(List<Article> articles, Element siteMap) {
for (Article article : articles) {
Element articleUrl = new Element("url",NAMESPACE);
articleUrl.addContent(new Element("loc",NAMESPACE)
.setText(ROOT + ARTICLEPREV + article.getId()));
siteMap.addContent(articleUrl);
}
}
private void addCategoryUrlsToSiteMap(List<Category> allCategories, Element siteMap) {
for (Category category : allCategories) {
Element categoryUrl = new Element("url",NAMESPACE);
categoryUrl.addContent(new Element("loc",NAMESPACE)
.setText(ROOT + CATEGORYPRE + "category="+category.getTitle()+"&tier="+category.getTier()+CATEGORYPRO));
siteMap.addContent(categoryUrl);
}
}
// 쓰레드 세잎한지 확신이 안듬 방어적으로 생각해서 객체 생성하고 캐싱으로 처리하자
private XMLOutputter getXmlOutputter() {
Format format = Format.getPrettyFormat();
format.setEncoding("UTF-8");
format.setLineSeparator("\r\n");
return new XMLOutputter(format);
List<Category> allCategories = categoryUseCase.getAllCategories();
SiteMap siteMap = SiteMap.from(articles, allCategories);
XMLOutputter xmlOutputter = XMLOutPutterBuildHelper.getXmlOutputter();
return xmlOutputter.outputString(siteMap.getSiteMapDoc());
}
}

View File

@@ -0,0 +1,13 @@
package myblog.blog.seo.application;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
public class XMLOutPutterBuildHelper {
static public XMLOutputter getXmlOutputter() {
Format format = Format.getPrettyFormat();
format.setEncoding("UTF-8");
format.setLineSeparator("\r\n");
return new XMLOutputter(format);
}
}

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.incomming;
package myblog.blog.seo.application.port.incomming;
public interface RssUseCase {
String getRssFeed();

View File

@@ -1,4 +1,4 @@
package myblog.blog.article.application.port.incomming;
package myblog.blog.seo.application.port.incomming;
public interface SiteMapUseCase {
String getSiteMap();

View File

@@ -0,0 +1,67 @@
package myblog.blog.seo.domain;
import lombok.Getter;
import myblog.blog.article.domain.Article;
import org.jdom2.*;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import static myblog.blog.shared.utils.MarkdownUtils.getHtmlRenderer;
import static myblog.blog.shared.utils.MarkdownUtils.getParser;
@Getter
public class RssFeed {
static private final String ITEM_ROOT = "https://www.jiniaslog.co.kr/article/view?articleId=";
private final Document rssDoc;
private RssFeed(Document rssDoc) {
this.rssDoc = rssDoc;
}
static public RssFeed from(List<Article> articles){
Document doc = new Document();
Element rss = buildRssRoot();
doc.setRootElement(rss);
Element channel = buildChannel();
rss.addContent(channel);
addArticleItemsToChannel(articles, channel, new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH));
return new RssFeed(doc);
}
static private Element buildRssRoot() {
Element rss = new Element("rss");
rss.setAttribute(new Attribute("version", "2.0"));
return rss;
}
static private Element buildChannel() {
Element channel = new Element("channel");
channel.addContent(new Element("title").addContent(new CDATA("Jinia's LOG")));
channel.addContent(new Element("link").setText("https://www.jiniaslog.co.kr"));
channel.addContent(new Element("description").addContent(new CDATA("비전공 출신, 개발자 지망생의 공부 내용을 기록하는 블로그입니다.")));
return channel;
}
static private void addArticleItemsToChannel(List<Article> articles, Element channel, SimpleDateFormat dateFormat) {
for (Article article : articles) {
Element item = createItemTo(article, dateFormat);
channel.addContent(item);
}
}
static private Element createItemTo(Article article, SimpleDateFormat dateFormat) {
Element item = new Element("item");
item
.addContent(new Element("title").addContent(new CDATA(article.getTitle())))
.addContent(new Element("link").setText(ITEM_ROOT + article.getId()))
.addContent(new Element("description").addContent(new CDATA(getHtmlRenderer().render(getParser().parse(article.getContent())))))
.addContent(new Element("pubDate").setText(dateFormat.format(Timestamp.valueOf(article.getCreatedDate()))))
.addContent(new Element("guid").setText(ITEM_ROOT + article.getId()));
return item;
}
}

View File

@@ -0,0 +1,64 @@
package myblog.blog.seo.domain;
import lombok.Getter;
import myblog.blog.article.domain.Article;
import myblog.blog.category.domain.Category;
import org.jdom2.Document;
import org.jdom2.Element;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
@Getter
public class SiteMap {
static private final String NAMESPACE = "http://www.sitemaps.org/schemas/sitemap/0.9";
static private final String ROOT = "https://www.jiniaslog.co.kr";
static private final String CATEGORYPRE = "/article/list?";
static private final String CATEGORYPRO = "&page=1";
static private final String ARTICLEPREV = "/article/view?articleId=";
private final Document siteMapDoc;
private SiteMap(Document siteMapDoc) {
this.siteMapDoc = siteMapDoc;
}
static public SiteMap from(List<Article> articles, List<Category> allCategories) {
Document doc = new Document();
Element siteMap = new Element("urlset", NAMESPACE);
doc.setRootElement(siteMap);
Element main = createMainElement();
siteMap.addContent(main);
addCategoryUrlsToSiteMap(allCategories, siteMap);
addArticleUrlToSiteMap(articles, siteMap);
return new SiteMap(doc);
}
static private Element createMainElement() {
Element main = new Element("url",NAMESPACE);
main.addContent(new Element("loc",NAMESPACE).setText(ROOT));
main.addContent(new Element("lastmod",NAMESPACE).setText(
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ENGLISH))));
main.addContent(new Element("priority",NAMESPACE).setText("1.0"));
return main;
}
static private void addArticleUrlToSiteMap(List<Article> articles, Element siteMap) {
for (Article article : articles) {
Element articleUrl = new Element("url",NAMESPACE);
articleUrl.addContent(new Element("loc",NAMESPACE)
.setText(ROOT + ARTICLEPREV + article.getId()));
siteMap.addContent(articleUrl);
}
}
static private void addCategoryUrlsToSiteMap(List<Category> allCategories, Element siteMap) {
for (Category category : allCategories) {
Element categoryUrl = new Element("url",NAMESPACE);
categoryUrl.addContent(new Element("loc",NAMESPACE)
.setText(ROOT + CATEGORYPRE + "category="+category.getTitle()+"&tier="+category.getTier()+CATEGORYPRO));
siteMap.addContent(categoryUrl);
}
}
}

View File

@@ -2,9 +2,9 @@ package myblog.blog.shared.queries;
import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.application.CommentService;
import myblog.blog.category.appliacation.port.incomming.response.CategoryViewForLayout;
import myblog.blog.comment.application.port.incomming.CommentUseCase;
import myblog.blog.comment.application.port.incomming.response.CommentDtoForLayout;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
@@ -15,14 +15,14 @@ import java.util.List;
public class LayoutRenderingQueries {
private final CategoryUseCase categoryUseCase;
private final CommentService commentService;
private final CommentUseCase commentUseCase;
/*
- 레이아웃에 필요한 모델 담기
*/
public void AddLayoutTo(Model model) {
CategoryViewForLayout categoryViewForLayout = categoryUseCase.getCategoryViewForLayout();
List<CommentDtoForLayout> comments = commentService.recentCommentList();
List<CommentDtoForLayout> comments = commentUseCase.recentCommentListForLayout();
model.addAttribute("category", categoryViewForLayout);
model.addAttribute("commentsList", comments);
}