需求分析:
在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 擴展配置如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 3 <properties> 4 <comment>IK Analyzer 擴展配置</comment> 5 <!--用戶可以在這里配置遠程擴展字典 --> 6 <entry key="remote_ext_dict">http://base-api-domain/load-customwords?secret_key=secret_value</entry> 7 </properties>
其中secret_value可以隨意設置,此處配置的值,需要在管理端搜索分詞列表處保存
base-api-domain改為自己的base-api域名或者IP:端口即可
源碼
說明:此處僅展示IK加載片段代碼,關於管理分此維護相關不做展示
CustomWordsBaseController
1 package com.enation.app.javashop.base.api; 2 3 import com.enation.app.javashop.core.base.SettingGroup; 4 import com.enation.app.javashop.core.client.system.SettingClient; 5 import com.enation.app.javashop.core.goods.GoodsErrorCode; 6 import com.enation.app.javashop.core.goodssearch.model.EsSecretSetting; 7 import com.enation.app.javashop.core.goodssearch.service.CustomWordsManager; 8 import com.enation.app.javashop.framework.exception.ServiceException; 9 import com.enation.app.javashop.framework.util.JsonUtil; 10 import com.enation.app.javashop.framework.util.StringUtil; 11 import io.swagger.annotations.Api; 12 import io.swagger.annotations.ApiImplicitParam; 13 import io.swagger.annotations.ApiImplicitParams; 14 import org.springframework.beans.factory.annotation.Autowired; 15 import org.springframework.web.bind.annotation.GetMapping; 16 import org.springframework.web.bind.annotation.RequestMapping; 17 import org.springframework.web.bind.annotation.RestController; 18 import springfox.documentation.annotations.ApiIgnore; 19 20 /** 21 * 自定義分詞控制器 22 * 23 * @author liuyulei 24 * @version v1.0 25 * @since v7.0.0 26 * 2019-05-26 27 */ 28 @RestController 29 @RequestMapping("/load-customwords") 30 @Api(description = "加載分詞庫") 31 public class CustomWordsBaseController { 32 33 @Autowired 34 private CustomWordsManager customWordsManager; 35 @Autowired 36 private SettingClient settingClient; 37 38 @GetMapping 39 @ApiImplicitParams({ 40 @ApiImplicitParam(name = "secret_key", value = "秘鑰", required = true, dataType = "String", paramType = "query") 41 42 }) 43 public String getCustomWords(@ApiIgnore String secretKey){ 44 45 if(StringUtil.isEmpty(secretKey)){ 46 return ""; 47 } 48 String value = settingClient.get(SettingGroup.ES_SIGN); 49 if(StringUtil.isEmpty(value)){ 50 return ""; 51 } 52 EsSecretSetting secretSetting = JsonUtil.jsonToObject(value,EsSecretSetting.class); 53 if(!secretKey.equals(secretSetting.getSecretKey())){ 54 throw new ServiceException(GoodsErrorCode.E310.code(),"秘鑰驗證失敗!"); 55 } 56 String res = this.customWordsManager.deploy(); 57 try { 58 return new String(res.getBytes(),"utf-8"); 59 }catch (Exception e){ 60 e.printStackTrace(); 61 } 62 return ""; 63 64 } 65 66 67 }
CustomWordsManager
1 package com.enation.app.javashop.core.goodssearch.service; 2 3 /** 4 * 自定義分詞表業務層 5 * @author fk 6 * @version v1.0 7 * @since v7.0.0 8 * 2018-06-20 16:08:07 9 * 10 * * update by liuyulei 2019-05-27 11 */ 12 public interface CustomWordsManager { 13 14 /** 15 * 部署替換 16 * @return 17 */ 18 String deploy(); 19 20 }
CustomWordsManagerImpl
1 package com.enation.app.javashop.core.goodssearch.service.impl; 2 3 import com.enation.app.javashop.core.goodssearch.model.CustomWords; 4 import com.enation.app.javashop.core.goodssearch.service.CustomWordsManager; 5 import com.enation.app.javashop.framework.context.ThreadContextHolder; 6 import com.enation.app.javashop.framework.database.DaoSupport; 7 import com.enation.app.javashop.framework.util.DateUtil; 8 import com.enation.app.javashop.framework.util.StringUtil; 9 import org.springframework.beans.factory.annotation.Autowired; 10 import org.springframework.beans.factory.annotation.Qualifier; 11 import org.springframework.stereotype.Service; 12 13 import javax.servlet.http.HttpServletResponse; 14 import java.text.SimpleDateFormat; 15 import java.util.List; 16 17 /** 18 * 自定義分詞表業務類 19 * 20 * @author fk 21 * @version v1.0 22 * @since v7.0.0 23 * 2018-06-20 16:08:07 24 * 25 * update by liuyulei 2019-05-27 26 */ 27 @Service 28 public class CustomWordsManagerImpl implements CustomWordsManager { 29 30 @Autowired 31 @Qualifier("goodsDaoSupport") 32 private DaoSupport daoSupport; 33 34 @Override 35 public String deploy() { 36 String sql = "select * from es_custom_words where disabled = 1 order by modify_time desc"; 37 List<CustomWords> list = this.daoSupport.queryForList(sql, CustomWords.class); 38 HttpServletResponse response = ThreadContextHolder.getHttpResponse(); 39 StringBuffer buffer = new StringBuffer(); 40 if (StringUtil.isNotEmpty(list)) { 41 int i = 0; 42 for (CustomWords word : list) { 43 if (i == 0) { 44 SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd hh:mm:ss" ); 45 try { 46 response.setHeader("Last-Modified", format.parse(DateUtil.toString(word.getAddTime(),"yyyy-MM-dd hh:mm:ss")) + ""); 47 response.setHeader("ETag", format.parse(DateUtil.toString(word.getModifyTime(),"yyyy-MM-dd hh:mm:ss")) + ""); 48 }catch (Exception e){ 49 e.printStackTrace(); 50 } 51 52 buffer.append(word.getName()); 53 } else { 54 buffer.append("\n" + word.getName()); 55 } 56 i++; 57 } 58 } 59 return buffer.toString(); 60 } 61 }
以上為此次分享內容,后續每周會不定期分享架構文章,大家可以關注我們!!!
易族智匯(javashop)原創文章