赞
踩
文章数据流
主要涉及技术
审核方式
自媒体文章自动审核流程-多端调用
文章状态:0 草稿 ;1 提交(待审核); 2 审核失败; 3 人工审核; 4 人工审核通过; 8 审核通过(待发布); 9 已发布
内容安全接口选型
内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。
目前很多平台都支持内容检测,如阿里云、腾讯云、百度AI、网易云等国内大型互联网公司都对外提供了API。
按照性能和收费来看,黑马头条项目使用的就是阿里云的内容安全接口,使用到了图片和文本的审核。
文本内容审核代码示例
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONArray;
- import com.alibaba.fastjson.JSONObject;
- import com.aliyun.oss.ClientException;
- import com.aliyuncs.DefaultAcsClient;
- import com.aliyuncs.IAcsClient;
- import com.aliyuncs.exceptions.ServerException;
- import com.aliyuncs.green.model.v20180509.TextScanRequest;
- import com.aliyuncs.http.FormatType;
- import com.aliyuncs.http.HttpResponse;
- import com.aliyuncs.profile.DefaultProfile;
- import com.aliyuncs.profile.IClientProfile;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.UUID;
-
- public class Main {
-
- public static void main(String[] args) throws Exception {
- /**
- * 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
- * 常见获取环境变量方式:
- * 方式一:
- * 获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
- * 获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
- * 方式二:
- * 获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
- * 获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
- */
- DefaultProfile profile = DefaultProfile.getProfile(
- "cn-shanghai",
- "建议从环境变量中获取RAM用户AccessKey ID",
- "建议从环境变量中获取RAM用户AccessKey Secret");
- DefaultProfile.addEndpoint("cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
- // 注意:此处实例化的client尽可能重复使用,提升检测性能。避免重复建立连接。
- IAcsClient client = new DefaultAcsClient(profile);
- TextScanRequest textScanRequest = new TextScanRequest();
- textScanRequest.setAcceptFormat(FormatType.JSON); // 指定API返回格式。
- textScanRequest.setHttpContentType(FormatType.JSON);
- textScanRequest.setMethod(com.aliyuncs.http.MethodType.POST); // 指定请求方法。
- textScanRequest.setEncoding("UTF-8");
- textScanRequest.setRegionId("cn-shanghai");
- List<Map<String, Object>> tasks = new ArrayList<Map<String, Object>>();
- Map<String, Object> task1 = new LinkedHashMap<String, Object>();
- task1.put("dataId", UUID.randomUUID().toString());
- /**
- * 待检测的文本,长度不超过10000个字符。
- */
- task1.put("content", "test content");
- tasks.add(task1);
- JSONObject data = new JSONObject();
-
- /**
- * 检测场景。文本垃圾检测请传递antispam。
- **/
- data.put("scenes", Arrays.asList("antispam"));
- data.put("tasks", tasks);
- System.out.println(JSON.toJSONString(data, true));
- textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
- // 请务必设置超时时间。
- textScanRequest.setConnectTimeout(3000);
- textScanRequest.setReadTimeout(6000);
- try {
- HttpResponse httpResponse = client.doAction(textScanRequest);
- if (!httpResponse.isSuccess()) {
- System.out.println("response not success. status:" + httpResponse.getStatus());
- // 业务处理。
- return;
- }
- JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
- System.out.println(JSON.toJSONString(scrResponse, true));
- if (200 != scrResponse.getInteger("code")) {
- System.out.println("detect not success. code:" + scrResponse.getInteger("code"));
- // 业务处理。
- return;
- }
- JSONArray taskResults = scrResponse.getJSONArray("data");
- for (Object taskResult : taskResults) {
- if (200 != ((JSONObject) taskResult).getInteger("code")) {
- System.out.println("task process fail:" + ((JSONObject) taskResult).getInteger("code"));
- // 业务处理。
- continue;
- }
- JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
- for (Object sceneResult : sceneResults) {
- String scene = ((JSONObject) sceneResult).getString("scene");
- String suggestion = ((JSONObject) sceneResult).getString("suggestion");
- // 根据scene和suggestion做相关处理。
- // suggestion为pass表示未命中垃圾。suggestion为block表示命中了垃圾,可以通过label字段查看命中的垃圾分类。
- System.out.println("args = [" + scene + "]");
- System.out.println("args = [" + suggestion + "]");
- }
- }
- } catch (ServerException e) {
- e.printStackTrace();
- } catch (ClientException e) {
- e.printStackTrace();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }
图片内容审核代码示例
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONArray;
- import com.alibaba.fastjson.JSONObject;
- import com.aliyuncs.DefaultAcsClient;
- import com.aliyuncs.IAcsClient;
- import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
- import com.aliyuncs.http.FormatType;
- import com.aliyuncs.http.HttpResponse;
- import com.aliyuncs.http.MethodType;
- import com.aliyuncs.http.ProtocolType;
- import com.aliyuncs.profile.DefaultProfile;
- import com.aliyuncs.profile.IClientProfile;
-
- import java.util.*;
-
- public class Main {
-
- public static void main(String[] args) throws Exception {
- /**
- * 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
- * 常见获取环境变量方式:
- * 方式一:
- * 获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
- * 获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
- * 方式二:
- * 获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
- * 获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
- */
- DefaultProfile profile = DefaultProfile.getProfile(
- "cn-shanghai",
- "建议从环境变量中获取RAM用户AccessKey ID",
- "建议从环境变量中获取RAM用户AccessKey Secret");
- DefaultProfile.addEndpoint("cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
- // 注意:此处实例化的client尽可能重复使用,提升检测性能。避免重复建立连接。
- IAcsClient client = new DefaultAcsClient(profile);
-
- ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
- // 指定API返回格式。
- imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
- // 指定请求方法。
- imageSyncScanRequest.setMethod(MethodType.POST);
- imageSyncScanRequest.setEncoding("utf-8");
- // 支持HTTP和HTTPS。
- imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
-
- JSONObject httpBody = new JSONObject();
- /**
- * 设置要检测的风险场景。计费依据此处传递的场景计算。
- * 一次请求中可以同时检测多张图片,每张图片可以同时检测多个风险场景,计费按照场景计算。
- * 例如,检测2张图片,场景传递porn和terrorism,计费会按照2张图片鉴黄,2张图片暴恐检测计算。
- * porn:表示鉴黄场景。
- */
- httpBody.put("scenes", Arrays.asList("porn"));
-
- /**
- * 设置待检测图片。一张图片对应一个task。
- * 多张图片同时检测时,处理的时间由最后一个处理完的图片决定。
- * 通常情况下批量检测的平均响应时间比单张检测的要长。一次批量提交的图片数越多,响应时间被拉长的概率越高。
- * 这里以单张图片检测作为示例, 如果是批量图片检测,请自行构建多个task。
- */
- JSONObject task = new JSONObject();
- task.put("dataId", UUID.randomUUID().toString());
-
- // 设置图片链接。URL中有特殊字符,需要对URL进行encode编码。
- task.put("url", "http://www.aliyundoc.com/xxx.test.jpg");
- task.put("time", new Date());
- httpBody.put("tasks", Arrays.asList(task));
-
- imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
- "UTF-8", FormatType.JSON);
-
- /**
- * 请设置超时时间。服务端全链路处理超时时间为10秒,请做相应设置。
- * 如果您设置的ReadTimeout小于服务端处理的时间,程序中会获得一个ReadTimeout异常。
- */
- imageSyncScanRequest.setConnectTimeout(3000);
- imageSyncScanRequest.setReadTimeout(10000);
- HttpResponse httpResponse = null;
- try {
- httpResponse = client.doAction(imageSyncScanRequest);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- // 服务端接收到请求,完成处理后返回的结果。
- if (httpResponse != null && httpResponse.isSuccess()) {
- JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
- System.out.println(JSON.toJSONString(scrResponse, true));
- int requestCode = scrResponse.getIntValue("code");
- // 每一张图片的检测结果。
- JSONArray taskResults = scrResponse.getJSONArray("data");
- if (200 == requestCode) {
- for (Object taskResult : taskResults) {
- // 单张图片的处理结果。
- int taskCode = ((JSONObject) taskResult).getIntValue("code");
- // 图片对应检测场景的处理结果。如果是多个场景,则会有每个场景的结果。
- JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
- if (200 == taskCode) {
- for (Object sceneResult : sceneResults) {
- String scene = ((JSONObject) sceneResult).getString("scene");
- String suggestion = ((JSONObject) sceneResult).getString("suggestion");
- // 根据scene和suggestion做相关处理。
- // 根据不同的suggestion结果做业务上的不同处理。例如,将违规数据删除等。
- System.out.println("scene = [" + scene + "]");
- System.out.println("suggestion = [" + suggestion + "]");
- }
- } else {
- // 单张图片处理失败, 原因视具体的情况详细分析。
- System.out.println("task process fail. task response:" + JSON.toJSONString(taskResult));
- }
- }
- } else {
- /**
- * 表明请求整体处理失败,原因视具体的情况详细分析。
- */
- System.out.println("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
- }
- }
- }
-
- }
主要是自媒体文章审核通过后,在文章微服务端进行保存
表结构
随着业务的增长,文章表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID。
分布式id的技术选型
雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0
①在heima-leadnews-feign-api中新增接口
导入feign的依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
在feign模块中中定义文章端的接口
- package com.heima.apis.article;
-
- import com.heima.model.article.dtos.ArticleDto;
- import com.heima.model.common.dtos.ResponseResult;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
-
- import java.io.IOException;
-
-
- @FeignClient(value = "leadnews-article")
- public interface IArticleClient {
-
- @PostMapping("/api/v1/article/save")
- public ResponseResult saveArticle(@RequestBody ArticleDto dto) ;
- }
②在heima-leadnews-article中实现该方法
- package com.heima.article.feign;
-
- import com.heima.apis.article.IArticleClient;
- import com.heima.article.service.ApArticleService;
- import com.heima.model.article.dtos.ArticleDto;
- import com.heima.model.common.dtos.ResponseResult;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.*;
-
- import java.io.IOException;
-
- @RestController
- public class ArticleClient implements IArticleClient {
-
- @Autowired
- private ApArticleService apArticleService;
-
- @Override
- @PostMapping("/api/v1/article/save")
- public ResponseResult saveArticle(@RequestBody ArticleDto dto) {
- return apArticleService.saveArticle(dto);
- }
-
- }
③拷贝mapper,在资料文件夹中拷贝ApArticleConfigMapper类到mapper文件夹中,同时,修改ApArticleConfig类,添加如下构造函数
- package com.heima.model.article.pojos;
-
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- import java.io.Serializable;
-
- /**
- * <p>
- * APP已发布文章配置表
- * </p>
- *
- * @author itheima
- */
-
- @Data
- @NoArgsConstructor
- @TableName("ap_article_config")
- public class ApArticleConfig implements Serializable {
-
- //--------------------------------------------------------
- public ApArticleConfig(Long articleId){
- this.articleId = articleId;
- this.isComment = true;
- this.isForward = true;
- this.isDelete = false;
- this.isDown = false;
- }
- //--------------------------------------------------------
- @TableId(value = "id",type = IdType.ID_WORKER)
- private Long id;
-
- /**
- * 文章id
- */
- @TableField("article_id")
- private Long articleId;
-
- /**
- * 是否可评论
- * true: 可以评论 1
- * false: 不可评论 0
- */
- @TableField("is_comment")
- private Boolean isComment;
-
- /**
- * 是否转发
- * true: 可以转发 1
- * false: 不可转发 0
- */
- @TableField("is_forward")
- private Boolean isForward;
-
- /**
- * 是否下架
- * true: 下架 1
- * false: 没有下架 0
- */
- @TableField("is_down")
- private Boolean isDown;
-
- /**
- * 是否已删除
- * true: 删除 1
- * false: 没有删除 0
- */
- @TableField("is_delete")
- private Boolean isDelete;
- }
④在ApArticleService中新增方法
- /**
- * 保存app端相关文章
- * @param dto
- * @return
- */
- ResponseResult saveArticle(ArticleDto dto) ;
实现类
- @Autowired
- private ApArticleConfigMapper apArticleConfigMapper;
-
- @Autowired
- private ApArticleContentMapper apArticleContentMapper;
-
- /**
- * 保存app端相关文章
- * @param dto
- * @return
- */
- @Override
- public ResponseResult saveArticle(ArticleDto dto) {
- //1.检查参数
- if(dto == null){
- return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
- }
-
- ApArticle apArticle = new ApArticle();
- BeanUtils.copyProperties(dto,apArticle);
-
- //2.判断是否存在id
- if(dto.getId() == null){
- //2.1 不存在id 保存 文章 文章配置 文章内容
-
- //保存文章
- save(apArticle);
-
- //保存配置
- ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
- apArticleConfigMapper.insert(apArticleConfig);
-
- //保存 文章内容
- ApArticleContent apArticleContent = new ApArticleContent();
- apArticleContent.setArticleId(apArticle.getId());
- apArticleContent.setContent(dto.getContent());
- apArticleContentMapper.insert(apArticleContent);
-
- }else {
- //2.2 存在id 修改 文章 文章内容
-
- //修改 文章
- updateById(apArticle);
-
- //修改文章内容
- ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
- apArticleContent.setContent(dto.getContent());
- apArticleContentMapper.updateById(apArticleContent);
- }
-
-
- //3.结果返回 文章的id
- return ResponseResult.okResult(apArticle.getId());
- }
①在heima-leadnews-wemedia中的service新增接口
- package com.heima.wemedia.service;
-
- public interface WmNewsAutoScanService {
-
- /**
- * 自媒体文章审核
- * @param id 自媒体文章id
- */
- public void autoScanWmNews(Integer id);
- }
实现类
- package com.heima.wemedia.service.impl;
-
- import com.alibaba.fastjson.JSONArray;
- import com.heima.apis.IArticleClient;
- import com.heima.common.aliyun.GreenImageScan;
- import com.heima.common.aliyun.GreenTextScan;
- import com.heima.file.service.FileStorageService;
- import com.heima.model.article.dtos.ArticleDto;
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.wemedia.pojos.WmChannel;
- import com.heima.model.wemedia.pojos.WmNews;
- import com.heima.model.wemedia.pojos.WmUser;
- import com.heima.wemedia.mapper.WmChannelMapper;
- import com.heima.wemedia.mapper.WmNewsMapper;
- import com.heima.wemedia.mapper.WmUserMapper;
- import com.heima.wemedia.service.WmNewsAutoScanService;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.kafka.common.protocol.types.Field;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.util.*;
- import java.util.stream.Collectors;
-
- @Service
- @Slf4j
- @Transactional
- public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {
- @Autowired
- private WmNewsMapper wmNewsMapper;
-
- /**
- * 自媒体文章审核
- *
- * @param id
- */
- @Override
- public void autoScanWmNews(Integer id) {
- //1.查询自媒体文章
- WmNews wmNews = wmNewsMapper.selectById(id);
- if (wmNews == null) {
- throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");
- }
- if (wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
- //从内容中提取出纯文本内容和图片
- Map<String, Object> textAndImages = handleTextAndImages(wmNews);
- //2,审核文本内容 阿里云接口
- boolean isTextScan = handleTextScan((String) textAndImages.get("content"), wmNews);
- if (!isTextScan) return;
-
- //3.审核图片 阿里云接口
- boolean isImageScan = handleImageScan((List<String>) textAndImages.get("images"), wmNews);
- if (!isImageScan) return;
- //4.审核成功,保存app端的相关文章数据
- ResponseResult responseResult = saveAppArticle(wmNews);
- if (responseResult.getCode().equals(200)) {
- throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");
- }
- //回填article_id
- wmNews.setArticleId((Long) responseResult.getData());
- updateWnNews(wmNews, (short) 9, "审核成功");
- }
-
- }
-
- @Autowired
- private IArticleClient articleClient;
-
- @Autowired
- private WmChannelMapper wmChannelMapper;
-
- @Autowired
- private WmUserMapper wmUserMapper;
-
- /**
- * 保存app端相关的文章数据
- *
- * @param wmNews
- */
- private ResponseResult saveAppArticle(WmNews wmNews) {
- ArticleDto dto = new ArticleDto();
- //属性拷贝
- BeanUtils.copyProperties(wmNews, dto);
- //文章的布局
- dto.setLayout(wmNews.getType());
- //文章的频道
- WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());
- if (wmChannel != null) {
- dto.setChannelName(wmChannel.getName());
- }
- //文章的作者
- dto.setAuthorId(wmNews.getUserId().longValue());
- WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
- if (wmUser != null) {
- dto.setAuthorName(wmUser.getName());
- }
- if (wmNews.getArticleId() != null) {
- dto.setId(wmNews.getArticleId());
- }
- dto.setCreatedTime(new Date());
-
- ResponseResult responseResult = articleClient.saveArticle(dto);
- return responseResult;
- }
-
- @Autowired
- private FileStorageService fileStorageService;
- @Autowired
- private GreenImageScan greenImageScan;
-
- /**
- * 审核图片
- *
- * @param images
- * @param wmNews
- * @return
- */
- private boolean handleImageScan(List<String> images, WmNews wmNews) {
- //无阿里云审核接口
- // return true;
-
- boolean flag = true;
- if (images == null || images.size() == 0) {
- return flag;
- }
- //1.从minIo中下载图片
- //图片去重
- images = images.stream().distinct().collect(Collectors.toList());
-
- List<byte[]> imageList = new ArrayList<>();
-
- for (String image : images) {
- byte[] bytes = fileStorageService.downLoadFile(image);
- imageList.add(bytes);
- }
- //2.审核图片
- try {
- Map map = greenImageScan.imageScan(imageList);
- if (map != null) {
- //审核失败
- if (map.get("suggestion").equals("block")) {
- flag = false;
- updateWnNews(wmNews, (short) 2, "当前文章中存在违规内容");
- }
- //不确定信息,需要人工审核
- if (map.get("suggestion").equals("review")) {
- flag = false;
- updateWnNews(wmNews, (short) 3, "当前文章中存在不确定内容");
- }
- }
- } catch (Exception e) {
- flag = false;
- e.printStackTrace();
- }
- return flag;
- }
-
- @Autowired
- private GreenTextScan greenTextScan;
-
- /**
- * 审核纯文本内容
- *
- * @param content
- * @param wmNews
- * @return
- */
- private boolean handleTextScan(String content, WmNews wmNews) {
-
- //无阿里云审核接口
- // return true;
-
- boolean flag = true;
- if ((wmNews.getTitle() + "-" + content).length() == 1) {
- return flag;
- }
- try {
- Map map = greenTextScan.greeTextScan(wmNews.getTitle() + "-" + content);
- if (map != null) {
- //审核失败
- if (map.get("suggestion").equals("block")) {
- flag = false;
- updateWnNews(wmNews, (short) 2, "当前文章中存在违规内容");
- }
- //不确定信息,需要人工审核
- if (map.get("suggestion").equals("review")) {
- flag = false;
- updateWnNews(wmNews, (short) 3, "当前文章中存在不确定内容");
- }
- }
- } catch (Exception e) {
- flag = false;
- e.printStackTrace();
- }
- return flag;
- }
-
- /**
- * 修改文章状态
- *
- * @param wmNews
- * @param status
- * @param reason
- */
- private void updateWnNews(WmNews wmNews, short status, String reason) {
- wmNews.setStatus(status);
- wmNews.setReason(reason);
- wmNewsMapper.updateById(wmNews);
- }
-
- /**
- * 1.从自媒体文章的内容中提取文本和图片
- * 2.提取文章的封面图片
- *
- * @param wmNews
- * @return
- */
- private Map<String, Object> handleTextAndImages(WmNews wmNews) {
- //存储纯文本内容
- StringBuilder stringBuilder = new StringBuilder();
- //存储纯图片地址
- List<String> images = new ArrayList<>();
- //1.从自媒体文章的内容中提取文本和图片
- if (StringUtils.isNotBlank(wmNews.getContent())) {
- List<Map> maps = JSONArray.parseArray(wmNews.getContent(), Map.class);
- for (Map map : maps) {
- //获取纯文本内容
- if (map.get("type").equals("text")) {
- stringBuilder.append(map.get("value"));
- }
- if (map.get("type").equals("image")) {
- images.add((String) map.get("value"));
- }
- }
- }
- //2.提取文章的封面图片
- if (StringUtils.isNotBlank(wmNews.getImages())) {
- String[] split = wmNews.getImages().split(",");
- images.addAll(Arrays.asList(split));
- }
- Map<String, Object> resultMap = new HashMap<>();
- resultMap.put("content", stringBuilder.toString());
- resultMap.put("images", images);
- return resultMap;
-
- }
- }
②单元测试
- package com.heima.wemedia.service;
-
- import com.heima.wemedia.WemediaApplication;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
-
- import static org.junit.Assert.*;
-
-
- @SpringBootTest(classes = WemediaApplication.class)
- @RunWith(SpringRunner.class)
- public class WmNewsAutoScanServiceTest {
-
- @Autowired
- private WmNewsAutoScanService wmNewsAutoScanService;
-
- @Test
- public void autoScanWmNews() {
-
- wmNewsAutoScanService.autoScanWmNews(6238);
- }
- }
在heima-leadnews-wemedia服务中已经依赖了heima-leadnews-feign-apis工程,只需要在自媒体的引导类中开启feign的远程调用即可。注解为:@EnableFeignClients(basePackages = "com.heima.apis")
需要指向apis这个包
服务降级处理
服务降级是服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃
服务降级虽然会导致请求失败,但是不会导致阻塞。
实现步骤:
①:在heima-leadnews-feign-api编写降级逻辑
- package com.heima.apis.article.fallback;
-
- import com.heima.apis.article.IArticleClient;
- import com.heima.model.article.dtos.ArticleDto;
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.common.enums.AppHttpCodeEnum;
- import org.springframework.stereotype.Component;
-
- /**
- * feign失败配置
- * @author itheima
- */
- @Component
- public class IArticleClientFallback implements IArticleClient {
- @Override
- public ResponseResult saveArticle(ArticleDto dto) {
- return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");
- }
- }
在自媒体微服务中添加类,扫描降级代码类的包
- package com.heima.wemedia.config;
-
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
-
- @Configuration
- @ComponentScan("com.heima.apis.article.fallback")
- public class InitConfig {
- }
②远程接口中指向降级代码
- package com.heima.apis.article;
-
- import com.heima.apis.article.fallback.IArticleClientFallback;
- import com.heima.model.article.dtos.ArticleDto;
- import com.heima.model.common.dtos.ResponseResult;
- import org.springframework.cloud.openfeign.FeignClient;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
-
- @FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class)
- public interface IArticleClient {
-
- @PostMapping("/api/v1/article/save")
- public ResponseResult saveArticle(@RequestBody ArticleDto dto);
- }
③:客户端开启降级heima-leadnews-wemedia
- feign:
- # 开启feign对hystrix熔断降级的支持
- hystrix:
- enabled: true
- # 修改调用超时时间
- client:
- config:
- default:
- connectTimeout: 2000
- readTimeout: 2000
①:在自动审核的方法上加上@Async注解(标明要异步调用)
- @Override
- @Async //标明当前方法是一个异步方法
- public void autoScanWmNews(Integer id) {
- //代码略
- }
②:在文章发布成功后调用审核的方法
- @Autowired
- private WmNewsAutoScanService wmNewsAutoScanService;
-
- /**
- * 发布修改文章或保存为草稿
- * @param dto
- * @return
- */
- @Override
- public ResponseResult submitNews(WmNewsDto dto) {
-
- //代码略
-
- //审核文章
- wmNewsAutoScanService.autoScanWmNews(wmNews.getId());
-
- return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
-
- }
③:在自媒体引导类中使用@EnableAsync注解开启异步调用
- @SpringBootApplication
- @EnableDiscoveryClient
- @MapperScan("com.heima.wemedia.mapper")
- @EnableFeignClients(basePackages = "com.heima.apis")
- @EnableAsync //开启异步调用
- public class WemediaApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(WemediaApplication.class,args);
- }
-
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
- return interceptor;
- }
- }
需要完成的功能:
需要自己维护一套敏感词,在文章审核的时候,需要验证文章是否包含这些敏感词
如:私人侦探、针孔摄象、信用卡提现、广告代理、代开发票、刻章办、出售答案、小额贷款…
技术选型
DFA原理
检索过程
文章中包含的图片要识别文字,过滤掉图片文字的敏感词
图片文字识别
什么是OCR?
OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程
入门案例
①:创建项目导入tess4j对应的依赖
- <dependency>
- <groupId>net.sourceforge.tess4j</groupId>
- <artifactId>tess4j</artifactId>
- <version>4.1.1</version>
- </dependency>
②:导入中文字体库, 把资料中的tessdata文件夹拷贝到自己的工作空间下
③编写测试类进行测试
- package com.heima.tess4j;
-
- import net.sourceforge.tess4j.ITesseract;
- import net.sourceforge.tess4j.Tesseract;
-
- import java.io.File;
-
- public class Application {
-
- public static void main(String[] args) {
- try {
- //获取本地图片
- File file = new File("D:\\26.png");
- //创建Tesseract对象
- ITesseract tesseract = new Tesseract();
- //设置字体库路径
- tesseract.setDatapath("D:\\workspace\\tessdata");
- //中文识别
- tesseract.setLanguage("chi_sim");
- //执行ocr识别
- String result = tesseract.doOCR(file);
- //替换回车和tal键 使结果为一行
- result = result.replaceAll("\\r|\\n","-").replaceAll(" ","");
- System.out.println("识别的结果为:"+result);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
1.新建ArticleFreemarkerService创建静态文件并上传到minIO中
- package com.heima.article.service;
-
- import com.heima.model.article.pojos.ApArticle;
-
- public interface ArticleFreemarkerService {
-
- /**
- * 生成静态文件上传到minIO中
- * @param apArticle
- * @param content
- */
- public void buildArticleToMinIO(ApArticle apArticle,String content);
- }
实现类
- package com.heima.article.service.impl;
-
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONArray;
- import com.baomidou.mybatisplus.core.toolkit.Wrappers;
- import com.heima.article.mapper.ApArticleContentMapper;
- import com.heima.article.service.ApArticleService;
- import com.heima.article.service.ArticleFreemarkerService;
- import com.heima.file.service.FileStorageService;
- import com.heima.model.article.pojos.ApArticle;
- import freemarker.template.Configuration;
- import freemarker.template.Template;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.io.ByteArrayInputStream;
- import java.io.InputStream;
- import java.io.StringWriter;
- import java.util.HashMap;
- import java.util.Map;
-
- @Service
- @Slf4j
- @Transactional
- public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
-
- @Autowired
- private ApArticleContentMapper apArticleContentMapper;
-
- @Autowired
- private Configuration configuration;
-
- @Autowired
- private FileStorageService fileStorageService;
-
- @Autowired
- private ApArticleService apArticleService;
-
- /**
- * 生成静态文件上传到minIO中
- * @param apArticle
- * @param content
- */
- @Async
- @Override
- public void buildArticleToMinIO(ApArticle apArticle, String content) {
- //已知文章的id
- //4.1 获取文章内容
- if(StringUtils.isNotBlank(content)){
- //4.2 文章内容通过freemarker生成html文件
- Template template = null;
- StringWriter out = new StringWriter();
- try {
- template = configuration.getTemplate("article.ftl");
- //数据模型
- Map<String,Object> contentDataModel = new HashMap<>();
- contentDataModel.put("content", JSONArray.parseArray(content));
- //合成
- template.process(contentDataModel,out);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- //4.3 把html文件上传到minio中
- InputStream in = new ByteArrayInputStream(out.toString().getBytes());
- String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);
-
-
- //4.4 修改ap_article表,保存static_url字段
- apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId,apArticle.getId())
- .set(ApArticle::getStaticUrl,path));
-
-
- }
- }
-
- }
2.在ApArticleService的saveArticle实现方法中添加调用生成文件的方法
- /**
- * 保存app端相关文章
- * @param dto
- * @return
- */
- @Override
- public ResponseResult saveArticle(ArticleDto dto) {
-
- // try {
- // Thread.sleep(3000);
- // } catch (InterruptedException e) {
- // e.printStackTrace();
- // }
- //1.检查参数
- if(dto == null){
- return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
- }
-
- ApArticle apArticle = new ApArticle();
- BeanUtils.copyProperties(dto,apArticle);
-
- //2.判断是否存在id
- if(dto.getId() == null){
- //2.1 不存在id 保存 文章 文章配置 文章内容
-
- //保存文章
- save(apArticle);
-
- //保存配置
- ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
- apArticleConfigMapper.insert(apArticleConfig);
-
- //保存 文章内容
- ApArticleContent apArticleContent = new ApArticleContent();
- apArticleContent.setArticleId(apArticle.getId());
- apArticleContent.setContent(dto.getContent());
- apArticleContentMapper.insert(apArticleContent);
-
- }else {
- //2.2 存在id 修改 文章 文章内容
-
- //修改 文章
- updateById(apArticle);
-
- //修改文章内容
- ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
- apArticleContent.setContent(dto.getContent());
- apArticleContentMapper.updateById(apArticleContent);
- }
-
- //异步调用 生成静态文件上传到minio中
- articleFreemarkerService.buildArticleToMinIO(apArticle,dto.getContent());
-
-
- //3.结果返回 文章的id
- return ResponseResult.okResult(apArticle.getId());
- }
3.文章微服务开启异步调用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。