学习笔记 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,该请求只需满足以下两点即可完成分词热更新。
- 该 http 请求需要返回两个头部(header),一个是
Last-Modified
,一个是ETag
,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。- 该 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";