Spring Boot配合Postgresql和MybatisPlus实现外卖平台常见的距离你xxx米功能

2,350 阅读3分钟

简介

最近在做一个业务功能,需求涉及到了通过终端的定位获取距离终端最近的实体店铺的需求。因为之前没有涉及到地理位置坐标计算距离的知识,只知道redis可以计算两个坐标点(经纬度)之间的距离。但是不满足目前的需求,因为除了计算距离外还要进行筛选、排序、分页。所以就不考虑在redis实现。更不可能全表查询内存排序。所以上网搜索资料,找到了pgsql的插件postgis,是数据库层面支持经纬度距离计算的插件。

postgis官网:postgis.net/

postgis安装

postgis的安装其实是有两种方法的。

第一种是在安装pgsql的时候通过stack builder进行安装。但是这种可能会出现现在不下来,活着很慢会卡顿的情况。

所以我是使用第二种方法安装,直接官网下载postgis插件安装包进行安装,整个过程也很简单。我使用的是pgsql 14(postgis需要和pgsql版本对应,具体可以看官网介绍)。安装过程大概就是下面的几步: 勾选创建数据库 选择pgsql安装目录 输入密码账号 默认数据库名称

然后选下面的步骤全部是即可

创建数据表

-- 先创建数据库,然后执行创建插件postgis
CREATE EXTENSION postgis;

create table t_store
(
    id          serial                   not null
        constraint t_store_pk
            primary key,
    name        varchar(64)              not null,
    longitude   varchar,
    latitude    varchar                  not null,
    geom        geometry(Point, 4326) not null,
    create_time timestamp default now()  not null,
    update_time timestamp default now()  not null
);
comment on column t_store.name is '名称';
comment on column t_store.longitude is '经度';
comment on column t_store.latitude is '纬度';
comment on column t_store.geom is '地理位置信息';
alter table t_store
    owner to postgres;

搭建Spring Boot + MybatisPlus开发框架

搭建代码主要有下面几个注意点。

添加对应依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.1'
    // postgis 插件依赖包
    compile group: 'net.postgis', name: 'postgis-jdbc', version: '2.5.1'
    compile group: 'org.postgresql', name: 'postgresql'
}

添加配置

spring.application.name=gradle_test    
server.port=8080

# pgsql
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgis_test
spring.datasource.username=postgres
spring.datasource.password=123456

# mybatis config :自定义的类型转换类
mybatis-plus.type-handlers-package=com.unfbx.gradle_test.config.typehandler

2、自定义TypeHandler来处理pgsql的地理位置字段

package com.unfbx.gradle_test.config.typehandler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.postgis.Geometry;
import org.postgis.PGgeometry;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 描述:
 *
 * @author
 * @date 2022-04-03
 */
public class AbstractGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {


    /**
     * 表示向PreparedStatement里面设置值
     *
     * @param ps
     * @param i
     * @param parameter
     * @param jdbcType
     * @throws SQLException
     */
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        PGgeometry geometry = new PGgeometry();
        geometry.setGeometry(parameter);
        ps.setObject(i, geometry);

    }

    /**
     * 根据列名获取值
     *
     * @param rs
     * @param columnName
     * @return
     * @throws SQLException
     */
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnName);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }

    /**
     * 根据列索引位置获取值
     *
     * @param rs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnIndex);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }

    /**
     * 获取值  通过列索引
     *
     * @param cs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) cs.getObject(columnIndex);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }
}

自定义坐标点处理类型转换继承AbstractGeometryTypeHandler

@MappedTypes(Point.class)
public class PointTypeHandler extends AbstractGeometryTypeHandler<Point> {
}

坐标转换工具

public class CoordinatesUtil {
    public static Point buildPoint(String longitude, String latitude) throws Exception {
        if (StringUtils.isEmpty(longitude) || StringUtils.isEmpty(latitude)) {
            throw new Exception("坐标数据异常");
        }
        try {
            Point point = new Point(Double.parseDouble(longitude),Double.parseDouble(latitude));
            return point;
        } catch (Exception e) {
            throw e;
        }
    }
}

接口功能测试

这里只做了俩接口。坐标经纬度拾取直接使用百度地图或者高德地图即可。

百度地图坐标拾取系统

新增一个经纬度信息

@PostMapping("/store")
public String add(@RequestBody Store store) throws Exception {
     store.setGeom(CoordinatesUtil.buildPoint(store.getLongitude(), store.getLatitude()));
     storeService.save(store);
     return "添加成功,id:" + store.getId();
}

插入数据库的经纬度+geom信息

根据定位信息查询距离内的商户

请求参数携带经纬度信息。下面查询的是距离南汇嘴10000m以内的地方。

@PostMapping("/stores")
public List<StoreResp> queryList(@RequestBody StoreReq req){
    return storeService.queryList(req);
}
{
    "longitude": "121.979515",
    "latitude": "30.888643",
    "distance":10000
}

返回信息

[    {        "id": 1,        "name": "上海天文馆",        "distance": 5585.5020385    },    {        "id": 2,        "name": "南汇嘴",        "distance": 0.0    }]

数据库查询的sql其实是:

select
    id,   name,geom,
    ST_Distance(ST_GeographyFromText(ST_AsText('POINT(121.926753 30.912628)')),
                ST_GeographyFromText(ST_AsText(geom))) as distance
from
    t_store;

源码地址

github.com/Grt1228/pos…

参考资料:

[1] 百度地图坐标拾取系统: api.map.baidu.com/lbsapi/getp…

[2] 参考csdn文章: blog.csdn.net/zxt521yt/ar…