java B2B2C多用戶商城系統-搜索分詞架構分享

需求分析:

在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)原創文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章