From a0f0d0cce923da756c603d426598090594a144ef Mon Sep 17 00:00:00 2001 From: Xubx <1827135378@qq.com> Date: Tue, 11 Mar 2025 20:36:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E7=B1=BB=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/constantConfiguration.java | 4 + .../controller/BlogsController.java | 18 ++++ .../springboot_01demo/entity/pojo/Blogs.java | 5 ++ .../springboot_01demo/mapper/BlogsMapper.java | 5 ++ .../service/BlogService.java | 11 +++ .../service/InterestPushService.java | 30 ++++++- .../service/impl/BlogsServiceImpl.java | 54 ++++++++++-- .../service/impl/InterestPushServiceImpl.java | 86 ++++++++++++++++++- 8 files changed, 205 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/xubx/springboot_01demo/configuration/constantConfiguration.java b/src/main/java/com/xubx/springboot_01demo/configuration/constantConfiguration.java index ecc7dc1..837abfe 100644 --- a/src/main/java/com/xubx/springboot_01demo/configuration/constantConfiguration.java +++ b/src/main/java/com/xubx/springboot_01demo/configuration/constantConfiguration.java @@ -62,4 +62,8 @@ public class constantConfiguration { //各标签下的视频列表 public static final String TAG_BLOGS = "tag:blogs:"; public static final String HOT_RANK = "hot:rank:"; + // 各分类下的视频列表 + public static final String CATEGORY_BLOGS = "category:blogs:"; + // 分类分片 -> 解决大key问题 + public static final String CATEGORY_BLOGS_FRAGMENTATION = "category:blogs:fragmentation:"; } diff --git a/src/main/java/com/xubx/springboot_01demo/controller/BlogsController.java b/src/main/java/com/xubx/springboot_01demo/controller/BlogsController.java index e3c89b7..53ed6ae 100644 --- a/src/main/java/com/xubx/springboot_01demo/controller/BlogsController.java +++ b/src/main/java/com/xubx/springboot_01demo/controller/BlogsController.java @@ -228,5 +228,23 @@ public class BlogsController { } } + /** + * 分类推送,根据分类推送博客 + * @param categoryId + * @return {@link ResponseEntity }<{@link ? }> + */ + @GetMapping("/pushBlogByCategory") + public ResponseEntity pushBlogByCategory(@RequestParam("categoryId") int categoryId) { + log.info("根据分类推送博客,{}", categoryId); + + try { + List blogs = blogService.pushBlogByCategory(categoryId); + return ResponseEntity.ok(blogs); + } catch (Exception e) { + log.error("根据分类推送博客失败", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("根据分类推送博客失败"); + } + } + } diff --git a/src/main/java/com/xubx/springboot_01demo/entity/pojo/Blogs.java b/src/main/java/com/xubx/springboot_01demo/entity/pojo/Blogs.java index b01cb57..69986d3 100644 --- a/src/main/java/com/xubx/springboot_01demo/entity/pojo/Blogs.java +++ b/src/main/java/com/xubx/springboot_01demo/entity/pojo/Blogs.java @@ -1,6 +1,7 @@ package com.xubx.springboot_01demo.entity.pojo; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; @@ -39,6 +40,10 @@ public class Blogs implements Serializable { @ApiModelProperty(value = "分类id") private Integer categoryId; + @ApiModelProperty(value = "分类分片数") + @TableField("category_fragmentation") + private String categoryFragmentation; + @ApiModelProperty(value = "封面图片") private String coverImage; diff --git a/src/main/java/com/xubx/springboot_01demo/mapper/BlogsMapper.java b/src/main/java/com/xubx/springboot_01demo/mapper/BlogsMapper.java index be8f61e..a7a8a52 100644 --- a/src/main/java/com/xubx/springboot_01demo/mapper/BlogsMapper.java +++ b/src/main/java/com/xubx/springboot_01demo/mapper/BlogsMapper.java @@ -127,5 +127,10 @@ public interface BlogsMapper extends BaseMapper { */ List getUserIdsByBlogId(String blogId); + /** + * 获取博客下的所有标签 + * @param blogId + * @return {@link List }<{@link String }> + */ List getTagsByBlogId(Integer blogId); } diff --git a/src/main/java/com/xubx/springboot_01demo/service/BlogService.java b/src/main/java/com/xubx/springboot_01demo/service/BlogService.java index 95d0005..a4e1d3a 100644 --- a/src/main/java/com/xubx/springboot_01demo/service/BlogService.java +++ b/src/main/java/com/xubx/springboot_01demo/service/BlogService.java @@ -58,5 +58,16 @@ public interface BlogService extends IService { */ List pushBlogs(); + /** + * 热度排行榜 + * @return {@link List }<{@link HotBlog }> + */ List listHotRank(); + + /** + * 分类推送 + * @param categoryId + * @return {@link List }<{@link Blogs }> + */ + List pushBlogByCategory(Integer categoryId); } diff --git a/src/main/java/com/xubx/springboot_01demo/service/InterestPushService.java b/src/main/java/com/xubx/springboot_01demo/service/InterestPushService.java index cbe0440..b28c3ae 100644 --- a/src/main/java/com/xubx/springboot_01demo/service/InterestPushService.java +++ b/src/main/java/com/xubx/springboot_01demo/service/InterestPushService.java @@ -1,7 +1,9 @@ package com.xubx.springboot_01demo.service; +import com.xubx.springboot_01demo.entity.pojo.Blogs; import com.xubx.springboot_01demo.entity.pojo.User; +import java.util.Collection; import java.util.List; /** @@ -33,7 +35,7 @@ public interface InterestPushService { * @param user * @return {@link List }<{@link String }> */ - List listBlogsByUserModel(User user); + Collection listBlogsByUserModel(User user); /** * 推入系统标签库 @@ -42,4 +44,30 @@ public interface InterestPushService { * @param tags */ void pushTagsStockIn(Integer bligId, List tags); + + /** + * 推入分类库,用于后续随机推送分类视频 + * + * @param blog + */ + void pushCategoryStockIn(Blogs blog); + + /** + * 分类推送,根据分类随机推送 + * @param categoryId + * @return {@link Collection }<{@link String }> + */ + Collection listBlogsByCategoryId(Integer categoryId); + + /** + * 删除分类库中的博客 + * @param blog + */ + void deletCategoryStockIn(Blogs blog); + + /** + * 删除标签库中的博客 + * @param blog + */ + void deletTagsStockIn(Blogs blog); } diff --git a/src/main/java/com/xubx/springboot_01demo/service/impl/BlogsServiceImpl.java b/src/main/java/com/xubx/springboot_01demo/service/impl/BlogsServiceImpl.java index 0882b17..0aff279 100644 --- a/src/main/java/com/xubx/springboot_01demo/service/impl/BlogsServiceImpl.java +++ b/src/main/java/com/xubx/springboot_01demo/service/impl/BlogsServiceImpl.java @@ -28,9 +28,7 @@ import org.springframework.util.ObjectUtils; import javax.annotation.Resource; import java.sql.Timestamp; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -171,13 +169,35 @@ public class BlogsServiceImpl extends ServiceImpl implements blog.setAuthorId(RequestHolder.getuserId()); // TODO 后续改为AOP切面统一进行创建、更新时间的插入 blog.setCreatedTime(Timestamp.valueOf(now)); + + // 推入系统分类库,判断库中数据量是否达到1万条,如果达到了,进行分类id的自增 + Integer categoryId = blog.getCategoryId(); + String categoryFragmentationKey = constantConfiguration.CATEGORY_BLOGS_FRAGMENTATION + categoryId; + final String categoryFragmentationNumber = (String) redisTemplate.opsForValue().get(categoryFragmentationKey); + + // 如果为null,则初始化为1 + if (categoryFragmentationNumber == null) { + redisTemplate.opsForValue().set(categoryFragmentationKey, "1"); + } + + if (redisTemplate.opsForSet().size(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + categoryFragmentationNumber) >= 10000) { + redisTemplate.opsForValue().increment(categoryFragmentationKey); + } + // 推入分类库 + interestPushService.pushCategoryStockIn(blog); + + // 冗余分片number字段 + blog.setCategoryFragmentation((String) redisTemplate.opsForValue().get(categoryFragmentationKey)); blogsMapper.insert(blog); + // 插入博客标签中间表 blogDto.getTags().forEach(tag -> { blogsMapper.insertTags(blog.getId(), tag); }); + // 插入系统标签库 interestPushService.pushTagsStockIn(blog.getId(), blogDto.getTags()); + } @Override @@ -188,6 +208,10 @@ public class BlogsServiceImpl extends ServiceImpl implements @Override public void deleteBlogs(int id) { blogsMapper.deleteBlogs(id); + Blogs blog = blogsMapper.findByIdBlogs(id); + //删除标签库和分类库中的博客 + interestPushService.deletTagsStockIn(blog); + interestPushService.deletCategoryStockIn(blog); } /** @@ -392,13 +416,13 @@ public class BlogsServiceImpl extends ServiceImpl implements if (userId != null) { user = userService.getUser(String.valueOf(userId)); } - List blogIds = interestPushService.listBlogsByUserModel(user); + Collection blogIds = interestPushService.listBlogsByUserModel(user); List blogs = new ArrayList<>(); // 如果该用户没有兴趣模型,默认给最新的十条博客 if (ObjectUtils.isEmpty(blogIds)) { blogIds = list(new LambdaQueryWrapper().orderByDesc(Blogs::getCreatedTime)).stream().map(Blogs::getId).map(String::valueOf).collect(Collectors.toList()); - blogIds = blogIds.subList(0, Math.min(10, blogIds.size())); + blogIds = new HashSet<>(blogIds).stream().limit(10).collect(Collectors.toList()); } blogs = listByIds(blogIds); return blogs; @@ -427,4 +451,24 @@ public class BlogsServiceImpl extends ServiceImpl implements } return hotBlogs; } + + /** + * 分类推送,根据分类推送博客 + * @param categoryId + * @return {@link List }<{@link Blogs }> + */ + @Override + public List pushBlogByCategory(Integer categoryId) { + if (categoryId == null) { + throw new IllegalArgumentException("分类id不能为空"); + } + Collection blogIds = interestPushService.listBlogsByCategoryId(categoryId); + List blogs; + + if (ObjectUtils.isEmpty(blogIds)) { + throw new IllegalArgumentException("该分类下没有博客"); + } + blogs = listByIds(blogIds); + return blogs; + } } diff --git a/src/main/java/com/xubx/springboot_01demo/service/impl/InterestPushServiceImpl.java b/src/main/java/com/xubx/springboot_01demo/service/impl/InterestPushServiceImpl.java index a05d937..1a69b52 100644 --- a/src/main/java/com/xubx/springboot_01demo/service/impl/InterestPushServiceImpl.java +++ b/src/main/java/com/xubx/springboot_01demo/service/impl/InterestPushServiceImpl.java @@ -1,6 +1,7 @@ package com.xubx.springboot_01demo.service.impl; import com.xubx.springboot_01demo.configuration.constantConfiguration; +import com.xubx.springboot_01demo.entity.pojo.Blogs; import com.xubx.springboot_01demo.mapper.BlogsMapper; import com.xubx.springboot_01demo.entity.pojo.User; import com.xubx.springboot_01demo.service.InterestPushService; @@ -104,7 +105,7 @@ public class InterestPushServiceImpl implements InterestPushService { * @return {@link List }<{@link String }> */ @Override - public List listBlogsByUserModel(User user) { + public Collection listBlogsByUserModel(User user) { // 非游客 if (!ObjectUtils.isEmpty(user)) { String userModelKey = constantConfiguration.USER_MODEL + user.getId(); @@ -129,7 +130,7 @@ public class InterestPushServiceImpl implements InterestPushService { return null; }); //TODO 根据观看历史进行已观看视频的去重 - return list.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toList()); + return list.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toSet()); } } //TODO 游客 随机获取十个标签 @@ -154,6 +155,87 @@ public class InterestPushServiceImpl implements InterestPushService { }); } + /** + * 新增博客推入分类库 + * @param blog + */ + @Override + @Async("asyncExecutor") + public void pushCategoryStockIn(Blogs blog) { + final String categoryId = String.valueOf(blog.getCategoryId()); + + // 通过分片解决redis的大key问题 + final String categoryFragmentationNumber = (String) redisTemplate.opsForValue().get(constantConfiguration.CATEGORY_BLOGS_FRAGMENTATION + categoryId); + + redisTemplate.opsForSet().add(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + categoryFragmentationNumber, String.valueOf(blog.getId())); + } + + /** + * 分类推送,根据分类随机推送视频 + * @param categoryId + * @return {@link Collection }<{@link String }> + */ + @Override + public Collection listBlogsByCategoryId(Integer categoryId) { + final String categoryFragmentationNumber = (String) redisTemplate.opsForValue().get(constantConfiguration.CATEGORY_BLOGS_FRAGMENTATION + categoryId); + List list = new ArrayList<>(); + + // 如果分片数 > 1, 推送12个,8个最新分片中的博客,4个随机分片中的博客 + if (Integer.parseInt(categoryFragmentationNumber) > 1) { + final List listFinal = redisTemplate.opsForSet().randomMembers(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + categoryFragmentationNumber, 8); + // 获取1 - 分片数的随机数 + final Random random = new Random(); + final int randomFragmentationNumber = random.nextInt(Integer.parseInt(categoryFragmentationNumber) - 1) + 1; + final List listRandom = redisTemplate.opsForSet().randomMembers(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + randomFragmentationNumber, 4); + list.addAll(listFinal); + list.addAll(listRandom); + } else { + list = redisTemplate.opsForSet().randomMembers(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + categoryFragmentationNumber, 12); + } + + // 可能有null + final HashSet result = new HashSet<>(); + for (Object o : list) { + if (o != null) { + result.add(o.toString()); + } + } + + return result; + } + + /** + * 删除分类库中博客 + * @param blog + */ + @Override + @Async("asyncExecutor") + public void deletCategoryStockIn(Blogs blog) { + final String categoryId = String.valueOf(blog.getCategoryId()); + final String categoryFragmentationNumber = blog.getCategoryFragmentation();//冗余分片数字段 + + redisTemplate.opsForSet().remove(constantConfiguration.CATEGORY_BLOGS + categoryId + ":" + categoryFragmentationNumber, String.valueOf(blog.getId())); + } + + /** + * 删除标签库中的博客 + * @param blog + */ + @Override + @Async("asyncExecutor") + public void deletTagsStockIn(Blogs blog) { + final Integer blogId = blog.getId(); + //获取该博客标签列表 + List tags =blogsMapper.getTagsByBlogId(blogId); + //使用pipelined批量执行 + redisTemplate.executePipelined((RedisCallback) connection -> { + for (String tag : tags) { + connection.sRem((constantConfiguration.TAG_BLOGS + tag).getBytes(), String.valueOf(blogId).getBytes()); + } + return null; + }); + } + /** * 构造概率数组,保存的元素是标签 -> probabilityArray = ["标签A", "标签A", "标签A", "标签B", "标签B", "标签C"] *