美文网首页
SAAS-HRM-day5(ElasticSearch)

SAAS-HRM-day5(ElasticSearch)

作者: 程序员Darker | 来源:发表于2019-09-30 08:24 被阅读0次

    1. 课程上下线业务

    1.1 业务描述

    1. 上线

      在系统中,我们添加了一个课程,用户不能立即就搜索到,需要上线以后才行。

    2. 下线

      当某个课程不想卖的时候,就要下线.当课程下线后,用户不能搜索到,但是数据库是还有的。

    1.2 技术方案

    1.2.1 技术方案一:数据库状态做判断-->不可取

    上线后,修改状态为”上线”,用户搜索时只能搜索到上线状态的.如果不想卖了,执行下线时,修改状态下线.

    --->垃圾(每次都要操作数据库)

    1.2.2 技术方案二:全文检索服务器-->可取

    上线时把课程数据同步到es,用户查询直接从es查询.也就意味着没有上线的课程用户查询不到,因为没有放到es库.

    下线时把es库课程数据删除掉.用户就查询不到了.

    --->牛B(以基于索引搜索代替数据查询)

    优点:

    • (1)降低数据库压力
    • (2)提高了查询速度,增强用户体验-基于索引搜索,效率远远数据库搜索

    2. 课程上下线实现

    2.1 技术架构

    //TODO 结构图以后自己画上补充

    简单描述:

    用户查询直接从ES库中查

    1. 管理员:
      • (1)添加课程。管理员将课程添加到db
      • (2)课程上线。把需要上线的课程查出来,同步到ES库。
      • (3)删除或修改课程。同步操作ES库和DB库。
      • (4)查询课程。这是后台的查询,就直接从数据库查询。
    2. 用户:用户查询直接从ES库中查
      • 减少数据库压力
      • 提高查询效率,用户体验更佳

    2.2 实现+分析

    课程服务调用搜索服务-服务内部调用feign

    步骤分析:

    1. 搭建搜索服务
    2. 课程的上下线处理

    2.2.1 搭建搜索服务

    2.2.1.1 步骤分析

    1. 创建项目
    2. 导包
    3. 配置
    4. 入口类
    5. doc准备
    6. repository准备-service
    7. query准备-interface
    8. IESCourseService接口-service
    9. ESCourseServiceImpl(实现上面那个接口)-service
    10. ESCourseController-service
    11. client-interface
    12. 生成文档映射-test-service
    13. 测试
    14. 日志集成
    15. 网关集成
    16. 本项目swagger集成
    17. 网关swagger集成
    18. 启动测试网关、日志、swagger

    2.2.1.2 步骤实现

    1. 创建项目

      在二级子模块hrm_basic_parent下创建三级子模块hrm_basic_es_interface和三级子模块hrm_basic_es_service

    2. 导包

    • hrm_basic_es_interface
     <!--公共工具依赖-->
            <dependency>
                <groupId>cn.wangningbo.hrm</groupId>
                <artifactId>hrm_basic_util</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--不能直接依赖starter,有自动配置,而消费者是不需要额。-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--测试场景-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--客户端feign支持-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <!--spring-data-elasticsearch 注意没有starter,否则要报错-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-elasticsearch</artifactId>
                <version>3.0.10.RELEASE</version>
            </dependency>
    
    • hrm_basic_es_service
    <dependency>
                <groupId>cn.wangningbo.hrm</groupId>
                <artifactId>hrm_basic_es_interface</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--web场景-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--测试场景-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- Eureka 客户端依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--引入swagger支持-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.9.2</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
            <!--配置中心支持-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-config</artifactId>
            </dependency>
            <!--springboot 对spring data es支持-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
    
    1. 配置(application.yml)-在service端
    server:
      port: 9004
    spring:
      application:
        name: hrm-es
      data:
        elasticsearch:
          cluster-name: elasticsearch
          cluster-nodes: 127.0.0.1:9300 #9200是图形界面端,9300代码端
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
      instance:
        prefer-ip-address: true
    
    1. 入口类-在service端
    package cn.wangningbo.hrm;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    public class ElasticSearch9004Application {
        public static void main(String[] args) {
            SpringApplication.run(ElasticSearch9004Application.class, args);
        }
    }
    
    1. doc准备-interface

      根据业务和表设计这里的doc

    package cn.wangningbo.hrm.doc;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    import java.math.BigDecimal;
    import java.util.Date;
    
    @Document(indexName = "hrm", type = "course")
    public class ESCourse {
        @Id
        private Long id;
        private String name;
        private String users;
        private Long courseTypeId;
        private String courseTypeName;
        private Long gradeId;
        private String gradeName;
        private Integer status;
        private Long tenantId;
        private String tenantName;
        private Long userId;
        private String userName;
        private Date startTime;
        private Date endTime;
        private String intro;
        private String resources; //图片
        private Date expires; //过期时间
        private BigDecimal priceOld; //原价
        private BigDecimal price; //原价
        private String qq; //原价
    
        @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
        private String all;
    
        public String getAll() {
            String tmp = name
                    + " " + users
                    + " " + courseTypeName
                    + " " + gradeName
                    + " " + tenantName
                    + " " + userName
                    + " " + intro;
            return tmp;
        }
    
        public void setAll(String all) {
            this.all = all;
        }
        //提供get、set和toString方法
    }
    
    1. repository准备-service

      由于需要操作es,所需需要repository操作

    package cn.wangningbo.hrm.repository;
    
    
    import cn.wangningbo.hrm.doc.ESCourse;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    
    public interface CourseRepository extends ElasticsearchRepository<ESCourse, Long> {
    }
    
    1. query准备-interface
    public class ESCourseQuery extends BaseQuery {
    }
    
    1. IESCourseService接口-service
    package cn.wangningbo.hrm.service;
    
    
    import cn.wangningbo.hrm.doc.ESCourse;
    import cn.wangningbo.hrm.query.ESCourseQuery;
    import cn.wangningbo.hrm.util.PageList;
    
    import java.util.List;
    
    /**
     * @author wangningbo
     * @since 2019-09-06
     */
    public interface IESCourseService {
        //添加
        void insert(ESCourse esCourse);
    
        //修改
        void updateById(ESCourse esCourse);
    
        //删除
        void deleteById(Long id);
    
        //查询一个
        ESCourse selectById(Long id);
    
        //查询所有
        List<ESCourse> selectList(Object o);
    
        //dsl高级查询
        PageList<ESCourse> selectListPage(ESCourseQuery query);
    
    }
    
    1. ESCourseServiceImpl(实现上面那个接口)-service
    @Service
    public class ESCourseServiceImpl implements IESCourseService {
    
        @Autowired
        private CourseRepository courseRepository;
    
        @Override
        public void insert(ESCourse esCourse) {
            courseRepository.save(esCourse);
        }
    
        @Override
        public void updateById(ESCourse esCourse) {
            courseRepository.save(esCourse);
        }
    
        @Override
        public void deleteById(Long id) {
            courseRepository.deleteById(id);
        }
    
        @Override
        public ESCourse selectById(Long id) {
            return courseRepository.findById(id).get();
        }
    
        @Override
        public List<ESCourse> selectList(Object o) {
            Page page = (Page) courseRepository.findAll();
            return page.getContent();
        }
    
        @Override
        public PageList<ESCourse> selectListPage(ESCourseQuery query) {
            NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
            BoolQueryBuilder bool = QueryBuilders.boolQuery();
            //模糊查询 @TODO
            bool.must(QueryBuilders.matchQuery("intro", "zhang"));
            //精确过滤 @TODO
            List<QueryBuilder> filters = bool.filter();
            filters.add(QueryBuilders.rangeQuery("age").gte(0).lte(200));
            builder.withQuery(bool); //query bool must(filter)
            //排序 @TODO
            builder.withSort(SortBuilders.fieldSort("age").order(SortOrder.ASC));
            //分页 当前页从0开始
            builder.withPageable(PageRequest.of(query.getPage() - 1, query.getRows()));
            //构造查询条件
            NativeSearchQuery esQuery = builder.build();
            //查询
            Page<ESCourse> page = courseRepository.search(esQuery);
            return new PageList<>(page.getTotalElements(), page.getContent());
        }
    }
    
    1. ESCourseController-service
    package cn.wangningbo.hrm.web.controller;
    
    import cn.wangningbo.hrm.doc.ESCourse;
    import cn.wangningbo.hrm.query.ESCourseQuery;
    import cn.wangningbo.hrm.service.IESCourseService;
    import cn.wangningbo.hrm.util.AjaxResult;
    import cn.wangningbo.hrm.util.PageList;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/esCourse")
    public class ESCourseController {
        @Autowired
        public IESCourseService esCourseService;
    
        /**
         * 保存和修改公用的
         *
         * @param esCourse 传递的实体
         * @return Ajaxresult转换结果
         */
        @RequestMapping(value = "/save", method = RequestMethod.POST)
        public AjaxResult save(@RequestBody ESCourse esCourse) {
            try {
                if (esCourse.getId() != null) {
                    esCourseService.updateById(esCourse);
                } else {
                    esCourseService.insert(esCourse);
                }
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                return AjaxResult.me().setMessage("保存对象失败!" + e.getMessage());
            }
        }
    
        /**
         * 删除
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public AjaxResult delete(@PathVariable("id") Long id) {
            try {
                esCourseService.deleteById(id);
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                return AjaxResult.me().setMessage("删除对象失败!" + e.getMessage());
            }
        }
    
        //获取用户
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        public ESCourse get(@PathVariable("id") Long id) {
            return esCourseService.selectById(id);
        }
    
    
        /**
         * 查看所有信息
         *
         * @return
         */
        @RequestMapping(value = "/list", method = RequestMethod.GET)
        public List<ESCourse> list() {
    
            return esCourseService.selectList(null);
        }
    
    
        /**
         * 分页查询数据
         *
         * @param query 查询对象
         * @return PageList 分页对象
         */
        @RequestMapping(value = "/json", method = RequestMethod.POST)
        public PageList<ESCourse> json(@RequestBody ESCourseQuery query) {
            return esCourseService.selectListPage(query);
        }
    }
    
    1. client-interface

      注意点:@FeignClient的value = "HRM-ES",自己服务端的名字,@RequestMapping("/esCourse")要与自己服务端controller的一样

    package cn.wangningbo.hrm.client;
    
    
    import cn.wangningbo.hrm.doc.ESCourse;
    import cn.wangningbo.hrm.query.ESCourseQuery;
    import cn.wangningbo.hrm.util.AjaxResult;
    import cn.wangningbo.hrm.util.PageList;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.cloud.openfeign.FeignClientsConfiguration;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @FeignClient(value = "HRM-ES", configuration = FeignClientsConfiguration.class,
            fallbackFactory = EsCourseClientHystrixFallbackFactory.class)
    @RequestMapping("/esCourse")
    public interface ESCourseClient {
        /**
         * 保存和修改公用的
         *
         * @param esCourse 传递的实体
         * @return Ajaxresult转换结果
         */
        @RequestMapping(value = "/save", method = RequestMethod.POST)
        AjaxResult save(ESCourse esCourse);
    
        /**
         * 删除
         *
         * @param id
         * @return
         */
        @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
        AjaxResult delete(@PathVariable("id") Integer id);
    
        //获取用户
        @RequestMapping("/{id}")
        ESCourse get(@RequestParam(value = "id", required = true) Long id);
    
    
        /**
         * 查看所有信息
         *
         * @return
         */
        @RequestMapping("/list")
        public List<ESCourse> list();
    
        /**
         * 分页查询数据
         *
         * @param query 查询对象
         * @return PageList 分页对象
         */
        @RequestMapping(value = "/json", method = RequestMethod.POST)
        PageList<ESCourse> json(@RequestBody ESCourseQuery query);
    }
    
    package cn.wangningbo.hrm.client;
    
    import cn.wangningbo.hrm.doc.ESCourse;
    import cn.wangningbo.hrm.query.ESCourseQuery;
    import cn.wangningbo.hrm.util.AjaxResult;
    import cn.wangningbo.hrm.util.PageList;
    import feign.hystrix.FallbackFactory;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    /**
     * @author wangningbo
     * @date 2019/09/06
     */
    @Component
    public class EsCourseClientHystrixFallbackFactory implements FallbackFactory<ESCourseClient> {
    
        @Override
        public ESCourseClient create(Throwable throwable) {
            return new ESCourseClient() {
                @Override
                public AjaxResult save(ESCourse esCourse) {
                    return null;
                }
    
                @Override
                public AjaxResult delete(Integer id) {
                    return null;
                }
    
                @Override
                public ESCourse get(Long id) {
                    return null;
                }
    
                @Override
                public List<ESCourse> list() {
                    return null;
                }
    
                @Override
                public PageList<ESCourse> json(ESCourseQuery query) {
                    return null;
                }
            };
        }
    }
    
    1. 生成文档映射-test-service
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = ElasticSearch9004Application.class)
    public class IESCourseServiceTest {
        @Autowired
        private ElasticsearchTemplate elasticsearchTemplate;
    
        @Test
        public void testInit() throws Exception {
            elasticsearchTemplate.createIndex(ESCourse.class);
            elasticsearchTemplate.putMapping(ESCourse.class);
        }
    }
    
    1. 测试

      http://localhost:9004/esCourse/list

    2. 日志集成

      resources下存放一个名字为logback-spring.xml的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!--
    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
    -->
    <configuration scan="false" scanPeriod="60 seconds" debug="false">
        <!-- 定义日志的根目录 -->
        <property name="LOG_HOME" value="/hrm/" />
        <!-- 定义日志文件名称 -->
        <property name="appName" value="hrm-es"></property>
        <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
        <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
            <!--
            日志输出格式:
                %d表示日期时间,
                %thread表示线程名,
                %-5level:级别从左显示5个字符宽度
                %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 
                %msg:日志消息,
                %n是换行符
            -->
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </layout>
        </appender>
    
        <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->  
        <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 指定日志文件的名称
               /hrm/hrm-course/hrm-course.log
            -->
            <file>${LOG_HOME}/${appName}/${appName}.log</file>
            <!--
            当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
            TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
            -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--
                滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动 
                %i:当文件大小超过maxFileSize时,按照i进行文件滚动
                -->
                <fileNamePattern>${LOG_HOME}/${appName}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
                <!-- 
                可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
                且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
                那些为了归档而创建的目录也会被删除。
                -->
                <MaxHistory>365</MaxHistory>
                <!-- 
                当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
                -->
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <!-- 日志输出格式: -->     
            <layout class="ch.qos.logback.classic.PatternLayout">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
            </layout>
        </appender>
    
        <!-- 
            logger主要用于存放日志对象,也可以定义日志类型、级别
            name:表示匹配的logger类型前缀,也就是包的前半部分
            level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
            additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
            false:表示只用当前logger的appender-ref,true:
            表示当前logger的appender-ref和rootLogger的appender-ref都有效
        -->
        <!-- hibernate logger -->
        <logger name="cn.wangningbo" level="debug" />
        <!-- Spring framework logger -->
        <logger name="org.springframework" level="debug" additivity="false"></logger>
    
    
    
        <!-- 
        root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
        要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 
        -->
        <root level="info">
            <appender-ref ref="stdout" />
            <appender-ref ref="appLogAppender" />
        </root>
    </configuration> 
    
    1. 网关集成

      在网关的配置文件中zuul.routes中再新加两行,配置es

        es.serviceId: hrm-es # 服务名
        es.path: /es/** # 把es打头的所有请求都转发给hrm-es
    
    1. 本项目swagger集成
    package cn.wangningbo.hrm.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class Swagger2 {
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    //对外暴露服务的包,以controller的方式暴露,所以就是controller的包.
                    .apis(RequestHandlerSelectors.basePackage("cn.wangningbo.hrm.web.controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("分布式全文检索api")
                    .description("分布式全文检索接口文档说明")
                    .contact(new Contact("wangningbo", "", "wang_ning_bo163@163.com"))
                    .version("1.0")
                    .build();
        }
    }
    
    1. 网关swagger集成

      在网关的DocumentationConfig配置类里面新加个

    resources.add(swaggerResource("分布式全文检索", "/services/es/v2/api-docs", "2.0"));
    
    1. 启动测试网关、日志、swagger

    2.2.2 课程上下线处理

    架构分析图(后续补上)

    2.2.2.1 步骤分析

    1. 改造商品的删除和修改(索引库也要进行对应的操作)
    2. 课程的上下线逻辑
    3. client

    2.2.2.2 步骤实现

    1. 改造商品的删除和修改(索引库也要进行对应的操作)

      由于要使用es,这里操作删除和修改的时候也要操作es库,所以要覆写删除和修改方法!

        @Override
        public boolean deleteById(Serializable id) {
            //删除数据库的同时也要判断状态是否要删除es库
            courseMapper.deleteById(id);
            Course course = courseMapper.selectById(id);
            if (course.getStatus() == 1)
                esCourseClient.delete(Integer.valueOf(id.toString()));
            return true;
        }
    
        // @TODO 不同服务,反3Fn设计冗余字段
        // @TODO 相同服务,关联查询
        //根据自己的需求和库中的表设计
        private ESCourse course2EsCourse(Course course) {
            ESCourse result = new ESCourse();
            result.setId(course.getId());
            result.setName(course.getName());
            result.setUsers(course.getUsers());
            result.setCourseTypeId(course.getCourseTypeId());
            //type-同库
            if (course.getCourseType() != null)
                result.setCourseTypeName(course.getCourseType().getName());
            //跨服务操作
            result.setGradeId(course.getGrade());
            result.setGradeName(null);
            result.setStatus(course.getStatus());
            result.setTenantId(course.getTenantId());
            result.setTenantName(course.getTenantName());
            result.setUserId(course.getUserId());
            result.setUserName(course.getUserName());
            result.setStartTime(course.getStartTime());
            result.setEndTime(course.getEndTime());
            //Detail
            result.setIntro(null);
            //resource
            result.setResources(null);
            //market
            result.setExpires(null);
            result.setPrice(null);
            result.setPriceOld(null);
            result.setQq(null);
            return result;
        }
    
        @Override
        public boolean updateById(Course entity) {
            //修改数据库的时候也要根据状态判断是否操作修改es库
            courseMapper.updateById(entity);
            Course course = courseMapper.selectById(entity.getId());
            if (course.getStatus() == 1)
                esCourseClient.save(course2EsCourse(entity));
            return true;
        }
    
    1. 课程的上下线逻辑

      简单逻辑:前端会发起上线或下线请求!到我这里的controller接口。我这里进行一层一层的实现逻辑!既要操作db库,也要操作es库!

      controller层新增两个方法,上线和下线。我这里先实现上线,再写下线(下线比较简单)!

    ==(上线---------------------->)==

    controller

    //由于前端可能是批量操作,所以我这里使用数组接收参数
        @PostMapping("/onLine")
        public AjaxResult onLine(@RequestBody Long[] ids) {
            try {
                courseService.onLine(ids);
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("online failed!"+e);
                return AjaxResult.me().setSuccess(false)
                        .setMessage("上线失败!"+e.getMessage());
            }
        }
    

    IService

        void onLine(Long[] ids);
    

    ServiceImpl

        /**
         * 商品课程上线
         * @param ids
         */
        @Override
        public void onLine(Long[] ids) {
            //批量操作数据库状态字段 //类似于这种update t_course set status = 1,start_time=xxx where id in (1,2,3)
            ArrayList<Map<String, Object>> listMap = new ArrayList<>();
            if (ids != null || ids.length > 0) {
                for (Long id : ids) {
                    HashMap<String, Object> map = new HashMap<>();
                    map.put("id", id);
                    map.put("start_time", new Date());
                    listMap.add(map);
                }
            }
            //批量修改db状态字段
            courseMapper.batchOnline(listMap);
            //批量操作索引库
            //从数据库中查出来需要上线的商品课程
            List<Course> courseList = courseMapper.selectBatchIds(Arrays.asList(ids));
            //把从数据库中查出来的商品课程转化为es的doc
            List<ESCourse> esCourseList = courseList2EsCourse(courseList);
            //批量操作把doc添加到es库 //批量添加到es库中的方法没有,自己实现一个
            esCourseClient.batchSave(esCourseList);
        }
        
            /**
         * db库的domain转化为es的doc
         *
         * @param courseList
         * @return
         */
        private List<ESCourse> courseList2EsCourse(List<Course> courseList) {
            ArrayList<ESCourse> list = new ArrayList<>();
            courseList.forEach(course -> list.add(course2EsCourse(course)));
            return list;
        }
    

    上面这个serviceImpl主要是做2步操作,第一步是批量修改db库的状态字段为上线,第二步是操作es库,批量把上线的商品课程添加es库中。

    ①先说操作db库的字段修改为上线的逻辑步骤

    Mapper.java

    void batchOnline(ArrayList<Map<String,Object>> listMap);
    

    Mapper.xml

        <!--批量上线商品课程 void batchOnline(ArrayList<Map<String,Object>> listMap);-->
        <update id="batchOnline" parameterType="arrayList">
            UPDATE t_course set status =1,start_time=now() where id IN
            <foreach collection="list" item="item" open="(" close=")" separator=",">
                #{item.id}
            </foreach>
        </update>
    

    ②再说操作es库,把上线的商品课程添加到es库

    es-->client

        //批量上线
        @PostMapping("/online")
        AjaxResult batchSave(List<ESCourse> esCourseList);
    

    es-->ClientHystrixFallbackFactory

                @Override
                public AjaxResult batchSave(List<ESCourse> esCourseList) {
                    return null;
                }
    

    es-->controller

        /**
         * 批量保存到es库,批量上线
         * @param esCourseList
         * @return
         */
        @PostMapping("/online")
        AjaxResult batchSave(@RequestBody List<ESCourse> esCourseList){
            try {
                esCourseService.batchSave(esCourseList);
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                return AjaxResult.me().setSuccess(false).setMessage("批量添加失败!"+e.getMessage());
            }
        }
    

    es-->IService

        //批量保存
        void batchSave(List<ESCourse> ids);
    

    es-->ServiceImpl

        //批量保存
        @Override
        public void batchSave(List<ESCourse> esCourseList) {
            courseRepository.saveAll(esCourseList);
        }
    

    ==注意:这时候调用es的那个模块的入口类就要打上注解@EnableFeignClients== 入口类获得feign支持

    测试是否成功

    ==(下线---------------------->)==

    下线的简单逻辑分析:管理员下线商品课程只需要做2步,第一步:修改db库中的商品课程字段为下线状态,第二步:删除es库中的商品课程

    controller

        /**
         * 商品课程下线
         *
         * @param ids
         * @return
         */
        @PostMapping("/offLine")
        public AjaxResult offLine(@RequestBody Long[] ids) {
            try {
                courseService.offLine(ids);
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("offLine failed!"+e);
                return AjaxResult.me().setSuccess(false)
                        .setMessage("下线失败!"+e.getMessage());
            }
        }
    

    IService

        void offLine(Long[] ids);
    

    ServiceImpl

        @Override
        public void offLine(Long[] ids) {
            //批量修改db库中商品课程的状态为下线状态
            courseMapper.batchOffline(Arrays.asList(ids));
            //批量删除es库中的商品课程 //时间方面是使用mysql的语法生成的
            List<Course> courseList = courseMapper.selectBatchIds(Arrays.asList(ids));
            List<ESCourse> esCourseList = courseList2EsCourse(courseList);
            esCourseClient.batchDel(esCourseList);
        }
    

    上面这个serviceImpl主要是做2步操作,第一步是批量修改db库的状态字段为下线状态,第二步是操作es库,批量把下线的商品课程从es库中删除掉。

    ①批量修改db库的状态字段为下线状态

    Mapper.java

        void batchOffline(List<Long> longs);
    

    Mapper.xml

        <!--批量下线商品课程void batchOffline(List<Long> longs);-->
        <update id="batchOffline" parameterType="arrayList">
            UPDATE t_course set status =0,end_time=now() where id IN
            <foreach collection="list" item="item" open="(" close=")" separator=",">
                #{item}
            </foreach>
        </update>
    

    es-->client

        //批量下线
        @PostMapping("/offline")
        void batchDel(List<ESCourse> esCourseList);
    

    es-->ClientHystrixFallbackFactory

                @Override
                public void batchDel(List<ESCourse> esCourseList) {
    
                }
    

    es-->controller

        @PostMapping("/offline")
        AjaxResult batchDel(@RequestBody List<ESCourse> esCourseList){
            try {
                esCourseService.batchDel(esCourseList);
                return AjaxResult.me();
            } catch (Exception e) {
                e.printStackTrace();
                return AjaxResult.me().setSuccess(false).setMessage("批量删除失败!"+e.getMessage());
            }
        }
    

    es-->Iservice

        //批量删除
        void batchDel(List<ESCourse> esCourseList);
    

    es-->ServiceImpl

        //批量删除
        @Override
        public void batchDel(List<ESCourse> esCourseList) {
            courseRepository.deleteAll(esCourseList);
        }
    

    测试是否成功

    3. 易错点总结

    1. ESClient那里的注解@FeignClient的参数。value = "HRM-ES"的值指向自己注册到eureka的服务。以下面为例
    @FeignClient(value = "HRM-ES",configuration = FeignClientsConfiguration.class,
            fallbackFactory = EsCourseClientHystrixFallbackFactory.class)
    
    1. ESClient那里的注@RequestMapping的参数值要与controller的一致。以下面的为例
    //client
    @FeignClient(value = "HRM-ES",configuration = FeignClientsConfiguration.class,
            fallbackFactory = EsCourseClientHystrixFallbackFactory.class)
    @RequestMapping("/esCourse")
    public interface EsCourseClient {}
    
    //controller
    @RestController
    @RequestMapping("/esCourse")
    public class EsCourseController {}
    
    1. 其他模块调用es的时候,作为es的客户端,在入口类上要加入feign的支持。也就是注解@EnableFeignClients。以下面为例
    package cn.wangningbo.hrm;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableEurekaClient
    @MapperScan("cn.wangningbo.hrm.mapper")
    @EnableFeignClients
    public class Course9002Application {
        public static void main(String[] args) {
            SpringApplication.run(Course9002Application.class, args);
        }
    }
    

    相关文章

      网友评论

          本文标题:SAAS-HRM-day5(ElasticSearch)

          本文链接:https://www.haomeiwen.com/subject/ltlcpctx.html