xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpOperationsUserController.java
@@ -238,6 +238,9 @@ @PostMapping("/api/v1/operationsUser/listAll") public R<List<XpOperationsUser>> listAll() { List<XpOperationsUser> listAll = xpOperationsUserService.list(); for (XpOperationsUser xpOperationsUser : listAll) { // todo 需要上传工单id,根据距离进行排序 } return R.ok(listAll); } xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpOperationsUserServiceImpl.java
@@ -550,7 +550,6 @@ @Override public PageInfo<XpOperationsUserWithRoleVO> pageListByIds(OptionalUsersPageListQuery q) { PageInfo<XpOperationsUserWithRoleVO> pageInfo = new PageInfo<>(q.getPageNum(), q.getPageSize()); List<XpOperationsUserWithRoleVO> list = baseMapper.pageListByIds(pageInfo, q); pageInfo.setRecords(list); xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpOperationUserClient.java
@@ -113,5 +113,5 @@ R<XpOperationsUser> getByExactPhone(@RequestParam("phoneNumber") String phoneNumber); @PostMapping("/logan/inner/xpOperationsUser/pageListByIds") R<PageInfo<XpOperationsUserWithRoleVO>> listByQueryIds(OptionalUsersPageListQuery query); R<PageInfo<XpOperationsUserWithRoleVO>> listByQueryIds(@RequestBody OptionalUsersPageListQuery query); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -759,7 +759,7 @@ List<XpTradingStrategyPush> xpTradingStrategyPushes = xpTradingStrategyPushMapper.selectList(new QueryWrapper<XpTradingStrategyPush>().eq("trading_strategy_id", xpTradingStrategy.getId())); long now = Long.parseLong(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmm"))); XpTradingStrategyPush xpTradingStrategyPush = xpTradingStrategyPushes.stream().filter(item -> { long start = Long.parseLong(item.getStarTime().replace(":", "")); long start = Long.parseLong(item.getStartTime().replace(":", "")); long end = Long.parseLong(item.getEndTime().replace(":", "")); return now >= start && now < end; }).findFirst().orElse(null); xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpTaskController.java
@@ -47,6 +47,7 @@ import org.apache.shardingsphere.transaction.core.TransactionType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.mongodb.core.MongoTemplate; @@ -358,30 +359,29 @@ @ApiOperation(value = "可改派的运维人员列表", tags = {"管理后台-运维执行-任务工单-待改派工单"}) @PostMapping("/api/v1/task/reassign/availableUsers") public R<PageInfo<XpOperationsUserWithRoleVO>> availableReassignUsers(@RequestBody OptionalUsersPageListQuery query) { public R<PageInfo<XpOperationsUserWithRoleVO>> availableReassignUsers(@RequestBody OptionalUsersPageListQuery query1) { XpTask task = xpTaskService.getById(query1.getTaskId()); // 找到中心点 GeoJsonPoint geoJsonPoint = new GeoJsonPoint(task.getVehicleLon().doubleValue(), task.getVehicleLat().doubleValue()); // 构造半径 Distance distanceR = new Distance(10, Metrics.KILOMETERS); // 画圆 Circle circle = new Circle(geoJsonPoint, distanceR); Query query = Query.query(new Criteria().andOperator( Criteria.where("location").withinSphere(circle) )); List<XpOperationsUserPositionDTO> vehiclePositions = mongoTemplate.find(query, XpOperationsUserPositionDTO.class); Map<Long, Double> disMap = vehiclePositions.stream().collect(Collectors.toMap(XpOperationsUserPositionDTO::getId, XpOperationsUserPositionDTO::getDistance)); XpTask task = xpTaskService.getById(query.getTaskId()); GeoJsonPoint point = new GeoJsonPoint( task.getVehicleLon().doubleValue(), task.getVehicleLat().doubleValue() ); Query q = new Query(Criteria.where("location").near(point) .maxDistance(new Distance(10, Metrics.KILOMETERS).getNormalizedValue())); q.with(Sort.by(Sort.Direction.ASC, "dis")); List<XpOperationsUserPositionDTO> up = mongoTemplate.find(q, XpOperationsUserPositionDTO.class); Map<Long, Double> disMap = up.stream().collect(Collectors.toMap(XpOperationsUserPositionDTO::getId, XpOperationsUserPositionDTO::getDistance)); // 查询车队下面面所有人员id和up求交集 List<Long> data = xpFleetClient.idsByFleetName(query.getFleet()).getData(); List<Long> data = xpFleetClient.idsByFleetName(query1.getFleet()).getData(); List<Long> ids = up.stream().map(XpOperationsUserPositionDTO::getId) List<Long> ids = vehiclePositions.stream().map(XpOperationsUserPositionDTO::getId) .filter(e -> data == null || data.isEmpty() || data.contains(e)) .collect(Collectors.toList()); query.setIds(ids); PageInfo<XpOperationsUserWithRoleVO> ret = operationUserClient.listByQueryIds(query).getData(); query1.setIds(ids); PageInfo<XpOperationsUserWithRoleVO> ret = operationUserClient.listByQueryIds(query1).getData(); // 设置距离,然后排序 xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpTaskServiceImpl.java
@@ -5,14 +5,18 @@ import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.collect.Lists; import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO; import com.xiaopeng.api.dto.account.XpOperationsUserQueryDTO; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO; import com.xiaopeng.api.feignClient.account.SysUserClient; import com.xiaopeng.api.feignClient.account.XpOperationUserClient; import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.SmsClient; import com.xiaopeng.api.feignClient.pushMessage.WebSocketMessageClient; import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient; @@ -26,8 +30,10 @@ import com.xiaopeng.api.model.maintenance.*; import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; import com.xiaopeng.api.vo.XpTaskVO; import com.xiaopeng.api.vo.account.XpOperationsUserWithRoleVO; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; import com.xiaopeng.api.vo.maintenance.TaskScheduleConstants; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.api.vo.operation.SmsVo; import com.xiaopeng.common.core.constant.AliPushTypeConstant; import com.xiaopeng.common.core.domain.R; @@ -49,6 +55,7 @@ import com.xiaopeng.maintenance.model.query.XpTaskRecordPageQuery; import com.xiaopeng.maintenance.model.vo.*; import com.xiaopeng.maintenance.repo.XpOperationsUserRepository; import com.xiaopeng.maintenance.service.IXpClockInService; import com.xiaopeng.maintenance.service.IXpTaskRuleRoleService; import com.xiaopeng.maintenance.service.IXpTaskRuleService; import com.xiaopeng.maintenance.service.XpTaskService; @@ -58,6 +65,7 @@ import com.xiaopeng.maintenance.vo.TaskCleanDetailVO; import com.xiaopeng.maintenance.vo.TaskDetectionDetailVO; import com.xiaopeng.maintenance.vo.TaskReassignmentVo; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; @@ -159,6 +167,9 @@ @Resource private WebSocketMessageClient xpWebSocketMessageClient; @Resource private IXpClockInService xpClockInService; @Override public PageInfo<WorkBenchVO> workbench(WorkBenchQuery query) throws UnsupportedEncodingException { @@ -285,7 +296,52 @@ List<Long> userIds = xpOperationsRoleResult.getData().stream().filter(x -> ids.contains(x.getOperationsUserId())) .map(XpOperationsRole::getOperationsUserId) .distinct().collect(Collectors.toList()); xpTask.setHandleUserId(StringUtils.join(userIds, ",")); //获取首选人的实时定位,计算每个人到事故车辆位置的导航距离 List<Operations> operationsList = new ArrayList<>(); for (Long userId : userIds) { Operations operations = new Operations(); operations.setUserId(userId); XpOperationsUserPositionDTO vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("id").is(userId)), XpOperationsUserPositionDTO.class); if(null != vehiclePosition){ LonLatDto start = new LonLatDto(vehiclePosition.getLongitude(), vehiclePosition.getLatitude()); LonLatDto end = new LonLatDto(xpTask.getVehicleLon().doubleValue(), xpTask.getVehicleLat().doubleValue()); RouteVo routeVo = gdMapGeocodingUtil.getDistanceAndTime(start, end, null, null, null, null); operations.setDistance(routeVo.getDistance()); } operationsList.add(operations); } //对距离进行排序,去距离最近的人员 operationsList.sort((o1, o2) -> o1.getDistance().compareTo(o2.getDistance()) * -1); Operations operations = operationsList.get(0); Long userId = operations.getUserId(); //如果多人距离相同,可按照上班状态>空闲状态>工号排序 List<Operations> collect = operationsList.stream().filter(s -> s.getDistance().equals(operations.getDistance())).collect(Collectors.toList()); if(collect.size() > 1){ //查询已经上班打卡了的人员 List<XpClockIn> xpClockIns = xpClockInService.list(new QueryWrapper<XpClockIn>().in("app_user_id", collect) .isNull("clock_out_result").last(" and DATE(now()) = DATE(clock_in_time)")); if(xpClockIns.size() == 1){ userId = xpClockIns.get(0).getAppUserId(); }else if(xpClockIns.size() > 1){ //查询有任务的人员 List<Long> collect1 = xpClockIns.stream().map(XpClockIn::getAppUserId).collect(Collectors.toList()); List<XpTask> list1 = this.list(new QueryWrapper<XpTask>().eq("task_category", 2).in("handle_user_id", collect1) .in("status", Arrays.asList(0, 1, 4))); if(list1.size() > 0){ //对工号进行排序 List<String> collect2 = list1.stream().map(XpTask::getHandleUserId).collect(Collectors.toList()); //找出没有任务的人员 List<Long> collect3 = collect1.stream().filter(s -> !collect2.contains(s.toString())).collect(Collectors.toList()); //查询人员数据,对其进行工号排序 XpOperationsUserQueryDTO dto1 = new XpOperationsUserQueryDTO(); dto1.setIds(collect3); List<XpOperationsUserWithRoleVO> data = xpOperationUserClient.findList(dto1).getData(); data.sort((o1, o2) -> o1.getUniqueNumber().compareTo(o2.getUniqueNumber()) * -1); userId = data.get(0).getId(); } } } xpTask.setHandleUserId(userId.toString()); } // 根据事故处理限时配置设置任务截止时间 LocalDateTime endTime = LocalDateTime.now().plusMinutes(taskRule.getProcessingTimeLimit()); @@ -308,6 +364,21 @@ return xpTask; } @Data class Operations { /** * 候选人id */ private Long userId; /** * 距离 */ private Integer distance; } /** * 异步发送事故通知 * @param xpTask 任务信息 xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/utils/GDMapGeocodingUtil.java
@@ -1,19 +1,28 @@ package com.xiaopeng.maintenance.utils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.api.dto.map.HistoryPointDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.common.core.utils.HttpUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ForkJoinPool; /** * 高德地图的地理编码工具类 */ @Component @Lazy @Slf4j public class GDMapGeocodingUtil { private String key = "66ae577f63898dae3016d599fda57221"; @@ -53,4 +62,93 @@ return map; } /** * 根据起始点获取驾车距离 和 时间 以及避让区域 * * @param start 起点 * @param end 终点 * @param avoidRoads 避让点 * @param fenceId mango id * @param routePlanRule 线路规划规则 * @return * @throws Exception */ public RouteVo getDistanceAndTime(LonLatDto start, LonLatDto end, ArrayList<JSONObject> avoidRoads, String fenceId, Integer routePlanRule, List<HistoryPointDto> history) { RouteVo routeVo = new RouteVo(); String url = "https://tsapi.amap.com/v1/route/price?key=" + key + "&extensions=all&start=" + start.getLongitude() + "," + start.getLatitude(); HashMap<String, Object> params = new HashMap<>(); params.put("extensions", "all"); params.put("start", start.getLongitude() + "," + start.getLatitude()); params.put("end", end.getLongitude() + "," + end.getLatitude()); params.put("routeUserType", 2); if (avoidRoads != null && !avoidRoads.isEmpty()) { params.put("avoidPolygons", avoidRoads); } if (routePlanRule != null) { params.put("strategy", routePlanRule); } if (null != history && !history.isEmpty()) { params.put("history", history); } log.info("高德地图接口:{}", url); log.info("高德地图参数:{}", JSON.toJSONString(params)); String post = HttpUtils.post(url, JSON.toJSONString(params)); log.info("高德地图返回结果:" + post); JSONObject jsonObject = JSON.parseObject(post); String data = jsonObject.getString("data"); JSONObject jsonObject1 = JSON.parseObject(data); JSONArray path = jsonObject1.getJSONArray("paths"); log.info("开始判断导航路径是否在运营区域范围内 {}", fenceId); log.info("先判断是否有大众常选的线路,优先选择大众常选的路线"); for (Object o : path) { JSONObject jsonObject2 = (JSONObject) o; String routeID = jsonObject2.getString("routeID"); String points = jsonObject2.getString("points"); String label = jsonObject2.getString("label"); if (label.contains("大众常选")) { String[] split = points.split(";"); Integer costTime = jsonObject2.getInteger("costTime"); Integer distance = jsonObject2.getInteger("distance"); routeVo.setDistance(distance); routeVo.setDuration(costTime); ArrayList<LonLatDto> lonLatDtos = new ArrayList<>(split.length); for (String s : split) { String[] split1 = s.split(","); LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1])); lonLatDtos.add(lonLatDto); } routeVo.setPolyline(lonLatDtos); routeVo.setRouteID(routeID); log.info("导航 {} 在 {} 运营区域内", routeVo.getRouteID(), fenceId); return routeVo; } } for (Object o : path) { JSONObject jsonObject2 = (JSONObject) o; String routeID = jsonObject2.getString("routeID"); String points = jsonObject2.getString("points"); String[] split = points.split(";"); Integer costTime = jsonObject2.getInteger("costTime"); Integer distance = jsonObject2.getInteger("distance"); routeVo.setDistance(distance); routeVo.setDuration(costTime); ArrayList<LonLatDto> lonLatDtos = new ArrayList<>(split.length); for (String s : split) { String[] split1 = s.split(","); LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1])); lonLatDtos.add(lonLatDto); } routeVo.setPolyline(lonLatDtos); routeVo.setRouteID(routeID); log.info("导航 {} 在 {} 运营区域内", routeVo.getRouteID(), fenceId); return routeVo; } return null; } } xp-robotaxi-map-boot/src/test/java/com/xiaopeng/map/service/serviceimpl/XpStationServiceImplTest.java
New file @@ -0,0 +1,27 @@ package com.xiaopeng.map.service.serviceimpl; import com.xiaopeng.map.MapApplication; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; /** * XpStationServiceImpl.isPolygonInPolygon 方法单元测试 * * 测试场景: * 1. 正常情况 - 小多边形完全在大多边形内 * 2. 边界情况 - 小多边形部分在大多边形外 * 3. 边界情况 - 小多边形完全在大多边形外 * 4. 边界情况 - 两个多边形相同 * 5. 异常情况 - 空值或null输入 * 6. 异常情况 - 无效的坐标格式 * 7. 边界情况 - 点位数量不足 */ @SpringBootTest public class XpStationServiceImplTest { } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/test/OrderTest.java
New file @@ -0,0 +1,68 @@ package com.xiaopeng.order.controller.test; import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.order.service.IXpOrderService; import com.xiaopeng.unifiedverification.interceptor.XpClientCheck; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/base/test") @Api(tags = "订单测试") public class OrderTest { @Resource private MongoTemplate mongoTemplate; @Resource private IXpOrderService xpOrderService; @Resource private XpVehicleClient xpVehicleClient; /** * 分享行程接口2-订单查询轨迹路线 */ @ApiOperation(value = "分享行程接口2-订单查询轨迹路线") @PostMapping("/mongo/saveVehiclePosition") @XpClientCheck(checkToken = false, checkOtpToken = false) public R upateOrderState(@RequestBody XpOrder param) { XpOrder xpOrder = xpOrderService.getById(param.getId()); // 查询车辆 XpVehicle xpVehicle = xpVehicleClient.queryById(param.getVehicleId()).getData(); xpOrder.setVehicleId(xpVehicle.getId()); xpOrder.setFleetId(xpVehicle.getFleetId()); xpOrderService.updateById(xpOrder); VehiclePosition vehiclePosition = new VehiclePosition(); vehiclePosition.setVin(xpVehicle.getVehicleVin()); vehiclePosition.setPoint(new GeoJsonPoint(xpOrder.getStartLon(), xpOrder.getStartLat())); vehiclePosition.setCourse(60D); vehiclePosition.setAlt(100F); vehiclePosition.setSpeed(50F); vehiclePosition.setCurrentElectricity(80); vehiclePosition.setMileageRange(300f); vehiclePosition.setHasOrder(0); vehiclePosition.setOrderId(param.getId()); mongoTemplate.save(vehiclePosition); return R.ok(); } } xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/PositionMsgHandler.java
@@ -1,5 +1,7 @@ package com.xiaopeng.pushMessage.webSocket.handler; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO; @@ -16,6 +18,10 @@ @Slf4j public class PositionMsgHandler implements BusinessMsgHandler{ @Autowired private MongoTemplate mongoTemplate; @Override public String getMessageType() { return "position"; @@ -23,36 +29,35 @@ @Override public void handle(String uid, String msg) { XpOperationsUserPositionDTO xpOperationsUserPositionDTO; try { xpOperationsUserPositionDTO = om.readValue(msg, XpOperationsUserPositionDTO.class); } catch (JsonProcessingException e) { throw new RuntimeException(e); } if (null != xpOperationsUserPositionDTO) { xpOperationsUserPositionDTO.setId(Long.parseLong(uid)); saveToMongo(xpOperationsUserPositionDTO); JSONObject jsonObject = JSON.parseObject(msg); if (null != jsonObject) { saveToMongo(uid, jsonObject); } } @Autowired ObjectMapper om; @Autowired MongoTemplate mongoTemplate; private void saveToMongo(XpOperationsUserPositionDTO dto) { XpOperationsUserPositionDTO vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("id").is(dto.getId())), XpOperationsUserPositionDTO.class); private void saveToMongo(String uid, JSONObject jsonObject) { Double latitude = jsonObject.getDouble("latitude"); Double longitude = jsonObject.getDouble("longitude"); XpOperationsUserPositionDTO vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("id").is(uid)), XpOperationsUserPositionDTO.class); if (null == vehiclePosition) { GeoJsonPoint location = new GeoJsonPoint(dto.getLongitude(), dto.getLatitude()); dto.setLocation(location); mongoTemplate.save(dto); vehiclePosition = new XpOperationsUserPositionDTO(); GeoJsonPoint location = new GeoJsonPoint(longitude, latitude); vehiclePosition.setLocation(location); vehiclePosition.setId(Long.parseLong(uid)); vehiclePosition.setLatitude(latitude); vehiclePosition.setLongitude(longitude); vehiclePosition.setLocation(location); mongoTemplate.save(vehiclePosition); }else{ Update update = new Update() .set("id",dto.getId()) .set("latitude", dto.getLatitude()) .set("longitude", dto.getLongitude()); double[] location = {dto.getLongitude(), dto.getLatitude()}; .set("id", Long.parseLong(uid)) .set("latitude", latitude) .set("longitude", longitude); GeoJsonPoint location = new GeoJsonPoint(longitude, latitude); update.set("location", location); mongoTemplate.updateFirst(new Query(Criteria.where("id").is(dto.getId())), update, XpOperationsUserPositionDTO.class); mongoTemplate.updateFirst(new Query(Criteria.where("id").is(Long.parseLong(uid))), update, XpOperationsUserPositionDTO.class); } log.info("保存位置信息成功:id:{},longitude:{},latitude:{}", dto.getId(), dto.getLongitude(),dto.getLatitude()); log.info("保存位置信息成功:id:{},longitude:{},latitude:{}", uid, longitude, latitude); } } xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/WebSocketTextFrameHandler.java
@@ -76,6 +76,7 @@ handleBusinessMessage(ctx, messageObj); } } catch (Exception e) { e.printStackTrace(); // 如果不是JSON格式,按普通文本处理 log.debug("消息不是JSON格式,按普通文本处理: {}", message); ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now() + "->" + message));