暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基于Java+SpringBoot+Vue前后端分离博客系统设计与实现

卡布奇诺海晨 2023-02-03
714

博主介绍:✌全网粉丝3W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战✌

博主作品:《微服务实战》专栏是本人的实战经验总结,《Spring家族及微服务系列》专注Spring、SpringMVC、SpringBoot、SpringCloud系列、Nacos等源码解读、热门面试题、架构设计等。除此之外还有不少文章等你来细细品味,更多惊喜等着你哦

开源项目:码云搜索CeaM/boot-mall

🍅文末关注公众号获取联系🍅精彩专栏推荐订阅👇🏻👇🏻 不然下次找不到哟

前言

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 = "文章模块")
    @RestController
    public class ArticleController {
    @Autowired
    private ArticleService articleService;
    @Autowired
    private UploadStrategyContext uploadStrategyContext;
    @Autowired
    private 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
        @Slf4j
        public class ArticleServiceImpl extends ServiceImpl<ArticleDao, Article> implements ArticleService {
        @Autowired
        private ArticleDao articleDao;
        @Autowired
        private CategoryDao categoryDao;
        @Autowired
        private TagDao tagDao;
        @Autowired
        private TagService tagService;
        @Autowired
        private ArticleTagDao articleTagDao;
        @Autowired
        private SearchStrategyContext searchStrategyContext;
        @Autowired
        private HttpSession session;
        @Autowired
        private RedisService redisService;
        @Autowired
        private ArticleTagService articleTagService;
        @Autowired
        private BlogInfoService blogInfoService;
        @Autowired
        private UploadStrategyContext uploadStrategyContext;

        @Override
        public 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());
        }

        @Override
        public 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);
        }

        @Override
        public List<ArticleHomeDTO> listArticles() {
        return articleDao.listArticles(PageUtils.getLimitCurrent(), PageUtils.getSize());
        }

        @Override
        public 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();
        }

        @Override
        public 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;
        }


        @Override
        public void saveArticleLike(Integer articleId) {
        // 判断是否点赞
        String articleLikeKey = ARTICLE_USER_LIKE + UserUtils.getLoginUser().getUserInfoId();
        if (redisService.sIsMember(articleLikeKey, articleId)) {
        // 点过赞则删除文章id
        redisService.sRemove(articleLikeKey, articleId);
        // 文章点赞量-1
        redisService.hDecr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
        } else {
        // 未点赞则增加文章id
        redisService.sAdd(articleLikeKey, articleId);
        // 文章点赞量+1
        redisService.hIncr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
        }
        }

        @Transactional(rollbackFor = Exception.class)
        @Override
        public 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;
        }

        @Override
        public void updateArticleTop(ArticleTopVO articleTopVO) {
        // 修改文章置顶状态
        Article article = Article.builder().id(articleTopVO.getId()).isTop(articleTopVO.getIsTop()).build();
        articleDao.updateById(article);
        }

        @Override
        public 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)
        @Override
        public void deleteArticles(List<Integer> articleIdList) {
        // 删除文章标签关联
        articleTagDao.delete(new LambdaQueryWrapper<ArticleTag>().in(ArticleTag::getArticleId, articleIdList));
        // 删除文章
        articleDao.deleteBatchIds(articleIdList);
        }

        @Override
        public 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;
        }

        @Override
        public List<ArticleSearchDTO> listArticlesBySearch(ConditionVO condition) {
        return searchStrategyContext.executeSearchStrategy(condition.getKeywords());
        }

        @Override
        public 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);
        // 浏览量+1
        redisService.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);
        }
        }

        }

        非开源,想获取源码,关注公众号获取微信联系方式

        文章转载自卡布奇诺海晨,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论