首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >SpringCloud项目阶段六:feign服务降级处理以及基于DFA算法的自管理敏感词审核和tess4j图片文字识别集成

SpringCloud项目阶段六:feign服务降级处理以及基于DFA算法的自管理敏感词审核和tess4j图片文字识别集成

作者头像
varin
发布2025-09-28 13:12:26
发布2025-09-28 13:12:26
12300
代码可运行
举报
文章被收录于专栏:/root/root
运行总次数:0
代码可运行

feign远程接口调用

  1. 流程:
  1. 添加依赖

  1. 在service父模块中添加common模块
代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-common</artifactId>
</dependency>
  1. 使用说明:
      1. 直接注入
代码语言:javascript
代码运行次数:0
运行
复制
@Autowired
private IArticleClient articleClient;
代码语言:javascript
代码运行次数:0
运行
复制
2. 在启动类中开启feign并添加扫描geign路径
代码语言:javascript
代码运行次数:0
运行
复制
package com.heima.wemedia;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;


@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.heima.wemedia.mapper")
@EnableFeignClients(basePackages = "cn.varin.apis.article")
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;
    }
}

审核成功后将自媒体文章添加到app端中

文件:heima-leadnews-service/heima-leadnews-wemedia/src/main/java/com/heima/wemedia/service/impl/WmNewAutoScanServiceImpl.java 方法名:saveArticle

代码语言:javascript
代码运行次数:0
运行
复制
@Autowired
    private IArticleClient articleClient;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmUserMapper wmUserMapper;

    // 保存app端文章
    private ResponseResult saveArticle(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.setChannelId(wmNews.getChannelId());
        }

        //作者
        dto.setAuthorId(wmNews.getUserId().longValue());
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());
        if(wmUser != null){
            dto.setAuthorName(wmUser.getName());
        }

        // 判断是否是已经审核过的app文章
        if (wmNews.getArticleId()!=null) {

            dto.setId(wmNews.getArticleId());
        }
        dto.setCreatedTime(new Date());
        ResponseResult save = articleClient.save(dto);
        return save;


    }

综合测试

  1. 测试类
代码语言:javascript
代码运行次数:0
运行
复制
package cn.varin;

import com.heima.wemedia.WemediaApplication;
import com.heima.wemedia.service.WmNewAutoScanService;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = WemediaApplication.class)
@RunWith(SpringRunner.class)
public class handleScanTest {
    @Autowired
    private WmNewAutoScanService wmNewAutoScanService;
    @Test
    public void wmNewAutoScanTest() {
        wmNewAutoScanService.AutoScanWmNews(6232);
    }
}
  1. 效果

ap_article表:

wm_news表:

feign远程调用服务降级处理

具体实现
  1. 编写一个fallback类并实现IArticleClient接口
代码语言:javascript
代码运行次数:0
运行
复制
package cn.varin.apis.article.fallback;

import cn.varin.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;

import javax.xml.ws.Response;
import java.util.ResourceBundle;

@Component
public class IArticleClientFallback implements IArticleClient {
    @Override
    public ResponseResult save(ArticleDto dto) {
        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"请求错误,获取数据失败");
    }
}
  1. 载IArticleClient接口中添加fallback配置
代码语言:javascript
代码运行次数:0
运行
复制
package cn.varin.apis.article;


import cn.varin.apis.article.fallback.IArticleClientFallback;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.pojos.ApArticleConfig;
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")
    ResponseResult save(@RequestBody ArticleDto dto);
}
  1. 配置applicaiton.yml
代码语言:javascript
代码运行次数:0
运行
复制
feign:
  # 开启feign对hystrix熔断降级的支持
  hystrix:
    enabled: true
  # 修改调用超时时间
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
模拟
  1. 在调用IArticleClient中的save方法后,使用线程睡眠5秒。
  1. 测试结果

异步任务审核文章

同步和异步概念

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

需求

当我们在自媒体端上发布文章后,并不是一定需要直接返回文章的审核结果,而是可以等待文章发布后,一段时间后再得到结果也是可以的。 所有,根据以上需求,我们可以在文章发布后,将审核的操作该成异步的操作。

实现
  1. 在审核方法上田间 @ Async注解
  1. 在多媒体类上启动异步注解@EnableAsync

自管理敏感词集成

新需求:

  • 文章审核不能过滤一些敏感词: 私人侦探、针孔摄象、信用卡提现、广告代理、代开发票、刻章办、出售答案、小额贷款…
使用技术:DFA算法

介绍:Deterministic Finite Automaton,即确定有穷自动机。 存储:一次性的把所有的敏感词存储到了多个map中,就是下图表示这种结构 敏感词:冰毒、大麻、大坏蛋

工具类和测试
代码语言:javascript
代码运行次数:0
运行
复制
package com.heima.utils.common;


import java.util.*;

public class SensitiveWordUtil {

    public static Map<String, Object> dictionaryMap = new HashMap<>();


    /**
     * 生成关键词字典库
     * @param words
     * @return
     */
    public static void initMap(Collection<String> words) {
        if (words == null) {
            System.out.println("敏感词列表不能为空");
            return ;
        }

        // map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字)
        Map<String, Object> map = new HashMap<>(words.size());
        // 遍历过程中当前层次的数据
        Map<String, Object> curMap = null;
        Iterator<String> iterator = words.iterator();

        while (iterator.hasNext()) {
            String word = iterator.next();
            curMap = map;
            int len = word.length();
            for (int i =0; i < len; i++) {
                // 遍历每个词的字
                String key = String.valueOf(word.charAt(i));
                // 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据
                Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key);
                if (wordMap == null) {
                    // 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志)
                    wordMap = new HashMap<>(2);
                    wordMap.put("isEnd", "0");
                    curMap.put(key, wordMap);
                }
                curMap = wordMap;
                // 如果当前字是词的最后一个字,则将isEnd标志置1
                if (i == len -1) {
                    curMap.put("isEnd", "1");
                }
            }
        }

        dictionaryMap = map;
    }

    /**
     * 搜索文本中某个文字是否匹配关键词
     * @param text
     * @param beginIndex
     * @return
     */
    private static int checkWord(String text, int beginIndex) {
        if (dictionaryMap == null) {
            throw new RuntimeException("字典不能为空");
        }
        boolean isEnd = false;
        int wordLength = 0;
        Map<String, Object> curMap = dictionaryMap;
        int len = text.length();
        // 从文本的第beginIndex开始匹配
        for (int i = beginIndex; i < len; i++) {
            String key = String.valueOf(text.charAt(i));
            // 获取当前key的下一个节点
            curMap = (Map<String, Object>) curMap.get(key);
            if (curMap == null) {
                break;
            } else {
                wordLength ++;
                if ("1".equals(curMap.get("isEnd"))) {
                    isEnd = true;
                }
            }
        }
        if (!isEnd) {
            wordLength = 0;
        }
        return wordLength;
    }

    /**
     * 获取匹配的关键词和命中次数
     * @param text
     * @return
     */
    public static Map<String, Integer> matchWords(String text) {
        Map<String, Integer> wordMap = new HashMap<>();
        int len = text.length();
        for (int i = 0; i < len; i++) {
            int wordLength = checkWord(text, i);
            if (wordLength > 0) {
                String word = text.substring(i, i + wordLength);
                // 添加关键词匹配次数
                if (wordMap.containsKey(word)) {
                    wordMap.put(word, wordMap.get(word) + 1);
                } else {
                    wordMap.put(word, 1);
                }

                i += wordLength - 1;
            }
        }
        return wordMap;
    }

   
}
代码语言:javascript
代码运行次数:0
运行
复制
// 测试
public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("法轮");
        list.add("法轮功");
        list.add("冰毒");
        initMap(list);
        String content="我是一个好人,并不会卖冰毒,也不操练法轮功,我真的不卖冰毒";
        Map<String, Integer> map = matchWords(content);
        System.out.println(map);
    }�
集成到文章审核中
  1. 倒入数据库表
  1. 编写mapper
代码语言:javascript
代码运行次数:0
运行
复制
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. 在使用腾讯云文本内容安全服务前调用自管理的敏感词库
代码语言:javascript
代码运行次数:0
运行
复制
   @Autowired
    private WmSensitiveMapper wmSensitiveMapper;

    private Boolean handleSensitiveScan(Map<String, Object> map, WmNews wmNews) {

        Boolean flag = false;
        String content =map.get("text").toString();

        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
        List<String> collect = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());
        SensitiveWordUtil.initMap(collect);
        Map<String, Integer> stringIntegerMap = SensitiveWordUtil.matchWords(content);
        if (stringIntegerMap.size() > 0) {flag=true;
            updataWmnews(wmNews,WmNews.Status.FAIL.getCode(),"文章出现违规信息,审核失败:"+stringIntegerMap.toString());

        }
  1. 结果:

图片识别文字审核敏感词

需求:

  • 文章中包含的图片要识别文字,过滤掉图片文字的敏感词
  1. 导入依赖
代码语言:javascript
代码运行次数:0
运行
复制
<dependency>
    <groupId>net.sourceforge.tess4j</groupId>

    <artifactId>tess4j</artifactId>

    <version>4.1.1</version>

</dependency>
  1. 测试文件
代码语言:javascript
代码运行次数:0
运行
复制
package cn.varin.test4j.test;


import cn.varin.test.Test4jApplication;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.*;

@SpringBootTest(classes = Test4jApplication.class)
@RunWith(SpringRunner.class)

public class Test4jTest {

    @Test
    public void test(){
        // 1.获取图片
        File file = new File("src/main/resources/images/ImageScanText.jpg");
        // 2.创建test4j对象
        Tesseract tesseract = new Tesseract();
        //设置字库路径
        tesseract.setDatapath("src/main/resources/tessdata");
        // 设置为识别中文
        tesseract.setLanguage("chi_sim");
        //执行ocr
        String s = null;
        try {
            s = tesseract.doOCR(file);
            System.out.println(s);
        } catch (TesseractException e) {
            throw new RuntimeException(e);
        }
    }
}

异步调用freemarker生成html并存储Minio

  1. 涉及模块 heima-leadnews-article
  2. 流程
  1. service
代码语言:javascript
代码运行次数:0
运行
复制
package com.heima.article.service;


import com.heima.model.article.pojos.ApArticle;

public interface ArticleFreemarkerService {
    void buildArticletoMinIO(ApArticle article, String content);
}
  1. serivceImpl
代码语言:javascript
代码运行次数:0
运行
复制
package com.heima.article.service.impl;


import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
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 lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
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.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@Service
@Transactional
@Slf4j
public class ArticleFreemarkerServiceImpl implements com.heima.article.service.ArticleFreemarkerService {

    @Autowired
    Configuration configurable;
    @Autowired
    FileStorageService fileStorageService;

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Override
    @Async
    public void buildArticletoMinIO(ApArticle article, String content) {
        // 第一步:获取到文章内容
        //判断是否有内容
        if (  StringUtils.isNotBlank(content)) {
            // 生成html文件

            Template template = null;
            StringWriter stringWriter = new StringWriter();
            try {
                template = configurable.getTemplate("article.ftl");
                Map<String, Object> map = new HashMap<>();
                map.put("content", JSONArray.parseArray(content));

                template.process(map,stringWriter);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

            // 上传
            InputStream byteArrayInputStream = new ByteArrayInputStream(stringWriter.toString().getBytes());
            String static_url = fileStorageService.uploadHtmlFile("", article.getId()+ ".html", byteArrayInputStream);
            System.out.println(static_url);
            // 修改 article表
            ApArticle apArticle = new ApArticle();
            apArticle.setId(article.getId());
            apArticle.setStaticUrl(static_url);
            apArticleMapper.updateById(apArticle);


        }

    }
}
  1. 开启异步
代码语言:javascript
代码运行次数:0
运行
复制
package com.heima.article;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;


@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.heima.article.mapper")
@EnableAsync
public class ArticleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ArticleApplication.class,args);
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
  1. 效果:

今天也是加油的一天呀。⛽️

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • feign远程接口调用
  • 审核成功后将自媒体文章添加到app端中
  • 综合测试
  • feign远程调用服务降级处理
    • 具体实现
    • 模拟
  • 异步任务审核文章
    • 同步和异步概念
    • 需求
    • 实现
  • 自管理敏感词集成
    • 使用技术:DFA算法
    • 工具类和测试
    • 集成到文章审核中
  • 图片识别文字审核敏感词
  • 异步调用freemarker生成html并存储Minio
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档