1. ES服务搭建
前台页面很多场景下,都需要用到搜索,ElasticSearch作为分布式全文搜索引擎,使用restful api对文档操作,我们要集成ES,把它作为一个独立的服务!使其他需要集成搜索功能的服务模块,只需要通过feign向ES服务发起调用即可!
①:创建es服务模块:
hrm-es-parent
hrm-es-common
hrm-es-feign
hrm-es-server-2050
②:集成注册中心
③:集成配置中心
④:集成swagger
⑤:集成zuul网关
⑥:zuul整合swagger
2. ES与SpringBoot的集成
- 导入jar包
org.springframework.boot
spring-boot-starter-data-elasticsearch
- 创建配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: es-server:2050 #服务注册到注册中心的id
server:
port: 2050
#应用的名字
spring:
application:
name: es-server
data:
elasticsearch:
cluster-name: elasticsearch #集群的名字
cluster-nodes: 127.0.0.1:9300 #9200是图形界面端,9300代码端
ribbon: #ribbon超时
ReadTimeout: 30000
ConnectTimeout: 30000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 40000
- 创建一个类用来描述文档信息
@Document(indexName = "hrm",type = "course")
public class CourseDoc {
//文档的ID,同时也是数据的id
@Id
private Long id;
//标题
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String name;
//适用人群
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String users;
//课程类型ID
@Field(type = FieldType.Long)
private Long courseTypeId;
//等级名字
//@Field(type = FieldType.Keyword)
private String gradeName;
//课程等级
private Long gradeId;
//机构id
private Long tenantId;
//机构名字
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
private String tenantName;
//开课时间
private Date startTime;
//结课时间
private Date endTime;
//封面
private String pic;
//免费、收费
private String chargeName;
//qq
private String qq;
//价格
private Float price;
//原价
private Float priceOld;
//课程介绍
private String description;
//上线时间
private Date onlineDate = new Date();
//浏览数
private Integer viewCount;
//购买数
private Integer buyCount;
...
@Document(indexName = "hrm",type = "course"):描述文档信息,indexName:索引库,type:文档类型。
@Id:文档的ID,同时也是数据的id
@Field(type =FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word"):描述字段信息,FieldType.Text:做分词处理,FieldType.Keyword:不做分词处理,analyzer:使用什么分词器做分词,searchAnalyzer:使用什么分词器做查询!
- 创建一个接口继承ElasticsearchRepository,其中有对文档操作的所有方法!
@Repository
public interface CourseDocRepository extends ElasticsearchRepository {
}
- 使用SpringBoot的测试环境测试CRUD
@RunWith(SpringRunner.class)
@SpringBootTest
public class CourseDocTest {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Autowired
private CourseDocRepository courseDocRepository;
@Test
public void test(){
elasticsearchTemplate.createIndex(CourseDoc.class);
elasticsearchTemplate.putMapping(CourseDoc.class);
}
@Test
public void testAddDoc(){
for(int i = 0; i< 20 ; i++){
CourseDoc courseDoc = new CourseDoc();
courseDoc.setId(Long.valueOf(1+i));
if(i % 2 == 0){
courseDoc.setName("Java从入门到超神");
}else{
courseDoc.setName("PHP从入门到放弃");
}
if(i % 3 == 0){
courseDoc.setGradeName("神级");
}else{
courseDoc.setGradeName("低级");
}
courseDoc.setCourseTypeId(Long.valueOf(1+i));
courseDocRepository.save(courseDoc);
}
}
@Test
public void testGetDoc(){
Optional optional = courseDocRepository.findById(23l);
if(optional!=null){
CourseDoc courseDoc = optional.get();
System.out.println(courseDoc);
}
}
@Test
public void testGetAll(){
Iterator iterator = courseDocRepository.findAll().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void testUpdata(){
CourseDoc courseDoc = new CourseDoc();
courseDoc.setId(23l);
courseDoc.setName("JAVA");
courseDocRepository.save(courseDoc);
}
@Test
public void testDelete(){
courseDocRepository.deleteById(23l);
}
ElasticsearchTemplate是es的内置对象,可以用来创建索引库,和指定映射规则!
高级查询:
使用关键对象NativeSearchQueryBuilder构建查询规则!
@Test
public void testQuery(){
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
/**
* 高级查询分页排序: DSL查询+DSL过滤
* // 需求:查询 name中包含 “Java”的课程,
* // 并且课程分类 CourseTypeId 在 1 - 10
* //课程等级 GradeName为“神级”
* //查询第2页,每页2条
* //按照id倒排序
*/
//查询 name中包含 “Java”的课程
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(new MatchQueryBuilder("name", "java"));
builder.withQuery(boolQueryBuilder);
//并且课程分类 CourseTypeId 在 1 - 10
boolQueryBuilder.filter(new RangeQueryBuilder("courseTypeId").gte(1).lte(20));
//课程等级 GradeName为“神级”
boolQueryBuilder.filter(new TermQueryBuilder("gradeName", "神级"));
//按照id倒排序
builder.withSort(new FieldSortBuilder("id").order(SortOrder.DESC));
//查询第1页,每页5条
builder.withPageable(PageRequest.of(0, 5));
//执行查询
Page docs = courseDocRepository.search(builder.build());
System.out.println("总条数:"+docs.getTotalElements());
System.out.println("查询的页数:"+docs.getTotalPages());
docs.getContent().forEach(e-> System.out.println("======"+e));
Iterator iterator = docs.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
3. ES与Feign的集成实现课程的上线下线
上线场景:
前台用户点击课程上线,请求到达控制器,控制器执行业务!
- 根据传过来的课程id去数据库中查询出来
- 判断是否是下线状态
- 如果是下线状态通过feign调用es服务,将数据保存到es中
- 修改数据库中课程的状态码
下线场景:
前台用户点击课程下线,请求到达控制器,控制器执行业务!
- 根据传过来的课程id去数据库中查询出来
- 判断是否是上线状态
- 如果是上线状态通过feign调用es服务,将数据从es中删除
- 修改数据库中课程的状态码
1:controller
/**
* 课程上线
* @param id
* @return
*/
@PostMapping("/onLineCourse/{id}")
public AjaxResult onLineCourse(@PathVariable Long id){
try {
courseService.onLineCourse(id);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("课程上线失败!"+e.getMessage());
}
}
/**
*课程下线
* @param
* @return
*/
@PostMapping("/offLineCourse/{id}")
public AjaxResult offLineCourse(@PathVariable Long id){
try {
courseService.offLineCourse(id);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("课程下线失败!"+e.getMessage());
}
}
2:es与feign的集成
①:导包
org.springframework.cloud
spring-cloud-starter-openfeign
②:创建feign的controller,接收请求的入口!
@RestController
@RequestMapping("/es")
public class EScontroller {
@Autowired
private CourseDocRepository courseDocRepository;
@GetMapping("/get/{id}")
public AjaxResult deleteById(@PathVariable("id") Long id){
courseDocRepository.deleteById(id);
return AjaxResult.me();
}
@PostMapping("/save")
public AjaxResult save(@RequestBody CourseDoc courseDoc){
courseDocRepository.save(courseDoc);
return AjaxResult.me();
}
}
③:创建feign的接口,分发请求到controller的具体方法
@FeignClient(value = "es-server" ,fallbackFactory = ESFeignFallbackFactory.class)
public interface ESFeignClient{
@GetMapping("/es/get/{id}")
AjaxResult deleteById(@PathVariable("id") Long id);
@PostMapping("/es/save")
AjaxResult save(@RequestBody CourseDoc courseDoc);
}
④:创建托底类,调用链发生异常后Hystrix返回托底数据!
@Component
public class ESFeignFallbackFactory implements FallbackFactory {
@Override
public ESFeignClient create(Throwable throwable) {
return new ESFeignClient() {
@Override
public AjaxResult deleteById(Long id) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false)
.setMessage("发生了一点小问题:["+throwable.getMessage()+"]");
}
@Override
public AjaxResult save(CourseDoc courseDoc) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false)
.setMessage("发生了一点小问题:["+throwable.getMessage()+"]");
}
};
}
}
⑤:消费者微服务开启feign(谁调用谁开启),课程微服务开启feign
@SpringBootApplication
@MapperScan("com.hanfengyi.course.mapper")
@EnableTransactionManagement
@EnableFeignClients("com.hanfengyi.feign")
public class CourseService2020 {
public static void main(String[] args) {
SpringApplication.run(CourseService2020.class);
}
}
@EnableFeignClients("com.hanfengyi.feign"):注意这里feign的包名要与提供者微服务的集成feign的包名要一致,如果不一致,要指定包名!
⑥:课程业务层执行的业务
@Service
public class CourseServiceImpl extends ServiceImpl implements ICourseService {
@Autowired
private CourseDetailMapper courseDetailMapper;
@Autowired
private CourseMarketMapper courseMarketMapper;
@Autowired
private ESFeignClient esFeignClient;
@Override
public void offLineCourse(Long id) {
//1. 根据id查询课程
Course course = baseMapper.selectById(id);
//2. 判断课程状态是否是上线状态
if(course!=null && course.getStatus()==1){
//3. 删除es中的数据
AjaxResult ajaxResult = esFeignClient.deleteById(id);
//es中数据删除成功
if(ajaxResult.isSuccess()){
//4. 修改mysql中的课程中的课程状态为下线状态
course.setStatus(Course.COURSE_OFFLINE);
baseMapper.updateById(course);
}
}else{
throw new RuntimeException("课程不存在或者课程已处于下线状态");
}
}
@Override
public void onLineCourse(Long id) {
//1. 根据id查询课程
Course course = baseMapper.selectById(id);
//2. 判断课程状态是否是下线状态
if(course!=null && course.getStatus()==0){
//3. 封装CourseDoc对象
CourseDoc courseDoc = new CourseDoc();
//使用工具类拷贝属性
BeanUtils.copyProperties(course, courseDoc);
CourseDetail courseDetail = courseDetailMapper.selectById(course.getId());
BeanUtils.copyProperties(courseDetail, courseDoc);
CourseMarket courseMarket = courseMarketMapper.selectById(course.getId());
courseDoc.setChargeName(courseMarket.getCharge().longValue()==1?"收费":"免费");
BeanUtils.copyProperties(courseMarket, courseDoc);
//4. 保存到es中
AjaxResult ajaxResult = esFeignClient.save(courseDoc);
//es中数据保存成功
if(ajaxResult.isSuccess()){
//5. 修改mysql中的课程中的课程状态为上线状态
course.setStatus(Course.COURSE_ONLINE);
baseMapper.updateById(course);
}
}else{
throw new RuntimeException("课程不存在或者课程已处于上线状态");
}
}
}
4. zuul的饥饿加载配置
zuul:
ignoredServices: '*' #忽略使用服务名访问
ribbon:
eager-load:
enabled: true
5. ribbon的饥饿加载配置
ribbon:
eager-load:
enabled: true
clients: es-server,redis-server,fastdfs-server #指定ribbon调用的服务名