需求分析:
在javashop电商系统中,商品数据是存在elasticsearch中,使用ik分词器分词,ik分词器的词库内置了2万多个。
但在实际运维过程中,因为商品的个性化,词库不一定可以满足,为了搜索引擎分词(关键词)更加准确,要求可对分词词库进行手工维护。
思路:
IK自定义词库是支持远程热加载的。
先看下官方的说明:
remote_ext_dict:
1.该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。
2.该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。
满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。
由此,我们可以开放一个API供IK调用。
搜索分词(关键词)架构思路
1.管理端对关键词进行维护;
2.管理端设置秘钥(此秘钥仅做加载分词API验证使用);
3.管理端展示分词列表,根据最后修改时间倒序展示。
时序图:
数据结构:
关键词表(es_custom_words):
id | id | int | 10 | 是 |
name | 关键词 | 字符串 | 100 | 否 |
add_time | 添加时间 | 长整型 | 20 | 否 |
modify_time | 最后修改时间 | 长整型 | 20 | 否 |
disabled | 是否可用:可用:1 ;隐藏: 0 | 整形 | 1 | 否 |
秘钥设置说明: 在系统设置表(es_setting)中新增分组(ES_SIGN),对秘钥进行维护时修改此分组下的数据。
领域模型
管理端
管理端添加搜索设置菜单,对关键词进行维护
模型
id | id | |
name | 分词名称必填 | |
addTime | 添加时间 | |
disabled | 是否可用 | 可用:1;不可用:0 |
modifyTime | 修改时间 |
ES加载词库API
在基础API中添加加载词库API,此Api需要校验秘钥,失败返回空字符串,成功则从数据库中加载数据并返回。
IK Analyzer 扩展配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://base-api-domain/load-customwords?secret_key=secret_value</entry>
</properties>
其中secret_value可以随意设置,此处配置的值,需要在管理端搜索分词列表处保存
base-api-domain改为自己的base-api域名或者IP:端口即可
源码
说明:此处仅展示IK加载片段代码,关于管理分此维护相关不做展示
CustomWordsBaseController
package com.enation.app.javashop.base.api;
import com.enation.app.javashop.core.base.SettingGroup;
import com.enation.app.javashop.core.client.system.SettingClient;
import com.enation.app.javashop.core.goods.GoodsErrorCode;
import com.enation.app.javashop.core.goodssearch.model.EsSecretSetting;
import com.enation.app.javashop.core.goodssearch.service.CustomWordsManager;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.util.JsonUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
/**
* 自定义分词控制器
*
* @author liuyulei
* @version v1.0
* @since v7.0.0
* 2019-05-26
*/
@RestController
@RequestMapping("/load-customwords")
@Api(description = "加载分词库")
public class CustomWordsBaseController {
@Autowired
private CustomWordsManager customWordsManager;
@Autowired
private SettingClient settingClient;
@GetMapping
@ApiImplicitParams({
@ApiImplicitParam(name = "secret_key", value = "秘钥", required = true, dataType = "String", paramType = "query")
})
public String getCustomWords(@ApiIgnore String secretKey){
if(StringUtil.isEmpty(secretKey)){
return "";
}
String value = settingClient.get(SettingGroup.ES_SIGN);
if(StringUtil.isEmpty(value)){
return "";
}
EsSecretSetting secretSetting = JsonUtil.jsonToObject(value,EsSecretSetting.class);
if(!secretKey.equals(secretSetting.getSecretKey())){
throw new ServiceException(GoodsErrorCode.E310.code(),"秘钥验证失败!");
}
String res = this.customWordsManager.deploy();
try {
return new String(res.getBytes(),"utf-8");
}catch (Exception e){
e.printStackTrace();
}
return "";
}
}
CustomWordsManager
package com.enation.app.javashop.core.goodssearch.service;
/**
* 自定义分词表业务层
* @author fk
* @version v1.0
* @since v7.0.0
* 2018-06-20 16:08:07
*
* * update by liuyulei 2019-05-27
*/
public interface CustomWordsManager {
/**
* 部署替换
* @return
*/
String deploy();
}
CustomWordsManagerImpl
package com.enation.app.javashop.core.goodssearch.service.impl;
import com.enation.app.javashop.core.goodssearch.model.CustomWords;
import com.enation.app.javashop.core.goodssearch.service.CustomWordsManager;
import com.enation.app.javashop.framework.context.ThreadContextHolder;
import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.util.DateUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* 自定义分词表业务类
*
* @author fk
* @version v1.0
* @since v7.0.0
* 2018-06-20 16:08:07
*
* update by liuyulei 2019-05-27
*/
@Service
public class CustomWordsManagerImpl implements CustomWordsManager {
@Autowired
@Qualifier("goodsDaoSupport")
private DaoSupport daoSupport;
@Override
public String deploy() {
String sql = "select * from es_custom_words where disabled = 1 order by modify_time desc";
List<CustomWords> list = this.daoSupport.queryForList(sql, CustomWords.class);
HttpServletResponse response = ThreadContextHolder.getHttpResponse();
StringBuffer buffer = new StringBuffer();
if (StringUtil.isNotEmpty(list)) {
int i = 0;
for (CustomWords word : list) {
if (i == 0) {
SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss" );
try {
response.setHeader("Last-Modified", format.parse(DateUtil.toString(word.getAddTime(),"yyyy-MM-dd hh:mm:ss")) + "");
response.setHeader("ETag", format.parse(DateUtil.toString(word.getModifyTime(),"yyyy-MM-dd hh:mm:ss")) + "");
}catch (Exception e){
e.printStackTrace();
}
buffer.append(word.getName());
} else {
buffer.append("\n" + word.getName());
}
i++;
}
}
return buffer.toString();
}
}
以上为此次分享内容,后续每周会不定期分享架构文章,大家可以关注我们!!!
易族智汇(javashop)原创文章