对于课程搜索页面中,需要完成下面的功能,如下:
课程搜索功能
课程搜索功能需求:
1,分页数据查询
2,根据关键字进行查询
3,根据课程分类和课程等级条件查询
根据关键字进行查询后的内容要高亮显示
在上面的查询条件需求中,需要使用DSL不同的查询方式进行实现。
1,关键字查询,要查询name和description中的数据,对此要使用MatchQuery查询方式来进行查询,查询的数据会先分词再查询。
2,课程等级和课程分类查询,要使用精确匹配,对此使用termQuery查询方式来查询,查询数据不会进行分词
3,优于是多个查询方式共同进行查询,对此要使用BooleanQuery来组合多个查询方式
4,对于课程等级和课程分类精确匹配查询,课程使用BooleanQuery查询中的过滤器来查询数据,这样可以提高查询效率
课程检索交互流程如下:
步骤描述:
1,前端输入检索参数,发起课程检索请求
2,课程搜索服务封装检索参数为ES检索条件,向ES服务发起检索请求
3,ES服务执行检索,并返回检索结果
4,课程检索服务封装ES检索结果为课程信息列表,最终返回给前端
1.2 课程检索接口定义
1.接口参数列表
根据前后端传入参数列表来定义接口
接口传入传出列表
2.传入传出参数封装类
- 传入参数封装
在 xc-api 工程的 com.xuecheng.api.search.model.qo
包下创建类,如下:
QueryCoursePubModel为课程检索参数
@ApiModel(value = "QueryCoursePubModel",description = "课程索引搜索条件查询对象")
public class QueryCoursePubModel {
@ApiModelProperty("查询关键字")
private String keyword;
@ApiModelProperty("课程二级分类")
private String mt;
@ApiModelProperty("课程三级分类")
private String st;
@ApiModelProperty("课程等级")
private String grade;
@ApiModelProperty("排序字段, 推荐/最新/热评")
private String sortFiled;
}
- 传出参数封装类
在 xc-api 工程的 com.xuecheng.api.search.model
包下创建类,如下:
CoursePubIndexDTO为检索返回的课程信息
@Data
@ApiModel(value="CoursePubIndexDTO", description="课程发布")
public class CoursePubIndexDTO implements Serializable {
@ApiModelProperty(value = "主键")
private Long indexId;
@ApiModelProperty(hidden = true)
private Long course_id;
@ApiModelProperty(value = "课程标识",example = "1")
public Long getCourseId() {
return course_id;
}
@ApiModelProperty(hidden = true)
private Long company_id;
@ApiModelProperty(value = "机构ID",example = "1")
public Long getCompanyId() {
return company_id;
}
@ApiModelProperty(hidden = true)
private String company_name;
@ApiModelProperty(value = "公司名称")
public String getCompanyName() {
return company_name;
}
@ApiModelProperty(value = "课程名称")
private String name;
@ApiModelProperty(value = "适用人群")
private String users;
@ApiModelProperty(value = "标签")
private String tags;
@ApiModelProperty(value = "大分类")
private String mt;
@ApiModelProperty(value = "大分类名称")
private String mtName;
@ApiModelProperty(value = "小分类")
private String st;
@ApiModelProperty(value = "小分类名称")
private String stName;
@ApiModelProperty(value = "课程等级")
private String grade;
@ApiModelProperty(value = "教育模式(common普通,record 录播,live直播等)")
private String teachmode;
@ApiModelProperty(value = "课程图片")
private String pic;
@ApiModelProperty(value = "课程介绍")
private String description;
@ApiModelProperty(value = "所有课程计划,json格式")
private String teachplan;
@ApiModelProperty(hidden = true)
private Date create_date;
@ApiModelProperty(value = "发布时间")
public Date getCreateDate() {
return create_date;
}
@ApiModelProperty(hidden = true)
private Date change_date;
@ApiModelProperty(value = "修改时间")
public Date getChangeDate() {
return change_date;
}
@ApiModelProperty(hidden = true)
private Integer is_latest;
@ApiModelProperty(value = "是否最新课程(1最新)")
public Integer getIsLatest() {
return is_latest;
}
@ApiModelProperty(hidden = true)
private Integer is_pub;
@ApiModelProperty(value = "是否发布(1发布 0取消发布)")
public Integer getIsPub() {
return is_pub;
}
@ApiModelProperty(value = "状态(1正常 0删除)")
private String status;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "课程营销数据")
private String market;
@ApiModelProperty(value = "收费规则,对应数据字典--203")
private String charge;
@ApiModelProperty(value = "现价")
private Float price;
@ApiModelProperty(value = "有效性,对应数据字典--204")
private String valid;
@ApiModelProperty(value = "学习人数")
private Long learners;
@ApiModelProperty(value = "课程评论数")
private Long comment_num;
}
1.3 接口实现
(1)服务层实现
接口定义:
在 xc-content-search-service 工程的 **com.xuecheng.search.service
**包下新增以下接口:
/**
* 课程搜索服务层
*/
public interface CoursePubSearchService {
/**
* 根据条件分页查询
* @param pageRequestParams {@link PageRequestParams} 分页数据封装对象
* @param queryModel {@link QueryCoursePubModel} 条件查询对象
* @return PageVO 分页封装数据
*/
PageVO<CoursePubIndexDTO> queryCoursePubIndex(PageRequestParams pageRequestParams, QueryCoursePubModel queryModel);
}
服务层实现:
在 xc-content-search-service 工程的 **com.xuecheng.search.service.impl
**包下新增以下实现类:
package com.xuecheng.search.service.impl;
import com.xuecheng.api.search.model.dto.CoursePubIndexDTO;
import com.xuecheng.api.search.model.qo.QueryCoursePubIndexModel;
import com.xuecheng.common.domain.page.PageRequestParams;
import com.xuecheng.common.domain.page.PageVO;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.JsonUtil;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.search.common.constant.ContentSearchErrorCode;
import com.xuecheng.search.service.CoursePubSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
/**
* 课程搜索服务实现层(es原始Api实现)
*/
@Slf4j
@Service
public class CoursePubSearchServiceImpl implements CoursePubSearchService {
@Value("${xuecheng.elasticsearch.course.index}")
private String indexName;
@Autowired
private RestHighLevelClient client;
/*
* 主方法的分析:
* 1.创建出SearchRequest
* 1.1 SearchRequest 设置索引库的名称
* 1.2 创建SearchSourceBuilder
* 分页
* 查询方式
* 高亮
* 排序(作业)
* 2.获得响应数据SearchResponse
* 通过Client方法Search方法来获得Response
* 3.解析结果数据并封装PageVO中
* 获得大Hits
* 从大Hits获得总条数
* 从大Hits获得小Htis数组
* 遍历小Hits
* 从小Hist中获得
* 文档id
* 文档的源数据_source
* 文档的高亮
* */
public PageVO<CoursePubIndexDTO> queryCoursePubIndex(PageRequestParams params,
QueryCoursePubModel model) {
PageVO<CoursePubIndexDTO> pageVO = null;
try {
// 1.创建出SearchRequest
SearchRequest request = getSearchRequest(params,model);
// 2.获得响应数据SearchResponse
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
// 3.解析结果数据并封装PageVO中
pageVO = parseResponse(searchResponse,params);
} catch (IOException e) {
log.error("课程搜索数据失败:{}", e.getMessage());
ExceptionCast.cast(ContentSearchErrorCode.E_150001);
}
return pageVO;
}
/*
* 解析结果数据并封装PageVO中
* */
private PageVO<CoursePubIndexDTO> parseResponse(SearchResponse searchResponse,PageRequestParams params) {
// 1.获得响应数据的大hits
SearchHits hits = searchResponse.getHits();
// 2.查询的总条数
long totalCount = hits.getTotalHits().value;
// 3.获得小hits
SearchHit[] hitsHits = hits.getHits();
ArrayList<CoursePubIndexDTO> list = new ArrayList<>();
// 4.遍历小hits封装数据到PageVO中
for (SearchHit hitsHit : hitsHits) {
// 获得文档的源数据内容
String id = hitsHit.getId();
String jsonString = hitsHit.getSourceAsString();
CoursePubIndexDTO dto = JsonUtil.jsonToObject(jsonString, CoursePubIndexDTO.class);
dto.setIndexId(new Long(id));
// 获得高亮数据
Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
if (!(ObjectUtils.isEmpty(highlightField))) {
Text[] fragments = highlightField.getFragments();
StringBuilder stringBuilder = new StringBuilder();
for (Text fragment : fragments) {
stringBuilder.append(fragment);
}
String highLightName = stringBuilder.toString();
dto.setName(highLightName);
}
list.add(dto);
}
PageVO<CoursePubIndexDTO> pageVO = new PageVO<>(list,totalCount,params.getPageNo(),params.getPageSize());
return pageVO;
}
/*
课程搜索功能需求:
1.分页数据查询
2.根据关键字进行查询
3.根据课程分类和课程等级条件查询
4.根据关键字进行查询后的内容要高亮显示。
* 创建出SearchRequest
*
*
* 1 SearchRequest 设置索引库的名称
2 创建SearchSourceBuilder
分页
查询方式
高亮
排序(作业)
3.构建查询方式
Boolean
must:
MultiMatchQuery
filter:
TermQuery
4.设置查询源数据对象
*
* */
private SearchRequest getSearchRequest(PageRequestParams params,
QueryCoursePubModel model) {
// 0.判断分页数据
if (params.getPageNo() < 1) {
params.setPageNo(PageRequestParams.DEFAULT_PAGE_NUM);
}
if (params.getPageSize() < 1) {
params.setPageSize(PageRequestParams.DEFAULT_PAGE_SIZE);
}
// 1.创建SearchRequest对象
SearchRequest request = new SearchRequest(indexName);
// 2.创建搜索源数据对象
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 2.1 设置分页数据
Long pageNo = params.getPageNo();
Integer pageSize = params.getPageSize();
int from = (pageNo.intValue() - 1) * pageSize;
sourceBuilder.from(from);
sourceBuilder.size(pageSize);
// 2.2 设置高亮数据
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<font color='red'><b>");
highlightBuilder.postTags("</b></font>");
highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
sourceBuilder.highlighter(highlightBuilder);
// 3.构建查询方式
/*
* 根据关键字进行查询
* 1.对关键字内容需要进行分词
* 2.需要对多个字段进行查询
*
* MultiMatchQuery
* 1.匹配查询--> 对查询的内容先分词后查询
* 2.可以这是对个字段
*
根据课程分类和课程等级条件查询
课程分类--> TermQuery
课程等级--> TermQuery
对于不分词的查询方式,我们可以使用过滤器来提高查询效率
上面查询是多种查询方式来构成:BooleanQuery可以对多种查询方式来一同查询
* */
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
String keyword = model.getKeyword();
if (StringUtil.isNotBlank(keyword)) {
MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "description")
.minimumShouldMatch("80%").field("name",3);
boolQueryBuilder.must(queryBuilder);
}
String grade = model.getGrade();
String mt = model.getMt();
String st = model.getSt();
if (StringUtil.isNotBlank(grade)) {
TermQueryBuilder gradeTerm = QueryBuilders.termQuery("grade", grade);
boolQueryBuilder.filter(gradeTerm);
}
if (StringUtil.isNotBlank(mt)) {
TermQueryBuilder mtTerm = QueryBuilders.termQuery("mt", mt);
boolQueryBuilder.filter(mtTerm);
}
if (StringUtil.isNotBlank(st)) {
TermQueryBuilder stTerm = QueryBuilders.termQuery("st", st);
boolQueryBuilder.filter(stTerm);
}
sourceBuilder.query(boolQueryBuilder);
// 将查询源数据对象存放到Request
request.source(sourceBuilder);
return request;
}
}
(2)Controller实现
CoursePubSearchController中实现课程检索接口
/**
* 课程搜索服务控制层
*/
@RestController
@RequestMapping
public class CoursePubSearchController implements CoursePubSearchApi {
@Resource
private CoursePubSearchService couresPubSearchService;
@PostMapping("course_index")
public PageVO<CoursePubIndexDTO> coursePubIndexByCondition(PageRequestParams pageParams, @RequestBody QueryCoursePubModel queryModel) {
PageVO<CoursePubIndexDTO> pageVO = couresPubSearchService.queryCoursePubIndex(pageParams, queryModel);
return pageVO;
}
}