HRM微服务项目day-04-课程上线下线、集成ElasticSearch

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的集成

  1. 导入jar包
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
  1. 创建配置文件
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      
  1. 创建一个类用来描述文档信息
@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:使用什么分词器做查询!

  1. 创建一个接口继承ElasticsearchRepository,其中有对文档操作的所有方法!
@Repository
public interface CourseDocRepository extends ElasticsearchRepository<CourseDoc,Long> {
}
  1. 使用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<CourseDoc> optional = courseDocRepository.findById(23l);
        if(optional!=null){
            CourseDoc courseDoc = optional.get();
            System.out.println(courseDoc);
        }
    }
    @Test
    public void testGetAll(){
        Iterator<CourseDoc> 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<CourseDoc> docs = courseDocRepository.search(builder.build());
        System.out.println("总条数:"+docs.getTotalElements());
        System.out.println("查询的页数:"+docs.getTotalPages());
        docs.getContent().forEach(e-> System.out.println("======"+e));
        Iterator<CourseDoc> 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的集成

①:导包

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

②:创建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<ESFeignClient> {
    @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<CourseMapper, Course> 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调用的服务名
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容