基于DFA实现的敏感词过滤算法及在JFinal中的应用

来源:互联网 发布:小众软件下载免费 编辑:程序博客网 时间:2024/06/10 15:42

上传论坛、商品信息等时候,需要对敏感词进行过滤,以符合法律法规。下面介绍一种基于DFA(Deterministic Finite Automaton)算法的敏感词过滤方法及应用。

首先定义敏感词过滤的接口:SensitiveWordFilter

package cn.esstx.cq.server.ext.sensitiveword.base;import java.util.Set;/** * 过滤器接口 */public interface SensitiveWordFilter{    // 初始化,装入敏感词    void init(Set<String> sensitiveWords);    // 判断是否包含敏感字符串    boolean isContains(String src);    // 输出包含的敏感词    Set<String> doGetSensitiveWords(String src);    // 传入源字符串,输出过滤后的字符串    String doFilter(String src);}

一个抽象类实现是否初始化的检查,所有的过滤器都继承于该类,当然也可以不。初始化后设置isInit值为true,在实现其他接口方法的时候,首先检查,即调用checkInit。

package cn.esstx.cq.server.ext.sensitiveword.base;/** * 统一进行是否初始化的检查 */public abstract class AbstractSensitiveWordFilter implements SensitiveWordFilter{    protected boolean isInit = false;// 调用初始化方法之后,设置该值为true。    public void checkInit(){        if(!isInit){            throw new RuntimeException("SensitiveWordFilter没被初始化,使用之前请调用init(Set<String> sensitiveWords)");        }    }}


再定义获取敏感词集合的

package cn.esstx.cq.server.ext.sensitiveword.wordset;import java.util.Set;/** * 获取所有敏感词,或者停顿词的接口 */public interface FilterWordSet{    Set<String> getWordSet();}

两种实现:文件和数据库、数据库方式。其中数据库方式基于JFinal的Db+Record方式

package cn.esstx.cq.server.ext.sensitiveword.wordset;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashSet;import java.util.Set;/** * 敏感词来自于文件,一行一个敏感词 */public class FileFilterWordSet implements FilterWordSet{    private String filePath;    private String encoding = "UTF-8";    public FileFilterWordSet(String filePath){        this.filePath = filePath;    }    public FileFilterWordSet(String filePath, String encoding){        this.filePath = filePath;        this.encoding = encoding;    }    @Override    public Set<String> getWordSet(){        Set<String> set = null;        File file = new File(filePath); // 读取文件        InputStreamReader read = null;        try{            read = new InputStreamReader(new FileInputStream(file), encoding);            if(file.isFile() && file.exists()){ // 文件流是否存在                set = new HashSet<String>();                @SuppressWarnings("resource")                BufferedReader bufferedReader = new BufferedReader(read);                String txt = null;                while((txt = bufferedReader.readLine()) != null){ // 读取文件,将文件内容放入到set中                    if(!"".equals(txt.trim())){                        set.add(txt);                    }                }            } else{ // 不存在抛出异常信息                throw new Exception("敏感词库文件不存在");            }        }        catch(Exception ex){            ex.printStackTrace();        }        finally{            try{                read.close();            }            catch(IOException e){                e.printStackTrace();            } // 关闭文件流        }        return set;    }}
package cn.esstx.cq.server.ext.sensitiveword.wordset;import java.util.HashSet;import java.util.List;import java.util.Set;import com.jfinal.plugin.activerecord.Db;import com.jfinal.plugin.activerecord.Record;public class DbFilterWordSet implements FilterWordSet{    private String tableName;    private String columnName = "word";    public DbFilterWordSet(String tableName){        this.tableName = tableName;    }    public DbFilterWordSet(String tableName, String columnName){        this.tableName = tableName;        this.columnName = columnName;    }    @Override    public Set<String> getWordSet(){        Set<String> set = new HashSet<String>();        List<Record> list = Db.find("select " + columnName + " from " + tableName);        String word = null;        for(Record record : list){            word = record.getStr(columnName);            if(null != word && !"".equals(word)){                set.add(word);            }        }        return set;    }}

第一种实现:SimpleWordFilter

package cn.esstx.cq.server.ext.sensitiveword.simpleFilter;import java.util.HashSet;import java.util.Iterator;import java.util.Map;import java.util.Set;import cn.esstx.cq.server.ext.sensitiveword.base.AbstractSensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.base.SensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.wordset.FileFilterWordSet;import cn.esstx.cq.server.ext.sensitiveword.wordset.FilterWordSet;/** * @Description: 敏感词过滤 * @Author : 熊诗言 * @Date : 2017-04-29 02:02:02 * @version 1.0 */public class SimpleWordFilter extends AbstractSensitiveWordFilter implements SensitiveWordFilter{    @SuppressWarnings("rawtypes")    private Map             sensitiveWordMap = null;    public static final int minMatchTYpe     = 1;           // 最小匹配规则    public static final int maxMatchType     = 2;           // 最大匹配规则    private int             matchType        = minMatchTYpe;    private String          replaceChar      = "*";    /**     * 构造函数,初始化敏感词库,字符集用默认的GBK     */    public SimpleWordFilter(){}    public SimpleWordFilter(String replaceChar){        this.replaceChar = replaceChar;    }    public SimpleWordFilter(int matchType, String replaceChar){        this.matchType = matchType;        this.replaceChar = replaceChar;    }    @Override    public void init(Set<String> sensitiveWords){        sensitiveWordMap = new SensitiveWordInit().initKeyWord(sensitiveWords);        isInit = true;    }    @Override    public String doFilter(String src){        checkInit();        if(null == src || "".equals(src)){            return src;        }        String resultTxt = src;        Set<String> set = doGetSensitiveWords(src); // 获取所有的敏感词        Iterator<String> iterator = set.iterator();        String word = null;        String replaceString = null;        while(iterator.hasNext()){            word = iterator.next();            replaceString = getReplaceChars(replaceChar, word.length());            resultTxt = resultTxt.replaceAll(word, replaceString);        }        return resultTxt;    }    @Override    public boolean isContains(String src){        checkInit();        if(null == src || "".equals(src)){            return false;        }        boolean flag = false;        for(int i = 0; i < src.length(); i++){            int matchFlag = this.checkSensitiveWord(src, i, matchType); // 判断是否包含敏感字符            if(matchFlag > 0){ // 大于0存在,返回true                flag = true;            }        }        return flag;    }    @Override    public Set<String> doGetSensitiveWords(String src){        Set<String> sensitiveWordSet = new HashSet<String>();        checkInit();        if(null == src || "".equals(src)){            return sensitiveWordSet;        }        for(int i = 0; i < src.length(); i++){            int length = checkSensitiveWord(src, i, matchType); // 判断是否包含敏感字符            if(length > 0){ // 存在,加入list中                sensitiveWordSet.add(src.substring(i, i + length));                i = i + length - 1; // 减1的原因,是因为for会自增            }        }        return sensitiveWordSet;    }    /**     * 获取替换字符串     *      * @param replaceChar     * @param length     * @return     * @version 1.0     */    private String getReplaceChars(String replaceChar, int length){        String resultReplace = replaceChar;        for(int i = 1; i < length; i++){            resultReplace += replaceChar;        }        return resultReplace;    }    /**     * 检查文字中是否包含敏感字符,检查规则如下:<br>     *      * @Author : 熊诗言     * @Date : 2017-04-29 02:02:02     * @param txt     * @param beginIndex     * @param matchType     * @return,如果存在,则返回敏感词字符的长度,不存在返回0     * @version 1.0     */    @SuppressWarnings({ "rawtypes" })    private int checkSensitiveWord(String txt, int beginIndex, int matchType){        boolean flag = false; // 敏感词结束标识位:用于敏感词只有1位的情况        int matchFlag = 0; // 匹配标识数默认为0        char word = 0;        Map nowMap = sensitiveWordMap;        for(int i = beginIndex; i < txt.length(); i++){            word = txt.charAt(i);            nowMap = (Map)nowMap.get(word); // 获取指定key            if(nowMap != null){ // 存在,则判断是否为最后一个                matchFlag++; // 找到相应key,匹配标识+1                if(SensitiveWordInit.isEnd_1.equals(nowMap.get(SensitiveWordInit.isEnd))){ // 如果为最后一个匹配规则,结束循环,返回匹配标识数                    flag = true; // 结束标志位为true                    if(SimpleWordFilter.minMatchTYpe == matchType){ // 最小规则,直接返回,最大规则还需继续查找                        break;                    }                }            } else{ // 不存在,直接返回                break;            }        }        if(matchFlag < 2 || !flag){ // 长度必须大于等于1,为词            matchFlag = 0;        }        return matchFlag;    }    public static void main(String[] args){        String string = "你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。";        System.out.println("=========================================================");        System.out.println("原字符串 : " + string);        System.out.println("解析字数 : " + string.length());        String re;        long nano = System.nanoTime();        FilterWordSet filterSensitiveWordSet = new FileFilterWordSet("C:\\Users\\xiongshiyan\\Desktop\\wd.txt");        SensitiveWordFilter filter = new SimpleWordFilter("#");        filter.init(filterSensitiveWordSet.getWordSet());        nano = (System.nanoTime() - nano);        System.out.println("加载时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        re = filter.doFilter(string);        nano = (System.nanoTime() - nano);        System.out.println("输出替换结果 : " + re);        System.out.println("解析时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        System.out.println("是否包含敏感词: " + filter.isContains(string));        nano = (System.nanoTime() - nano);        System.out.println("消耗时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        Set<String> set = filter.doGetSensitiveWords(string);        System.out.println("语句中包含敏感词的个数为 :" + set.size() + "。包含:" + set);        nano = (System.nanoTime() - nano);        System.out.println("消耗时间 : " + nano / 1000000 + " ms");    }}

负责初始化的工具类

package cn.esstx.cq.server.ext.sensitiveword.simpleFilter;
import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;/** * @Description: 初始化敏感词库,将敏感词加入到HashMap中,构建DFA算法模型 * @Author : 熊诗言 * @Date : 2017-04-29 02:02:02 * @version 1.0 */public class SensitiveWordInit{    public static final String isEnd   = "isEnd";    public static final String isEnd_1 = "1";    public static final String isEnd_0 = "0";    @SuppressWarnings("rawtypes")    public HashMap             sensitiveWordMap;    public SensitiveWordInit(){}    /**     * @Author : 熊诗言     * @Date : 2017-04-29 02:02:02     * @version 1.0     */    @SuppressWarnings("rawtypes")    public Map initKeyWord(Set<String> keyWordSet){        // 将敏感词库加入到HashMap中        addSensitiveWordToHashMap(keyWordSet);        // spring获取application,然后application.setAttribute("sensitiveWordMap",sensitiveWordMap);        return sensitiveWordMap;    }    /**     * 读取敏感词库,将敏感词放入HashSet中,构建一个DFA算法模型:<br>     * 中 = { isEnd = 0 国 = {<br>     * isEnd = 1 人 = {isEnd = 0 民 = {isEnd = 1} } 男 = { isEnd = 0 人 = { isEnd =     * 1 } } } } 五 = { isEnd = 0 星 = { isEnd = 0 红 = { isEnd = 0 旗 = { isEnd = 1     * } } } }     *      * @Author : 熊诗言     * @Date : 2017-04-29 02:02:02     * @param keyWordSet     *            敏感词库     * @version 1.0     */    @SuppressWarnings({ "rawtypes", "unchecked" })    private void addSensitiveWordToHashMap(Set<String> keyWordSet){        sensitiveWordMap = new HashMap(keyWordSet.size()); // 初始化敏感词容器,减少扩容操作        String key = null;        Map nowMap = null;        Map<String, String> newWorMap = null;        // 迭代keyWordSet        Iterator<String> iterator = keyWordSet.iterator();        while(iterator.hasNext()){            key = iterator.next(); // 关键字            nowMap = sensitiveWordMap;            for(int i = 0; i < key.length(); i++){                char keyChar = key.charAt(i); // 转换成char型                Object wordMap = nowMap.get(keyChar); // 获取                if(wordMap != null){ // 如果存在该key,直接赋值                    nowMap = (Map)wordMap;                } else{ // 不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个                    newWorMap = new HashMap<String, String>();                    newWorMap.put(isEnd, isEnd_0); // 不是最后一个                    nowMap.put(keyChar, newWorMap);                    nowMap = newWorMap;                }                if(i == key.length() - 1){                    nowMap.put(isEnd, isEnd_1); // 最后一个                }            }        }    }}

第二种实现:WithStopFilter,可以过滤停顿词


package cn.esstx.cq.server.ext.sensitiveword.withStopFilter;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;import cn.esstx.cq.server.ext.sensitiveword.base.AbstractSensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.base.SensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.wordset.FileFilterWordSet;import cn.esstx.cq.server.ext.sensitiveword.wordset.FilterWordSet;/** * 创建时间:2016年8月30日 下午3:01:12 *  * 思路: 创建一个FilterSet,枚举了0~65535的所有char是否是某个敏感词开头的状态 *  * 判断是否是 敏感词开头 | | 是 不是 获取头节点 OK--下一个字 然后逐级遍历,DFA算法 *  *  * 使用时,必须初始化,即调用init接口,传入需要的参数 *  * @author andy * @version 2.2 */public class WithStopFilter extends AbstractSensitiveWordFilter implements SensitiveWordFilter{    private final FilterSet              set         = new FilterSet();                         // 存储首字    private final Map<Integer, WordNode> nodes       = new HashMap<Integer, WordNode>(1024, 1); // 存储节点    private final Set<Integer>           stopwdSet   = new HashSet<>();                         // 停顿词    private char                         replaceChar = '*';                                     // 敏感词过滤替换    private static final Set<String>     stopWwods   = new HashSet<String>();    static{        stopWwods.add("!");        stopWwods.add(".");        stopWwods.add(",");        stopWwods.add("#");        stopWwods.add("$");        stopWwods.add("%");        stopWwods.add("&");        stopWwods.add("*");        stopWwods.add("(");        stopWwods.add(")");        stopWwods.add("|");        stopWwods.add("?");        stopWwods.add("/");        stopWwods.add("@");        stopWwods.add("\"");        stopWwods.add("'");        stopWwods.add(";");        stopWwods.add("[");        stopWwods.add("]");        stopWwods.add("{");        stopWwods.add("}");        stopWwods.add("+");        stopWwods.add("~");        stopWwods.add("-");        stopWwods.add("_");        stopWwods.add("=");        stopWwods.add("^");        stopWwods.add("<");        stopWwods.add(">");        stopWwods.add("");        stopWwods.add("!");        stopWwods.add("。");        stopWwods.add(",");        stopWwods.add("¥");        stopWwods.add("(");        stopWwods.add(")");        stopWwods.add("?");        stopWwods.add("、");        stopWwods.add("“");        stopWwods.add("‘");        stopWwods.add(";");        stopWwods.add("【");        stopWwods.add("】");        stopWwods.add("——");        stopWwods.add("……");        stopWwods.add("《");        stopWwods.add("》");    }    public WithStopFilter(){}    public WithStopFilter(char replaceChar){        this.replaceChar = replaceChar;    }    /**     *      * @Title: init     * @Description:默认停顿词     * @param filterSensitiveWords     */    @Override    public void init(Set<String> sensitiveWords){        addSensitiveWord(sensitiveWords);        // 为了方便,直接加载在内存中        // addStopWord(new FileFilterWordSet("stopwd.txt").getWordSet());        addStopWord(stopWwods);        isInit = true;    }    /**     * 过滤判断 将敏感词转化为成屏蔽词     *      * @param src     * @return     */    @Override    public final String doFilter(final String src){        checkInit();        if(null == src || "".equals(src)){            return src;        }        char[] chs = src.toCharArray();        int length = chs.length;        int currc;        int k;        WordNode node;        for(int i = 0; i < length; i++){            currc = charConvert(chs[i]);            if(!set.contains(currc)){                continue;            }            node = nodes.get(currc);// 日 2            if(node == null)// 其实不会发生,习惯性写上了                continue;            boolean couldMark = false;            int markNum = -1;            if(node.isLast()){// 单字匹配(日)                couldMark = true;                markNum = 0;            }            // 继续匹配(日你/日你妹),以长的优先            // 你-3 妹-4 夫-5            k = i;            for(; ++k < length;){                int temp = charConvert(chs[k]);                if(stopwdSet.contains(temp))                    continue;                node = node.querySub(temp);                if(node == null)// 没有了                    break;                if(node.isLast()){                    couldMark = true;                    markNum = k - i;// 3-2                }            }            if(couldMark){                for(k = 0; k <= markNum; k++){                    chs[k + i] = replaceChar;                }                i = i + markNum;            }        }        return new String(chs);    }    @Override    public Set<String> doGetSensitiveWords(String src){        Set<String> words = new HashSet<String>();// 装含有的敏感词        checkInit();        if(null == src || "".equals(src)){            return words;        }        char[] chs = src.toCharArray();        int length = chs.length;        int currc;        int k;        WordNode node;        for(int i = 0; i < length; i++){            currc = charConvert(chs[i]);            if(!set.contains(currc)){                continue;            }            node = nodes.get(currc);// 日 2            if(node == null)// 其实不会发生,习惯性写上了                continue;            boolean couldMark = false;            int markNum = -1;            if(node.isLast()){// 单字匹配(日)                couldMark = true;                markNum = 0;            }            // 继续匹配(日你/日你妹),以长的优先            // 你-3 妹-4 夫-5            k = i;            for(; ++k < length;){                int temp = charConvert(chs[k]);                if(stopwdSet.contains(temp))                    continue;                node = node.querySub(temp);                if(node == null)// 没有了                    break;                if(node.isLast()){                    couldMark = true;                    markNum = k - i;// 3-2                }            }            if(couldMark){                String sensitive = new String(chs, i, markNum + 1);                words.add(sensitive);                i = i + markNum;// 跳过本敏感词的字数            }        }        return words;    }    /**     * 是否包含敏感词     *      * @param src     * @return     */    @Override    public final boolean isContains(final String src){        checkInit();        if(null == src || "".equals(src)){            return false;        }        char[] chs = src.toCharArray();        int length = chs.length;        int currc;        int k;        WordNode node;        for(int i = 0; i < length; i++){            currc = charConvert(chs[i]);            if(!set.contains(currc)){                continue;            }            node = nodes.get(currc);// 日 2            if(node == null)// 其实不会发生,习惯性写上了                continue;            boolean couldMark = false;            if(node.isLast()){// 单字匹配(日)                couldMark = true;            }            // 继续匹配(日你/日你妹),以长的优先            // 你-3 妹-4 夫-5            k = i;            for(; ++k < length;){                int temp = charConvert(chs[k]);                if(stopwdSet.contains(temp))                    continue;                node = node.querySub(temp);                if(node == null)// 没有了                    break;                if(node.isLast()){                    couldMark = true;                }            }            if(couldMark){                return true;            }        }        return false;    }    /**     * 增加停顿词     *      * @param words     */    private void addStopWord(final Set<String> words){        if(words != null && words.size() > 0){            char[] chs;            for(String curr : words){                chs = curr.toCharArray();                for(char c : chs){                    stopwdSet.add(charConvert(c));                }            }        }    }    /**     * 添加DFA节点     *      * @param words     */    private void addSensitiveWord(final Set<String> words){        if(words != null && words.size() > 0){            char[] chs;            int fchar;            int lastIndex;            WordNode fnode; // 首字母节点            for(String curr : words){                chs = curr.toCharArray();                fchar = charConvert(chs[0]);                if(!set.contains(fchar)){// 没有首字定义                    set.add(fchar);// 首字标志位 可重复add,反正判断了,不重复了                    fnode = new WordNode(fchar, chs.length == 1);                    nodes.put(fchar, fnode);                } else{                    fnode = nodes.get(fchar);                    if(!fnode.isLast() && chs.length == 1)                        fnode.setLast(true);                }                lastIndex = chs.length - 1;                for(int i = 1; i < chs.length; i++){                    fnode = fnode.addIfNoExist(charConvert(chs[i]), i == lastIndex);                }            }        }    }    /**     * 大写转化为小写 全角转化为半角     *      * @param src     * @return     */    private static int charConvert(char src){        int r = BCConvert.qj2bj(src);        return (r >= 'A' && r <= 'Z') ? r + 32 : r;    }    public static void main(String[] args){        String string = "你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。";        System.out.println("=========================================================");        System.out.println("原字符串 : " + string);        System.out.println("解析字数 : " + string.length());        String re;        long nano = System.nanoTime();        FilterWordSet filterSensitiveWordSet = new FileFilterWordSet("C:\\Users\\xiongshiyan\\Desktop\\wd.txt");        SensitiveWordFilter filter = new WithStopFilter('#');        filter.init(filterSensitiveWordSet.getWordSet());        nano = (System.nanoTime() - nano);        System.out.println("加载时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        re = filter.doFilter(string);        nano = (System.nanoTime() - nano);        System.out.println("输出替换结果 : " + re);        System.out.println("解析时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        System.out.println("是否包含敏感词: " + filter.isContains(string));        nano = (System.nanoTime() - nano);        System.out.println("消耗时间 : " + nano / 1000000 + " ms");        System.out.println("=========================================================");        nano = System.nanoTime();        Set<String> set = filter.doGetSensitiveWords(string);        System.out.println("语句中包含敏感词的个数为 :" + set.size() + "。包含:" + set);        nano = (System.nanoTime() - nano);        System.out.println("消耗时间 : " + nano / 1000000 + " ms");    }}

为了性能更好些,将停顿词放在静态代码块中

!.,#$%&*()|?/@"';[]{}+~-_=^<> !。,¥()?、“‘;【】——……《》

工具类:WordNode、FilterSet、BCConvert

package cn.esstx.cq.server.ext.sensitiveword.withStopFilter;import java.util.LinkedList;import java.util.List;/** * 创建时间:2016年8月30日 下午3:07:45 *  * @author andy * @version 2.2 */public class WordNode{    private int            value;    // 节点名称    private List<WordNode> subNodes; // 子节点    private boolean        isLast;   // 默认false    public WordNode(int value){        this.value = value;    }    public WordNode(int value, boolean isLast){        this.value = value;        this.isLast = isLast;    }    /**     *      * @param subNode     * @return 就是传入的subNode     */    private WordNode addSubNode(final WordNode subNode){        if(subNodes == null)            subNodes = new LinkedList<WordNode>();        subNodes.add(subNode);        return subNode;    }    /**     * 有就直接返回该子节点, 没有就创建添加并返回该子节点     *      * @param value     * @return     */    public WordNode addIfNoExist(final int value, final boolean isLast){        if(subNodes == null){            return addSubNode(new WordNode(value, isLast));        }        for(WordNode subNode : subNodes){            if(subNode.value == value){                if(!subNode.isLast && isLast)                    subNode.isLast = true;                return subNode;            }        }        return addSubNode(new WordNode(value, isLast));    }    public WordNode querySub(final int value){        if(subNodes == null){            return null;        }        for(WordNode subNode : subNodes){            if(subNode.value == value)                return subNode;        }        return null;    }    public boolean isLast(){        return isLast;    }    public void setLast(boolean isLast){        this.isLast = isLast;    }    @Override    public int hashCode(){        return value;    }}


package cn.esstx.cq.server.ext.sensitiveword.withStopFilter;/** * 创建时间:2016年8月30日 下午2:57:10 *  * @author andy * @version 2.2 */public class FilterSet{    private final long[] elements;    public FilterSet(){        elements = new long[1 + (65535 >>> 6)];    }    public void add(final int no){        elements[no >>> 6] |= (1L << (no & 63));    }    public void add(final int... no){        for(int currNo : no)            elements[currNo >>> 6] |= (1L << (currNo & 63));    }    public void remove(final int no){        elements[no >>> 6] &= ~(1L << (no & 63));    }    /**     *      * @param no     * @return true:添加成功 false:原已包含     */    public boolean addAndNotify(final int no){        int eWordNum = no >>> 6;        long oldElements = elements[eWordNum];        elements[eWordNum] |= (1L << (no & 63));        boolean result = elements[eWordNum] != oldElements;        // if (result)        // size++;        return result;    }    /**     *      * @param no     * @return true:移除成功 false:原本就不包含     */    public boolean removeAndNotify(final int no){        int eWordNum = no >>> 6;        long oldElements = elements[eWordNum];        elements[eWordNum] &= ~(1L << (no & 63));        boolean result = elements[eWordNum] != oldElements;        return result;    }    public boolean contains(final int no){        return (elements[no >>> 6] & (1L << (no & 63))) != 0;    }    public boolean containsAll(final int... no){        if(no.length == 0)            return true;        for(int currNo : no)            if((elements[currNo >>> 6] & (1L << (currNo & 63))) == 0)                return false;        return true;    }    /**     * 不如直接循环调用contains     *      * @param no     * @return     */    public boolean containsAll_ueslessWay(final int... no){        long[] elements = new long[this.elements.length];        for(int currNo : no){            elements[currNo >>> 6] |= (1L << (currNo & 63));        } // 这一步执行完跟循环调用contains差不多了        for(int i = 0; i < elements.length; i++)            if((elements[i] & ~this.elements[i]) != 0)                return false;        return true;    }    /**     * 目前没有去维护size,每次都是去计算size     *      * @return     */    public int size(){        int size = 0;        for(long element : elements)            size += Long.bitCount(element);        return size;    }    public static void main(String[] args){        FilterSet oi = new FilterSet();        System.out.println(oi.elements.length);    }}


package cn.esstx.cq.server.ext.sensitiveword.withStopFilter;/** * 创建时间:2016年8月30日 下午2:56:37 *  * 全角/半角转换 *  * @author andy * @version 2.2 */public class BCConvert{    /**     * ASCII表中可见字符从!开始,偏移位值为33(Decimal)     */    static final char DBC_CHAR_START = 33;    // 半角!    /**     * ASCII表中可见字符到~结束,偏移位值为126(Decimal)     */    static final char DBC_CHAR_END   = 126;   // 半角~    /**     * 全角对应于ASCII表的可见字符从!开始,偏移值为65281     */    static final char SBC_CHAR_START = 65281; // 全角!    /**     * 全角对应于ASCII表的可见字符到~结束,偏移值为65374     */    static final char SBC_CHAR_END   = 65374; // 全角~    /**     * ASCII表中除空格外的可见字符与对应的全角字符的相对偏移     */    static final int  CONVERT_STEP   = 65248; // 全角半角转换间隔    /**     * 全角空格的值,它没有遵从与ASCII的相对偏移,必须单独处理     */    static final char SBC_SPACE      = 12288; // 全角空格 12288    /**     * 半角空格的值,在ASCII中为32(Decimal)     */    static final char DBC_SPACE      = ' ';   // 半角空格    /**     * <PRE>     * 半角字符->全角字符转换       * 只处理空格,!到˜之间的字符,忽略其他     * </PRE>     */    public static String bj2qj(String src){        if(src == null){            return src;        }        StringBuilder buf = new StringBuilder(src.length());        char[] ca = src.toCharArray();        for(int i = 0; i < ca.length; i++){            if(ca[i] == DBC_SPACE){ // 如果是半角空格,直接用全角空格替代                buf.append(SBC_SPACE);            } else if((ca[i] >= DBC_CHAR_START) && (ca[i] <= DBC_CHAR_END)){ // 字符是!到~之间的可见字符                buf.append((char)(ca[i] + CONVERT_STEP));            } else{ // 不对空格以及ascii表中其他可见字符之外的字符做任何处理                buf.append(ca[i]);            }        }        return buf.toString();    }    /**     * 半角转换全角     *      * @param src     * @return     */    public static int bj2qj(char src){        int r = src;        if(src == DBC_SPACE){ // 如果是半角空格,直接用全角空格替代            src = SBC_SPACE;        } else if((src >= DBC_CHAR_START) && (src <= DBC_CHAR_END)){ // 字符是!到~之间的可见字符            r = src + CONVERT_STEP;        }        return r;    }    /**     * <PRE>     * 全角字符->半角字符转换       * 只处理全角的空格,全角!到全角~之间的字符,忽略其他     * </PRE>     */    public static String qj2bj(String src){        if(src == null){            return src;        }        StringBuilder buf = new StringBuilder(src.length());        char[] ca = src.toCharArray();        for(int i = 0; i < src.length(); i++){            if(ca[i] >= SBC_CHAR_START && ca[i] <= SBC_CHAR_END){ // 如果位于全角!到全角~区间内                buf.append((char)(ca[i] - CONVERT_STEP));            } else if(ca[i] == SBC_SPACE){ // 如果是全角空格                buf.append(DBC_SPACE);            } else{ // 不处理全角空格,全角!到全角~区间外的字符                buf.append(ca[i]);            }        }        return buf.toString();    }    /**     * 全角转换半角     *      * @param src     * @return     */    public static int qj2bj(char src){        int r = src;        if(src >= SBC_CHAR_START && src <= SBC_CHAR_END){ // 如果位于全角!到全角~区间内            r = src - CONVERT_STEP;        } else if(src == SBC_SPACE){ // 如果是全角空格            r = DBC_SPACE;        }        return r;    }}


运行相应的main方法,得到测试结果:


simple
=========================================================
原字符串 : 你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析字数 : 218
加载时间 : 9 ms
=========================================================
输出替换结果 : 你是逗比吗?fuck!fUcK,你竟然用###,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后### 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析时间 : 2 ms
=========================================================
是否包含敏感词: true
消耗时间 : 0 ms
=========================================================
语句中包含敏感词的个数为 :1。包含:[法轮功]
消耗时间 : 0 ms




=========================================================
原字符串 : 你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析字数 : 218
加载时间 : 8 ms
=========================================================
输出替换结果 : 你是逗比吗?fuck!fUcK,你竟然用###,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后### 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析时间 : 1 ms
=========================================================
是否包含敏感词: true
消耗时间 : 0 ms
=========================================================
语句中包含敏感词的个数为 :1。包含:[法轮功]
消耗时间 : 0 ms




withstop
=========================================================
原字符串 : 你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析字数 : 218
加载时间 : 8 ms
=========================================================
输出替换结果 : 你是逗比吗?####!####,你竟然用###,#########,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后### 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析时间 : 0 ms
=========================================================
是否包含敏感词: true
消耗时间 : 0 ms
=========================================================
语句中包含敏感词的个数为 :4。包含:[法@!轮!%%%功, fUcK, 法轮功, fuck]
消耗时间 : 0 ms




=========================================================
原字符串 : 你是逗比吗?fuck!fUcK,你竟然用法轮功,法@!轮!%%%功,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后法轮功 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析字数 : 218
加载时间 : 7 ms
=========================================================
输出替换结果 : 你是逗比吗?####!####,你竟然用###,#########,太多的伤感情怀也许只局限于饲养基地 荧幕中的情节,主人公尝试着去用某种方式渐渐的很潇洒地释自杀指南怀那些自己经历的伤感。然后### 我们的扮演的角色就是跟随着主人公的喜红客联盟 怒哀乐而过于牵强的把自己的情感也附加于银幕情节中,然后感动就流泪,难过就躺在某一个人的怀里尽情的阐述心扉或者手机卡复制器一个人一杯红酒一部电影在夜三级片 深人静的晚上,关上电话静静的发呆着。
解析时间 : 0 ms
=========================================================
是否包含敏感词: true
消耗时间 : 0 ms
=========================================================
语句中包含敏感词的个数为 :4。包含:[法@!轮!%%%功, fUcK, 法轮功, fuck]
消耗时间 : 1 ms


应用,首先写一个Request的基类,用于完成过滤,然后所有的过滤处理,像xss、敏感词都可以继承于他,实现doFilter即可。


package cn.esstx.cq.server.ext;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Map.Entry;import javax.servlet.http.HttpServletRequest;/** * 对HttpServletRequestWrapper重写,形成一个基本的算法框架【模板方法模式】,xss和敏感词过滤可以继承于它,只需要写自己的逻辑 *  * @date 2014-04-30 上午07:30:00 */public abstract class BaseHttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper{    public BaseHttpServletRequestWrapper(HttpServletRequest request){        super(request);    }    /**     * 重写并过滤getParameter方法     */    @Override    public String getParameter(String name){        return doFilter(name, super.getParameter(name));    }    /**     * 重写并过滤getParameterValues方法     */    @Override    public String[] getParameterValues(String name){        String[] values = super.getParameterValues(name);        if(null == values){            return null;        }        String[] newValues = new String[values.length];        for(int i = 0; i < values.length; i++){            newValues[i] = doFilter(name, values[i]);        }        return newValues;    }    /**     * 重写并过滤getParameterMap方法     */    @Override    public Map<String, String[]> getParameterMap(){        Map<String, String[]> temp = new HashMap<String, String[]>();        Map<String, String[]> paraMap = super.getParameterMap();        // 对于paraMap为空的直接return        if(null == paraMap || paraMap.isEmpty()){            return paraMap;        }        Iterator<Entry<String, String[]>> iter = paraMap.entrySet().iterator();        while(iter.hasNext()){            Entry<String, String[]> entry = iter.next();            String key = entry.getKey();            String[] values = entry.getValue();            if(null == values){                continue;            }            String[] newValues = new String[values.length];            for(int i = 0; i < values.length; i++){                newValues[i] = doFilter(key, values[i]);            }            temp.put(key, newValues);        }        return temp;    }    abstract public String doFilter(String name, String value);}

实现敏感词过滤


package cn.esstx.cq.server.ext.sensitiveword;import javax.servlet.http.HttpServletRequest;import cn.esstx.cq.server.ext.BaseHttpServletRequestWrapper;import cn.esstx.cq.server.ext.sensitiveword.base.SensitiveWordFilter;/** * 对HttpServletRequestWrapper重写,统一敏感词过滤处理 *  * @date 2014-04-30 上午07:30:00 */public class HttpServletRequestWrapper extends BaseHttpServletRequestWrapper{    private SensitiveWordFilter filter;    public HttpServletRequestWrapper(HttpServletRequest request, SensitiveWordFilter filter){        super(request);        this.filter = filter;    }    @Override    public String doFilter(String name, String value){        return filter.doFilter(value);    }}

继承于JFinal的Handler进行过滤处理


package cn.esstx.cq.server.ext.sensitiveword;import java.util.Set;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.jfinal.handler.Handler;import cn.esstx.cq.server.ext.sensitiveword.base.SensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.withStopFilter.WithStopFilter;import cn.esstx.cq.server.ext.sensitiveword.wordset.DbFilterWordSet;/** * 统一敏感词过滤处理 *  * @author 熊诗言 * @date 2014-04-30 上午07:30:00 */public class SensitiveWordHandler extends Handler{    private SensitiveWordFilter         filter;    private static SensitiveWordHandler handler = null;    private SensitiveWordHandler(){        loadSensitiveWords();    }    public static synchronized SensitiveWordHandler me(){        if(null == handler){            handler = new SensitiveWordHandler();        }        return handler;    }    /**     *      * @Title: loadSensitiveWords     * @Description:一般在敏感词编辑或者新增之后,需要重新装载一个,使用一个新的filter     */    public void loadSensitiveWords(){        filter = new WithStopFilter();        Set<String> sensitiveWords = new DbFilterWordSet("sensitive_word").getWordSet();        filter.init(sensitiveWords);    }    @Override    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled){        request = new HttpServletRequestWrapper(request, filter);        nextHandler.handle(target, request, response, isHandled);    }}


配置Handler


 @Override    public void configHandler(Handlers me){        // 配置XSS handler        me.add(new XssHandler("/api/v1"));        // 防xss的攻击,效果可能更好        // me.add(new AttackHandler());        // 敏感词过滤        me.add(SensitiveWordHandler.me());    }

其他地方修改了敏感词之后调用SensitiveWordHandler.me().loadSensitiveWords();使敏感词立即生效。

有了敏感词禁止提交

使用Validator校验。filter设置为静态的,便于所有的Validator使用同一个Filter,便于其他地方修改了敏感词之后调用SensitiveWordValidator.initWords();使敏感词立即生效。

package cn.esstx.cq.server.validator.admin;import java.util.Enumeration;import java.util.Set;import com.jfinal.core.Controller;import com.jfinal.validate.Validator;import cn.esstx.cq.server.ext.sensitiveword.base.SensitiveWordFilter;import cn.esstx.cq.server.ext.sensitiveword.withStopFilter.WithStopFilter;import cn.esstx.cq.server.ext.sensitiveword.wordset.DbFilterWordSet;public class SensitiveWordValidator extends Validator{    private static SensitiveWordFilter FILTER = new WithStopFilter();    public static void initWords(){        SensitiveWordValidator.FILTER = new WithStopFilter();        Set<String> sensitiveWords = new DbFilterWordSet("sensitive_word").getWordSet();        SensitiveWordValidator.FILTER.init(sensitiveWords);    }    @Override    protected void validate(Controller c){        Enumeration<String> names = c.getParaNames();        while(names.hasMoreElements()){            String name = names.nextElement();            String value = c.getPara(name);            if(FILTER.isContains(value)){                Set<String> set = FILTER.doGetSensitiveWords(value);                addError("errorContent", "含有敏感词,禁止提交 " + set);                break;            }        }    }    @Override    protected void handleError(Controller c){        c.render("/common/error_page.html");    }}

程序启动的时候初始化

// 初始化敏感词SensitiveWordValidator.initWords();

在需要保存的时候,动用Validator校验

@Before({ Tx.class, SensitiveWordValidator.class, FormTokenValidator.class })    public void save(){

修改了敏感词之后调用SensitiveWordValidator.initWords使敏感词立即生效

SensitiveWordValidator.initWords();



参考链接:
http://www.cnblogs.com/naaoveGIS/archive/2016/10/14/5960352.html
http://www.cnblogs.com/AlanLee/p/5329555.html
http://m.blog.csdn.net/article/details?id=26961957
http://www.zuidaima.com/share/1863323818724352.htm
http://m.blog.csdn.net/article/details?id=52373005
http://www.open-open.com/lib/view/open1452046292823.html
http://www.tuicool.com/articles/BNjemmR
http://m.blog.csdn.net/article/details?id=42082205
http://www.cnblogs.com/AlanLee/p/5329555.html
http://www.cnblogs.com/naaoveGIS/archive/2016/10/14/5960352.html
java实现敏感词过滤
http://www.zuidaima.com/share/1863323818724352.htm
http://m.blog.csdn.net/article/details?id=52373005
                                             
0 0