黑马头条(4)

自媒体文章审核

文章数据流

image-20241228163038220

image-20241228163058158

审核方式

  • 自动审核

文章发布之后,系统自动审核,主要是通过第三方接口对文章的内容进行审核(成功、失败、不确定)

  • 人工审核

待自动审核返回不确定信息时,转到人工审核,由平台管理员进行审核

审核流程图:

image-20241228163946068

内容安全第三方接口

内容安全接口类型

通过aliyun服务检测内容安全

分布式id

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

唯一主键生成方案比较

方案 优势 劣势
Redis (INCR)生成一个全局连续递增的数字类型主键 增加了一个外部组件的依赖,Redis不可用,则整个数据库将无法插入
UUID 全局唯一,MySQL也有UUID实现 36个字符组成,占用空间大
Snowflake算法 全局唯一,数字类型,存储成本低 机器规模大于1024台无法支持

雪花算法

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

image-20241228183725551

feign远程调用服务降级处理

  • 服务降级是服务自我保护的一种方式,或者保护下流服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃。
  • 服务降级虽然会导致请求失败,但是不会导致阻塞

自管理敏感词过滤

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

方案 说明
数据库模糊查询 效率太低
String.indexOf("") 数据库量大的话也是比较慢
全文检索 分词再匹配
DFA算法 确定有穷自动机(一种数据结构)

DFA实现原理

DFA : Deterministic Finite Automation,确定有穷自动机

存储:一次性的把所有的敏感词存储到了多个map中,就是下图的结构

1、创建敏感词表

image-20241229182051731

2、敏感词识别工具类

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

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

3、添加对应的敏感词过滤逻辑


    /**
     * 自管理敏感词审核
     * @param content
     * @param wmNews
     * @return
     */
    private boolean handelSensitiveScan(String content, WmNews wmNews) {
        boolean flag = true;

        // 获取所有的敏感词
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(Wrappers.<WmSensitive>lambdaQuery().select(WmSensitive::getSensitives));
        List<String> collectList = wmSensitives.stream().map(WmSensitive::getSensitives).collect(Collectors.toList());

        // 初始化敏感词库
        SensitiveWordUtil.initMap(collectList);

        // 查看文章中是否包含敏感词
        Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
        if (!map.isEmpty()){
            updateWmNews(wmNews, WmNews.Status.FAIL, "当前文章中存在违规内容" + map);
            flag = false;
        }

        return flag;
    }

图片文字-敏感词过滤

图片文字识别

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

Tesseract - OCR
导入依赖:
        <dependency>
            <groupId>net.sourceforge.tess4j</groupId>
            <artifactId>tess4j</artifactId>
            <version>4.1.1</version>
        </dependency>
下载对应的模型

image-20241229192949313

识别
    public static void main(String[] args) throws TesseractException {
        ITesseract tesseract = new Tesseract();

        tesseract.setDatapath("D:\\workspace\\tessdata");
        tesseract.setLanguage("chi_sim");

        File file = new File("D:\\workspace\\image1.png");
        String result = tesseract.doOCR(file);
        System.out.println("识别的结果为:\n" + result.replaceAll("[\\r\\n]", "-"));
    }

文章详情-静态文件的生成

文章端创建app相关文章时,生成文章详情静态页上传到MinIO中

image-20241229205216347