网约车项目总结

网约车项目

项目业务架构图

项目技术介绍

前 端:uni-app

数 据 库:MySQL

缓 存:Redis

注册中心:Nacos

配置中心:Nacos

网 关:Spring Cloud Gateway

熔断限流:Spring Cloud Alibaba Sentinel

服务监控:Spring Cloud Sleuth、Spring Cloud zipkin

分布式锁:Redisson

分布式事务:Alibaba 的 Seata

服务通信:SSE

项目服务分析

api-boss(BOSS端)

具有以下功能:
用户管理——未设置
司机管理——添加司机、修改司机信息
车辆管理——添加车辆信息
司机和车辆关系管理——司机车辆关系绑定,司机车辆关系解绑

api-driver(司机端)

具有以下功能:
注册/登录——司机获取验证码、司机验证码验证、司机登录带token、司机登录不带token
用户管理——维护司机信息
司机听单——上传车辆位置,修改司机工作状态、查询司机车辆绑定关系
司机抢单——由系统派单,暂时还未有司机主动抢单的功能
订单流转——司机去接乘客、司机到达乘客地点、司机接到乘客、乘客到达目的地、司机取消订单
发起收款——司机发起收款

api-passenger(乘客端)

具有以下功能:
注册/登录——乘客获取验证码、乘客验证码验证、乘客登录带token、乘客登录不带token
预估价格——预估价格
乘客下单——乘客下单
乘客支付——乘客支付
乘客评价——暂无

乘客登录功能

  • api-passenger

  • service-passenger-user

  • service-verificationcode

司机登录功能

  • api-driver

  • service-driver-user

  • service-verificationcode

预估价格功能

  • api-passenger

  • serivce-price(预估价格)

  • service-map(计算距离及时间)

下订单功能涉及服务

  • api-passenger

  • service-order(下订单)

  • service-price(计算价格)

  • service-map(搜寻附近终端)

  • service-driver-user(匹配司机)

  • service-sse-push(消息推送)

乘客支付功能

  • api-passenger

  • service-pay(调用阿里支付接口)

司机上传车辆位置

  • api-driver

  • service-map(同步位置信息)

  • service-driver-user(根据车辆id获得车辆)

数据库设计

service-driver-user

车辆表
DROP TABLE IF EXISTS `car`;
CREATE TABLE `car`  (
  `id` bigint(0) NOT NULL,	
  `address` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vehicle_no` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `plate_color` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `seats` int(0) NULL DEFAULT NULL,
  `brand` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `model` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vehicle_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `owner_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vehicle_color` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `engine_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vin` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `certify_date_a` date NULL DEFAULT NULL,
  `fue_type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `engine_displace` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `trans_agency` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `trans_area` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `trans_date_start` date NULL DEFAULT NULL,
  `trans_date_end` date NULL DEFAULT NULL,
  `certify_date_b` date NULL DEFAULT NULL,
  `fix_state` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `next_fix_date` date NULL DEFAULT NULL,
  `check_state` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `fee_print_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `gps_brand` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `gps_model` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `gps_install_date` date NULL DEFAULT NULL,
  `register_date` date NULL DEFAULT NULL,
  `commercial_type` int(0) NULL DEFAULT NULL,
  `fare_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `trname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `trid` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `tid` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `state` tinyint(1) NULL DEFAULT NULL,
  `gmt_create` datetime(0) NULL DEFAULT NULL,
  `gmt_modified` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
司机表
DROP TABLE IF EXISTS `driver_user`;
CREATE TABLE `driver_user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `address` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_gender` tinyint(0) NULL DEFAULT NULL,
  `driver_birthday` date NULL DEFAULT NULL,
  `driver_nation` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_contact_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `license_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `get_driver_license_date` date NULL DEFAULT NULL,
  `driver_license_on` date NULL DEFAULT NULL,
  `driver_license_off` date NULL DEFAULT NULL,
  `taxi_driver` tinyint(0) NULL DEFAULT NULL,
  `network_car_issue_organization` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `certificate_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `network_car_issue_date` date NULL DEFAULT NULL,
  `get_network_car_proof_date` date NULL DEFAULT NULL,
  `network_car_proof_on` date NULL DEFAULT NULL,
  `network_car_proof_off` date NULL DEFAULT NULL,
  `register_date` date NULL DEFAULT NULL,
  `commercial_type` tinyint(0) NULL DEFAULT NULL,
  `contract_company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `contract_on` date NULL DEFAULT NULL,
  `contract_off` date NULL DEFAULT NULL,
  `state` tinyint(0) NULL DEFAULT NULL,
  `gmt_create` datetime(0) NULL DEFAULT NULL,
  `gmt_modified` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1610231270481735682 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
车辆司机绑定关系表
DROP TABLE IF EXISTS `driver_car_binding_relationship`;
CREATE TABLE `driver_car_binding_relationship`  (
  `id` bigint(0) NOT NULL,
  `driver_id` bigint(0) NULL DEFAULT NULL,
  `car_id` bigint(0) NULL DEFAULT NULL,
  `bind_state` int(0) NULL DEFAULT NULL,
  `binding_time` datetime(0) NULL DEFAULT NULL,
  `un_binding_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
司机工作状态表
DROP TABLE IF EXISTS `driver_user_work_status`;
CREATE TABLE `driver_user_work_status`  (
  `id` bigint(0) NOT NULL,
  `driver_id` bigint(0) NOT NULL,
  `work_status` int(0) NULL DEFAULT NULL,
  `gmt_create` datetime(0) NULL DEFAULT NULL,
  `gmt_modified` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

service-map

政区表
DROP TABLE IF EXISTS `dic_district`;
CREATE TABLE `dic_district`  (
  `address_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `address_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `parent_address_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `level` tinyint(0) NULL DEFAULT NULL,
  PRIMARY KEY (`address_code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

service-order

订单表
DROP TABLE IF EXISTS `order_info`;
CREATE TABLE `order_info`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `passenger_id` bigint(0) NULL DEFAULT NULL,
  `passenger_phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_id` bigint(0) NULL DEFAULT NULL,
  `driver_phone` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vehicle_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `car_id` bigint(0) NULL DEFAULT NULL,
  `address` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `order_time` datetime(0) NULL DEFAULT NULL,
  `depart_time` datetime(0) NULL DEFAULT NULL,
  `departure` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `dep_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `dep_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `destination` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `dest_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `dest_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `encrypt` int(0) NULL DEFAULT NULL,
  `fare_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `receive_order_car_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `receive_order_car_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `receive_order_time` datetime(0) NULL DEFAULT NULL,
  `license_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `vehicle_no` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `to_pick_up_passenger_time` datetime(0) NULL DEFAULT NULL,
  `to_pick_up_passenger_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `to_pick_up_passenger_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `to_pick_up_passenger_address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `driver_arrived_depature_time` datetime(0) NULL DEFAULT NULL,
  `pick_up_passenger_time` datetime(0) NULL DEFAULT NULL,
  `pick_up_passenger_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `pick_up_passenger_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `passenger_getoff_time` datetime(0) NULL DEFAULT NULL,
  `passenger_getoff_longitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `passenger_getoff_latitude` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `cancel_time` datetime(0) NULL DEFAULT NULL,
  `cancel_operator` int(0) NULL DEFAULT NULL,
  `cancel_type_code` int(0) NULL DEFAULT NULL,
  `drive_mile` bigint(0) NULL DEFAULT NULL,
  `drive_time` bigint(0) NULL DEFAULT NULL,
  `price` double(10, 2) NULL DEFAULT NULL,
  `order_status` int(0) NULL DEFAULT NULL,
  `gmt_create` datetime(0) NULL DEFAULT NULL,
  `gmt_modified` datetime(0) NULL DEFAULT NULL,
  `fare_version` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1610485435854426117 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

service-passenger-user

用户表
DROP TABLE IF EXISTS `passenger_user`;
CREATE TABLE `passenger_user`  (
  `passenger_Id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT,
  `gmt_create` datetime(0) NULL DEFAULT NULL,
  `gmt_modified` datetime(0) NULL DEFAULT NULL,
  `passenger_phone` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `passenger_name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `passenger_gender` tinyint(1) NULL DEFAULT NULL,
  `state` tinyint(1) NULL DEFAULT NULL,
  `profile_photo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`passenger_Id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

service-price

计价规则表
DROP TABLE IF EXISTS `price_rule`;
CREATE TABLE `price_rule`  (
  `city_code` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `vehicle_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `start_fare` double(4, 2) NULL DEFAULT NULL,
  `start_mile` int(0) NULL DEFAULT NULL,
  `unit_price_per_mile` double(4, 2) NULL DEFAULT NULL,
  `unit_price_per_minute` double(4, 2) NULL DEFAULT NULL,
  `fare_type` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `fare_version` int(0) NOT NULL,
  PRIMARY KEY (`city_code`, `vehicle_type`, `fare_version`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

前端开发流程

乘客端

  1. 乘客获取验证码

  1. 验证码校验

  1. 查询乘客信息

  1. 预估乘客订单价格

  1. 乘客下单

  1. 乘客取消订单

  1. 乘客支付

司机端

  1. 司机获取验证码

  1. 验证码校验

  1. 修改司机出车状态

  1. 查询司机绑定车辆信息

  1. 上传车辆位置

  1. 去接乘客

  1. 到达乘客上车点

  1. 司机接到乘客

  1. 行程结束,乘客到达下车点

  1. 司机发起收款

  1. 取消订单

乘客司机登录系统

乘客端

乘客登录时序图
乘客端不像司机端需要检验用户是否存在,如果不存在会直接进行注册,所以不再需要调用service-passenger-user服务

司机端

司机登录时序图

双Token时序图

利用JWT(Json Web Token)生成token

该流程主要在accessToken失效后,利用refreshToken生成新的Token

实现细节

用户登录
/**
     * 注册登录逻辑
     * @param passengerPhone
     * @return
     */
    public ResponseResult loginOrRegister(String passengerPhone){
        System.out.println("user service被调用,手机号:" + passengerPhone);

        //  根据手机号查询用户信息
        Map<String,Object> map = new HashMap<>();
        map.put("passenger_phone",passengerPhone);
        List<PassengerUser> passengerUsers = passengerUserMapper.selectByMap(map);
        System.out.println(passengerUsers.size() == 0 ? "无记录": passengerUsers.get(0).getPassengerPhone());

        //  判断用户信息是否存在
        if(passengerUsers.size() == 0){
            //  如果不存在,插入用户信息
            PassengerUser passengerUser = new PassengerUser();
            passengerUser.setPassengerName("张三");
            passengerUser.setPassengerGender((byte) 0);
            passengerUser.setPassengerPhone(passengerPhone);
            passengerUser.setState((byte) 0);

            LocalDateTime now = LocalDateTime.now();
            passengerUser.setGmtCreate(now);
            passengerUser.setGmtModified(now);

            passengerUserMapper.insert(passengerUser);
        }
    }

乘客司机信息系统

乘客端

查询用户信息时序图

司机端

司机地理位置管理
司机工作状态业务流程
司机车辆相关时序图

司机有两个输入渠道,一个是直接通过boss后台输入,一个是司机在app中自己输入

司机车辆关系

支付系统

调用支付宝接口活动图

实现细节

阿里支付SDK调用

aliPayConfig(配置类,用来作工厂初始化的配置)

package com.mashibing.servicepay.config;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


import javax.annotation.PostConstruct;

@Component
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayConfig {

    private String appId;

    private String appPrivateKey;

    private String publicKey;

    private String notifyUrl;

    @PostConstruct
    public void init(){
        Config config = new Config();
        //  基础配置
        config.protocol = "https";
        config.gatewayHost = "openapi.alipaydev.com";
        config.signType = "RSA2";

        //  业务配置
        config.appId = this.appId;
        config.merchantPrivateKey = this.appPrivateKey;
        config.alipayPublicKey = this.publicKey;
        config.notifyUrl = this.notifyUrl;

        Factory.setOptions(config);
        System.out.println("支付宝配置初始化完成");
    }
}

支付服务

@GetMapping("/pay")
    public String pay(String subject,String outTradeNo,String totalAmount){
        //	设置响应信息
        AlipayTradePagePayResponse response;
        try {
            //	利用alipay的Factory生成支付窗口,进行支付即可
            response = Factory.Payment.Page().pay(subject,outTradeNo,totalAmount,"");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
        return response.getBody();
    }

支付响应

 @RequestMapping("/notify")
    public String notify(HttpServletRequest request) throws Exception {
        System.out.println("支付回调 notify");
        String tradeStatus = request.getParameter("trade_status");
       	//	判断支付是否成功了
        if (tradeStatus.trim().equals("TRADE_SUCCESS")){
            //	获得响应头的信息进行进一步判断
            Map<String,String> param = new HashMap<>();

            Map<String,String[]> parameterMap = request.getParameterMap();
            //	获取所有响应头的key
            for (String name: parameterMap.keySet()){
                param.put(name,request.getParameter(name));
            }
		   //	一键验证
            if (Factory.Payment.Common().verifyNotify(param)){
                System.out.println("通过支付宝的验证");
			   //	获得支付的订单号并同步更新到订单中
                String out_trade_no = param.get("out_trade_no");
                Long orderId = Long.parseLong(out_trade_no);
                alipayService.pay(orderId);
            }else {
                System.out.println("支付宝验证 不通过!");
            }
        }
        return "success";
    }

计价系统

计价规则传递扩展

问题:计价规则有更新,下单时不知道

办法:当检测到计价规则有变动的时候,提示用户重新计算预估价格。

预估价格时序图

实现细节

计价规则的添加(通过拼接cityCode以及vehicleTpye形成fareType)
public ResponseResult add(PriceRule priceRule) {
        //  拼接fare_type
        String cityCode = priceRule.getCityCode();
        String vehicleType = priceRule.getVehicleType();
        //  通过城市code以及车辆类型来拼接版本类型
        String fareType = cityCode + "$" + vehicleType;

        priceRule.setFareType(fareType);

        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("city_code", cityCode);
        queryWrapper.eq("vehicle_type", vehicleType);
        queryWrapper.orderByDesc("fare_version");

        List<PriceRule> list = priceRuleMapper.selectList(queryWrapper);
        Integer fareVersion = 0;
        if (list.size() > 0) {
            return ResponseResult.fail(CommonStatusEnum.PRICE_RULE_EXISTS.getCode(), CommonStatusEnum.PRICE_RULE_EXISTS.getValue());
        }
        priceRule.setFareVersion(++fareVersion);
        priceRuleMapper.insert(priceRule);
        return ResponseResult.success();
    }
得到最新版本的计价规则(通过orderByDesc排序)
public ResponseResult<PriceRule> getNewestVersion(String fareType){
    QueryWrapper queryWrapper = new QueryWrapper();
    queryWrapper.eq("fare_type",fareType);
    queryWrapper.orderByDesc("fare_version");

    List<PriceRule> list = priceRuleMapper.selectList(queryWrapper);
    if (list.size() > 0){
        return ResponseResult.success(list.get(0));
    }else{
        return ResponseResult.fail(CommonStatusEnum.PRICE_RULE_EMPTY.getCode(),CommonStatusEnum.PRICE_RULE_EMPTY.getValue());
    }
}

订单系统

订单状态图

  • 订单无效(0)

  • 订单开始(1)

  • 司机接单(2)

  • 司机去接乘客(3)

  • 司机到达乘客起点(4)

  • 乘客上车,行程开始(5)

  • 乘客到达指定地点(6)

  • 司机发起收款(7)

  • 乘客支付完成(8)

  • 订单取消(9)

在状态5之前都可以进行取消,让订单进入取消状态

乘客下订单时序图

派单业务流程图

  1. 首先验证用户信息的合理性

  1. 对于业务情况进行检验,主要包括对当地是否开通业务?是否有计价规则?是否有司机?三个方面进行检验

  1. 同时开展三个逻辑:派单业务、定时任务、消息推送

  1. 派单业务主要分为实时订单和预约订单(无需查找)

  1. 实时订单通过对2km,4km,6km的反复搜索来搜索相关的司机

  1. 最后如果找到了司机就同时向乘客和司机推送消息

实现细节

定时任务处理(在一段时间内反复进行派单直到成功)
 //  定时任务的处理
        for (int i = 0; i < 6; i++) {
            //  派单
            int result = dispatchRealTimeOrder(order);
            if (result == 1){
                break;
            }
            if (i == 5){
                //  订单无效
                order.setOrderStatus(OrderConstant.ORDER_INVALID);
                orderInfoMapper.updateById(order);
            }else{
                //  等待20秒
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
派单逻辑

派单的整个逻辑

  1. 获得订单中所要求的经纬度,进行周围搜索车辆终端

  1. 找到能进行该订单的车辆id

  1. 根据车辆id找到司机,查询司机是否有正在进行的订单

  1. 查询车辆是否符合订单要求的车辆类型

  1. 通知司机、通知乘客

 /**
     * 实时订单派单逻辑
     * @param orderInfo
     */
    public int dispatchRealTimeOrder(OrderInfo orderInfo){
        log.info("循环一次");
        int result = 0;

        String depLongitude = orderInfo.getDepLongitude();
        String depLatitude = orderInfo.getDepLatitude();
        String center = depLatitude + "," + depLongitude;
        List<Long> radiusList = new ArrayList<>();
        radiusList.add(2000L);
        radiusList.add(4000L);
        radiusList.add(5000L);
        ResponseResult<List<TerminalResponse>> listResponseResult = null;
        boolean ifFind = false;
        for (int i = 0; i < radiusList.size() && !ifFind; i++) {
            Long radius = radiusList.get(i);
            listResponseResult = serviceMapClient.aroundSearch(center, radius);
//            log.info("在半径为:" + radius +"的范围内寻找车辆,结果:" + JSONArray.fromObject(listResponseResult.getData()).getJSONObject(0).toString());
            //  获得终端 {"carId":1604743372085096449,"tid":"612821667"}

            //  解析终端
            List<TerminalResponse> data = listResponseResult.getData();
            for (int j = 0; j < data.size(); j++) {
                TerminalResponse terminalResponse = data.get(j);
                String latitude = terminalResponse.getLatitude();
                String longitude = terminalResponse.getLongitude();
                long carId = terminalResponse.getCarId();

                ResponseResult<OrderDriverResponse> availableDriver = serviceDriverUserClient.getAvailableDriver(carId);
                if (availableDriver.getCode() == CommonStatusEnum.AVAILABLE_DRIVER_EMPTY.getCode()){
                    log.info("没有车辆ID:" + carId + "对应的司机");
                }else{
                    log.info("车辆ID:" + carId +",找到了正在出车的司机");
                    //  查看司机是否有正在运行的订单
                    OrderDriverResponse orderDriverResponse = availableDriver.getData();
                    Long driverId = orderDriverResponse.getDriverId();
                    String licenseId = orderDriverResponse.getLicenseId();
                    String vehicleNo = orderDriverResponse.getVehicleNo();
                    String driverPhone = orderDriverResponse.getDriverPhone();
                    //  判断车辆的车型是否符合(搜索到的车辆不一定都是符合要求的)
                    String vehicleTypeFromCar = orderDriverResponse.getVehicleType();
                    String vehicleType = orderInfo.getVehicleType();
                    if (!vehicleTypeFromCar.trim().equals(vehicleType.trim())){
                        log.info("车型不符合");
                        continue;
                    }

                    String lockKey = (driverId + "").intern();
                    RLock lock = redissonClient.getLock(lockKey);
                    lock.lock();

                    Integer driverOrderGoingOn = isDriverOrderGoingOn(driverId);
                    if (driverOrderGoingOn > 0){
                        log.info("司机Id:" + driverId + ",正在进行的订单数量:" + driverOrderGoingOn);
                        lock.unlock();
                        continue;
                    }
                    //  订单直接匹配司机
                    //  查询当前车辆信息

                    //  查询当前司机信息
                    orderInfo.setDriverId(driverId);
                    orderInfo.setDriverPhone(driverPhone);
                    orderInfo.setCarId(carId);
                    orderInfo.setLicenseId(licenseId);
                    orderInfo.setVehicleNo(vehicleNo);

                    orderInfo.setReceiveOrderCarLatitude(latitude);
                    orderInfo.setReceiveOrderCarLongitude(longitude);
                    orderInfo.setReceiveOrderTime(LocalDateTime.now());

                    orderInfo.setOrderStatus(OrderConstant.DRIVER_RECEIVE_ORDER);

                    orderInfoMapper.updateById(orderInfo);
                    ifFind = true;

                    //  通知司机
                    JSONObject driverContent = new JSONObject();
                    driverContent.put("orderId",orderInfo.getId());
                    driverContent.put("passengerId",orderInfo.getPassengerId());
                    driverContent.put("passengerPhone",orderInfo.getPassengerPhone());
                    driverContent.put("departure",orderInfo.getDeparture());
                    driverContent.put("depLongitude",orderInfo.getDepLongitude());
                    driverContent.put("depLatitude",orderInfo.getDepLatitude());

                    driverContent.put("destination",orderInfo.getDestination());
                    driverContent.put("destLongitude",orderInfo.getDestLongitude());
                    driverContent.put("destLatitude",orderInfo.getDestLatitude());

                    serviceSsePushClient.push(driverId, IdentityConstant.DRIVER_IDENTITY,driverContent.toString());

                    //  通知乘客
                    JSONObject passengerContent = new JSONObject();
                    passengerContent.put("orderId",orderInfo.getId());
                    passengerContent.put("driverId",orderInfo.getDriverId());
                    passengerContent.put("driverPhone",orderInfo.getDriverPhone());
                    passengerContent.put("vehicleNo",orderInfo.getVehicleNo());
                    //  车辆信息,调用服务
                    ResponseResult<Car> carById = serviceDriverUserClient.getCarById(carId);
                    Car remoteCar = carById.getData();

                    passengerContent.put("brand",remoteCar.getBrand());
                    passengerContent.put("model",remoteCar.getModel());
                    passengerContent.put("vehicleColor",remoteCar.getVehicleColor());

                    passengerContent.put("receiveOrderCarLatitude",orderInfo.getReceiveOrderCarLatitude());
                    passengerContent.put("receiveOrderCarLongitude",orderInfo.getReceiveOrderCarLongitude());

                    serviceSsePushClient.push(orderInfo.getPassengerId(), IdentityConstant.PASSENGER_IDENTITY,passengerContent.toString());
                    result = 1;

                    lock.unlock();
                    break;
                }
            }

        }
        return result;
    }
时间转化
Long endtime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
  1. 将时间统一成+8市区的时间

  1. 将此瞬间转换为从1970-01-01T00:00:00Z的纪元到long值的毫秒数

地图系统

实现细节

访问指定url获得Json数据
@Autowired
    private RestTemplate restTemplate;

    public String initDicDistrict(String keywords){
        //  获得数据
        //  拼接url
        StringBuilder url = new StringBuilder();
        url.append(AmapConfigConstant.DISTRICT_URL);
        url.append("?");
        url.append("keywords=" + keywords);
        url.append("&");
        url.append("subdistrict=2");
        url.append("&");
        url.append("key=" + amapkey );
        ResponseEntity<String> result = restTemplate.getForEntity(url.toString(), String.class);

        return result.getBody();
    }
解析Json数据
利用JSONObject类获得Json数据,再利用get方法获得各个关键字的信息
 /**
     * 插入行政区数据
     * @param keywords
     * @return
     */
    public ResponseResult initDicDistrict(String keywords){
        //  获得数据
        String dicDistrict = dicDistrictClient.initDicDistrict(keywords);
        //  解析结果
        JSONObject districtJSONObject = JSONObject.fromObject(dicDistrict);
        int status = districtJSONObject.getInt(AmapConfigConstant.STATUS);
        if (status == 0){
            return ResponseResult.fail(CommonStatusEnum.MAP_DISTRICT_ERROR.getCode(),CommonStatusEnum.MAP_DISTRICT_ERROR.getValue());
        }
        JSONArray countryArray = districtJSONObject.getJSONArray(AmapConfigConstant.DISTRICT);
        //  解析国家数据
        for (int c = 0; c < countryArray.size(); c++){
            JSONObject countryJsonObject = countryArray.getJSONObject(c);
            String countryCode = countryJsonObject.getString(AmapConfigConstant.ADCODE);
            String countryName = countryJsonObject.getString(AmapConfigConstant.NAME);
            String countryParentAddressCode = "0";
            String countryLevel = countryJsonObject.getString(AmapConfigConstant.LEVEL);
            insertData(countryCode,countryName,countryParentAddressCode,getLevel(countryLevel));

            //  解析省份数据
            JSONArray provinceJsonArray = countryJsonObject.getJSONArray(AmapConfigConstant.DISTRICT);
            for (int p = 0; p < provinceJsonArray.size(); p++){
                JSONObject provinceJsonObject = provinceJsonArray.getJSONObject(p);
                String provinceCode = provinceJsonObject.getString(AmapConfigConstant.ADCODE);
                String provinceName = provinceJsonObject.getString(AmapConfigConstant.NAME);
                String provinceParentAddressCode = countryCode;
                String provinceLevel = provinceJsonObject.getString(AmapConfigConstant.LEVEL);
                insertData(provinceCode,provinceName,provinceParentAddressCode,getLevel(provinceLevel));
                //  解析市县数据
                JSONArray cityJsonArray = provinceJsonObject.getJSONArray(AmapConfigConstant.DISTRICT);
                for (int city = 0; city < cityJsonArray.size(); city++){
                    JSONObject cityJsonObject = cityJsonArray.getJSONObject(city);
                    String cityCode = cityJsonObject.getString(AmapConfigConstant.ADCODE);
                    String cityName = cityJsonObject.getString(AmapConfigConstant.NAME);
                    String cityParentAddressCode = provinceCode;
                    String cityLevel = cityJsonObject.getString(AmapConfigConstant.LEVEL);
                    if (cityLevel.equals("district")){
                        continue;
                    }
                    insertData(cityCode,cityName,cityParentAddressCode,getLevel(cityLevel));
                }
            }
        }

        return ResponseResult.success();
    }

SSE通知系统

实现细节

建立连接
public static Map<String,SseEmitter> sseEmitterMap = new HashMap<>();

    /**
     * 建立连接
     * @param userId
     * @param identity
     * @return
     */
    @GetMapping("/connect")
    public SseEmitter connect(@RequestParam Long userId,@RequestParam String identity){
        log.info("用户ID:" + userId + ",身份类型:" + identity) ;
	    //	通过将内容放到SseEmitter中作为连接成功与否的判断标准
        SseEmitter sseEmitter = new SseEmitter(0L);

        String sseMapKey = SsePrefixUtils.generatorSseKey(userId, identity);
        sseEmitterMap.put(sseMapKey,sseEmitter);
        return sseEmitter;
    }
发送消息
/**
     * 发送消息
     * @param userId    用户id
     * @param identity  身份类型
     * @param content   消息内容
     * @return
     */
    @GetMapping("/push")
    public String push(@RequestParam Long userId,@RequestParam String identity,@RequestParam String content){

        log.info("用户ID:" + userId + ",身份类型:" + identity) ;
        String sseMapKey = SsePrefixUtils.generatorSseKey(userId, identity);
        try {
            if (sseEmitterMap.containsKey(sseMapKey)){
                //	通过静态成员变量再次获得SseEmitter来发送消息
                sseEmitterMap.get(sseMapKey).send(content);
            }else {
                return "推送失败";
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "给用户:" + sseMapKey + "发送了消息:" + content;
    }

这边发送,html页面会有对应的事件响应器

source = new EventSource("http://localhost:9000/connect?userId=" + userId+"&identity="+identity);

    if (window.EventSource){
        console.log("此浏览器支持SSE");

        //  监听服务的推送的内容
        source.addEventListener("message",function (e) {
            content = e.data;
            console.log(content);
            sendMessageContent(content);
        });

    }else {
        console.log("此浏览器不支持");
    }

    function sendMessageContent(content) {
        document.getElementById("message").innerHTML += (content+'</br>');
    }

验证码系统

实现细节

生成验证码

这里就是用一个随机数来生成的验证码

 @GetMapping("/numberCode/{size}")
    public ResponseResult numberCode(@PathVariable("size") int size){
        System.out.println("size:" + size);
        //  生成验证码
        //  获取随机数,利用小数点往后移就可以了,就是相当于乘
        double mathRandom = (Math.random() * 9 + 1 ) * Math.pow(10,size-1);
        int resultInt = (int)mathRandom;
        System.out.println("generate src code:"+ resultInt);

        //  定义返回值
        NumberCodeResponse response = new NumberCodeResponse();
        response.setNumberCode(resultInt);

        return ResponseResult.success(response);
    }

公共包

Token工具类

Token生成
 //  生成token
    public  static String generatorToken(String passengerPhone, String identity, String tokenType){
        Map<String,String> map = new HashMap<>();
        map.put(JWT_KEY_PHONE,passengerPhone);
        map.put(JWT_KEY_IDENTITY,identity);
        map.put(JWT_TOKEN_TYPE, tokenType);

        //  token过期时间
        map.put(JWT_TOKEN_TIME,Calendar.getInstance().getTime().toString());

        //	通过JWT工具类builder来生成token
        JWTCreator.Builder builder = JWT.create();

        //  整合map
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        //  整合过期时间
//        builder.withExpiresAt(date);

        //  生成token
        String sign = builder.sign(Algorithm.HMAC256(SIGN));

        return sign;
    }
Token解析
//  解析token
    public static TokenResult parseToken(String token){
	    //	构建解析器
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        //	获得手机号信息
        String phone = verify.getClaim(JWT_KEY_PHONE).asString();
       	//	获得身份信息
        String identity = verify.getClaim(JWT_KEY_IDENTITY).asString();
		
        TokenResult tokenResult = new TokenResult();
        tokenResult.setPhone(phone);
        tokenResult.setIdentity(identity);
        return tokenResult;
    }

BigDecimal工具类

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算
package com.mashibing.internalcommon.util;

import java.math.BigDecimal;

public class BigDecimalUtils {

    /**
     * 加法
     * @param v1
     * @param v2
     * @return
     */
    public static double add(double v1, double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);

        return b1.add(b2).doubleValue();
    }

    /**
     * 减法
     * @param v1
     * @param v2
     * @return
     */
    public static double substract(double v1,double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 乘法
     * @param v1
     * @param v2
     * @return
     */
    public static double multiply(double v1,double v2){
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 除法
     * @param v1
     * @param v2
     * @return
     */
    public static double divide(int v1, int v2){
        if (v2 <= 0){
            throw new IllegalArgumentException("除数非法");
        }
        BigDecimal b1 = BigDecimal.valueOf(v1);
        BigDecimal b2 = BigDecimal.valueOf(v2);

        return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}

黑名单业务流程图

BUG

feign调用post转get

联调

基础功能联调

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值