liujie
2026-05-12 0bcfca6b2561d096a2ce26e2e7200cd0768b2b6d
Merge branch '1.0' of http://120.76.84.145:10101/gitblit/r/java/Robotaxi into 1.0
1个文件已删除
6个文件已添加
42个文件已修改
1641 ■■■■■ 已修改文件
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XtigerSSOController.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/OIDCUserInfoVo.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/SSOToken.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/bootstrap.yml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCollectAddrMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/PushOrderDto.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/map/XpMapClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/map/XpStationClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/UserSubscribeMessageClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpStationClient.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpWhitelist.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/query/operation/PointListQuery.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/ElectronicFenceDetailVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/BluetoothDeliverVo.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandEnum.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/web/domain/AjaxResult.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-common/xp-common-security/src/main/java/com/xiaopeng/common/security/handler/GlobalExceptionHandler.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpStationController.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpElectronicFenceController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/job/base/AbstractJob.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpPointService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/OrderDistanceVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/bootstrap.yml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/BluetoothDebugConfig.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/PropertiesAutoConfiguration.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/ActiveOrderController.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpCruiseOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/PushChainReactionOrderJob.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpTestOrderServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaskOrderCancelUtil.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/TrajectoryDensifier.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/service/WebSocketUserChannelConnectionManager.java 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XtigerSSOController.java
New file
@@ -0,0 +1,134 @@
package com.xiaopeng.account.controller;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.xiaopeng.account.service.impl.SysLoginService;
import com.xiaopeng.account.vo.OIDCUserInfoVo;
import com.xiaopeng.account.vo.SSOToken;
import com.xiaopeng.api.model.account.SysRole;
import com.xiaopeng.api.vo.account.LoginOperationUser;
import com.xiaopeng.api.vo.account.LoginUser;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/ssoAuthSdk")
public class XtigerSSOController {
    @Value("${xiaopeng.xtiger.sso.issuer}")
    private String issuer;
    @Value("${xiaopeng.xtiger.sso.appId}")
    private String appId;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private SysLoginService sysLoginService;
    /**
     * 翼虎web登录重定向接口
     * 通过code换取token
     *
     * @param code
     * @param state
     */
    @GetMapping("/callback")
    public R<?> callback(String code, String state, HttpServletRequest request) {
        log.info("翼虎登录重定向接口, code: {}, state: {}", code, state);
        HttpRequest post = HttpUtil.createPost(issuer + "/oidc/token/v2");
        post.form("client_id", appId);
        post.form("code", code);
        post.form("grant_type", "authorization_code");
        HttpResponse execute = post.execute();
        if (!execute.isOk()) {
            throw new ServiceException("登录失败,请稍后重试。");
        }
        String body = execute.body();
        log.info("翼虎登录重定向接口, 返回结果: {}", body);
        SSOToken ssoToken = JSON.parseObject(body, SSOToken.class);
        String accessToken = ssoToken.getAccessToken();
        OIDCUserInfoVo userInfo = getUserInfo(accessToken);
        return login(userInfo, request);
    }
    /**
     * 翼虎app登录换取本系统token
     *
     * @param request
     * @return
     */
    @GetMapping("/appLogin")
    public R<?> appLogin(Integer deviceType, HttpServletRequest request) {
        String accessTokenHeader = request.getHeader("Authorization");
        String accessToken = StringUtils.isEmpty(accessTokenHeader) ? null : accessTokenHeader.replace("Bearer ", "");
        if (StringUtils.isEmpty(accessToken)) {
            throw new ServiceException("登录失败,请稍后重试。");
        }
        OIDCUserInfoVo userInfo = getUserInfo(accessToken);
        return appLogin(userInfo, deviceType, request);
    }
    public OIDCUserInfoVo getUserInfo(String accessToken) {
        log.info("翼虎登录使用token获取用户信息");
        HttpRequest post = HttpUtil.createGet(issuer + "/oidc/userinfo");
        post.header("Authorization", "Bearer " + accessToken);
        HttpResponse execute = post.execute();
        if (!execute.isOk()) {
            throw new ServiceException("登录失败,请稍后重试。");
        }
        String body = execute.body();
        log.info("翼虎登录使用token获取用户信息, 返回结果: {}", body);
        OIDCUserInfoVo ssoToken = JSON.parseObject(body, OIDCUserInfoVo.class);
        return ssoToken;
    }
    public R<?> login(OIDCUserInfoVo userInfo, HttpServletRequest request) {
        // 用户登录
        LoginUser loginUser = sysLoginService.login(userInfo.getPhone_number(), request);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", tokenService.createToken(loginUser));
        List<SysRole> roles = loginUser.getSysUser().getRoles();
        // if (CollectionUtils.isEmpty(roles)) {
        //     throw new ServiceException("请关联角色!", I18nKeyConstant.LOGIN_ROLE_NOT_BIND);
        // }
        // map.put("roleName",roles.get(0).getRoleName());
        map.put("info", userInfo);
        // 修改用户最后登录时间
        return R.ok(map);
    }
    public R<?> appLogin(OIDCUserInfoVo userInfo, Integer deviceType, HttpServletRequest request) {
        LoginOperationUser loginUser = sysLoginService.verificationCodeLogin(userInfo.getPhone_number(), deviceType, request);
        HashMap<String, Object> map = new HashMap<>();
        map.put("token", tokenService.createOperationUserToken(loginUser));
//        List<SysRole> roles = userInfo.getXpOperationsUser().getRoles();
//        if (CollectionUtils.isEmpty(roles)) {
//            return R.fail("请关联角色!");
//        }
//        map.put("roleName", roles.get(0).getRoleName());
        map.put("info", loginUser);
        return R.ok(map);
    }
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/SysLoginService.java
@@ -114,6 +114,54 @@
        // recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_SUCCESS_STATUS, "登录成功");
        return userInfo;
    }
    /**
     * 登录
     */
    public LoginUser login(String username, HttpServletRequest request) {
        username = username.trim();
        // 查询用户信息
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user)) {
            throw new ServiceException("帐号不存在或已被删除", I18nKeyConstant.ACCOUNT_NOT_EXISTS);
        }
        // 角色集合
        Set<String> roles = permissionService.getRolePermission(user);
        // 权限集合
        Set<String> menuPermission = permissionService.getMenuPermission(user);
        LoginUser userInfo = new LoginUser();
        userInfo.setSysUser(user);
        userInfo.setRoles(roles);
        userInfo.setPermissions(menuPermission);
        if (StringUtils.isNull(user)) {
            recordLogService.recordLogininfor(request, null, username, Constants.LOGIN_FAIL_STATUS, "登录用户不存在");
            throw new ServiceException("用户名/密码错误", I18nKeyConstant.LOGIN_USERNAME_PASSWORD);
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "用户名不在指定范围");
            throw new ServiceException("用户名不在指定范围",I18nKeyConstant.LOGIN_USERNAME_NOT_VALID);
        }
        // IP黑名单校验
        String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "很遗憾,访问IP已被列入系统黑名单");
            throw new ServiceException("很遗憾,访问IP已被列入系统黑名单", I18nKeyConstant.LOGIN_BLACK_LIST);
        }
        if (user.getDeleted()) {
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "对不起,您的账号已被删除");
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        if (Integer.valueOf("1").equals(user.getStatus())) {
            recordLogService.recordLogininfor(request, user.getId().intValue(), username, Constants.LOGIN_FAIL_STATUS, "用户已停用,请联系管理员");
            throw new ServiceException("您的账号:" + username + " 已禁用", I18nKeyConstant.LOGIN_ACCOUNT_DISABLED, new Object[]{username});
        }
        return userInfo;
    }
    
    public void logout(String loginName, HttpServletRequest request) {
        recordLogService.recordLogininfor(request, null, loginName, Constants.LOGIN_SUCCESS_STATUS, "退出成功");
@@ -179,6 +227,8 @@
        operationsUserService.updateById(operationsUser);
        return loginOperationUser;
    }
    public LoginOperationUser verificationCodeLogin(String phoneNumber, String code, Integer deviceType, HttpServletRequest request) {
        LoginOperationUser loginOperationUser = new LoginOperationUser();
        phoneNumber = phoneNumber.trim();
@@ -213,7 +263,30 @@
        operationsUserService.updateById(operationsUser);
        return loginOperationUser;
    }
    public Boolean verificationCodeUpdate(UpdatePasswordDTO updatePasswordDTO, HttpServletRequest request) {
    public LoginOperationUser verificationCodeLogin(String phoneNumber, Integer deviceType, HttpServletRequest request) {
        LoginOperationUser loginOperationUser = new LoginOperationUser();
        phoneNumber = phoneNumber.trim();
        // 查询运维用户信息
        XpOperationsUser operationsUser = operationsUserService.lambdaQuery().eq(
                XpOperationsUser::getPhoneNumber, phoneNumber
        ).last("limit 1").one();
        if (StringUtils.isNull(operationsUser)) {
            throw new ServiceException("验证码错误!");
        }
        if (!operationsUser.getStatus()){
            throw new ServiceException("账号已禁用!");
        }
        loginOperationUser.setXpOperationsUser(operationsUser);
        //更新用户登录设备类型
        operationsUser.setDeviceType(deviceType);
        operationsUserService.updateById(operationsUser);
        return loginOperationUser;
    }
    public Boolean verificationCodeUpdate(UpdatePasswordDTO updatePasswordDTO, HttpServletRequest request) {
        String phoneNumber = updatePasswordDTO.getPhoneNumber();
        String code = updatePasswordDTO.getVerificationCode();
        phoneNumber = phoneNumber.trim();
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/OIDCUserInfoVo.java
New file
@@ -0,0 +1,47 @@
package com.xiaopeng.account.vo;
import lombok.Data;
@Data
public class OIDCUserInfoVo {
    /**
     * 用户id
     */
    private String phone_number;
    /**
     * 用户名
     */
    private String username;
    /**
     * 姓名
     */
    private String name;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 用户头像图片地址
     */
    private String picture;
    /**
     * 工号
     */
    private String job_num;
    /**
     * 入职时间,yyyy-MM-dd格式
     */
    private String entry_date;
    /**
     * 更新时间, 时间戳, 精确到秒
     */
    private Long updated_at;
    /**
     * 更新时间,yyyy-MM-dd HH:mm:ss格式
     */
    private String updated_time;
    /**
     * 骁龙接入翼虎使用的用户中心映射uid
     */
    private Long xuid;
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/vo/SSOToken.java
New file
@@ -0,0 +1,36 @@
package com.xiaopeng.account.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * 翼虎登录token
 */
@Data
public class SSOToken {
    /**
     * 翼虎登录token
     */
    @JsonProperty("access_token")
    private String accessToken;
    /**
     * 翼虎登录token过期时间
     */
    @JsonProperty("expires_in")
    private Long expireIn;
    /**
     * 翼虎登录token类型
     */
    @JsonProperty("token_type")
    private String tokenType;
    /**
     * 翼虎登录token刷新token
     */
    @JsonProperty("refresh_token")
    private String refreshToken;
    /**
     * 翼虎登录token范围
     */
    @JsonProperty("scope")
    private String scope;
}
xp-robotaxi-account-boot/src/main/resources/bootstrap.yml
@@ -29,6 +29,7 @@
        namespace: 88de3938-82f9-4cab-97cf-6173202703e0
        username: nacos
        password: nacos
        ip: 192.168.110.85
      config:
        # 配置中心地址
        server-addr: 192.168.110.80:8848
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCollectAddrMapper.xml
@@ -14,7 +14,7 @@
        from xp_app_user_collect_addr c
        where c.app_user_id = #{uid}
          and c.is_deleted = 0
        order by c.create_time asc
        order by c.create_time desc
    </select>
    <select id="selectPointCollectTarget" resultType="com.xiaopeng.account.model.vo.CollectAddrTargetVO">
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/automaticPilot/PushOrderDto.java
@@ -26,12 +26,18 @@
    @ApiModelProperty("订单id")
    private String orderId;
    @ApiModelProperty("起点点位id")
    private Long startWaypointId;
    @ApiModelProperty("起点经度")
    private Double startLongitude;
    @ApiModelProperty("起点纬度")
    private Double startLatitude;
    @ApiModelProperty("起点点位id")
    private Long endWaypointId;
    @ApiModelProperty("终点经度")
    private Double endLongitude;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
@@ -29,6 +29,9 @@
    @ApiModelProperty(value = "剩余时间")
    private Double duration;
    @ApiModelProperty(value = "时速")
    private Float speed;
    @NotNull(message = "订单类型不能为空")
    @ApiModelProperty(value = "订单类型 1=实时订单 2=巡游订单 3=测试单")
    private Integer routeType;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/map/XpMapClientFallbackFactory.java
@@ -12,6 +12,8 @@
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class XpMapClientFallbackFactory implements FallbackFactory<XpMapClient> {
@@ -25,6 +27,12 @@
            }
            @Override
            public R<List<RouteVo>> getDistanceAndDurationList(RouteAndFareDto dto) {
                log.error("获取距离和时长失败:{}", cause.getMessage());
                return R.fail("获取距离和时长失败");
            }
            @Override
            public R<RouteVo> getOrderDistanceAndDuration(OrderRouteAndFareDto dto) {
                log.error("获取订单距离和时长失败:{}", cause.getMessage());
                return R.fail("获取订单距离和时长失败,"+cause.getMessage());
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/map/XpStationClientFallbackFactory.java
@@ -6,6 +6,8 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.util.List;
@Slf4j
public class XpStationClientFallbackFactory implements FallbackFactory<XpStationClient> {
    @Override
@@ -17,6 +19,12 @@
                log.error("根据id获取数据失败", cause);
                return R.fail("根据id获取数据失败");
            }
            @Override
            public R<List<XpStation>> getXpStationByElectronicId(Long electronicId) {
                log.error("根据围栏id获取场站失败:" + cause.getMessage());
                return R.fail("根据围栏id获取场站失败");
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/UserSubscribeMessageClient.java
@@ -20,6 +20,6 @@
     * @param uid
     * @return
     */
    @GetMapping("/logan/inner/v1/subscribeMessage/getUserOpenId/{v}")
    @GetMapping("/logan/inner/v1/subscribeMessage/getUserOpenId/{uid}")
    R<String> getUserOpenId(@PathVariable("uid") Integer uid);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java
@@ -15,6 +15,8 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(contextId = "XpMapClient", value = ServiceNameConstantsEnum.XP_MAP_SERVICE, fallbackFactory = XpMapClientFallbackFactory.class)
public interface XpMapClient {
    /**
@@ -25,6 +27,15 @@
    @PostMapping("/base/v1/map/getDistanceAndDuration")
    R<RouteVo> getDistanceAndDuration(@RequestBody RouteAndFareDto dto);
    /**
     * 获取距离和时长
     * @param dto
     * @return
     */
    @PostMapping("/base/v1/map/getDistanceAndDurationList")
    R<List<RouteVo>> getDistanceAndDurationList(@RequestBody RouteAndFareDto dto);
    /**
     * 获取距离和时长
     * @param dto
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/map/XpStationClient.java
@@ -7,6 +7,10 @@
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@FeignClient(contextId = "XpStationClient", value = ServiceNameConstantsEnum.XP_MAP_SERVICE, fallbackFactory = XpStationClientFallbackFactory.class)
public interface XpStationClient {
@@ -18,4 +22,12 @@
     */
    @GetMapping("/logan/inner/v1/xpStation/getXpStationById/{id}")
    R<XpStation> getXpStationById(@PathVariable("id") Long id);
    /**
     * 根据围栏id获取场站
     * @param electronicId
     * @return
     */
    @PostMapping("/logan/inner/v1/xpStation/getXpStationByElectronicId")
    R<List<XpStation>> getXpStationByElectronicId(@RequestParam("electronicId") Long electronicId);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/operation/XpWhitelist.java
@@ -63,13 +63,16 @@
    @ApiModelProperty(value = "用户id 根据手机号获取")
    private Long appUserId;
    public Boolean getStatus() {
        if(expireTime!= null){
            if (LocalDate.now().isAfter(expireTime)) {
                return false;
    /***
     * 在正常情况下 有效期是否过期
     * @return
     */
    public Boolean getIsExpireTime() {
        if(expireTime!= null && status != null){
            if(status){
                return !LocalDate.now().isAfter(expireTime);
            }
            return status;
        }
        return status;
        return false;
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/query/operation/PointListQuery.java
@@ -11,6 +11,9 @@
@ApiModel(value = "点位管理列表查询query")
public class PointListQuery extends BasePage {
    private Long id;
    @ApiModelProperty(value = "电子围栏区域id")
    private List<Long> electronicAreaIds;
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/ElectronicFenceDetailVo.java
@@ -2,6 +2,8 @@
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpStation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -14,8 +16,12 @@
    @ApiModelProperty("电子围栏区域列表")
    private List<XpElectronicFenceArea> electronicFenceAreas;
    @ApiModelProperty("点位列表")
    private List<XpPoint> xpPointList;
    @ApiModelProperty("点位数量")
    private Numbers pointsNumbers;
    @ApiModelProperty("场站列表")
    private List<XpStation> xpStationList;
    @ApiModelProperty("场站数量")
    private Numbers stationNumbers;
    @Data
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/BluetoothDeliverVo.java
@@ -6,9 +6,9 @@
@Data
public class BluetoothDeliverVo {
    /**
     * VIM码
     * VIN码
     */
    private String vim;
    private String vin;
    /**
     * 乘客用户 ID
     */
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandEnum.java
@@ -10,7 +10,7 @@
    DOOR_TRUNK_OPEN("DOOR_TRUNK_OPEN", "后备箱开启"),
    DOOR_TRUNK_CLOSE("DOOR_TRUNK_CLOSE", "后备箱关闭"),
    DOOR_UNLOCK("DOOR_UNLOCK", "车门开锁"),
    HVAC_TEMP_SET_FLOAT("HVAC_TEMP_SET_FLOAT", "设定温度"),
    LEFT_REAR_HVAC_TEMP_SET_FLOAT("LEFT_REAR_HVAC_TEMP_SET_FLOAT", "设定温度"),
    STATUS_REPORT_NOW("STATUS_REPORT_NOW", "上报实时车况"),
    HVAC_ON("HVAC_ON", "空调开启"),
    HVAC_OFF("HVAC_OFF", "空调关闭");
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java
@@ -54,6 +54,20 @@
@Api(tags = "订单任务Api")
public class OrderMissionController {
    /**
     * 共享线程池,用于推送司机位置轨迹,避免每次请求创建新线程池导致资源泄漏
     */
    private static final ThreadPoolExecutor DRIVER_LOCATION_EXECUTOR = new ThreadPoolExecutor(
            2, 4, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(500),
            r -> {
                Thread t = new Thread(r, "driver-location-push");
                t.setDaemon(true);
                return t;
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Resource
    private XPUUtil xpuUtil;
    @Resource
@@ -79,7 +93,7 @@
            taskBuilder.setTaskId(StringUtils.isNotEmpty(dto.getEndTaskId()) ? dto.getEndTaskId() : snowflake.nextId() + "");
            taskBuilder.setTaskType(OrderType.ORDER_TYPE_CRUISE);
            //构建起点坐标数据
            pointBuilderStart.setWaypointId(dto.getOrderId() + "-001");
            pointBuilderStart.setWaypointId(dto.getStartWaypointId() + "");
            pointBuilderStart.setPointType(PointType.START_POINT);
            pointBuilderStart.setSequence(1);
            pointBuilderStart.setActionType(ActionType.ACTION_UNSPECIFIED);
@@ -90,7 +104,7 @@
            taskBuilder.addPoints(pointBuilderStart);
            //构建终点数据
            pointBuilderEnd.setWaypointId(dto.getOrderId() + "-002");
            pointBuilderEnd.setWaypointId(dto.getEndWaypointId() + "");
            pointBuilderEnd.setPointType(PointType.CRUISE_END_POINT);
            pointBuilderEnd.setSequence(2);
            pointBuilderEnd.setActionType(ActionType.DRIVE_THROUGH);
@@ -108,7 +122,7 @@
                taskActionBuilder.setOrderId(dto.getLastOrderId());
                taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                taskActionBuilder.setActionType(dto.getTaskActionType());
                taskBuilder.setPreActions(0, taskActionBuilder);
                taskBuilder.addPreActions(taskActionBuilder.build());
            }
            taskListBuilder.addTasks(taskBuilder.build());
        }else if(dto.getOrderType().equals(com.xiaopeng.api.vo.OrderType.USER_ORDER)){
@@ -121,7 +135,6 @@
                    taskBuilder.setTaskType(OrderType.ORDER_TYPE_ROBOTAXI);
                    VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(dto.getVin())), VehiclePosition.class);
                    //构建车辆当前位置数据
                    pointBuilderStart.setWaypointId(dto.getOrderId() + "-JJ-001");
                    pointBuilderStart.setPointType(PointType.START_POINT);
                    pointBuilderStart.setActionType(ActionType.ACTION_UNSPECIFIED);
                    pointBuilderStart.setSequence(1);
@@ -132,7 +145,7 @@
                    taskBuilder.addPoints(pointBuilderStart);
                    //构建上车点位置信息
                    pointBuilderEnd.setWaypointId(dto.getOrderId() + "-JJ-002");
                    pointBuilderEnd.setWaypointId(dto.getStartWaypointId() + "");
                    pointBuilderEnd.setPointType(PointType.PICK_UP_POINT);
                    pointBuilderEnd.setActionType(ActionType.PICK_UP);
                    pointBuilderEnd.setSequence(2);
@@ -150,7 +163,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }else{
@@ -159,7 +172,7 @@
                    taskBuilder.setTaskId(StringUtils.isNotEmpty(dto.getEndTaskId()) ? dto.getEndTaskId() : snowflake.nextId() + "");
                    taskBuilder.setTaskType(OrderType.ORDER_TYPE_ROBOTAXI);
                    //构建上车点位置信息
                    pointBuilderStart.setWaypointId(dto.getOrderId() + "-SD-001");
                    pointBuilderStart.setWaypointId(dto.getStartWaypointId() + "");
                    pointBuilderStart.setPointType(PointType.PICK_UP_POINT);
                    pointBuilderStart.setActionType(ActionType.PICK_UP);
                    pointBuilderStart.setSequence(1);
@@ -170,7 +183,7 @@
                    taskBuilder.addPoints(pointBuilderStart);
                    //构建下车点位置信息
                    pointBuilderEnd.setWaypointId(dto.getOrderId() + "-SD-002");
                    pointBuilderEnd.setWaypointId(dto.getEndWaypointId() + "");
                    pointBuilderEnd.setPointType(PointType.END_POINT);
                    pointBuilderEnd.setActionType(ActionType.DROP_OFF);
                    pointBuilderEnd.setSequence(2);
@@ -189,7 +202,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }
@@ -204,7 +217,6 @@
                    taskBuilder.setTaskType(OrderType.ORDER_TYPE_ROBOTAXI);
                    VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(dto.getVin())), VehiclePosition.class);
                    //构建车辆当前位置数据
                    pointBuilderStart.setWaypointId(dto.getOrderId() + "-JJ-001");
                    pointBuilderStart.setPointType(PointType.START_POINT);
                    pointBuilderStart.setActionType(ActionType.ACTION_UNSPECIFIED);
                    pointBuilderStart.setSequence(1);
@@ -215,7 +227,7 @@
                    taskBuilder.addPoints(pointBuilderStart);
                    //构建上车点位置信息
                    pointBuilderEnd.setWaypointId(dto.getOrderId() + "-JJ-002");
                    pointBuilderEnd.setWaypointId(dto.getStartWaypointId() + "");
                    pointBuilderEnd.setPointType(PointType.PICK_UP_POINT);
                    pointBuilderEnd.setActionType(ActionType.PICK_UP);
                    pointBuilderEnd.setSequence(2);
@@ -234,7 +246,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }else{
@@ -243,7 +255,7 @@
                    taskBuilder.setTaskId(StringUtils.isNotEmpty(dto.getEndTaskId()) ? dto.getEndTaskId() : snowflake.nextId() + "");
                    taskBuilder.setTaskType(OrderType.ORDER_TYPE_ROBOTAXI);
                    //构建上车点位置信息
                    pointBuilderStart.setWaypointId(dto.getOrderId() + "-SD-001");
                    pointBuilderStart.setWaypointId(dto.getStartWaypointId() + "");
                    pointBuilderStart.setPointType(PointType.PICK_UP_POINT);
                    pointBuilderStart.setActionType(ActionType.PICK_UP);
                    pointBuilderStart.setSequence(1);
@@ -254,7 +266,7 @@
                    taskBuilder.addPoints(pointBuilderStart);
                    //构建下车点位置信息
                    pointBuilderEnd.setWaypointId(dto.getOrderId() + "-SD-002");
                    pointBuilderEnd.setWaypointId(dto.getEndWaypointId() + "");
                    pointBuilderEnd.setPointType(PointType.END_POINT);
                    pointBuilderEnd.setActionType(ActionType.DROP_OFF);
                    pointBuilderEnd.setSequence(2);
@@ -272,7 +284,7 @@
                        taskActionBuilder.setOrderId(dto.getLastOrderId());
                        taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                        taskActionBuilder.setActionType(dto.getTaskActionType());
                        taskBuilder.setPreActions(0, taskActionBuilder);
                        taskBuilder.addPreActions(taskActionBuilder.build());
                    }
                    taskListBuilder.addTasks(taskBuilder.build());
                }
@@ -284,7 +296,7 @@
            taskBuilder.setTaskId(StringUtils.isNotEmpty(dto.getEndTaskId()) ? dto.getEndTaskId() : snowflake.nextId() + "");
            taskBuilder.setTaskType(OrderType.ORDER_TYPE_CRUISE);
            //构建起点坐标数据
            pointBuilderStart.setWaypointId(dto.getOrderId() + "-001");
            pointBuilderStart.setWaypointId(dto.getStartWaypointId() + "");
            pointBuilderStart.setPointType(PointType.START_POINT);
            pointBuilderStart.setSequence(1);
            pointBuilderStart.setActionType(ActionType.ACTION_UNSPECIFIED);
@@ -295,7 +307,7 @@
            taskBuilder.addPoints(pointBuilderStart);
            //构建终点数据
            pointBuilderEnd.setWaypointId(dto.getOrderId() + "-002");
            pointBuilderEnd.setWaypointId(dto.getEndWaypointId() + "");
            pointBuilderEnd.setPointType(PointType.CRUISE_END_POINT);
            pointBuilderEnd.setSequence(2);
            pointBuilderEnd.setActionType(ActionType.DRIVE_THROUGH);
@@ -313,7 +325,7 @@
                taskActionBuilder.setOrderId(dto.getLastOrderId());
                taskActionBuilder.setTaskId(dto.getLastXpuTaskId());
                taskActionBuilder.setActionType(dto.getTaskActionType());
                taskBuilder.setPreActions(0, taskActionBuilder);
                taskBuilder.addPreActions(taskActionBuilder.build());
            }
            taskListBuilder.addTasks(taskBuilder.build());
        }
@@ -459,21 +471,40 @@
     */
    @GetMapping("/base/v1/order/acceptingOrder")
    public R acceptingOrder(Long orderId, String code, Long vehicleId, Double startLon, Double startLat){
        log.info("【模拟接单】开始,orderId={}, code={}, vehicleId={}, startLon={}, startLat={}", orderId, code, vehicleId, startLon, startLat);
        if(null == vehicleId){
            vehicleId = 11L;//默认使用造的测试车辆
            log.info("【模拟接单】vehicleId为空,使用默认测试车辆 vehicleId={}", vehicleId);
        }
        if(null == startLat || null == startLon){
            startLat = 23.146815D;
            startLon = 113.459765D;
            log.info("【模拟接单】起点坐标为空,使用默认坐标 startLon={}, startLat={}", startLon, startLat);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        log.info("【模拟接单】查询车辆信息,vehicleId={}, vin={}", vehicleId, xpVehicle != null ? xpVehicle.getVehicleVin() : "null");
        XpOrder xpOrder = null;
        if(null != orderId){
            xpOrder = xpOrderClient.queryOrderById(orderId).getData();
            log.info("【模拟接单】按orderId查询订单,orderId={}, orderNo={}", orderId, xpOrder != null ? xpOrder.getOrderNumber() : "null");
        }
        if(StringUtils.isNotEmpty(code)){
            xpOrder = xpOrderClient.queryOrderByOrderNo(code).getData();
            log.info("【模拟接单】按orderNo查询订单,code={}, orderId={}", code, xpOrder != null ? xpOrder.getId() : "null");
        }
        orderId = xpOrder.getId();
        log.info("【模拟接单】订单信息,orderId={}, appUserId={}, startLon={}, startLat={}", orderId, xpOrder.getAppUserId(), xpOrder.getStartLon(), xpOrder.getStartLat());
        OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
        dto.setOrderId(orderId);
        LonLatDto origin = new LonLatDto();
        origin.setLongitude(startLon);
        origin.setLatitude(startLat);
        dto.setOrigin(origin);
        //保存车辆实时位置到redis
        redisTemplate.opsForGeo().add("vehiclePosition:" + xpVehicle.getVehicleVin(), new Point(startLon, startLat), xpVehicle.getVehicleVin());
        log.info("【模拟接单】保存车辆位置到Redis,vin={}, lon={}, lat={}", xpVehicle.getVehicleVin(), startLon, startLat);
        //保存车辆实时信息到mongodb
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
@@ -509,32 +540,31 @@
            mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), update, VehiclePosition.class);
        }
        orderId = xpOrder.getId();
        OrderRouteAndFareDto dto = new OrderRouteAndFareDto();
        dto.setOrderId(orderId);
        LonLatDto origin = new LonLatDto();
        origin.setLongitude(startLon);
        origin.setLatitude(startLat);
        dto.setOrigin(origin);
        LonLatDto destination = new LonLatDto();
        destination.setLongitude(xpOrder.getStartLon());
        destination.setLatitude(xpOrder.getStartLat());
        dto.setDestination(destination);
        dto.setRouteType(1);
        dto.setVehicleId(xpOrder.getVehicleId());
        log.info("【模拟接单】请求规划接驾路线,origin=[{},{}], destination=[{},{}]", startLon, startLat, xpOrder.getStartLon(), xpOrder.getStartLat());
        R<RouteVo> routeVoR = xpMapClient.getOrderDistanceAndDuration(dto);
        if (200 != routeVoR.getCode()) {
            log.error("【模拟接单】获取订单线路和运费失败,orderId={}, code={}, msg={}", orderId, routeVoR.getCode(), routeVoR.getMsg());
            throw new RuntimeException("获取订单线路和运费失败");
        }
        RouteVo routeVo = routeVoR.getData();
        log.info("【模拟接单】获取接驾路线成功,orderId={}, distance={}, duration={}, polylineSize={}", orderId, routeVo.getDistance(), routeVo.getDuration(), routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        //缓存数据,用于后续推给乘客导航线路
        redisService.setCacheObject("ORDER_NAVIGATION_START_DATA:" + xpOrder.getAppUserId(), JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS);
        log.info("【模拟接单】缓存接驾导航数据成功,key=ORDER_NAVIGATION_START_DATA:{}", xpOrder.getAppUserId());
        PopOrderDto dto1 = new PopOrderDto();
        dto1.setOrderId(Long.valueOf(orderId));
        dto1.setXpVehicleVin(xpVehicle.getVehicleVin());
        dto1.setOrderType(com.xiaopeng.api.vo.OrderType.USER_ORDER);
        log.info("【模拟接单】调用popOrder,orderId={}, vin={}", orderId, xpVehicle.getVehicleVin());
        R<?> r = xpOrderClient.popOrder(dto1);
        log.info("【模拟接单】popOrder结果,orderId={}, code={}", orderId, r.getCode());
        //构建推送消息
        Map<String, Object> params = new HashMap<>();
@@ -549,11 +579,14 @@
        SendMessageDTO dto2 = new SendMessageDTO();
        dto2.setAppUserId(appUserId + "");
        dto2.setMessage(message);
        log.info("【模拟接单】推送订单状态(status=3)给用户,appUserId={}, orderId={}", appUserId, orderId);
        R<String> stringR = xpPushMessageClient.sendStatus(dto2);
        if (R.isError(stringR)) {
            log.error("推送消息失败:{}", stringR.getMsg());
            log.error("【模拟接单】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, orderId, stringR.getMsg());
        }
        log.info("【模拟接单】更新订单状态为PICK_UP,orderId={}", orderId);
        xpOrderClient.updateOrderStatus(orderId, OrderStatusEnum.PICK_UP.getCode(), false);
        log.info("【模拟接单】流程完成,orderId={}", orderId);
//        PushOrderRouteDTO dto3 = new PushOrderRouteDTO();
//        dto3.setOrderId(orderId);
@@ -575,8 +608,10 @@
     */
    @GetMapping("/base/v1/order/pushStartDriverLocation")
    private void pushStartDriverLocation(Long vehicleId, Long orderId, String code){
        log.info("【推送接驾轨迹】开始,vehicleId={}, orderId={}, code={}", vehicleId, orderId, code);
        if(null == vehicleId){
            vehicleId = 1L;
            log.info("【推送接驾轨迹】vehicleId为空,使用默认 vehicleId={}", vehicleId);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        XpOrder xpOrder = null;
@@ -589,31 +624,29 @@
        orderId = xpOrder.getId();
        String vin = xpVehicle.getVehicleVin();
        Long appUserId = xpOrder.getAppUserId();
        log.info("【推送接驾轨迹】订单信息,orderId={}, orderNo={}, vin={}, appUserId={}", orderId, xpOrder.getOrderNumber(), vin, appUserId);
        Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_START_DATA:" + xpOrder.getAppUserId());
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
//        RouteVo routeVo =new RouteVo();
//        ArrayList<LonLatDto> lonLatDtos = new ArrayList<>();
//        LonLatDto lonLatDto1 = new LonLatDto();
//        lonLatDto1.setLatitude(30.575685);
//        lonLatDto1.setLongitude(104.001118);
//        lonLatDtos.add(lonLatDto1);
//        routeVo.setPolyline(lonLatDtos);
        log.info("【推送接驾轨迹】获取接驾导航缓存数据,orderId={}, polylineSize={}", orderId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1000));
        Long finalOrderId = orderId;
        Long finalOrderId1 = orderId;
        threadPoolExecutor.execute(() -> {
            //等待10秒后开始执行车子位移
        DRIVER_LOCATION_EXECUTOR.execute(() -> {
            //等待1秒后开始执行车子位移
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
                Thread.currentThread().interrupt();
                log.error("pushStartDriverLocation 线程被中断", e);
                return;
            }
            List<LonLatDto> polyline = routeVo.getPolyline();
            log.info("【推送接驾轨迹】开始逐点推送位置,orderId={}, 总点数={}", finalOrderId, polyline.size());
            int idx = 0;
            for (LonLatDto lonLatDto : polyline) {
                Double latitude = lonLatDto.getLatitude();
                Double longitude = lonLatDto.getLongitude();
                log.info("【推送接驾轨迹】推送第{}/{}个点,orderId={}, lon={}, lat={}", ++idx, polyline.size(), finalOrderId, longitude, latitude);
                //保存车辆实时位置到redis
                redisTemplate.opsForGeo().add("vehiclePosition:" + vin, new Point(longitude, latitude), vin);
@@ -631,6 +664,8 @@
                    vehiclePosition.setMileageRange(300f);
                    vehiclePosition.setHasOrder(0);
                    vehiclePosition.setOrderId(finalOrderId1);
                    vehiclePosition.setTotalOdometer(10000L);
                    vehiclePosition.setAddTime(System.currentTimeMillis());
                    mongoTemplate.save(vehiclePosition);
                }else{
                    vehiclePosition.setOrderId(finalOrderId1);
@@ -642,7 +677,9 @@
                            .set("currentElectricity", 80)
                            .set("mileageRange", 300f)
                            .set("hasOrder", 0)
                            .set("orderId", finalOrderId1);
                            .set("orderId", finalOrderId1)
                            .set("totalOdometer", 10000L)
                            .set("addTime", System.currentTimeMillis());;
                    mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(vin)), update, VehiclePosition.class);
                }
                //调用推送服务接口
@@ -663,10 +700,13 @@
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                    Thread.currentThread().interrupt();
                    log.error("pushStartDriverLocation 循环线程被中断", e);
                    return;
                }
            }
            log.info("【推送接驾轨迹】所有点推送完毕,推送status=4给用户,orderId={}, appUserId={}", finalOrderId, appUserId);
            //构建推送消息
            Map<String, Object> params = new HashMap<>();
            Map<String, Object> content = new HashMap<>();
@@ -681,9 +721,11 @@
            dto2.setMessage(message);
            R<String> stringR = xpPushMessageClient.sendStatus(dto2);
            if (R.isError(stringR)) {
                log.error("推送消息失败:{}", stringR.getMsg());
                log.error("【推送接驾轨迹】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, finalOrderId, stringR.getMsg());
            }
            log.info("【推送接驾轨迹】更新订单状态为PICK_UP_GUESTS,orderId={}", finalOrderId);
            xpOrderClient.updateOrderStatus(finalOrderId, OrderStatusEnum.PICK_UP_GUESTS.getCode(), false);
            log.info("【推送接驾轨迹】流程完成,orderId={}", finalOrderId);
        });
    }
@@ -697,6 +739,7 @@
     */
    @GetMapping("/base/v1/order/updateOrderStatus")
    public R updateOrderStatus(Long orderId, String code, Integer status) {
        log.info("【模拟修改订单状态】开始,orderId={}, code={}, status={}", orderId, code, status);
        XpOrder xpOrder = null;
        if(null != orderId){
            xpOrder = xpOrderClient.queryOrderById(orderId).getData();
@@ -705,6 +748,7 @@
            xpOrder = xpOrderClient.queryOrderByOrderNo(code).getData();
        }
        orderId = xpOrder.getId();
        log.info("【模拟修改订单状态】订单信息,orderId={}, orderNo={}, appUserId={}", orderId, xpOrder.getOrderNumber(), xpOrder.getAppUserId());
        //构建推送消息
        Map<String, Object> params = new HashMap<>();
        Map<String, Object> content = new HashMap<>();
@@ -717,15 +761,19 @@
        SendMessageDTO dto2 = new SendMessageDTO();
        dto2.setAppUserId(xpOrder.getAppUserId() + "");
        dto2.setMessage(message);
        log.info("【模拟修改订单状态】推送状态消息给用户,appUserId={}, orderId={}, status={}", xpOrder.getAppUserId(), orderId, status);
        R<String> stringR = xpPushMessageClient.sendStatus(dto2);
        if (R.isError(stringR)) {
            log.error("推送消息失败:{}", stringR.getMsg());
            log.error("【模拟修改订单状态】推送消息失败,appUserId={}, orderId={}, msg={}", xpOrder.getAppUserId(), orderId, stringR.getMsg());
        }
        log.info("【模拟修改订单状态】更新订单状态,orderId={}, status={}", orderId, status);
        xpOrderClient.updateOrderStatus(orderId, status, false);
        if(5 == status){
            log.info("【模拟修改订单状态】status=5,主动推送全程轨迹,orderId={}", orderId);
            //主动推一次全程轨迹
            Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId());
            RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
            log.info("【模拟修改订单状态】获取送驾导航缓存,orderId={}, polylineSize={}", orderId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("type", "ORDER_ROUTE");
            jsonObject.put("timestamp", System.currentTimeMillis());
@@ -750,21 +798,25 @@
            SendMessageDTO messageDTO = new SendMessageDTO();
            messageDTO.setAppUserId(xpOrder.getAppUserId().toString());
            messageDTO.setMessage(jsonObject.toJSONString());
            log.info("【模拟修改订单状态】推送全程轨迹,appUserId={}, orderId={}", xpOrder.getAppUserId(), orderId);
            xpPushMessageClient.sendRoute(messageDTO);
        }
        log.info("【模拟修改订单状态】完成,orderId={}, status={}", orderId, status);
        return R.ok();
    }
    /**
     * 推送司机接驾轨迹和实时位置
     * 推送司机送驾轨迹和实时位置
     * @param orderId
     */
    @GetMapping("/base/v1/order/pushEndDriverLocation")
    private void pushEndDriverLocation(Long vehicleId, Long orderId, String code){
        log.info("【推送送驾轨迹】开始,vehicleId={}, orderId={}, code={}", vehicleId, orderId, code);
        if(null == vehicleId){
            vehicleId = 1L;
            log.info("【推送送驾轨迹】vehicleId为空,使用默认 vehicleId={}", vehicleId);
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(vehicleId).getData();
        XpOrder xpOrder = null;
@@ -780,22 +832,27 @@
        Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId());
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
        log.info("开始实施推送车辆位置和剩余导航线路 {} {}", xpOrder.getOrderNumber(), JSON.toJSONString(routeVo));
        log.info("【推送送驾轨迹】获取送驾导航缓存,orderId={}, vin={}, appUserId={}, polylineSize={}", orderId, vin, appUserId, routeVo.getPolyline() != null ? routeVo.getPolyline().size() : 0);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1000));
        Long finalOrderId = orderId;
        Long finalOrderId1 = orderId;
        threadPoolExecutor.execute(() -> {
            //等待10秒后开始执行车子位移
        DRIVER_LOCATION_EXECUTOR.execute(() -> {
            //等待1秒后开始执行车子位移
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
                Thread.currentThread().interrupt();
                log.error("pushEndDriverLocation 线程被中断", e);
                return;
            }
            List<LonLatDto> polyline = routeVo.getPolyline();
            log.info("【推送送驾轨迹】开始逐点推送位置,orderId={}, 总点数={}", finalOrderId, polyline.size());
            int idx = 0;
            for (LonLatDto lonLatDto : polyline) {
                Double latitude = lonLatDto.getLatitude();
                Double longitude = lonLatDto.getLongitude();
                log.info("【推送送驾轨迹】推送第{}/{}个点,orderId={}, lon={}, lat={}", ++idx, polyline.size(), finalOrderId, longitude, latitude);
                //保存车辆实时位置到redis
                redisTemplate.opsForGeo().add("vehiclePosition:" + vin, new Point(longitude, latitude), vin);
@@ -813,6 +870,8 @@
                    vehiclePosition.setMileageRange(300f);
                    vehiclePosition.setHasOrder(0);
                    vehiclePosition.setOrderId(finalOrderId1);
                    vehiclePosition.setTotalOdometer(10000L);
                    vehiclePosition.setAddTime(System.currentTimeMillis());
                    mongoTemplate.save(vehiclePosition);
                }else{
                    vehiclePosition.setOrderId(finalOrderId1);
@@ -824,7 +883,9 @@
                            .set("currentElectricity", 80)
                            .set("mileageRange", 300f)
                            .set("hasOrder", 0)
                            .set("orderId", finalOrderId1);
                            .set("orderId", finalOrderId1)
                            .set("totalOdometer", 10000L)
                            .set("addTime", System.currentTimeMillis());;
                    mongoTemplate.updateFirst(new Query(Criteria.where("vin").is(vin)), update, VehiclePosition.class);
                }
                //调用推送服务接口
@@ -851,14 +912,17 @@
                    xpOrderClient.saveVehicleLocation(orderTrajectory);
                }
                //5秒钟循环一次
                //1秒钟循环一次
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                    Thread.currentThread().interrupt();
                    log.error("pushEndDriverLocation 循环线程被中断", e);
                    return;
                }
            }
            log.info("【推送送驾轨迹】所有点推送完毕,推送status=7给用户,orderId={}, appUserId={}", finalOrderId, appUserId);
            //构建推送消息
            Map<String, Object> params = new HashMap<>();
            Map<String, Object> content = new HashMap<>();
@@ -873,11 +937,12 @@
            dto2.setMessage(message);
            R<String> stringR = xpPushMessageClient.sendStatus(dto2);
            if (R.isError(stringR)) {
                log.error("推送消息失败:{}", stringR.getMsg());
                log.error("【推送送驾轨迹】推送消息失败,appUserId={}, orderId={}, msg={}", appUserId, finalOrderId, stringR.getMsg());
            }
            log.info("【推送送驾轨迹】更新订单状态为SEE_OFF_GUESTS,orderId={}", finalOrderId);
            xpOrderClient.updateOrderStatus(finalOrderId, OrderStatusEnum.SEE_OFF_GUESTS.getCode(), false);
            log.info("【推送送驾轨迹】流程完成,orderId={}", finalOrderId);
        });
    }
}
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -445,6 +445,7 @@
                    dto.setRouteType(1);
                    dto.setLongitude(longitude);
                    dto.setLatitude(latitude);
                    dto.setSpeed(dynamicStatus.getSpeed());
                    if (rtTripInfo.hasCurrentTask() && rtTripInfo.getCurrentTask().hasNaviProgressInfo()) {
                        dto.setDuration(rtTripInfo.getCurrentTask().getNaviProgressInfo().getRouteRemainTime());
                        dto.setDistance(rtTripInfo.getCurrentTask().getNaviProgressInfo().getRouteRemainDist());
@@ -661,7 +662,7 @@
        LonLatDto origin = new LonLatDto();
        RouteVo routeVo = null;
        String key = "";
        Float speed = 0f;
        //添加redis锁,用于在发生偏航后重新规划线路与正常推送剩余导航数据之间的冲突
        RLock lock = redissonClient.getLock("PUSH_ORDER_ROUTE_LOCK:" + orderId);
@@ -700,6 +701,13 @@
            } else if(orderId.contains("TEST")){
                R<XpTestOrder> cruiseOrder = testOrderClient.queryOrderById(Long.parseLong(orderId.substring(4)));
                XpTestOrder testOrder = cruiseOrder.getData();
                XpVehicle xpVehicle = xpVehicleClient.queryById(testOrder.getVehicleId()).getData();
                VehiclePosition vehiclePosition = mongoTemplate.findOne(Query.query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
                if(null != vehiclePosition){
                    speed = vehiclePosition.getSpeed();
                }
                //区分测试单和测试巡游单
                if(1 == testOrder.getOrderType()){
                    orderType = OrderType.TEST_ORDER;
@@ -778,6 +786,10 @@
                }
                XpOrder xpOrder = xpOrderR.getData();
                XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
                VehiclePosition vehiclePosition = mongoTemplate.findOne(Query.query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
                if(null != vehiclePosition){
                    speed = vehiclePosition.getSpeed();
                }
                //驶往上车点的行程
                if (3 == xpOrder.getStatus()) {
                    //查询司机当前位置到起点的线路
@@ -833,6 +845,7 @@
                    dto.setRouteType(1);
                    dto.setLongitude(longitude);
                    dto.setLatitude(latitude);
                    dto.setSpeed(speed);
                    if (null != routeVo) {
                        dto.setDuration(null == routeVo.getDuration() ? 0 : routeVo.getDuration().doubleValue());
                        dto.setDistance(null == routeVo.getDistance() ? 0 : routeVo.getDistance().doubleValue());
xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/web/domain/AjaxResult.java
@@ -174,6 +174,20 @@
        return new AjaxResult(code, msg, null);
    }
    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg 返回内容
     * @return 错误消息
     */
    public static AjaxResult error(int code, String msg, Object data)
    {
        return new AjaxResult(code, msg, data);
    }
    /**
     * 是否为成功消息
     *
xp-robotaxi-common/xp-common-security/src/main/java/com/xiaopeng/common/security/handler/GlobalExceptionHandler.java
@@ -1,5 +1,7 @@
package com.xiaopeng.common.security.handler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaopeng.common.core.constant.HttpStatus;
@@ -13,6 +15,7 @@
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import feign.FeignException;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +30,7 @@
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
/**
 * 全局异常处理器
@@ -41,6 +45,15 @@
    private String maxFileSize;
    @Value("${spring.servlet.multipart.max-request-size:100MB}")
    private String maxRequestSize;
    @Value("${xiaopeng.xtiger.sso.issuer}")
    private String issuer;
    @Value("${xiaopeng.xtiger.sso.appId}")
    private String appId;
    @Value("${xiaopeng.xtiger.sso.serverHost}")
    private String serverHost;
    @Autowired
    private MessageSource messageSource;
@@ -76,7 +89,19 @@
    public AjaxResult notLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',登录校验失败'{}'", requestURI, e.getMessage());
        return AjaxResult.error(HttpStatus.UNAUTHORIZED, e.getMessage());
        String s = UUID.randomUUID().toString();
        String state1 = s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24);
        String s1 = UUID.randomUUID().toString();
        String state2 = s1.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24);
        String nonce = RandomStringUtils.randomAlphanumeric(6);
        JSONObject jsonObject = JSON.parseObject("{\n" +
                "    \"uid\": null,\n" +
                "    \"username\": null,\n" +
                "    \"authUrl\": \"" + issuer + "/oidc/auth?scope=openid+profile+offline_access+phone+email&response_type=code&redirect_uri=" + serverHost + "/ssoAuthSdk/callback&state=" + state1 + "&nonce=" + nonce + "&client_id=" + appId + "\",\n" +
                "    \"logoutUrl\": null,\n" +
                "    \"callbackUrl\": \"" + serverHost + "/ssoAuthSdk/callback?state=" + state2 + "\"\n" +
                "  }");
        return AjaxResult.error(HttpStatus.UNAUTHORIZED, e.getMessage(), jsonObject);
    }
    /**
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java
@@ -171,14 +171,14 @@
            throw new ServiceException("获取行驶中的车辆信息错误");
        }
        //剩余里程xx米,或者 剩余时间xx分钟 禁止修改目的地
        //获取订单的
        //获取订单的 状态 0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中,7=待客下车,8=待付款,9=待评价,10=已完成
        if(order.getStatus() == 6){
            Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(uid));
            if(cacheObject != null){
                RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class);
                Integer xczSyDistance = routeRemain.getDistance();//行程中剩余里程
                Integer xczSyTime = routeRemain.getDuration();//行程中剩余时间
                if(systemSetting.getXczSyDistance()<xczSyDistance || systemSetting.getXczSyTime() < xczSyTime){
                if(systemSetting.getXczSyDistance() > xczSyDistance || systemSetting.getXczSyTime() * 60 > xczSyTime){
                    throw new ServiceException("即将达到终点,禁止修改目的地");
                }
            }
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -15,6 +15,7 @@
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.map.RouteStaleCheckDto;
import com.xiaopeng.api.dto.map.SelectNavigationRouteDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.enums.RouteStrategyKind;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.account.UserRoutePlanRuleClient;
@@ -1141,19 +1142,33 @@
        List<RouteStrategyKind> kinds = planningConfig.getKinds();
        Map<String, RouteVo> routeById = new LinkedHashMap<>();
        Map<String, Set<RouteStrategyKind>> strategiesById = new LinkedHashMap<>();
        for (RouteStrategyKind kind : kinds) {
        Set<List<LonLatDto>> routePoints = new HashSet<>();
        aa: for (RouteStrategyKind kind : kinds) {
            RouteAndFareDto copy = new RouteAndFareDto();
            copy.setOrigin(routeAndFareDto.getOrigin());
            copy.setDestination(routeAndFareDto.getDestination());
            copy.setRoutePlanRule(kind.getAmapStrategy());
            R<RouteVo> r = xpMapClient.getDistanceAndDuration(copy);
            if (r.getCode() != 200 || r.getData() == null || r.getData().getRouteID() == null) {
            R<List<RouteVo>> r = xpMapClient.getDistanceAndDurationList(copy);
            if (r.getCode() != 200 || r.getData() == null || r.getData().isEmpty()) {
                continue;
            }
            String fid = r.getData().getRouteID();
            routeById.putIfAbsent(fid, r.getData());
            strategiesById.computeIfAbsent(fid, k -> new LinkedHashSet<>()).add(kind);
            bb: for (RouteVo datum : r.getData()) {
                for (List<LonLatDto> routePoint : routePoints) {
                    if(CollUtil.isEqualList(routePoint, datum.getPolyline())){
                        continue bb;
                    }
                }
                routePoints.add(datum.getPolyline());
                String fid = datum.getRouteID();
                routeById.putIfAbsent(fid, datum);
                strategiesById.computeIfAbsent(fid, k -> new LinkedHashSet<>()).add(kind);
                continue aa;
            }
        }
        if (routeById.isEmpty()) {
            throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND);
        }
@@ -1197,15 +1212,6 @@
            opt.setOperateFree(operateFree);
            options.add(opt);
        }
        // 根据 pathFingerprint 去重,保留第一次出现的路线
//        options = new ArrayList<>(options.stream()
//                .collect(Collectors.toMap(
//                        RouteOptionVo::getPathFingerprint,
//                        option -> option,
//                        (existing, replacement) -> existing,
//                        LinkedHashMap::new
//                )).values());
        RouteStrategyKind userPref = resolveUserPreferredKind(userId).orElse(RouteStrategyKind.SMART);
        options.sort((a, b) -> compareRouteOptionByUserPreference(a, b, userPref, strategiesById));
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -73,6 +73,71 @@
    }
    @PostMapping("/base/v1/map/getDistanceAndDurationList")
    public R<List<RouteVo>> getDistanceAndDurationList(@RequestBody RouteAndFareDto dto){
        // 获取电子围栏的禁行区域点位
        GeoPoint startPoint = new GeoPoint(dto.getOrigin().getLongitude(),dto.getOrigin().getLatitude());
        List<String> operatingArea = xpGeoFencingPolygonService.findFencesContainingPoint(startPoint, "operating_area", "points");
        String fenceId = operatingArea.get(0);
        List<Document> pointsInFence = xpGeoFencingPolygonService.findPointsByParentId(fenceId, "fenceId", "prohibited_area");
        // 避让区域
        ArrayList<JSONObject> pointStrParam = new ArrayList<>();
        try {
            for (Document document : pointsInFence) {
                // 获取points字段(它是GeoJSON对象)
                Document geoJsonDoc = document.get("points", Document.class);
                if (geoJsonDoc != null && "Polygon".equals(geoJsonDoc.getString("type"))) {
                    // 获取coordinates数组
                    List<List<List<Double>>> coordinates = (List<List<List<Double>>>) geoJsonDoc.get("coordinates");
                    if (coordinates != null && !coordinates.isEmpty()) {
                        // 获取第一个多边形环(通常只有一个)
                        List<List<Double>> polygonPoints = coordinates.get(0);
                        List<String> coordinatesList = new ArrayList<>();
                        // 转换为经纬度字符串
                        for (List<Double> point : polygonPoints) {
                            if (point.size() >= 2) {
                                coordinatesList.add(point.get(0) + "," + point.get(1));
                            }
                        }
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("coordinates", coordinatesList);
                        pointStrParam.add(jsonObject);
                    }
                }
            }
            List<HistoryPointDto> history = new ArrayList<>();
            if(null != dto.getVehicleId()){
//                history = redisService.getCacheList("VEHICLE_HISTORY_POINT_LIST:" + dto.getVehicleId());
//                if(history.size() > 10){
//                    history = history.subList(history.size() - 10, history.size());
//                }
//                history.forEach(s->{
//                    s.setDirection(new BigDecimal(s.getDirection()).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
//                });
//                redisService.setCacheList("VEHICLE_HISTORY_POINT_LIST:" + dto.getVehicleId(), history);
            }
            List<RouteVo> routeVo = gdMapGeocodingUtil.getDistanceAndTimeList(dto.getOrigin(), dto.getDestination(), pointStrParam, fenceId,dto.getRoutePlanRule(), history);
            if(routeVo==null || routeVo.isEmpty()){
                return R.fail("没有找到路线");
            }
            return R.ok(routeVo);
        }catch (Exception e){
            e.printStackTrace();
        }
        return R.fail("没有找到路线");
    }
    @PostMapping("/base/v1/map/getDistanceAndDuration")
    public R<RouteVo> getDistanceAndDuration(@RequestBody RouteAndFareDto dto){
        // 获取电子围栏的禁行区域点位
@@ -206,6 +271,7 @@
    }
    @Value("${map.urls.weather:https://restapi.amap.com/v3/weather/weatherInfo?key=%s&city=%s}")
    String WEATHER_URL;
@@ -235,4 +301,5 @@
        return R.fail("获取天气信息失败");
    }
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java
@@ -3,6 +3,7 @@
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -316,7 +317,13 @@
    @GetMapping("/queryById")
    @ApiOperation(value = "点位详情")
    public R<XpPoint> queryById(@RequestParam Long id) {
        return R.ok(xpPointService.getById(id));
        PointListQuery query = new PointListQuery();
        query.setId(id);
        List<XpPoint> pointList = xpPointService.findList(query);
        if(CollUtil.isNotEmpty(pointList)){
            return R.ok(pointList.get(0));
        }
        return R.ok(new XpPoint());
    }
    /**
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpStationController.java
@@ -1,12 +1,14 @@
package com.xiaopeng.map.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaopeng.api.model.operation.XpStation;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.map.service.XpStationService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
 * 场站
@@ -30,4 +32,17 @@
        return R.ok(xpStation);
    }
    /**
     * 根据围栏id获取场站
     * @param electronicId
     * @return
     */
    @PostMapping("/logan/inner/v1/xpStation/getXpStationByElectronicId")
    public R<List<XpStation>> getXpStationByElectronicId(@RequestParam("electronicId") Long electronicId){
        List<XpStation> xpStationList = xpStationService.list(new QueryWrapper<XpStation>().eq("electronic_id", electronicId));
        return R.ok(xpStationList);
    }
}
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java
@@ -154,6 +154,84 @@
    }
    /**
     * 根据起始点获取驾车距离 和 时间  以及避让区域
     *
     * @param start         起点
     * @param end           终点
     * @param avoidRoads    避让点
     * @param fenceId       mango id
     * @param routePlanRule 线路规划规则
     * @return
     * @throws Exception
     */
    public List<RouteVo> getDistanceAndTimeList(LonLatDto start, LonLatDto end,ArrayList<JSONObject> avoidRoads,String fenceId,Integer routePlanRule, List<HistoryPointDto> history)  {
        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);
        List<RouteVo> list = new ArrayList<>();
        for (Object o : path) {
            RouteVo routeVo = new RouteVo();
            JSONObject jsonObject2 = (JSONObject) o;
            String routeID = jsonObject2.getString("routeID");
            String points = jsonObject2.getString("points");
            String[] split = points.split(";");
            List<LonLatDto> pointsArray = new ArrayList<>();
            for (int i = 0; i < split.length; i++) {
                if((i % 2) == 0){
                    String[] split1 = split[i].split(",");
                    LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                    pointsArray.add(lonLatDto);
                }
            }
            ForkJoinPool pool = new ForkJoinPool(); // 创建ForkJoinPool对象(默认使用处理器的核心数作为线程池大小)
            ArrayPointsTask arrayPointsTask = new ArrayPointsTask(pointsArray, fenceId); // 创建求和任务对象并传入数组作为参数(这里也可以传入数组的一部分作为参数来实现更细粒度的拆分)
            Long temp = pool.invoke(arrayPointsTask); // 提交任务并等待处理完成(也可以使用submit方法提交任务并获取一个Future对象来异步获取结果)
            pool.shutdown(); // 关闭ForkJoinPool(释放资源)
            if (temp == 0) {
                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);
                list.add(routeVo);
            }
        }
        return list;
    }
    public static void main(String[] args) {
        String url = "https://tsapi.amap.com/v1/route/price?key=" + "66ae577f63898dae3016d599fda57221" + "&extensions=all&start=" + "113.384932" + "," + "23.155455";
        HashMap<String, Object> params = new HashMap<>();
xp-robotaxi-map-boot/src/main/resources/mapper/XpPointMapper.xml
@@ -100,6 +100,9 @@
        left join xp_station xs on xs.id = t1.station_id
        where t1.is_deleted = 0
        <if test="query.id != null ">
            and t1.id = #{query.id}
        </if>
        <if test="query.electronicAreaIds != null and query.electronicAreaIds.size() > 0">
            and t1.electronic_area_id in
            <foreach collection="query.electronicAreaIds" item="id" open="(" separator="," close=")">
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpElectronicFenceController.java
@@ -2,13 +2,13 @@
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xiaopeng.api.dto.operation.ElectronicFenceAddDto;
import com.xiaopeng.api.dto.operation.ElectronicFenceAreaUpdateStatusDto;
import com.xiaopeng.api.dto.operation.ElectronicFenceUpdateDto;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpRegion;
import com.xiaopeng.api.feignClient.map.XpStationClient;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.query.operation.ElectronicPageListQuery;
import com.xiaopeng.api.vo.operation.ElectronicFenceDetailVo;
import com.xiaopeng.common.core.domain.R;
@@ -16,9 +16,7 @@
import com.xiaopeng.common.core.utils.bean.BeanUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.constant.I18nKeyConstant;
import com.xiaopeng.operation.service.IXpElectronicFenceAreaService;
import com.xiaopeng.operation.service.IXpElectronicFenceService;
import com.xiaopeng.operation.service.XpRegionService;
import com.xiaopeng.operation.service.*;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
@@ -31,6 +29,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
@@ -55,6 +54,15 @@
    @Resource
    private XpRegionService regionService;
    @Resource
    private XpPointService xpPointService;
    @Resource
    private XpStationClient stationClient;
    /**
     * 查询省市管理
     */
@@ -127,6 +135,11 @@
        List<XpElectronicFenceArea> electronicFenceAreaList = xpElectronicFenceAreaService.lambdaQuery().eq(XpElectronicFenceArea::getElectronicId, id)
                .list();
        res.setElectronicFenceAreas(electronicFenceAreaList);
        List<Long> longList = electronicFenceAreaList.stream().map(XpElectronicFenceArea::getId).collect(Collectors.toList());
        List<XpPoint> xpPointList = xpPointService.list(new QueryWrapper<XpPoint>().in("electronic_area_id", longList));
        res.setXpPointList(xpPointList);
        List<XpStation> data = stationClient.getXpStationByElectronicId(id).getData();
        res.setXpStationList(data);
        return R.ok(res);
    }
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/job/base/AbstractJob.java
@@ -3,10 +3,7 @@
import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.operation.job.utils.SpringContextsUtil;
import com.xiaopeng.operation.service.IXpElectronicFenceAreaService;
import com.xiaopeng.operation.service.IXpElectronicFenceService;
import com.xiaopeng.operation.service.IXpPointService;
import com.xiaopeng.operation.service.XpSystemSettingService;
import com.xiaopeng.operation.service.*;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
@@ -22,7 +19,7 @@
    protected IXpElectronicFenceService xpElectronicFenceService;
    protected IXpElectronicFenceAreaService xpElectronicFenceAreaService;
    protected IXpPointService xpPointService;
    protected XpPointService xpPointService;
    protected XpPushMessageClient xpPushMessageClient;
    protected XpSystemSettingService xpSystemSettingService;
    protected RedisService redisService;
@@ -30,7 +27,7 @@
    public AbstractJob(){
        this.xpElectronicFenceService = SpringContextsUtil.getBean(IXpElectronicFenceService.class);
        this.xpElectronicFenceAreaService = SpringContextsUtil.getBean(IXpElectronicFenceAreaService.class);
        this.xpPointService = SpringContextsUtil.getBean(IXpPointService.class);
        this.xpPointService = SpringContextsUtil.getBean(XpPointService.class);
        this.xpPushMessageClient = SpringContextsUtil.getBean(XpPushMessageClient.class);
        this.xpSystemSettingService = SpringContextsUtil.getBean(XpSystemSettingService.class);
        this.redisService = SpringContextsUtil.getBean(RedisService.class);
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/IXpPointService.java
File was deleted
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -189,6 +189,7 @@
                nearestNextVo.setTime(xpPoint.getParkingTimeLimit());
                nearestNextVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
                nearestNextVo.setStationId(xpPoint.getStationId()==null?null:xpPoint.getStationId().toString());
                nearestNextVo.setIsCollect(2);
                // 判断是否收藏
                if(pointIdsByUserId.getData()!=null && !pointIdsByUserId.getData().isEmpty()){
@@ -200,11 +201,20 @@
                nearestNextVos.add(nearestNextVo);
            }
        }
        List<NearestNextVo> objects = new ArrayList<NearestNextVo>();
        if (!nearestNextVos.isEmpty()) {
            nearestNextVos = nearestNextVos.stream().sorted(Comparator.comparing(NearestNextVo::getDistance)).collect(Collectors.toList());
            //收藏的数据
            List<NearestNextVo> collect = nearestNextVos.stream().filter(s -> s.getIsCollect() == 1)
                    .sorted(Comparator.comparing(NearestNextVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect);
            //非收藏的数据
            List<NearestNextVo> collect1 = nearestNextVos.stream().filter(s -> s.getIsCollect() == 2)
                    .sorted(Comparator.comparing(NearestNextVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect1);
        }
        nearestVo.setPoints(nearestNextVos);
        nearestVo.setTotal(nearestNextVos.size());
        nearestVo.setPoints(objects);
        nearestVo.setTotal(objects.size());
        return nearestVo;
    }
@@ -323,6 +333,9 @@
    @Override
    public List<XpHistoryStartPointsVo> getHistoryStartPoints(HistoryStartPointsDto historyStartPointsDto) {
        List<XpPoint> xpPoints = this.baseMapper.selectBatchIds(historyStartPointsDto.getPointIds());
        if(xpPoints.isEmpty()){
            return new ArrayList<>();
        }
        ArrayList<XpHistoryStartPointsVo> resultList = new ArrayList<>(10);
        LocalTime now = LocalTime.now();
@@ -404,6 +417,9 @@
    @Override
    public List<XpHistoryEndPointsVo> getHistoryEndPoints(HistoryEndPointsDto historyEndPointsDto) {
        List<XpPoint> xpPoints = this.baseMapper.selectBatchIds(historyEndPointsDto.getPointIds());
        if(xpPoints.isEmpty()){
            return new ArrayList<XpHistoryEndPointsVo>();
        }
        ArrayList<XpHistoryEndPointsVo> resultList = new ArrayList<>(10);
        LocalTime now = LocalTime.now();
@@ -747,6 +763,12 @@
            orderDistanceVo.setAdsorptionDistance(xpSystemSetting.getAdsorptionDistance());
            orderDistanceVo.setNearPointDistance(xpSystemSetting.getNearPointDistance());
            orderDistanceVo.setTemperatureUnit(xpSystemSetting.getTemperatureUnit());
            orderDistanceVo.setXczSyDistance(xpSystemSetting.getXczSyDistance());
            orderDistanceVo.setXczSyTime(xpSystemSetting.getXczSyTime());
            orderDistanceVo.setXczCaCount(xpSystemSetting.getXczCaCount());
            orderDistanceVo.setXczHmSpeedPerHour(xpSystemSetting.getXczHmSpeedPerHour());
            orderDistanceVo.setXczHmDuration(xpSystemSetting.getXczHmDuration());
            return orderDistanceVo;
        }
        return orderDistanceVo;
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java
@@ -57,6 +57,7 @@
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
@@ -406,6 +407,8 @@
                                        PushOrderDto pushOrderDto = new PushOrderDto();
                                        pushOrderDto.setVin(priorityVin);
                                        pushOrderDto.setOrderId(orderCheck.getData().getId().toString());
                                        pushOrderDto.setStartWaypointId(orderCheck.getData().getStartPointId());
                                        pushOrderDto.setEndWaypointId(orderCheck.getData().getEndPointId());
                                        pushOrderDto.setStartLatitude(orderCheck.getData().getStartLat());
                                        pushOrderDto.setStartLongitude(orderCheck.getData().getStartLon());
                                        pushOrderDto.setEndLatitude(orderCheck.getData().getEndLat());
@@ -1091,6 +1094,8 @@
                            PushOrderDto pushOrderDto = new PushOrderDto();
                            pushOrderDto.setVin(vehiclePosition.getVehicleVin());
                            pushOrderDto.setOrderId(xpOrder.getId().toString());
                            pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
                            pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
                            pushOrderDto.setStartLatitude(xpOrder.getStartLat());
                            pushOrderDto.setStartLongitude(xpOrder.getStartLon());
                            pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -1156,6 +1161,8 @@
                            PushOrderDto pushOrderDto = new PushOrderDto();
                            pushOrderDto.setVin(vehiclePosition.getVin());
                            pushOrderDto.setOrderId(xpOrder.getId().toString());
                            pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
                            pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
                            pushOrderDto.setStartLatitude(xpOrder.getStartLat());
                            pushOrderDto.setStartLongitude(xpOrder.getStartLon());
                            pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -1188,79 +1195,76 @@
            if (servingVehicles.getCode() != 200) {
                continue;
            }
            LonLatDto userPostion = new LonLatDto(Double.parseDouble(split[0]), Double.parseDouble(split[1]));
            log.info("连环派单查询车辆结果 {} {}", orderNo, JSON.toJSONString(servingVehicles.getData()));
            XpTradingStrategyDetailVO strategyDetailVO = tradingStrategyClient.queryById().getData();
            log.info("连环排查推单逻辑 {} {}", orderNo, JSON.toJSONString(strategyDetailVO));
            Integer distanceRegardAsEnding = strategyDetailVO.getDistanceRegardAsEnding();
            Integer timeRegardAsEnding = strategyDetailVO.getTimeRegardAsEnding();
            Integer backupPower = strategyDetailVO.getBackupPower();
            Integer destToPassenger = strategyDetailVO.getDestToPassenger();
            Integer endingTimeToUseTime = strategyDetailVO.getEndingTimeToUseTime();
            LocalDateTime rideTime = xpOrder.getRideTime();
            LocalDateTime now = LocalDateTime.now();
            Optional<XpOrder> luckyOrderOpt = servingVehicles.getData().stream()
                    //获取位置
                    .map(vin -> mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class))
                    .map(VehiclePosition::getOrderId)
                    .map(xpOrderClient::queryOrderById)
                    .filter(R::isSuccess)
                    .map(R::getData)
                    .filter(o -> {
                        XpVehicle xpVehicle = xpVehicleClient.queryById(o.getVehicleId()).getData();
                        Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(o.getAppUserId().toString()));
                        RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class);
                        if (routeRemain.getDuration() / 60 > timeRegardAsEnding) {
                            return false;
                        }
                        if (routeRemain.getDistance() > distanceRegardAsEnding) {
                            return false;
                        }
                        RouteAndFareDto next = new RouteAndFareDto();
                        next.setOrigin(new LonLatDto(o.getStartLon(),o.getStartLat()));
                        next.setDestination(new LonLatDto(o.getEndLon(),o.getEndLat()));
                        RouteVo route = xpMapClient.getDistanceAndDuration(next).getData();
                        if (route.getDistance() > backupPower + xpVehicle.getMileageRange())
                            return false;
                        return routeRemain.getDistance() < destToPassenger
                                || now.plusSeconds(routeRemain.getDuration())
                                .plusMinutes(endingTimeToUseTime)
                                .isBefore(rideTime);
                    })
                    .findAny();
            if (luckyOrderOpt.isPresent()) {
                XpOrder luckyOder = luckyOrderOpt.get();
                log.info("有空闲车辆 {} 订单 {}", luckyOder.getOrderNumber(), orderNo);
                XpVehicle xpVehicle = xpVehicleClient.queryById(luckyOder.getVehicleId()).getData();
                // 设置路线信息
                RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
                routeAndFareDto.setDestination(userPostion);
                routeAndFareDto.setOrigin(new LonLatDto(luckyOder.getEndLon(),luckyOder.getEndLat()));
                RouteVo routeToOrigin = xpMapClient.getDistanceAndDuration(routeAndFareDto).getData();
                Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(luckyOder.getAppUserId().toString()));
            for (String vin : servingVehicles.getData()) {
                VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
                if(null == vehiclePosition){
                    log.info("连环派单 {} 车辆查询定位数据失败 {}", vin, orderNo);
                    continue;
                }
                XpOrder xpOrder1 = xpOrderClient.queryOrderById(vehiclePosition.getOrderId()).getData();
                Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(xpOrder1.getAppUserId().toString()));
                RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class);
                routeToOrigin.getPolyline().addAll(0, routeRemain.getPolyline());
                routeToOrigin.setDuration(routeToOrigin.getDuration() + routeRemain.getDuration());
                routeToOrigin.setDistance(routeToOrigin.getDistance() + routeRemain.getDistance());
                if (routeRemain.getDuration() / 60 > timeRegardAsEnding && routeRemain.getDistance() / 1000 > distanceRegardAsEnding) {
                    log.info("连环派单 {} 车辆订单结束时间不满足条件 {} 秒 {} 米 {}", vin, routeRemain.getDuration(), routeRemain.getDistance(), orderNo);
                    continue;
                }
                //此处找到车后先不忙给车推送接单消息,先修改订单上的车辆信息
                xpOrder.setVehicleId(xpVehicle.getId());
                xpOrder.setAcceptanceTime(LocalDateTime.now());
                xpOrder.setStatus(2);
                xpOrderClient.updateOrder(xpOrder);
                //剩余里程》=下一单预估里程+50
                //预估接驾行程+送驾行程
                RouteAndFareDto next = new RouteAndFareDto();
                next.setOrigin(new LonLatDto(xpOrder1.getEndLon(),xpOrder1.getEndLat()));
                next.setDestination(new LonLatDto(xpOrder.getStartLon(),xpOrder.getStartLat()));
                RouteVo route = xpMapClient.getDistanceAndDuration(next).getData();
                Object new_cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(xpOrder.getAppUserId().toString()));
                RouteVo new_routeRemain = JSON.parseObject(new_cacheObject.toString(), RouteVo.class);
                long xh = (new_routeRemain.getDistance() + route.getDistance()) / 1000 + backupPower;
                if(vehiclePosition.getMileageRange() < xh){
                    log.info("连环派单 {} 车辆剩余续航不满足派车条件 {} KM {}", vin, xh, orderNo);
                    continue;
                }
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("orderId", xpOrder.getId());
                jsonObject.put("endLongitude", xpOrder.getEndLat());
                jsonObject.put("endLatitude", xpOrder.getEndLat());
                redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), jsonObject.toJSONString());
                redisService.setCacheObject(CacheConstants.navigationStart(xpOrder.getAppUserId().toString()), routeToOrigin);
                redisService.removeByPattern(quoteName,xpOrder.getOrderNumber());
                log.info("将订单 {} 从队列 {} 弹出去", orderNo, quoteName);
                break;
                //下一单的起点距离本单终点小于X公里,或者下一单的用车时间与本单的预计结束时间偏差小于X分钟
                next = new RouteAndFareDto();
                next.setOrigin(new LonLatDto(xpOrder1.getEndLon(),xpOrder1.getEndLat()));
                next.setDestination(new LonLatDto(xpOrder.getStartLon(),xpOrder.getStartLat()));
                route = xpMapClient.getDistanceAndDuration(next).getData();
                //相差秒时间
                long time = routeRemain.getDuration() - xpOrder.getCreateTime().toEpochSecond(ZoneOffset.of("+8"));
                if(route.getDistance() / 1000 <= destToPassenger || time / 60 <= endingTimeToUseTime){
                    log.info("连环派单 {} 找到满足条件的车辆 {}", vin, orderNo);
                    // 设置接驾导航数据
                    //将上一单的剩余导航添加到接驾导航中
                    route.getPolyline().addAll(0, routeRemain.getPolyline());
                    route.setDuration(route.getDuration() + routeRemain.getDuration());
                    route.setDistance(route.getDistance() + routeRemain.getDistance());
                    //此处找到车后先不忙给车推送接单消息,先修改订单上的车辆信息
                    xpOrder.setVehicleId(xpOrder1.getVehicleId());
                    xpOrder.setAcceptanceTime(LocalDateTime.now());
                    xpOrder.setStatus(2);
                    xpOrderClient.updateOrder(xpOrder);
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("orderId", xpOrder.getId());
                    jsonObject.put("endLongitude", xpOrder.getEndLat());
                    jsonObject.put("endLatitude", xpOrder.getEndLat());
                    redisService.setCacheObject(CacheConstants.nextOrder(xpOrder1.getId().toString()), jsonObject.toJSONString());
                    redisService.setCacheObject(CacheConstants.navigationStart(xpOrder.getAppUserId().toString()), JSON.toJSONString(route));
                    redisService.removeByPattern(quoteName,xpOrder.getOrderNumber());
                    log.info("将订单 {} 从队列 {} 弹出去", orderNo, quoteName);
                    return true;
                }
            }
        }
        return false;
@@ -1349,6 +1353,8 @@
                                        pushOrderDto.setStartTaskId(orderCheck.getData().getXpuStartTaskId());
                                        pushOrderDto.setEndTaskId(orderCheck.getData().getXpuEndTaskId());
                                        pushOrderDto.setOrderId(orderCheck.getData().getId().toString());
                                        pushOrderDto.setStartWaypointId(orderCheck.getData().getStartPointId());
                                        pushOrderDto.setEndWaypointId(orderCheck.getData().getEndPointId());
                                        pushOrderDto.setStartLatitude(orderCheck.getData().getStartLat());
                                        pushOrderDto.setStartLongitude(orderCheck.getData().getStartLon());
                                        pushOrderDto.setEndLatitude(orderCheck.getData().getEndLat());
@@ -2027,6 +2033,8 @@
                        pushOrderDto.setEndTaskId(xpOrder.getXpuEndTaskId());
                        pushOrderDto.setVin(vehiclePosition.getVehicleVin());
                        pushOrderDto.setOrderId(xpOrder.getId().toString());
                        pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
                        pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
                        pushOrderDto.setStartLatitude(xpOrder.getStartLat());
                        pushOrderDto.setStartLongitude(xpOrder.getStartLon());
                        pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -2095,6 +2103,8 @@
                        pushOrderDto.setEndTaskId(xpOrder.getXpuEndTaskId());
                        pushOrderDto.setVin(vehiclePosition.getVin());
                        pushOrderDto.setOrderId(xpOrder.getId().toString());
                        pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
                        pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
                        pushOrderDto.setStartLatitude(xpOrder.getStartLat());
                        pushOrderDto.setStartLongitude(xpOrder.getStartLon());
                        pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -2218,6 +2228,8 @@
    private static PushOrderDto buildPushOrderDto(XpOrder xpOrder) {
        PushOrderDto pushOrderDto = new PushOrderDto();
        pushOrderDto.setOrderId(xpOrder.getId().toString());
        pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
        pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
        pushOrderDto.setStartLatitude(xpOrder.getStartLat());
        pushOrderDto.setStartLongitude(xpOrder.getStartLon());
        pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -2401,6 +2413,8 @@
                pushOrderDto.setEndTaskId(data.getXpuEndTaskId());
                pushOrderDto.setVin(vehiclePosition.getVin());
                pushOrderDto.setOrderId(data.getId().toString());
                pushOrderDto.setStartWaypointId(data.getStartPointId());
                pushOrderDto.setEndWaypointId(data.getEndPointId());
                pushOrderDto.setStartLatitude(data.getStartLat());
                pushOrderDto.setStartLongitude(data.getStartLon());
                pushOrderDto.setEndLatitude(data.getEndLat());
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/vo/OrderDistanceVo.java
@@ -32,4 +32,18 @@
    @ApiModelProperty("下单页停留时长")
    private Integer maxStandingTiming;
    @ApiModelProperty(value = "行程中剩余里程")
    private Integer xczSyDistance;
    @ApiModelProperty(value = "行程中剩余时间")
    private Integer xczSyTime;
    @ApiModelProperty(value = "行程中修改目地次数")
    private Integer xczCaCount;
    @ApiModelProperty(value = "行程中豁免时速 km/h")
    private Integer xczHmSpeedPerHour;
    @ApiModelProperty(value = "行程中豁免持续时间 分钟")
    private Integer xczHmDuration;
}
xp-robotaxi-operation-boot/src/main/resources/bootstrap.yml
@@ -28,6 +28,7 @@
        namespace: 88de3938-82f9-4cab-97cf-6173202703e0
        username: nacos
        password: nacos
        ip: 192.168.110.85
      config:
        # 配置中心地址
        server-addr: 192.168.110.80:8848
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/BluetoothDebugConfig.java
New file
@@ -0,0 +1,50 @@
package com.xiaopeng.order.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
 * 蓝牙开锁联调配置(临时)
 * <p>
 * 通过 Nacos 配置固定的蓝牙 token、macAddress、slaveMacList,用于联调测试。
 * <p>
 * 配置格式示例:
 * <pre>
 * bluetooth-debug:
 *   enabled: false
 *   token: "your-fixed-token"
 *   mac-address: "AA:BB:CC:DD:EE:FF"
 *   slave-mac-list:
 *     - "AA:BB:CC:DD:EE:01"
 *     - "AA:BB:CC:DD:EE:02"
 * </pre>
 */
@Slf4j
@Data
@ConfigurationProperties(prefix = "bluetooth-debug")
public class BluetoothDebugConfig {
    /**
     * 联调开关,默认 false(关闭)
     */
    private boolean enabled = false;
    /**
     * 固定的蓝牙 token
     */
    private String token;
    /**
     * 固定的 MAC 地址
     */
    private String macAddress;
    /**
     * 固定的从设备 MAC 地址列表
     */
    private List<String> slaveMacList;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/config/PropertiesAutoConfiguration.java
New file
@@ -0,0 +1,10 @@
package com.xiaopeng.order.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({BluetoothDebugConfig.class})
public class PropertiesAutoConfiguration {
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/ActiveOrderController.java
@@ -100,6 +100,9 @@
            key = "ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId();
        }
        Object cacheObject = redisService.getCacheObject(key);
        if(cacheObject == null){
            return R.fail("获取路线失败");
        }
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
@@ -162,6 +165,7 @@
            xpOrderDetailVO.setVehicleId(vehicle.getId());
            xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin());
            xpOrderDetailVO.setVehicleModel(vehicle.getVehicleModelName());
            xpOrderDetailVO.setVehicleCode(vehicle.getVehicleCode());
            xpOrderDetailVO.setVehicleColor(vehicle.getVehicleColor());
            xpOrderDetailVO.setPlateNumber(vehicle.getPlateNumber());
            xpOrderDetailVO.setVehiclePictures(vehicle.getVehiclePictures());
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpCruiseOrderController.java
@@ -118,6 +118,8 @@
        pushOrderDto.setEndTaskId(order.getXpuTaskId());
        pushOrderDto.setVin(order.getVehicleVin());
        pushOrderDto.setOrderId("XU" + order.getId());
        pushOrderDto.setStartWaypointId(order.getStartPointId());
        pushOrderDto.setEndWaypointId(order.getEndPointId());
        pushOrderDto.setStartLatitude(order.getStartLat());
        pushOrderDto.setStartLongitude(order.getStartLon());
        pushOrderDto.setEndLatitude(order.getEndLat());
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -39,6 +39,7 @@
import com.xiaopeng.common.log.enums.AdminOperationType;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.common.security.service.TokenService;
import com.xiaopeng.order.config.BluetoothDebugConfig;
import com.xiaopeng.order.dto.OrderNavigationDto;
import com.xiaopeng.api.model.order.OrderTrajectory;
import com.xiaopeng.order.model.dto.XpOrderCommentDto;
@@ -115,6 +116,10 @@
    @Resource
    private TokenService tokenService;
    @Autowired
    private BluetoothDebugConfig bluetoothDebugConfig;
    /**
@@ -740,6 +745,18 @@
    @GetMapping("/api/v1/mini-program/bluetooth/getUnlocktToken/{orderId}")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<BluetoothTokenVo> getBluetoothToken(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // ========== 联调临时逻辑:通过 Nacos 配置返回固定蓝牙信息(bluetooth-debug.enabled=true 时生效) ==========
        if (bluetoothDebugConfig.isEnabled()) {
            BluetoothTokenVo vo = new BluetoothTokenVo();
            vo.setToken(bluetoothDebugConfig.getToken());
            vo.setMacAddress(bluetoothDebugConfig.getMacAddress());
            vo.setSlaveMacList(bluetoothDebugConfig.getSlaveMacList());
            return R.ok(vo);
        }
        // ========== 联调临时逻辑结束 ==========
        // 原逻辑
        String uid = request.getHeader("XP-Uid");
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if (!Objects.equals(xpOrder.getAppUserId(), Long.valueOf(uid))) {
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/PushChainReactionOrderJob.java
@@ -55,6 +55,8 @@
            pushOrderDto.setStartTaskId(xpOrder.getXpuStartTaskId());
            pushOrderDto.setEndTaskId(xpOrder.getXpuEndTaskId());
            pushOrderDto.setOrderId(xpOrder.getId().toString());
            pushOrderDto.setStartWaypointId(xpOrder.getStartPointId());
            pushOrderDto.setEndWaypointId(xpOrder.getEndPointId());
            pushOrderDto.setStartLatitude(xpOrder.getStartLat());
            pushOrderDto.setStartLongitude(xpOrder.getStartLon());
            pushOrderDto.setEndLatitude(xpOrder.getEndLat());
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -697,7 +697,8 @@
                    redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                    XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                    BluetoothDeliverVo vo = new BluetoothDeliverVo();
                    vo.setVim(xpVehicle.getVehicleVin());
                    vo.setCredential(token);
                    vo.setVin(xpVehicle.getVehicleVin());
                    vo.setPassengerUid(order.getAppUserId());
                    BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                    if (null == delete || delete.getCode() != 0) {
@@ -1190,6 +1191,8 @@
            pushOrderDto.setEndTaskId(order.getXpuEndTaskId());
            pushOrderDto.setVin(newVehicle.getVehicleVin());
            pushOrderDto.setOrderId(order.getId().toString());
            pushOrderDto.setStartWaypointId(order.getStartPointId());
            pushOrderDto.setEndWaypointId(order.getEndPointId());
            pushOrderDto.setStartLatitude(order.getStartLat());
            pushOrderDto.setStartLongitude(order.getStartLon());
            pushOrderDto.setEndLatitude(order.getEndLat());
@@ -1325,6 +1328,8 @@
            pushOrderDto.setEndTaskId(returnOrder.getXpuTaskId());
            pushOrderDto.setVin(oldVehicle.getVehicleVin());
            pushOrderDto.setOrderId("XU" + returnOrder.getId());
            pushOrderDto.setStartWaypointId(returnOrder.getStartPointId());
            pushOrderDto.setEndWaypointId(returnOrder.getEndPointId());
            pushOrderDto.setStartLatitude(returnOrder.getStartLat());
            pushOrderDto.setStartLongitude(returnOrder.getStartLon());
            pushOrderDto.setEndLatitude(returnOrder.getEndLat());
@@ -1668,10 +1673,19 @@
                    }
            );
        }
        data = data.stream().sorted(Comparator.comparingInt(XpHistoryStartPointsVo::getNum).reversed()).collect(Collectors.toList());
        return data;
        List<XpHistoryStartPointsVo> objects = new ArrayList<XpHistoryStartPointsVo>();
        if(!data.isEmpty()){
            //收藏的数据
            List<XpHistoryStartPointsVo> collect = data.stream().filter(s -> s.getIsCollect() == 1)
                    .sorted(Comparator.comparing(XpHistoryStartPointsVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect);
            //非收藏的数据
            List<XpHistoryStartPointsVo> collect1 = data.stream().filter(s -> s.getIsCollect() == 2)
                    .sorted(Comparator.comparing(XpHistoryStartPointsVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect1);
        }
        return objects;
    }
    @Override
@@ -1699,9 +1713,18 @@
                    }
            );
        }
        data = data.stream().sorted(Comparator.comparingInt(XpHistoryEndPointsVo::getNum).reversed()).collect(Collectors.toList());
        return data;
        List<XpHistoryEndPointsVo> objects = new ArrayList<XpHistoryEndPointsVo>();
        if(!data.isEmpty()){
            //收藏的数据
            List<XpHistoryEndPointsVo> collect = data.stream().filter(s -> s.getIsCollect() == 1)
                    .sorted(Comparator.comparing(XpHistoryEndPointsVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect);
            //非收藏的数据
            List<XpHistoryEndPointsVo> collect1 = data.stream().filter(s -> s.getIsCollect() == 2)
                    .sorted(Comparator.comparing(XpHistoryEndPointsVo::getDistance)).collect(Collectors.toList());
            objects.addAll(collect1);
        }
        return objects;
    }
@@ -1877,7 +1900,8 @@
                redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setVim(xpVehicle.getVehicleVin());
                vo.setCredential(token);
                vo.setVin(xpVehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
                BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                if (null == delete || delete.getCode() != 0) {
@@ -1912,7 +1936,8 @@
                redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
                XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setVim(xpVehicle.getVehicleVin());
                vo.setCredential(token);
                vo.setVin(xpVehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
                BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
                if (null == delete || delete.getCode() != 0) {
@@ -2320,19 +2345,20 @@
        order.setAppUserId(appUserId);
        bootCommonService.setCache_orderStatus(order.getId(), order, null);
        //删除蓝牙秘钥
//        String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId);
//        if(StringUtils.isNotBlank(token)){
//            redisService.deleteObject("rt:bluetooth:unlock:" + token);
//            redisService.deleteObject("rt:bluetooth:unlock:order:" + orderId);
//            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
//            BluetoothDeliverVo vo = new BluetoothDeliverVo();
//            vo.setVim(xpVehicle.getVehicleVin());
//            vo.setPassengerUid(order.getAppUserId());
//            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
//            if(null == delete || delete.getCode() != 0){
//                log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber());
//            }
//        }
        String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId);
        if(StringUtils.isNotBlank(token)){
            redisService.deleteObject("rt:bluetooth:unlock:" + token);
            redisService.deleteObject("rt:bluetooth:unlock:order:" + orderId);
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
            BluetoothDeliverVo vo = new BluetoothDeliverVo();
            vo.setCredential(token);
            vo.setVin(xpVehicle.getVehicleVin());
            vo.setPassengerUid(order.getAppUserId());
            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
            if(null == delete || delete.getCode() != 0){
                log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber());
            }
        }
        // 根据订单所属开通城市id 查询定价
        XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(), order.getVehicleId());
@@ -2892,6 +2918,8 @@
        pushOrderDto.setEndTaskId(xpCruiseOrder.getXpuTaskId());
        pushOrderDto.setVin(xpCruiseOrder.getVehicleVin());
        pushOrderDto.setOrderId("XU" + xpCruiseOrder.getId());
        pushOrderDto.setStartWaypointId(xpCruiseOrder.getStartPointId());
        pushOrderDto.setEndWaypointId(xpCruiseOrder.getEndPointId());
        pushOrderDto.setStartLatitude(xpCruiseOrder.getStartLat());
        pushOrderDto.setStartLongitude(xpCruiseOrder.getStartLon());
        pushOrderDto.setEndLatitude(xpCruiseOrder.getEndLat());
@@ -3012,6 +3040,8 @@
        pushOrderDto.setEndTaskId(xpCruiseOrder.getXpuTaskId());
        pushOrderDto.setVin(xpCruiseOrder.getVehicleVin());
        pushOrderDto.setOrderId("XU" + xpCruiseOrder.getId());
        pushOrderDto.setStartWaypointId(xpCruiseOrder.getStartPointId());
        pushOrderDto.setEndWaypointId(xpCruiseOrder.getEndPointId());
        pushOrderDto.setStartLatitude(xpCruiseOrder.getStartLat());
        pushOrderDto.setStartLongitude(xpCruiseOrder.getStartLon());
        pushOrderDto.setEndLatitude(xpCruiseOrder.getEndLat());
@@ -3711,17 +3741,17 @@
                //开始下发蓝牙token
                BluetoothDeliverVo vo = new BluetoothDeliverVo();
                vo.setCredential(token);
                vo.setVim(vehicle.getVehicleVin());
                vo.setVin(vehicle.getVehicleVin());
                vo.setPassengerUid(order.getAppUserId());
//                BaseVehicleUserDto deliver = bluetoothUnlockClient.deliver(vo);
//                if(null == deliver){
//                    throw new ServiceException("下发蓝牙token失败");
//                }
//                if(deliver.getCode() != 0){
//                    log.error("下发蓝牙token失败:{} {}", deliver.getMsg(), order.getOrderNumber());
//                    throw new ServiceException(deliver.getMsg());
//                }
//                log.info("下发蓝牙token成功:{} {}", deliver.getData(), order.getOrderNumber());
                BaseVehicleUserDto deliver = bluetoothUnlockClient.deliver(vo);
                if(null == deliver){
                    throw new ServiceException("下发蓝牙token失败");
                }
                if(deliver.getCode() != 0){
                    log.error("下发蓝牙token失败:{} {}", deliver.getMsg(), order.getOrderNumber());
                    throw new ServiceException(deliver.getMsg());
                }
                log.info("下发蓝牙token成功:{} {}", deliver.getData(), order.getOrderNumber());
            }
            if (order.getStatus() == 2) {
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpTestOrderServiceImpl.java
@@ -275,6 +275,8 @@
            pushOrderDto.setEndTaskId(xpOrder.getXpuEndTaskId());
            pushOrderDto.setVin(xpVehicle.getVehicleVin());
            pushOrderDto.setOrderId("TEST" + xpOrder.getId());
            pushOrderDto.setStartWaypointId(xpOrder.getStartTestPointId());
            pushOrderDto.setEndWaypointId(xpOrder.getEndTestPointId());
            pushOrderDto.setStartLatitude(xpOrder.getStartLat());
            pushOrderDto.setStartLongitude(xpOrder.getStartLon());
            pushOrderDto.setEndLatitude(xpOrder.getEndLat());
@@ -838,6 +840,8 @@
            pushOrderDto.setEndTaskId(order.getXpuEndTaskId());
            pushOrderDto.setVin(newVehicle.getVehicleVin());
            pushOrderDto.setOrderId("TEST" + order.getId());
            pushOrderDto.setStartWaypointId(order.getStartTestPointId());
            pushOrderDto.setEndWaypointId(order.getEndTestPointId());
            pushOrderDto.setStartLatitude(order.getStartLat());
            pushOrderDto.setStartLongitude(order.getStartLon());
            pushOrderDto.setEndLatitude(order.getEndLat());
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/utils/TaskOrderCancelUtil.java
@@ -84,7 +84,8 @@
            redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId());
            XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData();
            BluetoothDeliverVo vo = new BluetoothDeliverVo();
            vo.setVim(xpVehicle.getVehicleVin());
            vo.setCredential(token);
            vo.setVin(xpVehicle.getVehicleVin());
            vo.setPassengerUid(order.getAppUserId());
            BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo);
            if(null == delete || delete.getCode() != 0){
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
@@ -26,14 +26,44 @@
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Api(tags = "订单推送轨迹接口")
@RestController
@RequestMapping("")
public class PushOrderRouteController {
    /**
     * 推送轨迹重试线程池(类级别单例复用)
     * 修复:原代码在每次 sendRoute 失败时都 new ThreadPoolExecutor 且不 shutdown,
     * 导致核心线程在 LinkedBlockingQueue.take() 上永久 WAITING,造成线程泄漏。
     *
     * 参数推导(基于业务并发量估算):
     * - 车端上报频率 ~1次/秒/车,接口限频1.5秒,实际处理 ~0.67次/秒/车
     * - 常态(50车 + 5%失败率):~1.65 任务/秒,任务耗时~5秒,同时~8个任务
     * - 突发(100车 + 20%失败率):~13 任务/秒,同时~66个任务
     * - corePoolSize=4 覆盖常态,maxPoolSize=16 + 队列512 覆盖突发
     */
    private static final ThreadPoolExecutor RETRY_EXECUTOR = new ThreadPoolExecutor(
            4,
            16,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(512),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "route-push-retry-" + counter.incrementAndGet());
                    t.setDaemon(true);
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    @Autowired
    private RedisService redisService;
@@ -89,6 +119,7 @@
                Integer distance = dto.getDistance().intValue();
                Integer duration = dto.getDuration().intValue();
                Integer speed = dto.getDuration().intValue();
                // 计算实时点位到线段的最优截取
                Map<String, List<LonLatDto>> remainingNavigation = MapMatchingUtil.getRemainingNavigation(routeVo.getPolyline(), dto.getLongitude(), dto.getLatitude());
                log.info("remainingNavigation {}", JSON.toJSONString(remainingNavigation));
@@ -113,6 +144,7 @@
                // 封装路由详情
                Map<String, Object> route = new HashMap<>();
                route.put("speed",speed);
                route.put("distance",distance);
                route.put("duration",duration);
                route.put("polyline",JSONObject.toJSONString(lonLatDtos));
@@ -139,8 +171,7 @@
                R<String> stringR = xpPushMessageClient.sendRoute(sendMessageDTO);
                //推送失败,重试3次
                if(stringR.getCode() != Constants.SUCCESS){
                    ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
                    executor.execute(() -> {
                    RETRY_EXECUTOR.execute(() -> {
                        for (int i = 0; i < 3; i++) {
                            R<String> stringR1 = xpPushMessageClient.sendStatus(sendMessageDTO);
                            if(stringR1.getCode() == Constants.SUCCESS){
@@ -149,7 +180,8 @@
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                                Thread.currentThread().interrupt();
                                return;
                            }
                            log.info("推送乘客端剩余导航数据消息失败:{} {} {}", stringR1.getMsg(), JSON.toJSONString(sendMessageDTO), xpOrder.getOrderNumber());
                        }
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/utils/TrajectoryDensifier.java
@@ -11,62 +11,95 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class TrajectoryDensifier {
    private static final int THREAD_NUM = 5;
    /**
     * 轨迹补全计算线程池(类级别单例复用)
     * 修复:原代码每次调用 densify() 都 new ThreadPoolExecutor 并在方法结束时 shutdown,
     * 频繁创建/销毁线程开销大,且异常路径(latch.await 被中断)会跳过 shutdown 导致线程泄漏。
     * 改为单例线程池复用,避免上述问题。
     */
    private static final ThreadPoolExecutor DENSIFY_EXECUTOR = new ThreadPoolExecutor(
            5, 5, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(256),
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r, "trajectory-densify-" + counter.incrementAndGet());
                    t.setDaemon(true);
                    return t;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );
    static {
        // 允许核心线程超时回收,空闲时不占用资源
        DENSIFY_EXECUTOR.allowCoreThreadTimeOut(true);
    }
    public static List<LonLatDto> densify(List<LonLatDto> originalPoints, int distance) {
        if (originalPoints == null || originalPoints.size() <= 1) {
            return new ArrayList<>(originalPoints);
        }
        int num = originalPoints.size() / THREAD_NUM;
        if(num == 0){
            num = 1;
        }
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(num, num, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        CountDownLatch latch = new CountDownLatch(num);
        Map<Integer, List<LonLatDto>> map = new HashMap<>();
        Map<Integer, List<LonLatDto>> map = new ConcurrentHashMap<>();
        for (int i = 0; i < num; i++) {
            List<LonLatDto> trackPoints = originalPoints.subList(i * THREAD_NUM, i == num - 1 ? originalPoints.size() : (i + 1) * THREAD_NUM);
            int finalI = i;
            poolExecutor.execute(()->{
                List<LonLatDto> new_polyline = new ArrayList<>();
                for (int j = 0; j < trackPoints.size(); j++) {
                    LonLatDto trackPoint = trackPoints.get(j);
                    if(j == trackPoints.size() - 1){
                        new_polyline.add(new LonLatDto(trackPoint.getLongitude(), trackPoint.getLatitude()));
                        break;
                    }
                    LonLatDto trackPoint1 = trackPoints.get(j+1);
            DENSIFY_EXECUTOR.execute(()->{
                try {
                    List<LonLatDto> new_polyline = new ArrayList<>();
                    for (int j = 0; j < trackPoints.size(); j++) {
                        LonLatDto trackPoint = trackPoints.get(j);
                        if(j == trackPoints.size() - 1){
                            new_polyline.add(new LonLatDto(trackPoint.getLongitude(), trackPoint.getLatitude()));
                            break;
                        }
                        LonLatDto trackPoint1 = trackPoints.get(j+1);
                    //对两个点位进行坐标补全
                    List<TrackPoint> track = new ArrayList<>();
                    track.add(new TrackPoint(trackPoint.getLatitude(), trackPoint.getLongitude(), null));
                    track.add(new TrackPoint(trackPoint1.getLatitude(), trackPoint1.getLongitude(), null));
                    //10米补全一个点位
                    List<TrackPoint> densified = densifyByDistance(track, distance);
                    for (TrackPoint point : densified) {
                        new_polyline.add(new LonLatDto(point.getLongitude(), point.getLatitude()));
                        //对两个点位进行坐标补全
                        List<TrackPoint> track = new ArrayList<>();
                        track.add(new TrackPoint(trackPoint.getLatitude(), trackPoint.getLongitude(), null));
                        track.add(new TrackPoint(trackPoint1.getLatitude(), trackPoint1.getLongitude(), null));
                        //10米补全一个点位
                        List<TrackPoint> densified = densifyByDistance(track, distance);
                        for (TrackPoint point : densified) {
                            new_polyline.add(new LonLatDto(point.getLongitude(), point.getLatitude()));
                        }
                    }
                    if (!new_polyline.isEmpty()) {
                        new_polyline.remove(new_polyline.size() - 1);
                    }
                    map.put(finalI, new_polyline);
                } finally {
                    latch.countDown();
                }
                new_polyline.remove(new_polyline.size() - 1);
                map.put(finalI, new_polyline);
                latch.countDown();
            });
        }
        try {
            latch.await();
            poolExecutor.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
        List<LonLatDto> result = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            result.addAll(map.get(i));
            List<LonLatDto> chunk = map.get(i);
            if (null != chunk) {
                result.addAll(chunk);
            }
        }
        //添加最后一个点
        result.add(originalPoints.get(originalPoints.size() - 1));
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/service/WebSocketUserChannelConnectionManager.java
New file
@@ -0,0 +1,282 @@
package com.xiaopeng.pushMessage.webSocket.service;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 基于 AppUser 的 WebSocket 连接管理器
 * 使用 Spring WebSocketSession(共用 Spring Boot HTTP 端口)
 * 支持按用户 ID 管理连接,一个用户可以有多个设备连接
 */
@Component
@Slf4j
public class WebSocketUserChannelConnectionManager {
    /**
     * 存储用户的所有连接
     * Key: 用户ID (appUserId)
     * Value: 该用户的所有连接Channel列表
     */
    private static final ConcurrentHashMap<String, Set<Channel>> userConnections = new ConcurrentHashMap<>();
    /**
     * 存储连接与用户的映射关系
     * Key: ChannelId.asLongText()
     * Value: 用户ID (appUserId)
     */
    private static final ConcurrentHashMap<String, String> channelToUser = new ConcurrentHashMap<>();
    /**
     * 存储所有连接
     * Key: ChannelId.asLongText()
     * Value: Channel
     */
    private static final ConcurrentHashMap<String, Channel> allConnections = new ConcurrentHashMap<>();
    /**
     * 添加用户连接
     * @param appUserId 用户ID
     * @param channel WebSocket连接通道
     */
    public void addUserConnection(String appUserId, Channel channel) {
        String channelId = channel.id().asLongText();
        // 添加到用户连接映射
        userConnections.computeIfAbsent(appUserId, k -> ConcurrentHashMap.newKeySet())
                .add(channel);
        // 添加到连接用户映射
        channelToUser.put(channelId, appUserId);
        // 添加到所有连接
        allConnections.put(channelId, channel);
        log.info("用户连接已添加,用户ID: {}, ChannelId: {}, 该用户连接数: {}",
                appUserId, channelId, userConnections.get(appUserId).size());
    }
    /**
     * 移除用户连接
     * @param channel WebSocket连接通道
     */
    public void removeUserConnection(Channel channel) {
        String channelId = channel.id().asLongText();
        String appUserId = channelToUser.get(channelId);
        if (appUserId != null) {
            // 从用户连接中移除
            Set<Channel> userChannels = userConnections.get(appUserId);
            if (userChannels != null) {
                userChannels.remove(channel);
                // 如果用户没有连接了,删除用户映射
                if (userChannels.isEmpty()) {
                    userConnections.remove(appUserId);
                }
            }
            // 从连接用户映射中移除
            channelToUser.remove(channelId);
            log.info("用户连接已移除,用户ID: {}, ChannelId: {}, 该用户剩余连接数: {}",
                    appUserId, channelId, userChannels != null ? userChannels.size() : 0);
        }
        // 从所有连接中移除
        allConnections.remove(channelId);
    }
    /**
     * 向指定用户的所有设备发送消息
     * @param appUserId 用户ID
     * @param message 消息内容
     * @return 成功发送的设备数
     */
    public int sendMessageToUser(String appUserId, String message) {
        Set<Channel> userChannels = userConnections.get(appUserId);
        if (userChannels == null || userChannels.isEmpty()) {
            log.warn("用户 {} 没有活跃连接", appUserId);
            return 0;
        }
        int successCount = 0;
        for (Channel channel : userChannels) {
            if (channel != null && channel.isActive()) {
                try {
                    channel.writeAndFlush(new TextWebSocketFrame(message));
                    successCount++;
                } catch (Exception e) {
                    log.error("向用户 {} 发送消息失败,连接ID: {}, 错误: {}",
                            appUserId, channel.id().asLongText(), e.getMessage());
                }
            }
        }
        log.info("向用户 {} 发送消息完成,成功发送到 {} 个设备,消息内容: {}",
                appUserId, successCount, message);
        return successCount;
    }
    /**
     * 向指定连接发送消息
     * @param channelId 连接ID
     * @param message 消息内容
     * @return 是否发送成功
     */
    public boolean sendMessageToChannel(String channelId, String message) {
        Channel channel = allConnections.get(channelId);
        if (channel != null && channel.isActive()) {
            try {
                channel.writeAndFlush(new TextWebSocketFrame(message));
                log.info("消息已发送到连接: {}, 内容: {}", channelId, message);
                return true;
            } catch (Exception e) {
                log.error("发送消息失败,连接ID: {}, 错误: {}", channelId, e.getMessage());
                return false;
            }
        } else {
            log.warn("连接不存在或已关闭,ChannelId: {}", channelId);
            return false;
        }
    }
    /**
     * 向所有连接广播消息
     * @param message 消息内容
     * @return 成功发送的连接数
     */
    public int broadcastMessage(String message) {
        int successCount = 0;
        for (Channel channel : allConnections.values()) {
            if (channel != null && channel.isActive()) {
                try {
                    channel.writeAndFlush(new TextWebSocketFrame(message));
                    successCount++;
                } catch (Exception e) {
                    log.error("广播消息失败,连接ID: {}, 错误: {}",
                            channel.id().asLongText(), e.getMessage());
                }
            }
        }
        log.info("广播消息完成,成功发送到 {} 个连接,消息内容: {}", successCount, message);
        return successCount;
    }
    /**
     * 向多个用户发送消息
     * @param appUserIds 用户ID列表
     * @param message 消息内容
     * @return 成功发送的用户数
     */
    public int sendMessageToUsers(List<String> appUserIds, String message) {
        int successUserCount = 0;
        for (String appUserId : appUserIds) {
            int deviceCount = sendMessageToUser(appUserId, message);
            if (deviceCount > 0) {
                successUserCount++;
            }
        }
        log.info("向 {} 个用户发送消息完成,成功发送到 {} 个用户", appUserIds.size(), successUserCount);
        return successUserCount;
    }
    /**
     * 获取用户的所有连接
     * @param appUserId 用户ID
     * @return 连接列表
     */
    public Set<Channel> getUserConnections(String appUserId) {
        return userConnections.getOrDefault(appUserId, Collections.emptySet());
    }
    /**
     * 获取用户连接数
     * @param appUserId 用户ID
     * @return 连接数
     */
    public int getUserConnectionCount(String appUserId) {
        Set<Channel> userChannels = userConnections.get(appUserId);
        return userChannels != null ? userChannels.size() : 0;
    }
    /**
     * 获取总连接数
     * @return 总连接数
     */
    public int getTotalConnectionCount() {
        return allConnections.size();
    }
    /**
     * 获取在线用户数
     * @return 在线用户数
     */
    public int getOnlineUserCount() {
        return userConnections.size();
    }
    /**
     * 获取所有在线用户ID
     * @return 用户ID集合
     */
    public Set<String> getOnlineUserIds() {
        return userConnections.keySet();
    }
    /**
     * 检查用户是否在线
     * @param appUserId 用户ID
     * @return 是否在线
     */
    public boolean isUserOnline(String appUserId) {
        Set<Channel> userChannels = userConnections.get(appUserId);
        if (userChannels == null || userChannels.isEmpty()) {
            return false;
        }
        // 检查是否有活跃连接
        return userChannels.stream().anyMatch(Channel::isActive);
    }
    /**
     * 检查连接是否存在
     * @param channelId 连接ID
     * @return 是否存在
     */
    public boolean isConnectionExists(String channelId) {
        Channel channel = allConnections.get(channelId);
        return channel != null && channel.isActive();
    }
    /**
     * 根据连接ID获取用户ID
     * @param channelId 连接ID
     * @return 用户ID
     */
    public String getUserIdByChannelId(String channelId) {
        return channelToUser.get(channelId);
    }
    /**
     * 获取连接统计信息
     * @return 统计信息
     */
    public Map<String, Object> getConnectionStats() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalConnections", getTotalConnectionCount());
        stats.put("onlineUsers", getOnlineUserCount());
        stats.put("onlineUserIds", getOnlineUserIds());
        // 统计每个用户的连接数
        Map<String, Integer> userConnectionCounts = new HashMap<>();
        for (Map.Entry<String, Set<Channel>> entry : userConnections.entrySet()) {
            userConnectionCounts.put(entry.getKey(), entry.getValue().size());
        }
        stats.put("userConnectionCounts", userConnectionCounts);
        return stats;
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -538,7 +538,7 @@
            // 设定温度
            VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
            vehiclesCommandVo.setVin(xpVehicle.getVehicleVin());
            vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.HVAC_TEMP_SET_FLOAT.getCommand());
            vehiclesCommandVo.setCommandCode(VehiclesCommandEnum.LEFT_REAR_HVAC_TEMP_SET_FLOAT.getCommand());
            VehiclesCommandParamVo vehiclesCommandParamVo = new VehiclesCommandParamVo();
            vehiclesCommandParamVo.setDegree(dto.getTargetTemperature());
            vehiclesCommandVo.setParam(vehiclesCommandParamVo);
@@ -1638,6 +1638,7 @@
            dto1.setLatitude(latitude);
            dto1.setDuration(dto.getDuration());
            dto1.setDistance(dto.getDistance());
            dto1.setSpeed(dto.getSpeed());
            dto1.setDirection(dto.getCourse().intValue());
            OrderTrajectoryVo orderTrajectory = new OrderTrajectoryVo();