From 695fac5e3ac8fbc73a83591cac09eb242c79b37f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=B9=8F?= Date: Tue, 25 Aug 2026 11:08:59 +0800 Subject: [PATCH] feat: optimize source project pages and SEO --- .../src/main/resources/application.yml | 4 + .../blog/controller/CmsBlogController.java | 26 +- .../com/ruoyi/cms/blog/domain/CmsBlogTag.java | 10 + .../ruoyi/cms/blog/domain/CmsBlogType.java | 10 + .../ruoyi/cms/blog/mapper/CmsBlogMapper.java | 15 + .../cms/blog/mapper/CmsBlogTagMapper.java | 4 + .../cms/blog/mapper/CmsBlogTypeMapper.java | 4 + .../cms/blog/service/ICmsBlogService.java | 13 + .../blog/service/impl/CmsBlogServiceImpl.java | 123 +- .../sitemap/controller/SitemapController.java | 36 + .../ruoyi/cms/sitemap/domain/SitemapUrl.java | 45 + .../cms/sitemap/mapper/SitemapMapper.java | 14 + .../cms/sitemap/service/SitemapService.java | 128 ++ .../tag/service/impl/CmsTagServiceImpl.java | 7 +- .../type/service/impl/CmsTypeServiceImpl.java | 7 +- .../resources/mapper/cms/CmsBlogMapper.xml | 44 +- .../resources/mapper/cms/CmsBlogTagMapper.xml | 19 +- .../mapper/cms/CmsBlogTypeMapper.xml | 19 +- .../resources/mapper/cms/CmsTagMapper.xml | 15 +- .../resources/mapper/cms/CmsTypeMapper.xml | 18 +- .../resources/mapper/cms/SitemapMapper.xml | 46 + .../framework/config/SecurityConfig.java | 2 + ruoyi-ui/.env.production | 3 + ruoyi-ui/public/index.html | 3 +- ruoyi-ui/public/robots.txt | 9 +- ruoyi-ui/src/App.vue | 27 +- ruoyi-ui/src/api/cms/blog.js | 8 +- ruoyi-ui/src/permission.js | 2 +- ruoyi-ui/src/router/index.js | 85 +- ruoyi-ui/src/utils/seo.js | 128 ++ ruoyi-ui/src/views/cms/Footer.vue | 70 +- ruoyi-ui/src/views/cms/NavBar.vue | 779 ++++--- ruoyi-ui/src/views/cms/cmslogin.vue | 2 +- ruoyi-ui/src/views/cms/comment/index.vue | 3 +- ruoyi-ui/src/views/cms/components/cmsBlog.vue | 1320 ++++++++++-- .../views/cms/components/cmsCategories.vue | 342 +++ .../src/views/cms/components/cmsDoucument.vue | 52 +- .../src/views/cms/components/cmsEssay.vue | 613 +++++- .../src/views/cms/components/cmsIndex.vue | 1859 +++++++++++++++-- .../cms/components/comment/Ipcomment.vue | 4 +- ruoyi-ui/src/views/cms/index.vue | 119 +- ruoyi-ui/src/views/cms/main.vue | 26 +- 42 files changed, 5038 insertions(+), 1025 deletions(-) create mode 100644 ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/controller/SitemapController.java create mode 100644 ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/domain/SitemapUrl.java create mode 100644 ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/mapper/SitemapMapper.java create mode 100644 ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/service/SitemapService.java create mode 100644 ruoyi-cms/src/main/resources/mapper/cms/SitemapMapper.xml create mode 100644 ruoyi-ui/src/utils/seo.js create mode 100644 ruoyi-ui/src/views/cms/components/cmsCategories.vue diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index b3c2838..c5eab8e 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -114,6 +114,10 @@ swagger: # 请求前缀 pathMapping: /dev-api +# Search engine canonical URL configuration +seo: + site-url: https://www.yidaima.cn + # 防止XSS攻击 xss: # 过滤开关 diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/controller/CmsBlogController.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/controller/CmsBlogController.java index ba2b9ae..9e8ecc0 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/controller/CmsBlogController.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/controller/CmsBlogController.java @@ -14,6 +14,7 @@ import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.web.service.SysPermissionService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -66,7 +67,7 @@ public class CmsBlogController extends BaseController startPage(); //状态为发布 cmsBlog.setStatus("1"); - List list = cmsBlogService.selectCmsBlogList(cmsBlog); + List list = cmsBlogService.selectCmsBlogCardList(cmsBlog); return getDataTable(list); } @@ -74,7 +75,7 @@ public class CmsBlogController extends BaseController * 首页获取文章详细信息 */ @GetMapping(value = { "/cms/detail/", "/cms/detail/{id}" }) - public AjaxResult getInfoDetail(@PathVariable(value = "id", required = false) Long id) + public ResponseEntity getInfoDetail(@PathVariable(value = "id", required = false) Long id) { AjaxResult ajax = AjaxResult.success(); CmsType cmsType = new CmsType(); @@ -83,9 +84,14 @@ public class CmsBlogController extends BaseController ajax.put("tags", cmsTagService.selectCmsTagList(cmsTag)); if (StringUtils.isNotNull(id)) { - ajax.put(AjaxResult.DATA_TAG, cmsBlogService.selectCmsBlogById(id)); + CmsBlog blog = cmsBlogService.selectPublishedCmsBlogById(id); + if (blog == null) + { + return ResponseEntity.notFound().build(); + } + ajax.put(AjaxResult.DATA_TAG, blog); } - return ajax; + return ResponseEntity.ok(ajax); } /** @@ -127,14 +133,18 @@ public class CmsBlogController extends BaseController * 首页增加阅读量 */ @GetMapping("/cms/addBlogViews/{id}") - public AjaxResult addBlogViews(@PathVariable(value = "id", required = false) Long id) + public ResponseEntity addBlogViews(@PathVariable(value = "id", required = false) Long id) { - CmsBlog cmsBlog = cmsBlogService.selectCmsBlogById(id); + CmsBlog cmsBlog = cmsBlogService.selectPublishedCmsBlogById(id); + if (cmsBlog == null) + { + return ResponseEntity.notFound().build(); + } Long views = cmsBlog.getViews(); - views+=Long.parseLong("1"); + views = views == null ? 1L : views + 1L; cmsBlog.setViews(views); cmsBlogService.updateCmsBlog(cmsBlog); - return AjaxResult.success(id); + return ResponseEntity.ok(AjaxResult.success(id)); } /** diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogTag.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogTag.java index b47c7bf..aaa0de6 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogTag.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogTag.java @@ -8,6 +8,7 @@ package com.ruoyi.cms.blog.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.cms.tag.domain.CmsTag; /** * @Author: WangNing @@ -18,6 +19,7 @@ public class CmsBlogTag { private Long tagId; private Long blogId; private Long[] tagIds; + private CmsTag tag; public Long getTagId() { return tagId; @@ -43,6 +45,14 @@ public class CmsBlogTag { this.tagIds = tagIds; } + public CmsTag getTag() { + return tag; + } + + public void setTag(CmsTag tag) { + this.tag = tag; + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogType.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogType.java index 9a10404..d4bd01b 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogType.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/domain/CmsBlogType.java @@ -8,6 +8,7 @@ package com.ruoyi.cms.blog.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.cms.type.domain.CmsType; /** * @Author: WangNing @@ -18,6 +19,7 @@ public class CmsBlogType { private Long typeId; private Long blogId; private Long[] typeIds; + private CmsType type; public Long getTypeId() { return typeId; @@ -43,6 +45,14 @@ public class CmsBlogType { this.typeIds = typeIds; } + public CmsType getType() { + return type; + } + + public void setType(CmsType type) { + this.type = type; + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogMapper.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogMapper.java index 1dd4ef2..60f6219 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogMapper.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogMapper.java @@ -20,6 +20,11 @@ public interface CmsBlogMapper */ public CmsBlog selectCmsBlogById(Long id); + /** + * Query a public detail page. Drafts must behave like missing pages. + */ + public CmsBlog selectPublishedCmsBlogById(Long id); + /** * 查询文章管理列表 * @@ -28,6 +33,16 @@ public interface CmsBlogMapper */ public List selectCmsBlogList(CmsBlog cmsBlog); + /** + * 查询首页卡片所需的轻量字段 + */ + public List selectCmsBlogCardList(CmsBlog cmsBlog); + + /** + * 批量读取缺少封面的文章正文,用于提取首图 + */ + public List selectCmsBlogContentByIds(List ids); + public List selectCmsBlogListBetweenCreateTime(@Param("title") String title,@Param("type") String type,@Param("top") String top,@Param("status") String status,@Param("createTimeBegin") String createTimeBegin,@Param("createTimeEnd") String createTimeEnd,@Param("createBy") String createBy); /** diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTagMapper.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTagMapper.java index 03d3bc2..8da6268 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTagMapper.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTagMapper.java @@ -27,6 +27,10 @@ public interface CmsBlogTagMapper { * 查询文件列表 */ public List selectBlogTagList(Long blogId); + /** + * 批量查询博客及其标签,避免列表页逐条查询 + */ + public List selectBlogTagsByBlogIds(List blogIds); /** * 通过tagId删除blog文件关联 */ diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTypeMapper.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTypeMapper.java index e804a19..ad6580e 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTypeMapper.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/mapper/CmsBlogTypeMapper.java @@ -27,6 +27,10 @@ public interface CmsBlogTypeMapper { * 查询博客列表 */ public List selectBlogTypeList(Long blogId); + /** + * 批量查询博客及其分类,避免列表页逐条查询 + */ + public List selectBlogTypesByBlogIds(List blogIds); /** * 通过typeId删除blog文件关联 */ diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/ICmsBlogService.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/ICmsBlogService.java index 08894b1..ad09904 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/ICmsBlogService.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/ICmsBlogService.java @@ -19,6 +19,14 @@ public interface ICmsBlogService */ public CmsBlog selectCmsBlogById(Long id); + /** + * Query an indexable public detail page. + * + * @param id content primary key + * @return published content, or null when it must not be indexed + */ + public CmsBlog selectPublishedCmsBlogById(Long id); + /** * 查询文章管理列表 * @@ -27,6 +35,11 @@ public interface ICmsBlogService */ public List selectCmsBlogList(CmsBlog cmsBlog); + /** + * 查询首页项目卡片列表 + */ + public List selectCmsBlogCardList(CmsBlog cmsBlog); + /** * 查询推荐文章列表 */ diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/impl/CmsBlogServiceImpl.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/impl/CmsBlogServiceImpl.java index e4adaa0..052d2e2 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/impl/CmsBlogServiceImpl.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/blog/service/impl/CmsBlogServiceImpl.java @@ -1,7 +1,9 @@ package com.ruoyi.cms.blog.service.impl; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,6 +56,10 @@ public class CmsBlogServiceImpl implements ICmsBlogService public CmsBlog selectCmsBlogById(Long id) { CmsBlog blog = cmsBlogMapper.selectCmsBlogById(id); + if (blog == null) + { + return null; + } //查询标签列表 List blogTagList = cmsBlogTagMapper.selectBlogTagList(id); Long[] tagIds = new Long[blogTagList.size()]; @@ -83,6 +89,13 @@ public class CmsBlogServiceImpl implements ICmsBlogService return blog; } + @Override + public CmsBlog selectPublishedCmsBlogById(Long id) + { + CmsBlog blog = selectCmsBlogById(id); + return blog != null && "1".equals(blog.getStatus()) ? blog : null; + } + /** * 查询文章管理列表 * @@ -103,18 +116,19 @@ public class CmsBlogServiceImpl implements ICmsBlogService return blogList; } + @Override + public List selectCmsBlogCardList(CmsBlog cmsBlog) + { + return prepareBlogCards(cmsBlogMapper.selectCmsBlogCardList(cmsBlog)); + } + /** * 查询推荐文章列表 */ @Override public List selectCmsBlogListRecommend(CmsBlog cmsBlog) { List cmsBlogList = cmsBlogMapper.selectCmsBlogListRecommend(cmsBlog); - // 为每个博客设置首页图片(如果为空,则从content中提取) - for (CmsBlog blog : cmsBlogList) { - setDefaultBlogPicFromContent(blog); - } - List blogList = BlogListAddTypeAndTag(cmsBlogList); - return blogList; + return prepareBlogCards(cmsBlogList); } /** @@ -123,12 +137,7 @@ public class CmsBlogServiceImpl implements ICmsBlogService @Override public List selectCmsBlogListByTypeId(Long id) { List cmsBlogList = cmsBlogMapper.selectCmsBlogListByTypeId(id); - // 为每个博客设置首页图片(如果为空,则从content中提取) - for (CmsBlog blog : cmsBlogList) { - setDefaultBlogPicFromContent(blog); - } - List blogList = BlogListAddTypeAndTag(cmsBlogList); - return blogList; + return prepareBlogCards(cmsBlogList); } /** @@ -137,12 +146,7 @@ public class CmsBlogServiceImpl implements ICmsBlogService @Override public List selectCmsBlogListByTagId(Long id) { List cmsBlogList = cmsBlogMapper.selectCmsBlogListByTagId(id); - // 为每个博客设置首页图片(如果为空,则从content中提取) - for (CmsBlog blog : cmsBlogList) { - setDefaultBlogPicFromContent(blog); - } - List blogList = BlogListAddTypeAndTag(cmsBlogList); - return blogList; + return prepareBlogCards(cmsBlogList); } /** @@ -307,30 +311,71 @@ public class CmsBlogServiceImpl implements ICmsBlogService return null; } - private List BlogListAddTypeAndTag(List cmsBlogList){ - if (cmsBlogList==null||cmsBlogList.size()<0){ + /** + * 列表不返回正文;仅对没有独立封面的项目批量读取正文并提取首图。 + */ + private List prepareBlogCards(List cmsBlogList) { + if (cmsBlogList == null || cmsBlogList.isEmpty()) { return cmsBlogList; } + + Map missingCoverBlogs = new HashMap<>(); for (CmsBlog blog : cmsBlogList) { - Long blogId = blog.getId(); - //查询标签列表 - List blogTagList = cmsBlogTagMapper.selectBlogTagList(blogId); - List cmsTagList = new ArrayList<>(); - blogTagList.forEach((CmsBlogTag cmsBlogTag)->{ - Long tagId = cmsBlogTag.getTagId(); - CmsTag cmsTag = cmsTagMapper.selectCmsTagByTagId(tagId); - cmsTagList.add(cmsTag); - }); - blog.setTags(cmsTagList); - //查询分类列表 - List blogTypeList = cmsBlogTypeMapper.selectBlogTypeList(blogId); - List cmsTypeList = new ArrayList<>(); - blogTypeList.forEach((CmsBlogType cmsBlogType)->{ - Long typeId = cmsBlogType.getTypeId(); - CmsType cmsType = cmsTypeMapper.selectCmsTypeByTypeId(typeId); - cmsTypeList.add(cmsType); - }); - blog.setTypes(cmsTypeList); + boolean hasCover = (blog.getBlogPic() != null && !blog.getBlogPic().trim().isEmpty()) + || (blog.getBlogPicLink() != null && !blog.getBlogPicLink().trim().isEmpty()); + if (!hasCover) { + missingCoverBlogs.put(blog.getId(), blog); + } + } + + if (!missingCoverBlogs.isEmpty()) { + List ids = new ArrayList<>(missingCoverBlogs.keySet()); + for (CmsBlog contentBlog : cmsBlogMapper.selectCmsBlogContentByIds(ids)) { + CmsBlog targetBlog = missingCoverBlogs.get(contentBlog.getId()); + if (targetBlog == null || contentBlog.getContent() == null) { + continue; + } + String firstImageUrl = extractFirstImageFromContent(contentBlog.getContent()); + if (firstImageUrl != null && !firstImageUrl.trim().isEmpty()) { + targetBlog.setBlogPicType("0"); + targetBlog.setBlogPicLink(firstImageUrl); + } + } + } + + return BlogListAddTypeAndTag(cmsBlogList); + } + + private List BlogListAddTypeAndTag(List cmsBlogList){ + if (cmsBlogList == null || cmsBlogList.isEmpty()) { + return cmsBlogList; + } + + List blogIds = new ArrayList<>(); + Map> tagsByBlogId = new HashMap<>(); + Map> typesByBlogId = new HashMap<>(); + for (CmsBlog blog : cmsBlogList) { + blogIds.add(blog.getId()); + tagsByBlogId.put(blog.getId(), new ArrayList<>()); + typesByBlogId.put(blog.getId(), new ArrayList<>()); + } + + for (CmsBlogTag relation : cmsBlogTagMapper.selectBlogTagsByBlogIds(blogIds)) { + List tags = tagsByBlogId.get(relation.getBlogId()); + if (tags != null && relation.getTag() != null) { + tags.add(relation.getTag()); + } + } + for (CmsBlogType relation : cmsBlogTypeMapper.selectBlogTypesByBlogIds(blogIds)) { + List types = typesByBlogId.get(relation.getBlogId()); + if (types != null && relation.getType() != null) { + types.add(relation.getType()); + } + } + + for (CmsBlog blog : cmsBlogList) { + blog.setTags(tagsByBlogId.get(blog.getId())); + blog.setTypes(typesByBlogId.get(blog.getId())); } return cmsBlogList; } diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/controller/SitemapController.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/controller/SitemapController.java new file mode 100644 index 0000000..477e0aa --- /dev/null +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/controller/SitemapController.java @@ -0,0 +1,36 @@ +package com.ruoyi.cms.sitemap.controller; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import com.ruoyi.cms.sitemap.service.SitemapService; +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Public XML sitemap endpoint. + */ +@RestController +public class SitemapController +{ + private static final MediaType APPLICATION_XML_UTF8 = + new MediaType("application", "xml", StandardCharsets.UTF_8); + + private final SitemapService sitemapService; + + public SitemapController(SitemapService sitemapService) + { + this.sitemapService = sitemapService; + } + + @GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE) + public ResponseEntity sitemap() + { + return ResponseEntity.ok() + .contentType(APPLICATION_XML_UTF8) + .cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic()) + .body(sitemapService.buildSitemap()); + } +} diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/domain/SitemapUrl.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/domain/SitemapUrl.java new file mode 100644 index 0000000..ec2b1aa --- /dev/null +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/domain/SitemapUrl.java @@ -0,0 +1,45 @@ +package com.ruoyi.cms.sitemap.domain; + +import java.util.Date; + +/** + * A canonical, indexable URL source used to build the public sitemap. + */ +public class SitemapUrl +{ + private Long id; + + private String pageType; + + private Date lastModified; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getPageType() + { + return pageType; + } + + public void setPageType(String pageType) + { + this.pageType = pageType; + } + + public Date getLastModified() + { + return lastModified; + } + + public void setLastModified(Date lastModified) + { + this.lastModified = lastModified; + } +} diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/mapper/SitemapMapper.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/mapper/SitemapMapper.java new file mode 100644 index 0000000..37a4b17 --- /dev/null +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/mapper/SitemapMapper.java @@ -0,0 +1,14 @@ +package com.ruoyi.cms.sitemap.mapper; + +import java.util.List; +import com.ruoyi.cms.sitemap.domain.SitemapUrl; + +/** + * Queries only URLs that have a real, published public page. + */ +public interface SitemapMapper +{ + public List selectIndexableCategories(); + + public List selectIndexableContent(); +} diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/service/SitemapService.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/service/SitemapService.java new file mode 100644 index 0000000..c5a74a2 --- /dev/null +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/sitemap/service/SitemapService.java @@ -0,0 +1,128 @@ +package com.ruoyi.cms.sitemap.service; + +import java.io.StringWriter; +import java.net.URI; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import com.ruoyi.cms.sitemap.domain.SitemapUrl; +import com.ruoyi.cms.sitemap.mapper.SitemapMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * Builds the UTF-8 sitemap from canonical URLs backed by published data. + */ +@Service +public class SitemapService +{ + private static final String SITEMAP_NAMESPACE = "http://www.sitemaps.org/schemas/sitemap/0.9"; + + private final SitemapMapper sitemapMapper; + + private final String siteUrl; + + public SitemapService(SitemapMapper sitemapMapper, + @Value("${seo.site-url:https://www.yidaima.cn}") String siteUrl) + { + this.sitemapMapper = sitemapMapper; + this.siteUrl = normalizeSiteUrl(siteUrl); + } + + public String buildSitemap() + { + Map canonicalUrls = new LinkedHashMap<>(); + addUrls(canonicalUrls, sitemapMapper.selectIndexableCategories()); + addUrls(canonicalUrls, sitemapMapper.selectIndexableContent()); + + try + { + StringWriter output = new StringWriter(); + XMLStreamWriter xml = XMLOutputFactory.newFactory().createXMLStreamWriter(output); + xml.writeStartDocument("UTF-8", "1.0"); + xml.writeStartElement("urlset"); + xml.writeDefaultNamespace(SITEMAP_NAMESPACE); + + for (Map.Entry entry : canonicalUrls.entrySet()) + { + xml.writeStartElement("url"); + writeElement(xml, "loc", siteUrl + entry.getKey()); + if (entry.getValue().getLastModified() != null) + { + writeElement(xml, "lastmod", + DateTimeFormatter.ISO_INSTANT.format(entry.getValue().getLastModified().toInstant())); + } + xml.writeEndElement(); + } + + xml.writeEndElement(); + xml.writeEndDocument(); + xml.close(); + return output.toString(); + } + catch (XMLStreamException exception) + { + throw new IllegalStateException("Unable to generate sitemap XML", exception); + } + } + + private void addUrls(Map canonicalUrls, List urls) + { + if (urls == null) + { + return; + } + + for (SitemapUrl url : urls) + { + if (url == null || url.getId() == null || !isSupportedPageType(url.getPageType())) + { + continue; + } + canonicalUrls.putIfAbsent("/" + url.getPageType() + "/" + url.getId(), url); + } + } + + private boolean isSupportedPageType(String pageType) + { + return "category".equals(pageType) || "source".equals(pageType) || "article".equals(pageType); + } + + private void writeElement(XMLStreamWriter xml, String name, String value) throws XMLStreamException + { + xml.writeStartElement(name); + xml.writeCharacters(value); + xml.writeEndElement(); + } + + private String normalizeSiteUrl(String configuredSiteUrl) + { + String value = configuredSiteUrl == null ? "" : configuredSiteUrl.trim(); + while (value.endsWith("/")) + { + value = value.substring(0, value.length() - 1); + } + + URI uri; + try + { + uri = URI.create(value); + } + catch (IllegalArgumentException exception) + { + throw new IllegalStateException("seo.site-url must be an absolute HTTP(S) URL", exception); + } + + if (uri.getHost() == null + || (!"http".equalsIgnoreCase(uri.getScheme()) && !"https".equalsIgnoreCase(uri.getScheme())) + || uri.getQuery() != null || uri.getFragment() != null) + { + throw new IllegalStateException("seo.site-url must be an absolute HTTP(S) URL without query or fragment"); + } + return value; + } +} diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/tag/service/impl/CmsTagServiceImpl.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/tag/service/impl/CmsTagServiceImpl.java index 983251e..a3ae1f4 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/tag/service/impl/CmsTagServiceImpl.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/tag/service/impl/CmsTagServiceImpl.java @@ -47,12 +47,7 @@ public class CmsTagServiceImpl implements ICmsTagService @Override public List selectCmsTagList(CmsTag cmsTag) { - List cmsTagList = cmsTagMapper.selectCmsTagList(cmsTag); - for (CmsTag tag : cmsTagList) { - int blogNum = cmsBlogTagMapper.countBlogByTagId(tag.getTagId()); - tag.setBlogNum(blogNum); - } - return cmsTagList; + return cmsTagMapper.selectCmsTagList(cmsTag); } /** diff --git a/ruoyi-cms/src/main/java/com/ruoyi/cms/type/service/impl/CmsTypeServiceImpl.java b/ruoyi-cms/src/main/java/com/ruoyi/cms/type/service/impl/CmsTypeServiceImpl.java index 6329d62..e388e42 100644 --- a/ruoyi-cms/src/main/java/com/ruoyi/cms/type/service/impl/CmsTypeServiceImpl.java +++ b/ruoyi-cms/src/main/java/com/ruoyi/cms/type/service/impl/CmsTypeServiceImpl.java @@ -51,12 +51,7 @@ public class CmsTypeServiceImpl implements ICmsTypeService @Override public List selectCmsTypeList(CmsType cmsType) { - List cmsTypeList = cmsTypeMapper.selectCmsTypeList(cmsType); - for (CmsType type : cmsTypeList) { - int blogNum = cmsBlogTypeMapper.countBlogByTypeId(type.getTypeId()); - type.setBlogNum(blogNum); - } - return cmsTypeList; + return cmsTypeMapper.selectCmsTypeList(cmsType); } /** diff --git a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogMapper.xml b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogMapper.xml index 27d8a2a..379f6bb 100644 --- a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogMapper.xml +++ b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogMapper.xml @@ -29,6 +29,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select id, create_by, create_time, update_by, update_time, title, type, content, content_type, content_markdown, Top, views, status, blog_pic_type, blog_pic, blog_pic_link, blog_desc, blog_files from cms_blog + + select id, create_by, create_time, update_time, title, type, Top, views, status, + blog_pic_type, blog_pic, blog_pic_link, blog_desc + from cms_blog + + + + + + - + where id = #{id} and status = '1' + + - \ No newline at end of file + diff --git a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTagMapper.xml b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTagMapper.xml index 99cb9ed..b989b6e 100644 --- a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTagMapper.xml +++ b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTagMapper.xml @@ -5,6 +5,14 @@ + + + + + + + + insert into cms_blog_tag(tag_id, blog_id) values @@ -30,7 +38,16 @@ + - \ No newline at end of file + diff --git a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTypeMapper.xml b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTypeMapper.xml index b6fe263..66cc2ed 100644 --- a/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTypeMapper.xml +++ b/ruoyi-cms/src/main/resources/mapper/cms/CmsBlogTypeMapper.xml @@ -5,6 +5,14 @@ + + + + + + + + insert into cms_blog_type(type_id, blog_id) values @@ -30,7 +38,16 @@ + - \ No newline at end of file + diff --git a/ruoyi-cms/src/main/resources/mapper/cms/CmsTagMapper.xml b/ruoyi-cms/src/main/resources/mapper/cms/CmsTagMapper.xml index 7362b8d..3c3e3d6 100644 --- a/ruoyi-cms/src/main/resources/mapper/cms/CmsTagMapper.xml +++ b/ruoyi-cms/src/main/resources/mapper/cms/CmsTagMapper.xml @@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -18,12 +19,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + select t.type_id, t.create_by, t.create_time, t.update_by, t.update_time, t.type_name, + t.type_pic_type, t.type_pic, t.type_pic_link, count(bt.blog_id) as blog_num + from cms_type t + left join cms_blog_type bt on bt.type_id = t.type_id - and type_name like concat('%', #{typeName}, '%') - and type_pic = #{typePic} - and create_by = #{createBy} + and t.type_name like concat('%', #{typeName}, '%') + and t.type_pic = #{typePic} + and t.create_by = #{createBy} - ORDER BY create_time DESC + group by t.type_id, t.create_by, t.create_time, t.update_by, t.update_time, t.type_name, + t.type_pic_type, t.type_pic, t.type_pic_link + ORDER BY t.create_time DESC + SELECT + t.type_id AS id, + 'category' AS page_type, + CASE + WHEN COALESCE(t.update_time, t.create_time) > MAX(COALESCE(b.update_time, b.create_time)) + THEN COALESCE(t.update_time, t.create_time) + ELSE MAX(COALESCE(b.update_time, b.create_time)) + END AS last_modified + FROM cms_type t + INNER JOIN cms_blog_type bt ON bt.type_id = t.type_id + INNER JOIN cms_blog b ON b.id = bt.blog_id AND b.status = '1' + GROUP BY t.type_id, t.update_time, t.create_time + ORDER BY t.type_id + + + + + + diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index a0a4557..5895bed 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -97,6 +97,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter // 过滤请求 .authorizeRequests() .antMatchers("/profile/**").permitAll() + // Public sitemap must be crawlable without a token. + .antMatchers(HttpMethod.GET, "/sitemap.xml").permitAll() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 .antMatchers("/login", "/register", "/captchaImage").anonymous() // 首页获取文章信息 允许匿名访问 diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index f1243a5..ad88d63 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -6,3 +6,6 @@ ENV = 'production' # 若依管理系统/生产环境 VUE_APP_BASE_API = '/prod-api' + +# Canonical host used by public pages and the XML sitemap +VUE_APP_SITE_URL = 'https://www.yidaima.cn' diff --git a/ruoyi-ui/public/index.html b/ruoyi-ui/public/index.html index 925455c..5ea8523 100644 --- a/ruoyi-ui/public/index.html +++ b/ruoyi-ui/public/index.html @@ -6,7 +6,8 @@ - <%= webpackConfig.name %> + 南音源码库 - Java、Python、前端项目源码与开发教程 + diff --git a/ruoyi-ui/src/views/cms/NavBar.vue b/ruoyi-ui/src/views/cms/NavBar.vue index 0ffa630..7bdbf1f 100644 --- a/ruoyi-ui/src/views/cms/NavBar.vue +++ b/ruoyi-ui/src/views/cms/NavBar.vue @@ -1,438 +1,433 @@ diff --git a/ruoyi-ui/src/views/cms/cmslogin.vue b/ruoyi-ui/src/views/cms/cmslogin.vue index 75410bc..b5df7b3 100644 --- a/ruoyi-ui/src/views/cms/cmslogin.vue +++ b/ruoyi-ui/src/views/cms/cmslogin.vue @@ -141,7 +141,7 @@ export default { Cookies.remove('rememberMe'); } this.$store.dispatch("Login", this.loginForm).then(() => { - this.$router.push({ path: "/cms/main/cmsIndex" }); + this.$router.push({ path: "/" }); }).catch(() => { this.loading = false; if (this.captchaOnOff) { diff --git a/ruoyi-ui/src/views/cms/comment/index.vue b/ruoyi-ui/src/views/cms/comment/index.vue index b853c15..8a561fd 100644 --- a/ruoyi-ui/src/views/cms/comment/index.vue +++ b/ruoyi-ui/src/views/cms/comment/index.vue @@ -365,9 +365,8 @@ export default { // 跳转到评论页 getBlogInfo(mes) { let routeUrl = this.$router.resolve({ - path: '/cms/main/blog', + path: `/article/${mes.blogId}`, query: { - id: mes.blogId, commentId: mes.id } }); diff --git a/ruoyi-ui/src/views/cms/components/cmsBlog.vue b/ruoyi-ui/src/views/cms/components/cmsBlog.vue index 26da70b..02d5978 100644 --- a/ruoyi-ui/src/views/cms/components/cmsBlog.vue +++ b/ruoyi-ui/src/views/cms/components/cmsBlog.vue @@ -1,142 +1,237 @@ + + diff --git a/ruoyi-ui/src/views/cms/components/cmsDoucument.vue b/ruoyi-ui/src/views/cms/components/cmsDoucument.vue index 270d98f..e99175b 100644 --- a/ruoyi-ui/src/views/cms/components/cmsDoucument.vue +++ b/ruoyi-ui/src/views/cms/components/cmsDoucument.vue @@ -1,39 +1,41 @@ - diff --git a/ruoyi-ui/src/views/cms/components/cmsEssay.vue b/ruoyi-ui/src/views/cms/components/cmsEssay.vue index 9e5cc68..1e415e5 100644 --- a/ruoyi-ui/src/views/cms/components/cmsEssay.vue +++ b/ruoyi-ui/src/views/cms/components/cmsEssay.vue @@ -1,103 +1,556 @@ diff --git a/ruoyi-ui/src/views/cms/components/cmsIndex.vue b/ruoyi-ui/src/views/cms/components/cmsIndex.vue index e1062d2..940455c 100644 --- a/ruoyi-ui/src/views/cms/components/cmsIndex.vue +++ b/ruoyi-ui/src/views/cms/components/cmsIndex.vue @@ -1,128 +1,265 @@ diff --git a/ruoyi-ui/src/views/cms/main.vue b/ruoyi-ui/src/views/cms/main.vue index 8e7a0e2..ceab24e 100644 --- a/ruoyi-ui/src/views/cms/main.vue +++ b/ruoyi-ui/src/views/cms/main.vue @@ -1,22 +1,24 @@