问题发布与敏感词过滤

起因

这其实是上节课的内容了,不过还是记录一下好了;

问题发布

题外话

  • 对一个web程序,架构是不变的,service负责拿数据,上层也不在意你是怎么取得的;所以一个功能的实现,要从建立数据库开始,然后是与之对应的model,然后是DAO层,service,然后controller就可以去的数据了,然后再通过model传到页面上,数据量大的话,可以使用ViewObject 来进行传输;
  • 切面可以让我们在业务中横插一刀,不管是打印log信息,还是做什么,都很好用;拦截器则是针对请求,我们可以在拦截器中检验用户状态,也可以进行重定向;

部分重点

  • 发布问题其实就是在数据库中添加一条记录,然后刷新页面,页面又会提取几条最新的数据,这样就发布了问题,代码上没什么困难;
  • 问题表有一个created_date的索引,据说如果想要找到一个范围的数据,这个索引还是有用处的;

    敏感词过滤

    html便签过滤

  • 这个其实就是调用函数就ok了,做的也就是将HTML的关键词给转义掉;
    1
    question.setContent(HtmlUtils.htmlEscape(question.getContent()));

敏感词与前缀树

  • 重点就是这个部分了啦
  • 前缀树的结构:会持有一个map或者一个数组表明它有那些子节点,然后判断敏感词是不是能够继续,另外就是是否为关键词,这里的布尔值有局限性,就是当一个字符串为另一个的前缀时,不过这也取决于我们的文本是如何写的;简单的方法是结尾结点保存沿途字符所构成的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    private class TrieNode {
    //是否为关键词结尾的结点
    private boolean end = false;
    //前缀树中的一个结点持有一个map,这个map中就是当前结点的子结点
    private Map<Character, TrieNode> subNodes = new HashMap<Character, TrieNode>();
    //增加子节点
    public void addSubNode(Character key, TrieNode node) {
    subNodes.put(key, node);
    }
    //得到子节点
    TrieNode getSubNode(Character key) {
    return subNodes.get(key);
    }
    //判断是否为结尾节点
    boolean isKeyWordEnd() {
    return end;
    }
    //设置为结尾结点
    void setKeyWord(boolean end) {
    this.end = end;
    }
    }
  • 增加敏感词的函数,其实还是蛮容易懂得了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 增加关键字的结点
    * 每次从lineText中读一个字符。然后查询当前结点下有没有这个字符
    * 如果没有就创建一下,然后当前指针向下移动
    * 如果lineText走到最后,说明为结尾结点
    * @param lineText
    */
    private void addWord(String lineText) {
    TrieNode temp = rootNode;
    for (int i = 0; i < lineText.length(); i++) {
    Character c = lineText.charAt(i);
    TrieNode node = temp.getSubNode(c);
    if (node == null) {
    node = new TrieNode();
    temp.addSubNode(c, node);
    }
    temp = node;
    if (i == lineText.length() - 1) {
    temp.setKeyWord(true);
    }
    }
    }
  • 过滤函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    //判断是否为故意扰乱敏感词的字符
    private boolean isSymbol(char c) {
    int ic = (int) c;
    //东亚文字
    return !CharUtils.isAsciiAlphanumeric(c) && (ic < 0x2E80 || ic > 0x9FFF);
    }
    /**
    * 用来过滤的函数
    * @param text
    * @return
    */
    public String filter(String text) {
    if (StringUtils.isBlank(text)) {
    return text;
    }
    StringBuilder result = new StringBuilder();
    String replaceMent = "***";
    TrieNode tempNode = rootNode;
    //begin一直向后移动,代表当前搜索的敏感词的头结点
    int begin = 0;
    //position是当前敏感词的某一个结点,来回移动的那个
    int position = 0;
    while (position < text.length()) {
    char c = text.charAt(position);
    if (isSymbol(c)) {
    //如果还在rootNode,说明还没进前缀树,
    //这部分奇怪的字符直接加进来就好
    //否则就跳过,只找那些关键字过滤
    if (tempNode == rootNode) {
    result.append(c);
    begin++;
    }
    position++;
    continue;
    }
    tempNode = tempNode.getSubNode(c);
    //当前结点为null,说明不是敏感词
    if (tempNode == null) {
    result.append(text.charAt(begin));
    position = begin + 1;
    begin = position;
    tempNode = rootNode;
    } else if (tempNode.isKeyWordEnd()) {
    result.append(replaceMent);
    position = position + 1;
    begin = position;
    tempNode = rootNode;
    } else {
    ++position;
    }
    }
    //position走到了最后,别忘了把begin剩下的也加进来,
    //不过也有可能begin也没有啥嘞
    result.append(text.substring(begin));
    return result.toString();
    }