酒店数据全文检索练习(3)-定价排名广告置顶
前言
上一节(酒店数据全文检索练习(2)-按距离排序实现离我最近功能)的学习我们实现了按照经纬度坐标实现距离排序,以及价格排序的功能,这一节我们学习广告置顶,竞价排名功能。
我们用百度的时候,有没有看到过这种情况,我们搜索的结果中,排在前面的是广告,而且根据不同公司交的钱不同,你排名的顺序就不一样,如下图。
那么我们如何用Elasticsearch实现这个功能呢?其实用es的相关性算分查询就能实现,相关原理可以去看我之前写的文章elasticsearch之DSL-查询。
实现步骤分析
我们要实现上述功能,首先要区分那些文档是广告,那些文档不是,所以我们需要在是广告的文档上打上标记,表明是广告,然后给广告不一样的打分权重,让他排到前面。我们用isAd
字段进行标识,首先在HotelDoc
中增加字段
private Boolean isAD;
然后选取一些文档,将isAd
标记为true
POST /hotel/_update/60214
{
"doc": {
"isAD": true
}
}
POST /hotel/_update/396189
{
"doc": {
"isAD": true
}
}
POST /hotel/_update/609023
{
"doc": {
"isAD": true
}
}
我随便选了三个数据标识为isAd
为true
然后修改查询方法
@Service
@RequiredArgsConstructor
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
private final RestHighLevelClient client;
@Override
public ResponseObject list(RequestParams requestParams) {
SearchRequest request = new SearchRequest("hotel");
//构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//构建查询语句
buildBasicQuery(requestParams, boolQuery);
//分页
int page = requestParams.getPage();
int size = requestParams.getSize();
request.source().from((page - 1) * size).size(size);
// 排序
String location = requestParams.getLocation();
if (StringUtils.isNotEmpty(location)) {
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS) //单位:km
);
}
// 价格和评价分数排序
String sortBy = requestParams.getSortBy();
if (StringUtils.isNotEmpty(sortBy)) {
if ("score".equals(sortBy)) {
request.source().sort("score", SortOrder.ASC);
} else if ("price".equals(sortBy)) {
request.source().sort("price", SortOrder.ASC);
}
}
//算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
// 原始查询,参与算分的查询
boolQuery,
// FunctionScore数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件 只有广告需要处理权重
QueryBuilders.termQuery("isAD", true),
// 权重处理 直接乘10
ScoreFunctionBuilders.weightFactorFunction(10)
)
}
);
request.source().query(functionScoreQuery);
ResponseObject responseObject = new ResponseObject();
List<HotelDoc> hotelDocList = new ArrayList<>();
try {
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
long total = hits.getTotalHits().value;
responseObject.setTotal(total);
SearchHit[] searchHits = hits.getHits();
for (SearchHit searchHit : searchHits) {
HotelDoc hotelDoc = JSONObject.parseObject(searchHit.getSourceAsString(), HotelDoc.class);
//处理排序结果
Object[] sortValues = searchHit.getSortValues();
if (sortValues != null && sortValues.length != 0) {
hotelDoc.setDistance(sortValues[0]);
}
hotelDocList.add(hotelDoc);
}
responseObject.setHotels(hotelDocList);
} catch (IOException e) {
e.printStackTrace();
}
return responseObject;
}
重启项目,查看效果,我们发现刚刚标记的三个都被置顶了,而且前端根据isAD
标识,在右上角添加了广告的图标
竞价排名
现在这三个都是一样的,可以按照广告费的高低,实现不用的排名吗?
我们可以使用field_value_factor
例如:使用 likes
点赞数字段进行综合搜索:
{
"query": {
"function_score": {
"query": { "match": { "message": "elasticsearch" } },
"field_value_factor": {
"field": "likes",
"factor": 1.2,
"missing": 1,
"modifier": "log1p"
}
}
}
}
说明:
field
: 参与计算的字段。factor
: 乘积因子,默认为 1 ,将会与field
的字段值相乘。missing
: 如果field
字段不存在则使用missing
指定的缺省值。modifier
: 计算函数,为了避免分数相差过大,用于平滑分数,可以是以下之一:none
: 不处理,默认log
:log(factor * field_value)
log1p
:log(1 + factor * field_value)
log2p
:log(2 + factor * field_value)
ln
:ln(factor * field_value)
ln1p
:ln(1 + factor * field_value)
ln2p
:ln(2 + factor * field_value)
square
: 平方,(factor * field_value)^2
sqrt
: 开方,sqrt(factor * field_value)
reciprocal
: 求倒数,1/(factor * field_value)
假设某个匹配的文档的点赞数是 1000 ,那么例子中其打分函数生成的分数就是 log(1 + 1.2 * 1000)
,最终的分数是原来的 query 分数与此打分函数分数相差的结果。
代码实现
我们新增一个字段adCost
用来表示广告费
POST /hotel/_update/60214
{
"doc": {
"adCost": 1000
}
}
POST /hotel/_update/396189
{
"doc": {
"adCost": 2000
}
}
POST /hotel/_update/609023
{
"doc": {
"adCost": 3000
}
}
HotelDoc
中也新增
private Integer adCost;
修改算分逻辑
@Override
public ResponseObject list(RequestParams requestParams) {
SearchRequest request = new SearchRequest("hotel");
//构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//构建查询语句
buildBasicQuery(requestParams, boolQuery);
//分页
int page = requestParams.getPage();
int size = requestParams.getSize();
request.source().from((page - 1) * size).size(size);
// 排序
String location = requestParams.getLocation();
if (StringUtils.isNotEmpty(location)) {
request.source().sort(SortBuilders
.geoDistanceSort("location", new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS) //单位:km
);
}
// 价格和评价分数排序
String sortBy = requestParams.getSortBy();
if (StringUtils.isNotEmpty(sortBy)) {
if ("score".equals(sortBy)) {
request.source().sort("score", SortOrder.ASC);
} else if ("price".equals(sortBy)) {
request.source().sort("price", SortOrder.ASC);
}
}
//算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
// 原始查询,参与算分的查询
boolQuery,
// FunctionScore数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件 只有广告需要处理权重
QueryBuilders.termQuery("isAD", true),
// 算分
ScoreFunctionBuilders.fieldValueFactorFunction("adCost")
.factor(1.2f)
.missing(1)
.modifier(FieldValueFactorFunction.Modifier.LOG1P)
)
}
);
request.source().query(functionScoreQuery);
ResponseObject responseObject = new ResponseObject();
List<HotelDoc> hotelDocList = new ArrayList<>();
try {
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
long total = hits.getTotalHits().value;
responseObject.setTotal(total);
SearchHit[] searchHits = hits.getHits();
for (SearchHit searchHit : searchHits) {
HotelDoc hotelDoc = JSONObject.parseObject(searchHit.getSourceAsString(), HotelDoc.class);
//处理排序结果
Object[] sortValues = searchHit.getSortValues();
if (sortValues != null && sortValues.length != 0) {
hotelDoc.setDistance(sortValues[0]);
}
hotelDocList.add(hotelDoc);
}
responseObject.setHotels(hotelDocList);
} catch (IOException e) {
e.printStackTrace();
}
return responseObject;
}
我们在前端代码上显示一下广告费用,这样方便我们观察
<div v-if="hotel.isAD && hotel.adCost != null">广告费:{{hotel.adCost}} 元</div>
效果展示
启动项目,我们看看效果
可以看到广告在最前面,而且广告费用越高,排名越靠前~
下一节我们讲酒店数据全文检索练习(4)-数据聚合,过滤条件
酒店数据全文检索练习(3)-定价排名广告置顶
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/1012