小蔡学Java

项目二总结:(一)项目架构介绍及核心流程

2024-02-05 21:52 1345 0 项目 项目架构微服务项目实战

项目介绍:

我最近做过的⼀个项目是物流类的项⽬叫做准达物流, 是自己学习练手的⼀个项目, 主要是为了体现智能 调度这一方面, 主要是基于微服务架构体系的物流项⽬系统, 主要是通过智能管控⻋辆调研、线路规划等核⼼业务流程,操作智能化,⼤幅度提升⼈效及管控效率, 节省运输成本;

该项⽬类似顺丰速运,是向C端⽤户提供快递服务的系统。竞品有:顺丰、中通、圆通、京东快递等。 项⽬产品主要有4端产品:

  • ⽤户端:基于微信⼩程序开发,外部客户使⽤,可以寄件、查询物流信息等。
  • 快递员端:基于安卓开发的⼿机APP,公司内部的快递员使⽤,可以接收取派件任务等。
  • 司机端:基于安卓开发的⼿机APP,公司内部的司机使⽤,可以接收运输任务、上报位置信息等。
  • 后台系统管理:基于vue开发,PC端使⽤,公司内部管理员⽤户使⽤,可以进⾏基础数据维护、订单管理、运单管理等。

这个项⽬主要采⽤SpringCloud + SpringBoot分布式架构, 前后端分离的开发模式, ⽹关使⽤到了 nginx服务 + gateway, 注册中⼼和配置中⼼使⽤的是nacos, 微服务之间使⽤feign调⽤, 消息中间件使⽤了rabbitmq, 微服务保护⽤的是SpringCloudAlibaba的sentinel, 分布式事务问题⽤到了seata, 分布式任务调度⽤到了Xxl-Job;

整体架构

技术架构

项目大概流程

流程说明:

  • ● 用户在【用户端】下单后,生成订单
  • ● 系统会根据订单生成【取件任务】,快递员上门取件后成功后生成【运单】
  • ● 用户对订单进行支付,会产生【交易单】
  • ● 快件开始运输,会经历起始营业部、分拣中心、转运中心、分拣中心、终点营业部之间的转运运输,在此期间会有多个【运输任务】
  • ● 到达终点网点后,系统会生成【派件任务】,快递员进行派件作业
  • ● 最后,用户将进行签收或拒收操作

各端说明

用户端

用户下单 ** 估算运费**

下单成功

快递员端

取件任务列表

去取件

扫描支付

取件成功

司机端

司机运输任务

任务详情

提货成功(运输开始)

到达目的地

后台管理系统

工作台

车辆管理

订单管理

司机管理

排班管理

运输任务管理

线路管理

运单管理

机构管理

运费管理

微服务间调用关系如下:

模块情况

AOP切面的应用

微服务之间的接口调用,对于传输的数据是需要做校验的,一般校验方式有2种:

方式一:采用hibernate-validator注解方式校验,如下:

方式二:在程序中通过if()进行判断,如下:

我们采用哪一种方式呢?实际上在项目中,我们采用二者结合的方式进行校验。 对于第一种方式的补充说明:

  • 在Controller中需要增加@Validated注解,来开启校验

  • 对于表单、url参数校验,在Controller中方法增加校验规则

  • 对于@RequestBody对象的校验,校验规则写的DTO对象中,统一通过Spring的AOP进行校验,具体在common工程中ValidatedAspect进实现:

自定义异常

我们统一做了自定义异常的处理。 定义了2个异常:

  • com.sl.transport.common.exception.SLException
    • 用于微服务之前接口调用抛出的异常
  • com.sl.transport.common.exception.SLWebException
    • 用于前后端交互时抛出的异常 SLException的定义:

SLWebException的定义:

这两个异常的区别在于code、status的值不同。 疑问:为什么不使用一个,而是要设置两个?

这个主要是前端和后端的设计不同,一般在微服务间接口调用时会采用标准的RESTful方式,按照RESTful的规范响应的状态码要使用标准的http状态码,成功->200,失败->500,没有权限->401等。 而前后端进行交互时,一般都是响应200,即使出错也是200,只是响应结果中通过msg和code进行表达是否成功。 基于以上的场景,所以设置了两个异常类。 统一异常处理: 具体的业务逻辑在com.sl.transport.common.handler.GlobalExceptionHandler中实现。 关键代码如下:

在该类中对于4种异常做处理,分别是: ● ValidationException

/**
     * 参数校验失败异常
     *
     * @param exception 校验失败异常
     * @return 响应数据
     */
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<Object> handle(ValidationException exception) {
        List<String> errors = null;
        if (exception instanceof ConstraintViolationException) {
            ConstraintViolationException exs = (ConstraintViolationException) exception;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            errors = violations.stream()
                    .map(ConstraintViolation::getMessage).collect(Collectors.toList());
        }
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("参数校验失败异常 -> ", exception);
        }
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(MapUtil.<String, Object>builder()
                        .put("code", HttpStatus.BAD_REQUEST.value())
                        .put("msg", errors)
                        .build());
    }

● SLException

/**
     * 自定义异常处理
     *
     * @param exception 自定义异常
     * @return 响应数据
     */
    @ExceptionHandler(SLException.class)
    public ResponseEntity<Object> handle(SLException exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("自定义异常处理 -> ", exception);
        }
        return ResponseEntity.status(exception.getStatus())
                .body(MapUtil.<String, Object>builder()
                        .put("code", exception.getCode())
                        .put("msg", exception.getMsg())
                        .build());
    }

● SLWebException

/**
     * web自定义异常处理
     * 用于统一封装VO对象返回前端
     *
     * @param exception web自定义异常
     * @return 响应数据
     */
    @ExceptionHandler(SLWebException.class)
    public ResponseEntity<Object> handle(SLWebException exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("自定义异常处理 -> ", exception);
        }
        JSONObject jsonObject = JSONUtil.parseObj(exception);
        return ResponseEntity.ok(MapUtil.<String, Object>builder()
                .put("code", exception.getCode())
                .put("msg", jsonObject.getStr("msg"))
                .build());
    }

● Exception

 /**
     * 其他未知异常
     *
     * @param exception 未知异常
     * @return 响应数据
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handle(Exception exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("其他未知异常 -> ", exception);
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(MapUtil.<String, Object>builder()
                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
                        .put("msg", ExceptionUtil.stacktraceToString(exception))
                        .build());
    }

项目特性

  • 取派件、分段运输转发等核心环节接入高可用任务调度,可定制调度策略,目前支持成本优先、距离优先俩种方式。
  • 多类型(干线,支线,接驳线)、网状线路,支持多维度配置线路,有力缓解疫情等突发原因导致的运输任务积压。
  • 分布式仓储(Redis缓存方式)配合集装箱模式灵活转运订单。
  • MongoDB存储订单信息流,实时追踪订单状态。
  • 司机、快递员等多端点位采集位置上报,运输轨迹一目了然。
  • 公交车模式运输任务,预规划车次计划的基础上可插拔配置不同车型车辆与多样化司机作息安排。
  • 小程序下单、多途径支付、取消,拒收等履约方式、预约取件时间等机制,一切从用户角度出发。
  • 根据地图坐标范围、工作负载,行政机构范围等多特征点智能分配取件、派件作业任务,整体提升运作效率。
  • 线上线下结合,多场景、多系统角色支撑的生产级系统业务编排。

评论( 0 )

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

文章目录