对接Hanlp的最佳实践(仅为本公司的对接示例)

2022-08-23  本文已影响0人  天草二十六_简村人

一、Hanlp的背景介绍

二、目标

三、敏感词服务的设计思路

hanlp在敏感词检测服务中的作用.png

四、关键设计思路

3.1、引入离线包

       <dependency>
            <groupId>com.hankcs</groupId>
            <artifactId>hanlp</artifactId>
            <version>portable-1.8.2</version>
        </dependency>

3.2、引入配置文件hanlp.properties

/**
     * hanlp.properties的路径,一般情况下位于classpath目录中。
     * 但在某些极端情况下(不标准的Java虚拟机,用户缺乏相关知识等),允许将其设为绝对路径
     */
    public static String HANLP_PROPERTIES_PATH;
默认读取classpath目录下的hanlp.properties.png

3.3、指定字典所在的根目录(方式一)

只需要修改这一个配置,root=/opt/SensitiveData/data-for-1.7.5/
这种方式,会需要把hanlp.properties放在Jar包外,额外带来了下面的几行代码。

        String hanlp_properties_path = System.getProperty("hanlp_properties_path");
       
        if (null != hanlp_properties_path && !"".equals(hanlp_properties_path.trim())) {
            Predefine.HANLP_PROPERTIES_PATH = hanlp_properties_path;
        }

3.4、指定字典所在的根目录(方式二)

设置环境变量HANLP_ROOT,推荐使用这种方式。

3.5、JVM内存中自定义的敏感词库做到实时刷新

这里的jvm内存数据结构,可以是Set/Map,更推荐caffeine实现。

创建一个标识,用来记录单个jvm节点的内存数据是否需要刷新。
多个Jvm节点,保存到redis的set集合中(值可以是jvm进程号)。在启动的时候,判断当前的进程号是否存在于redis的set集合中,如果不存在,说明需要重新刷新jvm内存中的自定义的敏感词库。

在敏感词维护的时候,新增或删除敏感词,除了操作mysql数据库外,将当前进程号从redis中的set集合移除。做到重新刷新jvm内存中的敏感词库。

3.6、hanlp的自定义词典

CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt;data/dictionary/custom/自定义.txt;data/dictionary/custom/现代汉语补充词库.txt;data/dictionary/custom/全国地名大全.txt ns;data/dictionary/custom/人名词典.txt;data/dictionary/custom/机构名词典.txt;data/dictionary/custom/上海地名.txt ns;data/dictionary/person/nrf.txt nrf;

因为mainPath=data/dictionary/custom/CustomDictionary.txt,所以 if (loadDat(mainPath, dat)) return true; 所有后面的词典文件不再读取。

CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt
/**
     * 加载词典
     *
     * @param mainPath 缓存文件文件名
     * @param path     自定义词典
     * @param isCache  是否缓存结果
     */
    public static boolean loadMainDictionary(String mainPath, String path[], DoubleArrayTrie<CoreDictionary.Attribute> dat, boolean isCache)
    {
        logger.info("自定义词典开始加载:" + mainPath);
        if (loadDat(mainPath, dat)) return true;
        TreeMap<String, CoreDictionary.Attribute> map = new TreeMap<String, CoreDictionary.Attribute>();
        LinkedHashSet<Nature> customNatureCollector = new LinkedHashSet<Nature>();
        try
        {
            //String path[] = HanLP.Config.CustomDictionaryPath;
            for (String p : path)
            {
                Nature defaultNature = Nature.n;
                File file = new File(p);
                String fileName = file.getName();
                int cut = fileName.lastIndexOf(' ');
                if (cut > 0)
                {
                    // 有默认词性
                    String nature = fileName.substring(cut + 1);
                    p = file.getParent() + File.separator + fileName.substring(0, cut);
                    try
                    {
                        defaultNature = LexiconUtility.convertStringToNature(nature, customNatureCollector);
                    }
                    catch (Exception e)
                    {
                        logger.severe("配置文件【" + p + "】写错了!" + e);
                        continue;
                    }
                }
                logger.info("以默认词性[" + defaultNature + "]加载自定义词典" + p + "中……");
                boolean success = load(p, defaultNature, map, customNatureCollector);
                if (!success) logger.warning("失败:" + p);
            }
            if (map.size() == 0)
            {
                logger.warning("没有加载到任何词条");
                map.put(Predefine.TAG_OTHER, null);     // 当作空白占位符
            }
            logger.info("正在构建DoubleArrayTrie……");
            dat.build(map);
            if (isCache)
            {
                // 缓存成dat文件,下次加载会快很多
                logger.info("正在缓存词典为dat文件……");
                // 缓存值文件
                List<CoreDictionary.Attribute> attributeList = new LinkedList<CoreDictionary.Attribute>();
                for (Map.Entry<String, CoreDictionary.Attribute> entry : map.entrySet())
                {
                    attributeList.add(entry.getValue());
                }
                DataOutputStream out = new DataOutputStream(new BufferedOutputStream(IOUtil.newOutputStream(mainPath + Predefine.BIN_EXT)));
                // 缓存用户词性
                if (customNatureCollector.isEmpty()) // 热更新
                {
                    for (int i = Nature.begin.ordinal() + 1; i < Nature.values().length; ++i)
                    {
                        customNatureCollector.add(Nature.values()[i]);
                    }
                }
                IOUtil.writeCustomNature(out, customNatureCollector);
                // 缓存正文
                out.writeInt(attributeList.size());
                for (CoreDictionary.Attribute attribute : attributeList)
                {
                    attribute.save(out);
                }
                dat.save(out);
                out.close();
            }
        }
        catch (FileNotFoundException e)
        {
            logger.severe("自定义词典" + mainPath + "不存在!" + e);
            return false;
        }
        catch (IOException e)
        {
            logger.severe("自定义词典" + mainPath + "读取错误!" + e);
            return false;
        }
        catch (Exception e)
        {
            logger.warning("自定义词典" + mainPath + "缓存失败!\n" + TextUtility.exceptionToString(e));
        }
        return true;
    }
上一篇 下一篇

猜你喜欢

热点阅读