1.商品上架同步索引库
1.1 实现思路
1). 搜索的数据为什么不是从MySQL中搜索, 而要从ES中搜索 ?
搜索精准度 : 从MySQL搜索只能使用模糊匹配 like ‘%华为手机Meta30%’ , 搜索精准度低 ;
搜索效率 : 在大数据量的搜索中, 如果从MySQL中搜索, 搜索性能很低 ; ES搜索性能高 ;
2). 商品上架同步索引库流程
1.2 MQ准备
1). 声明队列 ;
2). 声明交换机 ;
3). 生成队列与交换的绑定关系 ;
代码实现:
@Configuration
public class RabbitMQConfig {
/**
* 定义一个队列名称 代表广告表的队列
/
public static final String AD_UPDATE_QUEUE = “ad_update_queue”;
/*
* 定义一个产品上架的队列名称
*/
public static final String GOODS_UP_QUEUE = “goods_up_queue”;
/**
* 定义一个产品上架的交换机名称
*/
public static final String GOODS_UP_EXCHANGE = "goods_up_exchange";
/**
* 声明队列 用于监听广告表数据发生更新后发送消息
*/
@Bean
public Queue queue(){
return new Queue(AD_UPDATE_QUEUE);
}
/**
* 申明队列 用于产品上架
*/
@Bean(GOODS_UP_QUEUE)
public Queue goodsUpQueue(){
return new Queue(GOODS_UP_QUEUE);
}
/**
* 声明交换机,用于产品上架
*/
@Bean(GOODS_UP_EXCHANGE)
public Exchange goodsUpExchange(){
return ExchangeBuilder.fanoutExchange(GOODS_UP_EXCHANGE).durable(true).build();
}
/**
* 绑定产品上架的交换机和队列
*/
@Bean
public Binding goodsUpExchangeBinding(@Qualifier(GOODS_UP_QUEUE) Queue queue,@Qualifier(GOODS_UP_EXCHANGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
}
1.3 监控上架发送消息
监控的就是 is_marketable 从0 到1 这个操作 ;
-
监控的就是 is_marketable 从0 到1 这个操作 ;
*/
@CanalEventListener
public class SpuListenerRaven {@Autowired
private RabbitTemplate rabbitTemplate;/**
-
监听商品数据库中的spu表的数据变化
-
@param eventType 操作的数据库的类型
-
@param rowData 当前操作数据库的每一行的数据
*/
@ListenPoint(schema = “changgou_goods”,table = “tb_spu”)
public void goodsUp(CanalEntry.EventType eventType,CanalEntry.RowData rowData){
// 定义一个map集合用来存储改变之前的数据,并把这些数据存储在map中
Map<String,String> oldData = new HashMap<>();
// 获取之前的数据,进行遍历,并把数据存储到map中
rowData.getAfterColumnsList().forEach(c -> oldData.put(c.getName(),c.getValue()));// 定义一个map集合用来存储改变之后的数据,并把数据存储到map中
Map<String,String> newData = new HashMap<>();
// 获取变化后的数据,进行遍历,并把数据存储到map中
rowData.getBeforeColumnsList().forEach(column -> newData.put(column.getName(),column.getValue()));// 判断,从oldData以及newData中获取数据,如果相对应的is_marketable从0变为1.代表产品上架
if (“0”.equals(oldData.get(“is_marketable”)) && “1”.equals(newData.get(“is_marketable”))){
// 调用rabbitMQ往队列中发送消息。 发送的消息内容为当前商品的id
rabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_UP_EXCHANGE,"",newData.get(“id”));
}
}
}
-
1.4 搜索微服务构建
1). ES 环境
ElasticSearch的docker容器已经部署好了, 直接通过head插件访问即可 ;
2). ES 索引库操作
索引库名称 : skuinfo
操作方式 : SpringDataElasticSearch ----------> 操作实体类的方式, 来操作索引库 ;
实体类 : SkuInfo
@Document(indexName = “skuinfo”, type = “docs”)
public class SkuInfo implements Serializable {
//商品id,同时也是商品编号
@Id
@Field(index = true, store = true, type = FieldType.Keyword)
private Long id;
//SKU名称
@Field(index = true, store = true, type = FieldType.Text, analyzer = “ik_smart”)
private String name;
//商品价格,单位为:元
@Field(index = true, store = true, type = FieldType.Double)
private Long price;
//库存数量
@Field(index = true, store = true, type = FieldType.Integer)
private Integer num;
//商品图片
@Field(index = false, store = true, type = FieldType.Text)
private String image;
//商品状态,1-正常,2-下架,3-删除
@Field(index = true, store = true, type = FieldType.Keyword)
private String status;
//创建时间
private Date createTime;
//更新时间
private Date updateTime;
//是否默认
@Field(index = true, store = true, type = FieldType.Keyword)
private String isDefault;
//SPUID
@Field(index = true, store = true, type = FieldType.Long)
private Long spuId;
//类目ID
@Field(index = true, store = true, type = FieldType.Long)
private Long categoryId;
//类目名称
@Field(index = true, store = true, type = FieldType.Keyword)
private String categoryName;
//品牌名称
@Field(index = true, store = true, type = FieldType.Keyword)
private String brandName;
//规格
private String spec;//json
//规格参数
private Map<String, Object> specMap;//Map
}
3). 微服务搭建
A. pom.xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
com.changgou
changgou_service_goods_api
1.0-SNAPSHOT
com.changgou
changgou_service_search_api
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-amqp
B.application.yml
server:
port: 9009
spring:
application:
name: search
rabbitmq:
host: 192.168.192.152
redis:
host: 192.168.192.152
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 192.168.192.152:9300
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
client:
config:
default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
connectTimeout: 600000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
readTimeout: 600000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: false
isolation:
strategy: SEMAPHORE
C.引导类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {“com.changgou.goods.feign”})
public class SearchApplication {
public static void main(String[] args) {
SpringApplication.run(SearchApplication.class,args);
}
}
1.5 根据SPUID查询SKU列表接口
/**
-
根据spuId查询sku的列表
-
@param spuId spu商品的id
-
@return sku的列表集合
*/
@GetMapping("/spu/{spuId}")
public List findSkuListBySpuId(@PathVariable(“spuId”) String spuId) {
// 构建一个map进行条件查询
Map<String,Object> searchMap = new HashMap<>();// 如果spuId不是ALL,,则查询指定的spuId的数据
if (!“all”.equals(spuId)){
searchMap.put(“spuId”,spuId);
}// 将状态为审核通过的商品设置为查询条件
searchMap.put(“status”,“1”);
// 调用service 使用条件查询,查询符合条件的skuList
List list = skuService.findList(searchMap);
return list;
}
Feign客户端接口 :
@FeignClient(name = “goods”)
public interface SkuFeign {
@GetMapping("/sku/spu/{spuId}")
public List<Sku> findSkuListBySpuId(@PathVariable("spuId") String spuId);
}
1.6 搜索服务导入索引库
SpringDataElasticSearch
Dao接口定义 :
public interface ESManagerMapper extends ElasticsearchRepository<SkuInfo,Long> {
}
Service接口定义:
public interface ESManagerService {
/**
* 创建索引库及映射
*/
void createMappingAndIndex();
/**
* 导入所有的索引库
*/
void importAll();
/**
* 根据spuId导入索引库
*/
void importDataBySpuId(String spuId);
}
1.6.1 创建索引库及映射
@Override
public void createMappingAndIndex() {
//创建索引
elasticsearchTemplate.createIndex(SkuInfo.class);
//创建映射
elasticsearchTemplate.putMapping(SkuInfo.class);
}
1.6.2 批量导入索引库
@Override
public void importAll() {
//查询sku集合
List skuList = skuFeign.findSkuListBySpuId(“all”);
if (skuList == null || skuList.size()<=0){
throw new RuntimeException(“当前没有数据被查询到,无法导入索引库”);
}
// 将SKU的List集合, 转换为SkuInfo的List集合
//skulist转换为json
String jsonSkuList = JSON.toJSONString(skuList);// [{......},{......},{......}]
//将json转换为skuinfo
List<SkuInfo> skuInfoList = JSON.parseArray(jsonSkuList, SkuInfo.class); // List<SkuInfo>
for (SkuInfo skuInfo : skuInfoList) {
//将规格信息转换为map
Map specMap = JSON.parseObject(skuInfo.getSpec(), Map.class);
skuInfo.setSpecMap(specMap);
}
//导入索引库
esManagerMapper.saveAll(skuInfoList);
}
1.6.3 根据 SPUID 导入索引库
@Override
public void importDataBySpuId(String spuId) {
List skuList = skuFeign.findSkuListBySpuId(spuId);
if (skuList == null || skuList.size()<=0){
throw new RuntimeException(“当前没有数据被查询到,无法导入索引库”);
}
//将集合转换为json
String jsonSkuList = JSON.toJSONString(skuList);
List<SkuInfo> skuInfoList = JSON.parseArray(jsonSkuList, SkuInfo.class);
for (SkuInfo skuInfo : skuInfoList) {
//将规格信息进行转换
Map specMap = JSON.parseObject(skuInfo.getSpec(), Map.class);
skuInfo.setSpecMap(specMap);
}
//添加索引库
esManagerMapper.saveAll(skuInfoList);
}
Controller:
//创建索引库结构
@GetMapping("/create")
public Result create(){
esManagerService.createMappingAndIndex();
return new Result(true, StatusCode.OK,“创建索引库结构成功”);
}
//导入全部数据
@GetMapping("/importAll")
public Result importAll(){
esManagerService.importAll();
return new Result(true, StatusCode.OK,“导入全部数据成功”);
}
以上这两个方法只会调用一次 ;
1.7 监听消息队列,导入索引库
@Component
public class GoodsUpListener {
@Autowired
private ESManagerService esManagerService;
@RabbitListener(queues = RabbitMQConfig.SEARCH_ADD_QUEUE)
public void receiveMessage(String spuId){
System.out.println("接收到的消息为: "+spuId);
//查询skulist,并导入到索引库
esManagerService.importDataBySpuId(spuId);
}
}
2.商品下架删除索引库
2.1 准备MQ
1). 声明交换机
@Bean(GOODS_DOWN_EXCHANGE)
public Exchange GOODS_DOWN_EXCHANGE(){
return ExchangeBuilder.fanoutExchange(GOODS_DOWN_EXCHANGE).durable(true).build();
}
2). 声明队列
@Bean(SEARCH_DEL_QUEUE)
public Queue SEARCH_DEL_QUEUE(){
return new Queue(SEARCH_DEL_QUEUE);
}
3). 声明队列与交换机的绑定关系
@Bean
public Binding GOODS_DOWN_EXCHANGE_BINDING(@Qualifier(SEARCH_DEL_QUEUE)Queue queue,@Qualifier(GOODS_DOWN_EXCHANGE)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
2.2 发送消息到MQ
//获取最新下架的商品 1->0
if (“1”.equals(oldData.get(“is_marketable”)) && “0”.equals(newData.get(“is_marketable”))){
//将商品的spuid发送到mq
rabbitTemplate.convertAndSend(RabbitMQConfig.GOODS_DOWN_EXCHANGE,"",newData.get(“id”));
}
往MQ中发送的消息, 消息内容是 : spuid
2.3 监听消息队列,删除索引库数据
1). 监听类
@Component
public class GoodsDelListener {
@Autowired
private ESManagerService esManagerService;
@RabbitListener(queues = RabbitMQConfig.SEARCH_DEL_QUEUE)
public void receiveMessage(String spuId){
System.out.println("删除索引库监听类,接收到的spuId: "+spuId);
//调用业务层完成索引库数据删除
esManagerService.delDataBySpuId(spuId);
}
}
2). 根据SPUID , 删除索引库数据
@Override
public void delDataBySpuId(String spuId) {//spuId -----> skuId
List skuList = skuFeign.findSkuListBySpuId(spuId);
if (skuList == null || skuList.size()<=0){
throw new RuntimeException(“当前没有数据被查询到,无法导入索引库”);
}
for (Sku sku : skuList) {
esManagerMapper.deleteById(Long.parseLong(sku.getId()));
}
}