• 厂里资讯之自媒体文章自动审核


    自媒体文章-自动审核

    1)自媒体文章自动审核流程

    1 自媒体端发布文章后,开始审核文章

    2 审核的主要是审核文章的内容(文本内容和图片)

    3 借助第三方提供的接口审核文本

    4 借助第三方提供的接口审核图片,由于图片存储到minIO中,需要先下载才能审核

    5 如果审核失败,则需要修改自媒体文章的状态,status:2 审核失败 status:3 转到人工审核

    6 如果审核成功,则需要在文章微服务中创建app端需要的文章

    2)内容安全第三方接口

    2.1)概述

    内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。

    目前很多平台都支持内容检测,如阿里云、腾讯云、百度AI、网易云等国内大型互联网公司都对外提供了API。

    按照性能和收费来看,黑马头条项目使用的就是阿里云的内容安全接口,使用到了图片和文本的审核。

    阿里云收费标准:https://www.aliyun.com/price/product/?spm=a2c4g.11186623.2.10.4146401eg5oeu8#/lvwang/detail

    2.2)准备工作

    您在使用内容检测API之前,需要先注册阿里云账号,添加Access Key并签约云盾内容安全。

    操作步骤

    1. 前往阿里云官网注册账号。如果已有注册账号,请跳过此步骤。

      进入阿里云首页后,如果没有阿里云的账户需要先进行注册,才可以进行登录。由于注册较为简单,课程和讲义不在进行体现(注册可以使用多种方式,如淘宝账号、支付宝账号、微博账号等...)。

      需要实名认证和活体认证。

    2. 打开云盾内容安全产品试用页面,单击立即开通,正式开通服务。

    内容安全控制台

    3.在AccessKey管理页面管理您的AccessKeyID和AccessKeySecret。

    管理自己的AccessKey,可以新建和删除AccessKey

    查看自己的AccessKey,

    AccessKey默认是隐藏的,第一次申请的时候可以保存AccessKey,点击显示,通过验证手机号后也可以查看

    2.3)文本内容审核接口

    文本垃圾内容检测:如何调用文本检测接口进行文本内容审核_内容安全(Content Moderation)-阿里云帮助中心

    文本垃圾内容Java SDK: 如何使用JavaSDK文本反垃圾接口_内容安全(Content Moderation)-阿里云帮助中心

    2.4)图片审核接口

    图片垃圾内容检测:调用图片同步检测接口/green/image/scan审核图片内容_内容安全(Content Moderation)-阿里云帮助中心

    图片垃圾内容Java SDK: 如何使用JavaSDK接口检测图片是否包含风险内容_内容安全(Content Moderation)-阿里云帮助中心

    2.5)项目集成

    ①:从官网上拷贝工具类到common模块下面,并添加到自动配置

    包括了GreenImageScan和GreenTextScan及对应的工具类

    添加到自动配置中

    ②: accessKeyId和secret(需自己申请)

    在changli-Information-wemedia中的nacos配置中心添加以下配置:

    1. aliyun:
    2. accessKeyId: LTAI5tCWHCcfvqQzu8k2oKmX
    3. secret: auoKUFsghimbfVQHpy7gtRyBkoR4vc
    4. #aliyun.scenes=porn,terrorism,ad,qrcode,live,logo
    5. scenes: terrorism

    ③:在自媒体微服务中测试类中注入审核文本和图片的bean进行测试

    1. package com.kjz.wemedia;
    2. import com.kjz.common.aliyun.GreenImageScan;
    3. import com.kjz.common.aliyun.GreenTextScan;
    4. import com.kjz.file.service.FileStorageService;
    5. import org.junit.Test;
    6. import org.junit.runner.RunWith;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.boot.test.context.SpringBootTest;
    9. import org.springframework.test.context.junit4.SpringRunner;
    10. import java.util.Arrays;
    11. import java.util.Map;
    12. @SpringBootTest(classes = WemediaApplication.class)
    13. @RunWith(SpringRunner.class)
    14. public class AliyunTest {
    15.    @Autowired
    16.    private GreenTextScan greenTextScan;
    17.    @Autowired
    18.    private GreenImageScan greenImageScan;
    19.    @Autowired
    20.    private FileStorageService fileStorageService;
    21.    @Test
    22.    public void testScanText() throws Exception {
    23.        Map map = greenTextScan.greeTextScan("我是一个好人,冰毒");
    24.        System.out.println(map);
    25.   }
    26.    @Test
    27.    public void testScanImage() throws Exception {
    28.        byte[] bytes = fileStorageService.downLoadFile("http://192.168.200.130:9000/leadnews/2021/04/26/ef3cbe458db249f7bd6fb4339e593e55.jpg");
    29.        Map map = greenImageScan.imageScan(Arrays.asList(bytes));
    30.        System.out.println(map);
    31.   }
    32. }

    3)app端文章保存接口

    3.1)表结构说明

    ap_article 文章信息表

    ap_article_config 文章配置表

    ap_article_content 文章内容表

    3.2)分布式id

    随着业务的增长,文章表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID

    snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0

    文章端相关的表都使用雪花算法生成id,包括ap_article、 ap_article_config、 ap_article_content

    mybatis-plus已经集成了雪花算法,完成以下两步即可在项目中集成雪花算法

    第一:在实体类中的id上加入如下配置,指定类型为id_worker

    1. @TableId(value = "id",type = IdType.ID_WORKER)
    2. private Long id;

    第二:在application.yml文件中配置数据中心id和机器id

    1. mybatis-plus:
    2. mapper-locations: classpath*:mapper/*.xml
    3.  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    4. type-aliases-package: com.kjz.model.article.pojos
    5. global-config:
    6.   datacenter-id: 1
    7.   workerId: 1

    datacenter-id:数据中心id(取值范围:0-31)

    workerId:机器id(取值范围:0-31)

    3.3)思路分析

    在文章审核成功以后需要在app的article库中新增文章数据

    1.保存文章信息 ap_article

    2.保存文章配置信息 ap_article_config

    3.保存文章内容 ap_article_content

    实现思路:

    3.4)feign接口
    说明
    接口路径/api/v1/article/save
    请求方式POST
    参数ArticleDto
    响应结果ResponseResult

    ArticleDto

    1. package com.kjz.model.article.dtos;
    2. import com.kjz.model.article.pojos.ApArticle;
    3. import lombok.Data;
    4. @Data
    5. public class ArticleDto  extends ApArticle {
    6.    /**
    7.     * 文章内容
    8.     */
    9.    private String content;
    10. }

    成功:

    {
      "code": 200,
      "errorMessage" : "操作成功",
      "data":"1302864436297442242"
     }

    失败:

    {
      "code":501,
      "errorMessage":"参数失效",
     }
    {
      "code":501,
      "errorMessage":"文章没有找到",
     }

    功能实现:

    ①:在changli-Information-feign-api中新增接口

    第一:导入feign的依赖

    1.    org.springframework.cloud
    2.    spring-cloud-starter-openfeign

    第二:定义文章端的接口

    1. package com.kjz.apis.article;
    2. import com.kjz.model.article.dtos.ArticleDto;
    3. import com.kjz.model.common.dtos.ResponseResult;
    4. import org.springframework.cloud.openfeign.FeignClient;
    5. import org.springframework.web.bind.annotation.PostMapping;
    6. import org.springframework.web.bind.annotation.RequestBody;
    7. import java.io.IOException;
    8. @FeignClient(value = "Information-article")
    9. public interface IArticleClient {
    10.    @PostMapping("/api/v1/article/save")
    11.    public ResponseResult saveArticle(@RequestBody ArticleDto dto) ;
    12. }

    ②:在changli-Information-article中实现该方法

    1. package com.kjz.article.feign;
    2. import com.kjz.apis.article.IArticleClient;
    3. import com.kjz.article.service.ApArticleService;
    4. import com.kjz.model.article.dtos.ArticleDto;
    5. import com.kjz.model.common.dtos.ResponseResult;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.web.bind.annotation.*;
    8. import java.io.IOException;
    9. @RestController
    10. public class ArticleClient implements IArticleClient {
    11.    @Autowired
    12.    private ApArticleService apArticleService;
    13.    @Override
    14.    @PostMapping("/api/v1/article/save")
    15.    public ResponseResult saveArticle(@RequestBody ArticleDto dto) {
    16.        return apArticleService.saveArticle(dto);
    17.   }
    18. }

    ③:创建mapper

    创建ApArticleConfigMapper类到mapper文件夹中

    同时,修改ApArticleConfig类,添加如下构造函数

    1. package com.heima.model.article.pojos;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. import lombok.NoArgsConstructor;
    8. import java.io.Serializable;
    9. /**
    10. *

    11. * APP已发布文章配置表
    12. *

    13. *
    14. * @author itheima
    15. */
    16. @Data
    17. @NoArgsConstructor
    18. @TableName("ap_article_config")
    19. public class ApArticleConfig implements Serializable {
    20.    public ApArticleConfig(Long articleId){
    21.        this.articleId = articleId;
    22.        this.isComment = true;
    23.        this.isForward = true;
    24.        this.isDelete = false;
    25.        this.isDown = false;
    26.   }
    27.    @TableId(value = "id",type = IdType.ID_WORKER)
    28.    private Long id;
    29.    /**
    30.     * 文章id
    31.     */
    32.    @TableField("article_id")
    33.    private Long articleId;
    34.    /**
    35.     * 是否可评论
    36.     * true: 可以评论   1
    37.     * false: 不可评论 0
    38.     */
    39.    @TableField("is_comment")
    40.    private Boolean isComment;
    41.    /**
    42.     * 是否转发
    43.     * true: 可以转发   1
    44.     * false: 不可转发 0
    45.     */
    46.    @TableField("is_forward")
    47.    private Boolean isForward;
    48.    /**
    49.     * 是否下架
    50.     * true: 下架   1
    51.     * false: 没有下架 0
    52.     */
    53.    @TableField("is_down")
    54.    private Boolean isDown;
    55.    /**
    56.     * 是否已删除
    57.     * true: 删除   1
    58.     * false: 没有删除 0
    59.     */
    60.    @TableField("is_delete")
    61.    private Boolean isDelete;
    62. }

    ④:在ApArticleService中新增方法

    1. /**
    2.     * 保存app端相关文章
    3.     * @param dto
    4.     * @return
    5.     */
    6. ResponseResult saveArticle(ArticleDto dto) ;

    实现类:

    1. @Autowired
    2. private ApArticleConfigMapper apArticleConfigMapper;
    3. @Autowired
    4. private ApArticleContentMapper apArticleContentMapper;
    5. /**
    6.     * 保存app端相关文章
    7.     * @param dto
    8.     * @return
    9.     */
    10. @Override
    11. public ResponseResult saveArticle(ArticleDto dto) {
    12.    //1.检查参数
    13.    if(dto == null){
    14.        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    15.   }
    16.    ApArticle apArticle = new ApArticle();
    17.    BeanUtils.copyProperties(dto,apArticle);
    18.    //2.判断是否存在id
    19.    if(dto.getId() == null){
    20.        //2.1 不存在id 保存 文章 文章配置 文章内容
    21.        //保存文章
    22.        save(apArticle);
    23.        //保存配置
    24.        ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
    25.        apArticleConfigMapper.insert(apArticleConfig);
    26.        //保存 文章内容
    27.        ApArticleContent apArticleContent = new ApArticleContent();
    28.        apArticleContent.setArticleId(apArticle.getId());
    29.        apArticleContent.setContent(dto.getContent());
    30.        apArticleContentMapper.insert(apArticleContent);
    31.   }else {
    32.        //2.2 存在id   修改 文章 文章内容
    33.        //修改 文章
    34.        updateById(apArticle);
    35.        //修改文章内容
    36.        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
    37.        apArticleContent.setContent(dto.getContent());
    38.        apArticleContentMapper.updateById(apArticleContent);
    39.   }
    40.    //3.结果返回 文章的id
    41.    return ResponseResult.okResult(apArticle.getId());
    42. }

    ⑤:测试

    编写junit单元测试,或使用postman进行测试

    {
        "id":1390209114747047938,
        "title":"厂里资讯项目背景22222222222222",
        "authoId":1102,
        "layout":1,
        "labels":"厂里资讯",
        "publishTime":"2028-03-14T11:35:49.000Z",
        "images": "http://192.168.200.130:9000/leadnews/2021/04/26/5ddbdb5c68094ce393b08a47860da275.jpg",
        "content":"22222222222222222厂里资讯项目背景,厂里资讯项目背景,厂里资讯项目背景,厂里资讯项目背景,厂里资讯项目背景"
    }

    4)自媒体文章自动审核功能实现

    4.1)表结构说明

    wm_news 自媒体文章表

    status字段:0 草稿 1 待审核 2 审核失败 3 人工审核 4 人工审核通过 8 审核通过(待发布) 9 已发布

    4.2)实现

    在changli-lnformation-wemedia中的service新增接口

    1. package com.kjz.wemedia.service;
    2. public interface WmNewsAutoScanService {
    3.    /**
    4.     * 自媒体文章审核
    5.     * @param id 自媒体文章id
    6.     */
    7.    public void autoScanWmNews(Integer id);
    8. }

    实现类:

    1. package com.kjz.wemedia.service.impl;
    2. import com.alibaba.fastjson.JSONArray;
    3. import com.heima.apis.article.IArticleClient;
    4. import com.heima.common.aliyun.GreenImageScan;
    5. import com.heima.common.aliyun.GreenTextScan;
    6. import com.heima.file.service.FileStorageService;
    7. import com.heima.model.article.dtos.ArticleDto;
    8. import com.heima.model.common.dtos.ResponseResult;
    9. import com.heima.model.wemedia.pojos.WmChannel;
    10. import com.heima.model.wemedia.pojos.WmNews;
    11. import com.heima.model.wemedia.pojos.WmUser;
    12. import com.heima.wemedia.mapper.WmChannelMapper;
    13. import com.heima.wemedia.mapper.WmNewsMapper;
    14. import com.heima.wemedia.mapper.WmUserMapper;
    15. import com.heima.wemedia.service.WmNewsAutoScanService;
    16. import lombok.extern.slf4j.Slf4j;
    17. import org.apache.commons.lang3.StringUtils;
    18. import org.springframework.beans.BeanUtils;
    19. import org.springframework.beans.factory.annotation.Autowired;
    20. import org.springframework.stereotype.Service;
    21. import org.springframework.transaction.annotation.Transactional;
    22. import java.util.*;
    23. import java.util.stream.Collectors;
    24. @Service
    25. @Slf4j
    26. @Transactional
    27. public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {
    28.    @Autowired
    29.    private WmNewsMapper wmNewsMapper;
    30.    /**
    31.     * 自媒体文章审核
    32.     *
    33.     * @param id 自媒体文章id
    34.     */
    35.    @Override
    36.    public void autoScanWmNews(Integer id) {
    37.        //1.查询自媒体文章
    38.        WmNews wmNews = wmNewsMapper.selectById(id);
    39.        if(wmNews == null){
    40.            throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");
    41.       }
    42.        if(wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())){
    43.            //从内容中提取纯文本内容和图片
    44.            Map textAndImages = handleTextAndImages(wmNews);
    45.            //2.审核文本内容 阿里云接口
    46.            boolean isTextScan = handleTextScan((String) textAndImages.get("content"),wmNews);
    47.            if(!isTextScan)return;
    48.            //3.审核图片 阿里云接口
    49.            boolean isImageScan =  handleImageScan((List) textAndImages.get("images"),wmNews);
    50.            if(!isImageScan)return;
    51.            //4.审核成功,保存app端的相关的文章数据
    52.            ResponseResult responseResult = saveAppArticle(wmNews);
    53.            if(!responseResult.getCode().equals(200)){
    54.                throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");
    55.           }
    56.            //回填article_id
    57.            wmNews.setArticleId((Long) responseResult.getData());
    58.            updateWmNews(wmNews,(short) 9,"审核成功");
    59.       }
    60.   }
    61.    @Autowired
    62.    private IArticleClient articleClient;
    63.    @Autowired
    64.    private WmChannelMapper wmChannelMapper;
    65.    @Autowired
    66.    private WmUserMapper wmUserMapper;
    67.    /**
    68.     * 保存app端相关的文章数据
    69.     * @param wmNews
    70.     */
    71.    private ResponseResult saveAppArticle(WmNews wmNews) {
    72.        ArticleDto dto = new ArticleDto();
    73.        //属性的拷贝
    74.        BeanUtils.copyProperties(wmNews,dto);
    75.        //文章的布局
    76.        dto.setLayout(wmNews.getType());
    77.        //频道
    78.        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
    79.        if(wmChannel != null){
    80.            dto.setChannelName(wmChannel.getName());
    81.       }
    82.        //作者
    83.        dto.setAuthorId(wmNews.getUserId().longValue());
    84.        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
    85.        if(wmUser != null){
    86.            dto.setAuthorName(wmUser.getName());
    87.       }
    88.        //设置文章id
    89.        if(wmNews.getArticleId() != null){
    90.            dto.setId(wmNews.getArticleId());
    91.       }
    92.        dto.setCreatedTime(new Date());
    93.        ResponseResult responseResult = articleClient.saveArticle(dto);
    94.        return responseResult;
    95.   }
    96.    @Autowired
    97.    private FileStorageService fileStorageService;
    98.    @Autowired
    99.    private GreenImageScan greenImageScan;
    100.    /**
    101.     * 审核图片
    102.     * @param images
    103.     * @param wmNews
    104.     * @return
    105.     */
    106.    private boolean handleImageScan(List images, WmNews wmNews) {
    107.        boolean flag = true;
    108.        if(images == null || images.size() == 0){
    109.            return flag;
    110.       }
    111.        //下载图片 minIO
    112.        //图片去重
    113.        images = images.stream().distinct().collect(Collectors.toList());
    114.        List<byte[]> imageList = new ArrayList<>();
    115.        for (String image : images) {
    116.            byte[] bytes = fileStorageService.downLoadFile(image);
    117.            imageList.add(bytes);
    118.       }
    119.        //审核图片
    120.        try {
    121.            Map map = greenImageScan.imageScan(imageList);
    122.            if(map != null){
    123.                //审核失败
    124.                if(map.get("suggestion").equals("block")){
    125.                    flag = false;
    126.                    updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
    127.               }
    128.                //不确定信息 需要人工审核
    129.                if(map.get("suggestion").equals("review")){
    130.                    flag = false;
    131.                    updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
    132.               }
    133.           }
    134.       } catch (Exception e) {
    135.            flag = false;
    136.            e.printStackTrace();
    137.       }
    138.        return flag;
    139.   }
    140.    @Autowired
    141.    private GreenTextScan greenTextScan;
    142.    /**
    143.     * 审核纯文本内容
    144.     * @param content
    145.     * @param wmNews
    146.     * @return
    147.     */
    148.    private boolean handleTextScan(String content, WmNews wmNews) {
    149.        boolean flag = true;
    150.        if((wmNews.getTitle()+"-"+content).length() == 0){
    151.            return flag;
    152.       }
    153.        try {
    154.            Map map = greenTextScan.greeTextScan((wmNews.getTitle()+"-"+content));
    155.            if(map != null){
    156.                //审核失败
    157.                if(map.get("suggestion").equals("block")){
    158.                    flag = false;
    159.                    updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
    160.               }
    161.                //不确定信息 需要人工审核
    162.                if(map.get("suggestion").equals("review")){
    163.                    flag = false;
    164.                    updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
    165.               }
    166.           }
    167.       } catch (Exception e) {
    168.            flag = false;
    169.            e.printStackTrace();
    170.       }
    171.        return flag;
    172.   }
    173.    /**
    174.     * 修改文章内容
    175.     * @param wmNews
    176.     * @param status
    177.     * @param reason
    178.     */
    179.    private void updateWmNews(WmNews wmNews, short status, String reason) {
    180.        wmNews.setStatus(status);
    181.        wmNews.setReason(reason);
    182.        wmNewsMapper.updateById(wmNews);
    183.   }
    184.    /**
    185.     * 1。从自媒体文章的内容中提取文本和图片
    186.     * 2.提取文章的封面图片
    187.     * @param wmNews
    188.     * @return
    189.     */
    190.    private Map handleTextAndImages(WmNews wmNews) {
    191.        //存储纯文本内容
    192.        StringBuilder stringBuilder = new StringBuilder();
    193.        List images = new ArrayList<>();
    194.        //1。从自媒体文章的内容中提取文本和图片
    195.        if(StringUtils.isNotBlank(wmNews.getContent())){
    196.            List maps = JSONArray.parseArray(wmNews.getContent(), Map.class);
    197.            for (Map map : maps) {
    198.                if (map.get("type").equals("text")){
    199.                    stringBuilder.append(map.get("value"));
    200.               }
    201.                if (map.get("type").equals("image")){
    202.                    images.add((String) map.get("value"));
    203.               }
    204.           }
    205.       }
    206.        //2.提取文章的封面图片
    207.        if(StringUtils.isNotBlank(wmNews.getImages())){
    208.            String[] split = wmNews.getImages().split(",");
    209.            images.addAll(Arrays.asList(split));
    210.       }
    211.        Map resultMap = new HashMap<>();
    212.        resultMap.put("content",stringBuilder.toString());
    213.        resultMap.put("images",images);
    214.        return resultMap;
    215.   }
    216. }
    4.3)单元测试
    1. package com.kjz.wemedia.service;
    2. import com.heima.wemedia.WemediaApplication;
    3. import org.junit.Test;
    4. import org.junit.runner.RunWith;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.boot.test.context.SpringBootTest;
    7. import org.springframework.test.context.junit4.SpringRunner;
    8. import static org.junit.Assert.*;
    9. @SpringBootTest(classes = WemediaApplication.class)
    10. @RunWith(SpringRunner.class)
    11. public class WmNewsAutoScanServiceTest {
    12.    @Autowired
    13.    private WmNewsAutoScanService wmNewsAutoScanService;
    14.    @Test
    15.    public void autoScanWmNews() {
    16.        wmNewsAutoScanService.autoScanWmNews(6238);
    17.   }
    18. }
    4.4)feign远程接口调用方式

    在heima-leadnews-wemedia服务中已经依赖了heima-leadnews-feign-apis工程,只需要在自媒体的引导类中开启feign的远程调用即可

    注解为:@EnableFeignClients(basePackages = "com.heima.apis") 需要指向apis这个包

    4.5)服务降级处理

    • 服务降级是服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃

    • 服务降级虽然会导致请求失败,但是不会导致阻塞。

    实现步骤:

    ①:在heima-leadnews-feign-api编写降级逻辑

    1. package com.heima.apis.article.fallback;
    2. import com.heima.apis.article.IArticleClient;
    3. import com.heima.model.article.dtos.ArticleDto;
    4. import com.heima.model.common.dtos.ResponseResult;
    5. import com.heima.model.common.enums.AppHttpCodeEnum;
    6. import org.springframework.stereotype.Component;
    7. /**
    8. * feign失败配置
    9. * @author itheima
    10. */
    11. @Component
    12. public class IArticleClientFallback implements IArticleClient {
    13.    @Override
    14.    public ResponseResult saveArticle(ArticleDto dto) {
    15.        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");
    16.   }
    17. }

    在自媒体微服务中添加类,扫描降级代码类的包

    1. package com.heima.wemedia.config;
    2. import org.springframework.context.annotation.ComponentScan;
    3. import org.springframework.context.annotation.Configuration;
    4. @Configuration
    5. @ComponentScan("com.heima.apis.article.fallback")
    6. public class InitConfig {
    7. }

    ②:远程接口中指向降级代码

    1. package com.heima.apis.article;
    2. import com.heima.apis.article.fallback.IArticleClientFallback;
    3. import com.heima.model.article.dtos.ArticleDto;
    4. import com.heima.model.common.dtos.ResponseResult;
    5. import org.springframework.cloud.openfeign.FeignClient;
    6. import org.springframework.web.bind.annotation.PostMapping;
    7. import org.springframework.web.bind.annotation.RequestBody;
    8. @FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class)
    9. public interface IArticleClient {
    10.    @PostMapping("/api/v1/article/save")
    11.    public ResponseResult saveArticle(@RequestBody ArticleDto dto);
    12. }

    ③:客户端开启降级heima-leadnews-wemedia

    在wemedia的nacos配置中心里添加如下内容,开启服务降级,也可以指定服务响应的超时的时间

    1. feign:
    2.  # 开启feign对hystrix熔断降级的支持
    3. hystrix:
    4.   enabled: true
    5.  # 修改调用超时时间
    6. client:
    7.   config:
    8.     default:
    9.       connectTimeout: 2000
    10.       readTimeout: 2000

    ④:测试

    在ApArticleServiceImpl类中saveArticle方法添加代码

    1. try {
    2.    Thread.sleep(3000);
    3. } catch (InterruptedException e) {
    4.    e.printStackTrace();
    5. }

    在自媒体端进行审核测试,会出现服务降级的现象

    5)发布文章提交审核集成

    5.1)同步调用与异步调用

    同步:就是在发出一个调用时,在没有得到结果之前, 该调用就不返回(实时处理)

    异步:调用在发出之后,这个调用就直接返回了,没有返回结果(分时处理)

    异步线程的方式审核文章

    5.2)Springboot集成异步线程调用

    ①:在自动审核的方法上加上@Async注解(标明要异步调用)

    1. @Override
    2. @Async  //标明当前方法是一个异步方法
    3. public void autoScanWmNews(Integer id) {
    4. //代码略
    5. }

    ②:在文章发布成功后调用审核的方法

    1. @Autowired
    2. private WmNewsAutoScanService wmNewsAutoScanService;
    3. /**
    4. * 发布修改文章或保存为草稿
    5. * @param dto
    6. * @return
    7. */
    8. @Override
    9. public ResponseResult submitNews(WmNewsDto dto) {
    10.    //代码略
    11.    //审核文章
    12.    wmNewsAutoScanService.autoScanWmNews(wmNews.getId());
    13.    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    14. }

    ③:在自媒体引导类中使用@EnableAsync注解开启异步调用

    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. @MapperScan("com.heima.wemedia.mapper")
    4. @EnableFeignClients(basePackages = "com.heima.apis")
    5. @EnableAsync  //开启异步调用
    6. public class WemediaApplication {
    7.    public static void main(String[] args) {
    8.        SpringApplication.run(WemediaApplication.class,args);
    9.   }
    10.    @Bean
    11.    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    12.        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    13.        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    14.        return interceptor;
    15.   }
    16. }

    6)文章审核功能-综合测试

    6.1)服务启动列表

    1,nacos服务端

    2,article微服务

    3,wemedia微服务

    4,启动wemedia网关微服务

    5,启动前端系统wemedia

    6.2)测试情况列表

    1,自媒体前端发布一篇正常的文章

    审核成功后,app端的article相关数据是否可以正常保存,自媒体文章状态和app端文章id是否回显

    2,自媒体前端发布一篇包含敏感词的文章

    正常是审核失败, wm_news表中的状态是否改变,成功和失败原因正常保存

    3,自媒体前端发布一篇包含敏感图片的文章

    正常是审核失败, wm_news表中的状态是否改变,成功和失败原因正常保存

    7)新需求-自管理敏感词

    7.1)需求分析

    文章审核功能已经交付了,文章也能正常发布审核。突然,产品经理过来说要开会。

    会议的内容核心有以下内容:

    • 文章审核不能过滤一些敏感词:

      私人侦探、针孔摄象、信用卡提现、广告代理、代开发票、刻章办、出售答案、小额贷款…

    需要完成的功能:

    需要自己维护一套敏感词,在文章审核的时候,需要验证文章是否包含这些敏感词

    7.2)敏感词-过滤

    技术选型

    方案说明
    数据库模糊查询效率太低
    String.indexOf("")查找数据量大的话也是比较慢
    全文检索分词再匹配
    DFA算法确定有穷自动机(一种数据结构)
    7.3)DFA实现原理

    DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。

    存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这种结构

    敏感词:冰毒、大麻、大坏蛋

    检索的过程

    7.4)自管理敏感词集成到文章审核中

    ①:创建敏感词表,导入资料中wm_sensitive到leadnews_wemedia库中

    1. package com.heima.model.wemedia.pojos;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. import java.io.Serializable;
    8. import java.util.Date;
    9. /**
    10. *

    11. * 敏感词信息表
    12. *

    13. *
    14. * @author itheima
    15. */
    16. @Data
    17. @TableName("wm_sensitive")
    18. public class WmSensitive implements Serializable {
    19.    private static final long serialVersionUID = 1L;
    20.    /**
    21.     * 主键
    22.     */
    23.    @TableId(value = "id", type = IdType.AUTO)
    24.    private Integer id;
    25.    /**
    26.     * 敏感词
    27.     */
    28.    @TableField("sensitives")
    29.    private String sensitives;
    30.    /**
    31.     * 创建时间
    32.     */
    33.    @TableField("created_time")
    34.    private Date createdTime;
    35. }

    ②:拷贝对应的wm_sensitive的mapper到项目中

    1. package com.heima.wemedia.mapper;
    2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    3. import com.heima.model.wemedia.pojos.WmSensitive;
    4. import org.apache.ibatis.annotations.Mapper;
    5. @Mapper
    6. public interface WmSensitiveMapper extends BaseMapper {
    7. }

    ③:在文章审核的代码中添加自管理敏感词审核

    第一:在WmNewsAutoScanServiceImpl中的autoScanWmNews方法上添加如下代码

    1. //从内容中提取纯文本内容和图片
    2. //.....省略
    3. //自管理的敏感词过滤
    4. boolean isSensitive = handleSensitiveScan((String) textAndImages.get("content"), wmNews);
    5. if(!isSensitive) return;
    6. //2.审核文本内容 阿里云接口
    7. //.....省略

    新增自管理敏感词审核代码

    1. @Autowired
    2. private WmSensitiveMapper wmSensitiveMapper;
    3. /**
    4.     * 自管理的敏感词审核
    5.     * @param content
    6.     * @param wmNews
    7.     * @return
    8.     */
    9. private boolean handleSensitiveScan(String content, WmNews wmNews) {
    10.    boolean flag = true;
    11.    //获取所有的敏感词
    12.    List wmSensitives = wmSensitiveMapper.selectList(Wrappers.lambdaQuery().select(WmSensitive::getSensitives));
    13.    List sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
    14.    //初始化敏感词库
    15.    SensitiveWordUtil.initMap(sensitiveList);
    16.    //查看文章中是否包含敏感词
    17.    Map map = SensitiveWordUtil.matchWords(content);
    18.    if(map.size() >0){
    19.        updateWmNews(wmNews,(short) 2,"当前文章中存在违规内容"+map);
    20.        flag = false;
    21.   }
    22.    return flag;
    23. }

    8)新需求-图片识别文字审核敏感词

    8.1)需求分析

    产品经理召集开会,文章审核功能已经交付了,文章也能正常发布审核。对于上次提出的自管理敏感词也很满意,这次会议核心的内容如下:

    • 文章中包含的图片要识别文字,过滤掉图片文字的敏感词

    8.2)图片文字识别

    什么是OCR?

    OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程

    方案说明
    百度OCR收费
    Tesseract-OCRGoogle维护的开源OCR引擎,支持Java,Python等语言调用
    Tess4J封装了Tesseract-OCR ,支持Java调用
    8.3)Tess4j案例

    ①:创建项目导入tess4j对应的依赖

    1.    net.sourceforge.tess4j
    2.    tess4j
    3.    4.1.1

    ②:导入中文字体库, 把资料中的tessdata文件夹拷贝到自己的工作空间下

    ③:编写测试类进行测试

    1. package com.heima.tess4j;
    2. import net.sourceforge.tess4j.ITesseract;
    3. import net.sourceforge.tess4j.Tesseract;
    4. import java.io.File;
    5. public class Application {
    6.    public static void main(String[] args) {
    7.        try {
    8.            //获取本地图片
    9.            File file = new File("D:\\26.png");
    10.            //创建Tesseract对象
    11.            ITesseract tesseract = new Tesseract();
    12.            //设置字体库路径
    13.            tesseract.setDatapath("D:\\workspace\\tessdata");
    14.            //中文识别
    15.            tesseract.setLanguage("chi_sim");
    16.            //执行ocr识别
    17.            String result = tesseract.doOCR(file);
    18.            //替换回车和tal键 使结果为一行
    19.            result = result.replaceAll("\\r|\\n","-").replaceAll(" ","");
    20.            System.out.println("识别的结果为:"+result);
    21.       } catch (Exception e) {
    22.            e.printStackTrace();
    23.       }
    24.   }
    25. }
    8.4)管理敏感词和图片文字识别集成到文章审核

    ①:在heima-leadnews-common中创建工具类,简单封装一下tess4j

    需要先导入pom

    1. <dependency>
    2.    <groupId>net.sourceforge.tess4jgroupId>
    3.    <artifactId>tess4jartifactId>
    4.    <version>4.1.1version>
    5. dependency>

    工具类

    1. package com.heima.common.tess4j;
    2. import lombok.Getter;
    3. import lombok.Setter;
    4. import net.sourceforge.tess4j.ITesseract;
    5. import net.sourceforge.tess4j.Tesseract;
    6. import net.sourceforge.tess4j.TesseractException;
    7. import org.springframework.boot.context.properties.ConfigurationProperties;
    8. import org.springframework.stereotype.Component;
    9. import java.awt.image.BufferedImage;
    10. @Getter
    11. @Setter
    12. @Component
    13. @ConfigurationProperties(prefix = "tess4j")
    14. public class Tess4jClient {
    15.    private String dataPath;
    16.    private String language;
    17.    public String doOCR(BufferedImage image) throws TesseractException {
    18.        //创建Tesseract对象
    19.        ITesseract tesseract = new Tesseract();
    20.        //设置字体库路径
    21.        tesseract.setDatapath(dataPath);
    22.        //中文识别
    23.        tesseract.setLanguage(language);
    24.        //执行ocr识别
    25.        String result = tesseract.doOCR(image);
    26.        //替换回车和tal键 使结果为一行
    27.        result = result.replaceAll("\\r|\\n", "-").replaceAll(" ", "");
    28.        return result;
    29.   }
    30. }

    在spring.factories配置中添加该类,完整如下:

    1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    2.  com.heima.common.exception.ExceptionCatch,\
    3.  com.heima.common.swagger.SwaggerConfiguration,\
    4.  com.heima.common.swagger.Swagger2Configuration,\
    5.  com.heima.common.aliyun.GreenTextScan,\
    6.  com.heima.common.aliyun.GreenImageScan,\
    7.  com.heima.common.tess4j.Tess4jClient

    ②:在heima-leadnews-wemedia中的配置中添加两个属性

    1. tess4j:
    2. data-path: D:\workspace\tessdata
    3. language: chi_sim

    ③:在WmNewsAutoScanServiceImpl中的handleImageScan方法上添加如下代码

    1. try {
    2.    for (String image : images) {
    3.        byte[] bytes = fileStorageService.downLoadFile(image);
    4.        //图片识别文字审核---begin-----
    5.        //从byte[]转换为butteredImage
    6.        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
    7.        BufferedImage imageFile = ImageIO.read(in);
    8.        //识别图片的文字
    9.        String result = tess4jClient.doOCR(imageFile);
    10.        //审核是否包含自管理的敏感词
    11.        boolean isSensitive = handleSensitiveScan(result, wmNews);
    12.        if(!isSensitive){
    13.            return isSensitive;
    14.       }
    15.        //图片识别文字审核---end-----
    16.        imageList.add(bytes);
    17.   }
    18. }catch (Exception e){
    19.    e.printStackTrace();
    20. }

    最后附上文章审核的完整代码如下:

    1. package com.heima.wemedia.service.impl;
    2. import com.alibaba.fastjson.JSONArray;
    3. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    4. import com.heima.apis.article.IArticleClient;
    5. import com.heima.common.aliyun.GreenImageScan;
    6. import com.heima.common.aliyun.GreenTextScan;
    7. import com.heima.common.tess4j.Tess4jClient;
    8. import com.heima.file.service.FileStorageService;
    9. import com.heima.model.article.dtos.ArticleDto;
    10. import com.heima.model.common.dtos.ResponseResult;
    11. import com.heima.model.wemedia.pojos.WmChannel;
    12. import com.heima.model.wemedia.pojos.WmNews;
    13. import com.heima.model.wemedia.pojos.WmSensitive;
    14. import com.heima.model.wemedia.pojos.WmUser;
    15. import com.heima.utils.common.SensitiveWordUtil;
    16. import com.heima.wemedia.mapper.WmChannelMapper;
    17. import com.heima.wemedia.mapper.WmNewsMapper;
    18. import com.heima.wemedia.mapper.WmSensitiveMapper;
    19. import com.heima.wemedia.mapper.WmUserMapper;
    20. import com.heima.wemedia.service.WmNewsAutoScanService;
    21. import lombok.extern.slf4j.Slf4j;
    22. import org.apache.commons.lang3.StringUtils;
    23. import org.springframework.beans.BeanUtils;
    24. import org.springframework.beans.factory.annotation.Autowired;
    25. import org.springframework.scheduling.annotation.Async;
    26. import org.springframework.stereotype.Service;
    27. import org.springframework.transaction.annotation.Transactional;
    28. import javax.imageio.ImageIO;
    29. import java.awt.image.BufferedImage;
    30. import java.io.ByteArrayInputStream;
    31. import java.util.*;
    32. import java.util.stream.Collectors;
    33. @Service
    34. @Slf4j
    35. @Transactional
    36. public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {
    37.    @Autowired
    38.    private WmNewsMapper wmNewsMapper;
    39.    /**
    40.     * 自媒体文章审核
    41.     *
    42.     * @param id 自媒体文章id
    43.     */
    44.    @Override
    45.    @Async  //标明当前方法是一个异步方法
    46.    public void autoScanWmNews(Integer id) {
    47. //       int a = 1/0;
    48.        //1.查询自媒体文章
    49.        WmNews wmNews = wmNewsMapper.selectById(id);
    50.        if (wmNews == null) {
    51.            throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");
    52.       }
    53.        if (wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
    54.            //从内容中提取纯文本内容和图片
    55.            Map textAndImages = handleTextAndImages(wmNews);
    56.            //自管理的敏感词过滤
    57.            boolean isSensitive = handleSensitiveScan((String) textAndImages.get("content"), wmNews);
    58.            if(!isSensitive) return;
    59.            //2.审核文本内容 阿里云接口
    60.            boolean isTextScan = handleTextScan((String) textAndImages.get("content"), wmNews);
    61.            if (!isTextScan) return;
    62.            //3.审核图片 阿里云接口
    63.            boolean isImageScan = handleImageScan((List) textAndImages.get("images"), wmNews);
    64.            if (!isImageScan) return;
    65.            //4.审核成功,保存app端的相关的文章数据
    66.            ResponseResult responseResult = saveAppArticle(wmNews);
    67.            if (!responseResult.getCode().equals(200)) {
    68.                throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");
    69.           }
    70.            //回填article_id
    71.            wmNews.setArticleId((Long) responseResult.getData());
    72.            updateWmNews(wmNews, (short) 9, "审核成功");
    73.       }
    74.   }
    75.    @Autowired
    76.    private WmSensitiveMapper wmSensitiveMapper;
    77.    /**
    78.     * 自管理的敏感词审核
    79.     * @param content
    80.     * @param wmNews
    81.     * @return
    82.     */
    83.    private boolean handleSensitiveScan(String content, WmNews wmNews) {
    84.        boolean flag = true;
    85.        //获取所有的敏感词
    86.        List wmSensitives = wmSensitiveMapper.selectList(Wrappers.lambdaQuery().select(WmSensitive::getSensitives));
    87.        List sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
    88.        //初始化敏感词库
    89.        SensitiveWordUtil.initMap(sensitiveList);
    90.        //查看文章中是否包含敏感词
    91.        Map map = SensitiveWordUtil.matchWords(content);
    92.        if(map.size() >0){
    93.            updateWmNews(wmNews,(short) 2,"当前文章中存在违规内容"+map);
    94.            flag = false;
    95.       }
    96.        return flag;
    97.   }
    98.    @Autowired
    99.    private IArticleClient articleClient;
    100.    @Autowired
    101.    private WmChannelMapper wmChannelMapper;
    102.    @Autowired
    103.    private WmUserMapper wmUserMapper;
    104.    /**
    105.     * 保存app端相关的文章数据
    106.     *
    107.     * @param wmNews
    108.     */
    109.    private ResponseResult saveAppArticle(WmNews wmNews) {
    110.        ArticleDto dto = new ArticleDto();
    111.        //属性的拷贝
    112.        BeanUtils.copyProperties(wmNews, dto);
    113.        //文章的布局
    114.        dto.setLayout(wmNews.getType());
    115.        //频道
    116.        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
    117.        if (wmChannel != null) {
    118.            dto.setChannelName(wmChannel.getName());
    119.       }
    120.        //作者
    121.        dto.setAuthorId(wmNews.getUserId().longValue());
    122.        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
    123.        if (wmUser != null) {
    124.            dto.setAuthorName(wmUser.getName());
    125.       }
    126.        //设置文章id
    127.        if (wmNews.getArticleId() != null) {
    128.            dto.setId(wmNews.getArticleId());
    129.       }
    130.        dto.setCreatedTime(new Date());
    131.        ResponseResult responseResult = articleClient.saveArticle(dto);
    132.        return responseResult;
    133.   }
    134.    @Autowired
    135.    private FileStorageService fileStorageService;
    136.    @Autowired
    137.    private GreenImageScan greenImageScan;
    138.    @Autowired
    139.    private Tess4jClient tess4jClient;
    140.    /**
    141.     * 审核图片
    142.     *
    143.     * @param images
    144.     * @param wmNews
    145.     * @return
    146.     */
    147.    private boolean handleImageScan(List images, WmNews wmNews) {
    148.        boolean flag = true;
    149.        if (images == null || images.size() == 0) {
    150.            return flag;
    151.       }
    152.        //下载图片 minIO
    153.        //图片去重
    154.        images = images.stream().distinct().collect(Collectors.toList());
    155.        List<byte[]> imageList = new ArrayList<>();
    156.        try {
    157.            for (String image : images) {
    158.                byte[] bytes = fileStorageService.downLoadFile(image);
    159.                //图片识别文字审核---begin-----
    160.                //从byte[]转换为butteredImage
    161.                ByteArrayInputStream in = new ByteArrayInputStream(bytes);
    162.                BufferedImage imageFile = ImageIO.read(in);
    163.                //识别图片的文字
    164.                String result = tess4jClient.doOCR(imageFile);
    165.                //审核是否包含自管理的敏感词
    166.                boolean isSensitive = handleSensitiveScan(result, wmNews);
    167.                if(!isSensitive){
    168.                    return isSensitive;
    169.               }
    170.                //图片识别文字审核---end-----
    171.                imageList.add(bytes);
    172.           }
    173.       }catch (Exception e){
    174.            e.printStackTrace();
    175.       }
    176.        //审核图片
    177.        try {
    178.            Map map = greenImageScan.imageScan(imageList);
    179.            if (map != null) {
    180.                //审核失败
    181.                if (map.get("suggestion").equals("block")) {
    182.                    flag = false;
    183.                    updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
    184.               }
    185.                //不确定信息 需要人工审核
    186.                if (map.get("suggestion").equals("review")) {
    187.                    flag = false;
    188.                    updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
    189.               }
    190.           }
    191.       } catch (Exception e) {
    192.            flag = false;
    193.            e.printStackTrace();
    194.       }
    195.        return flag;
    196.   }
    197.    @Autowired
    198.    private GreenTextScan greenTextScan;
    199.    /**
    200.     * 审核纯文本内容
    201.     *
    202.     * @param content
    203.     * @param wmNews
    204.     * @return
    205.     */
    206.    private boolean handleTextScan(String content, WmNews wmNews) {
    207.        boolean flag = true;
    208.        if ((wmNews.getTitle() + "-" + content).length() == 0) {
    209.            return flag;
    210.       }
    211.        try {
    212.            Map map = greenTextScan.greeTextScan((wmNews.getTitle() + "-" + content));
    213.            if (map != null) {
    214.                //审核失败
    215.                if (map.get("suggestion").equals("block")) {
    216.                    flag = false;
    217.                    updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
    218.               }
    219.                //不确定信息 需要人工审核
    220.                if (map.get("suggestion").equals("review")) {
    221.                    flag = false;
    222.                    updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
    223.               }
    224.           }
    225.       } catch (Exception e) {
    226.            flag = false;
    227.            e.printStackTrace();
    228.       }
    229.        return flag;
    230.   }
    231.    /**
    232.     * 修改文章内容
    233.     *
    234.     * @param wmNews
    235.     * @param status
    236.     * @param reason
    237.     */
    238.    private void updateWmNews(WmNews wmNews, short status, String reason) {
    239.        wmNews.setStatus(status);
    240.        wmNews.setReason(reason);
    241.        wmNewsMapper.updateById(wmNews);
    242.   }
    243.    /**
    244.     * 1。从自媒体文章的内容中提取文本和图片
    245.     * 2.提取文章的封面图片
    246.     *
    247.     * @param wmNews
    248.     * @return
    249.     */
    250.    private Map handleTextAndImages(WmNews wmNews) {
    251.        //存储纯文本内容
    252.        StringBuilder stringBuilder = new StringBuilder();
    253.        List images = new ArrayList<>();
    254.        //1。从自媒体文章的内容中提取文本和图片
    255.        if (StringUtils.isNotBlank(wmNews.getContent())) {
    256.            List maps = JSONArray.parseArray(wmNews.getContent(), Map.class);
    257.            for (Map map : maps) {
    258.                if (map.get("type").equals("text")) {
    259.                    stringBuilder.append(map.get("value"));
    260.               }
    261.                if (map.get("type").equals("image")) {
    262.                    images.add((String) map.get("value"));
    263.               }
    264.           }
    265.       }
    266.        //2.提取文章的封面图片
    267.        if (StringUtils.isNotBlank(wmNews.getImages())) {
    268.            String[] split = wmNews.getImages().split(",");
    269.            images.addAll(Arrays.asList(split));
    270.       }
    271.        Map resultMap = new HashMap<>();
    272.        resultMap.put("content", stringBuilder.toString());
    273.        resultMap.put("images", images);
    274.        return resultMap;
    275.   }
    276. }

    9)文章详情-静态文件生成

    9.1)思路分析

    文章端创建app相关文章时,生成文章详情静态页上传到MinIO中

    9.2)实现步骤

    1.新建ArticleFreemarkerService创建静态文件并上传到minIO中

    1. package com.heima.article.service;
    2. import com.heima.model.article.pojos.ApArticle;
    3. public interface ArticleFreemarkerService {
    4.    /**
    5.     * 生成静态文件上传到minIO中
    6.     * @param apArticle
    7.     * @param content
    8.     */
    9.    public void buildArticleToMinIO(ApArticle apArticle,String content);
    10. }

    实现

    1. package com.heima.article.service.impl;
    2. import com.alibaba.fastjson.JSON;
    3. import com.alibaba.fastjson.JSONArray;
    4. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    5. import com.heima.article.mapper.ApArticleContentMapper;
    6. import com.heima.article.service.ApArticleService;
    7. import com.heima.article.service.ArticleFreemarkerService;
    8. import com.heima.file.service.FileStorageService;
    9. import com.heima.model.article.pojos.ApArticle;
    10. import freemarker.template.Configuration;
    11. import freemarker.template.Template;
    12. import lombok.extern.slf4j.Slf4j;
    13. import org.apache.commons.lang3.StringUtils;
    14. import org.springframework.beans.BeanUtils;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.scheduling.annotation.Async;
    17. import org.springframework.stereotype.Service;
    18. import org.springframework.transaction.annotation.Transactional;
    19. import java.io.ByteArrayInputStream;
    20. import java.io.InputStream;
    21. import java.io.StringWriter;
    22. import java.util.HashMap;
    23. import java.util.Map;
    24. @Service
    25. @Slf4j
    26. @Transactional
    27. public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
    28.    @Autowired
    29.    private ApArticleContentMapper apArticleContentMapper;
    30.    @Autowired
    31.    private Configuration configuration;
    32.    @Autowired
    33.    private FileStorageService fileStorageService;
    34.    @Autowired
    35.    private ApArticleService apArticleService;
    36.    /**
    37.     * 生成静态文件上传到minIO中
    38.     * @param apArticle
    39.     * @param content
    40.     */
    41.    @Async
    42.    @Override
    43.    public void buildArticleToMinIO(ApArticle apArticle, String content) {
    44.        //已知文章的id
    45.        //4.1 获取文章内容
    46.        if(StringUtils.isNotBlank(content)){
    47.            //4.2 文章内容通过freemarker生成html文件
    48.            Template template = null;
    49.            StringWriter out = new StringWriter();
    50.            try {
    51.                template = configuration.getTemplate("article.ftl");
    52.                //数据模型
    53.                Map contentDataModel = new HashMap<>();
    54.                contentDataModel.put("content", JSONArray.parseArray(content));
    55.                //合成
    56.                template.process(contentDataModel,out);
    57.           } catch (Exception e) {
    58.                e.printStackTrace();
    59.           }
    60.            //4.3 把html文件上传到minio中
    61.            InputStream in = new ByteArrayInputStream(out.toString().getBytes());
    62.            String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);
    63.            //4.4 修改ap_article表,保存static_url字段
    64.            apArticleService.update(Wrappers.lambdaUpdate().eq(ApArticle::getId,apArticle.getId())
    65.                   .set(ApArticle::getStaticUrl,path));
    66.       }
    67.   }
    68. }

    2.在ApArticleService的saveArticle实现方法中添加调用生成文件的方法

    1. /**
    2.     * 保存app端相关文章
    3.     * @param dto
    4.     * @return
    5.     */
    6. @Override
    7. public ResponseResult saveArticle(ArticleDto dto) {
    8.    //       try {
    9.    //           Thread.sleep(3000);
    10.    //       } catch (InterruptedException e) {
    11.    //           e.printStackTrace();
    12.    //       }
    13.    //1.检查参数
    14.    if(dto == null){
    15.        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    16.   }
    17.    ApArticle apArticle = new ApArticle();
    18.    BeanUtils.copyProperties(dto,apArticle);
    19.    //2.判断是否存在id
    20.    if(dto.getId() == null){
    21.        //2.1 不存在id 保存 文章 文章配置 文章内容
    22.        //保存文章
    23.        save(apArticle);
    24.        //保存配置
    25.        ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
    26.        apArticleConfigMapper.insert(apArticleConfig);
    27.        //保存 文章内容
    28.        ApArticleContent apArticleContent = new ApArticleContent();
    29.        apArticleContent.setArticleId(apArticle.getId());
    30.        apArticleContent.setContent(dto.getContent());
    31.        apArticleContentMapper.insert(apArticleContent);
    32.   }else {
    33.        //2.2 存在id   修改 文章 文章内容
    34.        //修改 文章
    35.        updateById(apArticle);
    36.        //修改文章内容
    37.        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
    38.        apArticleContent.setContent(dto.getContent());
    39.        apArticleContentMapper.updateById(apArticleContent);
    40.   }
    41.    //异步调用 生成静态文件上传到minio中
    42.    articleFreemarkerService.buildArticleToMinIO(apArticle,dto.getContent());
    43.    //3.结果返回 文章的id
    44.    return ResponseResult.okResult(apArticle.getId());
    45. }

    3.文章微服务开启异步调用

  • 相关阅读:
    java毕业设计——基于java+J2EE+sqlserver的音像店租赁管理系统设计与实现(毕业论文+程序源码)——租赁管理系统
    09 编译2022年最新的Linux kernel、U-Boot和BusyBox rootfs源码,并用QEMU模拟器运行
    ウクアージ / 小黑
    美团一面面经及详细答案
    208. 实现 Trie (前缀树)
    npm运行vue项目出现禁止运行脚本
    DS18B20U概述,VSC7429_VSC8572以太网(PHY)
    ptmalloc源码分析 - _int_malloc函数之largebins和Top chunk(08)
    Java技术学习|消息队列|初级RabbitMQ
    PIE:1979-2018年中国气温数据产品(空间分辨率为0.1º)
  • 原文地址:https://blog.csdn.net/m0_74229735/article/details/139723531