🎨 修改rss/sitemap的渲染方式,新增html形式的sitemap

pull/89/head^2
ruibaby 2019-01-31 15:18:58 +08:00
parent 1bb6b72cb3
commit d68d7019f0
11 changed files with 310 additions and 163 deletions

View File

@ -34,7 +34,6 @@
<oh-my-email.version>0.0.4</oh-my-email.version>
<lombok.version>1.18.4</lombok.version>
<ehcache.version>3.6.3</ehcache.version>
<rome.version>1.0</rome.version>
<hutool-all.version>4.4.2</hutool-all.version>
<upyun-java-sdk.version>4.0.1</upyun-java-sdk.version>
<qiniu-java-sdk.version>7.2.18</qiniu-java-sdk.version>
@ -120,13 +119,6 @@
<version>${ehcache.version}</version>
</dependency>
<!-- rss -->
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>${rome.version}</version>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>

View File

@ -83,8 +83,7 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/templates/themes/")
.addResourceLocations("classpath:/robots.txt");
.addResourceLocations("classpath:/templates/themes/");
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:///" + System.getProperties().getProperty("user.home") + "/halo/upload/");
registry.addResourceHandler("/favicon.ico")

View File

@ -248,22 +248,6 @@ public interface PostService {
*/
Integer getCountByStatus(Integer status);
/**
* rss
*
* @param posts posts
* @return String
*/
String buildRss(List<Post> posts);
/**
* sitemap
*
* @param posts posts
* @return String
*/
String buildSiteMap(List<Post> posts);
/**
*
*

View File

@ -508,36 +508,6 @@ public class PostServiceImpl implements PostService {
return postRepository.countAllByPostStatusAndPostType(status, PostTypeEnum.POST_TYPE_POST.getDesc());
}
/**
* rss
*
* @param posts posts
*
* @return String
*/
@Override
public String buildRss(List<Post> posts) {
String rss = "";
try {
rss = HaloUtils.getRss(posts);
} catch (Exception e) {
e.printStackTrace();
}
return rss;
}
/**
* sitemap
*
* @param posts posts
*
* @return String
*/
@Override
public String buildSiteMap(List<Post> posts) {
return HaloUtils.getSiteMap(posts);
}
/**
*
*

View File

@ -1,19 +1,11 @@
package cc.ryanc.halo.utils;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.BackupDto;
import cc.ryanc.halo.model.dto.Theme;
import cc.ryanc.halo.model.enums.BlogPropertiesEnum;
import cc.ryanc.halo.model.enums.CommonParamsEnum;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.StrUtil;
import com.sun.syndication.feed.rss.Channel;
import com.sun.syndication.feed.rss.Content;
import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.WireFeedOutput;
import io.github.biezhi.ome.OhMyEmail;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
@ -32,8 +24,6 @@ import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import static cc.ryanc.halo.model.dto.HaloConst.OPTIONS;
/**
* <pre>
*
@ -285,84 +275,6 @@ public class HaloUtils {
}
}
/**
* rss
*
* @param posts posts
*
* @return String
*
* @throws FeedException FeedException
*/
public static String getRss(List<Post> posts) throws FeedException {
Assert.notEmpty(posts, "posts must not be empty");
final Channel channel = new Channel("rss_2.0");
if (null == OPTIONS.get(BlogPropertiesEnum.BLOG_TITLE.getProp())) {
channel.setTitle("");
} else {
channel.setTitle(OPTIONS.get(BlogPropertiesEnum.BLOG_TITLE.getProp()));
}
if (null == OPTIONS.get(BlogPropertiesEnum.BLOG_URL.getProp())) {
channel.setLink("");
} else {
channel.setLink(OPTIONS.get(BlogPropertiesEnum.BLOG_URL.getProp()));
}
if (null == OPTIONS.get(BlogPropertiesEnum.SEO_DESC.getProp())) {
channel.setDescription("");
} else {
channel.setDescription(OPTIONS.get(BlogPropertiesEnum.SEO_DESC.getProp()));
}
channel.setLanguage("zh-CN");
final List<Item> items = new ArrayList<>();
for (Post post : posts) {
final Item item = new Item();
item.setTitle(post.getPostTitle());
final Content content = new Content();
String value = post.getPostContent();
final char[] xmlChar = value.toCharArray();
for (int i = 0; i < xmlChar.length; ++i) {
if (xmlChar[i] > 0xFFFD) {
xmlChar[i] = ' ';
} else if (xmlChar[i] < 0x20 && xmlChar[i] != 't' & xmlChar[i] != 'n' & xmlChar[i] != 'r') {
xmlChar[i] = ' ';
}
}
value = new String(xmlChar);
content.setValue(value);
item.setContent(content);
item.setLink(OPTIONS.get(BlogPropertiesEnum.BLOG_URL.getProp()) + "/archives/" + post.getPostUrl());
item.setPubDate(post.getPostDate());
items.add(item);
}
channel.setItems(items);
final WireFeedOutput out = new WireFeedOutput();
return out.outputString(channel);
}
/**
* sitemap
*
* @param posts posts
*
* @return String
*/
public static String getSiteMap(List<Post> posts) {
Assert.notEmpty(posts, "post mut not be empty");
final StrBuilder head = new StrBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
final StrBuilder urlBody = new StrBuilder();
final String urlPath = OPTIONS.get(BlogPropertiesEnum.BLOG_URL.getProp()) + "/archives/";
for (Post post : posts) {
urlBody.append("<url><loc>");
urlBody.append(urlPath);
urlBody.append(post.getPostUrl());
urlBody.append("</loc><lastmod>");
urlBody.append(DateUtil.format(post.getPostDate(), "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
urlBody.append("</lastmod></url>");
}
return head.append(urlBody).append("</urlset>").toString();
}
/**
*
*

View File

@ -1,23 +1,28 @@
package cc.ryanc.halo.web.controller.front;
import cc.ryanc.halo.model.domain.Post;
import cc.ryanc.halo.model.dto.HaloConst;
import cc.ryanc.halo.model.enums.BlogPropertiesEnum;
import cc.ryanc.halo.model.enums.PostTypeEnum;
import cc.ryanc.halo.service.PostService;
import cn.hutool.core.util.StrUtil;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.IOException;
import java.util.List;
import static cc.ryanc.halo.model.dto.HaloConst.*;
import static cc.ryanc.halo.model.dto.HaloConst.OPTIONS;
/**
* <pre>
@ -33,51 +38,102 @@ public class FrontOthersController {
@Autowired
private PostService postService;
@Autowired
private FreeMarkerConfigurer freeMarker;
/**
* rss
*
* @return rss
* @param model model
*
* @return String
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = {"feed", "feed.xml", "atom", "atom.xml"}, produces = "application/xml;charset=UTF-8")
@ResponseBody
public String feed() {
public String feed(Model model) throws IOException, TemplateException {
String rssPosts = OPTIONS.get(BlogPropertiesEnum.RSS_POSTS.getProp());
if (StrUtil.isBlank(rssPosts)) {
rssPosts = "20";
}
//获取文章列表并根据时间排序
final Sort sort = new Sort(Sort.Direction.DESC, "postDate");
final Pageable pageable = PageRequest.of(0, Integer.parseInt(rssPosts), sort);
final Page<Post> postsPage = postService.findPostByStatus(0, PostTypeEnum.POST_TYPE_POST.getDesc(), pageable).map(post -> {
if(StrUtil.isNotEmpty(post.getPostPassword())){
if (StrUtil.isNotEmpty(post.getPostPassword())) {
post.setPostContent("该文章为加密文章");
post.setPostSummary("该文章为加密文章");
}
return post;
});
final List<Post> posts = postsPage.getContent();
return postService.buildRss(posts);
model.addAttribute("posts", posts);
final Template template = freeMarker.getConfiguration().getTemplate("common/web/rss.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* sitemap
* XML
*
* @return sitemap
* @param model model
*
* @return String
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = {"sitemap", "sitemap.xml"}, produces = "application/xml;charset=UTF-8")
@ResponseBody
public String siteMap() {
//获取文章列表并根据时间排序
final Sort sort = new Sort(Sort.Direction.DESC, "postDate");
final Pageable pageable = PageRequest.of(0, 999, sort);
final Page<Post> postsPage = postService.findPostByStatus(0, PostTypeEnum.POST_TYPE_POST.getDesc(), pageable).map(post -> {
if(StrUtil.isNotEmpty(post.getPostPassword())){
public String sitemapXml(Model model) throws IOException, TemplateException {
final Page<Post> postsPage = postService.findPostByStatus(0, PostTypeEnum.POST_TYPE_POST.getDesc(), null).map(post -> {
if (StrUtil.isNotEmpty(post.getPostPassword())) {
post.setPostContent("该文章为加密文章");
post.setPostSummary("该文章为加密文章");
}
return post;
});
final List<Post> posts = postsPage.getContent();
return postService.buildSiteMap(posts);
model.addAttribute("posts", posts);
final Template template = freeMarker.getConfiguration().getTemplate("common/web/sitemap_xml.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
/**
* HTML
*
* @param model model
*
* @return String
*/
@GetMapping(value = "sitemap.html", produces = {"text/html"})
public String sitemapHtml(Model model) {
final Page<Post> postsPage = postService.findPostByStatus(0, PostTypeEnum.POST_TYPE_POST.getDesc(), null).map(post -> {
if (StrUtil.isNotEmpty(post.getPostPassword())) {
post.setPostContent("该文章为加密文章");
post.setPostSummary("该文章为加密文章");
}
return post;
});
final List<Post> posts = postsPage.getContent();
model.addAttribute("posts", posts);
return "common/web/sitemap_html";
}
/**
* robots
*
* @param model model
*
* @return String
*
* @throws IOException IOException
* @throws TemplateException TemplateException
*/
@GetMapping(value = "robots.txt", produces = {"text/plain"})
@ResponseBody
public String robots(Model model) throws IOException, TemplateException {
final Template template = freeMarker.getConfiguration().getTemplate("common/web/robots.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
}
}

View File

@ -1,3 +0,0 @@
User-agent: *
Disallow: /admin/
Sitemap: /sitemap.xml

View File

@ -0,0 +1,4 @@
User-agent: *
Disallow: /admin/
Sitemap: ${options.blog_url!}/sitemap.xml
Sitemap: ${options.blog_url!}/sitemap.html

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>${options.blog_title!}</title>
<link>${options.blog_url!}</link>
<#if user.userDesc??>
<description>${user.userDesc!}</description>
</#if>
<language>zh-CN</language>
<#if posts?? && posts?size gt 0>
<#list posts as post>
<item>
<title>${post.postTitle!}</title>
<link>${options.blog_url}/archives/${post.postUrl!}</link>
<content:encoded>${post.postContent!}</content:encoded>
<pubDate>${post.postDate}</pubDate>
</item>
</#list>
</#if>
</channel>
</rss>

View File

@ -0,0 +1,201 @@
<#--
see https://gitee.com/yadong.zhang/DBlog/blob/master/blog-web/src/main/java/com/zyd/blog/controller/RestWebSiteController.java
-->
<#compress >
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>${options.blog_title!} 网站地图</title>
<meta name="robots" content="index,follow"/>
<style type="text/css">
body {
color: #000000;
background: #ffffff;
margin: 20px;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 14px;
}
#myTable {
list-style: none;
margin: 10px 0px 10px 0px;
padding: 0px;
width: 100%;
min-width: 804px;
}
#myTable li {
list-style-type: none;
width: 100%;
min-width: 404px;
height: 20px;
line-height: 20px;
padding: 2px 0;
}
.pull-left{
float: left!important;
}
.pull-right{
float: right!important;
}
#myTable li .T1-h {
font-weight: bold;
min-width: 300px;
}
#myTable li .T2-h {
width: 200px;
font-weight: bold;
}
#myTable li .T3-h {
width: 200px;
font-weight: bold;
}
#myTable li .T4-h {
width: 100px;
font-weight: bold;
}
#myTable li .T1 {
min-width: 300px;
}
#myTable li .T2 {
width: 200px;
}
#myTable li .T3 {
width: 200px;
}
#myTable li .T4 {
width: 100px;
}
#footer {
padding: 2px;
margin: 0px;
font-size: 8pt;
color: gray;
min-width: 900px;
}
#footer a {
color: gray;
}
.myClear {
clear: both;
}
#nav, #content, #footer {
padding: 8px;
border: 1px solid #EEEEEE;
clear: both;
width: 95%;
margin: auto;
margin-top: 10px;
}
/* 窗口缩小到768px以下时 */
@media (max-width: 768px) {
.T2-h, .T3-h, .T4-h, .T2, .T3, .T4 {
display: none;
}
#myTable, #footer, #myTable li .T1, #myTable li, #myTable li .T1-h, #myTable li .T1 {
max-width: 100%;
min-width: auto;
white-space: nowrap;
word-wrap: normal;
text-overflow: ellipsis;
overflow: hidden;
}
}
/* 窗口放大到768px以上时 */
@media (min-width: 768px) {
}
</style>
</head>
<body>
<h2 style="text-align: center; margin-top: 20px">${options.blog_title!} 网站地图 </h2>
<div id="nav"><a href="${options.blog_url!}"><strong>${options.blog_title!}</strong></a> &raquo; <a href="${options.blog_url!}/sitemap.html">站点地图</a></div>
<div id="content">
<h3>最新文章</h3>
<ul id="myTable">
<li>
<div class="T1-h pull-left">URL</div>
<div class="T2-h pull-right">Last Change</div>
<div class="T3-h pull-right">Change Frequency</div>
<div class="T4-h pull-right">Priority</div>
</li>
<div class="myClear"></div>
<li>
<div class="T1 pull-left"><a href="${options.blog_url!}" title="${options.blog_title!}">${options.blog_title!}</a></div>
<div class="T2 pull-right">${options.blog_start!}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">1</div>
</li>
<div class="myClear"></div>
<#if posts?? && posts?size gt 0>
<#list posts as post>
<li>
<div class="T1 pull-left"><a href="${options.blog_url!}/archives/${post.postUrl!}" title="${post.postTitle!}">${post.postTitle!} | ${options.blog_title!}</a></div>
<div class="T2 pull-right">${post.postDate?string('yyyy-MM-dd')}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
</li>
<div class="myClear"></div>
</#list>
</#if>
</ul>
</div>
<div id="content">
<h3>分类目录</h3>
<ul id="myTable">
<@commonTag method="categories">
<#if categories?? && categories?size gt 0>
<#list categories as cate>
<li>
<div class="T1 pull-left"><a href="${options.blog_url}/categories/${cate.cateUrl!}" title="前端编程">${cate.cateName} | ${options.blog_title!}</a></div>
<div class="T2 pull-right">${options.blog_start!}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
</li>
<div class="myClear"></div>
</#list>
</#if>
</@commonTag>
</ul>
</div>
<div id="content">
<h3>标签目录</h3>
<ul id="myTable">
<@commonTag method="tags">
<#if tags?? && tags?size gt 0>
<#list tags as tag>
<li>
<div class="T1 pull-left"><a href="${options.blog_url}/tags/${tag.tagUrl!}" title="前端编程">${tag.tagName} | ${options.blog_title!}</a></div>
<div class="T2 pull-right">${options.blog_start!}</div>
<div class="T3 pull-right">daily</div>
<div class="T4 pull-right">0.6</div>
</li>
<div class="myClear"></div>
</#list>
</#if>
</@commonTag>
</ul>
</div>
<div id="footer">
该文件由<a href="${options.blog_url!}" title="${options.blog_title!}">${options.blog_title!}</a>网站自动生成。
</div>
</body>
</html>
</#compress>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<#if posts?? && posts?size gt 0>
<#list posts as post>
<url>
<loc>${options.blog_url!}/archives/${post.postUrl!}</loc>
<lastmod>${post.postDate?iso_local}</lastmod>
</url>
</#list>
</#if>
</urlset>