diff --git a/novel-common/src/main/java/com/java2nb/novel/core/cache/CacheKey.java b/novel-common/src/main/java/com/java2nb/novel/core/cache/CacheKey.java index 457b50d..f856cfb 100644 --- a/novel-common/src/main/java/com/java2nb/novel/core/cache/CacheKey.java +++ b/novel-common/src/main/java/com/java2nb/novel/core/cache/CacheKey.java @@ -68,4 +68,10 @@ public interface CacheKey { * AI生成图片 * */ String AI_GEN_PIC = "aiGenPic"; + + /** + * 网站根目录地址 + * */ + String WEB_SITE_ROOT_URL = "websiteRootUrl"; + } diff --git a/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java b/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java index 26584c0..3c2eeba 100644 --- a/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java +++ b/novel-front/src/main/java/com/java2nb/novel/controller/page/PageController.java @@ -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 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>> 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(); + } + } diff --git a/novel-front/src/main/java/com/java2nb/novel/core/schedule/SitemapGenSchedule.java b/novel-front/src/main/java/com/java2nb/novel/core/schedule/SitemapGenSchedule.java new file mode 100644 index 0000000..cb2cfdc --- /dev/null +++ b/novel-front/src/main/java/com/java2nb/novel/core/schedule/SitemapGenSchedule.java @@ -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("\n"); + writer.write("\n"); + + Date maxCreateTime = new Date(); + List 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("\n"); + writer.write(" " + webRootUrl + "/book/" + (book.getId()) + ".html" + "\n"); + writer.write( + " " + DateUtil.formatDate(book.getLastIndexUpdateTime(), DateUtil.DATE_PATTERN) + + "\n"); + writer.write(" daily\n"); + writer.write(" 0.8\n"); + writer.write("\n"); + } + } while (!books.isEmpty()); + + // 写入文件尾 + writer.write("\n"); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + + } + +}