背景
在我的物流项目中用户端下了个订单,是从上海发往北京的包裹,神领物流接收这个单子后,是如何进行运输的呢?是直接开一辆车去送吗?还是需要中转?需要中转多少次呢?怎么样的中转成本是最低的?
路线分析
假设物流系统有这些运输路线数据:
按照上面的订单,是由【上海浦东迪士尼】发往【北京昌平金燕龙】,有直达的路线吗?其实是没有的,实际上很少会有【网点↔网点】的直达路线的,这样成本太高了,所以完成一次运输都是通过各个【转运】完成的。
如果参考上面的路线数据,可以找出两条转运路线:
- 路线1:迪士尼营业部 -> 浦东新区转运中心 -> 上海转运中心 -> 北京转运中心 -> 昌平区转运中心 -> 金燕龙营业部
- 路线2:迪士尼营业部-> 浦东新区转运中心 -> 上海转运中心 -> 杭州转运中心 -> 北京转运中心 -> 昌平区转运中心 -> 金燕龙营业部
问题分析
如果我们基于上面的数据表进行查找路线时,基本上都是一条一条的数据过,看是否能够【链接】起来,直到目的地。 这样的查找有什么问题吗? 想象一下,如果这个表的数据变的很大的时候呢?比如说,有100、1000、10000、……条数据的时候,查找会非常慢,而且我们编写代码也会非常的复杂。 显然,这个并不是好的解决方案。有没有什么好的解决呢?
问题解决
寻找一种以图的方式存储各个节点的库
首当其冲当然是选择neo4j图数据库
如果有对这种数据库不了解的伙伴请看:物流项目:Neo4j的学习使用
项目中各个节点之间的关系
节点
路线
记录着路线指向,路线成本,还有其他信息
项目使用
在物流中,会存在网点、二级转运中心、一级转运中心,我们分别用Agency、TLT、OLT表示。
BaseEntity
由于以上三个机构的属性是相同的,但在Neo4j中的标签是不一样的,所以既要保证不同的类,也有相同的属性,这种场景比较适合将属性写到父类中,自己继承父类来实现,这里我们采用抽象类的来实现。
@Data
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseEntity {
@Id
@GeneratedValue
@ApiModelProperty(value = "Neo4j ID", hidden = true)
private Long id;
@ApiModelProperty(value = "业务id", required = true)
private Long bid;
@ApiModelProperty(value = "名称", required = true)
private String name;
@ApiModelProperty(value = "电话", required = true)
private String phone;
@ApiModelProperty(value = "地址", required = true)
private String address;
@ApiModelProperty(value = "位置坐标, x: 纬度,y: 经度", required = true)
private Point location;
//机构类型
public abstract OrganTypeEnum getAgencyType();
}
机构枚举:
/**
* 机构类型枚举
*/
public enum OrganTypeEnum implements BaseEnum {
OLT(1, "一级转运中心"),
TLT(2, "二级转运中心"),
AGENCY(3, "网点");
/**
* 类型编码
*/
private final Integer code;
/**
* 类型值
*/
private final String value;
OrganTypeEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
public Integer getCode() {
return code;
}
public String getValue() {
return value;
}
public static OrganTypeEnum codeOf(Integer code) {
return EnumUtil.getBy(OrganTypeEnum::getCode, code);
}
}
三级实体
/**
* 网点实体
*/
@Node("AGENCY")
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class AgencyEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.AGENCY;
}
}
/**
* 一级转运中心实体 (OneLevelTransportEntity)
*/
@Node("OLT")
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class OLTEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.OLT;
}
}
/**
* 二级转运中心实体(TwoLevelTransportEntity)
*/
@Node("TLT")
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class TLTEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.TLT;
}
}
运输路线实体
/**
* 运输路线实体
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransportLine {
private Long id;
private Double cost; //成本
}
上面都是基本用法,下面举例几个经典例子来实战一下
实现成本最低优先的路线查询
@Override
public List<TransportLineNodeDTO> findPathList(AgencyEntity start, AgencyEntity end, int depth, int limit) {
//获取网点数据在Neo4j中的类型
String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];
//构造查询语句
String cypherQuery = StrUtil.format(
"MATCH path = (start:{}) -[*..{}]-> (end:{})\n" +
"WHERE start.bid = $startId AND end.bid = $endId AND start.status = true AND end.status = true\n" +
"UNWIND relationships(path) AS r\n" +
"WITH sum(r.cost) AS cost, path\n" +
"RETURN path ORDER BY cost ASC, LENGTH(path) ASC LIMIT {}", type, depth, type, limit);
return this.executeQueryPath(cypherQuery, start, end);
}
实现距离最近优先的路线查询
@Override
public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end, int depth) {
//获取网点数据在Neo4j中的类型
String type = AgencyEntity.class.getAnnotation(Node.class).value()[0];
//构造查询语句
String cypherQuery = StrUtil.format(
"MATCH path = shortestPath((start:{}) -[*..{}]-> (end:{}))\n" +
"WHERE start.bid = $startId AND end.bid = $endId AND start.status = true AND end.status = true\n" +
"RETURN path", type, depth, type);
Collection<TransportLineNodeDTO> transportLineNodeDTOS = this.executeQueryPath(cypherQuery, start, end);
if (CollUtil.isEmpty(transportLineNodeDTOS)) {
return null;
}
for (TransportLineNodeDTO transportLineNodeDTO : transportLineNodeDTOS) {
return transportLineNodeDTO;
}
return null;
}
评论( 0 )