spring boot 使用DFA算法实现敏感词过滤

spring boot 使用DFA算法实现敏感词过滤

在这里插入图片描述

敏感词、文字过滤是一个网站必不可少的功能,如何设计一个好的、高效的过滤算法是非常有必要的。


DFA算法简介

DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

  • 确定:状态以及引起状态转换的事件都是可确定的,不存在“意外”。
  • 有穷:状态以及事件的数量都是可穷举的。

[图片上传失败...(image-c9e941-1599144517045)]

计算机操作系统中的进程状态与切换可以作为 DFA 算法的一种近似理解。如下图所示,其中椭圆表示状态,状态之间的连线表示事件,进程的状态以及事件都是可确定的,且都可以穷举。

数据结构

state_event_dict = {
    "匹": {
        "配": {
            "算": {
                "法": {
                    "is_end": True
                },
                "is_end": False
            },
            "关": {
                "键": {
                    "词": {
                        "is_end": True
                    },
                    "is_end": False
                },
                "is_end": False
            },
            "is_end": False
        },
        "is_end": False
    },
    "信": {
        "息": {
            "抽": {
                "取": {
                    "is_end": True
                },
                "is_end": False
            },
            "is_end": False
        },
        "is_end": False
    }
}

Java实现DFA算法实现敏感词过滤
  1. 定义敏感词词库 让SpringBoot加载到
  • 这里用到了 @Bean 以及 @Autowired 注入到算法map里面
@Configuration
public class CheckConfig {

    @Autowired
    private DFAUtil dfaUtil;

    @Bean
    public void init(){
        Set<String> set=new HashSet<>();
        set.add("大中华");
        set.add("大中华帝国");
        set.add("大汉民族");
        set.add("日本人");
        set.add("日本鬼子");
        //将集合放到算法里,这里可以优化,写词库文件等等,
        dfaUtil.createDFAHashMap(set);
    }
}
  1. 创建AOP切面 前置通知
package com.yxl.aspect;


@Component
@Aspect
@Slf4j
public class CheckInputParameterAspect {

    private static final Log Logger = LogFactory.getLog(com.yxzapp.aspect.CheckInputParameterAspect.class);


    @Autowired
    private DFAUtil dfaUtil ;

    /**
     * 定义切入点:拦截controller层所有方法
     */
    @Pointcut("execution(public * com.yxl.modules.*..*Controller.*(..))")
    public void params() {
    }

    /**
     * 前置通知
     *
     * @param
     * @throws Throwable
     */
    @Before("params()")
    public Object before(JoinPoint point) throws Throwable {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        //获取请求参数
        Object[] args = point.getArgs();
        // 数组转 String
        Set<String> sensitiveWordByDFAMap = dfaUtil.getSensitiveWordByDFAMap(StringUtils.join(args,","), 1);
        Logger.info("敏感词有"+sensitiveWordByDFAMap.size()+"个");
        if(sensitiveWordByDFAMap.size()>=1){
            //自定义的异常 
            throw new BizException("500","您输入的内容有敏感词");
        }
        Logger.info("当前调用接口-[" + request.getRequestURL() + "]");
        return args;
    }

}



  1. DFA算法 工具类
package com.yxzapp.utils;


import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

@Component
public class DFAUtil {

    HashMap<String, Object> dfaMap;

    public static final int minMatchType=1;

    public static final int maxMatchType=2;

    /*{日=
     *  {本=
     *      {人={isEnd=1},
     *      鬼=
     *          {子={isEnd=1},
     *          isEnd=0},
     *      isEnd=0},
     *  isEnd=0},
     *
     * 大=
     *  {汉=
     *      {民={isEnd=0,
     *          族={isEnd=1}},
     *      isEnd=0},
     *  isEnd=0,
     *  中={isEnd=0,
     *      华={isEnd=1,
     *          帝={isEnd=0,
     *              国={isEnd=1}}}}}}*/
    /**set作为敏感词,创建出对应的dfa的Map,以供检验敏感词
     * @param set
     */
    public void createDFAHashMap(Set<String> set){
        HashMap<String, Object> nowMap;
        //根据set的大小,创建map的大小
        dfaMap=new HashMap<>(set.size());
        //对set里的字符串进行循环
        for(String key:set){
            //对每个字符串最初,nowMap就是dfaMap
            nowMap=dfaMap;
            for(int i=0;i<key.length();i++){
                //一个个字符循环
                String nowChar=String.valueOf(key.charAt(i));
                //根据nowChar得到nowMap里面对应的value
                HashMap<String, Object> map=(HashMap<String, Object>)nowMap.get(nowChar);
                //如果map为空,则说明nowMap里面没有以nowChar开头的东西,则创建一个新的hashmap,
                //以nowChar为key,新的map为value,放入nowMap
                if(map==null){
                    map=new HashMap<String,Object>();
                    nowMap.put(nowChar, map);
                }
                //nowMap=map,就是nowChar对应的对象
                nowMap=map;
                //最后在nowMap里设置isEnd
                //如果nowMap里面已经有isEnd,并且为1,说明以前已经有关键字了,就不再设置isEnd
                //因为如果没有这一步,大中华和大中华帝国,先设置大中华
                //在大中华帝国设置的时候,华对应的map有isEnd=1,如果这时对它覆盖,就会isEnd=0,导致大中华这个关键字失效
                if(nowMap.containsKey("isEnd")&&nowMap.get("isEnd").equals("1")){
                    continue;
                }
                if(i!=key.length()-1){
                    nowMap.put("isEnd", "0");
                }
                else{
                    nowMap.put("isEnd", "1");
                }
            }
        }
        System.out.println(dfaMap);
    }


    /** 用创建的dfaMap,根据matchType检验字符串string是否包含敏感词,返回包含所有对于敏感词的set
     * @param string 要检查是否有敏感词在内的字符串
     * @param matchType 检查类型,如大中华帝国牛逼对应大中华和大中华帝国两个关键字,1为最小检查,会检查出大中华,2位最大,会检查出大中华帝国
     * @return
     */
    public Set<String> getSensitiveWordByDFAMap(String string,int matchType){
        Set<String> set=new HashSet<>();
        for(int i=0;i<string.length();i++){
            //matchType是针对同一个begin的后面,在同一个begin匹配最长的还是最短的敏感词
            int length=getSensitiveLengthByDFAMap(string,i,matchType);
            if(length>0){
                set.add(string.substring(i,i+length));
                //这个对应的是一个敏感词内部的关键字(不包括首部),如果加上,大中华帝国,对应大中华和中华两个敏感词,只会对应大中华而不是两个
                //i=i+length-1;//减1的原因,是因为for会自增
            }
        }
        return set;
    }

    /**如果存在,则返回敏感词字符的长度,不存在返回0
     * @param string
     * @param beginIndex
     * @param matchType  1:最小匹配规则,2:最大匹配规则
     * @return
     */
    public int getSensitiveLengthByDFAMap(String string,int beginIndex,int matchType){
        //当前匹配的长度
        int nowLength=0;
        //最终匹配敏感词的长度,因为匹配规则2,如果大中华帝,对应大中华,大中华帝国,在华的时候,nowLength=3,因为是最后一个字,将nowLenth赋给resultLength
        //然后在帝的时候,now=4,result=3,然后不匹配,resultLength就是上一次最大匹配的敏感词的长度
        int resultLength=0;
        HashMap<String, Object> nowMap=dfaMap;
        for(int i=beginIndex;i<string.length();i++){
            String nowChar=String.valueOf(string.charAt(i));
            //根据nowChar得到对应的map,并赋值给nowMap
            nowMap=(HashMap<String, Object>)nowMap.get(nowChar);
            //nowMap里面没有这个char,说明不匹配,直接返回
            if(nowMap==null){
                break;
            }
            else{
                nowLength++;
                //如果现在是最后一个,更新resultLength
                if("1".equals(nowMap.get("isEnd"))){
                    resultLength=nowLength;
                    //如果匹配模式是最小,直接匹配到,退出
                    //匹配模式是最大,则继续匹配,resultLength保留上一次匹配到的length
                    if(matchType==minMatchType){
                        break;
                    }
                }
            }
        }
        return resultLength;
    }
}
  1. 测试
   @ApiOperation(value = "测试", notes = "测试")
    @PostMapping("gettest")
    public ResultBody gettest(@RequestParam String aa,
                              @RequestParam String bb){

        return ResultBody.success(aa+bb);
    }
在这里插入图片描述
  • ==有更好方式 或者 优化的小伙伴可以评论==
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354