feat(SEO): 自动生成和更新网站地图 sitemap.xml

This commit is contained in:
xiongxiaoyang committed 2026-10-10 21:41:18 +08:00
1 parent f10ffa664e
commit cfc5d894ab
3 files changed
+135 -1

No files matched your search

@@ -68,4 +68,10 @@ public interface CacheKey {
* AI生成图片
* */
String AI_GEN_PIC = "aiGenPic";
/**
* 网站根目录地址
* */
String WEB_SITE_ROOT_URL = "websiteRootUrl";
}
@@ -2,6 +2,8 @@ package com.java2nb.novel.controller.page;
import com.java2nb.novel.controller.BaseController;
import com.java2nb.novel.core.bean.UserDetails;
import com.java2nb.novel.core.cache.CacheKey;
import com.java2nb.novel.core.cache.CacheService;
import com.java2nb.novel.core.utils.ThreadLocalUtil;
import com.java2nb.novel.entity.*;
import com.java2nb.novel.service.*;
@@ -43,6 +45,8 @@ public class PageController extends BaseController {
private final Map<String, BookContentService> bookContentServiceMap;
private final CacheService cacheService;
@RequestMapping("{url}.html")
public String module(@PathVariable("url") String url) {
return url;
@@ -80,7 +84,10 @@ public class PageController extends BaseController {
*/
@SneakyThrows
@RequestMapping(path = {"/", "/index", "/index.html"})
public String index(Model model) {
public String index(HttpServletRequest request, Model model) {
// 将网站根目录地址保存起来
String rootUrl = getRootUrl(request);
cacheService.set(CacheKey.WEB_SITE_ROOT_URL, rootUrl);
//加载小说首页小说基本信息线程
CompletableFuture<Map<String, List<BookSettingVO>>> bookCompletableFuture = CompletableFuture.supplyAsync(
bookService::listBookSettingVO, threadPoolExecutor);
@@ -359,5 +366,21 @@ public class PageController extends BaseController {
return "author/register";
}
private String getRootUrl(HttpServletRequest request) {
String scheme = request.getScheme(); // http 或 https
String serverName = request.getServerName(); // ibiqugu.net
int serverPort = request.getServerPort(); // 80 / 443 / 8080
StringBuilder root = new StringBuilder();
root.append(scheme).append("://").append(serverName);
if (("http".equals(scheme) && serverPort != 80)
|| ("https".equals(scheme) && serverPort != 443)) {
root.append(":").append(serverPort);
}
return root.toString();
}
}
@@ -0,0 +1,105 @@
package com.java2nb.novel.core.schedule;
import com.java2nb.novel.core.cache.CacheKey;
import com.java2nb.novel.core.cache.CacheService;
import com.java2nb.novel.core.utils.DateUtil;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.mapper.BookMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.Date;
import java.util.List;
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.*;
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.book;
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.lastIndexUpdateTime;
import static org.mybatis.dynamic.sql.SqlBuilder.isGreaterThan;
import static org.mybatis.dynamic.sql.SqlBuilder.isLessThan;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;
/**
* 生产环境自动生成网站地图
*
* @author cd
*/
@Profile("prod")
@Service
@RequiredArgsConstructor
@Slf4j
public class SitemapGenSchedule {
@Value("${spring.web.resources.static-locations}")
private String webRootDir;
private static final String WEB_ROOT_DIR_PREFIX = "file:";
private final BookMapper bookMapper;
private final CacheService cacheService;
/**
* 每天自动更新 sitemap.xml 文件
*/
@Scheduled(cron = "0 0 2 * * ?")
public void statistics() {
if (webRootDir.startsWith(WEB_ROOT_DIR_PREFIX)) {
webRootDir = webRootDir.substring(WEB_ROOT_DIR_PREFIX.length());
String webRootUrl = cacheService.get(CacheKey.WEB_SITE_ROOT_URL);
if (StringUtils.isBlank(webRootUrl)) {
return;
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(webRootDir + "sitemap.xml"))) {
// 写入文件头
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
writer.write("<urlset>\n");
Date maxCreateTime = new Date();
List<Book> books;
do {
SelectStatementProvider selectStatement = select(id, createTime, lastIndexUpdateTime)
.from(book)
.where(createTime, isLessThan(maxCreateTime))
.and(wordCount, isGreaterThan(0))
.orderBy(createTime.descending())
.limit(100)
.build()
.render(RenderingStrategies.MYBATIS3);
books = bookMapper.selectMany(selectStatement);
for (Book book : books) {
maxCreateTime = book.getCreateTime();
if (book.getLastIndexUpdateTime() == null) {
continue;
}
writer.write("<url>\n");
writer.write(" <loc>" + webRootUrl + "/book/" + (book.getId()) + ".html" + "</loc>\n");
writer.write(
" <lastmod>" + DateUtil.formatDate(book.getLastIndexUpdateTime(), DateUtil.DATE_PATTERN)
+ "</lastmod>\n");
writer.write(" <changefreq>daily</changefreq>\n");
writer.write(" <priority>0.8</priority>\n");
writer.write("</url>\n");
}
} while (!books.isEmpty());
// 写入文件尾
writer.write("</urlset>\n");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
}