小蔡学Java

项目二总结:(四)项目中是如何存储各个节点及如何计算运费的

2024-02-17 15:48 933 0 项目 图数据库Neo4j

背景

在我的物流项目中用户端下了个订单,是从上海发往北京的包裹,神领物流接收这个单子后,是如何进行运输的呢?是直接开一辆车去送吗?还是需要中转?需要中转多少次呢?怎么样的中转成本是最低的?

路线分析

假设物流系统有这些运输路线数据:

按照上面的订单,是由【上海浦东迪士尼】发往【北京昌平金燕龙】,有直达的路线吗?其实是没有的,实际上很少会有【网点↔网点】的直达路线的,这样成本太高了,所以完成一次运输都是通过各个【转运】完成的。

如果参考上面的路线数据,可以找出两条转运路线:

  • 路线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 )

  • 博主 Mr Cai
  • 坐标 河南 信阳
  • 标签 Java、SpringBoot、消息中间件、Web、Code爱好者

文章目录