Day11_搜索服务
1 课程搜索需求分析
1.1 需求分析
1、根据分类搜索课程信息。
2、根据关键字搜索课程信息,搜索方式为全文检索,关键字需要匹配课程的名称、课程内容。
3、根据难度等级搜索课程。
4、搜索结点分页显示。
1.2 搜索流程
1、课程管理服务将数据写到MySQL数据库
2、使用Logstash将MySQL数据库中的数据写到ES的索引库
3、用户在前端搜索课程信息,请求到搜索服务
4、搜索服务请求ES搜索课程信息
2 全文检索技术研究
参考链接:https://www.cnblogs.com/artwalker/p/13553888.html
3 课程索引
3.1 技术方案
如何维护课程索引信息?
1、当课程向MySQL添加后同时将课程信息添加到索引库。
采用Logstach实现,Logstach会从MySQL中将数据采集到ES索引库。
2、当课程在MySQL更新信息后同时更新该课程在索引库的信息。
采用Logstach实现。
3、当课程在MySQL删除后同时将该课程从索引库删除。
手工写程序实现,在删除课程后将索引库中该课程信息删除。
3.2 准备课程索引信息
课程发布成功在MySQL数据库存储课程发布信息,此信息作为课程索引信息。
3.2.1创建课程发布表
课程信息分布在course_base、course_pic等不同的表中。
课程发布成功为了方便进行索引将这几张表的数据合并在一张表中,作为课程发布信息。
course_pub表:
3.2.2 创建课程发布表模型
在课程管理服务创建模型:
package com.xuecheng.framework.domain.course;
import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* Created by admin on 2018/2/10.
*/
@Data
@ToString
@Entity
@Table(name="course_pub")
@GenericGenerator(name = "jpa-assigned", strategy = "assigned")
public class CoursePub implements Serializable {
private static final long serialVersionUID = -916357110051689487L;
@Id
@GeneratedValue(generator = "jpa-assigned")
@Column(length = 32)
private String id;
private String name;
private String users;
private String mt;
private String st;
private String grade;
private String studymodel;
private String teachmode;
private String description;
private String pic;//图片
private Date timestamp;//时间戳
private String charge;
private String valid;
private String qq;
private Float price;
private Float price_old;
private String expires;
private String teachplan;//课程计划
@Column(name="pub_time")
private String pubTime;//课程发布时间
}
3.2.3 修改课程发布
在课程管理服务定义dao:
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.CoursePub;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by Administrator.
*/
public interface CoursePubRepository extends JpaRepository<CoursePub,String> {
}
- 修改课程发布service
//保存CoursePub
public CoursePub saveCoursePub(String id, CoursePub coursePub) {
if (StringUtils.isNotEmpty(id)) {
ExceptionCast.cast(CourseCode.COURSE_PUBLISH_COURSEIDISNULL);
}
CoursePub coursePubNew = null;
Optional<CoursePub> coursePubOptional = coursePubRepository.findById(id);
if (coursePubOptional.isPresent()) {
coursePubNew = coursePubOptional.get();
}
if (coursePubNew == null) {
coursePubNew = new CoursePub();
}
BeanUtils.copyProperties(coursePub, coursePubNew);
//设置主键
coursePubNew.setId(id);
//更新时间戳为最新时间
coursePub.setTimestamp(new Date());
//发布时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY‐MM‐dd HH:mm:ss");
String date = simpleDateFormat.format(new Date());
coursePub.setPubTime(date);
coursePubRepository.save(coursePub);
return coursePub;
}
//创建coursePub对象
private CoursePub createCoursePub(String id) {
CoursePub coursePub = new CoursePub();
coursePub.setId(id);
//基础信息
Optional<CourseBase> courseBaseOptional = courseBaseRepository.findById(id);
if (courseBaseOptional == null) {
CourseBase courseBase = courseBaseOptional.get();
BeanUtils.copyProperties(courseBase, coursePub);
}
//查询课程图片
Optional<CoursePic> picOptional = coursePicRepository.findById(id);
if (picOptional.isPresent()) {
CoursePic coursePic = picOptional.get();
BeanUtils.copyProperties(coursePic, coursePub);
}
//课程营销信息
Optional<CourseMarket> marketOptional = courseMarketRepository.findById(id);
if (marketOptional.isPresent()) {
CourseMarket courseMarket = marketOptional.get();
BeanUtils.copyProperties(courseMarket, coursePub);
}
//课程计划
TeachplanNode teachplanNode = teachplanMapper.selectList(id);
//将课程计划转成json
String teachplanString = JSON.toJSONString(teachplanNode);
coursePub.setTeachplan(teachplanString);
return coursePub;
}
修改课程发布方法,添加调用saveCoursePub方法的代码,添加后的代码如下:
//课程发布
@Transactional
public CoursePublishResult publish(String courseId) {
//创建课程索引
//创建课程索引信息
CoursePub coursePub = createCoursePub(courseId);
//向数据库保存课程索引信息
CoursePub newCoursePub = saveCoursePub(courseId, coursePub);
if (newCoursePub == null) {
//创建课程索引信息失败
ExceptionCast.cast(CourseCode.COURSE_PUBLISH_CREATE_INDEX_ERROR);
}
//课程信息
CourseBase one = this.findCourseBaseById(courseId);
//发布课程详情页面
CmsPostPageResult cmsPostPageResult = publish_page(courseId);
if (!cmsPostPageResult.isSuccess()) {
ExceptionCast.cast(CommonCode.FAIL);
}
//更新课程状态
CourseBase courseBase = saveCoursePubState(courseId);
//课程索引...
//课程缓存...
//页面url
String pageUrl = cmsPostPageResult.getPageUrl();
return new CoursePublishResult(CommonCode.SUCCESS, pageUrl);
}
3.3 搭建ES环境
3.3.1 ES安装
开发环境使用ES单机环境,启动ES服务端。
注意:旧的ES环境,可以删除elasticsearch-6.2.1\data\nodes目录以完全清除ES环境。
安装elasticsearch-head并启动。
3.3.2 创建索引库
创建索引库
创建xc_course索引库,一个分片,0个副本。
3.3.3 创建映射
POST http://localhost:9200/xc_course/doc/_mapping
{
"properties": {
"description": {
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"type": "text"
},
"grade": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"mt": {
"type": "keyword"
},
"name": {
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"type": "text"
},
"users": {
"index": false,
"type": "text"
},
"charge": {
"type": "keyword"
},
"valid": {
"type": "keyword"
},
"pic": {
"index": false,
"type": "keyword"
},
"qq": {
"index": false,
"type": "keyword"
},
"price": {
"type": "float"
},
"price_old": {
"type": "float"
},
"st": {
"type": "keyword"
},
"status": {
"type": "keyword"
},
"studymodel": {
"type": "keyword"
},
"teachmode": {
"type": "keyword"
},
"teachplan": {
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"type": "text"
},
"expires": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss"
},
"pub_time": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss"
},
"start_time": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss"
},
"end_time": {
"type": "date",
"format": "yyyy‐MM‐dd HH:mm:ss"
}
}
}
3.4 Logstash创建索引
Logstash是ES下的一款开源软件,它能够同时从多个来源采集数据、转换数据,然后将数据发送到Eleasticsearch 中创建索引。
本项目使用Logstash将MySQL中的数据采用到ES索引中。
3.4.1 下载Logstash
下载Logstash6.2.1版本,和本项目使用的Elasticsearch6.2.1版本一致。
解压:
3.4.2 安装logstash-input-jdbc
logstash-input-jdbc 是ruby开发的,先下载ruby并安装
下载地址: https://rubyinstaller.org/downloads/
下载2.5版本即可
安装完成查看是否安装成功
Logstash5.x以上版本本身自带有logstash-input-jdbc,6.x版本本身不带logstash-input-jdbc插件,需要手动安装
本人MacOS,这里终端命令是./logstash-plugin install logstash-input-jdbc,但是官网下载好的安装包已经包括了logstash-input-jdbc插件
安装成功后我们可以在logstash根目录下的以下目录查看对应的插件版本
解压资料里提供的logstash-6.2.1.zip,此logstash中已集成了logstash-input-jdbc插件。
3.4.3 创建模板文件
Logstash的工作是从MySQL中读取数据,向ES中创建索引,这里需要提前创建mapping的模板文件以便logstash 使用。
在logstach的config目录创建xc_course_template.json,内容如下:
本教程的xc_course_template.json目录是:/logstash-6.2.1/config/xc_course_template.json
{
"mappings" : {
"doc" : {
"properties" : {
"charge" : {
"type" : "keyword"
},
"description" : {
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart",
"type" : "text"
},
"end_time" : {
"format" : "yyyy-MM-dd HH:mm:ss",
"type" : "date"
},
"expires" : {
"format" : "yyyy-MM-dd HH:mm:ss",
"type" : "date"
},
"grade" : {
"type" : "keyword"
},
"id" : {
"type" : "keyword"
},
"mt" : {
"type" : "keyword"
},
"name" : {
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart",
"type" : "text"
},
"pic" : {
"index" : false,
"type" : "keyword"
},
"price" : {
"type" : "float"
},
"price_old" : {
"type" : "float"
},
"pub_time" : {
"format" : "yyyy-MM-dd HH:mm:ss",
"type" : "date"
},
"qq" : {
"index" : false,
"type" : "keyword"
},
"st" : {
"type" : "keyword"
},
"start_time" : {
"format" : "yyyy-MM-dd HH:mm:ss",
"type" : "date"
},
"status" : {
"type" : "keyword"
},
"studymodel" : {
"type" : "keyword"
},
"teachmode" : {
"type" : "keyword"
},
"teachplan" : {
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart",
"type" : "text"
},
"users" : {
"index" : false,
"type" : "text"
},
"valid" : {
"type" : "keyword"
}
}
}
},
"template" : "xc_course"
}
3.4.4 配置mysql.conf
在logstash的config目录下配置mysql.conf文件供logstash使用,logstash会根据mysql.conf文件的配置的地址从 MySQL中读取数据向ES中写入索引。
参考链接:https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html
配置输入数据源和输出数据源。
input {
stdin {}
jdbc {
jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/xc_course?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"
# the user we wish to excute our statement as
jdbc_user => "root"
jdbc_password => "root"
# the path to our downloaded jdbc driver
jdbc_driver_library => "/Users/XinxingWang/Development/elasticsearchmac/logstash-6.2.1/vendor/mysql-connector-java-8.0.16.jar"
# the name of the driver class for mysql
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
#要执行的sql文件
#statement_filepath => "/conf/course.sql"
statement => "select * from course_pub where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)"
#定时配置
schedule => "* * * * *"
record_last_run => true
last_run_metadata_path => "/Users/XinxingWang/Development/elasticsearchmac/logstash-6.2.1/config/logstash_metadata"
}
}
output {
elasticsearch {
#ES的ip地址和端口
hosts => "127.0.0.1:9200"
#hosts => [
# "localhost:9200",
# "localhost:9202",
# "localhost:9203"
# ]
#ES索引库名称
index => "xc_course"
document_id => "%{id}"
document_type => "doc"
template =>"/Users/XinxingWang/Development/elasticsearchmac/logstash-6.2.1/config/xc_course_template.json"
template_name =>"xc_course"
template_overwrite =>"true"
}
stdout {
#日志输出
codec => json_lines
}
}
说明:
1、ES采用UTC时区问题
ES采用UTC 时区,比北京时间早8小时,所以ES读取数据时让最后更新时间加8小时
where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)
2、logstash每个执行完成会在/Users/XinxingWang/Development/elasticsearchmac/logstash-6.2.1/config/logstash_metadata记录执行时间,下次以此时间为基准进行增量同步数据到索引库。
3.4.5 测试
启动logstash :
./logstash ‐f ../config/mysql.conf
此时系统报错
Unrecognized VM option 'UseParNewGC'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.[ERROR] 2020-08-24 16:59:52.544 [main] Logstash - java.lang.IllegalStateException: org.jruby.exceptions.RaiseException: (SystemExit) exit
之后下载jdk8-171版本,安装ruby@2.5,遇到mac系统安全设置允许软件打开,之后还是不好使,但是之后又试了一下,不知道怎么就好使了
之后报错,连接不上数据库
Unable to connect to database. Tried 1 times {:error_message=>"Java::ComMysqlJdbcExceptionsJdbc4::CommunicationsException:
为了解决这个错误,试了很多办法,最后发现是mysql-connector-java版本不兼容,本人MySql版本为5.7.29,无法使用mysql-connector-java-5.x版本,使用mysql-connector-java-8.0.16,logstash运行正常
之后运行起来又出现:Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class is com.mysql.cj.jdbc.Driver. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
之后发现不能从数据库中查出来数据
原因是MySQL中的course_pub表timestamp列的数据是2018年,倒是sql语句条件始终不成立(select * from course_pub where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)),修改数据为2020年xx月xx日,要比当前时间大。
之后能查到数据了,但是ES无法同步,并logstash报错
org.elasticsearch.index.mapper.MapperParsingException: failed to parse pub_time错误
解决参考链接:https://blog.csdn.net/weixin_43333483/article/details/100600328
数据库pub_time字段的类型为varcha(32)
ES创建的映射类型是date
把创建的映射改成text类型就不报错了,然后查看ES发现有数据了
但是像start_time数据库类型是varchar(32),映射类型也是date,但是就能够解析,不知道为什么pub_time的映射类型不能是date类型??
修改course_pub中的数据,并且修改timestamp为当前时间,观察Logstash日志是否读取到要索引的数据。
最后用head登录ES查看索引文档内容是否修改。
更改数据库后:
4 课程搜索
4.1 需求分析
1、根据分类搜索课程信息。
2、根据关键字搜索课程信息,搜索方式为全文检索,关键字需要匹配课程的名称、课程内容。
3、根据难度等级搜索课程。
4、搜索结点分页显示。
技术分析:
1、根据关键字搜索,采用MultiMatchQuery,搜索name、description、teachplan。
2、根据分类、课程等级搜索采用过虑器实现。
3、分页查询。
4、高亮显示。
4.2 创建搜索服务工程
1)完善xc-service-search工程
2)配置
1、配置appliction.yml
server:
port: ${port:40100}
spring:
application:
name: xc-search-service
xuecheng:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔
course:
index: xc_course
type: doc
2、配置RestHighLevelClient和RestClient
package com.xuecheng.search.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Administrator
* @version 1.0
**/
@Configuration
public class ElasticsearchConfig {
@Value("${xuecheng.elasticsearch.hostlist}")
private String hostlist;
@Bean
public RestHighLevelClient restHighLevelClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i++){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
//创建RestHighLevelClient客户端
return new RestHighLevelClient(RestClient.builder(httpHostArray));
}
//项目主要使用RestHighLevelClient,对于低级的客户端暂时不用
@Bean
public RestClient restClient(){
//解析hostlist配置信息
String[] split = hostlist.split(",");
//创建HttpHost数组,其中存放es主机和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for(int i=0;i<split.length;i++){
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
return RestClient.builder(httpHostArray).build();
}
}
4.3 API
package com.xuecheng.api.course;
import com.xuecheng.framework.domain.course.CoursePub;
import com.xuecheng.framework.domain.search.CourseSearchParam;
import com.xuecheng.framework.model.response.QueryResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
/**
* @author HackerStar
* @create 2020-08-25 11:42
*/
@Api(value = "课程搜索", description = "课程搜索", tags = {"课程搜索"})
public interface EsCourseControllerApi {
@ApiOperation("课程搜索")
public QueryResponseResult<CoursePub> list(int page, int size, CourseSearchParam courseSearchParam) throws IOException;
}
此时,QueryResponseResult类并没有加上范型,所以到QueryResponseResult类中加上CoursePub范型。
package com.xuecheng.framework.model.response;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class QueryResponseResult<CoursePub> extends ResponseResult {
QueryResult queryResult;
public QueryResponseResult(ResultCode resultCode,QueryResult queryResult){
super(resultCode);
this.queryResult = queryResult;
}
}
4.4 Service
1)在appliction.yml中配置source_field
xuecheng:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔
course:
index: xc_course
type: doc
source_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_ time,start_time,end_time
2)service完整代码如下
package com.xuecheng.search.service;
import com.xuecheng.framework.domain.course.CoursePub;
import com.xuecheng.framework.domain.search.CourseSearchParam;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.framework.model.response.QueryResult;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author HackerStar
* @create 2020-08-25 11:47
*/
@Service
public class EsCourseService {
private static final Logger LOGGER = LoggerFactory.getLogger(EsCourseService.class);
@Value("${xuecheng.elasticsearch.course.index}")
private String es_index;
@Value("${xuecheng.elasticsearch.course.type}")
private String es_type;
@Value("${xuecheng.elasticsearch.course.source_field}")
private String source_field;
@Autowired
RestHighLevelClient restHighLevelClient;
public QueryResponseResult<CoursePub> list(int page, int size, CourseSearchParam courseSearchParam) {
//设置索引
SearchRequest searchRequest = new SearchRequest(es_index);
//设置类型
searchRequest.types(es_type);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//source源字段过滤
String[] source_fields = source_field.split(",");
searchSourceBuilder.fetchSource(source_fields, new String[]{});
//关键字
if (StringUtils.isNotEmpty(courseSearchParam.getKeyword())) {
//匹配关键字
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(courseSearchParam.getKeyword(), "name", "teachplan", "description");
//设置匹配占比
multiMatchQueryBuilder.minimumShouldMatch("70%");
//提升另个字段的Boost值
multiMatchQueryBuilder.field("name", 10);
boolQueryBuilder.must(multiMatchQueryBuilder);
}
//布尔查询
searchSourceBuilder.query(boolQueryBuilder);
//请求搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
try {
searchResponse = restHighLevelClient.search(searchRequest);
} catch (IOException e) {
e.printStackTrace();
LOGGER.error("xuecheng search error..{}", e.getMessage());
return new QueryResponseResult(CommonCode.SUCCESS, new QueryResult<CoursePub>());
}
//结果集处理
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
//记录总数
long totalHits = hits.getTotalHits();
//数据列表
List<CoursePub> list = new ArrayList<>();
for (SearchHit hit : searchHits) {
CoursePub coursePub = new CoursePub();
//取出source
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//取出名称
String name = (String) sourceAsMap.get("name");
coursePub.setName(name);
//图片
String pic = (String) sourceAsMap.get("pic");
coursePub.setPic(pic);
//价格
Float price = null;
try {
if (sourceAsMap.get("price") != null) {
price = Float.parseFloat((String) sourceAsMap.get("price"));
}
} catch (Exception e) {
e.printStackTrace();
}
coursePub.setPrice(price);
Float price_old = null;
try {
if (sourceAsMap.get("price_old") != null) {
price_old = Float.parseFloat((String) sourceAsMap.get("price_old"));
}
} catch (Exception e) {
e.printStackTrace();
}
coursePub.setPrice_old(price_old);
list.add(coursePub);
}
QueryResult<CoursePub> queryResult = new QueryResult<>();
queryResult.setList(list);
queryResult.setTotal(totalHits);
QueryResponseResult<CoursePub> coursePubQueryResponseResult = new QueryResponseResult<CoursePub>(CommonCode.SUCCESS, queryResult);
return coursePubQueryResponseResult;
}
}
4.5 Controller
package com.xuecheng.search.controller;
import com.xuecheng.api.course.EsCourseControllerApi;
import com.xuecheng.framework.domain.course.CoursePub;
import com.xuecheng.framework.domain.search.CourseSearchParam;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.search.service.EsCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
/**
* @author HackerStar
* @create 2020-08-25 14:38
*/
@RestController
@RequestMapping("/search/course")
public class EsCourseController implements EsCourseControllerApi {
@Autowired
EsCourseService esCourseService;
@Override
@GetMapping(value = "/list/{page}/{size}")
public QueryResponseResult<CoursePub> list(@PathVariable("page") int page, @PathVariable("size") int size, CourseSearchParam courseSearchParam) throws IOException {
return esCourseService.list(page, size, courseSearchParam);
}
}
4.5 测试
使用postman测试/search/course
GET http://localhost:40100/search/course/list/1/10?keyword=java开发