网约车项目
项目业务架构图

项目技术介绍
前 端: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;
前端开发流程
乘客端
乘客获取验证码
验证码校验
查询乘客信息
预估乘客订单价格
乘客下单
乘客取消订单
乘客支付

司机端
司机获取验证码
验证码校验
修改司机出车状态
查询司机绑定车辆信息
上传车辆位置
去接乘客
到达乘客上车点
司机接到乘客
行程结束,乘客到达下车点
司机发起收款
取消订单

乘客司机登录系统
乘客端
乘客登录时序图
乘客端不像司机端需要检验用户是否存在,如果不存在会直接进行注册,所以不再需要调用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之前都可以进行取消,让订单进入取消状态
乘客下订单时序图

派单业务流程图


首先验证用户信息的合理性
对于业务情况进行检验,主要包括对当地是否开通业务?是否有计价规则?是否有司机?三个方面进行检验
同时开展三个逻辑:派单业务、定时任务、消息推送
派单业务主要分为实时订单和预约订单(无需查找)
实时订单通过对2km,4km,6km的反复搜索来搜索相关的司机
最后如果找到了司机就同时向乘客和司机推送消息
实现细节
定时任务处理(在一段时间内反复进行派单直到成功)
// 定时任务的处理
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();
}
}
}
派单逻辑
派单的整个逻辑
获得订单中所要求的经纬度,进行周围搜索车辆终端
找到能进行该订单的车辆id
根据车辆id找到司机,查询司机是否有正在进行的订单
查询车辆是否符合订单要求的车辆类型
通知司机、通知乘客
/**
* 实时订单派单逻辑
* @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();
将时间统一成+8市区的时间
将此瞬间转换为从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
联调
基础功能联调
