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

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

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


DFA算法简介

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

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

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

计算机操作系统中的进程状态与切换可以作为 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);
    }

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

上一篇:雪花算法【snowflake】


下一篇:SpringCloud Alibaba