乘客下单
主要业务是从乘客选择起始地点和终止地点之后,也就是初步计算了估计价格然后点击呼叫司机之后的过程。等到附近的司机抢单成功,该业务执行完毕。
流程大致是:
- 在乘客点击下单之后,向数据库增加了一个order,前端每5s询问一次order的当前状态是否为接单,在这期间就没有客户端的事情了。
- 这个下单的动作会激活xxl-job的一个任务调度,给附近已经开启接单的司机的队列中加上这个order的id
- 等到司机端前端检查队列发现了这个订单,就会弹窗问是否需要接取,随后就是一个加锁然后抢票的逻辑了,需要有高并发的支持。
- 这个司机抢到了之后就会update这个订单,把司机的id变为自己的,状态改变。
- 等到乘客5s轮询发现改变了状态,此时接单成功。
其中,最重要的过程是xxl-job的这个任务如何找到周围的司机。平时使用xxl-job都是在web新建的,这里要求每一个订单自动给xxl-job,也就是要对xxl-job的接口做调整,使其能够继承springboot新建任务。
那么周围的司机如何找,每一个司机在接单的时候都会把自己的地理纬度上传到redis,用redis的数据结构geo快速完成计算。
新建订单
POST”/order/submitOrder”
由前端调用百度的api得到的初始位置和结束位置,以及乘客的id来在order数据库中新建一个订单。
对外接口
| 12
 3
 4
 5
 6
 7
 
 | @Operation(summary = "乘客下单")@GuiguLogin
 @PostMapping("/submitOrder")
 public Result<Long> submitOrder(@RequestBody SubmitOrderForm submitOrderForm) {
 submitOrderForm.setCustomerId(AuthContextHolder.getUserId());
 return Result.ok(orderService.submitOrder(submitOrderForm));
 }
 
 | 
但是在数据库中这个表是需要有估计里程和估计价格的,也就是说还要再去调用一次map微服务和rule微服务。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | @Autowiredprivate OrderInfoFeignClient orderInfoFeignClient;
 
 @Override
 public Long submitOrder(SubmitOrderForm submitOrderForm) {
 
 CalculateDrivingLineForm calculateDrivingLineForm = new CalculateDrivingLineForm();
 BeanUtils.copyProperties(submitOrderForm, calculateDrivingLineForm);
 DrivingLineVo drivingLineVo = mapFeignClient.calculateDrivingLine(calculateDrivingLineForm).getData();
 
 
 FeeRuleRequestForm calculateOrderFeeForm = new FeeRuleRequestForm();
 calculateOrderFeeForm.setDistance(drivingLineVo.getDistance());
 calculateOrderFeeForm.setStartTime(new Date());
 calculateOrderFeeForm.setWaitMinute(0);
 FeeRuleResponseVo feeRuleResponseVo = feeRuleFeignClient.calculateOrderFee(calculateOrderFeeForm).getData();
 
 
 OrderInfoForm orderInfoForm = new OrderInfoForm();
 
 BeanUtils.copyProperties(submitOrderForm, orderInfoForm);
 
 orderInfoForm.setExpectDistance(drivingLineVo.getDistance());
 orderInfoForm.setExpectAmount(feeRuleResponseVo.getTotalAmount());
 
 
 Long orderId = orderInfoFeignClient.saveOrderInfo(orderInfoForm).getData();
 
 
 
 return orderId;
 }
 
 | 
订单微服务
| 12
 3
 4
 5
 6
 7
 8
 
 | @Autowiredprivate OrderInfoService orderInfoService;
 
 @Operation(summary = "保存订单信息")
 @PostMapping("/saveOrderInfo")
 public Result<Long> saveOrderInfo(@RequestBody OrderInfoForm orderInfoForm) {
 return Result.ok(orderInfoService.saveOrderInfo(orderInfoForm));
 }
 
 | 
设置订单的信息,状态为正在等待接单,给redis设置一个标识,说明正在接单。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | @Autowiredprivate OrderInfoMapper orderInfoMapper;
 
 @Autowired
 private OrderStatusLogMapper orderStatusLogMapper;
 
 @Autowired
 private RedisTemplate redisTemplate;
 
 @Transactional(rollbackFor = {Exception.class})
 @Override
 public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
 OrderInfo orderInfo = new OrderInfo();
 BeanUtils.copyProperties(orderInfoForm, orderInfo);
 String orderNo = UUID.randomUUID().toString().replaceAll("-","");
 orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());
 orderInfo.setOrderNo(orderNo);
 orderInfoMapper.insert(orderInfo);
 
 
 this.log(orderInfo.getId(), orderInfo.getStatus());
 
 
 redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK, "0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);
 return orderInfo.getId();
 }
 
 public void log(Long orderId, Integer status) {
 OrderStatusLog orderStatusLog = new OrderStatusLog();
 orderStatusLog.setOrderId(orderId);
 orderStatusLog.setOrderStatus(status);
 orderStatusLog.setOperateTime(new Date());
 orderStatusLogMapper.insert(orderStatusLog);
 }
 
 | 
司机和乘客查询订单状态GET”/getOrderStatus/{orderId}”
乘客下完单后,订单状态为1,乘客端小程序会轮询订单状态,当订单状态为2时,说明已经有司机接单了,那么页面进行跳转,进行下一步操作
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @Overridepublic Integer getOrderStatus(Long orderId) {
 LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
 queryWrapper.eq(OrderInfo::getId, orderId);
 queryWrapper.select(OrderInfo::getStatus);
 OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper);
 if(null == orderInfo) {
 
 return OrderStatus.NULL_ORDER.getStatus();
 }
 return orderInfo.getStatus();
 }
 
 | 
乘客端是一下单就一直询问这个接口有没有改变;司机端得是抢到单了然后才能有订单的id,再去用这个id请求方法
xxl-job和redis完成司机搜索调度
xxl-job定时任务查询附近司机,开启接单的司机会把地理坐标传到redis
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | @Autowiredprivate RedisTemplate redisTemplate;
 
 @Override
 public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
 
 
 
 
 Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(), updateDriverLocationForm.getLatitude().doubleValue());
 redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION, point, updateDriverLocationForm.getDriverId().toString());
 return true;
 }
 
 @Override
 public Boolean removeDriverLocation(Long driverId) {
 redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());
 return true;
 }
 
 | 
POST”/searchNearByDriver”
司机端的小程序开启接单服务后,开始实时上传司机的定位信息到redis的GEO缓存,前面乘客已经下单,现在我们就要查找附近适合接单的司机,如果有对应的司机,那就给司机发送新订单消息。
首先是配置经纬度点和距离,作为中心和半径传入redis的arg,得到了一个升序排序的位置表,然后根据这些待选司机的一些配置(比如超过多少距离不予配送)筛选出符合的一队司机,作为结果返回。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 
 | @Autowiredprivate DriverInfoFeignClient driverInfoFeignClient;
 
 @Override
 public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
 
 
 Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());
 
 Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
 
 Circle circle = new Circle(point, distance);
 
 
 RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
 .includeDistance()
 .includeCoordinates()
 .sortAscending();
 
 
 GeoResults<RedisGeoCommands.GeoLocation<String>> result = this.redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);
 
 
 List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = result.getContent();
 
 
 List<NearByDriverVo> list = new ArrayList();
 if(!CollectionUtils.isEmpty(content)) {
 Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = content.iterator();
 while (iterator.hasNext()) {
 GeoResult<RedisGeoCommands.GeoLocation<String>> item = iterator.next();
 
 
 Long driverId = Long.parseLong(item.getContent().getName());
 
 BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
 log.info("司机:{},距离:{}",driverId, item.getDistance().getValue());
 
 
 DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
 
 if(driverSet.getAcceptDistance().doubleValue() != 0 && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0) {
 continue;
 }
 
 if(driverSet.getOrderDistance().doubleValue() != 0 && driverSet.getOrderDistance().subtract(searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
 continue;
 }
 
 
 NearByDriverVo nearByDriverVo = new NearByDriverVo();
 nearByDriverVo.setDriverId(driverId);
 nearByDriverVo.setDistance(currentDistance);
 list.add(nearByDriverVo);
 }
 }
 return list;
 }
 
 | 
xxl-job这里配置就不做介绍了,主要梳理业务
准备一个添加任务调度的业务,一分钟执行一次
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | @Transactional(rollbackFor = Exception.class)@Override
 public Long addAndStartTask(NewOrderTaskVo newOrderTaskVo) {
 OrderJob orderJob = orderJobMapper.selectOne(new LambdaQueryWrapper<OrderJob>().eq(OrderJob::getOrderId, newOrderTaskVo.getOrderId()));
 if(null == orderJob) {
 
 Long jobId = xxlJobClient.addAndStart("newOrderTaskHandler", "", "0 0/1 * * * ?", "新订单任务,订单id:"+newOrderTaskVo.getOrderId());
 
 
 orderJob = new OrderJob();
 orderJob.setOrderId(newOrderTaskVo.getOrderId());
 orderJob.setJobId(jobId);
 orderJob.setParameter(JSONObject.toJSONString(newOrderTaskVo));
 orderJobMapper.insert(orderJob);
 }
 return orderJob.getJobId();
 }
 
 | 
newOrderTaskHandler
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 
 | @XxlJob("newOrderTaskHandler")public void newOrderTaskHandler() {
 log.info("新订单调度任务:{}", XxlJobHelper.getJobId());
 
 
 
 XxlJobLog xxlJobLog = new XxlJobLog();
 xxlJobLog.setJobId(XxlJobHelper.getJobId());
 long startTime = System.currentTimeMillis();
 try {
 
 newOrderService.executeTask(XxlJobHelper.getJobId());
 
 xxlJobLog.setStatus(1);
 } catch (Exception e) {
 xxlJobLog.setStatus(0);
 xxlJobLog.setError(ExceptionUtil.getAllExceptionMsg(e));
 log.error("定时任务执行失败,任务id为:{}", XxlJobHelper.getJobId());
 e.printStackTrace();
 } finally {
 
 int times = (int) (System.currentTimeMillis() - startTime);
 xxlJobLog.setTimes(times);
 xxlJobLogMapper.insert(xxlJobLog);
 }
 }
 
 @Override
 public Boolean executeTask(Long jobId) {
 
 OrderJob orderJob = orderJobMapper.selectOne(new LambdaQueryWrapper<OrderJob>().eq(OrderJob::getJobId, jobId));
 if(null == orderJob) {
 return true;
 }
 NewOrderTaskVo newOrderTaskVo = JSONObject.parseObject(orderJob.getParameter(), NewOrderTaskVo.class);
 
 
 Integer orderStatus = orderInfoFeignClient.getOrderStatus(newOrderTaskVo.getOrderId()).getData();
 if(orderStatus.intValue() != OrderStatus.WAITING_ACCEPT.getStatus().intValue()) {
 xxlJobClient.stopJob(jobId);
 log.info("停止任务调度: {}", JSON.toJSONString(newOrderTaskVo));
 return true;
 }
 
 
 SearchNearByDriverForm searchNearByDriverForm = new SearchNearByDriverForm();
 searchNearByDriverForm.setLongitude(newOrderTaskVo.getStartPointLongitude());
 searchNearByDriverForm.setLatitude(newOrderTaskVo.getStartPointLatitude());
 searchNearByDriverForm.setMileageDistance(newOrderTaskVo.getExpectDistance());
 List<NearByDriverVo> nearByDriverVoList = locationFeignClient.searchNearByDriver(searchNearByDriverForm).getData();
 
 nearByDriverVoList.forEach(driver -> {
 
 String repeatKey = RedisConstant.DRIVER_ORDER_REPEAT_LIST+newOrderTaskVo.getOrderId();
 boolean isMember = redisTemplate.opsForSet().isMember(repeatKey, driver.getDriverId());
 if(!isMember) {
 
 redisTemplate.opsForSet().add(repeatKey, driver.getDriverId());
 
 redisTemplate.expire(repeatKey, RedisConstant.DRIVER_ORDER_REPEAT_LIST_EXPIRES_TIME, TimeUnit.MINUTES);
 
 NewOrderDataVo newOrderDataVo = new NewOrderDataVo();
 newOrderDataVo.setOrderId(newOrderTaskVo.getOrderId());
 newOrderDataVo.setStartLocation(newOrderTaskVo.getStartLocation());
 newOrderDataVo.setEndLocation(newOrderTaskVo.getEndLocation());
 newOrderDataVo.setExpectAmount(newOrderTaskVo.getExpectAmount());
 newOrderDataVo.setExpectDistance(newOrderTaskVo.getExpectDistance());
 newOrderDataVo.setExpectTime(newOrderTaskVo.getExpectTime());
 newOrderDataVo.setFavourFee(newOrderTaskVo.getFavourFee());
 newOrderDataVo.setDistance(driver.getDistance());
 newOrderDataVo.setCreateTime(newOrderTaskVo.getCreateTime());
 
 
 String key = RedisConstant.DRIVER_ORDER_TEMP_LIST+driver.getDriverId();
 redisTemplate.opsForList().leftPush(key, JSONObject.toJSONString(newOrderDataVo));
 
 
 redisTemplate.expire(key, RedisConstant.DRIVER_ORDER_TEMP_LIST_EXPIRES_TIME, TimeUnit.MINUTES);
 log.info("该新订单信息已放入司机临时队列: {}", JSON.toJSONString(newOrderDataVo));
 }
 });
 return true;
 }
 
 | 
最后再在下单的那个任务中执行添加xxl-job任务调度。