当前位置:   article > 正文

黑马头条详解(二)

黑马头条

四、自媒体文章审核

文章数据流

主要涉及技术

4.1、自媒体文章自动审核流程

 审核方式

自媒体文章自动审核流程-多端调用

文章状态:0 草稿 ;1 提交(待审核); 2 审核失败; 3 人工审核; 4 人工审核通过; 8 审核通过(待发布);  9 已发布

4.2、内容安全第三方接口

内容安全接口选型

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

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

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

文本内容审核代码示例

  1. import com.alibaba.fastjson.JSON;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.aliyun.oss.ClientException;
  5. import com.aliyuncs.DefaultAcsClient;
  6. import com.aliyuncs.IAcsClient;
  7. import com.aliyuncs.exceptions.ServerException;
  8. import com.aliyuncs.green.model.v20180509.TextScanRequest;
  9. import com.aliyuncs.http.FormatType;
  10. import com.aliyuncs.http.HttpResponse;
  11. import com.aliyuncs.profile.DefaultProfile;
  12. import com.aliyuncs.profile.IClientProfile;
  13. import java.util.ArrayList;
  14. import java.util.Arrays;
  15. import java.util.LinkedHashMap;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.UUID;
  19. public class Main {
  20. public static void main(String[] args) throws Exception {
  21. /**
  22. * 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
  23. * 常见获取环境变量方式:
  24. * 方式一:
  25. * 获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
  26. * 获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
  27. * 方式二:
  28. * 获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
  29. * 获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
  30. */
  31. DefaultProfile profile = DefaultProfile.getProfile(
  32. "cn-shanghai",
  33. "建议从环境变量中获取RAM用户AccessKey ID",
  34. "建议从环境变量中获取RAM用户AccessKey Secret");
  35. DefaultProfile.addEndpoint("cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
  36. // 注意:此处实例化的client尽可能重复使用,提升检测性能。避免重复建立连接。
  37. IAcsClient client = new DefaultAcsClient(profile);
  38. TextScanRequest textScanRequest = new TextScanRequest();
  39. textScanRequest.setAcceptFormat(FormatType.JSON); // 指定API返回格式。
  40. textScanRequest.setHttpContentType(FormatType.JSON);
  41. textScanRequest.setMethod(com.aliyuncs.http.MethodType.POST); // 指定请求方法。
  42. textScanRequest.setEncoding("UTF-8");
  43. textScanRequest.setRegionId("cn-shanghai");
  44. List<Map<String, Object>> tasks = new ArrayList<Map<String, Object>>();
  45. Map<String, Object> task1 = new LinkedHashMap<String, Object>();
  46. task1.put("dataId", UUID.randomUUID().toString());
  47. /**
  48. * 待检测的文本,长度不超过10000个字符。
  49. */
  50. task1.put("content", "test content");
  51. tasks.add(task1);
  52. JSONObject data = new JSONObject();
  53. /**
  54. * 检测场景。文本垃圾检测请传递antispam。
  55. **/
  56. data.put("scenes", Arrays.asList("antispam"));
  57. data.put("tasks", tasks);
  58. System.out.println(JSON.toJSONString(data, true));
  59. textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
  60. // 请务必设置超时时间。
  61. textScanRequest.setConnectTimeout(3000);
  62. textScanRequest.setReadTimeout(6000);
  63. try {
  64. HttpResponse httpResponse = client.doAction(textScanRequest);
  65. if (!httpResponse.isSuccess()) {
  66. System.out.println("response not success. status:" + httpResponse.getStatus());
  67. // 业务处理。
  68. return;
  69. }
  70. JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
  71. System.out.println(JSON.toJSONString(scrResponse, true));
  72. if (200 != scrResponse.getInteger("code")) {
  73. System.out.println("detect not success. code:" + scrResponse.getInteger("code"));
  74. // 业务处理。
  75. return;
  76. }
  77. JSONArray taskResults = scrResponse.getJSONArray("data");
  78. for (Object taskResult : taskResults) {
  79. if (200 != ((JSONObject) taskResult).getInteger("code")) {
  80. System.out.println("task process fail:" + ((JSONObject) taskResult).getInteger("code"));
  81. // 业务处理。
  82. continue;
  83. }
  84. JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
  85. for (Object sceneResult : sceneResults) {
  86. String scene = ((JSONObject) sceneResult).getString("scene");
  87. String suggestion = ((JSONObject) sceneResult).getString("suggestion");
  88. // 根据scene和suggestion做相关处理。
  89. // suggestion为pass表示未命中垃圾。suggestion为block表示命中了垃圾,可以通过label字段查看命中的垃圾分类。
  90. System.out.println("args = [" + scene + "]");
  91. System.out.println("args = [" + suggestion + "]");
  92. }
  93. }
  94. } catch (ServerException e) {
  95. e.printStackTrace();
  96. } catch (ClientException e) {
  97. e.printStackTrace();
  98. } catch (Exception e) {
  99. e.printStackTrace();
  100. }
  101. }
  102. }

 图片内容审核代码示例

  1. import com.alibaba.fastjson.JSON;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.aliyuncs.DefaultAcsClient;
  5. import com.aliyuncs.IAcsClient;
  6. import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
  7. import com.aliyuncs.http.FormatType;
  8. import com.aliyuncs.http.HttpResponse;
  9. import com.aliyuncs.http.MethodType;
  10. import com.aliyuncs.http.ProtocolType;
  11. import com.aliyuncs.profile.DefaultProfile;
  12. import com.aliyuncs.profile.IClientProfile;
  13. import java.util.*;
  14. public class Main {
  15. public static void main(String[] args) throws Exception {
  16. /**
  17. * 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
  18. * 常见获取环境变量方式:
  19. * 方式一:
  20. * 获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
  21. * 获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
  22. * 方式二:
  23. * 获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
  24. * 获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
  25. */
  26. DefaultProfile profile = DefaultProfile.getProfile(
  27. "cn-shanghai",
  28. "建议从环境变量中获取RAM用户AccessKey ID",
  29. "建议从环境变量中获取RAM用户AccessKey Secret");
  30. DefaultProfile.addEndpoint("cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
  31. // 注意:此处实例化的client尽可能重复使用,提升检测性能。避免重复建立连接。
  32. IAcsClient client = new DefaultAcsClient(profile);
  33. ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
  34. // 指定API返回格式。
  35. imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
  36. // 指定请求方法。
  37. imageSyncScanRequest.setMethod(MethodType.POST);
  38. imageSyncScanRequest.setEncoding("utf-8");
  39. // 支持HTTP和HTTPS。
  40. imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
  41. JSONObject httpBody = new JSONObject();
  42. /**
  43. * 设置要检测的风险场景。计费依据此处传递的场景计算。
  44. * 一次请求中可以同时检测多张图片,每张图片可以同时检测多个风险场景,计费按照场景计算。
  45. * 例如,检测2张图片,场景传递porn和terrorism,计费会按照2张图片鉴黄,2张图片暴恐检测计算。
  46. * porn:表示鉴黄场景。
  47. */
  48. httpBody.put("scenes", Arrays.asList("porn"));
  49. /**
  50. * 设置待检测图片。一张图片对应一个task。
  51. * 多张图片同时检测时,处理的时间由最后一个处理完的图片决定。
  52. * 通常情况下批量检测的平均响应时间比单张检测的要长。一次批量提交的图片数越多,响应时间被拉长的概率越高。
  53. * 这里以单张图片检测作为示例, 如果是批量图片检测,请自行构建多个task。
  54. */
  55. JSONObject task = new JSONObject();
  56. task.put("dataId", UUID.randomUUID().toString());
  57. // 设置图片链接。URL中有特殊字符,需要对URL进行encode编码。
  58. task.put("url", "http://www.aliyundoc.com/xxx.test.jpg");
  59. task.put("time", new Date());
  60. httpBody.put("tasks", Arrays.asList(task));
  61. imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
  62. "UTF-8", FormatType.JSON);
  63. /**
  64. * 请设置超时时间。服务端全链路处理超时时间为10秒,请做相应设置。
  65. * 如果您设置的ReadTimeout小于服务端处理的时间,程序中会获得一个ReadTimeout异常。
  66. */
  67. imageSyncScanRequest.setConnectTimeout(3000);
  68. imageSyncScanRequest.setReadTimeout(10000);
  69. HttpResponse httpResponse = null;
  70. try {
  71. httpResponse = client.doAction(imageSyncScanRequest);
  72. } catch (Exception e) {
  73. e.printStackTrace();
  74. }
  75. // 服务端接收到请求,完成处理后返回的结果。
  76. if (httpResponse != null && httpResponse.isSuccess()) {
  77. JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
  78. System.out.println(JSON.toJSONString(scrResponse, true));
  79. int requestCode = scrResponse.getIntValue("code");
  80. // 每一张图片的检测结果。
  81. JSONArray taskResults = scrResponse.getJSONArray("data");
  82. if (200 == requestCode) {
  83. for (Object taskResult : taskResults) {
  84. // 单张图片的处理结果。
  85. int taskCode = ((JSONObject) taskResult).getIntValue("code");
  86. // 图片对应检测场景的处理结果。如果是多个场景,则会有每个场景的结果。
  87. JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
  88. if (200 == taskCode) {
  89. for (Object sceneResult : sceneResults) {
  90. String scene = ((JSONObject) sceneResult).getString("scene");
  91. String suggestion = ((JSONObject) sceneResult).getString("suggestion");
  92. // 根据scene和suggestion做相关处理。
  93. // 根据不同的suggestion结果做业务上的不同处理。例如,将违规数据删除等。
  94. System.out.println("scene = [" + scene + "]");
  95. System.out.println("suggestion = [" + suggestion + "]");
  96. }
  97. } else {
  98. // 单张图片处理失败, 原因视具体的情况详细分析。
  99. System.out.println("task process fail. task response:" + JSON.toJSONString(taskResult));
  100. }
  101. }
  102. } else {
  103. /**
  104. * 表明请求整体处理失败,原因视具体的情况详细分析。
  105. */
  106. System.out.println("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
  107. }
  108. }
  109. }
  110. }

4.3、app端文章保存接口

主要是自媒体文章审核通过后,在文章微服务端进行保存

表结构

 4.3.1、分布式id

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

分布式id的技术选型

雪花算法

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

4.3.2、保存app端文章-思路分析

 4.3.3、保存app端文章-feign接口

 

4.3.4、代码实现

 ①在heima-leadnews-feign-api中新增接口

导入feign的依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-openfeign</artifactId>
  4. </dependency>

在feign模块中中定义文章端的接口

  1. package com.heima.apis.article;
  2. import com.heima.model.article.dtos.ArticleDto;
  3. import com.heima.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 = "leadnews-article")
  9. public interface IArticleClient {
  10. @PostMapping("/api/v1/article/save")
  11. public ResponseResult saveArticle(@RequestBody ArticleDto dto) ;
  12. }

②在heima-leadnews-article中实现该方法

  1. package com.heima.article.feign;
  2. import com.heima.apis.article.IArticleClient;
  3. import com.heima.article.service.ApArticleService;
  4. import com.heima.model.article.dtos.ArticleDto;
  5. import com.heima.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. * <p>
  11. * APP已发布文章配置表
  12. * </p>
  13. *
  14. * @author itheima
  15. */
  16. @Data
  17. @NoArgsConstructor
  18. @TableName("ap_article_config")
  19. public class ApArticleConfig implements Serializable {
  20. //--------------------------------------------------------
  21. public ApArticleConfig(Long articleId){
  22. this.articleId = articleId;
  23. this.isComment = true;
  24. this.isForward = true;
  25. this.isDelete = false;
  26. this.isDown = false;
  27. }
  28. //--------------------------------------------------------
  29. @TableId(value = "id",type = IdType.ID_WORKER)
  30. private Long id;
  31. /**
  32. * 文章id
  33. */
  34. @TableField("article_id")
  35. private Long articleId;
  36. /**
  37. * 是否可评论
  38. * true: 可以评论 1
  39. * false: 不可评论 0
  40. */
  41. @TableField("is_comment")
  42. private Boolean isComment;
  43. /**
  44. * 是否转发
  45. * true: 可以转发 1
  46. * false: 不可转发 0
  47. */
  48. @TableField("is_forward")
  49. private Boolean isForward;
  50. /**
  51. * 是否下架
  52. * true: 下架 1
  53. * false: 没有下架 0
  54. */
  55. @TableField("is_down")
  56. private Boolean isDown;
  57. /**
  58. * 是否已删除
  59. * true: 删除 1
  60. * false: 没有删除 0
  61. */
  62. @TableField("is_delete")
  63. private Boolean isDelete;
  64. }

④在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.<ApArticleContent>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. }

4.4、自媒体文章审核功能实现

①在heima-leadnews-wemedia中的service新增接口

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

实现类

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

②单元测试

  1. package com.heima.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.5、feign远程接口调用方式

 

在heima-leadnews-wemedia服务中已经依赖了heima-leadnews-feign-apis工程,只需要在自媒体的引导类中开启feign的远程调用即可。注解为:@EnableFeignClients(basePackages = "com.heima.apis") 需要指向apis这个包

服务降级处理

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

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

实现步骤:

①:在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

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

4.6、发布文章提交审核集成

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. }

4.7、自管理敏感词过滤

需要完成的功能:

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

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

技术选型

DFA原理

检索过程

4.8、图片文字-敏感词过滤

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

图片文字识别

什么是OCR?

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

 入门案例

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

  1. <dependency>
  2. <groupId>net.sourceforge.tess4j</groupId>
  3. <artifactId>tess4j</artifactId>
  4. <version>4.1.1</version>
  5. </dependency>

②:导入中文字体库, 把资料中的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. }

4.9、文章详情-静态文件生成

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<String,Object> 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.<ApArticle>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.<ApArticleContent>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.文章微服务开启异步调用

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/988164
推荐阅读
相关标签
  

闽ICP备14008679号