博主介绍:✌全网粉丝3W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战✌
博主作品:《微服务实战》专栏是本人的实战经验总结,《Spring家族及微服务系列》专注Spring、SpringMVC、SpringBoot、SpringCloud系列、Nacos等源码解读、热门面试题、架构设计等。除此之外还有不少文章等你来细细品味,更多惊喜等着你哦
开源项目:点击这里克隆或者下载
🍅文末关注公众号获取联系🍅精彩专栏推荐订阅👇🏻👇🏻 不然下次找不到哟
✨【微服务】Nacos为什么丢弃短连接(http)而选择拥抱长连接(gRPC)
目录
一、前言
1.导言
1.1编写目的
1.2项目范围
1.3参考资料
2.项目介绍
2.1项目背景
2.2项目目标
二、系统设计
1、系统运行环境
2、系统架构设计
三、功能需求简介
1、系统角色分析
1.1、游客
1.2、会员
1.3管理员
2、用例分析与设计
2.1、管理员
2.2、博主
2.3、游客编辑
四、功能截图
1、管理员
1.1、文章管理
1.2、消息管理
1.3、用户管理
1.4、权限管理
1.5、系统管理
1.6、相册管理
1.7、说说管理
2、博主
2.1、首页
2.2、点击测试文章进来
五、实现代码
1、文章控制器
2、服务接口
3、实现类
六、获取源码
一、前言
1.导言
1.1编写目的
该文档描述了个人博客网站的功能和性能的要求,将作为对该项目在概要设计阶段的设计输入。
本文档的预期读者包括:
1.设计开发人员
2.项日管理人员
3.测试人员
4.用户
1.2项目范围
该文档的月的是解决整个项月系统中“做仁么”的问题。对于开发技术在这里并没有涉及,而+要是通过建立模型的方式来描述用户的需求为,客户、用户,开发方等不同参与方提供一个交流的平台。
1.3参考资料
[1]《软件工程导论第6版》 张海潘等清华大学出版社
[2]《软件项目管理案例教程第3版》韩万江等,机械工业出版社
2.项目介绍
2.1项目背景
博客是使用特定的软件,在网络上出版、发表和张贴个人文章的人,或者是一种通常由个人管理、不定期张贴新的文章的网站。博客上的文章通常以网页形式出现,并根据张贴时间,以倒序排列。博客是继MSN、BBS、ICQ之后出现的第4种网络交流方式,现已受到大家的欢迎,是网络时代的个人“读者文摘”,是以超级链接为入口的网络日记,它代表着新的生活、工作和学习方式。许多博客专注在特定的课题上提供评论或新闻,其他则被作为个人性的日记。一个典型的博客结合了文字、图像、其他博客或网站的链接及其它与主题相关的媒体,能够让读者以互动的方式留下意见,是许多博客的重要要素。大部分的博客内容以文字为主,但仍有一些博客专注在艺术、摄影、视频、音乐、播客等各种主题
2.2项目目标
开发一个具有开放体系结构的、易扩充的、易维护的、具有良好人机交互界面的个人博客网站,为网络用户提供进行在线交流的网络平台。
通过个人博客网站可以结交更多的朋友,表达更多的想法,它随时可以发布文章。
二、系统设计
1、系统运行环境
开发系统:Windows10左右
架构模式:MVC/前后端分离
JDK版本:Java JDK1.8
开发工具:IDEA
数据库版本:mysql5.7
数据库可视化工具:SQL yog或Navicat for MySQL
服务器:SpringBoot自带 apache tomcat
主要技术:Java、SpringBoot2、MyBatis-plus、SpringSecurity、MySQL、RabbitMQ、ES、Html、Vue、Elementui等,建造者模式、策略模式等
2、系统架构设计
三、功能需求简介
1、系统角色分析
角色是指与系统产生交互的外部用户或者外部系统。本系统的使用角色主要有游客、会员、管理员三种。以下是详细介绍
1.1、游客
游客是第一次进入网站,但未进行注册,进行部分功能试用,在具体的功能实现上,可以分为以下几个部分:
1.1.1.搜索和浏览他人的博客
游客不须登录系统就可以实现查看日志照片以及博客主的资料信息。
1.1.2.用户注册
游客将个人的信息存储到博客网站的数据库中,以成为本博客的正式用户。
1.2、会员
通过计算机网络将前台与后台的数据库相连,系统用户将从前台得到的信息进行处理,实现文章管理,信息管理,个人相册管理,评论,留言等子系统。
1.2.1.博客管理
注册用户员对网站中自己的文章进行删除,更新等操作。
1.2.2.信息管理
发布,更改个人资料信息。
1.2.3.个人相册管理
对博客相册中的图片进行上传,更新,删除操作。
1.2.4.好友管理
添加或删除好友。
1.2.5.评论
对于他人给自己的不恰当评论予以删除。
1.2.6.留言
对他人给自己的留言进行回复或删除。
1.3管理员
1.3.1.用户管理
对已注册的用户进行管理。
1.3.2.评论、留言管理
对已注册的用户发表的评论和留言进行管理。
1.3.3.相册管理
对已注册用户上传的照片进行审核,严禁上传不和谐的照片。
1.3.4.博客管理
对用户已发表的博文进行管理,规范其内容,屏蔽掉一些不健康或反动的言论。
2、用例分析与设计
分析现有情况及问题,将个人博客系统划分为三个功能用例:游客用例、用户用例、管理员用例。
在个人博客系统中,管理员要让每个博客申请个人博客账号,并让博客设置个人密码,账户内存储每个博客的个人信息。有账号的博客用员可以通过管理员浏览好友动态、写博文、分享博文等。每个博客浏览的范围、期限不同,可通过互联网或登录个人博客网站查询个人信息和其他情况。
登录个人博客主页时,先输入博客的账号和密码,系统验证该帐号的有效性,无效则提示其原因,有效则显示博客的主页信息,供管理员人工核对。然后可以进行浏览动态,添加应用等一些功能。
1系统总体用例
2.1、管理员
2.2、博主
2.3、游客
四、功能截图
1、管理员
登录界面:
首页:
1.1、文章管理
1)发布文章
2)文章列表
3)分类管理
4)标签管理
1.2、消息管理
1)评论管理
2)留言管理
1.3、用户管理
1)用户列表
2)在线用户
1.4、权限管理
1)角色管理
2)接口管理
3)菜单管理
1.5、系统管理
1)友链管理
1.6、相册管理
1)相册列表
1.7、说说管理
1)发布说说
2)说说列表
2、博主
博主也有很多功能界面,这里就不一一举出了。
2.1、首页
可以播放音乐,试听结合
2.2、点击测试文章进来
五、实现代码
1、文章控制器
@Api(tags = "文章模块")@RestControllerpublic class ArticleController {@Autowiredprivate ArticleService articleService;@Autowiredprivate UploadStrategyContext uploadStrategyContext;@Autowiredprivate ArticleImportStrategyContext articleImportStrategyContext;/*** 查看文章归档** @return {@link Result<ArchiveDTO>} 文章归档列表*/@ApiOperation(value = "查看文章归档")@GetMapping("/articles/archives")public Result<PageResult<ArchiveDTO>> listArchives() {return Result.ok(articleService.listArchives());}/*** 查看首页文章** @return {@link Result<ArticleHomeDTO>} 首页文章列表*/@ApiOperation(value = "查看首页文章")@GetMapping("/articles")public Result<List<ArticleHomeDTO>> listArticles() {return Result.ok(articleService.listArticles());}/*** 查看后台文章** @param conditionVO 条件* @return {@link Result<ArticleBackDTO>} 后台文章列表*/@ApiOperation(value = "查看后台文章")@GetMapping("/admin/articles")public Result<PageResult<ArticleBackDTO>> listArticleBacks(ConditionVO conditionVO) {return Result.ok(articleService.listArticleBacks(conditionVO));}/*** 添加或修改文章** @param articleVO 文章信息* @return {@link Result<>}*/@OptLog(optType = SAVE_OR_UPDATE)@ApiOperation(value = "添加或修改文章")@PostMapping("/admin/articles")public Result<?> saveOrUpdateArticle(@Valid @RequestBody ArticleVO articleVO) {articleService.saveOrUpdateArticle(articleVO);return Result.ok();}/*** 修改文章置顶状态** @param articleTopVO 文章置顶信息* @return {@link Result<>}*/@OptLog(optType = UPDATE)@ApiOperation(value = "修改文章置顶")@PutMapping("/admin/articles/top")public Result<?> updateArticleTop(@Valid @RequestBody ArticleTopVO articleTopVO) {articleService.updateArticleTop(articleTopVO);return Result.ok();}/*** 恢复或删除文章** @param deleteVO 逻辑删除信息* @return {@link Result<>}*/@OptLog(optType = UPDATE)@ApiOperation(value = "恢复或删除文章")@PutMapping("/admin/articles")public Result<?> updateArticleDelete(@Valid @RequestBody DeleteVO deleteVO) {articleService.updateArticleDelete(deleteVO);return Result.ok();}/*** 上传文章图片** @param file 文件* @return {@link Result<String>} 文章图片地址*/@ApiOperation(value = "上传文章图片")@ApiImplicitParam(name = "file", value = "文章图片", required = true, dataType = "MultipartFile")@PostMapping("/admin/articles/images")public Result<String> saveArticleImages(MultipartFile file) {return Result.ok(uploadStrategyContext.executeUploadStrategy(file, FilePathEnum.ARTICLE.getPath()));}/*** 删除文章** @param articleIdList 文章id列表* @return {@link Result<>}*/@OptLog(optType = REMOVE)@ApiOperation(value = "物理删除文章")@DeleteMapping("/admin/articles")public Result<?> deleteArticles(@RequestBody List<Integer> articleIdList) {articleService.deleteArticles(articleIdList);return Result.ok();}/*** 根据id查看后台文章** @param articleId 文章id* @return {@link Result<ArticleVO>} 后台文章*/@ApiOperation(value = "根据id查看后台文章")@ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "Integer")@GetMapping("/admin/articles/{articleId}")public Result<ArticleVO> getArticleBackById(@PathVariable("articleId") Integer articleId) {return Result.ok(articleService.getArticleBackById(articleId));}/*** 根据id查看文章** @param articleId 文章id* @return {@link Result<ArticleDTO>} 文章信息*/@ApiOperation(value = "根据id查看文章")@ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "Integer")@GetMapping("/articles/{articleId}")public Result<ArticleDTO> getArticleById(@PathVariable("articleId") Integer articleId) {return Result.ok(articleService.getArticleById(articleId));}/*** 根据条件查询文章** @param condition 条件* @return {@link Result<ArticlePreviewListDTO>} 文章列表*/@ApiOperation(value = "根据条件查询文章")@GetMapping("/articles/condition")public Result<ArticlePreviewListDTO> listArticlesByCondition(ConditionVO condition) {return Result.ok(articleService.listArticlesByCondition(condition));}/*** 搜索文章** @param condition 条件* @return {@link Result<ArticleSearchDTO>} 文章列表*/@ApiOperation(value = "搜索文章")@GetMapping("/articles/search")public Result<List<ArticleSearchDTO>> listArticlesBySearch(ConditionVO condition) {return Result.ok(articleService.listArticlesBySearch(condition));}/*** 点赞文章** @param articleId 文章id* @return {@link Result<>}*/@ApiOperation(value = "点赞文章")@ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "Integer")@PostMapping("/articles/{articleId}/like")public Result<?> saveArticleLike(@PathVariable("articleId") Integer articleId) {articleService.saveArticleLike(articleId);return Result.ok();}/*** 导出文章** @param articleIdList 文章id列表* @return {@link List<String>} 文件url列表*/@ApiOperation(value = "导出文章")@ApiImplicitParam(name = "articleIdList", value = "文章id", required = true, dataType = "List<Integer>")@PostMapping("/admin/articles/export")public Result<List<String>> exportArticles(@RequestBody List<Integer> articleIdList) {return Result.ok(articleService.exportArticles(articleIdList));}/*** 导入文章** @param file 文件* @param type 文章类型* @return {@link Result<>}*/@ApiOperation(value = "导入文章")@PostMapping("/admin/articles/import")public Result<?> importArticles(MultipartFile file, @RequestParam(required = false) String type) {articleImportStrategyContext.importArticles(file, type);return Result.ok();}}
2、服务接口
public interface ArticleService extends IService<Article> {/*** 查询文章归档** @return 文章归档*/PageResult<ArchiveDTO> listArchives();/*** 查询后台文章** @param condition 条件* @return 文章列表*/PageResult<ArticleBackDTO> listArticleBacks(ConditionVO condition);/*** 查询首页文章** @return 文章列表*/List<ArticleHomeDTO> listArticles();/*** 根据条件查询文章列表** @param condition 条件* @return 文章列表*/ArticlePreviewListDTO listArticlesByCondition(ConditionVO condition);/*** 搜索文章** @param condition 条件* @return 文章列表*/List<ArticleSearchDTO> listArticlesBySearch(ConditionVO condition);/*** 根据id查看后台文章** @param articleId 文章id* @return 文章列表*/ArticleVO getArticleBackById(Integer articleId);/*** 根据id查看文章** @param articleId 文章id* @return {@link ArticleDTO} 文章信息*/ArticleDTO getArticleById(Integer articleId);/*** 点赞文章** @param articleId 文章id*/void saveArticleLike(Integer articleId);/*** 添加或修改文章** @param articleVO 文章信息*/void saveOrUpdateArticle(ArticleVO articleVO);/*** 修改文章置顶** @param articleTopVO 文章置顶信息*/void updateArticleTop(ArticleTopVO articleTopVO);/*** 删除或恢复文章** @param deleteVO 逻辑删除对象*/void updateArticleDelete(DeleteVO deleteVO);/*** 物理删除文章** @param articleIdList 文章id集合*/void deleteArticles(List<Integer> articleIdList);/*** 导出文章** @param articleIdList 文章id列表* @return {@link List}<{@link String}> 文件地址*/List<String> exportArticles(List<Integer> articleIdList);}
3、实现类
@Service@Slf4jpublic class ArticleServiceImpl extends ServiceImpl<ArticleDao, Article> implements ArticleService {@Autowiredprivate ArticleDao articleDao;@Autowiredprivate CategoryDao categoryDao;@Autowiredprivate TagDao tagDao;@Autowiredprivate TagService tagService;@Autowiredprivate ArticleTagDao articleTagDao;@Autowiredprivate SearchStrategyContext searchStrategyContext;@Autowiredprivate HttpSession session;@Autowiredprivate RedisService redisService;@Autowiredprivate ArticleTagService articleTagService;@Autowiredprivate BlogInfoService blogInfoService;@Autowiredprivate UploadStrategyContext uploadStrategyContext;@Overridepublic PageResult<ArchiveDTO> listArchives() {Page<Article> page = new Page<>(PageUtils.getCurrent(), PageUtils.getSize());// 获取分页数据Page<Article> articlePage = articleDao.selectPage(page, new LambdaQueryWrapper<Article>().select(Article::getId, Article::getArticleTitle, Article::getCreateTime).orderByDesc(Article::getCreateTime).eq(Article::getIsDelete, FALSE).eq(Article::getStatus, PUBLIC.getStatus()));List<ArchiveDTO> archiveDTOList = BeanCopyUtils.copyList(articlePage.getRecords(), ArchiveDTO.class);return new PageResult<>(archiveDTOList, (int) articlePage.getTotal());}@Overridepublic PageResult<ArticleBackDTO> listArticleBacks(ConditionVO condition) {// 查询文章总量Integer count = articleDao.countArticleBacks(condition);if (count == 0) {return new PageResult<>();}// 查询后台文章List<ArticleBackDTO> articleBackDTOList = articleDao.listArticleBacks(PageUtils.getLimitCurrent(), PageUtils.getSize(), condition);// 查询文章点赞量和浏览量Map<Object, Double> viewsCountMap = redisService.zAllScore(ARTICLE_VIEWS_COUNT);Map<String, Object> likeCountMap = redisService.hGetAll(ARTICLE_LIKE_COUNT);// 封装点赞量和浏览量articleBackDTOList.forEach(item -> {Double viewsCount = viewsCountMap.get(item.getId());if (Objects.nonNull(viewsCount)) {item.setViewsCount(viewsCount.intValue());}item.setLikeCount((Integer) likeCountMap.get(item.getId().toString()));});return new PageResult<>(articleBackDTOList, count);}@Overridepublic List<ArticleHomeDTO> listArticles() {return articleDao.listArticles(PageUtils.getLimitCurrent(), PageUtils.getSize());}@Overridepublic ArticlePreviewListDTO listArticlesByCondition(ConditionVO condition) {// 查询文章List<ArticlePreviewDTO> articlePreviewDTOList = articleDao.listArticlesByCondition(PageUtils.getLimitCurrent(), PageUtils.getSize(), condition);// 搜索条件对应名(标签或分类名)String name;if (Objects.nonNull(condition.getCategoryId())) {name = categoryDao.selectOne(new LambdaQueryWrapper<Category>().select(Category::getCategoryName).eq(Category::getId, condition.getCategoryId())).getCategoryName();} else {name = tagService.getOne(new LambdaQueryWrapper<Tag>().select(Tag::getTagName).eq(Tag::getId, condition.getTagId())).getTagName();}return ArticlePreviewListDTO.builder().articlePreviewDTOList(articlePreviewDTOList).name(name).build();}@Overridepublic ArticleDTO getArticleById(Integer articleId) {// 查询推荐文章CompletableFuture<List<ArticleRecommendDTO>> recommendArticleList = CompletableFuture.supplyAsync(() -> articleDao.listRecommendArticles(articleId));// 查询最新文章CompletableFuture<List<ArticleRecommendDTO>> newestArticleList = CompletableFuture.supplyAsync(() -> {List<Article> articleList = articleDao.selectList(new LambdaQueryWrapper<Article>().select(Article::getId, Article::getArticleTitle, Article::getArticleCover, Article::getCreateTime).eq(Article::getIsDelete, FALSE).eq(Article::getStatus, PUBLIC.getStatus()).orderByDesc(Article::getId).last("limit 5"));return BeanCopyUtils.copyList(articleList, ArticleRecommendDTO.class);});// 查询id对应文章ArticleDTO article = articleDao.getArticleById(articleId);if (Objects.isNull(article)) {throw new BizException("文章不存在");}// 更新文章浏览量updateArticleViewsCount(articleId);// 查询上一篇下一篇文章Article lastArticle = articleDao.selectOne(new LambdaQueryWrapper<Article>().select(Article::getId, Article::getArticleTitle, Article::getArticleCover).eq(Article::getIsDelete, FALSE).eq(Article::getStatus, PUBLIC.getStatus()).lt(Article::getId, articleId).orderByDesc(Article::getId).last("limit 1"));Article nextArticle = articleDao.selectOne(new LambdaQueryWrapper<Article>().select(Article::getId, Article::getArticleTitle, Article::getArticleCover).eq(Article::getIsDelete, FALSE).eq(Article::getStatus, PUBLIC.getStatus()).gt(Article::getId, articleId).orderByAsc(Article::getId).last("limit 1"));article.setLastArticle(BeanCopyUtils.copyObject(lastArticle, ArticlePaginationDTO.class));article.setNextArticle(BeanCopyUtils.copyObject(nextArticle, ArticlePaginationDTO.class));// 封装点赞量和浏览量Double score = redisService.zScore(ARTICLE_VIEWS_COUNT, articleId);if (Objects.nonNull(score)) {article.setViewsCount(score.intValue());}article.setLikeCount((Integer) redisService.hGet(ARTICLE_LIKE_COUNT, articleId.toString()));// 封装文章信息try {article.setRecommendArticleList(recommendArticleList.get());article.setNewestArticleList(newestArticleList.get());} catch (Exception e) {log.error(StrUtil.format("堆栈信息:{}", ExceptionUtil.stacktraceToString(e)));}return article;}@Overridepublic void saveArticleLike(Integer articleId) {// 判断是否点赞String articleLikeKey = ARTICLE_USER_LIKE + UserUtils.getLoginUser().getUserInfoId();if (redisService.sIsMember(articleLikeKey, articleId)) {// 点过赞则删除文章idredisService.sRemove(articleLikeKey, articleId);// 文章点赞量-1redisService.hDecr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);} else {// 未点赞则增加文章idredisService.sAdd(articleLikeKey, articleId);// 文章点赞量+1redisService.hIncr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);}}@Transactional(rollbackFor = Exception.class)@Overridepublic void saveOrUpdateArticle(ArticleVO articleVO) {// 查询博客配置信息CompletableFuture<WebsiteConfigVO> webConfig = CompletableFuture.supplyAsync(() -> blogInfoService.getWebsiteConfig());// 保存文章分类Category category = saveArticleCategory(articleVO);// 保存或修改文章Article article = BeanCopyUtils.copyObject(articleVO, Article.class);if (Objects.nonNull(category)) {article.setCategoryId(category.getId());}// 设定默认文章封面if (StrUtil.isBlank(article.getArticleCover())){try {article.setArticleCover(webConfig.get().getArticleCover());} catch (Exception e) {throw new BizException("设定默认文章封面失败");}}article.setUserId(UserUtils.getLoginUser().getUserInfoId());this.saveOrUpdate(article);// 保存文章标签saveArticleTag(articleVO, article.getId());}/*** 保存文章分类** @param articleVO 文章信息* @return {@link Category} 文章分类*/private Category saveArticleCategory(ArticleVO articleVO) {// 判断分类是否存在Category category = categoryDao.selectOne(new LambdaQueryWrapper<Category>().eq(Category::getCategoryName, articleVO.getCategoryName()));if (Objects.isNull(category) && !articleVO.getStatus().equals(DRAFT.getStatus())) {category = Category.builder().categoryName(articleVO.getCategoryName()).build();categoryDao.insert(category);}return category;}@Overridepublic void updateArticleTop(ArticleTopVO articleTopVO) {// 修改文章置顶状态Article article = Article.builder().id(articleTopVO.getId()).isTop(articleTopVO.getIsTop()).build();articleDao.updateById(article);}@Overridepublic void updateArticleDelete(DeleteVO deleteVO) {// 修改文章逻辑删除状态List<Article> articleList = deleteVO.getIdList().stream().map(id -> Article.builder().id(id).isTop(FALSE).isDelete(deleteVO.getIsDelete()).build()).collect(Collectors.toList());this.updateBatchById(articleList);}@Transactional(rollbackFor = Exception.class)@Overridepublic void deleteArticles(List<Integer> articleIdList) {// 删除文章标签关联articleTagDao.delete(new LambdaQueryWrapper<ArticleTag>().in(ArticleTag::getArticleId, articleIdList));// 删除文章articleDao.deleteBatchIds(articleIdList);}@Overridepublic List<String> exportArticles(List<Integer> articleIdList) {// 查询文章信息List<Article> articleList = articleDao.selectList(new LambdaQueryWrapper<Article>().select(Article::getArticleTitle, Article::getArticleContent).in(Article::getId, articleIdList));// 写入文件并上传List<String> urlList = new ArrayList<>();for (Article article : articleList) {try (ByteArrayInputStream inputStream = new ByteArrayInputStream(article.getArticleContent().getBytes())) {String url = uploadStrategyContext.executeUploadStrategy(article.getArticleTitle() + FileExtEnum.MD.getExtName(), inputStream, FilePathEnum.MD.getPath());urlList.add(url);} catch (Exception e) {log.error(StrUtil.format("导入文章失败,堆栈:{}", ExceptionUtil.stacktraceToString(e)));throw new BizException("导出文章失败");}}return urlList;}@Overridepublic List<ArticleSearchDTO> listArticlesBySearch(ConditionVO condition) {return searchStrategyContext.executeSearchStrategy(condition.getKeywords());}@Overridepublic ArticleVO getArticleBackById(Integer articleId) {// 查询文章信息Article article = articleDao.selectById(articleId);// 查询文章分类Category category = categoryDao.selectById(article.getCategoryId());String categoryName = null;if (Objects.nonNull(category)) {categoryName = category.getCategoryName();}// 查询文章标签List<String> tagNameList = tagDao.listTagNameByArticleId(articleId);// 封装数据ArticleVO articleVO = BeanCopyUtils.copyObject(article, ArticleVO.class);articleVO.setCategoryName(categoryName);articleVO.setTagNameList(tagNameList);return articleVO;}/*** 更新文章浏览量** @param articleId 文章id*/public void updateArticleViewsCount(Integer articleId) {// 判断是否第一次访问,增加浏览量Set<Integer> articleSet = CommonUtils.castSet(Optional.ofNullable(session.getAttribute(ARTICLE_SET)).orElseGet(HashSet::new), Integer.class);if (!articleSet.contains(articleId)) {articleSet.add(articleId);session.setAttribute(ARTICLE_SET, articleSet);// 浏览量+1redisService.zIncr(ARTICLE_VIEWS_COUNT, articleId, 1D);}}/*** 保存文章标签** @param articleVO 文章信息*/private void saveArticleTag(ArticleVO articleVO, Integer articleId) {// 编辑文章则删除文章所有标签if (Objects.nonNull(articleVO.getId())) {articleTagDao.delete(new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getArticleId, articleVO.getId()));}// 添加文章标签List<String> tagNameList = articleVO.getTagNameList();if (CollectionUtils.isNotEmpty(tagNameList)) {// 查询已存在的标签List<Tag> existTagList = tagService.list(new LambdaQueryWrapper<Tag>().in(Tag::getTagName, tagNameList));List<String> existTagNameList = existTagList.stream().map(Tag::getTagName).collect(Collectors.toList());List<Integer> existTagIdList = existTagList.stream().map(Tag::getId).collect(Collectors.toList());// 对比新增不存在的标签tagNameList.removeAll(existTagNameList);if (CollectionUtils.isNotEmpty(tagNameList)) {List<Tag> tagList = tagNameList.stream().map(item -> Tag.builder().tagName(item).build()).collect(Collectors.toList());tagService.saveBatch(tagList);List<Integer> tagIdList = tagList.stream().map(Tag::getId).collect(Collectors.toList());existTagIdList.addAll(tagIdList);}// 提取标签id绑定文章List<ArticleTag> articleTagList = existTagIdList.stream().map(item -> ArticleTag.builder().articleId(articleId).tagId(item).build()).collect(Collectors.toList());articleTagService.saveBatch(articleTagList);}}}
六、获取源码
大家点赞、收藏、关注、评论啦、关注下方公众号获取联系方式👇🏻👇🏻