近期有一個需求,需要對優(yōu)惠券可用商品列表加個排序,只針對面值類的券不包括折扣券。
10年的揭東網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。網(wǎng)絡(luò)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整揭東建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。成都創(chuàng)新互聯(lián)從事“揭東網(wǎng)站設(shè)計”,“揭東網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
需求是這樣的,假設(shè)有一張面值券 50 塊錢,可用商品列表 A 100、B 40、C 10,當(dāng)用戶查詢當(dāng)前券可用商品列表的時候優(yōu)先將卡券可以直接抵扣且不需要用戶在額外支付的商品排在前面。
C 10
B 40
A 100
其實排序有很多側(cè)重,比如:
1.根據(jù)用戶利益最大化原則,排序列表應(yīng)該是 B、C、A
2.根據(jù)用戶購買習(xí)慣,有可能是 A、B、C
3.根據(jù)運營策略、第三方利益等有可能是C、B、A
這里暫且先不擴展如何對商品列表進行智能排序,如果需要完整的個性化商品推薦,涉及很多東西,后面有經(jīng)驗在拿來分享。
我們就這個簡單的 case,一開始最直接的想法就是加個排序列,建索引的時候?qū)⑴判蛑涤嬎愫弥苯訉懭?。后來分析了下原來索?index) 結(jié)構(gòu)不是這種笛卡爾積的排列,所以在短時間內(nèi)很難立馬上線,需要新建 index結(jié)構(gòu)。
后來通過討論用影響評分的方法來解決,可以節(jié)省時間快速上線。
ES query DSL支持很多種類型的查詢,結(jié)果的排序如果沒有特殊聲明 sort field則是根據(jù)es打分(score)來排序的,score分值越高排序越靠前。
ES score計算比較復(fù)雜,涉及到 TF(詞頻)/IDF(逆向文檔頻率)、罕見詞、匹配文檔長度、權(quán)重 boost向量空間模型等,不過 ES提供了幾種封裝好的評分插件供使用。
function_score 查詢來讓我們根據(jù)業(yè)務(wù)場景改變文檔評分方法,根據(jù)業(yè)務(wù)場景我們需要完全控制score 生成的邏輯,所以我們選擇script_score方式。
script_score
如果需求超出以上范圍時,用自定義腳本可以完全控制評分計算,實現(xiàn)所需邏輯。
(參考:https://www.elastic.co/guide/cn/elasticsearch/guide/current/function-score-query.html)
腳本默認是 groovy,當(dāng)然也可以根據(jù)需要使用其他腳本語言,我們來看下實現(xiàn)。
script.inline: on
script.enfine.groovy.inline.aggs: on
script.indexed: on
script.file: on
首先在 es.yml 配置中打開腳本支持相關(guān)選項。
{
"query": {
"function_score": {
"query": {
"bool": {
"should": [
{
"match": {
"productName": "英語"
}
}
]
}
},
"score_mode": "first",
"script_score": {
"lang": "groovy",
"params": {
"couponPrice": 100
},
"script": "def deduct = couponPrice - doc['unitCost'].value.toFloat(); if (deduct > 0) {return 10000 + deduct;}else if(deduct==0 || (deduct<1 && deduct>0)){return 20000;}else{return doc['unitCost'].value.toFloat()-couponPrice;}"
},
"boost_mode": "replace"
}
},
"from": 0,
"size": 100
}
查詢條件可以任意,關(guān)鍵是 __script_score 對象,script 是需要ES__ 腳本引擎執(zhí)行的腳本代碼。
一個比較重要的選項 boost_mode ,boost_mode是控制整個 document的評分方式,這里我們選擇替代(replace)默認計算好的評分。
這里面的排序有一個小技巧,如何將負數(shù)排序在前面,正數(shù)排序在后面,還有抵扣后是0的處理。
def deduct = couponPrice - doc['unitCost'].value.toFloat();
if (deduct > 0) {
return 10000 + deduct;
}else if(deduct==0 || (deduct<1 && deduct>0)){
return 20000;
}else{
return doc['unitCost'].value.toFloat()-couponPrice;
}
通過 couponPrice 變量表示優(yōu)惠券面值金額,如果當(dāng)前商品抵扣完是負數(shù)說明需要排序在前面,那么如何和抵扣完正數(shù)分開尼,這里可以取一個稍微大點的值加上抵扣后的負值,這樣把負值轉(zhuǎn)換成正數(shù)自然就排序在前面。
抵扣后等于0的或者小于1大于0的值也是可以優(yōu)先安排在前面,當(dāng)然這里還是不夠靈活的,最好的方式是根據(jù)當(dāng)前面值、商品價格動態(tài)計算才準確。
最后就是抵扣完需要用戶在額外支付的排在最后面,直接取需要額外支付的金額數(shù)值作為排序。
通過 ES 評分我們能做很多事情,這個case只是一個簡單的場景。
作者:王清培 (滬江集團資深架構(gòu)師)