乘客下单
主要业务是从乘客选择起始地点和终止地点之后,也就是初步计算了估计价格然后点击呼叫司机之后的过程。等到附近的司机抢单成功,该业务执行完毕。
流程大致是:
- 在乘客点击下单之后,向数据库增加了一个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数据库中新建一个订单。
对外接口
1 2 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微服务。
1 2 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
| @Autowired private 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; }
|
订单微服务
1 2 3 4 5 6 7 8
| @Autowired private OrderInfoService orderInfoService;
@Operation(summary = "保存订单信息") @PostMapping("/saveOrderInfo") public Result<Long> saveOrderInfo(@RequestBody OrderInfoForm orderInfoForm) { return Result.ok(orderInfoService.saveOrderInfo(orderInfoForm)); }
|
设置订单的信息,状态为正在等待接单,给redis设置一个标识,说明正在接单。
1 2 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
| @Autowired private 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时,说明已经有司机接单了,那么页面进行跳转,进行下一步操作
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Autowired private 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,得到了一个升序排序的位置表,然后根据这些待选司机的一些配置(比如超过多少距离不予配送)筛选出符合的一队司机,作为结果返回。
1 2 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
| @Autowired private 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这里配置就不做介绍了,主要梳理业务
准备一个添加任务调度的业务,一分钟执行一次
1 2 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
1 2 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任务调度。