1、部分效果展示

map-min

2、获取百度 SDK

百度地图开发平台:传送门

登录之后,创建应用->提交->复制访问 AK码

map

*注意:为了测试方便,Referer 白名单写 *0.0.0.0 就行

3、什么是 HBase

3.1、初识 HBase

jiagou

HBase 是一个面向列式存储的分布式数据库,其设计思想来源于 Google 的 BigTable 论文。HBase 底层存储基于 HDFS 实现,集群的管理基于 ZooKeeper 实现。HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能,基于数据副本机制和分区机制可以轻松实现在线扩容、缩容和数据容灾,是大数据领域中 Key-Value 数据结构存储最常用的数据库方案。

特点:易扩展,海量储存,列式储存,高可靠性,稀疏性

特点之一模块组成:
HBase 可以将数据存储在本地文件系统,也可以存储在 HDFS 文件系统。在生产环境中,HBase 一般运行在 HDFS 上,以 HDFS 作为基础的存储设施。HBase 通过 HBase Client 提供的 Java API 来访问 HBase 数据库,以完成数据的写入和读取。HBase 集群主由HMaster、Region Server 和 ZooKeeper 组成。

module

HMaster

  • 负责管理 RegionServer,实现其负载均衡;
  • 管理和分配 Region,比如在 Region split时分配新的 Region,在 RegionServer 退出时迁移其内的 Region 到其他 RegionServer上;
  • 管理namespace和table的元数据(实际存储在HDFS上);
  • 权限控制(ACL)。

RegionServer

  • 存放和管理本地 Region;
  • 读写HDFS,管理Table中的数据;
  • Client 从 HMaster 中获取元数据,找到 RowKey 所在的 RegionServer 进行读写数据。

ZooKeeper

  • 存放整个 HBase集群的元数据以及集群的状态信息;
  • 实现HMaster主从节点的failover。

3.2、HBase 数据模型

HBase 是一个面向列式存储的分布式数据库。HBase 的数据模型与 BigTable 十分相似。在 HBase 表中,一条数据拥有一个全局唯一的键(RowKey)和任意数量的列(Column),一列或多列组成一个列族(Column Family),同一个列族中列的数据在物理上都存储在同一个 HFile 中,这样基于列存储的数据结构有利于数据缓存和查询。 HBase 中的表是疏松地存储的,因此用户可以动态地为数据定义各种不同的列。HBase中的数据按主键排序,同时,HBase 会将表按主键划分为多个 Region 存储在不同 Region Server 上,以完成数据的分布式存储和读取。

HBase 根据列成来存储数据,一个列族对应物理存储上的一个 HFile,列族包含多列列族在创建表的时候被指定。

data

3.2.1、Column Family

Column Family 即列族,HBase 基于列划分数据的物理存储,一个列族可以包含包意多列。

一般同一类的列会放在一个列族中,每个列族都有一组存储属性:

  • 是否应该缓存在内存中;
  • 数据如何被压缩或行键如何编码等。

HBase 在创建表的时候就必须指定列族。HBase的列族不是越多越好,官方荐一个表的列族数量最好小于或者等于3,过多的列族不利于 HBase 数据的管理和索引。

3.2.2、RowKey

RowKey的概念与关系型数据库中的主键相似,HBase 使用 RowKey 来唯一标识某行的数据。

访问 HBase 数据的方式有三种:

  • 基于 RowKey的单行查询;
  • 基于RowKey的范围查询;
  • 全表扫描查询。

3.2.3、Region

HBase 将表中的数据基于 RowKey 的不同范围划分到不同 Region 上,每个Region都负责一定范围的数据存储和访问。

region

每个表一开始只有一个 Region,随着数据不断插入表,Region 不断增大,当增大到一个阀值的时候,Region 就会等分成两个新的 Region。当table中的行不断增多,就会有越来越多的 Region。另外,Region 是 Hbase 中分布式存储和负载均衡的最小单元,不同的 Region 可以分布在不同的 HRegion Server上。

这样即使有一个包括上百亿条数据的表,由于数据被划分到不同的 Region上,每个 Region 都可以独立地进行写入和查询,HBase 写查询时候可以于多 Region 分布式并发操作,因此访问速度也不会有太大的降低。

3.2.4、TimeStamp

TimeStamp 是实现 HBase 多版本的关键。在HBase 中,使用不同 TimeStamp 来标识相同RowKey对应的不同版本的数据。相同 RowKey的数据按照 TimeStamp 倒序排列。默认查询的是最新的版本,当然用户也可以指定 TimeStamp 的值来读取指定版本的数据。

3.3、列式储存

为什么列式储存会广泛应用在 OLAP 领域,和行式存储相比,它有什么优势?

其实,列式存储并不是一项新技术,最早可以追溯到 1983 年的论文 Cantor。然而,受限于早期的硬件条件和应用场景,传统的事务型数据库(OLTP)如 Oracle、MySQL 等关系型数据库都是以行的方式来存储数据的。

直到近几年分析型数据库(OLAP)的兴起,列式存储这一概念又变得流行,如 HBase、Cassandra 等大数据相关的数据库都是以列的方式来存储数据的。

3.3.1、行式存储的原理与特点

对于 OLTP 场景,大多都是对一整行记录进行增删改查操作的,那么行式存储采用以行的行式在磁盘上存储数据就是一个不错的选择。当查询基于需求字段查询和返回结果时,由于这些字段都埋藏在各行数据中,就必须读取每一条完整的行记录,大量磁盘转动寻址的操作使得读取效率大大降低。

举个例子,下图为员工信息emp表。

emp

数据在磁盘上是以行的形式存储在磁盘上,同一行的数据紧挨着存放在一起。

hang1

对于 emp 表,要查询部门 dept 为 A 的所有员工的名字。

select name from emp where dept = A

由于 dept 的值是离散地存储在磁盘中,在查询过程中,需要磁盘转动多次,才能完成数据的定位和返回结果。

hang2

3.3.2、列式存储的原理与特点

对于 OLAP 场景,一个典型的查询需要遍历整个表,进行分组、排序、聚合等操作,这样一来行式存储中把一整行记录存放在一起的优势就不复存在了。而且,分析型 SQL 常常不会用到所有的列,而仅仅对其中某些需要的的列做运算,那一行中无关的列也不得不参与扫描。

然而在列式存储中,由于同一列的数据被紧挨着存放在了一起,如下图所示。

lie1

那么基于需求字段查询和返回结果时,就不许对每一行数据进行扫描,按照列找到需要的数据,磁盘的转动次数少,性能也会提高。

还是上面例子中的查询,由于在列式存储中 dept 的值是按照顺序存储在磁盘上的,因此磁盘只需要顺序查询和返回结果即可。

lie2

列式存储不仅具有按需查询来提高效率的优势,由于同一列的数据属于同一种类型,如数值类型,字符串类型等,相似度很高,还可以选择使用合适的编码压缩可减少数据的存储空间,进而减少IO提高读取性能。

3.4、HBase 的写入流程

write

3.4.1、Region Server 寻址

  1. HBase Client 访问 ZooKeeper;
  2. 获取写入 Region 所在的位置,即获取 hbase:meta 表位于哪个 Region Server;
  3. 访问对应的 Region Server;
  4. 获取 hbase:meta 表,并查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 Region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问;

3.4.2、写 Hlog

  1. HBase Client 向 Region Server 发送写 Hlog 请求;
  2. Region Server 会通过顺序写入磁盘的方式,将 Hlog 存储在 HDFS 上;

3.4.3、写 MemStore 并返回结果

  1. HBase Client 向 Region Server 发送写 MemStore 请求;
  2. 只有当写 Hlog 和写 MemStore 的请求都成功完成之后,并将反馈给 HBase Client,这时对于整个 HBase Client 写入流程已经完成。

3.4.4、MemStore 刷盘

HBase 会根据 MemStore 配置的刷盘策略定时将数据刷新到 StoreFile 中,完成数据持久化存储。

3.4.5、为什么要把 WAL 加载到 MemStore中,再刷写成 HFile 呢?

WAL (Write-Ahead-Log) 预写日志是 HBase 的 RegionServer 在处理数据插入和删除过程中用来记录操作内容的一种日志。每次Put、Delete等一条记录时,首先将其数据写入到 RegionServer 对应的 HLog 文件中去。

而WAL是保存在HDFS上的持久化文件,数据到达 Region 时先写入 WAL,然后被加载到 MemStore 中。这样就算Region宕机了,操作没来得及执行持久化,也可以再重启的时候从 WAL 加载操作并执行。

那么,我们从写入流程中可以看出,数据进入 HFile 之前就已经被持久化到 WAL了,而 WAL 就是在 HDFS 上的,MemStore 是在内存中的,增加 MemStore 并不能提高写入性能,为什么还要从 WAL 加载到 MemStore中,再刷写成 HFile 呢?

  • 数据需要顺序写入,但 HDFS 是不支持对数据进行修改的;
  • WAL 的持久化为了保证数据的安全性,是无序的;
  • Memstore在内存中维持数据按照row key顺序排列,从而顺序写入磁盘;

所以 MemStore 的意义在于维持数据按照RowKey的字典序排列,而不是做一个缓存提高写入效率。

3.5、HBase 的读取流程

read

3.5.1、Region Server 寻址

HBase Client 请求 ZooKeeper 获取元数据表所在的 Region Server的地址。

3.5.2、Region 寻址

HBase Client 请求 RegionServer 获取需要访问的元数据,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。

3.5.3、数据读取

HBase Client 请求数据所在的 Region Server,获取所需要的数据。 Region 首先在 MemStore 中查找,若命中则返回;如果在MemStore 中找不到,则通过 BloomFilter 判断数据是否存在;如果存在,则在:StoreFile 中扫描并将结果返回客户端。

3.6、HBase 的数据删除

HBase 的数据删除操作并不会立即将数据从磁盘上删除,因为 HBase 的数据通常被保存在 HDFS 中,而 HDFS 只允许新增或者追加数据文件,所以删除操作主要对要被删除的数据进行标记。

当执行删除操作时,HBase 新插入一条相同的 Key-Value 数据,但是 keyType=Delete,这便意味着数据被删除了,直到发生 Major_compaction 操作,数据才会真正地被从磁盘上删除。

HBase这种基于标记删除的方式是按顺序写磁盘的的,因此很容易实现海量数据的快速删除,有效避免了在海量数据中查找数据、执行删除及重建索引等复杂的流程。

4、Docker 安装 HBase

1、拉取镜像 docker pull harisekhon/hbase
2、启动镜像

docker run -d --name hbase-container -p 2181:2181 -p 8082:8082 -p 8085:8085 -p 9090:9090 -p 9095:9095 -p 16000:16000 -p 16020:16020 -p 16010:16010 -p 16201:16201 -p 16301:16301   harisekhon/hbase

3、配置hosts文件(Win:C:\Windows\System32\drivers\etc\hosts Linux:/etc/hosts)中配置如下信息:

IP                容器ID
192.168.126.133    6475573379b1

4、浏览器访问:http://ip地址:16010/master-status

google_hbase

5、IDEA 连接 Docker 查看 Hbase

idea_hbase

6、使用 Hbase 可视化工具查看

[](https://)

5、后端集成

5.1、配置文件

pom.xml依赖

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>2.1.6</version>
    <exclusions>
        <exclusion>
            <groupId>*</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.1.6</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>

application.yml配置

# hbase配置
hbase:
    host: 192.168.126.133
    port: 2181

5.2、工具类

HbaseUtils.java

package com.ruoyi.system.utils;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * User: lilinhan
 * DateTime: 2024/1/19 11:32
 */
@Component
public class HbaseUtils {
    @Value("${hbase.host}")
    private String HBASE_HOST;
    @Value("${hbase.port}")
    private String HBASE_PORT;

    Configuration configuration = null;
    Connection conn;
    Admin admin;
    TableName table;

    // 初始化hbase连接
    @PostConstruct
    public void init() throws Exception {
        configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.quorum", HBASE_HOST);
        configuration.set("hbase.zookeeper.port", HBASE_PORT);
        conn = ConnectionFactory.createConnection(configuration);
        admin = conn.getAdmin();

        System.err.println(configuration);
    }

    /**
     * 删除数据
     * @param tableName     要删除的表名
     * @return boolean
     */
    public boolean deleteTable(String tableName) {
        try {
            admin = conn.getAdmin();
            table = TableName.valueOf(tableName);
            if (admin.tableExists(table)) {
                admin.disableTable(table); // 禁用表
                admin.deleteTable(table); // 删除表
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    // 添加数据
    public void putData(String name, String colFamily, String rowKey, Map<String, String> data) throws Exception {
        TableName tableName = TableName.valueOf(name);
        // 如果表存在,则添加数据
        if (admin.tableExists(tableName)) {
            Table table = conn.getTable(tableName);
            Put put = new Put(Bytes.toBytes(rowKey));
            for (Map.Entry<String, String> entry : data.entrySet()) {
                put.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(entry.getKey()), Bytes.toBytes(entry.getValue()));
            }
            table.put(put);
        } else {
            System.out.println("tableName [" + name + "] does not exist.");
        }
    }

    /**
     * 获取数据
     * @param clazz   传入具体的对象
     * @param tableName     表名
     * @param colFamily
     * @return
     * @throws Exception
     */
    public List getData(Class clazz,String tableName, String colFamily)throws Exception {
        //条件潘墩
        if (tableName == null || tableName.trim().equals(""))
            return null;
        // 初始化返回的数据集合
        List dataList = new ArrayList<>();
        TableName tableHbaseName = TableName.valueOf(tableName);
        // 获取具体的表
        Table table = conn.getTable(tableHbaseName);
        ResultScanner resultScanner = table.getScanner(new Scan());
        // 获取无参构造器
        Constructor constructor = clazz.getConstructor();
        Field[] fields = clazz.getDeclaredFields();
        for (Result result : resultScanner) {
            // 反射创建对象
            Object data = constructor.newInstance();
            for (Cell cell : result.rawCells()) {
                //列族
                String family = Bytes.toString(cell.getFamilyArray(),cell.getFamilyOffset(), cell.getFamilyLength());
                //列
                String qualifier =Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(),cell.getQualifierLength());
                //值
                String value = Bytes.toString(cell.getValueArray(),cell.getValueOffset(), cell.getValueLength());
                // 获取指定属性
                Field field = clazz.getDeclaredField(qualifier);
                field.setAccessible(true);
                if (value!=null){
                    if (field.getType() == Float.class){
                        field.set(data,new Float(value));
                    }
                    if (field.getType() == Double.class){
                        field.set(data,new Double(value));
                    }
                    if (field.getType() == Long.class){
                        field.set(data,new Long(value));
                    }
                    if (field.getType() == Integer.class){
                        field.set(data,new Integer(value));
                    }
                    if (field.getType() == String.class){
                        field.set(data,value);
                    }
                }
            }
            //存入TopicEvent topicEvent中
            dataList.add(data);
        }
        return dataList;
    }
}

5.3、部分代码

DriverController.java 控制层

package com.ruoyi.traces.controller;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.vo.LocationInfoVo;
import com.ruoyi.system.service.ITWaybillService;
import com.ruoyi.traces.domain.TraceLngLat;
import com.ruoyi.traces.service.DriverService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/traces/driver")
public class DriverController {

    @Autowired
    private DriverService driverService;

    @Autowired
    ITWaybillService tWaybillService;

    // 从hbase获取地址信息
    @GetMapping("/getTransportInfoToHbase")
    public AjaxResult getTransportInfoToHbase(){
        List<LocationInfoVo> transportInfoToHbase = tWaybillService.getTransportInfoToHbase();
        return AjaxResult.success(transportInfoToHbase);
    }

    // 保存定位信息到hbase
    @PostMapping("/saveAndUpdateTransportInfoToHbase")
    public AjaxResult saveAndUpdateTransportInfoToHbase(@RequestBody LocationInfoVo locationInfoVo) throws Exception {
        System.err.println(locationInfoVo);
        tWaybillService.saveAndUpdateTransportInfoToHbase(locationInfoVo);
        return  AjaxResult.success("添加成功");
    }

    @GetMapping("/listTransport/{driverId}")
    public AjaxResult listTransport(@PathVariable("driverId") String driverId) {
        return AjaxResult.success(driverService.listTransport(driverId));
    }

    @PostMapping("/saveAndUpdateTransportInfoToDb")
    public AjaxResult saveAndUpdateTransportInfoToDb(@RequestBody TraceLngLat traceLngLat) {
        System.out.println("lat   " + traceLngLat);
        return AjaxResult.success(driverService.saveAndUpdateTransportInfoToDb(traceLngLat));
    }

}

ITWayBillService.java 服务接口

package com.ruoyi.system.service;

import com.ruoyi.system.domain.TDict;
import com.ruoyi.system.domain.TWaybill;
import com.ruoyi.system.domain.vo.DriverVo;
import com.ruoyi.system.domain.vo.LocationInfoVo;
import com.ruoyi.system.domain.vo.WaybillVo;

import java.util.List;

/**
 * 运货单Service接口
 *
 * @author ruoyi
 * @date 2024-01-17
 */
public interface ITWaybillService {
    /**
     * 查询运货单
     *
     * @param id 运货单ID
     * @return 运货单
     */
    public TWaybill selectTWaybillById(Integer id);

    /**
     * 查询运货单列表
     *
     * @param tWaybill 运货单
     * @return 运货单集合
     */
    public List<TWaybill> selectTWaybillList(TWaybill tWaybill);

    /**
     * 新增运货单
     *
     * @param tWaybill 运货单
     * @return 结果
     */
    public int insertTWaybill(TWaybill tWaybill);

    /**
     * 修改运货单
     *
     * @param tWaybill 运货单
     * @return 结果
     */
    public int updateTWaybill(TWaybill tWaybill);

    /**
     * 批量删除运货单
     *
     * @param ids 需要删除的运货单ID
     * @return 结果
     */
    public int deleteTWaybillByIds(Integer[] ids);

    /**
     * 删除运货单信息
     *
     * @param id 运货单ID
     * @return 结果
     */
    public int deleteTWaybillById(Integer id);

    // 获取检查、质检人、质量多选框
    List<TDict> getDictList();

    // 获取司机列表
    List<DriverVo> driverList();

    // 添加运输信息
    int saveWay(WaybillVo waybillVo);

    // 获取运输列表
    List<TWaybill> selectWayList(TWaybill waybill);

    // 逻辑删除
    int delWay(Integer id);

    // 修改运输信息
    int updateWay(WaybillVo waybillVo);

    // 获取当前行的运输信息
    WaybillVo getWayById(Integer id);

    // 保存定位信息到hbase
    void saveAndUpdateTransportInfoToHbase(LocationInfoVo locationInfoVo) throws Exception;

    // 从hbase获取地址信息
    List<LocationInfoVo> getTransportInfoToHbase();

}

ITWayBillServiceImpl.java 服务实现类

package com.ruoyi.system.service.impl;

import cn.hutool.json.JSONUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.system.domain.TDict;
import com.ruoyi.system.domain.TWaybill;
import com.ruoyi.system.domain.TWaybillDict;
import com.ruoyi.system.domain.vo.DriverVo;
import com.ruoyi.system.domain.vo.LocationInfoVo;
import com.ruoyi.system.domain.vo.WaybillVo;
import com.ruoyi.system.mapper.TWaybillMapper;
import com.ruoyi.system.service.ITWaybillService;
import com.ruoyi.system.utils.HbaseUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 运货单Service业务层处理
 *
 * @author ruoyi
 * @date 2024-01-17
 */
@Service
public class TWaybillServiceImpl implements ITWaybillService {
    @Autowired
    private TWaybillMapper tWaybillMapper;

    @Autowired
    HbaseUtils hbaseUtils;

    /**
     * 查询运货单
     *
     * @param id 运货单ID
     * @return 运货单
     */
    @Override
    public TWaybill selectTWaybillById(Integer id) {
        return tWaybillMapper.selectTWaybillById(id);
    }

    /**
     * 查询运货单列表
     *
     * @param tWaybill 运货单
     * @return 运货单
     */
    @Override
    public List<TWaybill> selectTWaybillList(TWaybill tWaybill) {
        return tWaybillMapper.selectTWaybillList(tWaybill);
    }

    /**
     * 新增运货单
     *
     * @param tWaybill 运货单
     * @return 结果
     */
    @Override
    public int insertTWaybill(TWaybill tWaybill) {
        tWaybill.setCreateTime(DateUtils.getNowDate());
        return tWaybillMapper.insertTWaybill(tWaybill);
    }

    /**
     * 修改运货单
     *
     * @param tWaybill 运货单
     * @return 结果
     */
    @Override
    public int updateTWaybill(TWaybill tWaybill) {
        tWaybill.setUpdateTime(DateUtils.getNowDate());
        return tWaybillMapper.updateTWaybill(tWaybill);
    }

    /**
     * 批量删除运货单
     *
     * @param ids 需要删除的运货单ID
     * @return 结果
     */
    @Override
    public int deleteTWaybillByIds(Integer[] ids) {
        return tWaybillMapper.deleteTWaybillByIds(ids);
    }

    /**
     * 删除运货单信息
     *
     * @param id 运货单ID
     * @return 结果
     */
    @Override
    public int deleteTWaybillById(Integer id) {
        return tWaybillMapper.deleteTWaybillById(id);
    }

    @Override
    public List<TDict> getDictList() {
        return tWaybillMapper.getDictList();
    }

    @Override
    public List<DriverVo> driverList() {
        return tWaybillMapper.driverList();
    }

    @Override
    @Transactional
    public int saveWay(WaybillVo waybillVo) {
        System.err.println(waybillVo);

        waybillVo.setCreateTime(new Date());
        // 添加运输表
        tWaybillMapper.saveWay(waybillVo);
        //  添加运输详情关联表
        tWaybillMapper.saveWayItem(waybillVo);
        Integer id = waybillVo.getId();

        int[] checkIds = waybillVo.getCheckIds();
        int[] userIds = waybillVo.getUserIds();

        int result = 0;
        // 删除关联表
        tWaybillMapper.delWayDict(checkIds);
        for (int i = 0; i < checkIds.length; i++) {
            TWaybillDict waybillDict = new TWaybillDict();
            waybillDict.setCheckId(checkIds[i]);
            for (int j = 0; j < userIds.length; j++) {
                waybillDict.setUserId(userIds[j]);
                waybillDict.setZhiId(waybillVo.getZhiIds());
                waybillDict.setWaybillId(id);
                // 循环添加关联表
                result = tWaybillMapper.saveWayDict(waybillDict);
            }
        }

        return result;
    }

    @Override
    public List<TWaybill> selectWayList(TWaybill waybill) {
        return tWaybillMapper.selectWayList(waybill);
    }

    @Override
    public int delWay(Integer id) {
        return tWaybillMapper.delWay(id);
    }

    @Override
    public int updateWay(WaybillVo waybillVo) {
        // 修改运输主表
        waybillVo.setUpdateTime(new Date());
        tWaybillMapper.updateWay(waybillVo);
        // 修改运输明细表
        tWaybillMapper.updateWayItem(waybillVo);

        int[] checkIds = waybillVo.getCheckIds();
        int[] userIds = waybillVo.getUserIds();
        int result = 0;

        // 删除关系表
        tWaybillMapper.delWayDict(checkIds);
        for (int i = 0; i < checkIds.length; i++) {
            TWaybillDict waybillDict = new TWaybillDict();
            waybillDict.setCheckId(checkIds[i]);
            for (int j = 0; j < userIds.length; j++) {
                waybillDict.setUserId(userIds[j]);
                waybillDict.setZhiId(waybillVo.getZhiIds());
                waybillDict.setWaybillId(waybillVo.getId());
                // 循环添加关联表
                result = tWaybillMapper.saveWayDict(waybillDict);
            }
        }

        return result;
    }

    @Override
    public WaybillVo getWayById(Integer id) {
        WaybillVo wayById = tWaybillMapper.getWayById(id);
        // 截取拼接的数据
        String[] citys = wayById.getCityName().split(",");
        String[] checks = wayById.getCheckName().split(",");
        String[] users = wayById.getUserName().split(",");
        int zhi = Integer.parseInt(wayById.getZhiName());

        // 将string数组转成int数组
        int[] cityArray = Arrays.stream(citys).mapToInt(Integer::parseInt).toArray();
        int[] checkArray = Arrays.stream(checks).mapToInt(Integer::parseInt).toArray();
        int[] userArray = Arrays.stream(users).mapToInt(Integer::parseInt).toArray();

        // 重新赋值
        wayById.setValue(cityArray);
        wayById.setCheckIds(checkArray);
        wayById.setUserIds(userArray);
        wayById.setZhiIds(zhi);
        System.err.println(wayById);
        return wayById;
    }

    @Override
    public void saveAndUpdateTransportInfoToHbase(LocationInfoVo locationInfoVo){
        Map<String,String> map = new HashMap<>();
        map.put("bw", JSONUtil.toJsonStr(locationInfoVo));
        try {
            hbaseUtils.putData("test","test","detail",map);
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }

    @Override
    public List<LocationInfoVo> getTransportInfoToHbase() {
        try {
            List<LocationInfoVo> data = hbaseUtils.getData(LocationInfoVo.class, "test", "test");
            return data;
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }
    }
}

6、前端集成

6.1、导入组件

在 main.js 中进入地图插件:

//引入地图插件
import VueAMap from 'vue-amap';
Vue.use(VueAMap);

//高德地图方法
VueAMap.initAMapApiLoader({
  key: '3e5db00ff818adf2b2e2f600f0a72608',
   v: '1.4.4'
});

// 百度地图
Vue.use(BaiduMap, {
    // ak 是在百度地图开发者平台申请的密钥
    ak: 'Pa1qWRlVR55b5y7pG4hGt67lOzPGi58u'
})

6.2、API 接口

import request from '@/utils/request'

export function listTransport(driverId) {
    return request({
        url: '/traces/driver/listTransport/' + driverId,
        method: 'get'
    })
}

export function getLocation() {
    return request({
        url: '/traces/driver/getTransportInfoToHbase',
        method: 'post',
        params: {
            cropId: ''
        }
    })
}

// 保存定位信息到hbase
export function saveAndUpdateTransportInfoToHbase(data) {
    return request({
        url: '/traces/driver/saveAndUpdateTransportInfoToHbase',
        method: 'post',
        data: data
    })
}

export function saveAndUpdateTransportInfoToDb(data) {
    return request({
        url: '/traces/driver/saveAndUpdateTransportInfoToDb',
        method: 'post',
        data: data
    })
}

export function updateTransportStatus(cropsId) {
    return request({
        url: '/traces/driver/updateTransportStatus/' + cropsId,
        method: 'get'
    })
}

6.3、司机页面

<template>
    <div class="app-container">
        <el-divider>待处理业务</el-divider>
        
        <br/>
        <el-table :data="transportList">
            <el-table-column type="selection" width="55" align="center"/>
            <el-table-column label="货物编号" prop="cropsId"/>
            <el-table-column label="通知人" prop="farmerNickName"/>
            <el-table-column label="部门" prop="farmerDept"/>
            <el-table-column label="时间" prop="time"/>
            <el-table-column label="备注" prop="remarks"/>
            <el-table-column label="状态" prop="status">
                <template slot-scope="scope">
                    <el-tag v-if="scope.row.status === 0">未运输</el-tag>
                    <el-tag v-if="scope.row.status === 1">运输中</el-tag>
                    <el-tag v-if="scope.row.status === 2">运输完成</el-tag>
                </template>
            </el-table-column>
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                <template slot-scope="scope">
                    <el-button v-show="scope.row.status === 0" size="mini" type="text"
                               @click="startTransport(scope.row)"
                    >开始配送
                    </el-button>
                    <el-button v-show="scope.row.status === 1" size="mini" type="text"
                               @click="transportLocation1(scope.row)"
                    >中途定位
                        <el-button v-show="scope.row.status === 1" size="mini" type="text"
                                   @click="toMap"
                        >物流跟踪
                    </el-button>
                    <el-button v-show="scope.row.status === 1" size="mini" type="text"
                               @click="isOk(scope.row)"
                    >配送完毕
                    </el-button>
                    </el-button>
                    <!--                    <el-button size="mini" type="text" @click="isOk(scope.row)">物流追踪</el-button>-->
                </template>
            </el-table-column>
        </el-table>
        
        <div v-show="false" id="tip"></div>
        <div v-show="false" id="lng"></div>
        <div v-show="false" id="lat"></div>
        
        <el-dialog title="中途定位" :visible="showPos">
            <el-row :gutter="15">
                <el-form ref="elForm" :model="formData" size="medium" label-width="100px">
                    <el-col :span="24">
                        <el-form-item label="位置">
                            <el-input id="formData.address" v-model="formData.address"
                                      placeholder="请输入位置"  clearable  :style="{width: '100%'}"
                            ></el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="经度">
                            <el-input id="formData.lng" v-model="formData.lng" placeholder="请输入经度"
                                      clearable :style="{width: '100%'}"
                            >
                            </el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="维度">
                            <el-input id="formData.lat" v-model="formData.lat"
                                      placeholder="请输入维度" clearable :style="{width: '100%'}"
                            >
                            </el-input>
                        </el-form-item>
                    </el-col>
                    <el-col :span="15">
                        <el-form-item label="按钮">
                            <el-button type="primary" size="mini" @click="savePos"> 保存</el-button>
                            <el-button type="primary" size="mini" @click="closePos"> 取消</el-button>
                            <el-button type="primary" size="mini" @click="relocate">重新定位</el-button>
                        </el-form-item>
                    </el-col>
                </el-form>
            </el-row>
        </el-dialog>
    </div>
</template>

<script>
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from '@/api/system/dept'
import {
    listTransport,
    saveAndUpdateTransportInfoToDb,
    updateTransportStatus,
    saveAndUpdateTransportInfoToHbase
} from '../../../api/trace/driver'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { formatDate } from '@/utils'

export default {
    name: 'Dept',
    components: { Treeselect },
    data() {
        return {
            geolocation: null, // 定位的组件
            // 定位信息显示
            showPos: false,
            formData: {
                address: '',
                lng: 0,
                lat: 0
            },
            noticeDetaiDialog: false,
            // 遮罩层
            loading: true,
            // 显示搜索条件
            showSearch: true,
            // 表格树数据
            deptList: [],
            // 部门树选项
            deptOptions: [],
            // 弹出层标题
            title: '',
            // 是否显示弹出层
            open: false,
            // 状态数据字典
            statusOptions: [],
            // 查询参数
            queryParams: {
                deptName: undefined,
                status: undefined
            },
            // 表单参数
            form: {},
            
            transportList: [],
            cityDetail: '',
            lng: Number,
            lat: Number
        }
    },
    created() {
        this.getList()
    },
    
    mounted() {
        const _this = this
        
        AMap.plugin('AMap.Geolocation', function() {
            var geolocation = new AMap.Geolocation({
                // 是否使用高精度定位,默认:true
                enableHighAccuracy: true,
                // 设置定位超时时间,默认:无穷大
                timeout: 10000,
                // 定位按钮的停靠位置的偏移量,默认:Pixel(10, 20)
                buttonOffset: new AMap.Pixel(10, 20),
                //  定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false
                zoomToAccuracy: true,
                //  定位按钮的排放位置,  RB表示右下
                buttonPosition: 'RB'
            })
            //方便调用
            _this.geolocation = geolocation
            geolocation.getCurrentPosition()
            AMap.event.addListener(geolocation, 'complete', onComplete)
            AMap.event.addListener(geolocation, 'error', onError)
            
            function onComplete(data) {
                this.cityDetail = data.addressComponent.province + data.addressComponent.city + data.addressComponent.district + data.addressComponent.township
                this.lng = data.position.lng
                this.lat = data.position.lat
                const str = []
                str.push(data.position.lng)
                str.push(data.position.lat)
                
                _this.locateOk(data.position.lng, data.position.lat, this.cityDetail,data.formattedAddress,data.addressComponent.adcode)
                
                document.getElementById('lng').innerHTML = data.position.lng
                document.getElementById('lat').innerHTML = data.position.lat
                document.getElementById('tip').innerHTML = this.cityDetail
                
                // console.log(data)
                // console.log(data.addressComponent.adcode)
                // console.log(data.formattedAddress)
            }
            
            function onError(data) {
                console.log('定位错误   ' + JSON.stringify(data))
            }
        })
    },
    
    methods: {
        toMap(){
            this.$router.push("/trace/map")
        },
        localError() {
            alert('localtion error')
        },
        locateOk(lng, lat, address,xsAddress,code) {
            //locateOk(data.position.lng,data.position.lat,this.cityDetail)
            this.formData.lng = lng
            this.formData.lat = lat
            this.formData.address = address
            this.formData.xsAddress = xsAddress
            this.formData.code = code
        },
        // 重新定位
        relocate() {
            this.geolocation.getCurrentPosition()
        },
        closePos() {
            this.showPos = false
        },
        savePos() {
            this.formData.cropsId = 'THISISCROS'
            this.formData.ddress = this.formData.address
            saveAndUpdateTransportInfoToHbase(this.formData).then(res=>{
                if(res.code==200){
                    this.$message.success(res.msg);
                    this.showPos = false;
                    this.getList();
                }
            })
        },
        isOk(row) {
            const transportArray = []
            const id = new this.$snowFlakeId().generate()
            transportArray.push(id)
            transportArray.push(id)
            transportArray.push(this.$store.getters.name)
            transportArray.push(this.$store.getters.nickName)
            transportArray.push(this.$store.getters.phonenumber)
            transportArray.push(this.$store.getters.deptName)
            transportArray.push(formatDate(new Date()))
            transportArray.push(document.getElementById('tip').innerHTML)
            transportArray.push(row.cropsId)
            transportArray.push('终点站')
            this.$confirm('是否确认配送完毕', '配送', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
                .then(() => {
                    this.$httpBlock
                        .post(this.$httpUrl + '/driverapi/createTransport', { transportArray: transportArray })
                        .then(res => {
                            if (res.status === 200) {
                                this.msgSuccess('数据上链成功')
                                this.growDialog = false
                                const transportInfo = {
                                    lng: Number(document.getElementById('lng').innerHTML),
                                    lat: Number(document.getElementById('lat').innerHTML),
                                    cropsId: row.cropsId,
                                    status: 2,
                                    id: row.id,
                                    outFactoryStatus: 0
                                }
                                saveAndUpdateTransportInfoToDb(transportInfo)
                                    .then(res => {
                                        this.getList()
                                    })
                                    .catch(err => {
                                    })
                            }
                        })
                        .catch(err => {
                            this.msgError('存储异常 ' + err)
                        })
                    this.$message({
                        type: 'success',
                        message: '配送成功!'
                    })
                })
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '取消'
                    })
                })
        },
        /**
         * 姜定位经纬度保存到数据库当中
         * @param row
         */
        transportLocation1(row) {
            this.showPos = true
            
        },
        transportLocation(row) {
            const transportArray = []
            const id = new this.$snowFlakeId().generate()
            transportArray.push(id)
            transportArray.push(id)
            transportArray.push(this.$store.getters.name)
            transportArray.push(this.$store.getters.nickName)
            transportArray.push(this.$store.getters.phonenumber)
            transportArray.push(this.$store.getters.deptName)
            transportArray.push(formatDate(new Date()))
            transportArray.push(document.getElementById('tip').innerHTML)
            transportArray.push(row.cropsId)
            transportArray.push('中途定位')
            this.$confirm(document.getElementById('tip').innerHTML, '定位', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
                .then(() => {
                    this.$httpBlock
                        .post(this.$httpUrl + '/driverapi/createTransport', { transportArray: transportArray })
                        .then(res => {
                            if (res.status === 200) {
                                this.msgSuccess('数据上链成功')
                                this.growDialog = false
                                const transportInfo = {
                                    lng: Number(document.getElementById('lng').innerHTML),
                                    lat: Number(document.getElementById('lat').innerHTML),
                                    cropsId: row.cropsId,
                                    status: 1
                                }
                                saveAndUpdateTransportInfoToDb(transportInfo)
                                    .then(res => {
                                        this.getList()
                                    })
                                    .catch(err => {
                                    
                                    })
                            }
                        })
                        .catch(err => {
                            this.msgError('存储异常 ' + err)
                        })
                    
                })
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '取消定位'
                    })
                })
        },
        /**
         * @param {Object} row开始配送
         */
        startTransport(row) {
            const transportArray = []
            const id = new this.$snowFlakeId().generate()
            transportArray.push(id)
            transportArray.push(id)
            transportArray.push(this.$store.getters.name)
            transportArray.push(this.$store.getters.nickName)
            transportArray.push(this.$store.getters.phonenumber)
            transportArray.push(this.$store.getters.deptName)
            transportArray.push(formatDate(new Date()))
            transportArray.push(document.getElementById('tip').innerHTML)
            transportArray.push(row.cropsId)
            transportArray.push('始发地')
            this.$confirm('是否开始运送货物', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
                .then(() => {
                        
                        this.msgSuccess('数据上链成功')
                        this.growDialog = false
                        const transportInfo = {
                            lng: Number(document.getElementById('lng').innerHTML),
                            lat: Number(document.getElementById('lat').innerHTML),
                            cropsId: row.cropsId,
                            id: row.id,
                            status: 1
                        }
                        saveAndUpdateTransportInfoToDb(transportInfo)
                            .then(res => {
                                
                                this.getList()
                            })
                            .catch(err => {
                            
                            })
                    }
                    /*{
              
                                  this.$httpBlock
                                      .post(this.$httpUrl + '/driverapi/createTransport', { transportArray: transportArray })
                                      .then(res => {
                                          /!*if (res.status === 200) *!/
                        {
                                              this.msgSuccess('数据上链成功');
                                              this.growDialog = false;
                                              const transportInfo = {
                                                  lng: Number(document.getElementById('lng').innerHTML),
                                                  lat: Number(document.getElementById('lat').innerHTML),
                                                  cropsId: row.cropsId,
                                                  id: row.id,
                                                  status: 1,
                                              };
                                              saveAndUpdateTransportInfoToDb(transportInfo)
                                                  .then(res => {
              
                                                      this.getList();
                                                  })
                                                  .catch(err => {
              
                                                  });
                                          }
                                      })
                                      .catch(err => {
                                          this.msgError('存储异常 ' + err);
              
              
                                      });
                                  this.$message({
                                      type: 'success',
                                      message: '开始运送!'
                                  });
                              }*/)
                .catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消运送'
                    })
                })
        },
        
        getList() {
            listTransport(this.$store.getters.name).then(res => {
                this.transportList = res.data
            })
        },
        /** 转换部门数据结构 */
        normalizer(node) {
            if (node.children && !node.children.length) {
                delete node.children
            }
            return {
                id: node.deptId,
                label: node.deptName,
                children: node.children
            }
        },
        // 字典状态字典翻译
        statusFormat(row, column) {
            return this.selectDictLabel(this.statusOptions, row.status)
        },
        // 取消按钮
        cancel() {
            this.open = false
            this.reset()
        },
        // 表单重置
        reset() {
            this.form = {
                deptId: undefined,
                parentId: undefined,
                deptName: undefined,
                orderNum: undefined,
                leader: undefined,
                phone: undefined,
                email: undefined,
                status: '0'
            }
            this.resetForm('form')
        },
        /** 搜索按钮操作 */
        handleQuery() {
            this.getList()
        },
        /** 重置按钮操作 */
        resetQuery() {
            this.resetForm('queryForm')
            this.handleQuery()
        },
        /** 新增按钮操作 */
        handleAdd(row) {
            this.reset()
            if (row != undefined) {
                this.form.parentId = row.deptId
            }
            this.open = true
            this.title = '添加部门'
            listDept().then(response => {
                this.deptOptions = this.handleTree(response.data, 'deptId')
            })
        },
        /** 修改按钮操作 */
        handleUpdate(row) {
            this.reset()
            getDept(row.deptId).then(response => {
                this.form = response.data
                this.open = true
                this.title = '修改部门'
            })
            listDeptExcludeChild(row.deptId).then(response => {
                this.deptOptions = this.handleTree(response.data, 'deptId')
            })
        },
        /** 提交按钮 */
        submitForm: function() {
            this.$refs['form'].validate(valid => {
                if (valid) {
                    if (this.form.deptId != undefined) {
                        updateDept(this.form).then(response => {
                            if (response.code === 200) {
                                this.msgSuccess('修改成功')
                                this.open = false
                                this.getList()
                            }
                        })
                    } else {
                        addDept(this.form).then(response => {
                            if (response.code === 200) {
                                this.msgSuccess('新增成功')
                                this.open = false
                                this.getList()
                            }
                        })
                    }
                }
            })
        },
        /** 删除按钮操作 */
        handleDelete(row) {
            this.$confirm('是否确认删除名称为"' + row.deptName + '"的数据项?', '警告', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
                .then(function() {
                    return delDept(row.deptId)
                })
                .then(() => {
                    this.getList()
                    this.msgSuccess('删除成功')
                })
                .catch(function() {
                })
        }
    }
}
</script>

6.4、地图页面

<script>
import {BaiduMap, BmDriving, BmGeolocation, BmlLushu, BmNavigation, BmPanorama, BmScale} from "vue-baidu-map";

export default {
    components: {
        BaiduMap,
        BmlLushu,
        BmScale,
        BmDriving,
        BmPanorama,
        BmNavigation,
        BmGeolocation,
    },
    data(){
        return{
            BMap: null,
            map: null,
            // 初始缩放比
            mapZoom: 15,
            // 中心坐标
            center: {lng: 116.30024495819092, lat: 40.041272711966954},
            // 开始坐标
            startCenter: {lng: 116.23720231750488, lat: 40.21736198759237},
            // 结束坐标
            endCenter: {lng: 116.39410081604004, lat: 40.02420230893232},
            // 允许鼠标滚轮缩放
            scrollZoom:true,
            // 信息窗体内容
            detail:'一言不合就开车',
            // 移动速度
            speed:5000,
            // 是否前进
            play: true,
            // 构成路线的坐标点
            path: [],
            // 小车图标
            icon: {
                url: 'http://api.map.baidu.com/library/LuShu/1.2/examples/car.png',
                size: {width: 52, height: 26},
                opts: {anchor: {width: 27, height:13}}
            }
        }
    },
    methods:{
        stopCar(){
            this.play = false;
        },
        startCar(){
            this.play = true;
        },
        handleSearchComplete(res){
            console.log(res)
            console.log(res.getPlan(0))
            console.log(res.getPlan(0).getRoute(0))
            console.log(res.taxiFare.remark)
            this.path = res.getPlan(0).getRoute(0).getPath()
        },
        onReady(){
        
        },
        syncCenterAndZoom(e){
            this.center = e.target.getCenter();
            this.mapZoom = e.target.getZoom();
        }
    }
}
</script>

<template>
    <div>
        <input v-model="center.lng">
        <input v-model="center.lat">
        <input v-model="mapZoom">
        <el-button type="primary" size="mini" @click="stopCar">停止</el-button>
        <el-button type="primary" size="mini" @click="startCar">开始</el-button>
        <baidu-map
            class="bm-view"
            :center="center"
            :zoom="mapZoom"
            :scroll-wheel-zoom="scrollZoom"
            @ready="onReady"
            @moving="syncCenterAndZoom"
            @zoomend="syncCenterAndZoom">
            <bm-driving :start="endCenter" :end="startCenter" @searchcomplete="handleSearchComplete" :panel="false" :autoViewport="true"></bm-driving>
            <bml-lushu
                @stop="stopCar"
                @start="startCar"
                :speed="speed"
                :path="path"
                :icon="icon"
                :play="play"
                :content="detail"
                :rotation="true">
            </bml-lushu>
            <bm-scale anchor="BMAP_ANCHOR_TOP_RIGHT"></bm-scale>
            <bm-navigation anchor="BMAP_ANCHOR_TOP_RIGHT"></bm-navigation>
            <bm-geolocation anchor="BMAP_ANCHOR_BOTTOM_RIGHT" :showAddressBar="true" :autoLocation="true"></bm-geolocation>
        </baidu-map>
    </div>
</template>

<style scoped>
.bm-view {
    height: calc(800px - 88px);
}
</style>
标签:no tag
本文到此就结束啦
Last modification:March 22, 2024
如果觉得我的文章对你有用,请随意赞赏