Rss feed 서비스 리팩토링; 의존성 분리, parser renderer static 유틸 클래스로 변경, facade 패턴 사용

This commit is contained in:
jinia91
2022-03-01 19:02:55 +09:00
parent e94400c41e
commit d506b8c393
6 changed files with 138 additions and 67 deletions

View File

@@ -75,6 +75,7 @@ public class CacheConfig {
- 4. seo 캐시
- key 0 : rss
- key 1 : sitemap
- key 2 :
*/
CacheConfiguration rssConfiguration = new CacheConfiguration()
.eternal(false)

View File

@@ -1,17 +1,22 @@
package myblog.blog.seo.controller;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.article.service.ArticleService;
import myblog.blog.seo.service.RssService;
import myblog.blog.seo.service.SeoFacadeService;
import myblog.blog.seo.service.SiteMapService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class SEOController {
private final RssService rssService;
private final SeoFacadeService seoFacadeService;
private final SiteMapService siteMapService;
/*
@@ -21,7 +26,7 @@ public class SEOController {
*/
@GetMapping(value = "/rss",produces = "application/xml;charset=utf-8")
public @ResponseBody String rssFeed() {
return rssService.makeRssFeed();
return seoFacadeService.getRssFeed();
}
/*

View File

@@ -2,16 +2,8 @@ package myblog.blog.seo.service;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.article.service.ArticleService;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jdom2.Attribute;
import org.jdom2.CDATA;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.springframework.cache.annotation.Cacheable;
import org.jdom2.*;
import org.jdom2.output.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -19,50 +11,43 @@ import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import static myblog.blog.utils.MarkdownUtils.*;
/*
- rss 서비스 로직
1. jdom2 사용하여 xml파일 작성
2. 모든 게시물 item 변환후 xml에 삽입
3. 발행
*/
@Service
@Transactional
@RequiredArgsConstructor
public class RssService {
private final HtmlRenderer htmlRenderer;
private final Parser parser;
private final ArticleService articleService;
static final String ITEM_ROOT = "https://www.jiniaslog.co.kr/article/view?articleId=";
/*
- rss 빌드 서비스 로직
1. 모든 게시물 조회
2. jdom2 사용하여 xml파일 작성
3. 모든 게시물 item 변환후 xml에 삽입
4. 발행
*/
@Cacheable(value = "seoCaching", key = "0")
public String makeRssFeed(){
List<Article> articles = articleService.getTotalArticle();
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
// rss 레이아웃 루트 작성
Element rss = new Element("rss");
rss.setAttribute(new Attribute("version","2.0"));
// 채널 작성 밑 루트에 삽입
Element channel = buildChannel();
rss.addContent(channel);
// 채널에 아이템 삽입
addItemToChannel(articles, dateFormat, channel);
// 포매팅해서 발행
Document doc = new Document();
doc.setRootElement(rss);
public String getRssFeed(List<Article> articles) {
Document doc = makeRssFeedDocument(articles);
XMLOutputter xmlOutputter = getXmlOutputter();
return xmlOutputter.outputString(doc);
}
/*
- 채널 작성 로직
*/
private Document makeRssFeedDocument(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")));
@@ -71,31 +56,28 @@ public class RssService {
return channel;
}
/*
- xml 발행기 생성
*/
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);
}
/*
- item 작성 , 채널에 추가
*/
private void addItemToChannel(List<Article> articles, SimpleDateFormat dateFormat, Element channel) {
for (Article article : articles) {
Element item = new Element("item");
item.addContent(new Element("title").addContent(new CDATA(article.getTitle())));
item.addContent(new Element("link").setText("https://www.jiniaslog.co.kr/article/view?articleId=" + article.getId()));
item.addContent(new Element("description").addContent(new CDATA(htmlRenderer.render(parser.parse(article.getContent())))));
item.addContent(new Element("pubDate").setText(dateFormat.format(Timestamp.valueOf(article.getCreatedDate()))));
item.addContent(new Element("guid").setText("https://www.jiniaslog.co.kr/article/view?articleId=" + article.getId()));
channel.addContent(item);
}
}
}

View File

@@ -0,0 +1,25 @@
package myblog.blog.seo.service;
import lombok.RequiredArgsConstructor;
import myblog.blog.article.domain.Article;
import myblog.blog.article.service.ArticleService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class SeoFacadeService {
private final RssService rssService;
private final SiteMapService siteMapService;
private final ArticleService articleService;
@Cacheable(value = "seoCaching", key = "0")
public String getRssFeed(){
List<Article> articles = articleService.getTotalArticle();
return rssService.getRssFeed(articles);
}
}

View File

@@ -0,0 +1,30 @@
package myblog.blog.utils;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import java.util.List;
public class MarkdownUtils {
private static final HtmlRenderer htmlRenderer;
private static final Parser parser;
static {
htmlRenderer = HtmlRenderer.builder()
.extensions(List.of(TablesExtension.create()))
.build();
parser = Parser.builder()
.extensions(List.of(TablesExtension.create()))
.build();
}
public static HtmlRenderer getHtmlRenderer() {
return htmlRenderer;
}
public static Parser getParser() {
return parser;
}
}

View File

@@ -0,0 +1,28 @@
package myblog.blog.seo.service;
import myblog.blog.article.service.ArticleService;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
class RssServiceTest {
@InjectMocks
RssService rssService;
@Mock
ArticleService articleService;
@Test
public void RssFeed생성성공테스트() throws Exception {
// given
// when
// then
}
}