springboot + elasticsearch + springcloud + canal +rabbiMQ (产品上下架)

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()));
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值