一、前言
为什么要使用ElasticSearch
在项目工程中需要实现商品的上下架,对于商品这个对象来说,上下架的操作基本就是对其中例如状态码、库存等参数进行修改,同时还需要实现数据实时加载,如果单纯使用MySQL的话,首先如果需要查询对象状态或实时展示对象状态时,需要不断对数据库执行查询指令。其次,对于少数几个对象的状态进行更改或查询,如果使用MySQL则会造成多余的网络请求和零散的IO,对设备性能有额外的开销。最后,当存在大量请求时又会加重后端的压力,造成数据混乱等现象。因此使用一个能提供及时查询的引擎是很有必要的,因此选择ElasticSearch。
二、框架
1.服务
service-product:该模块负责商品的相关信息上传、上架、分类、删除等操作。
service-search:该模块负责管理ElasticSearch,以及商品上下架的功能调用。
2.组件
ElasticSearch:分布式的文档存储和搜索引擎
RabbitMQ:消息队列,实现请求的异步处理
3.框架逻辑图
三、框架搭建
1.service-search
在该组件中将会配置ElasticSearch以及设置对其他模块进行远程调用。
(1).配置ElasticSearch仓库,并根据业务需求设置相应的接口
public interface SkuRepository extends ElasticsearchRepository<SkuEs , Long> {
//获取爆款商品
Page<SkuEs> findByOrderByHotScoreDesc(Pageable pageable);
// 判断keyword是否为空,如果为空 ,根据仓库id + 分类id查询
Page<SkuEs> findByCategoryIdAndWareId(Long categoryId, Long wareId, Pageable pageable);
///如果keyword不为空根据仓库id + keyword进行查询
Page<SkuEs> findByKeywordAndWareId(String keyword, Long wareId, Pageable pageable);
}
(2).创建controller
@RestController
@RequestMapping("/api/search/sku")
public class SkuApiController {
@Autowired
private SkuService skuService;
//查询分类商品
@GetMapping("{page}/{limit}")
public Result listSku(@PathVariable Integer page,
@PathVariable Integer limit,
SkuEsQueryVo skuEsQueryVo) {
//创建pageable对象,0代表第一页
Pageable pageable = PageRequest.of(page-1,limit);
Page<SkuEs> pageModel = skuService.search(pageable,skuEsQueryVo);
return Result.ok(pageModel);
}
//上架
@GetMapping("inner/upperSku/{skuId}")
public Result upperSku(@PathVariable Long skuId) {
skuService.upperSku(skuId);
return Result.ok(null);
}
//下架
@GetMapping("inner/lowerSku/{skuId}")
public Result lowerSku(@PathVariable Long skuId) {
skuService.lowerSku(skuId);
return Result.ok(null);
}
//获取爆款商品
@GetMapping("inner/findHotSkuList")
public List<SkuEs> findHotSkuList() {
return skuService.findHotSkuList();
}
//更新商品热度
@GetMapping("inner/incrHotScore/{skuId}")
public Boolean incrHotScore(@PathVariable("skuId") Long skuId) {
skuService.incrHotScore(skuId);
return true;
}
}
(3).创建Service和Impl
public interface SkuService {
//上架
void upperSku(Long skuId);
//下架
void lowerSku(Long skuId);
//获取爆款商品
List<SkuEs> findHotSkuList();
//查询分类商品
Page<SkuEs> search(Pageable pageable, SkuEsQueryVo skuEsQueryVo);
//更新商品热度
void incrHotScore(Long skuId);
}
2.创建OpenFeign组件用于远程调用(service-product-client)
由于search模块需要使用product进行商品的查询,因此模块之间使用OpenFeign进行远程调用。
@FeignClient(value = "service-product")
public interface ProductFeignClient {
//根据skuId获取sku信息
@GetMapping("/api/product/inner/getSkuInfoVo/{skuId}")
public SkuInfoVo getSkuInfoVo(@PathVariable Long skuId);
//获取新人专享商品
@GetMapping("/api/product/inner/findNewPersonSkuInfoList")
public List<SkuInfo> findNewPersonSkuInfoList();
//所有分类
@GetMapping("/api/product/inner/findAllCategoryList")
public List<Category> findAllCategoryList();
@GetMapping("/api/product/inner/getCategory/{categoryId}")
public Category getCategory(@PathVariable("categoryId") Long categoryId);
@GetMapping("/api/product/inner/getSkuInfo/{skuId}")
public SkuInfo getSkuInfo(@PathVariable("skuId") Long skuId);
//根据skuId列表得到sku信息列表
@PostMapping("/api/product/inner/findSkuInfoList")
public List<SkuInfo> findSkuInfoList(@RequestBody List<Long> skuIdList);
//根据分类id获取分类列表
@PostMapping("/api/product/inner/findCategoryList")
public List<Category> findCategoryList(@RequestBody List<Long> categoryIdList);
//根据关键字匹配sku列表
@GetMapping("/api/product/inner/findSkuInfoByKeyword/{keyword}")
public List<SkuInfo> findSkuInfoByKeyword(@PathVariable("keyword") String keyword);
//验证和锁定库存
@PostMapping("/api/product/inner/checkAndLock/{orderNo}")
public Boolean checkAndLock(@RequestBody List<SkuStockLockVo> skuStockLockVoList,
@PathVariable("orderNo") String orderNo);
}
3.rabbit-util
该组件中需要创建一个调用RabbitMQ的配置文件以及相关的应答属性。
引入依赖
<dependencies>
<!--rabbitmq消息队列-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
创建config配置文件
(1).MQConfig 消息转换器
@Configuration
public class MQConfig {
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
(2).MQProducerAckConfig 消息确认配置,在消息添加成功或失败时返回相关信息
@Component
public class MQProducerAckConfig implements RabbitTemplate.ReturnCallback,RabbitTemplate.ConfirmCallback {
// 我们发送消息使用的是 private RabbitTemplate rabbitTemplate; 对象
// 如果不做设置的话 当前的rabbitTemplate 与当前的配置类没有任何关系!
@Autowired
private RabbitTemplate rabbitTemplate;
// 设置 表示修饰一个非静态的void方法,在服务器加载Servlet的时候运行。并且只执行一次!
@PostConstruct
public void init(){
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setConfirmCallback(this);
}
/**
* 表示消息是否正确发送到了交换机上
* @param correlationData 消息的载体
* @param ack 判断是否发送到交换机上
* @param cause 原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息发送成功!");
}else {
System.out.println("消息发送失败!"+cause);
}
}
/**
* 消息如果没有正确发送到队列中,则会走这个方法!如果消息被正常处理,则这个方法不会走!
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体: " + new String(message.getBody()));
System.out.println("应答码: " + replyCode);
System.out.println("描述:" + replyText);
System.out.println("消息使用的交换器 exchange : " + exchange);
System.out.println("消息使用的路由键 routing : " + routingKey);
}
}
(3).设置应答字符串
public class MqConst {
/**
* 消息补偿
*/
public static final String MQ_KEY_PREFIX = "ssyx.mq:list";
public static final int RETRY_COUNT = 3;
/**
* 商品上下架
*/
public static final String EXCHANGE_GOODS_DIRECT = "ssyx.goods.direct";
public static final String ROUTING_GOODS_UPPER = "ssyx.goods.upper";
public static final String ROUTING_GOODS_LOWER = "ssyx.goods.lower";
//队列
public static final String QUEUE_GOODS_UPPER = "ssyx.goods.upper";
public static final String QUEUE_GOODS_LOWER = "ssyx.goods.lower";
/**
* 团长上下线
*/
public static final String EXCHANGE_LEADER_DIRECT = "ssyx.leader.direct";
public static final String ROUTING_LEADER_UPPER = "ssyx.leader.upper";
public static final String ROUTING_LEADER_LOWER = "ssyx.leader.lower";
//队列
public static final String QUEUE_LEADER_UPPER = "ssyx.leader.upper";
public static final String QUEUE_LEADER_LOWER = "ssyx.leader.lower";
//订单
public static final String EXCHANGE_ORDER_DIRECT = "ssyx.order.direct";
public static final String ROUTING_ROLLBACK_STOCK = "ssyx.rollback.stock";
public static final String ROUTING_MINUS_STOCK = "ssyx.minus.stock";
public static final String ROUTING_DELETE_CART = "ssyx.delete.cart";
//解锁普通商品库存
public static final String QUEUE_ROLLBACK_STOCK = "ssyx.rollback.stock";
public static final String QUEUE_SECKILL_ROLLBACK_STOCK = "ssyx.seckill.rollback.stock";
public static final String QUEUE_MINUS_STOCK = "ssyx.minus.stock";
public static final String QUEUE_DELETE_CART = "ssyx.delete.cart";
//支付
public static final String EXCHANGE_PAY_DIRECT = "ssyx.pay.direct";
public static final String ROUTING_PAY_SUCCESS = "ssyx.pay.success";
public static final String QUEUE_ORDER_PAY = "ssyx.order.pay";
public static final String QUEUE_LEADER_BILL = "ssyx.leader.bill";
//取消订单
public static final String EXCHANGE_CANCEL_ORDER_DIRECT = "ssyx.cancel.order.direct";
public static final String ROUTING_CANCEL_ORDER = "ssyx.cancel.order";
//延迟取消订单队列
public static final String QUEUE_CANCEL_ORDER = "ssyx.cancel.order";
/**
* 定时任务
*/
public static final String EXCHANGE_DIRECT_TASK = "ssyx.exchange.direct.task";
public static final String ROUTING_TASK_23 = "ssyx.task.23";
//队列
public static final String QUEUE_TASK_23 = "ssyx.queue.task.23";
}
(4).添加Service
@Service
public class RabbitService {
@Autowired
private RabbitTemplate rabbitTemplate;
//发送消息的方法
//exchange交换机
//routingKey路由
//message消息
public boolean sendMessage(String exchange,String routingKey,Object message) {
rabbitTemplate.convertAndSend(exchange,routingKey,message);
return true;
}
}
四、总结
本章简单讲解了ES配合MQ实现商品上下架以及用户下单的操作,以及整个流程实现的框架分析和实现,其中涉及到的内容比较多,例如ES、MQ组件等、以及如何实现各个模块之间的相互调用,碍于篇幅,有些内容无法详细讲解。博主将持续复盘项目,随后会更新相应组件的讲解,同时也会持续更新本篇内容。