学习笔记 ik分词器实现词库热更新

学习笔记 ik分词器实现词库热更新

一、ik本地文件读取方式

添加自定义词典文件

ik插件已经为用户提供自定义词典扩展功能,只要修改配给文件即可

在config目录下创建一个custom目录,创建一个you.dic文件

you.dic文件中添加自定义的热词

修改IKAnalyzer.cfg.xml配置文件

修改config目录下IKAnalyzer.cfg.xml,修改成这个样子

	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
								<!-- 将这里修改为你的**.dic文件	 -->
	<entry key="ext_dict">/custom/you.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->

二、ik远程词库调用

ik官方文档说明

ik分词器支持热更新 需要在配置文件中修改这里的配置

 	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">location</entry>
 	<!--用户可以在这里配置远程扩展停止词字典-->
	<entry key="remote_ext_stopwords">location</entry>

其中 location 是指一个 url,比如 http://lsqo.cn/getDic,该请求只需满足以下两点即可完成分词热更新。

  1. 该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。
  2. 该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。

满足上面两点要求就可以实现热更新分词了,不需要重启 ES

假设 我们的热词需要管理员在后台添加或别的情况需要使用数据库操作 这里我就使用mysql来操作

我们将词库放在了mysql 中进行管理,首先新建一个ik 数据库,并新建dic分词表:

CREATE TABLE `t_dic` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dict_text` varchar(255) NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

然后咧 建一个spring boot工程 写两个接口

Controller层

/**
 * @author 谢宗佑
 */
@Slf4j
@RestController
public class DicController {
    @Autowired
    DicServiceImpl dicService;
    /**
     *
     * @param request
     * @param response
     * @return java.lang.String
     * @author 谢宗佑
     * @creed: Talk is cheap,show me the code
     * @date 2022/4/10 21:22
     * 验证当前是否是最新的
     */

    @RequestMapping(value = "/extDic", method = RequestMethod.HEAD)
    public String headExtDict(HttpServletRequest request, HttpServletResponse response) throws ParseException {
        String modified = request.getHeader("If-Modified-Since");
        String eTag = request.getHeader("If-None-Match");
        log.info("head请求,接收modified:{} ,eTag:{}", modified, eTag);
        String newModified = dicService.getCurrentNewModified();
        String newTag = String.valueOf(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(newModified).getTime());
        response.setHeader("Last-Modified", newModified);
        response.setHeader("ETag", newTag);
        return "success";
    }

    /**
     *
     * @param request
	 * @param response
     * @return java.lang.String
     * @author 谢宗佑
     * @creed: Talk is cheap,show me the code
     * @date 2022/4/10 21:23
     * 上面的接口返回了最新分词时间给ik 时间不一致就会访问此get接口 获取所有分词
     */

    @GetMapping( "/extDic")
    public String getExtDict(HttpServletRequest request, HttpServletResponse response) throws ParseException {
        String newModified = dicService.getCurrentNewModified();
        String newTag = String.valueOf(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(newModified).getTime());
        response.setHeader("Last-Modified", newModified);
        response.setHeader("ETag", newTag);
        return dicService.getDict();
    }
}

@Service层

/**
* @author nanana
* @description 针对表【t_dic】的数据库操作Service实现
* @createDate 2022-04-10 21:16:46
*/
@Service
public class DicServiceImpl extends ServiceImpl<DicMapper, Dic>
    implements DicService{
    @Autowired
    private DicMapper dicMapper;
    @Override
    public String getCurrentNewModified() {
        return dicMapper.getCurrentNewModified();
    }

    @Override
    public String getDict() {
        List<Dic> updateList = dicMapper.selectList(null);
        return String.join("\n", updateList.stream().map(Dic::getDictText).collect(Collectors.toSet()));
    }
}

@Mapper层

/**
* @author nanana
* @description 针对表【t_dic】的数据库操作Mapper
* @createDate 2022-04-10 21:16:46
* @Entity cn.lsqo.domain.Dic
*/
@Mapper
public interface DicMapper extends BaseMapper<Dic> {
    @Select("select max(update_time) from t_dic")
    String getCurrentNewModified();
}

两个接口,一个是验证当前是否是最新的时间 一个是获取到具体分词的get接口,

坑点1

这两个url地址需要一模一样

在head接口中直接返回了当前数据库最后更新的时间作为 newModified newTag是时间戳

返回到ik 假如ik判断时间不一致 就会访问get接口 接口返回所有的分词 不换行不生效

配置ik分词器config下的configIKAnalyzer.cfg.xml

一、ik重写ik源码链接mysql 获取分词

下载ik源码 在pom文件中导入依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

在项目根目录下的config目录中添加config\jdbc.yml配置文件

jdbc:
  url: jdbc:mysql:///test?useSSL=false&useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true&allowMultiQueries=true
  user: root
  password : 12345678
  sql: SELECT dict_text FROM t_dic

找到字典管理类

加一下mysql的连接

static {
    try {
        //利用反射把mysql驱动加载到内存
        Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 /**
 * 从mysql加载热更新词典
 */
private void loadExtDictByMysql() {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    Properties prop = null;
    InputStream inputStream = null;
    try {
        prop = new Properties();
        inputStream = inputStream = new FileInputStream(PathUtils.get(getDictRoot(), "jdbc.yml").toFile());
        prop.load(inputStream);
        conn = DriverManager.getConnection(
                prop.getProperty("url"),
                prop.getProperty("user"),
                prop.getProperty("password"));
        stmt = conn.createStatement();
        rs = stmt.executeQuery(prop.getProperty("sql"));

        while (rs.next()) {
            String theWord = rs.getString("dict_text");
            _MainDict.fillSegment(theWord.trim().toCharArray());
        }
        logger.info("从mysql加载热更新词典成功!");
    } catch (Exception e) {
        logger.error("error", e);
    } finally {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                logger.error("error", e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                logger.error("error", e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                logger.error("error", e);
            }
        }
    }
}

然后找到这个位置加上

这里设置的是120秒刷新一次 加载词典

	pool.scheduleAtFixedRate(new Runnable() {
							@Override
							public void run() {
								Dictionary.getSingleton().loadExtDictByMysql();
							}
						},10,120,TimeUnit.SECONDS);

打包咯 芜湖芜湖!

然后咧放到es里

!!!

这里有个坑

AccessControlException: access denied ("java.net.SocketPermission" "127.0.0.1:3306" "connect,resolve")

百度了一下 修改jre目录下的java.policy

新增一行

permission java.net.SocketPermission "*", "connect,resolve";
posted @ 2022-04-10 23:51  炫我疯狂的炫  阅读(740)  评论(0编辑  收藏  举报