基于Elasticsearch构建外卖试吃活动参与记录的全文检索与行为分析
在“吃喝不愁”App中,用户参与霸王餐、试吃活动的行为数据(如报名时间、门店偏好、评论内容、签到状态)需支持高效检索与多维分析。传统关系型数据库在全文搜索和聚合性能上存在瓶颈。本文基于 Elasticsearch 8.x 构建试吃活动参与记录的索引模型,并通过 Java High Level REST Client 实现数据写入、关键词检索及用户行为分析。定义使用ik中文分词器提升中文检索准确率。
·
基于Elasticsearch构建外卖试吃活动参与记录的全文检索与行为分析
在“吃喝不愁”App中,用户参与霸王餐、试吃活动的行为数据(如报名时间、门店偏好、评论内容、签到状态)需支持高效检索与多维分析。传统关系型数据库在全文搜索和聚合性能上存在瓶颈。本文基于 Elasticsearch 8.x 构建试吃活动参与记录的索引模型,并通过 Java High Level REST Client 实现数据写入、关键词检索及用户行为分析。
1. 索引结构设计
定义 free_meal_participation 索引,关键字段如下:
PUT /free_meal_participation
{
"mappings": {
"properties": {
"userId": { "type": "keyword" },
"activityId": { "type": "keyword" },
"shopName": { "type": "text", "analyzer": "ik_max_word" },
"dishKeywords": { "type": "text", "analyzer": "ik_smart" },
"userComment": { "type": "text", "analyzer": "ik_max_word" },
"status": { "type": "keyword" },
"applyTime": { "type": "date" },
"checkInTime": { "type": "date" },
"location": { "type": "geo_point" }
}
}
}
使用 ik 中文分词器提升中文检索准确率。
2. Java实体类与索引映射
package baodanbao.com.cn.elasticsearch.model;
import co.elastic.clients.elasticsearch._types.mapping.*;
import java.time.Instant;
public class FreeMealParticipation {
private String userId;
private String activityId;
private String shopName;
private String dishKeywords;
private String userComment;
private String status; // APPLIED, CHECKED_IN, COMPLETED, CANCELLED
private Instant applyTime;
private Instant checkInTime;
private double lat;
private double lon;
// getters and setters
}
3. 初始化Elasticsearch客户端
package baodanbao.com.cn.elasticsearch.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticsearchConfig {
@Bean
public ElasticsearchClient elasticsearchClient() {
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200)
).build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
return new ElasticsearchClient(transport);
}
}
4. 参与记录写入Elasticsearch
package baodanbao.com.cn.elasticsearch.service;
import baodanbao.com.cn.elasticsearch.model.FreeMealParticipation;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.UUID;
@Service
public class ParticipationIndexService {
@Autowired
private ElasticsearchClient client;
public String indexParticipation(FreeMealParticipation record) throws IOException {
String id = UUID.randomUUID().toString();
IndexRequest<FreeMealParticipation> request = IndexRequest.of(i -> i
.index("free_meal_participation")
.id(id)
.document(record)
);
IndexResponse response = client.index(request);
return response.id();
}
}
5. 全文检索:按关键词查试吃记录
支持按门店名、菜品关键词或用户评论模糊搜索:
public SearchResponse<FreeMealParticipation> searchByKeyword(String keyword) throws IOException {
Query query = Query.of(q -> q
.multi_match(mm -> mm
.query(keyword)
.fields("shopName", "dishKeywords", "userComment")
)
);
SearchRequest request = SearchRequest.of(s -> s
.index("free_meal_participation")
.query(query)
.size(20)
);
return client.search(request, FreeMealParticipation.class);
}
6. 行为分析:聚合统计
按状态统计参与人数
public Aggregate getStateAggregation() throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("free_meal_participation")
.size(0)
.aggregations("by_status", a -> a
.terms(t -> t.field("status").size(10))
)
);
SearchResponse<Void> response = client.search(request, Void.class);
return response.aggregations().get("by_status");
}
按小时分析报名高峰
public Aggregate getApplyHourHistogram() throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index("free_meal_participation")
.size(0)
.aggregations("apply_by_hour", a -> a
.date_histogram(dh -> dh
.field("applyTime")
.calendarInterval(DateInterval.Hour)
)
)
);
SearchResponse<Void> response = client.search(request, Void.class);
return response.aggregations().get("apply_by_hour");
}
地理围栏:查找某区域内的试吃用户
public SearchResponse<FreeMealParticipation> searchNear(double lat, double lon, String distance) throws IOException {
GeoPoint center = new GeoPoint.Builder().lat(lat).lon(lon).build();
Query geoQuery = Query.of(q -> q
.geo_distance(gd -> gd
.field("location")
.location(center)
.distance(distance) // e.g., "5km"
)
);
return client.search(b -> b
.index("free_meal_participation")
.query(geoQuery)
.size(50), FreeMealParticipation.class);
}
7. 高效更新与删除
当用户取消参与时,需更新状态:
public void updateParticipationStatus(String docId, String newStatus) throws IOException {
Map<String, Object> updateFields = Map.of("status", newStatus);
client.update(u -> u
.index("free_meal_participation")
.id(docId)
.doc(updateFields),
Object.class
);
}
8. 性能与运维建议
- 为
userId、activityId建立keyword类型,支持精确过滤。 - 使用 Index Lifecycle Management (ILM) 自动滚动索引,避免单索引过大。
- 对高频查询字段开启
doc_values: true(默认已开),加速聚合。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
更多推荐
所有评论(0)