From 2749754eb0ed11d98f8c7774b658bc7fc2740164 Mon Sep 17 00:00:00 2001 From: barney <15270405776@163.com> Date: Fri, 21 Oct 2022 18:35:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E5=85=B1=E9=83=A8=E5=88=86=E7=9A=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8A=A0=E4=B8=8A=E7=BC=93=E5=AD=98=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E5=B9=B6=E5=B0=86=E9=A6=96=E9=A1=B5=E7=9A=84?= =?UTF-8?q?=E6=96=87=E7=AB=A0=E5=88=97=E8=A1=A8=E5=81=9A=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cc/bnblogs/common/ArticleSearch.java | 2 + src/main/java/cc/bnblogs/common/LRUCache.java | 39 ++ .../java/cc/bnblogs/config/WebConfig.java | 8 + .../bnblogs/controller/IndexController.java | 79 +++- .../java/cc/bnblogs/mapper/ArticleMapper.java | 22 + .../java/cc/bnblogs/mapper/TagMapper.java | 13 + src/main/java/cc/bnblogs/pojo/Article.java | 3 +- src/main/java/cc/bnblogs/pojo/Category.java | 13 + src/main/java/cc/bnblogs/pojo/Tag.java | 4 + .../cc/bnblogs/service/ArticleService.java | 40 +- .../cc/bnblogs/service/CategoryService.java | 25 +- .../java/cc/bnblogs/service/TagService.java | 16 +- src/main/resources/templates/category.html | 350 ++++------------ src/main/resources/templates/common.html | 37 +- src/main/resources/templates/index.html | 31 +- src/main/resources/templates/list.html | 41 +- src/main/resources/templates/tags.html | 388 ++++-------------- 17 files changed, 489 insertions(+), 622 deletions(-) create mode 100644 src/main/java/cc/bnblogs/common/LRUCache.java diff --git a/src/main/java/cc/bnblogs/common/ArticleSearch.java b/src/main/java/cc/bnblogs/common/ArticleSearch.java index f87c5dd..8249339 100644 --- a/src/main/java/cc/bnblogs/common/ArticleSearch.java +++ b/src/main/java/cc/bnblogs/common/ArticleSearch.java @@ -25,6 +25,8 @@ public class ArticleSearch { private Integer status; //标题 private String title; + //搜索关键字 + private String keyword; //文章类型 private Integer type; //页码 diff --git a/src/main/java/cc/bnblogs/common/LRUCache.java b/src/main/java/cc/bnblogs/common/LRUCache.java new file mode 100644 index 0000000..e31ee74 --- /dev/null +++ b/src/main/java/cc/bnblogs/common/LRUCache.java @@ -0,0 +1,39 @@ +package cc.bnblogs.common; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * @author zfp@bnblogs.cc + * @createTime: 2022/10/21 + */ +public class LRUCache { + + // 使用hashSet + private final Set cache = new LinkedHashSet<>(); + + // 最大缓存数量 + private final int limit; + + public LRUCache(int limit) { + this.limit = limit; + } + + public int size() { + return cache.size(); + } + + public List list() { + return new ArrayList<>(cache); + } + + public void add(String keyword) { + while (cache.size() >= limit) { + cache.remove(cache.stream().findFirst().orElse(null)); + } + cache.remove(keyword); + cache.add(keyword); + } +} diff --git a/src/main/java/cc/bnblogs/config/WebConfig.java b/src/main/java/cc/bnblogs/config/WebConfig.java index 818b601..10634a6 100644 --- a/src/main/java/cc/bnblogs/config/WebConfig.java +++ b/src/main/java/cc/bnblogs/config/WebConfig.java @@ -1,7 +1,9 @@ package cc.bnblogs.config; +import cc.bnblogs.common.LRUCache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -19,6 +21,8 @@ public class WebConfig implements WebMvcConfigurer { @Value("${upload.base-dir}") private String baseDir; + public static final Integer MAX_LRU_CACHE_SIZE = 20; + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 创建static目录到/static/的路由 @@ -37,4 +41,8 @@ public class WebConfig implements WebMvcConfigurer { // 记住在absolutePath前加一个'/',否则本地图片读取不出来(重要的事情说三遍) registry.addResourceHandler("/upload/**").addResourceLocations("file://" + '/' + absolutePath); } + @Bean + public LRUCache lruCache() { + return new LRUCache(MAX_LRU_CACHE_SIZE); + } } diff --git a/src/main/java/cc/bnblogs/controller/IndexController.java b/src/main/java/cc/bnblogs/controller/IndexController.java index 0f367d7..bd45a19 100644 --- a/src/main/java/cc/bnblogs/controller/IndexController.java +++ b/src/main/java/cc/bnblogs/controller/IndexController.java @@ -1,7 +1,9 @@ package cc.bnblogs.controller; import cc.bnblogs.common.ArticleSearch; +import cc.bnblogs.common.LRUCache; import cc.bnblogs.common.PageHelper; +import cc.bnblogs.config.WebConfig; import cc.bnblogs.pojo.Article; import cc.bnblogs.pojo.Category; import cc.bnblogs.pojo.Tag; @@ -13,7 +15,9 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; -import java.util.Objects; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; /** * @author zfp@bnblogs.cc @@ -29,15 +33,20 @@ public class IndexController { private final BannerService bannerService; private final CategoryService categoryService; + private final LRUCache lruCache; + + public IndexController(FriendsService friendsService, NavigationService navigationService, - TagService tagService, ArticleService articleService, BannerService bannerService, CategoryService categoryService) { + TagService tagService, ArticleService articleService, BannerService bannerService, + CategoryService categoryService, LRUCache lruCache) { this.friendsService = friendsService; this.navigationService = navigationService; this.tagService = tagService; this.articleService = articleService; this.bannerService = bannerService; this.categoryService = categoryService; + this.lruCache = lruCache; } @@ -46,8 +55,19 @@ public class IndexController { // todo 可以在这里定义这个controller公用的model的属性 model.addAttribute("friends",friendsService.list()); model.addAttribute("navigations",navigationService.show()); - model.addAttribute("tags",tagService.show((int)tagService.count())); + // 标签云 + model.addAttribute("tags",tagService.list((int)tagService.count())); model.addAttribute("hots",articleService.hotList(5)); + + int size = lruCache.size(); + List keywords = lruCache.list(); + Collections.reverse(keywords); + if (size < WebConfig.MAX_LRU_CACHE_SIZE) { + // 关键字不够时用标签来补充 + keywords.addAll(tagService.list( WebConfig.MAX_LRU_CACHE_SIZE - size).stream() + .map(Tag::getName).collect(Collectors.toList())); + } + model.addAttribute("keywords",keywords); } @@ -55,7 +75,7 @@ public class IndexController { @GetMapping("/") public String index(@RequestParam(required = false, defaultValue = "1") Integer pageNumber,Model model) { pageNumber = pageNumber < 1 ? 1: pageNumber; - model.addAttribute("articlePage",articleService.search(ArticleSearch.indexSearch(pageNumber,1))); + model.addAttribute("articlePage",articleService.search(ArticleSearch.indexSearch(pageNumber,5))); model.addAttribute("banners",bannerService.list()); return "index"; } @@ -83,6 +103,15 @@ public class IndexController { return "list"; } + /** + * 对应分类的列表页 + * 格式: http://localhost:8080/category/{id}.html?pageNumber=1 + * @param id 分类id + * @param pageNumber 页号 + * @param model model + * @return 分类列表页 + */ + @GetMapping("/category/{id}.html") public String categoryList(@PathVariable Integer id, @RequestParam(required = false,defaultValue = "1") Integer pageNumber, @@ -94,16 +123,50 @@ public class IndexController { articleSearch.setCid(id); PageHelper
articlePage = articleService.search(articleSearch); model.addAttribute("articlePage",articlePage); + model.addAttribute("pageType","category"); return "list"; } - @GetMapping("/category") - public String category() { + /** + * 返回搜索到的文章 + * @param pageNumber 页号 + * @param keyword 搜索关键字(同时搜索标题和内容中是否包含该关键字) + * @param model model + * @return 搜索结果 + */ + @GetMapping("/search.html") + public String search(@RequestParam(required = false,defaultValue = "1") Integer pageNumber, + String keyword, + Model model){ + lruCache.add(keyword); + model.addAttribute("keyword",keyword); + ArticleSearch articleSearch = ArticleSearch.indexSearch(pageNumber,5); + articleSearch.setKeyword(keyword); + PageHelper
articlePage = articleService.search(articleSearch); + model.addAttribute("articlePage",articlePage); + model.addAttribute("pageType","search"); + return "list"; + } + + /** + * 前台分类列表页 + * @param model model + * @return 所有分类 + */ + @GetMapping("/category.html") + public String category(Model model) { + model.addAttribute("categories",categoryService.show()); return "category"; } - @GetMapping("/tags") - public String tags() { + /** + * 前台标签列表页 + * @param model model + * @return 所有标签 + */ + @GetMapping("/tag.html") + public String tag(Model model) { + model.addAttribute("tagList",tagService.show()); return "tags"; } diff --git a/src/main/java/cc/bnblogs/mapper/ArticleMapper.java b/src/main/java/cc/bnblogs/mapper/ArticleMapper.java index 73eeafe..51eb6f5 100644 --- a/src/main/java/cc/bnblogs/mapper/ArticleMapper.java +++ b/src/main/java/cc/bnblogs/mapper/ArticleMapper.java @@ -1,10 +1,12 @@ package cc.bnblogs.mapper; import cc.bnblogs.pojo.Article; +import cc.bnblogs.pojo.Category; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; +import java.util.Date; import java.util.List; /** @@ -12,6 +14,26 @@ import java.util.List; * @createTime: 2022/10/17 */ public interface ArticleMapper extends JpaRepository, JpaSpecificationExecutor
{ + /** + * 返回留言量最高的几篇文章 + * @param limit 文章数 + * @return 匹配的文章列表 + */ @Query(value = "select * from blog_article where type=1 and status=1 order by views limit ?1", nativeQuery = true) List
hotList(int limit); + + /** + * 统计分类文章数 + * @param category 分类 + * @return 文章个数 + */ + Long countByCategory(Category category); + + /** + * 返回该分类下最后一次更新时间 + * @param cid 分类id + * @return 最后一次更新时间 + */ + @Query(value="SELECT updated FROM blog_article WHERE category_id=?1 ORDER BY updated DESC LIMIT 1",nativeQuery = true) + Date lastUpdated(Integer cid); } diff --git a/src/main/java/cc/bnblogs/mapper/TagMapper.java b/src/main/java/cc/bnblogs/mapper/TagMapper.java index 41f392b..33c2203 100644 --- a/src/main/java/cc/bnblogs/mapper/TagMapper.java +++ b/src/main/java/cc/bnblogs/mapper/TagMapper.java @@ -12,6 +12,11 @@ import java.util.List; * @createTime: 2022/10/17 */ public interface TagMapper extends JpaRepository { + /** + * 随机返回一定数量的标签 + * @param limit 标签数上限 + * @return 标签云 + */ @Query(value = "SELECT * FROM blog_tag\n" + "WHERE\n" + " id >= ( SELECT FLOOR(\n" + @@ -24,4 +29,12 @@ public interface TagMapper extends JpaRepository { " ) \n" + "ORDER BY id LIMIT ?1",nativeQuery = true) List findRandom(int limit); + + /** + * 统计该标签下的文章数 + * @param tid 标签id + * @return 文章数量 + */ + @Query(value = "SELECT COUNT(1) FROM blog_article_tags WHERE tags_id=?1",nativeQuery = true) + Long articleCountByTid(Integer tid); } diff --git a/src/main/java/cc/bnblogs/pojo/Article.java b/src/main/java/cc/bnblogs/pojo/Article.java index 8a37968..cef6258 100644 --- a/src/main/java/cc/bnblogs/pojo/Article.java +++ b/src/main/java/cc/bnblogs/pojo/Article.java @@ -74,7 +74,8 @@ public class Article implements Serializable { public static final Integer COMMENT_DISABLE = 2; // 返回摘要 public String summary() { - return this.content.substring(0,Math.max(200,this.content.length())); + String summary = this.content.replaceAll("[^\\u4E00-\\u9FA5a-zA-Z]", " "); + return summary.substring(0,Math.min(200,summary.length())); } } diff --git a/src/main/java/cc/bnblogs/pojo/Category.java b/src/main/java/cc/bnblogs/pojo/Category.java index 6664b30..5c985dc 100644 --- a/src/main/java/cc/bnblogs/pojo/Category.java +++ b/src/main/java/cc/bnblogs/pojo/Category.java @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; import javax.persistence.*; import java.io.Serializable; +import java.util.Date; /** * @author zfp@bnblogs.cc @@ -35,4 +36,16 @@ public class Category implements Serializable { * 分类介绍 */ private String summary; + + /** + * 该分类的文章总数 + */ + @Transient + private Long articleCount; + + /** + * 该分类下文章最近一次更新时间 + */ + @Transient + private Date lastUpdated; } diff --git a/src/main/java/cc/bnblogs/pojo/Tag.java b/src/main/java/cc/bnblogs/pojo/Tag.java index 3be3a42..83f5a03 100644 --- a/src/main/java/cc/bnblogs/pojo/Tag.java +++ b/src/main/java/cc/bnblogs/pojo/Tag.java @@ -24,4 +24,8 @@ public class Tag implements Serializable { private Integer id; @Column(unique = true) private String name; + + // 该标签下的文章总数 + @Transient + private Long articleCount; } diff --git a/src/main/java/cc/bnblogs/service/ArticleService.java b/src/main/java/cc/bnblogs/service/ArticleService.java index 048d729..998cad7 100644 --- a/src/main/java/cc/bnblogs/service/ArticleService.java +++ b/src/main/java/cc/bnblogs/service/ArticleService.java @@ -20,6 +20,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import org.thymeleaf.util.StringUtils; import javax.persistence.criteria.JoinType; @@ -135,26 +136,49 @@ public class ArticleService { // 按照创建时间的降序返回结果 Pageable pageable = PageRequest.of(search.getPageNum() - 1, search.getPageSize(), Sort.by(Sort.Direction.DESC,"created")); Page
articlePage = articleMapper.findAll((Specification
) (root, query, builder) -> { - List predicateList = new ArrayList<>(); + List predicateAdd = new ArrayList<>(); if (Objects.nonNull(search.getCid())) { - predicateList.add(builder.equal(root.get("category"), Category.builder().id(search.getCid()).build())); + /* + * 传入一个分类id,查询对应的文章列表 + * SELECT * FROM blog_article WHERE category_id={id}; + */ + predicateAdd.add(builder.equal(root.get("category"), Category.builder().id(search.getCid()).build())); } if (Objects.nonNull(search.getStatus())) { - predicateList.add(builder.equal(root.get("status"), search.getStatus())); + predicateAdd.add(builder.equal(root.get("status"), search.getStatus())); } if (Objects.nonNull(search.getType())) { - predicateList.add(builder.equal(root.get("type"), search.getType())); + predicateAdd.add(builder.equal(root.get("type"), search.getType())); } if (!StringUtils.isEmptyOrWhitespace(search.getTitle())) { // 标题的模糊查询 - predicateList.add(builder.like(root.get("title"), "%" + search.getTitle() + "%")); + predicateAdd.add(builder.like(root.get("title"), "%" + search.getTitle() + "%")); } if (Objects.nonNull(search.getTid())) { - // todo 这里涉及多表连接,再看一下 + /* + * SELECT * FROM blog_article as article + * LEFT JOIN blog_article_tags bat on article.id = bat.article_id + * LEFT JOIN blog_tag bt on bt.id = bat.tags_id + * WHERE bt.id={ID} + */ ListJoin join = root.join(root.getModel().getList("tags", Tag.class), JoinType.LEFT); - predicateList.add(builder.equal(join.get("id"), search.getTid())); + predicateAdd.add(builder.equal(join.get("id"), search.getTid())); } - return builder.and(predicateList.toArray(new Predicate[predicateList.size()])); + List predicateOr = new ArrayList<>(); + if (!StringUtils.isEmptyOrWhitespace(search.getKeyword())) { + // 后面可以考虑使用搜索引擎ElasticSearch + predicateOr.add(builder.like(root.get("title"),"%" + search.getKeyword() + "%")); + predicateOr.add(builder.like(root.get("content"),"%" + search.getKeyword() + "%")); + } + if (CollectionUtils.isEmpty(predicateOr)) { + // or条件为空时,直接返回与条件的搜索结果 + return builder.and(predicateAdd.toArray(new Predicate[predicateAdd.size()])); + } + return query.where( + builder.and(predicateAdd.toArray(new Predicate[predicateAdd.size()])), + builder.or(predicateOr.toArray(new Predicate[predicateOr.size()]))) + .getRestriction(); + },pageable); return PageHelper.
builder(). diff --git a/src/main/java/cc/bnblogs/service/CategoryService.java b/src/main/java/cc/bnblogs/service/CategoryService.java index 3c1e1f9..a738b86 100644 --- a/src/main/java/cc/bnblogs/service/CategoryService.java +++ b/src/main/java/cc/bnblogs/service/CategoryService.java @@ -1,22 +1,30 @@ package cc.bnblogs.service; +import cc.bnblogs.mapper.ArticleMapper; import cc.bnblogs.mapper.CategoryMapper; import cc.bnblogs.pojo.Category; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; /** * @author zfp@bnblogs.cc * @createTime: 2022/10/16 */ @Service +@CacheConfig(cacheNames = {"blog-cache"}) public class CategoryService { private final CategoryMapper categoryMapper; + private final ArticleMapper articleMapper; - public CategoryService(CategoryMapper categoryMapper) { + public CategoryService(CategoryMapper categoryMapper, ArticleMapper articleMapper) { this.categoryMapper = categoryMapper; + this.articleMapper = articleMapper; } /** @@ -48,6 +56,7 @@ public class CategoryService { * 保存分类 * @param category 待保存的分类对象 */ + @CacheEvict(allEntries = true) public void save(Category category) { System.out.println(category); categoryMapper.save(category); @@ -57,7 +66,21 @@ public class CategoryService { * 根据id删除分类对象 * @param id 待删除分类的id */ + @CacheEvict(allEntries = true) public void delete(Integer id) { categoryMapper.deleteById(id); } + + /** + * 返回文章数不为0的所有分类 + * @return 有效分类 + */ + @Cacheable + public List show() { + return categoryMapper.findAll().stream().peek(category -> { + // 返回该分类下的文章总数 + category.setArticleCount(articleMapper.countByCategory(category)); + category.setLastUpdated(articleMapper.lastUpdated(category.getId())); + }).filter(category -> category.getArticleCount() > 0).collect(Collectors.toList()); + } } diff --git a/src/main/java/cc/bnblogs/service/TagService.java b/src/main/java/cc/bnblogs/service/TagService.java index c9dbf0f..1fa3a93 100644 --- a/src/main/java/cc/bnblogs/service/TagService.java +++ b/src/main/java/cc/bnblogs/service/TagService.java @@ -1,5 +1,6 @@ package cc.bnblogs.service; +import cc.bnblogs.mapper.ArticleMapper; import cc.bnblogs.mapper.TagMapper; import cc.bnblogs.pojo.Tag; import org.springframework.cache.annotation.CacheConfig; @@ -8,6 +9,7 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.Collectors; /** * @author zfp@bnblogs.cc @@ -19,7 +21,7 @@ public class TagService { private final TagMapper tagMapper; - public TagService(TagMapper tagMapper) { + public TagService(TagMapper tagMapper, ArticleMapper articleMapper) { this.tagMapper = tagMapper; } @@ -76,7 +78,17 @@ public class TagService { * @return 标签云 */ @Cacheable - public List show(int limit) { + public List list(int limit) { return tagMapper.findRandom(limit); } + + /** + * 返回文章数不为0的所有标签 + * @return + */ + public List show() { + return tagMapper.findAll().stream().peek(tag -> { + tag.setArticleCount(tagMapper.articleCountByTid(tag.getId())); + }).filter(tag -> tag.getArticleCount() > 0).collect(Collectors.toList()); + } } diff --git a/src/main/resources/templates/category.html b/src/main/resources/templates/category.html index 680c4d3..c8bf8d7 100644 --- a/src/main/resources/templates/category.html +++ b/src/main/resources/templates/category.html @@ -1,289 +1,113 @@ - - - - Title - - - - - - - - + + + - - -
+
- - - + + +
+ + + + + + +
+ + + + + - - - - \ No newline at end of file