# 智能拆分订单
新零售系统的智能拆分订单,因为我们的业务中有自建仓库,那么实际使用中,用户下单就面临一个问题,当用户所在地没有仓库,或则说购买的商品,在本地仓库没有,需要从外地仓库发货,还需要计算哪个仓库距离用户最近;
电商系统是一个很复杂的系统,包含了很多子系统:交易、订单、仓库、物流、售后子系统等等,如果都部署在一台机器上,在大型电商上场景中,根本就撑不住。最常见的方式就是搞分布式架构,微服务。
# 就进发货的难题
我们实现一个体验较好的购买流程:
用户下单
用户只需要关注商品是否有库存,不需要关心商品从哪个仓库发货的,
智能拆分订单
系统接收到订单通知后,系统判断哪些商品在本地有货,哪些在需要从其他仓库发货,并且计算哪家仓库距离收货地址是最近的。
要计算收货地址最近,需要知道用户收货地址的地里坐标。
# 注册高的地图开发者
通过高的地图的 API 来获取到地址的地理坐标,需要
拿到这个 key 后,就可以使用这个 地理/逆地理编码 API,如下的一个测试返回数据
响应中的 location 就是地址位置坐标了。API 调用数据都有了,在程序中来发送 HTTP 请求,这里推荐一个工具包
# hutool
Hutool 是 Java 里一个工具集合的依赖包,可以使用它的网络类,发送请求。
相对于 httpClient 来说更加简单;笔者也看了下官网说明,涵盖了相当多的工具,还不错。
compile 'cn.hutool:hutool-all:5.3.7'
compile 'mysql:mysql-connector-java:8.0.16'
2
拿到举例收货地址的地址位置信息之后,还要计算哪一个仓库离收货地址最近,所以把 MySQL 驱动也引入;
@Test
public void geocodeGet() {
HashMap<String, Object> params = new HashMap<>();
params.put("key", "xxx");
params.put("address", "营口市盼盼工业园");
String resp = HttpUtil.get("https://restapi.amap.com/v3/geocode/geo", params);
JSON json = JSONUtil.parse(resp);
String location = json.getByPath("geocodes[0].location", String.class);
String[] lngItems = location.split(",");
Double lng = Double.valueOf(lngItems[0]); // 经度
Double lat = Double.valueOf(lngItems[1]); // 维度
System.out.println(lng); // 122.268278
System.out.println(lat); // 40.731062
}
2
3
4
5
6
7
8
9
10
11
12
13
14
把 t_warehouse 仓库表新增地里位置字段
alter table t_warehouse
add lng decimal(12,6) null comment '经度';
alter table t_warehouse
add lat decimal(12,6) null comment '纬度';
2
3
4
5
# 利用 MySQL 计算两点之间的距离
st_distance
函数可以计算两个坐标之间相差的度数
select st_distance(
point(116.414042, 39.92556), -- 北京市
point(121.486864, 31.232965) -- 上海市
) * 111195;
-- 10.06452834849746
-- 计算出来的是度数,要 * 111195,还原成距离,单位是米
2
3
4
5
6
对于仓库的地里位置,上面接口也可以获取到,还可以通过 坐标拾取器 在高德地图提供的 web 界面中去获取到
我们找几个沈阳市和大连市的几个坐标点,用来当做我们测试的仓库地址,并增加到仓库表中
INSERT INTO neti.t_warehouse (id, city_id, address, tel, lng, lat) VALUES (5, 1, '大连市五一广场', '0411-98213210', 121.603687, 38.917862);
INSERT INTO neti.t_warehouse (id, city_id, address, tel, lng, lat) VALUES (6, 1, '大连市五四广场', '0411-98213210', 121.589590, 38.913605);
INSERT INTO neti.t_warehouse (id, city_id, address, tel, lng, lat) VALUES (7, 1, '沈阳市政府', '0411-98213210', 123.465009, 41.677287);
2
3
select id,
address,
st_distance(
point(122.268278, 40.731062), -- 营口市盼盼工业园
point(lng, lat)
) * 111195 / 1000 as sistance
from t_warehouse
where lng is not null
2
3
4
5
6
7
8
查询结果为
id | address | sistance |
---|---|---|
5 | 大连市五一广场 | 214.73523519655598 |
6 | 大连市五四广场 | 215.72309589574027 |
7 | 沈阳市政府 | 169.6409679674254 |
可以看到沈阳市政府,距离营口市盼盼工业园距离更近一点,只有 169 公里;
那么改造 SQL 返回一个最近的仓库
select id,
address,
st_distance(
point(122.268278, 40.731062), -- 营口市盼盼工业园
point(lng, lat)
) * 111195 / 1000 as dsistance
from t_warehouse
where lng is not null
order by dsistance
limit 1
2
3
4
5
6
7
8
9
10
拿到了最近的仓库,还需要计算这个商品是否在这个仓库中有库存;
select t.id
from (select id,
address,
st_distance(
point(122.268278, 40.731062), -- 营口市盼盼工业园
point(lng, lat)
) * 111195 / 1000 as dsistance
from t_warehouse
where lng is not null
order by dsistance
limit 1) t
join t_warehouse_sku ws on ws.warehouse_id = t.id
and ws.sku_id = 1 -- 购买的商品 id
and ws.num >= 1; -- 购买的数量
2
3
4
5
6
7
8
9
10
11
12
13
14
使用 Java 代码来简单实现下
public String geocodeGet(String address) {
HashMap<String, Object> params = new HashMap<>();
params.put("key", "xx");
params.put("address", address);
String resp = HttpUtil.get("https://restapi.amap.com/v3/geocode/geo", params);
JSON json = JSONUtil.parse(resp);
String location = json.getByPath("geocodes[0].location", String.class);
return location;
}
// 运行这个测试
@Test
public void test2() throws SQLException {
String location = geocodeGet("营口市盼盼工业园");
String[] lngItems = location.split(",");
String lng = lngItems[0]; // 经度
String lat = lngItems[1]; // 维度
DriverManager.registerDriver(new Driver());
// 说是 8.0 不加 serverTimezone 链接会有问题
String url = "jdbc:mysql://192.168.56.101:3306/neti?serverTimezone=GMT%2B8";
String username = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, username, password);
String sql = "select t.id\n" +
"from (select id,\n" +
" address,\n" +
" st_distance(\n" +
" point(?, ?),\n" +
" point(lng, lat)\n" +
" ) * 111195 / 1000 as dsistance\n" +
" from t_warehouse\n" +
" where lng is not null\n" +
" order by dsistance\n" +
" limit 1) t\n" +
" join t_warehouse_sku ws on ws.warehouse_id = t.id\n" +
" and ws.sku_id = ? and ws.num >= ?;";
PreparedStatement pst = connection.prepareStatement(sql);
pst.setObject(1, lng);
pst.setObject(2, lat);
pst.setObject(3, 4); // 商品 sku_id
pst.setObject(4, 1); // 购买数量
ResultSet resultSet = pst.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
System.out.println(id);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
上面的 SQL 没有考虑是否有库存的,改写下 SQL,完成:最近且满足库存条件的
select id
from (
select w.id,
w.address,
st_distance(
point(122.268278, 40.731062), -- 营口市盼盼工业园
point(w.lng, w.lat)
) * 111195 / 1000 as dsistance
from t_warehouse w
join t_warehouse_sku ws on ws.warehouse_id = w.id
and ws.sku_id = 4 and ws.num >= 1
where lng is not null
order by dsistance
limit 1) temp
2
3
4
5
6
7
8
9
10
11
12
13
14
← 数据库/程序 缓存如何选? 中文分词技术 →