sl-express-ms-search使用手册

sl-express-ms-search使用手册

1.说明

该模块为搜索相关微服务,主要将ElasticSearch搜索技术相关的使用代码进行统一,所以其相关的服务功能均抽取到了本工程。
该工程主要提供ES搜索与数据同步服务,主要功能有:

  • 快递员端搜索功能(搜索字段:运单号、姓名、电话)
  • 新增/全量修改快递员任务
  • 根据取派件id查询快递员任务
  • mq消费者对快递员任务进行新增或更新

2.使用

2.1导入依赖

如需使用搜索微服务功能,需要引入搜索微服务依赖:

 <dependency>
    <groupId>com.sl-express.ms.search</groupId>
    <artifactId>sl-express-ms-search-api</artifactId>
    <version>1.1-SNAPSHOT</version>
</dependency>

2.2feign方法

@ApiIgnore
@FeignClient(value = "sl-express-ms-search", contextId = "CourierTask", path = "courierSearch")
public interface CourierTaskFeign {

    /**
     * 分页查询
     *
     * @param pageQueryDTO 分页查询条件
     * @return 分页查询结果
     */
    @PostMapping("pageQuery")
    PageResponse<CourierTaskDTO> pageQuery(@RequestBody CourierTaskPageQueryDTO pageQueryDTO);

    /**
     * 新增快递员任务
     *
     * @param courierTaskDTO 快递员任务
     */
    @PostMapping
    void saveOrUpdate(@RequestBody CourierTaskDTO courierTaskDTO);

    /**
     * 根据取派件id查询快递员任务
     *
     * @param id 取派件id
     * @return 快递员任务
     */
    @GetMapping("/{id}")
    CourierTaskDTO findById(@PathVariable("id") Long id);
}

2.3 es数据同步

2.3.1 es初始化同步数据

初始化同步数据功能已经做成测试用例,参考test目录下的 InitTest 代码学习,可直接运行进行初始化数据同步。

2.3.2 es数据新增与更新

当调用某个接口使得数据发生新增或者更新时,需要通过mq发送新增或更新消息到队列,然后搜索微服务中的mq消费者处理消息。 需要注意的是创建运单之后,需要将运单号同步到es当中,也是通过mq消息来异步通知的。创建运单的方法里已经发送了mq消息,消息格式已经确定,需要另外写消费者实现逻辑。

 //发送消息,同步更新快递员任务
CourierTaskDTO courierTaskDTO = courierTaskFeign.findById(pickupDispatchTaskDTO.getId());

//构建快递员任务更新内容
    ..........................................
        
mqFeign.sendMsg(Constants.MQ.Exchanges.COURIER_TASK, Constants.MQ.RoutingKeys.COURIER_TASK_SAVE_OR_UPDATE, JSONUtil.toJsonStr(courierTaskDTO));

3.技术方案

3.1环境部署

该工程基于ES提供服务,所以使用前需进行环境部署。主要部署搜索引擎Elasticsearch、可视化操作平台kibana,项目中统一使用7.17.5版本。主要介绍通过docker部署的方式。

3.1.1 Elasticsearch部署

注意:虚拟机中elasticsearch服务和kibana服务已经部署完毕,但是不是开机自启动,自己使用docker命令启动即可。

  1. 部署命令
#拉取es镜像
docker pull elasticsearch:7.17.5

#运行es(注意如需kibana与es同一网络,还需提前创建docker网络)
docker run -d \
    --name elasticsearch \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.17.5
  1. 启动成功页面

docker启动后访问:http://自己的ip:9200,出现以下页面即为成功
es部署成功页面

3.1.2 kibana部署

  1. 部署命令
#拉取kibana镜像
docker pull kibana:7.17.5

#运行kibana
docker run -d \
--name kibana \
# 我们基于没有docker网络进行的部署,所以需要配置es的ip和端口
-e ELASTICSEARCH_HOSTS=http://es的ip:端口 \
-p 5601:5601  \
kibana:7.17.5
  1. 启动成功

启动成功后可以访问:http://你的ip:5601
kibana页面

3.1.3 ik分词器安装

拼音分词器安装同样操作,地址:https://github.com/medcl/elasticsearch-analysis-pinyin/releases/tag/v7.17.5

es自带的standard对中文的分词效果并不理想,所以我们需要安装ik分词器,更好的支持中文分词。

  1. 在线安装ik分词器
# 进入容器内部
docker exec -it elasticsearch /bin/bash

# 在线下载并安装
./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.17.5.zip

#退出
exit

#重启容器
docker restart elasticsearch
  1. 离线安装

国内访问github很慢,在线安装大多数情况会失败,我们推荐离线安装

(1)查看es的plugins挂载的数据卷位置

docker volume inspect es-plugins

显示结果:

[
    {
        "CreatedAt": "2022-07-28T12:53:21+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data这个目录中。

(2)下载对应版本(7.17.5)的ik插件

https://github.com/medcl/elasticsearch-analysis-ik/releases

(3)上传插件到数据卷

首先将下载下来的压缩包解压,重命名为ik;然后将文件夹上传到(1)中查到的数据卷目录

拼音分词器的文件夹名称命名为pinyin

(4)重启容器

docker restart elasticsearch

3.2 springboot集成es

springBoot集成es有三种方法:java api、rest client、data-es,对于java api这种方式,官方已经明确表示在ES 7.0版本中将弃用TransportClient客户端,同时在7.15版本以后,官方宣布弃用了RestHighLevelClient所以我们只引用其他两种方式:

3.2.1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    <version>2.6.6</version>
</dependency>
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>7.17.5</version>
</dependency>

3.2.2 es-api文档

  • es官方开发文档:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/index.html

3.2.3 es数据同步

本项目中我们采用的数据同步技术方案为mq方式

  1. 技术架构图

mq同步es数据技术架构图

  1. 同步操作
  • 数据发生新增或更新发送mq消息
 //发送消息,同步更新快递员任务
CourierTaskDTO courierTaskDTO = courierTaskFeign.findById(pickupDispatchTaskDTO.getId());

//构建快递员任务更新内容
    ..........................................
        
mqFeign.sendMsg(Constants.MQ.Exchanges.COURIER_TASK, Constants.MQ.RoutingKeys.COURIER_TASK_SAVE_OR_UPDATE, JSONUtil.toJsonStr(courierTaskDTO));

3.2.4 es数据初始化同步

初始化同步数据功能已经做成测试用例,参考test目录下的 InitTest 代码学习,可直接运行进行初始化数据同步。

package com.sl.ms.search;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import com.sl.ms.oms.api.OrderFeign;
import com.sl.ms.oms.dto.OrderDTO;
import com.sl.ms.search.entity.CourierTaskEntity;
import com.sl.ms.work.api.PickupDispatchTaskFeign;
import com.sl.ms.work.api.TransportOrderFeign;
import com.sl.ms.work.domain.dto.PickupDispatchTaskDTO;
import com.sl.ms.work.domain.dto.TransportOrderDTO;
import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskStatus;
import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskType;
import com.sl.transport.common.exception.SLException;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * ES初始化数据
 *
 * @Author itcast
 * @Create 2023/3/7 13:55
 **/
@Slf4j
@SpringBootTest
class InitTest {
    @Resource
    private ElasticsearchClient client;
    @Resource
    private OrderFeign orderFeign;
    @Resource
    private PickupDispatchTaskFeign pickupDispatchTaskFeign;
    @Resource
    private TransportOrderFeign transportOrderFeign;
    @Value("${sl.es.index_name}")
    private String indexName;

    /**
     * 初始化同步数据到es
     */
    @Test
    void init() throws IOException {
        // 1.删除所有文档(不需要删除的话,注释这部分代码)
        client.deleteByQuery(d -> d
                .index(indexName)
                .query(q -> q
                        .matchAll(m -> m)));

        // 2.查询全部取派件任务
        List<PickupDispatchTaskDTO> items = pickupDispatchTaskFeign.findAll(null, null, null, null);

        // 3.根据订单id列表批量查询订单
        List<String> orderIds = items.stream().map(item -> item.getOrderId().toString()).collect(Collectors.toList());
        List<OrderDTO> orderDTOS = orderFeign.findByIds(orderIds);
        Map<Long, OrderDTO> map = orderDTOS.stream().collect(Collectors.toMap(OrderDTO::getId, dto -> dto));

        // 4.数据封装为entity
        List<CourierTaskEntity> entities = new ArrayList<>();
        for (PickupDispatchTaskDTO item : items) {
            // 封装快递员任务对象
            CourierTaskEntity courierTaskEntity = this.getCourierTaskEntity(item, map);

            // 封装好的对象,添加到快递员任务列表
            entities.add(courierTaskEntity);
        }

        // 5.循环添加新增操作
        BulkRequest.Builder br = new BulkRequest.Builder();
        for (CourierTaskEntity entity : entities) {
            br.operations(op -> op.index(idx -> idx
                    .index(indexName)
                    .id(String.valueOf(entity.getId()))
                    .document(entity)));
        }

        // 6.批量新增
        client.bulk(br.build());
    }

    /**
     * 封装快递员任务对象
     *
     * @param pickupDispatchTaskDTO 取派件任务
     * @return 快递员任务
     */
    private CourierTaskEntity getCourierTaskEntity(PickupDispatchTaskDTO pickupDispatchTaskDTO, Map<Long, OrderDTO> map) {
        // 1.取派件任务字段复制到快递员任务
        CourierTaskEntity courierTaskEntity = BeanUtil.toBean(pickupDispatchTaskDTO, CourierTaskEntity.class);
        courierTaskEntity.setTaskType(pickupDispatchTaskDTO.getTaskType().getCode());
        courierTaskEntity.setStatus(pickupDispatchTaskDTO.getStatus().getCode());
        courierTaskEntity.setIsDeleted(pickupDispatchTaskDTO.getIsDeleted().getCode());


        // 2.根据id查询订单
        OrderDTO orderDTO = map.get(pickupDispatchTaskDTO.getOrderId());
        if (ObjectUtil.isEmpty(orderDTO)) {
            String errorMsg = CharSequenceUtil.format("id为{}的取派件任务的订单不存在!", pickupDispatchTaskDTO.getId());
            throw new SLException(errorMsg);
        }
        // 3.设置快递员任务的姓名、电话、地址字段
        this.setNameAndPhoneAndAddress(pickupDispatchTaskDTO.getTaskType(), orderDTO, courierTaskEntity);

        // 为快递员任务set运单号:取件任务新任务状态和已取消状态不查运单
        if (ObjectUtil.equal(PickupDispatchTaskType.DISPATCH, pickupDispatchTaskDTO.getTaskType()) || ObjectUtil.equal(PickupDispatchTaskStatus.COMPLETED, pickupDispatchTaskDTO.getStatus())) {
            TransportOrderDTO transportOrderDTO = transportOrderFeign.findByOrderId(pickupDispatchTaskDTO.getOrderId());
            courierTaskEntity.setTransportOrderId(transportOrderDTO.getId());// 运单id
        }
        return courierTaskEntity;
    }

    /**
     * 设置快递员任务的姓名、电话、地址字段
     *
     * @param taskType          任务类型
     * @param orderDTO          订单
     * @param courierTaskEntity 快递员任务
     */
    private void setNameAndPhoneAndAddress(PickupDispatchTaskType taskType, OrderDTO orderDTO, CourierTaskEntity courierTaskEntity) {
        String name;
        String phone;
        String address;
        if (taskType.equals(PickupDispatchTaskType.PICKUP)) {
            name = orderDTO.getSenderName();
            phone = orderDTO.getSenderPhone();
            address = orderDTO.getSenderAddress();
        } else {
            name = orderDTO.getReceiverName();
            phone = orderDTO.getReceiverPhone();
            address = orderDTO.getReceiverAddress();
        }
        courierTaskEntity.setAddress(address);
        courierTaskEntity.setName(name);
        courierTaskEntity.setPhone(phone);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值