1. 程式人生 > >重寫solr的積分計算來滿足自定義的積分計算、排序的策略

重寫solr的積分計算來滿足自定義的積分計算、排序的策略

說明版本:歷史遺留專案使用的solr5.3,高版本原理類似

第一步,

簡單粗暴,把solr目錄下lib下的jar包都copy到一個新的專案

幽雅,使用maven引入solr

我這裡使用的方式一...

第二步,(需要參考 solrconfig.xml),通過註冊一個ParserPlugin或者Funtion來控制

<!-- Query Parsers

       http://wiki.apache.org/solr/SolrQuerySyntax

       Multiple QParserPlugins can be registered by name, and then
       used in either the "defType" param for the QueryComponent (used
       by SearchHandler) or in LocalParams
    -->
  <!-- example of registering a query parser -->
  <!--
     <queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
    -->

  <!-- Function Parsers

       http://wiki.apache.org/solr/FunctionQuery

       Multiple ValueSourceParsers can be registered by name, and then
       used as function names when using the "func" QParser.
    -->
  <!-- example of registering a custom function parser  -->
  <!--
     <valueSourceParser name="myfunc" 
                        class="com.mycompany.MyValueSourceParser" />
    -->

通過這裡的兩種方式都可以實現,一個是registering a query parser  ,一個是 registering a custom function parser

方式一:registering a query parser

原理:通過defType指定一個自定義的Parser,通過Parser建立自定的query ,然後提供provider的customScore 方法來干預評分

public class HmkxQueryParserPlugin extends QParserPlugin {
    final static Logger log= LoggerFactory.getLogger(HmkxQueryParserPlugin.class);

    public void init(NamedList args) {

    }

    @Override
    public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
        return  new HmkxParser(qstr,localParams,params,req);
    }
}
public class HmkxParser extends QParser {
    final static Logger log= LoggerFactory.getLogger(HmkxParser.class);
    private Query innerQuery;

    @Override
    public Query parse() throws SyntaxError {
        return new HmkxQuery(innerQuery);
    }

    public HmkxParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
        super(qstr, localParams, params, req);
        try {
            //這裡比較重要,把你實際使用的 原query 放在這裡 我們原來使用的 具體使用什麼參考 QParserPlugin 的standardPlugins
            QParser parser = getParser(qstr, "edismax", getReq());
            this.innerQuery = parser.parse();
        } catch (SyntaxError ex) {
            throw new RuntimeException("error parsing query", ex);
        }
    }
}
public class HmkxQuery extends CustomScoreQuery {

    public HmkxQuery(Query subQuery) {
        super(subQuery);
    }

    @Override
    protected CustomScoreProvider getCustomScoreProvider(LeafReaderContext context) throws IOException {
        //此處返回,定義的Provider
        HmkxProvider provider=new HmkxProvider(context);
        return provider;
    }
}
public class HmkxProvider extends CustomScoreProvider {

    final static Logger log= LoggerFactory.getLogger(HmkxProvider.class);
    private SortedDocValues titleValues;


    public HmkxProvider(LeafReaderContext context) {
        super(context);
    }

    @Override
    public float customScore(int doc, float subQueryScore, float valSrcScore) throws IOException {
        float myScore = 1f;
        //補充額外的業務邏輯  可以獲取doc中的某個欄位,這裡獲取的是釋出時間
        LeafReader lr = context.reader();
        Date pubDate = new Date(lr.getNumericDocValues("pubdate").get(doc));
        Document document = lr.document(doc);
        int diffMonth = getMonthDiff(new Date(),pubDate);
//        log.error("id:"+doc+",pudate:"+pubDate);
//        log.error("月份查:"+diffMonth);
        myScore = myScore-(diffMonth*0.05f);
        if(myScore>1){
            myScore = 1;
        }else if(myScore<0.5){
            myScore = 0.5f;
        }
//        log.error("時間係數:"+myScore);
        return  subQueryScore*valSrcScore *myScore;
    }

    /**
     *  獲取兩個日期相差的月數
     * @param d1    較大的日期
     * @param d2    較小的日期
     * @return  如果d1>d2返回 月數差 否則返回0
     */
    public static int getMonthDiff(Date d1, Date d2) {
        Calendar c1 = Calendar.getInstance();
        Calendar c2 = Calendar.getInstance();
        c1.setTime(d1);
        c2.setTime(d2);
        if(c1.getTimeInMillis() < c2.getTimeInMillis()) return 0;
        int year1 = c1.get(Calendar.YEAR);
        int year2 = c2.get(Calendar.YEAR);
        int month1 = c1.get(Calendar.MONTH);
        int month2 = c2.get(Calendar.MONTH);
        int day1 = c1.get(Calendar.DAY_OF_MONTH);
        int day2 = c2.get(Calendar.DAY_OF_MONTH);
        // 獲取年的差值 假設 d1 = 2015-8-16  d2 = 2011-9-30
        int yearInterval = year1 - year2;
        // 如果 d1的 月-日 小於 d2的 月-日 那麼 yearInterval-- 這樣就得到了相差的年數
        if(month1 < month2 || month1 == month2 && day1 < day2) yearInterval --;
        // 獲取月數差值
        int monthInterval =  (month1 + 12) - month2  ;
        if(day1 < day2) monthInterval --;
        monthInterval %= 12;
        return yearInterval * 12 + monthInterval;
    }

}

新建4個類,通過idea獲取其它工具生成可引入的jar包,放到solr/webapp/WEB-INF/lib目錄下面,然後修改第一步提到的配置檔案

<queryParser name="hmkxParser" class="com.hmkx.solr.query.HmkxQueryParserPlugin" /> 

然後重點來了,在呼叫select介面的時候,記得指定 defType=hmkxParser

方式二:registering a custom function parser

原理簡述:積分計算的時候會通過多個,valueSource獲取doc的一個分值(係數),solr定義了一些列的valueSouce,如下圖

而使用哪些valueSource通過valueSourceParser決定,

ValueSourceParser 在這個類裡面初始化了很多 Parser到 standardValueSourceParsers 裡面
public static Map<String, ValueSourceParser> standardValueSourceParsers = new HashMap();
static {
    addParser("testfunc", new ValueSourceParser() {
        public ValueSource parse(FunctionQParser fp) throws SyntaxError {
            ValueSource source = fp.parseValueSource();
            return new TestValueSource(source);
        }
    });
    ....省略
}

原理結束,下面直接寫方法了

定義Funtion外掛

public class HmkxValueParser extends ValueSourceParser {
    final static Logger log= LoggerFactory.getLogger(HmkxValueParser.class);

    @Override
    public ValueSource parse(FunctionQParser fq) throws SyntaxError {
        return new FunctionValueSource(fq.parseValueSourceList());
    }
}
public class FunctionValueSource extends ValueSource {
    final static Logger log= LoggerFactory.getLogger(FunctionValueSource.class);
    private  List<ValueSource>  valueSources;

    public FunctionValueSource(List<ValueSource> source) {
        this.valueSources=source;
    }

    @Override
    public FunctionValues getValues(Map map, final LeafReaderContext leafReaderContext) throws IOException {
        final FunctionValues y=this.valueSources.get(0).getValues(map,leafReaderContext);
        final FunctionValues m=this.valueSources.get(1).getValues(map,leafReaderContext);
        return new FloatDocValues(this) {
            @Override
            public float floatVal(int docId) {
                String title = y.strVal(docId);
                String des =m.strVal(docId);
                //這裡加入你的邏輯計算
                return 2l;
            }
        };
    }

    @Override
    public boolean equals(Object o) {
        return false;
    }

    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public String description() {
        return null;
    }
}

配置檔案修改

<valueSourceParser name="myfunc"  class="com.hmkx.solr.funtion.HmkxValueSourceParser" />

重要的地方:查詢需要指定引數 {!boost b=myfunc(title,description)}id:I517648

總結:個人更喜歡用第一種方式,使用上更簡單

程式碼地址 https://gitee.com/huhaitao/solr2test/tree/master/solr2score