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

news/2024/9/17 18:27:24/ 标签: 笔记, spring boot, java, 微服务, 后端

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

测试类

java">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

java">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

java">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
java">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对应其他四个默认值,不下架不删除

java">   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回去
java">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

java">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结构收集

java">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
java">@EnableFeignClients(basePackages = "com.heima.apis")

image-20240907105406797

不报红了了

3.2.4单元测试

image-20240907112752598

选中类名ctrl + shift + T创建

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

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

java">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

java">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

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

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

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

扫描类代码

image-20240907142928422

java">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

java">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

实体

java">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

java">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. 调用方法,判断敏感次数

方法

java">    @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

java">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>

测试类

java">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

java">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)

新的检测类

java">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调用

接口

java">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);
}

实现

java">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.ppmy.cn/news/1524793.html

相关文章

【C++二分查找】2517. 礼盒的最大甜蜜度

本文涉及的基础知识点 C二分查找 贪心&#xff08;决策包容性) LeetCode 2517. 礼盒的最大甜蜜度 给你一个正整数数组 price &#xff0c;其中 price[i] 表示第 i 类糖果的价格&#xff0c;另给你一个正整数 k 。 商店组合 k 类 不同 糖果打包成礼盒出售。礼盒的 甜蜜度 是礼…

Leetcode面试经典150题-141.环形链表

题目比较简单&#xff0c;重点是理解思想 解法都在代码里&#xff0c;不懂就留言或者私信 /*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public…

克雷格·费德里吉谈Apple Intelligence保密技术背后的挑战

苹果必须实现克雷格-费德里吉所说的突破&#xff0c;这样 Apple Intelligence公司才能在云中使用大型语言模型&#xff0c;同时还能保护用户隐私&#xff0c;苹果是这样做的。在"It’s Glowtime"活动中&#xff0c;苹果公司谈到了私有云计算作为保护用户隐私的方式。…

高级算法设计与分析 学习笔记5 红黑树

定义&#xff1a; 根节点必黑&#xff0c;红节点孩子必黑&#xff0c;叶子节点&#xff08;外部节点&#xff0c;null的那种&#xff09;也是黑&#xff0c;每条路的黑节点数量一致。 首先看各个节点的平衡值&#xff0c;从根节点开始算&#xff0c;哪个最后超过1就是从这里开始…

【Unity新闻】Unity将取消Runtime费用

兜兜转转又回来了&#xff0c;一大早就看到Unity发布新闻&#xff0c;将取消Runtime费用&#xff0c;但同时也将提高各级付费账号的年费。这是新任CEO Matt上任后的价格调整策略。 非常不错的一点是&#xff1a; 当 Unity 6 在今年晚些时候发布时&#xff0c;使用 Unity Pers…

问:有一种Java语法叫注解,一起来扒一扒~

在Java编程语言中&#xff0c;注解&#xff08;Annotation&#xff09;和元注解&#xff08;Meta-Annotation&#xff09;为开发者提供了丰富的机制来嵌入元数据&#xff0c;从而增强代码的可读性、可维护性&#xff0c;并允许编译器或运行时环境进行特定的处理。 一、注解&am…

大型语言模型:通过代码生成、调试和 CI/CD 集成改变软件开发的游戏规则

借助 AI&#xff0c;软件开发领域正在经历一个突破性阶段&#xff0c;不断集成最先进的大型语言模型&#xff0c;如 GPT-4 和 Claude Opus。这些模型超越了传统开发人员工具的作用&#xff0c;直接帮助开发人员将口头指令转换为跨各种编程语言的可执行代码&#xff0c;从而加快…

深度学习的零碎知识点

显卡内存 什么是显卡内存 简单来说就是&#xff0c;Windows 会在物理显存/「专用 GPU 内存」不够用或只有集成显卡的情况下&#xff0c;将物理内存 RAM 当作 GPU 的虚拟显存/「共享 GPU 内存」来使用。 什么是 Windows「共享 GPU 内存」&#xff0c;它与 VRAM 有什么不同 (s…

【学习笔记】SSL密码套件之哈希

本篇将介绍TLS/SSL密码套件中常用的哈希算法&#xff0c;包括Poly1305、SHA384、SHA256、SHA、MD5 以上的哈希算法将作为 MAC 使用 MAC - Message Authentication Code 为批量数据提供了完整性&#xff08;Integrity&#xff09;以及真实性&#xff08;Authentication&#xf…

yolo学习 (一) 安装yolov8及训练

随便搞个python环境&#xff0c;直接装或者anaconda都行&#xff0c;python版本最低3.8以上 一、安装yolov8 &#xff08;cpu版本&#xff09; pip install ultralytics yolov8安装版本比较省事&#xff0c;不过这里默认装的是CPU版本 import torch print(torch.__version_…

前端 + 接口请求实现 vue 动态路由

前端 接口请求实现 vue 动态路由 在 Vue 应用中&#xff0c;通过前端结合后端接口请求来实现动态路由是一种常见且有效的权限控制方案。这种方法允许前端根据用户的角色和权限&#xff0c;动态生成和加载路由&#xff0c;而不是在应用启动时就固定所有的路由配置。 实现原理…

【springboot】整合spring security 和 JWT

目录 1. 整合spring security 1. 导入依赖 2. 配置类 3. 实体类实现UserDetails接口 4. 业务逻辑实现类实现UserDetailsService接口 5. 控制类实现登录功能 6. 测试登录功能 2. 分析源码 1. UsernamePasswordAuthenticationToken 2. A…

windows JOB作业类的处理

windows JOB作业类的处理 windows JOB作业类的处理 文章目录 windows JOB作业类的处理 # windows JOB作业类的处理 /* moduel Job.h Notices: */#pragma once #include <malloc.h> //for _alloca; class CJob { private:HANDLE m_hJob; public:CJob(HANDLE hJob NULL);…

论文翻译:USENIX-2021 Extracting Training Data from Large Language Models

Extracting Training Data from Large Language Models 从大型语言模型中提取训练数据 https://www.usenix.org/system/files/sec21-carlini-extracting.pdf 文章目录 从大型语言模型中提取训练数据摘要1 引言 摘要 现在&#xff0c;发布在私有数据集上训练的大型&#xff…

828华为云征文|基于华为云Flexus云服务器X部署Minio服务

文章目录 ❀前言❀Minio简介❀部署环境准备❀yum环境配置❀安装docker❀获取镜像❀创建挂载目录❀启动容器❀查看容器状态❀安全组开放❀浏览器访问❀总结 ❀前言 大家好&#xff0c;我是早九晚十二。 近期华为云推出了最新的华为云Flexus云服务器X&#xff0c;这款云主机在算…

windows 显示进程地址空间

windows 显示进程地址空间 windows 显示进程地址空间 文章目录 windows 显示进程地址空间显示进程地址空间 显示进程地址空间 /* 3-ProcessInfo.cpp 显示进程地址空间 */#include "..\\CommonFiles\\CmnHdr.h" #include "..\\CommonFiles\\Toolhelp.h"#i…

Debian命令行设置samba共享目录

Samba 是一个用于在 Unix/Linux 系统上实现 SMB/CIFS 网络协议的软件套件,使这些系统能够与 Windows 网络共享文件和打印机。在 Debian 10 上安装和配置 Samba 可以实现 Linux 和 Windows 之间的无缝文件共享。 安装 Samba 1. 更新包列表并安装 Samba: sudo apt update sud…

dplyr、tidyverse和ggplot2初探

dplyr、tidyverse 和 ggplot2 之间有紧密的联系&#xff0c;它们都是 R 语言中用于数据处理和可视化的工具&#xff0c;且都源于 Hadley Wickham 的工作。它们各自有不同的功能&#xff0c;但可以无缝协作&#xff0c;帮助用户完成从数据处理到数据可视化的工作流。以下是它们之…

Kubernetes 系列 | k8s入门运维

目录 一、K8S集群搭建1.1 部署方式1.2 了解kubeadm1.3 部署流程1.3.1 初始化配置1.3.2 安装容器运行时1.3.3 安装K8S软件包1.3.4 创建集群 二、集群高可用1.1 集群高可用-堆叠1.2 集群高可用-集群外etcd 三、Pod运维3.1 Pod运维3.2 Pod的生命周期3.3 Pod状况3.4 Pod阶段3.5 容器…

java的BigInteget介绍

当java程序需要处理一个非常大的整数&#xff0c;超过long类型的取值范围&#xff0c;就无法用基本类型对数值接收&#xff0c;这样就要用到BigInteget类。 BigInteger类的方法 BigInteger(String val) 将字符串变为BigInteger类型数据 示例代码如下 import java.math.BigI…