1. 课程上下线业务
1.1 业务描述
-
上线
在系统中,我们添加了一个课程,用户不能立即就搜索到,需要上线以后才行。
-
下线
当某个课程不想卖的时候,就要下线.当课程下线后,用户不能搜索到,但是数据库是还有的。
1.2 技术方案
1.2.1 技术方案一:数据库状态做判断-->不可取
上线后,修改状态为”上线”,用户搜索时只能搜索到上线状态的.如果不想卖了,执行下线时,修改状态下线.
--->垃圾(每次都要操作数据库)
1.2.2 技术方案二:全文检索服务器-->可取
上线时把课程数据同步到es,用户查询直接从es查询.也就意味着没有上线的课程用户查询不到,因为没有放到es库.
下线时把es库课程数据删除掉.用户就查询不到了.
--->牛B(以基于索引搜索代替数据查询)
优点:
- (1)降低数据库压力
- (2)提高了查询速度,增强用户体验-基于索引搜索,效率远远数据库搜索
2. 课程上下线实现
2.1 技术架构
//TODO 结构图以后自己画上补充
简单描述:
用户查询直接从ES库中查
- 管理员:
- (1)添加课程。管理员将课程添加到db
- (2)课程上线。把需要上线的课程查出来,同步到ES库。
- (3)删除或修改课程。同步操作ES库和DB库。
- (4)查询课程。这是后台的查询,就直接从数据库查询。
- 用户:用户查询直接从ES库中查
- 减少数据库压力
- 提高查询效率,用户体验更佳
2.2 实现+分析
课程服务调用搜索服务-服务内部调用feign
步骤分析:
- 搭建搜索服务
- 课程的上下线处理
2.2.1 搭建搜索服务
2.2.1.1 步骤分析
- 创建项目
- 导包
- 配置
- 入口类
- doc准备
- repository准备-service
- query准备-interface
- IESCourseService接口-service
- ESCourseServiceImpl(实现上面那个接口)-service
- ESCourseController-service
- client-interface
- 生成文档映射-test-service
- 测试
- 日志集成
- 网关集成
- 本项目swagger集成
- 网关swagger集成
- 启动测试网关、日志、swagger
2.2.1.2 步骤实现
-
创建项目
在二级子模块hrm_basic_parent下创建三级子模块hrm_basic_es_interface和三级子模块hrm_basic_es_service
-
导包
- 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>
- 配置(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
- 入口类-在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);
}
}
-
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方法
}
-
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> {
}
- query准备-interface
public class ESCourseQuery extends BaseQuery {
}
- 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);
}
- 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());
}
}
- 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);
}
}
-
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;
}
};
}
}
- 生成文档映射-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);
}
}
-
测试
-
日志集成
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>
-
网关集成
在网关的配置文件中zuul.routes中再新加两行,配置es
es.serviceId: hrm-es # 服务名
es.path: /es/** # 把es打头的所有请求都转发给hrm-es
- 本项目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();
}
}
-
网关swagger集成
在网关的DocumentationConfig配置类里面新加个
resources.add(swaggerResource("分布式全文检索", "/services/es/v2/api-docs", "2.0"));
- 启动测试网关、日志、swagger
2.2.2 课程上下线处理
架构分析图(后续补上)
2.2.2.1 步骤分析
- 改造商品的删除和修改(索引库也要进行对应的操作)
- 课程的上下线逻辑
- client
2.2.2.2 步骤实现
-
改造商品的删除和修改(索引库也要进行对应的操作)
由于要使用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;
}
-
课程的上下线逻辑
简单逻辑:前端会发起上线或下线请求!到我这里的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. 易错点总结
- ESClient那里的注解@FeignClient的参数。value = "HRM-ES"的值指向自己注册到eureka的服务。以下面为例
@FeignClient(value = "HRM-ES",configuration = FeignClientsConfiguration.class,
fallbackFactory = EsCourseClientHystrixFallbackFactory.class)
- 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 {}
- 其他模块调用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);
}
}
网友评论