2024/9/6黑马头条跟学笔记(四)

D4内容介绍

image-20240906101020691

阿里三方安全审核

分布式主键

异步调用

feign

熔断降级

1.自媒体文章自动审核

image-20240906100928119

1.1审核流程

image-20240906101332056

image-20240906101633320

查文章——调接口文本审核——minio下载图片图片审核——审核通过保存文章——发布

草稿1,失败2,人工3,发布9

1.2接口获取

注册阿里云,开通内容安全

image-20240906101850390

image-20240906102014686

获取akey和skey

1.3文本内容审核接口

文本垃圾内容检测:https://help.aliyun.com/document_detail/70439.html?spm=a2c4g.11186623.6.659.35ac3db3l0wV5k

图片垃圾内容检测:https://help.aliyun.com/document_detail/70292.html?spm=a2c4g.11186623.6.616.5d7d1e7f9vDRz4

图片垃圾内容Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4

image-20240906103116395

image-20240906103205077

参数1,scene场景

参数2,task (图片地址)

设置两种鉴别场景 收费两次

image-20240906103554040

提供了相应的java sdk

1.4项目集成

image-20240906103648693

1.4.1依赖导入

<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.1.1</version>
</dependency>
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-green</artifactId><version>3.6.6</version>
</dependency>
<dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.9</version>
</dependency>
<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.8.3</version>
</dependency>

1.4.2实体类拷贝

拷贝阿里云审核工具类到common模块的common包下

image-20240906104028267

image-20240906104234811

配置文件里读取并设置

image-20240906104407538

传入content 返回map,信息包含是否通过,人工审核等其他建议

1.4.3配置中心里wemedia添加配置

不会自己百度查如何申请阿里云的accesskey

aliyun:accessKeyId: 自填secret: 自填
#aliyun.scenes=porn,terrorism,ad,qrcode,live,logoscenes: terrorism

image-20240906104853138

1.4.4测试

现在common模块注入那俩util工具类,wemedia微服务test测试类才能使用到

image-20240906105200914

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.heima.common.exception.ExceptionCatch,\com.heima.common.swagger.SwaggerConfiguration,\com.heima.common.swagger.Swagger2Configuration,\com.heima.common.aliyun.GreenImageScan,\com.heima.common.aliyun.GreenTextScan

测试类

package com.heima.wemedia;import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.file.service.FileStorageService;
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 java.util.Arrays;
import java.util.Map;@SpringBootTest(classes = WemediaApplication.class)
@RunWith(SpringRunner.class)
public class AliyunTest {@Autowiredprivate GreenTextScan greenTextScan;@Autowiredprivate GreenImageScan greenImageScan;@Autowiredprivate FileStorageService fileStorageService;@Testpublic void testScanText() throws Exception {Map map = greenTextScan.greeTextScan("我是一个好人,冰毒");System.out.println(map);}@Testpublic void testScanImage() throws Exception {byte[] bytes = fileStorageService.downLoadFile("http://192.168.200.130:9000/leadnews/2021/04/26/ef3cbe458db249f7bd6fb4339e593e55.jpg");Map map = greenImageScan.imageScan(Arrays.asList(bytes));System.out.println(map);}
}

image-20240906111008764

image-20240906111747344

image-20240906111820835

不过听说增强版相较于1.0不需企业认证?但是由于教程没有且不想耗费时间钻研这段直接跳过

image-20240906113645217

image-20240906113705163

当然图片审核也跳过了

1.5分布式主键策略——雪花算法

image-20240906115201523

审核通过时进行文章保存

1.5.1表结构-article库

image-20240906115238739

1对1表关系

表数据满到快溢出来了,进行分表,不过自增id会重复

1.5.2分布式ID技术选型

image-20240906115535822

  • 第一位为0不用,为1负数,所以不用
  • 第二部分为时间戳
  • 前五位机房id 25=32,后五位为每个机房有多少个工作id 也32台,
    32个机房每个机房32台工作id,一共1024台机器
  • 序列号12位,4096个id不重复

image-20240906120453515

ID_WORKER为雪花算法

指定机房id和机器id

在nacos注册中心的article微服务里替换原先的mp配置

image-20240906120542812

image-20240906120701396

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

2.app端文章保存接口

image-20240906131919977

为什么自媒体库也有文章id?。当修改审核通过后根据文章id修改前台的文章 aparticle

image-20240906132546609

怎么没有修改文章配置?因为默认前端会设置好配置,保存时初始化添加即可

image-20240906132453909

feign接口

image-20240906132819500

微服务间的调用使用远程客户端,操作成功且审核成功后返回文章id后续修改通过该id再次远程app端修改文章

如果没审核通过则没有aid

image-20240906133007681

实现步骤

image-20240906133204548

2.1在feign-api微服务定义接口

导入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
定义文章端的接口

接收wm文章的实体dto

image-20240906134346885

package com.heima.model.article.dtos;import com.heima.model.article.pojos.ApArticle;
import lombok.Data;@Data
public class ArticleDto  extends ApArticle {/*** 文章内容*/private String content;
}
在service下的article服务实现接口

image-20240906200114903

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 {@Autowiredprivate ApArticleService apArticleService;@Override@PostMapping("/api/v1/article/save")public ResponseResult saveArticle(@RequestBody ArticleDto dto) {return apArticleService.saveArticle(dto);}}
mapper
package com.heima.article.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleConfig;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface ApArticleConfigMapper extends BaseMapper<ApArticleConfig> {
}
修改ApArticleConfig

添加如下构造函数,当保存文章时初始化实体类,id对应其他四个默认值,不下架不删除

   public ApArticleConfig(Long articleId){this.articleId = articleId;this.isComment = true;this.isForward = true;this.isDelete = false;this.isDown = false;}
service添加保存方法

思路

  1. 没id,保存, 文章 文章配置 文章内容
  2. 有id,传来的文章id对应章内容表里的文章id 进行修改。先根据articleId查出来content实体,然后setContent在insert回去
package com.heima.article.service.impl;import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.Date;
import java.util.List;@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {@AutowiredApArticleMapper apArticleMapper;/*** 加载文章列表** @param dto* @param type 1为加载更多,2为加载最新* @return*/@Overridepublic ResponseResult load(ArticleHomeDto dto, Short loadtype) {// 参数校验// 判断大小是否正确Integer size = dto.getSize();if (size == null || size == 0) {size = Math.min(size, 50);}// 类型参数检验,既不为1,加载更多也不为2加载最新,那么就默认1加载更多if (!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;}// 文章频道校验,如果不指定频道,那就是首页,直接加载最新10条if (StringUtils.isEmpty(dto.getTag())) {dto.setTag(ArticleConstants.DEFAULT_TAG);}// 时间校验。如果没有最大和最小时间,那么说明时间范围为无限,此时降序展示10条最新数据,与前面的Tag频道搭配if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());// 2.查询数据List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);return ResponseResult.okResult(apArticles);}@Autowiredprivate ApArticleConfigMapper articleConfigMapper;@Autowiredprivate ApArticleContentMapper apArticleContentMapper;@Autowiredprivate ApArticleMapper articleMapper;/*** 保存app端相关文章** @param dto* @return*/@Overridepublic ResponseResult saveArticle(ArticleDto dto) {// 0. 校验参数if (dto == null) {return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}// 保存文章,先拷贝ApArticle apArticle = new ApArticle();BeanUtils.copyProperties(dto, apArticle);// 1. 没由id的情况下if (dto.getId() == null) {// 保存文章save(apArticle);// 初始化文章配置实体,保存文章实体ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());articleConfigMapper.insert(apArticleConfig);// 保存文章内容到文章内容表ApArticleContent apArticleContent = new ApArticleContent();// id+content id相当于开后门,后续修改update可以根据他找到家~apArticleContent.setArticleId(apArticle.getId());apArticleContent.setContent(dto.getContent());apArticleContentMapper.insert(apArticleContent);}// 2.存在id,那就是修改了else {// 直接修改文章,可能是封面?还是标题?articleMapper.updateById(apArticle);// 修改分出去的另一张文章内容表 ,根据文章id找,使用lambdaApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));apArticleContent.setContent(dto.getContent());apArticleContentMapper.updateById(apArticleContent);}//3结果返回idreturn ResponseResult.okResult(apArticle.getId());}
}
启动postman发保存请求测试

http://localhost:51802/api/v1/article/save

{"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黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景"
}

image-20240906215853985

image-20240906215934366

好家伙2028年

好家伙讲义authoId,少个r

image-20240906220151253

修改测试
{"title":"黑马头条项目背景66666","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黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景,黑马头条项目背景"
}

先捋一下前面的服务

image-20240906221533057

model,common,utl模块静态引用

feign-api内部服务互相访问

gateway外部请求重定向具体服务(具体的某个网关添加拦截器id加工处理操作对象标识)

service多个业务服务

basic自定义starter(minio)后续文章保存微服务引入并调用生成静态url路径并保存内容到数据库

test测试模块包含对freemarker的使用,和minio的使用

  • 当有很多标识时使用枚举或常量

  • 共同条件放最后,独有条件前判断,过五关斩六将

  • 大文本大空间分表,减少数据库压力

  • minio存静态url,减少查询大表数据

  • 提交文章时根据类型进行 设置type并且设置从内容抽取封面图,文章and素材绑定写表,删素材必须先删文章

  • 自媒体保存文章,调用前台文章的保存,或修改,且配置表不变,只在新增时变化

  • 存素材,拦截器存id,接口拿id,以id存对应素材,和收藏关系

  • feign要调用谁,谁实现接口,到时候审核完毕则调用该保存接口

  • 微服务实现流程

    springboot

    • 配置bootstrap yml,填入远程注册中心端口

    • 写服务名(后续拿着这个去注册中心匹配),
      服务发现注册:discovery配置云端nacos的服务地址+端口

      配置注册:config,配置云端nacos IP+PORT ,指定文件后缀yml

    nacos中心

    • datasource,mp配置

    • 路由则填写每个服务的路由名

      • id 唯一标识

      • lb://leadnews-user 标识转发到后端服务的地址
        image-20240906230737469

      • predicates翻译过来是谓词?转发路由所需满足的条件?
        -Path=/user/** 必须以/user打头的请求路径

      • stripPrefix,翻译为条带前缀 等于1意为去掉路径的第一标识

        /user/** => /** 这样就能成功定位到后端的某个接口

3.自媒体文章审核

3.1表结构

image-20240907000351766

3.2实现

3.2.1思路

image-20240907000419931

自媒体审核在自媒体微服务下

3.2.2service

传自媒体文章id

package com.heima.wemedia.service;public interface WmNewsAutoScanService {/*** 自媒体文章审核* @param id  自媒体文章id*/public void autoScanWmNews(Integer id);
}

3.2.3实现

image-20240907092559621

content的结构是由多个对象组成,用map的key-value结构收集

package com.heima.wemedia.service.impl;import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.heima.apis.article.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.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;@Service
@Slf4j
@Transactional
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {@Autowiredprivate WmNewsMapper wmNewsMapper;/*** 自媒体文章审核** @param id 自媒体文章id*/@Overridepublic 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())) {// 对其内容抽取取出所有的Text和Image,这里我们封装一个方法Map<String, Object> stringObjectMap = extractTextAndImage(wmNews);// 2.文本审核方法.传文本和文章,后续根据审核结果修改文章数据库的状态为人工审核还是审核成功的状态if (scanText(stringObjectMap.get("text"), wmNews)) return;// 3.图片审核if (scanImages(wmNews, (List<String>) stringObjectMap.get("images"))) return;// 4.审核通过,修改状态为已发布ResponseResult result = saveApArticle(wmNews);if (result.getCode() != 200) {throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");}//回填article_id,下次进行修改会使用到wmNews.setArticleId((Long) result.getData());updateWmNews(wmNews, (short) 9, "审核成功");}}@Autowiredprivate WmChannelMapper wmChannelMapper;@Autowiredprivate WmUserMapper wmUserMapper;@Autowiredprivate IArticleClient articleClient;private ResponseResult saveApArticle(WmNews wmNews) {wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());ArticleDto articleDto = new ArticleDto();BeanUtils.copyProperties(wmNews, articleDto);// 文章布局,自媒体里叫做type,而ap端叫layout,也就是封面那玩意了其实articleDto.setLayout(wmNews.getType());// 拷贝频道名 因为分表了WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());if (wmChannel != null) {articleDto.setChannelName(wmChannel.getName());}// 作者名Integer userId = wmNews.getUserId();WmUser wmUser = wmUserMapper.selectById(userId);articleDto.setAuthorId(Long.valueOf(userId));if (wmUser != null) {articleDto.setAuthorName(wmUser.getName());}// 文章id设置同步自媒体和app端idif (wmNews.getArticleId() != null) {articleDto.setId(wmNews.getArticleId());}// 创建时间articleDto.setCreatedTime(new Date());//远程调用传递数据ResponseResult responseResult = articleClient.saveArticle(articleDto);return responseResult;}@Autowiredprivate GreenImageScan greenImageScan;@Autowiredprivate FileStorageService fileStorageService;private boolean scanImages(WmNews wmNews, List<String> images) {boolean flag = true;// 判断有无图片,无图片无需审核if (images.size() == 0 || images == null) {return flag;}// 依次下载图片到list里,用bytes数组存储每一张图片// 由于封面可能来自于内容,所以要去重images = images.stream().distinct().collect(Collectors.toList());List<byte[]> bytes = new ArrayList<>();for (String image : images) {byte[] imageBytes = fileStorageService.downLoadFile(image);bytes.add(imageBytes);}// 将该集合传给接口审核try {Map map = greenImageScan.imageScan(bytes);if (map != null) {if (map.get("suggestion").equals("block")) {flag = false;updateWmNews(wmNews, (short) 2, "当前文章存在违规内容");}// 不确定信息  需要人工审核if (map.get("suggestion").equals("review")) {flag = false;updateWmNews(wmNews, (short) 3, "当前文章存在不确定内容,需要人工审核");}}} catch (Exception e) {flag = false;e.printStackTrace();}return flag;}@Autowiredprivate GreenTextScan greenTextScan;private boolean scanText(Object text, WmNews wmNews) {boolean flag = true;if (StringUtils.isBlank(wmNews.getTitle()) || StringUtils.isBlank(text.toString())) {return flag;}try {Map map = greenTextScan.greeTextScan(wmNews.getTitle() + "-" + text.toString());if (map != null) {if (map.get("suggestion").equals("block")) {flag = false;updateWmNews(wmNews, (short) 2, "当前文章存在违规内容");} else if (map.get("suggestion").equals("review")) {flag = false;updateWmNews(wmNews, (short) 3, "当前文章存在不确定内容,需要人工审核");}}} catch (Exception e) {flag = false;e.printStackTrace();}return flag;}private void updateWmNews(WmNews wmNews, short status, String reason) {wmNews.setStatus((short) status);wmNews.setReason(reason);wmNewsMapper.updateById(wmNews);}private Map<String, Object> extractTextAndImage(WmNews vmNews) {// 抽取出来content然后加工String content = vmNews.getContent();// 初始化String容器和List<String>容器来存储Text和ImagesStringBuilder stringBuilder = new StringBuilder();List<String> images = new ArrayList<>();// 对content进行拆分,拆分出Text和Images// 1,从内容提取出图片和文本if (StringUtils.isNotBlank(content)) {// 由于存放的数据是json字符串,这里我们把它转为对象,且属性为多个Map,KV结构List<Map> maps = JSON.parseArray(content, Map.class);// 遍历map,如果是文本就放到字符串构造器,图片放list里for (Map map : maps)if (map.get("type").equals("text")) {stringBuilder.append(map.get("value"));} else if (map.get("type").equals("image")) {images.add(map.get("value").toString());}}// 2.提取文章封面图String covers = vmNews.getImages();if (StringUtils.isNotBlank(covers)) {// 转为字符串数组String[] split = covers.split(",");// 转为List并且addAll该list的所有内容到图片集合images.addAll(Arrays.asList(split));}// 3.将文本和图片存入map里后续审核Map<String, Object> stringObjectMap = new HashMap<>();stringObjectMap.put("content", stringBuilder.toString());stringObjectMap.put("images", images);return stringObjectMap;}
}

添加feign注解,扫描feign apis包否则iarticleClient注入,远程调用失效

image-20240907105128652
@EnableFeignClients(basePackages = "com.heima.apis")

image-20240907105406797

不报红了了

3.2.4单元测试

image-20240907112752598

选中类名ctrl + shift + T创建

指定上下文和运行类(运行测试类之前先启动aparticle服务,因为feign要调他

id填写之前创建的数据
image-20240907113443216

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 {@Autowiredprivate WmNewsAutoScanService wmNewsAutoScanService;@Testpublic void autoScanWmNews() {wmNewsAutoScanService.autoScanWmNews(6238);}
}

如果出现timeout的在feign模块下新建配置文件application.yml 超时时间拉满,因为你电脑太fw了加载半天

#hystrix的超时时间
hystrix:command:default:execution:timeout:enabled: trueisolation:thread:timeoutInMilliseconds: 3000000
#ribbon的超时时间
ribbon:ReadTimeout: 30000000ConnectTimeout: 300000000

image-20240907124520272

wmNews测试通过,状态为已发布,审核成功

image-20240907124631786

用户浏览端数据库增加成功

image-20240907124729124

内容配置表新增成功…

3.2.5fegin接口调用方式

image-20240907140013186

wemediea服务(发请求的人)引入apis依赖,调用article客户端发请求

引导类增加注释,开启feign指定客户端端包

4.feign调用服务降级

4.1场景

image-20240907135059777

服务自我保护,保护服务不崩溃

会导致请求失败,但是不会阻塞请求也就是说不会卡住,会闪退的意思

当服务接收不了太多请求时降级,

4.2步骤

image-20240907135253130

①实现接口,设置响应

②远程接口注解里增加属性fallback,指向降级类

③客户端配置文件开启熔断降级支持

降级类

image-20240907142026030

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;@Component
public class IArticleClientFallback implements IArticleClient {@Overridepublic ResponseResult saveArticle(ArticleDto dto) {return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");}
}

远程接口指向降级代码类

image-20240907142211952

@FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class)

由于该降级类存在在feign模块,不在wemedia下,不能被其加载component注解失效,因此要在wemedia服务下创建初始配置类扫描apis/fallback包,

因为该降级处理类作用在客户端wemedia上也就是发请求的那一方,所以客户端要将该类加载到自身的容器里

扫描类代码

image-20240907142928422

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

配置文件开启服务降级

一般不在编码的配置文件里设置 而在nacos配置中心

为什么基本配置都写在nacos呢?

  • 统一管理
  • 动态更新,不用重新编译打包部署重启,直接改配置中心
  • 版本历史回滚
  • 权限控制,不同角色配置内容不一样
  • 健康检查,配置文件坏了及时通知管理员
  • 格式灵活,yml xml。。。等等 还有许多由于不光说不练假把式就不列举了,反正用到再来

这里我们前面在feign模块下也有设置了。此时我们可以把原先的删除重新在这指定超时时间即可

image-20240907143648977

feign:# 开启feign对hystrix熔断降级的支持hystrix:enabled: true# 修改调用超时时间client:config:default:connectTimeout: 2000readTimeout: 2000

注意这里缩进不要复制错了,刚才报了1次错浪费了我几分钟,泪目

4.3测试

在文章保存实现类设置定时器,3000超过时间则失败,尝尝咸淡

image-20240907144151844

   try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}

审核测试,由于之前创建了多条,这回还另一篇待审核的试试,你也可以修改刚才审核成功的9为1然后试试看

image-20240907144333151

运行查看

image-20240907150003670

image-20240907150029590

成功到达降级类

image-20240907150057996

记得删掉 “优化加钱代码”

image-20240907150151559

5.发布文章后异步审核

哪种好?异步的,发出审核调用后直接下一条代码接着干,同步还得搁那傻傻的等待,阻塞请求,体验感极差

5.1步骤

image-20240907150445417

①方法上加注解

②成功发布后调用审核方法

③引导类加注解开启异步调用

image-20240907151209093

6.综合测试

服务启动列表

image-20240907151303102

image-20240907151417426

测试发布&&自动审核

image-20240907151529996

image-20240907155345729

自动审核成功

如果你的内容很多,那么数据库保存 素材~文章 关系表会很慢,因此下文拿不到

image-20240907160228462

就是说前面保存的vmNews的动作还没执行完,id也没生成好,由于数据库操作是异步的

image-20240907162331833

image-20240907162444514
这俩其中之一涉及数据库的操作 没执行完此时

这一步还没搞完因此select不到,

image-20240907162555179

image-20240907162903912

我们可以给他加一个定时器1秒让他执行完前面数据库操作再来select,不过一旦涉及到了定时器,业务就开始变得臃肿了感觉

刷新再次添加大内容的文章,等待三秒刷新 审核成功,

image-20240907163621202

太捞了b样的,我一张素材库还只能添加一次。因为他是素材库和imgurl一一对应的,也就是前面比较url.size=dbUrl.size也就是说我一张图片给多次引用就产生了 dbUrl=1 urlSize=5次引用,

image-20240907164347907

不过可以给前端content内的url去重,测试保存成功

image-20240907164149717

多图保存202497

还有个槽点,这个图片添加的按钮太tm小了吧,而且一次一张,猴年马月

如果你有内容安全的接口如果输入敏感词会得到以下效果

image-20240907164745558

image-20240907164947886

敏感ak47,csdn不会检测到吧 (笑)

如果审核过程中炸了不影响发布,不过审核状态一直为待审核

image-20240907165300675

image-20240907165316142

上页面了,不过一直待审核

7.自管理的敏感词

image-20240907171422664

image-20240907170607109

意思是以什么开头,且以什么为end,说明该词为敏感词 通俗点举例,end为1,说明这个词完蛋了,end结束了

例如

  • 冰,不是end为1
  • 冰,不是end为1,
    • 下一个字符不存在,只有单个冰,则结束
    • 下一个字符毒存在,get索引,查下一个如果下一个字符存在,则获取是否为最后一个,isEnd,如果是,则该tree成立,判断为敏感词
    • 下一个字符块存在,get索引,不在该树里,且跳出结束 ,但是冰X毒怎么算?

直至检测到最后一个值不在该树里则通过

7.1dfv算法工具类

检测到则提示出现次数

7.2文章审核集成自管理敏感词

image-20240907185202638

实体

package com.heima.model.wemedia.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 java.io.Serializable;
import java.util.Date;/*** <p>* 敏感词信息表* </p>** @author itheima*/
@Data
@TableName("wm_sensitive")
public class WmSensitive implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 敏感词*/@TableField("sensitives")private String sensitives;/*** 创建时间*/@TableField("created_time")private Date createdTime;}

mapper

package com.heima.wemedia.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmSensitive;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface WmSensitiveMapper extends BaseMapper<WmSensitive> {
}

业务实现

  1. 查所有敏感词
  2. 初始化
  3. 调用方法,判断敏感次数

方法

    @Autowiredprivate WmSensitiveMapper wmSensitiveMapper;private boolean handleSensitiveScan(String content, WmNews wmNews) {boolean flag = true;// 查询所有的敏感词.仅需一个字段,用map加lambdaList<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));// 转为字符串类型的集合,后续进行工具类调用List<String> sensitiveList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());// 初始化敏感词库SensitiveWordUtil.initMap(sensitiveList);// 开始检测返回值为敏感的词名Map<String, Integer> stringIntegerMap = SensitiveWordUtil.matchWords(content);if (stringIntegerMap.size() > 0) {updateWmNews(wmNews, (short) 2, "包含敏感词" + stringIntegerMap);flag = false;}return flag;}

调用地方

image-20240907192845636

if (!handleSensitiveScan((String) stringObjectMap.get("content"), wmNews))return;

测试

image-20240907193034924

image-20240907193548270

8.图片文字识别

image-20240907193856404

8.1需求

识别图片文字。过滤敏感词并返回错误信息

8.2技术点

image-20240907194017164

OCR,optical character recognition 光学字符识别,电子设备通过字符的亮暗确定形状,字符识别将形状翻译成文字的 过程

8.3Tesseract-OCR

image-20240907194150394

  • 支持UTF8编码,100多种语言识别
  • 多种输出格式,文本,html,pdf
  • 图片要清晰好分辨

8.4步骤

image-20240907194640295

image-20240907194816316

依赖

 <dependencies><dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess4j</artifactId><version>4.1.1</version></dependency></dependencies>

测试类

package com.heima;import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;import java.io.File;public class Main {public static void main(String[] args) throws TesseractException {// 创建实例Tesseract tesseract = new Tesseract();//设置库路径tesseract.setDatapath("E:\\javaOCR\\tessdata");//设置语言 -->简体中文tesseract.setLanguage("chi_sim");File file = new File("E:\\javaOCR\\testImg\\1.png");String result = tesseract.doOCR(file);// System.out.println("识别的结果为"+result);// 替换换行和回车,同时替换掉的用-连接System.out.println("识别的结果为"+result.replaceAll("\\r|\\n", "-"));}
}

测试结果

image-20240907195932903

image-20240907200219769

哎哟我去,一丝不差

8.5集成

image-20240907200406117

common模块创建工具类,封装tess4j方法

image-20240907212824866

package com.heima.common.tess4j;import lombok.Getter;
import lombok.Setter;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.awt.image.BufferedImage;@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "tess4j")
public class Tess4jClient {private String dataPath;private String language;public String doOCR(BufferedImage image) throws TesseractException {//创建Tesseract对象ITesseract tesseract = new Tesseract();//设置字体库路径tesseract.setDatapath(dataPath);//中文识别tesseract.setLanguage(language);//执行ocr识别String result = tesseract.doOCR(image);//替换回车和tal键  使结果为一行result = result.replaceAll("\\r|\\n", "-").replaceAll(" ", "");return result;}}

注册到工厂

image-20240907213307420

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.heima.common.exception.ExceptionCatch,\com.heima.common.swagger.SwaggerConfiguration,\com.heima.common.swagger.Swagger2Configuration,\com.heima.common.aliyun.GreenTextScan,\com.heima.common.aliyun.GreenImageScan,\com.heima.common.tess4j.Tess4jClient

配置文件

在wemedia下yml配置补充

image-20240907213133958

tess4j:data-path: E:\javaOCR\tessdatalanguage: chi_sim

使用

在图片审核方法中,下载图片后的步骤加入,注入使用即可(先转字节数组输入流,在通过ImageIO读取到bufferedImg)

新的检测类

private boolean scanImages(WmNews wmNews, List<String> images) {boolean flag = true;// 判断有无图片,无图片无需审核if (images.size() == 0 || images == null) {return flag;}images = images.stream().distinct().collect(Collectors.toList());List<byte[]> bytes = new ArrayList<>();try {// 依次下载图片到list里,用bytes数组存储每一张图片// 由于封面可能来自于内容,所以要去重for (String image : images) {byte[] imageBytes = fileStorageService.downLoadFile(image);ByteArrayInputStream stream = new ByteArrayInputStream(imageBytes);BufferedImage bufferedImage = ImageIO.read(stream);String ocrContent = tess4jClient.doOCR(bufferedImage);boolean scanResult = handleSensitiveScan(ocrContent, wmNews);if (!scanResult) {return scanResult;}bytes.add(imageBytes);}} catch (Exception e) {e.printStackTrace();}// // 将该集合传给接口审核// try {//     Map map = greenImageScan.imageScan(bytes);//     if (map != null) {//         if (map.get("suggestion").equals("block")) {//             flag = false;//             updateWmNews(wmNews, (short) 2, "当前文章存在违规内容");//         }////         // 不确定信息  需要人工审核//         if (map.get("suggestion").equals("review")) {//             flag = false;//             updateWmNews(wmNews, (short) 3, "当前文章存在不确定内容,需要人工审核");//         }//     }// } catch (Exception e) {//     flag = false;//     e.printStackTrace();// }return true;}

发图片测试

image-20240907215331386

image-20240907215347939

识别成功

image-20240907215411632

返回

image-20240907215431223

数据库

image-20240907215503322

前台

9.文章详情静态文件生成

image-20240907215821490

代码思路

根据内容上传文件返回url的接口和实现

在保存文章业务后面增加上传静态页面

方法增加异步注解

引导类开启异步注解 和

实现类事务注解(操作了数据库)

由原先直接指定id查内容到现在 现成apdto 含有content调用

接口

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.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleContent;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {// 注入template模板类@Autowiredprivate Configuration configuration;// 文件上传类@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleMapper apArticleMapper;public void buildArticleToMinIO(ApArticle apArticle,String content)  {// 1.获取文章内容if (StringUtils.isNotBlank(content)) {// 2.文章内容通过freemarker生成html文件StringWriter out = new StringWriter();Template template = null;try {template = configuration.getTemplate("article.ftl");Map<String, Object> params = new HashMap<>();params.put("content", JSONArray.parseArray(content));template.process(params, out);} catch (Exception e) {throw new RuntimeException(e);}InputStream is = new ByteArrayInputStream(out.toString().getBytes());// 3.把html文件上传到minio中String path = fileStorageService.uploadHtmlFile("",  apArticle.getId()+ ".html", is);// 4.修改ap_article表,保存static_url字段ApArticle article = new ApArticle();article.setId(apArticle.getId());article.setStaticUrl(path);apArticleMapper.updateById(article);}}
}

在article服务的saveArticle方法调用

image-20240907222644827

测试

重启文章服务,生成静态文件出加断点调试

image-20240907223015640

image-20240907223041605

image-20240907223211536

访问成功

10.今日作业

任务1

image-20240907223317672

微服务之间崩了没互相之间通知一下,使用seata 完成微服务一致性

任务2

image-20240907223422270

定时发布的文章审核时间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/53346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

How can I load the openai api configuration through js in html?

题意&#xff1a;怎样在HTML中通过JavaScript加载OpenAI API配置 问题背景&#xff1a; I am trying to send a request through js in my html so that openai analyzes it and sends a response, but if in the js I put the following: 我正在尝试通过HTML中的JavaScript发…

Qt/C++ 个人开源项目#串口助手(源码与发布链接)

一、项目概述 该串口助手工具基于Qt/C开发&#xff0c;专为简化串口通信调试与开发而设计&#xff0c;适合新手快速上手。工具具有直观的用户界面和丰富的功能&#xff0c;旨在帮助用户与串口设备建立可靠通信&#xff0c;便于调试、数据传输和分析。 二、主要功能 波特率&a…

【信创建设】信息系统信创建设整体技方案(word原件完整版)

信创&#xff0c;即“信息技术应用创新”。我国自主信息产业聚焦信息技术应用创新&#xff0c;旨在通过对IT硬件、软件等各个环节的重构&#xff0c;基于我国自有IT底层架构和标准&#xff0c;形成自有开放生态&#xff0c;从根本上解决本质安全问题&#xff0c;实现信息技术可…

多款式随身WiFi如何挑选,USB随身WiFi、无线电池随身WiFi、充电宝随身WiFi哪个好?优缺点分析!

市面上的随身WiFi款式多样琳琅满目&#xff0c;最具代表性的就是USB插电款、无线款和充电宝款。今天就来用一篇文章分析一下这三种款式的优缺点。 USB插电款 优点&#xff1a;便宜&#xff0c;无需充电&#xff0c;在有电源的地方可以随时随地插电使用&#xff0c;比如中兴的U…

Spring Boot 部署方案!打包 + Shell 脚本详解

本篇和大家分享的是springboot打包并结合shell脚本命令部署&#xff0c;重点在分享一个shell程序启动工具&#xff0c;希望能便利工作&#xff1b; profiles指定不同环境的配置 maven-assembly-plugin打发布压缩包 分享shenniu_publish.sh程序启动工具 linux上使用shenniu_p…

python进阶篇-day07-高级语法与正则

day07-python其他高级语法 一. with(上下文管理) 介绍 概述 一个类只要实现了__ enter __ () 和 __ exit __ ()方法, 这个类就是一个上下文管理器类, 该类的对象 上下文管理器对象 目的 节约资源, 提高效率, 避免手动释放资源, 且出bug的时候, 也会自动尝试释放资源 特点…

【Centos】绕开报错ModuleNotFoundError: No module named ‘dnf‘

问题原因&#xff1a; 安装python3.9后不能使用yum Traceback (most recent call last):File "/usr/bin/yum", line 57, in <module>from dnf.cli import main ModuleNotFoundError: No module named dnf绕开yum解决方法&#xff1a; 直接下载相关依赖包 htt…

怎样将手机屏幕(远程)投屏到家里的大电视上?

我不住家里&#xff0c;前几次回去都会替老爸老妈清理手机。这两个星期没空回去&#xff0c;老爸吐槽手机用几天就又卡了&#xff0c;其实就是清理一些手机缓存的问题。 我说我远程控制他的手机&#xff0c;给他清理一下。他一听“控制”就不喜欢&#xff0c;说我大了&#xf…

【Python知识宝库】文件操作:读写文件的最佳实践

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、文件读取1. 使用open函数2. 逐行读取3. 使用readlines和readline 二、文件写入1. 写入文本2. 追加内容3. 写入…

使用shell脚本安装mysql8,进行主从备份配置

思路 在3台主机上安装mysql进行主从备份配置 使用rpm包yum安装mysql 首先&#xff0c;我们要准备好安装文件&#xff0c;首先下载rpm包 wget -P "/opt/" https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm 然后执行安装&#xff08;默认已配置…

HMI设计:嵌入式设备和电脑的差异化,工控领域首选。

嵌入式设备属于专机专用&#xff0c;电脑是通用&#xff0c;从性能、用途、特殊能力、成本、通信上嵌入式设备完全优于电脑&#xff0c;是工控领域的首选。 嵌入式设备和电脑在很多方面有着显著的差异&#xff0c;主要体现在以下几个方面&#xff1a; 1. 设计用途&#xff1a…

在js渲染的dom中的事件中传递对象

在某些情况下&#xff0c;可能需要将整个对象或部分对象嵌入到 HTML 元素的属性中&#xff0c;可以将对象数据序列化为 JSON 字符串&#xff0c;存储在 data-* 自定义属性中。这样可以在事件中取出并解析对象数据&#xff1a; <!DOCTYPE html> <html lang"en&qu…

C++万字解读类和对象(上)

1.类的定义 class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。 为了区分成员变量&…

[QT] QT事件与事件重写

一.事件 事件(event)是由系统或者 Qt本身在不同的场景下发出的。当用户按下鼠标、敲下键盘&#xff0c;或者是窗口关闭等都会发出一个相应的事件。 一些事件在用户操作时发出(如鼠标/键盘事件); 另一些事件则是由系统自动发出(如计时器事件)。 Qt窗口中对于产生的一系列事件都…

MarkdownEditor 配置以及使用

MarkdownEditor 配置以及使用 MarkdownEditor是一款基于浏览器的 Markdown 编辑器&#xff0c;虽然他是独立软件&#xff0c;但该软件内嵌一个浏览器。功能非常简单实用、反应速度很快&#xff0c;号称是Markdown领域的NotePad&#xff08;记事本&#xff09;。 MarkdownEdit…

面向对象23种设计模式通俗理解

终点即是起点,自强不息! 设计模式的理解 设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 模式&#xff1a;在某些场景下&#xff0c;针对某类问题的某种通用的解决方案。 场景&#xff1a;项目所在的环境 问题&#xff1a;约束条件&#xff0c;项目目标…

vue3 项目中使用git

一.vue项目创建 二.创建本地仓库并和远程仓库进行绑定 在vue3-project-git 项目文件夹下 初始化一个新的Git仓库&#xff0c;可以看到初始化成功之后就会出现一个.git文件&#xff0c;该文件包含所有必要的 Git 配置和版本控制信息。 创建远程仓库: 打开gitee ,点击右上角 ‘…

MapSet之二叉搜索树

系列文章&#xff1a; 1. 先导片--Map&Set之二叉搜索树 2. Map&Set之相关概念 目录 前言 1.二叉搜索树 1.1 定义 1.2 操作-查找 1.3 操作-新增 1.4 操作-删除(难点) 1.5 总体实现代码 1.6 性能分析 前言 TreeMap 和 TreeSet 是 Java 中基于搜索树实现的 M…

DELTA_IA-ASD_ASDA-A2简明教程

该文章仅供参考&#xff0c;编写人不对任何实验设备、人员及测量结果负责&#xff01;&#xff01;&#xff01; 0 引言 文章主要介绍电机的硬件连接、软件配置、转动调试以及软件控制。文章中提到的内容在产品手册中都有说明&#xff0c;强烈建议在操作前通读产品手册&#…

RocketMQ高级特性三-消费者分类

目录 前言 概述 区别 PullConsumer 定义与概述 原理机制 使用场景 优缺点 Java 代码示例 SimpleConsumer 定义与概述 原理机制 使用场景 优缺点 Java 代码示例 PushConsumer 定义与概述 原理机制 使用场景 优缺点 Java 代码示例 总结 前言 RocketMQ中的消…