kong
2026-04-12 e62b24da90d324032754b70c7a059258dc6321ce
add 路线偏好设置
5个文件已修改
129 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/enums/RouteStrategyKind.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RoutePreferenceItemVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/enums/RouteStrategyKind.java
@@ -11,8 +11,8 @@
    SMART(32, "智能推荐", 1),
    /** 时间优先 → 速度最快 */
    TIME(38, "时间优先", 2),
    /** 价格优先 → 少收费 */
    PRICE(36, "价格优先", 3);
    /** 低价优先 → 少收费 */
    PRICE(36, "低价优先", 3);
    private final int amapStrategy;
    private final String displayName;
@@ -33,4 +33,13 @@
        }
        return kinds.stream().min((a, b) -> Integer.compare(a.priority, b.priority)).orElse(SMART);
    }
    public static java.util.Optional<RouteStrategyKind> fromAmapStrategy(int strategy) {
        for (RouteStrategyKind k : values()) {
            if (k.amapStrategy == strategy) {
                return java.util.Optional.of(k);
            }
        }
        return java.util.Optional.empty();
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RoutePreferenceItemVo.java
@@ -22,4 +22,7 @@
    @ApiModelProperty("对应高德 strategy")
    private Integer amapStrategy;
    @ApiModelProperty("当前用户是否选中该项(用于弹窗勾选态)")
    private Boolean selected;
}
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java
@@ -181,10 +181,11 @@
    }
    @GetMapping("/base/v1/mini-program/navigation/route-preferences")
    @ApiOperation(value = "路线偏好可选项", tags = {"小程序-路线选择"}, response = RoutePreferenceItemVo.class)
    @ApiOperation(value = "路线偏好可选项(含当前用户勾选态 selected)", tags = {"小程序-路线选择"}, response = RoutePreferenceItemVo.class)
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<List<RoutePreferenceItemVo>> listRoutePreferences(@RequestParam Long openCityId) {
        return R.ok(xpPricingService.listRoutePreferences(openCityId));
    public R<List<RoutePreferenceItemVo>> listRoutePreferences(@RequestParam Long openCityId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpPricingService.listRoutePreferences(openCityId, uid));
    }
    @PostMapping("/base/v1/mini-program/navigation/route-preference")
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java
@@ -70,7 +70,7 @@
    /**
     * 路线偏好可选项(后台可扩展为配置表)
     */
    List<RoutePreferenceItemVo> listRoutePreferences(Long openCityId);
    List<RoutePreferenceItemVo> listRoutePreferences(Long openCityId, String userId);
    /**
     * 保存用户默认路线偏好(影响后续起终点规划策略)
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -139,6 +139,12 @@
    @Value("${navigation.route.price-wave-percent:5}")
    private double navigationPriceWavePercent;
    /**
     * 后台可配置的偏好项编码,逗号分隔,如 SMART,TIME,PRICE
     */
    @Value("${navigation.route.preference.enabled-codes:SMART,TIME,PRICE}")
    private String navigationPreferenceEnabledCodes;
    // 自定义线程池用于并行执行任务
    private final ExecutorService executorService;
@@ -867,7 +873,7 @@
            throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
        }
        List<RouteStrategyKind> kinds = Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE);
        List<RouteStrategyKind> kinds = buildStrategyKindsForPlanning(userId);
        Map<String, RouteVo> routeById = new LinkedHashMap<>();
        Map<String, Set<RouteStrategyKind>> strategiesById = new LinkedHashMap<>();
        for (RouteStrategyKind kind : kinds) {
@@ -924,6 +930,9 @@
            options.add(opt);
        }
        RouteStrategyKind userPref = resolveUserPreferredKind(userId).orElse(RouteStrategyKind.SMART);
        options.sort((a, b) -> compareRouteOptionByUserPreference(a, b, userPref, strategiesById));
        String snapshotId = UUID.randomUUID().toString().replace("-", "");
        long now = System.currentTimeMillis();
        MultiRouteSnapshotPayload payload = new MultiRouteSnapshotPayload();
@@ -942,7 +951,7 @@
        vo.setGeneratedAtMillis(now);
        vo.setShowRouteSelector(options.size() >= 2);
        vo.setRoutes(options);
        vo.setRoutePreferences(listRoutePreferences(routeAndFareDto.getOpenCityId()));
        vo.setRoutePreferences(listRoutePreferences(routeAndFareDto.getOpenCityId(), userId));
        vo.setStaleCheckSeconds(navigationStaleSeconds);
        vo.setPriceWaveThresholdPercent(navigationPriceWavePercent);
@@ -1017,16 +1026,22 @@
    }
    @Override
    public List<RoutePreferenceItemVo> listRoutePreferences(Long openCityId) {
    public List<RoutePreferenceItemVo> listRoutePreferences(Long openCityId, String userId) {
        Set<String> enabledCodes = parseEnabledPreferenceCodes();
        Optional<RouteStrategyKind> savedPref = resolveUserPreferredKind(userId);
        List<RoutePreferenceItemVo> list = new ArrayList<>();
        int i = 0;
        for (RouteStrategyKind k : Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE)) {
            if (!enabledCodes.contains(k.name())) {
                continue;
            }
            RoutePreferenceItemVo v = new RoutePreferenceItemVo();
            v.setCode(k.name());
            v.setName(k.getDisplayName());
            v.setEnabled(true);
            v.setSortOrder(i++);
            v.setAmapStrategy(k.getAmapStrategy());
            v.setSelected(savedPref.isPresent() && k == savedPref.get());
            list.add(v);
        }
        return list;
@@ -1043,4 +1058,87 @@
        redisService.setCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId, String.valueOf(k.getAmapStrategy()), 365L, TimeUnit.DAYS);
    }
    private Set<String> parseEnabledPreferenceCodes() {
        Set<String> set = new LinkedHashSet<>();
        for (String part : navigationPreferenceEnabledCodes.split(",")) {
            String p = part.trim().toUpperCase(Locale.ROOT);
            if (!p.isEmpty()) {
                set.add(p);
            }
        }
        if (set.isEmpty()) {
            set.add(RouteStrategyKind.SMART.name());
            set.add(RouteStrategyKind.TIME.name());
            set.add(RouteStrategyKind.PRICE.name());
        }
        return set;
    }
    /**
     * 用户偏好策略优先请求,其余策略仍请求以便合并去重。
     */
    private List<RouteStrategyKind> buildStrategyKindsForPlanning(String userId) {
        List<RouteStrategyKind> all = new ArrayList<>();
        Set<String> enabled = parseEnabledPreferenceCodes();
        for (RouteStrategyKind k : Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE)) {
            if (enabled.contains(k.name())) {
                all.add(k);
            }
        }
        if (all.isEmpty()) {
            all.add(RouteStrategyKind.SMART);
        }
        Optional<RouteStrategyKind> pref = resolveUserPreferredKind(userId);
        if (!pref.isPresent()) {
            return all;
        }
        RouteStrategyKind p = pref.get();
        if (!all.contains(p)) {
            return all;
        }
        List<RouteStrategyKind> ordered = new ArrayList<>();
        ordered.add(p);
        for (RouteStrategyKind k : all) {
            if (k != p) {
                ordered.add(k);
            }
        }
        return ordered;
    }
    private Optional<RouteStrategyKind> resolveUserPreferredKind(String userId) {
        if (userId == null || userId.isEmpty()) {
            return Optional.empty();
        }
        Object raw = redisService.getCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId);
        if (raw == null) {
            return Optional.empty();
        }
        try {
            int strategy = Integer.parseInt(raw.toString());
            return RouteStrategyKind.fromAmapStrategy(strategy);
        } catch (Exception e) {
            return Optional.empty();
        }
    }
    private int compareRouteOptionByUserPreference(RouteOptionVo a, RouteOptionVo b, RouteStrategyKind pref,
            Map<String, Set<RouteStrategyKind>> strategiesById) {
        Set<RouteStrategyKind> sa = strategiesById.getOrDefault(a.getPathFingerprint(), Collections.emptySet());
        Set<RouteStrategyKind> sb = strategiesById.getOrDefault(b.getPathFingerprint(), Collections.emptySet());
        boolean aHas = sa.contains(pref);
        boolean bHas = sb.contains(pref);
        if (aHas != bHas) {
            return bHas ? 1 : -1;
        }
        boolean aPrimary = a.getPrimaryRoutePlanRule() != null && a.getPrimaryRoutePlanRule() == pref.getAmapStrategy();
        boolean bPrimary = b.getPrimaryRoutePlanRule() != null && b.getPrimaryRoutePlanRule() == pref.getAmapStrategy();
        if (aPrimary != bPrimary) {
            return bPrimary ? 1 : -1;
        }
        int da = a.getRoute() != null && a.getRoute().getDuration() != null ? a.getRoute().getDuration() : Integer.MAX_VALUE;
        int db = b.getRoute() != null && b.getRoute().getDuration() != null ? b.getRoute().getDuration() : Integer.MAX_VALUE;
        return Integer.compare(da, db);
    }
}