liujie
2026-02-04 b8100f505a42a3622a865ee44d4b150cfc461bbb
小程序接口  冒泡页面部分
2个文件已删除
2 文件已重命名
22个文件已添加
20个文件已修改
3107 ■■■■■ 已修改文件
xp-api/src/main/java/com/xiaopeng/api/dto/map/RouteAndFareDto.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/dto/operation/LonLatDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/factory/RemoteMenuFallbackFactory.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/factory/map/XpMapClientFallbackFactory.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/factory/operation/XpSystemSettingClientFallbackFactory.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpSystemSettingClient.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/model/operation/TreeSelect.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/model/operation/XpPricing.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/vo/map/XpDistanceAndDurationVo.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/vo/operation/FareVo.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/vo/operation/RouteAndFareVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/vo/operation/RouteVo.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/vo/operation/XpElectronicFenceVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-common/xp-common-security/src/main/java/com/xiaopeng/common/security/auth/AuthLogic.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/ElectronicFenceController.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceController.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/XpMapController.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/dto/WalkTimeDto.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/XpElectronicFenceAreaService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/XpElectronicFenceService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceAreaServiceImpl.java 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/AmapGeoFenceUtil.java 528 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/AmapWalkingRouteUtil.java 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/ElectricFenceUtil.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/mangoUtils/MongoGeoFenceUtil.java 604 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/IsInOperationAreaVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/WalkTimeVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/XpGeoFencingPolygon.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/pom.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpPricingItemController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpSystemSettingController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/mapper/XpAgreementAppUserMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpAgreementAppUserService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpPricingService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpSystemSettingService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpSystemSettingServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/utils/XpPricingUtil.java 335 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/utils/holiday/HolidayUtil.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/vo/XpPricingCardVo.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-api/src/main/java/com/xiaopeng/api/dto/map/RouteAndFareDto.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.api.dto.map;
import com.xiaopeng.api.dto.operation.LonLatDto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import javax.validation.constraints.NotNull;
@lombok.Data
@ApiModel(description = "线路和运费Dto")
public class RouteAndFareDto {
    @ApiModelProperty(value = "出发地")
    @NotNull(message = "出发地不能为空")
    private LonLatDto origin;
    @ApiModelProperty(value = "目的地")
    @NotNull(message = "目的地不能为空")
    private LonLatDto destination;
    @ApiModelProperty(value = "开通城市id")
    @NotNull(message = "开通城市id不能为空")
    private Long openCityId;
}
xp-api/src/main/java/com/xiaopeng/api/dto/operation/LonLatDto.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.dto.operation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(value = "经纬度DTO")
@AllArgsConstructor
@NoArgsConstructor
public class LonLatDto {
    @ApiModelProperty(value = "经度")
    @NotNull(message = "经度不能为空")
    private Double longitude;
    @ApiModelProperty(value = "纬度")
    @NotNull(message = "纬度不能为空")
    private Double latitude;
}
xp-api/src/main/java/com/xiaopeng/api/factory/RemoteMenuFallbackFactory.java
@@ -22,7 +22,7 @@
        return new RemoteMenuService(){
            @Override
            public R<List<SysMenu>> getMenusByMenuIds(List<Long> menuIds, String source) {
            public R<List<SysMenu>> getMenusByMenuIds(List<Long> menuIds) {
                log.error("获取菜单列表失败,menuIds: {}, 原因: {}", menuIds, cause.getMessage());
                // 降级返回空列表,避免登录失败
                return R.ok(new ArrayList<>());
xp-api/src/main/java/com/xiaopeng/api/factory/map/XpMapClientFallbackFactory.java
New file
@@ -0,0 +1,31 @@
package com.xiaopeng.api.factory.map;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.vo.map.XpDistanceAndDurationVo;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class XpMapClientFallbackFactory implements FallbackFactory<XpMapClient> {
    @Override
    public XpMapClient create(Throwable cause) {
        return new XpMapClient(){
            @Override
            public R<XpDistanceAndDurationVo> getDistanceAndDuration(RouteAndFareDto dto) {
                log.error("获取距离和时长失败:{}", cause.getMessage());
                return R.fail("获取距离和时长失败");
            }
            @Override
            public R<Boolean> diagnosisArea(RouteAndFareDto dto) {
                log.error("判断是否同一经营区域失败:{}", cause.getMessage());
                return R.fail("判断是否同一经营区域失败");
            }
        };
    }
}
xp-api/src/main/java/com/xiaopeng/api/factory/operation/XpSystemSettingClientFallbackFactory.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.factory.operation;
import com.xiaopeng.api.feignClient.operation.SysLogClient;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.model.operation.SysOperLog;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class XpSystemSettingClientFallbackFactory implements FallbackFactory<XpSystemSettingClient> {
    @Override
    public XpSystemSettingClient create(Throwable cause) {
        return new XpSystemSettingClient(){
            @Override
            public R getOrderDistance() {
                log.error("获取下单距离失败:{}", cause.getMessage());
                return R.fail("获取下单距离失败");
            }
        };
    }
}
xp-api/src/main/java/com/xiaopeng/api/feignClient/map/XpMapClient.java
New file
@@ -0,0 +1,36 @@
package com.xiaopeng.api.feignClient.map;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.factory.map.XpMapClientFallbackFactory;
import com.xiaopeng.api.factory.operation.XpSystemSettingClientFallbackFactory;
import com.xiaopeng.api.vo.map.XpDistanceAndDurationVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(contextId = "XpMapClient", value = ServiceNameConstantsEnum.XP_MAP_SERVICE, fallbackFactory = XpMapClientFallbackFactory.class)
public interface XpMapClient {
    /**
     * 获取距离和时长
     * @param dto
     * @return
     */
    @PostMapping("/base/v1/mini-program/map/getDistanceAndDuration")
    R<XpDistanceAndDurationVo> getDistanceAndDuration(@RequestBody RouteAndFareDto dto);
    /**
     * 判断是否同一经营区域
     * @param dto
     * @return
     */
    @PostMapping("/base/v1/mini-program/fence/diagnosisArea")
    R<Boolean> diagnosisArea(@RequestBody RouteAndFareDto dto);
}
xp-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpSystemSettingClient.java
New file
@@ -0,0 +1,20 @@
package com.xiaopeng.api.feignClient.operation;
import com.xiaopeng.api.factory.operation.XpSystemSettingClientFallbackFactory;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PostMapping;
@FeignClient(contextId = "XpSystemSettingClient", value = ServiceNameConstantsEnum.XP_OPERATION_SERVICE, fallbackFactory = XpSystemSettingClientFallbackFactory.class)
public interface XpSystemSettingClient {
    @GetMapping("/base/v1/mini-program/support/getOrderDistance")
    R getOrderDistance();
}
xp-api/src/main/java/com/xiaopeng/api/model/operation/TreeSelect.java
@@ -38,7 +38,7 @@
    public TreeSelect(SysMenuBak menu)
    {
        this.id = menu.getId();
        this.id = menu.getMenuId();
        this.label = menu.getMenuName();
        this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
    }
xp-api/src/main/java/com/xiaopeng/api/model/operation/XpPoint.java
@@ -1,5 +1,6 @@
package com.xiaopeng.api.model.operation;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -63,6 +64,10 @@
    @ApiModelProperty(value = "生效时间段")
    private String effectiveTimePeriod;
    @ApiModelProperty(value = "点位地址")
    @TableField("point_address")
    private String pointAddress;
    @ApiModelProperty(value = "备注")
    private String remarks;
xp-api/src/main/java/com/xiaopeng/api/model/operation/XpPricing.java
@@ -55,6 +55,10 @@
    @TableField("basic_pricing_json")
    private String basicPricingJson;
    @ApiModelProperty(value = "起步价json配置")
    @TableField("starting_fare_json")
    private String startingFareJson;
    @ApiModelProperty(value = "动态价格json配置")
    @TableField("dynamic_pricing_json")
    private String dynamicPricingJson;
xp-api/src/main/java/com/xiaopeng/api/vo/map/XpDistanceAndDurationVo.java
New file
@@ -0,0 +1,15 @@
package com.xiaopeng.api.vo.map;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "XpDistanceAndDurationVo对象", description = "距离和时长")
public class XpDistanceAndDurationVo {
    // 距离 米
    private String distance;
    // 时长 秒
    private String duration;
}
xp-api/src/main/java/com/xiaopeng/api/vo/operation/FareVo.java
New file
@@ -0,0 +1,24 @@
package com.xiaopeng.api.vo.operation;
import io.swagger.annotations.ApiModel;
@lombok.Data
@ApiModel(description = "费用Vo")
public class FareVo {
    private String id;
    private Double estimatedDistanceKm;
    private Integer estimatedDurationMin;
    private Double originalFare;
    private Double discountAmount;
    private Double finalFare;
    private Integer waitingTime;
    private Integer maxPeopleNumber=5;
}
xp-api/src/main/java/com/xiaopeng/api/vo/operation/RouteAndFareVo.java
New file
@@ -0,0 +1,13 @@
package com.xiaopeng.api.vo.operation;
import io.swagger.annotations.ApiModel;
@lombok.Data
@ApiModel(description = "线路和运费")
public class RouteAndFareVo {
    private RouteVo route;
    private FareVo fareEstimate;
}
xp-api/src/main/java/com/xiaopeng/api/vo/operation/RouteVo.java
New file
@@ -0,0 +1,18 @@
package com.xiaopeng.api.vo.operation;
import com.xiaopeng.api.dto.operation.LonLatDto;
import io.swagger.annotations.ApiModel;
import java.util.List;
@lombok.Data
@ApiModel(description = "线路和运费")
public class RouteVo {
    private Integer distance;
    private Integer duration;
    private String routeID;
    private List<LonLatDto> polyline;
}
xp-api/src/main/java/com/xiaopeng/api/vo/operation/XpElectronicFenceVo.java
@@ -10,7 +10,7 @@
@ApiModel("电子围栏开放区域Vo")
public class XpElectronicFenceVo {
    @ApiModelProperty(value = "id")
    private Long id;
    private String FenceId;
    @ApiModelProperty(value = "围栏名称")
    private String name;
xp-common/xp-common-security/src/main/java/com/xiaopeng/common/security/auth/AuthLogic.java
@@ -355,7 +355,8 @@
            if(null == loginUser.getPermissions()){
                return new HashSet<>();
            }
            return loginUser.getPermissions();
//            return loginUser.getPermissions();
            return null;
        }
        catch (Exception e)
        {
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/ElectronicFenceController.java
File was deleted
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/XpElectronicFenceController.java
New file
@@ -0,0 +1,72 @@
package com.xiaopeng.map.controller;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.vo.operation.XpElectronicFenceVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import com.xiaopeng.map.dto.WalkTimeDto;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.vo.IsInOperationAreaVo;
import com.xiaopeng.map.vo.WalkTimeVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/base/v1/mini-program")
@Api(tags = "电子围栏")
public class XpElectronicFenceController extends BaseController {
    private final XpElectronicFenceAreaService xpElectronicFenceAreaService;
    public XpElectronicFenceController(XpElectronicFenceAreaService xpElectronicFenceAreaService) {
        this.xpElectronicFenceAreaService = xpElectronicFenceAreaService;
    }
    /**
     * 获取地图开放区域围栏
     * @return
     */
    @ApiOperation(value = "获取地图开放区域围栏",tags = {"小程序-获取地图开放区域围栏"},response = XpElectronicFenceVo.class)
    @GetMapping("/cities/list")
    public AjaxResult<List<XpElectronicFenceVo>> citiesList() {
        return success(xpElectronicFenceAreaService.citiesList());
    }
    @ApiOperation(value = "判断当前位置是否在运营区域内",tags = {"小程序-判断当前位置是否在运营区域内"})
    @PostMapping("/fence/check")
    public AjaxResult<IsInOperationAreaVo> isInOperationArea(@RequestBody @Valid  LonLatDto dto) {
        return success(xpElectronicFenceAreaService.isInOperationArea(dto));
    }
    @ApiOperation(value = "根据点位获取步行时间和距离",tags = {"小程序-根据点位获取步行时间和距离"})
    @PostMapping("/fence/walkTime")
    public AjaxResult<WalkTimeVo> getWalkTime(@RequestBody @Valid WalkTimeDto dto) {
        return success(xpElectronicFenceAreaService.getWalkTime(dto));
    }
    /**
     * 判断是否同一经营区域
     * @param dto
     * @return
     */
    @PostMapping("/fence/diagnosisArea")
    public R<Boolean> diagnosisArea(@RequestBody RouteAndFareDto dto){
        return R.ok(xpElectronicFenceAreaService.diagnosisArea(dto));
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/controller/XpMapController.java
New file
@@ -0,0 +1,65 @@
package com.xiaopeng.map.controller;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.vo.map.XpDistanceAndDurationVo;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.map.utils.GDMapGeocodingUtil;
import com.xiaopeng.map.utils.mangoUtils.MongoGeoFenceUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/base/v1/mini-program")
@Api(tags = "地图调用服务")
public class XpMapController {
    private final GDMapGeocodingUtil gdMapGeocodingUtil;
    private final MongoGeoFenceUtil mongoGeoFenceUtil;
    public XpMapController(GDMapGeocodingUtil gdMapGeocodingUtil, MongoGeoFenceUtil mongoGeoFenceUtil) {
        this.gdMapGeocodingUtil = gdMapGeocodingUtil;
        this.mongoGeoFenceUtil = mongoGeoFenceUtil;
    }
    @PostMapping("/map/getDistanceAndDuration")
    public R<XpDistanceAndDurationVo> getDistanceAndDuration(@RequestBody RouteAndFareDto dto){
        // 获取电子围栏的禁行区域点位
        MongoGeoFenceUtil.GeoPoint startPoint = new MongoGeoFenceUtil.GeoPoint(dto.getOrigin().getLongitude(),dto.getOrigin().getLatitude());
        List<String> operatingArea = mongoGeoFenceUtil.findFencesContainingPoint(startPoint, "operating_area", "points");
        String fenceId = operatingArea.get(0);
//        List<Document> pointsInFence = mongoGeoFenceUtil.findPointsInFence(fenceId, "parentId", "prohibited_area");
        List<Document> pointsInFence = mongoGeoFenceUtil.findPointsByParentId(fenceId, "parentId", "prohibited_area");
        List<LonLatDto> avoidRoads = pointsInFence.stream().map(point -> new LonLatDto(point.getDouble("longitude"), point.getDouble("latitude"))).collect(Collectors.toList());
        RouteVo routeVo = gdMapGeocodingUtil.getDistanceAndTime(dto.getOrigin(), dto.getDestination(), avoidRoads, fenceId);
        if(routeVo==null){
            return R.fail("没有找到路线");
        }
        return R.ok();
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/dto/WalkTimeDto.java
New file
@@ -0,0 +1,30 @@
package com.xiaopeng.map.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(value = "步行时间DTO")
public class WalkTimeDto {
    @ApiModelProperty(value = "开始经度")
    @NotNull(message = "开始经度不能为空")
    private Double startLongitude;
    @ApiModelProperty(value = "开始纬度")
    @NotNull(message = "开始纬度不能为空")
    private Double startLatitude;
    @ApiModelProperty(value = "结束经度")
    @NotNull(message = "结束经度不能为空")
    private Double endLongitude;
    @ApiModelProperty(value = "结束纬度")
    @NotNull(message = "结束纬度不能为空")
    private Double endLatitude;
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/XpElectronicFenceAreaService.java
@@ -2,9 +2,15 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.vo.operation.XpElectronicFenceVo;
import com.xiaopeng.map.dto.WalkTimeDto;
import com.xiaopeng.map.vo.IsInOperationAreaVo;
import com.xiaopeng.map.vo.WalkTimeVo;
import javax.validation.Valid;
import java.util.List;
/**
@@ -17,9 +23,32 @@
 */
public interface XpElectronicFenceAreaService extends IService<XpElectronicFenceArea> {
    /**
     * 获取所有的开放区域
     * @return
     */
    List<XpElectronicFenceVo> citiesList();
    /**
     * 判断当前位置是否在运营区域内
     * @param dto
     * @return
     */
    IsInOperationAreaVo isInOperationArea(LonLatDto dto);
    /**
     * 根据点位获取步行时间
     * @param dto
     * @return
     */
    WalkTimeVo getWalkTime(WalkTimeDto dto);
    /**
     * 检测区域
     * @param dto
     * @return
     */
    Boolean diagnosisArea(RouteAndFareDto dto);
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/XpElectronicFenceService.java
@@ -14,17 +14,9 @@
 */
public interface XpElectronicFenceService extends IService<XpElectronicFence> {
    /**
     * 获取电子围栏列表
     */
    List<XpElectronicFenceVo> getXpElectronicFenceList(String cityCode);
    /**
     * 判断当前位置是否在运营区域内
     */
    Boolean isInElectronicFence(Double latitude, Double longitude,String cityCode);
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceAreaServiceImpl.java
@@ -2,13 +2,39 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.api.vo.operation.XpElectronicFenceVo;
import com.xiaopeng.api.vo.operation.XpPointsVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.map.dto.WalkTimeDto;
import com.xiaopeng.map.mapper.XpElectronicFenceAreaMapper;
import com.xiaopeng.map.repository.XpGeoFencingPolygonRepository;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.utils.AmapGeoFenceUtil;
import com.xiaopeng.map.utils.AmapWalkingRouteUtil;
import com.xiaopeng.map.utils.ElectricFenceUtil;
import com.xiaopeng.map.utils.mangoUtils.MongoGeoFenceUtil;
import com.xiaopeng.map.vo.IsInOperationAreaVo;
import com.xiaopeng.map.vo.WalkTimeVo;
import com.xiaopeng.map.vo.XpGeoFencingPolygon;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.springframework.data.geo.Point;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
 * <p>
@@ -21,8 +47,215 @@
@Service
public class XpElectronicFenceAreaServiceImpl extends ServiceImpl<XpElectronicFenceAreaMapper, XpElectronicFenceArea> implements XpElectronicFenceAreaService {
    @Resource
    private XpSystemSettingClient xpSystemSettingClient;
    @Resource
    private XpGeoFencingPolygonRepository xpGeoFencingPolygonRepository;
    @Resource
    private AmapGeoFenceUtil amapGeoFenceUtil;
    @Override
    public List<XpElectronicFenceVo> citiesList() {
        List<XpGeoFencingPolygon> xpGeoFencingPolygons = xpGeoFencingPolygonRepository.findAll();
        List<XpElectronicFenceVo> result = new ArrayList<>();
        for (XpGeoFencingPolygon polygon : xpGeoFencingPolygons) {
            XpElectronicFenceVo vo = new XpElectronicFenceVo();
            // 设置id(使用siteId作为id)
            vo.setFenceId(polygon.getFenceId());
            // 设置围栏名称
            vo.setName(polygon.getName());
            // 转换GeoJsonPolygon为List<XpPointsVo>
            List<XpPointsVo> pointsVoList = new ArrayList<>();
            if (polygon.getPoints() != null && polygon.getPoints().getCoordinates() != null) {
                // GeoJsonPolygon的coordinates是一个二维数组,第一个元素是外部环
                List<Point> points = polygon.getPoints().getPoints();
                for (Point coord : points) {
                    XpPointsVo pointsVo = new XpPointsVo();
                    // GeoJson的坐标顺序是[经度, 纬度]
                    pointsVo.setLng(coord.getY());
                    pointsVo.setLat(coord.getX());
                    pointsVoList.add(pointsVo);
                }
            }
            vo.setPoints(pointsVoList);
            result.add(vo);
        }
        return result;
    }
    /**
     * 从MongoDB获取所有地理围栏多边形数据
     * @return 地理围栏多边形列表
     */
    public List<XpGeoFencingPolygon> getAllGeoFencingPolygons() {
        return xpGeoFencingPolygonRepository.findAll();
    }
    /**
     * 根据围栏id从MongoDB获取地理围栏多边形数据
     * @param siteId 围栏id
     * @return 地理围栏多边形
     */
    public XpGeoFencingPolygon getGeoFencingPolygonBySiteId(Integer siteId) {
        return xpGeoFencingPolygonRepository.findBySiteId(siteId);
    }
    /**
     * 根据围栏名称从MongoDB获取地理围栏多边形数据
     * @param name 围栏名称
     * @return 地理围栏多边形列表
     */
    public List<XpGeoFencingPolygon> getGeoFencingPolygonsByName(String name) {
        if (name == null) {
            return xpGeoFencingPolygonRepository.findAll();
        }
        return xpGeoFencingPolygonRepository.findByName(name);
    }
    @Resource
    private MongoGeoFenceUtil mongoGeoFenceUtil;
    /**
     * 判断经纬度是否在开放区域中
     * @param dto
     * @return
     */
    @Override
    public IsInOperationAreaVo isInOperationArea(LonLatDto dto) {
        IsInOperationAreaVo vo = new IsInOperationAreaVo();
        // 组装数据
        MongoGeoFenceUtil.GeoPoint geoPoint = new MongoGeoFenceUtil.GeoPoint(dto.getLongitude(),dto.getLatitude());
        List<String> operatingArea = mongoGeoFenceUtil.findFencesContainingPoint(geoPoint, "operating_area", "points");
        if (!operatingArea.isEmpty()) {
            vo.setStatus("inside");
        }
        return vo;
//        try {
//            executor.execute(() -> {
//                for (XpElectronicFenceVo xpElectronicFenceVo : xpElectronicFenceVos) {
//                    ArrayList<AmapGeoFenceUtil.GeoPoint> geoPoints = new ArrayList<>();
//                    // 获取多边形顶点坐标
//                    List<XpPointsVo> points = xpElectronicFenceVo.getPoints();
//                    for (XpPointsVo point : points) {
//                        geoPoints.add(new AmapGeoFenceUtil.GeoPoint(point.getLng(), point.getLat()));
//                    }
//                    AmapGeoFenceUtil.GeoFence geoFence = new AmapGeoFenceUtil.GeoFence();
//                    geoFence.setPoints(geoPoints);
//
//                    AmapGeoFenceUtil.GeoFenceResult geoFenceResult = null;
//                    try {
//
//
//                        ElectricFenceUtil.areaMonitoring()
//                        geoFenceResult = amapGeoFenceUtil.checkInPolygonFence(geoPoint, geoFence);
//                        if (geoFenceResult.isInFence()) {
//                            vo.setStatus("inside");
//                        }
//                    } catch (IOException e) {
//                        throw new RuntimeException(e);
//                    }
//                }
//            });
//            return vo;
//        }catch (Exception e){
//            e.printStackTrace();
//            vo.setStatus("outside");
//        }finally {
//            gracefulShutdown(executor);
//        }
//
//        return vo;
    }
    /**
     * 优雅关闭线程池通用方法
     *
     * @param pool 需要关闭的线程池
     */
    private static void gracefulShutdown(ExecutorService pool) {
        pool.shutdown(); // 拒绝新任务提交
        try {
            // 等待现有任务完成
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                pool.shutdownNow(); // 取消等待中的任务 只等待运行中的任务
                // 再次等待任务响应中断
                if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                }
            }
        } catch (InterruptedException e) {
            // 重新尝试关闭
            pool.shutdownNow();
            Thread.currentThread().interrupt();
        } finally {
        }
    }
    @Override
    public WalkTimeVo getWalkTime(WalkTimeDto dto) {
        WalkTimeVo walkTimeVo = new WalkTimeVo();
        // TODO 高德key
        AmapWalkingRouteUtil util = new AmapWalkingRouteUtil("");
        // 示例1:使用经纬度直接查询
        System.out.println("=== 示例1:直接使用经纬度查询 ===");
        AmapWalkingRouteUtil.Location origin = new AmapWalkingRouteUtil.Location(dto.getStartLongitude(), dto.getStartLatitude());  // 天安门
        AmapWalkingRouteUtil.Location destination = new AmapWalkingRouteUtil.Location(dto.getEndLongitude(), dto.getEndLatitude());  // 王府井
        AmapWalkingRouteUtil.WalkingRouteResult result = util.getWalkingRoute(origin, destination);
        if (result.isSuccess()) {
            System.out.println("预计时间: " + result.getFormattedDuration());
            System.out.println("步行距离: " + result.getFormattedDistance());
            walkTimeVo.setWalkTime(result.getFormattedDuration());
            walkTimeVo.setWalkDistance(result.getDistance());
            walkTimeVo.setWalkDistance(result.getDistance());
            R r = xpSystemSettingClient.getOrderDistance();
            if (r.getCode()==200) {
                Object orderDistance = r.getData();
                if (orderDistance != null) {
                    Double orderDistanceDouble = Double.parseDouble(orderDistance.toString());
                    if(orderDistanceDouble>result.getDistance()){
                        walkTimeVo.setStatus(1);
                    }
                }
            }
            return walkTimeVo;
        }else {
            System.out.println("错误信息: " + result.getMessage());
            // 抛出异常
            throw new RuntimeException(result.getMessage());
        }
    }
    @Override
    public Boolean diagnosisArea(RouteAndFareDto dto) {
        // 起点数据
        MongoGeoFenceUtil.GeoPoint startPoint = new MongoGeoFenceUtil.GeoPoint(dto.getOrigin().getLongitude(),dto.getOrigin().getLatitude());
        // 终点数据
        MongoGeoFenceUtil.GeoPoint endPoint = new MongoGeoFenceUtil.GeoPoint(dto.getDestination().getLongitude(),dto.getDestination().getLatitude());
        List<String> operatingArea = mongoGeoFenceUtil.findFencesContainingPoint(startPoint, "operating_area", "points");
        if (!operatingArea.isEmpty()) {
            List<String> operatingArea2 = mongoGeoFenceUtil.findFencesContainingPoint(endPoint, "operating_area", "points");
            if (!operatingArea2.isEmpty()) {
                // "开始判断是否在相同区域";
                if(operatingArea.get(0).equals(operatingArea2.get(0))){
                    return true;
                }
            }
        }
        return null;
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceServiceImpl.java
@@ -28,55 +28,6 @@
public class XpElectronicFenceServiceImpl extends ServiceImpl<XpElectronicFenceMapper, XpElectronicFence> implements XpElectronicFenceService {
    @Resource
    private XpOpenCityMapper xpOpenCityMapper;
    @Resource
    private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper;
    @Override
    public List<XpElectronicFenceVo> getXpElectronicFenceList(String cityCode) {
        // 通过code找到电子围栏信息
        // TODO 魔法数字
        List<XpOpenCity> xpOpenCities = xpOpenCityMapper.selectList(new LambdaQueryWrapper<XpOpenCity>().eq(XpOpenCity::getCityCode, cityCode).eq(XpOpenCity::getStatus, 1));
        if (!xpOpenCities.isEmpty()) {
            List<Long> openCityIds = xpOpenCities.stream().map(XpOpenCity::getId).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }
    @Override
    public Boolean isInElectronicFence(Double latitude, Double longitude,String cityCode) {
        // 获取所有可用的电子围栏信息
        return null;
    }
    /**
     * 通过一个点位 判断是否在某个电子围栏内
     */
    private static boolean isInElectronicFence(double lon, double lat,List<String> fencePoints) {
        boolean contains = false;
        for (String fencePoint : fencePoints) {
            ArrayList<GeoPolygonUtil.GeoPoint> geoPoints = new ArrayList<>();
            String[] split = fencePoint.split(",");
            for (String lonLat : split) {
                String[] split1 = lonLat.split("_");
                GeoPolygonUtil.GeoPoint geoPoint = new GeoPolygonUtil.GeoPoint(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                geoPoints.add(geoPoint);
            }
            contains = GeoPolygonUtil.contains(lon, lat, geoPoints);
            if(contains){
                // 一旦在范围中直接返回true
                return true;
            }
        }
        return false;
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/AmapGeoFenceUtil.java
New file
@@ -0,0 +1,528 @@
package com.xiaopeng.map.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * 高德地图地理围栏工具类
 */
@Component
public class AmapGeoFenceUtil {
    private final String apiKey;
    private final OkHttpClient httpClient;
    private final ObjectMapper objectMapper;
    // 缓存
    private final Map<String, GeoFence> geoFenceCache = new HashMap<>();
    public AmapGeoFenceUtil(String apiKey) {
        this.apiKey = apiKey;
        this.httpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .build();
        this.objectMapper = new ObjectMapper();
    }
    /**
     * 地理坐标点
     */
    public static class GeoPoint {
        private double longitude;
        private double latitude;
        public GeoPoint(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }
        public double getLongitude() {
            return longitude;
        }
        public double getLatitude() {
            return latitude;
        }
        @Override
        public String toString() {
            return String.format("%.6f,%.6f", longitude, latitude);
        }
        public String toLngLatString() {
            return String.format("%.6f,%.6f", longitude, latitude);
        }
        public String toLatLngString() {
            return String.format("%.6f,%.6f", latitude, longitude);
        }
    }
    /**
     * 地理围栏类型
     */
    public enum GeoFenceType {
        CIRCLE,    // 圆形
        POLYGON,   // 多边形
        DISTRICT   // 行政区
    }
    /**
     * 地理围栏
     */
    public static class GeoFence {
        private String id;
        private String name;
        private GeoFenceType type;
        private List<GeoPoint> points; // 多边形顶点
        private GeoPoint center;       // 圆心
        private double radius;         // 半径(米)
        private String adcode;         // 行政区划代码
        private String polygonString;  // 多边形字符串
        public GeoFence(String id, String name, GeoFenceType type) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.points = new ArrayList<>();
        }
        public GeoFence() {
        }
        // Getters and Setters
        public String getId() { return id; }
        public void setId(String id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public GeoFenceType getType() { return type; }
        public void setType(GeoFenceType type) { this.type = type; }
        public List<GeoPoint> getPoints() { return points; }
        public void setPoints(List<GeoPoint> points) { this.points = points; }
        public GeoPoint getCenter() { return center; }
        public void setCenter(GeoPoint center) { this.center = center; }
        public double getRadius() { return radius; }
        public void setRadius(double radius) { this.radius = radius; }
        public String getAdcode() { return adcode; }
        public void setAdcode(String adcode) { this.adcode = adcode; }
        public String getPolygonString() { return polygonString; }
        public void setPolygonString(String polygonString) { this.polygonString = polygonString; }
        public void addPoint(GeoPoint point) {
            this.points.add(point);
        }
        public void addPoint(double lng, double lat) {
            this.points.add(new GeoPoint(lng, lat));
        }
    }
    /**
     * 围栏检查结果
     */
    public static class GeoFenceResult {
        private boolean isInFence;
        private String fenceId;
        private String fenceName;
        private double distance;  // 距离围栏边界的距离(米)
        private String message;
        private GeoPoint location;
        private GeoFenceType fenceType;
        // Getters and Setters
        public boolean isInFence() { return isInFence; }
        public void setInFence(boolean inFence) { isInFence = inFence; }
        public String getFenceId() { return fenceId; }
        public void setFenceId(String fenceId) { this.fenceId = fenceId; }
        public String getFenceName() { return fenceName; }
        public void setFenceName(String fenceName) { this.fenceName = fenceName; }
        public double getDistance() { return distance; }
        public void setDistance(double distance) { this.distance = distance; }
        public String getMessage() { return message; }
        public void setMessage(String message) { this.message = message; }
        public GeoPoint getLocation() { return location; }
        public void setLocation(GeoPoint location) { this.location = location; }
        public GeoFenceType getFenceType() { return fenceType; }
        public void setFenceType(GeoFenceType fenceType) { this.fenceType = fenceType; }
        @Override
        public String toString() {
            return String.format("GeoFenceResult{inFence=%s, fence='%s', distance=%.2fm, message='%s'}",
                    isInFence, fenceName, distance, message);
        }
    }
    /**
     * 检查点是否在圆形围栏内(调用高德API)
     */
    public GeoFenceResult checkInCircleFence(GeoPoint point, GeoFence fence) throws IOException {
        GeoFenceResult result = new GeoFenceResult();
        result.setLocation(point);
        result.setFenceId(fence.getId());
        result.setFenceName(fence.getName());
        result.setFenceType(GeoFenceType.CIRCLE);
        // 构建API请求参数
        Map<String, String> params = new HashMap<>();
        params.put("key", apiKey);
        params.put("location", point.toLngLatString());
        params.put("radius", String.valueOf(fence.getRadius()));
        params.put("center", fence.getCenter().toLngLatString());
        String url = buildUrl("https://restapi.amap.com/v3/geofence/status", params);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                result.setInFence(false);
                result.setMessage("API请求失败: " + response.code());
                return result;
            }
            String responseBody = response.body().string();
            JsonNode rootNode = objectMapper.readTree(responseBody);
            String status = rootNode.get("status").asText();
            String info = rootNode.get("info").asText();
            if ("1".equals(status)) {
                JsonNode fencesNode = rootNode.get("fences");
                if (fencesNode != null && fencesNode.size() > 0) {
                    JsonNode fenceNode = fencesNode.get(0);
                    String fenceStatus = fenceNode.get("status").asText();
                    // status: in-在围栏内, out-在围栏外
                    result.setInFence("in".equals(fenceStatus));
                    result.setMessage("围栏状态: " + fenceStatus);
                    // 获取距离
                    if (fenceNode.has("distance")) {
                        result.setDistance(fenceNode.get("distance").asDouble());
                    }
                }
            } else {
                result.setInFence(false);
                result.setMessage("API返回错误: " + info);
            }
        } catch (Exception e) {
            result.setInFence(false);
            result.setMessage("检查围栏状态时发生错误: " + e.getMessage());
        }
        return result;
    }
    /**
     * 检查点是否在多边形围栏内(调用高德API)
     */
    public GeoFenceResult checkInPolygonFence(GeoPoint point, GeoFence fence) throws IOException {
        GeoFenceResult result = new GeoFenceResult();
        result.setLocation(point);
        result.setFenceId(fence.getId());
        result.setFenceName(fence.getName());
        result.setFenceType(GeoFenceType.POLYGON);
        // 构建多边形字符串
        String polygon = buildPolygonString(fence.getPoints());
        // 构建API请求参数
        Map<String, String> params = new HashMap<>();
        params.put("key", apiKey);
        params.put("location", point.toLngLatString());
        params.put("polygon", polygon);
        String url = buildUrl("https://restapi.amap.com/v3/geofence/status", params);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                result.setInFence(false);
                result.setMessage("API请求失败: " + response.code());
                return result;
            }
            String responseBody = response.body().string();
            JsonNode rootNode = objectMapper.readTree(responseBody);
            String status = rootNode.get("status").asText();
            String info = rootNode.get("info").asText();
            if ("1".equals(status)) {
                JsonNode fencesNode = rootNode.get("fences");
                if (fencesNode != null && fencesNode.size() > 0) {
                    JsonNode fenceNode = fencesNode.get(0);
                    String fenceStatus = fenceNode.get("status").asText();
                    result.setInFence("in".equals(fenceStatus));
                    result.setMessage("围栏状态: " + fenceStatus);
                    if (fenceNode.has("distance")) {
                        result.setDistance(fenceNode.get("distance").asDouble());
                    }
                }
            } else {
                result.setInFence(false);
                result.setMessage("API返回错误: " + info);
            }
        } catch (Exception e) {
            result.setInFence(false);
            result.setMessage("检查围栏状态时发生错误: " + e.getMessage());
        }
        return result;
    }
    /**
     * 检查点是否在行政区划内
     */
    public GeoFenceResult checkInDistrict(GeoPoint point, String adcode) throws IOException {
        GeoFenceResult result = new GeoFenceResult();
        result.setLocation(point);
        result.setFenceType(GeoFenceType.DISTRICT);
        // 调用逆地理编码API获取行政区划信息
        Map<String, String> params = new HashMap<>();
        params.put("key", apiKey);
        params.put("location", point.toLngLatString());
        params.put("extensions", "base");
        params.put("poitype", "");
        String url = buildUrl("https://restapi.amap.com/v3/geocode/regeo", params);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                result.setInFence(false);
                result.setMessage("API请求失败: " + response.code());
                return result;
            }
            String responseBody = response.body().string();
            JsonNode rootNode = objectMapper.readTree(responseBody);
            String status = rootNode.get("status").asText();
            String info = rootNode.get("info").asText();
            if ("1".equals(status)) {
                JsonNode regeocode = rootNode.get("regeocode");
                if (regeocode != null) {
                    JsonNode addressComponent = regeocode.get("addressComponent");
                    if (addressComponent != null) {
                        String districtAdcode = addressComponent.get("adcode").asText();
                        result.setInFence(adcode.equals(districtAdcode));
                        result.setMessage("行政区划代码: " + districtAdcode);
                        result.setFenceId(adcode);
                        result.setFenceName(addressComponent.get("district").asText());
                    }
                }
            } else {
                result.setInFence(false);
                result.setMessage("API返回错误: " + info);
            }
        } catch (Exception e) {
            result.setInFence(false);
            result.setMessage("检查行政区划时发生错误: " + e.getMessage());
        }
        return result;
    }
    /**
     * 批量检查多个围栏
     */
    public List<GeoFenceResult> checkMultipleFences(GeoPoint point, List<GeoFence> fences) {
        List<GeoFenceResult> results = new ArrayList<>();
        for (GeoFence fence : fences) {
            try {
                GeoFenceResult result;
                if (fence.getType() == GeoFenceType.CIRCLE) {
                    result = checkInCircleFence(point, fence);
                } else if (fence.getType() == GeoFenceType.POLYGON) {
                    result = checkInPolygonFence(point, fence);
                } else {
                    result = new GeoFenceResult();
                    result.setInFence(false);
                    result.setMessage("不支持的围栏类型");
                }
                results.add(result);
            } catch (IOException e) {
                GeoFenceResult errorResult = new GeoFenceResult();
                errorResult.setInFence(false);
                errorResult.setFenceId(fence.getId());
                errorResult.setFenceName(fence.getName());
                errorResult.setMessage("检查失败: " + e.getMessage());
                results.add(errorResult);
            }
        }
        return results;
    }
    /**
     * 创建并缓存地理围栏
     */
    public void createGeoFence(GeoFence fence) {
        geoFenceCache.put(fence.getId(), fence);
    }
    /**
     * 从缓存获取地理围栏
     */
    public GeoFence getGeoFence(String fenceId) {
        return geoFenceCache.get(fenceId);
    }
    /**
     * 通过高德地图行政区域查询API获取边界坐标
     */
    public GeoFence getDistrictPolygon(String adcode) throws IOException {
        Map<String, String> params = new HashMap<>();
        params.put("key", apiKey);
        params.put("keywords", adcode);
        params.put("subdistrict", "0");  // 不获取下级行政区
        params.put("extensions", "all"); // 获取边界坐标
        String url = buildUrl("https://restapi.amap.com/v3/config/district", params);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response.code());
            }
            String responseBody = response.body().string();
            JsonNode rootNode = objectMapper.readTree(responseBody);
            String status = rootNode.get("status").asText();
            String info = rootNode.get("info").asText();
            if ("1".equals(status)) {
                JsonNode districts = rootNode.get("districts");
                if (districts != null && districts.size() > 0) {
                    JsonNode district = districts.get(0);
                    GeoFence fence = new GeoFence(
                        adcode,
                        district.get("name").asText(),
                        GeoFenceType.POLYGON
                    );
                    // 解析多边形坐标
                    String polyline = district.get("polyline").asText();
                    parsePolylineString(polyline, fence);
                    return fence;
                }
            }
            throw new IOException("获取行政区划失败: " + info);
        }
    }
    /**
     * 解析多边形坐标字符串
     */
    private void parsePolylineString(String polyline, GeoFence fence) {
        // 格式示例: "116.32,39.91|116.33,39.92|116.34,39.93"
        String[] points = polyline.split("\\|");
        for (String point : points) {
            String[] coords = point.split(",");
            if (coords.length >= 2) {
                double lng = Double.parseDouble(coords[0].trim());
                double lat = Double.parseDouble(coords[1].trim());
                fence.addPoint(lng, lat);
            }
        }
    }
    /**
     * 计算两点间距离(Haversine公式)
     */
    public double calculateDistance(GeoPoint p1, GeoPoint p2) {
        final double EARTH_RADIUS = 6371000.0; // 地球平均半径(米)
        double lat1 = Math.toRadians(p1.getLatitude());
        double lon1 = Math.toRadians(p1.getLongitude());
        double lat2 = Math.toRadians(p2.getLatitude());
        double lon2 = Math.toRadians(p2.getLongitude());
        double dlat = lat2 - lat1;
        double dlon = lon2 - lon1;
        double a = Math.sin(dlat / 2) * Math.sin(dlat / 2) +
                   Math.cos(lat1) * Math.cos(lat2) *
                   Math.sin(dlon / 2) * Math.sin(dlon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return EARTH_RADIUS * c;
    }
    /**
     * 构建多边形字符串
     */
    private String buildPolygonString(List<GeoPoint> points) {
        StringBuilder sb = new StringBuilder();
        for (GeoPoint point : points) {
            sb.append(point.getLongitude()).append(",").append(point.getLatitude()).append("|");
        }
        // 移除最后一个竖线
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }
    /**
     * 构建URL
     */
    private String buildUrl(String baseUrl, Map<String, String> params) {
        StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?");
        params.forEach((key, value) -> {
            urlBuilder.append(key).append("=").append(value).append("&");
        });
        urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        return urlBuilder.toString();
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/AmapWalkingRouteUtil.java
New file
@@ -0,0 +1,336 @@
package com.xiaopeng.map.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * 高德地图步行导航工具类
 */
public class AmapWalkingRouteUtil {
    // 高德地图Web服务API配置
    private static final String AMAP_API_BASE_URL = "https://restapi.amap.com/v3/direction/walking";
    private static final String AMAP_GEOCODING_URL = "https://restapi.amap.com/v3/geocode/geo";
    // 你的高德地图API Key(请替换成你自己的)
    private final String apiKey;
    // HTTP客户端
    private final OkHttpClient httpClient;
    private final ObjectMapper objectMapper;
    /**
     * 构造函数
     * @param apiKey 高德地图API Key
     */
    public AmapWalkingRouteUtil(String apiKey) {
        this.apiKey = apiKey;
        this.httpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .build();
        this.objectMapper = new ObjectMapper();
    }
    /**
     * 位置坐标类
     */
    public static class Location {
        private final double longitude; // 经度
        private final double latitude;  // 纬度
        public Location(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }
        public double getLongitude() {
            return longitude;
        }
        public double getLatitude() {
            return latitude;
        }
        @Override
        public String toString() {
            return String.format("%.6f,%.6f", longitude, latitude);
        }
    }
    /**
     * 步行路线结果类
     */
    public static class WalkingRouteResult {
        private int duration;      // 步行时间(秒)
        private int distance;      // 步行距离(米)
        private String origin;     // 起点地址
        private String destination; // 终点地址
        private boolean success;   // 是否成功
        private String message;    // 返回信息
        // getters and setters
        public int getDuration() {
            return duration;
        }
        public void setDuration(int duration) {
            this.duration = duration;
        }
        public int getDistance() {
            return distance;
        }
        public void setDistance(int distance) {
            this.distance = distance;
        }
        public String getOrigin() {
            return origin;
        }
        public void setOrigin(String origin) {
            this.origin = origin;
        }
        public String getDestination() {
            return destination;
        }
        public void setDestination(String destination) {
            this.destination = destination;
        }
        public boolean isSuccess() {
            return success;
        }
        public void setSuccess(boolean success) {
            this.success = success;
        }
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
        /**
         * 获取格式化的步行时间
         * @return 格式化的时间字符串(如:15分钟30秒)
         */
        public String getFormattedDuration() {
            int hours = duration / 3600;
            int minutes = (duration % 3600) / 60;
            int seconds = duration % 60;
            if (hours > 0) {
                return String.format("%d小时%d分钟", hours, minutes);
            }  else {
                return String.format("%d秒", seconds);
            }
        }
        /**
         * 获取格式化的步行距离
         * @return 格式化的距离字符串(如:1.2公里)
         */
        public String getFormattedDistance() {
            if (distance >= 1000) {
                double km = distance / 1000.0;
                return String.format("%.1f公里", km);
            } else {
                return String.format("%d米", distance);
            }
        }
    }
    /**
     * 根据地址获取经纬度坐标
     * @param address 地址信息
     * @return 经纬度坐标
     */
    public Location getLocationByAddress(String address) throws IOException {
        Map<String, String> params = new HashMap<>();
        params.put("key", apiKey);
        params.put("address", address);
        String url = buildUrl(AMAP_GEOCODING_URL, params);
        Request request = new Request.Builder().url(url).build();
        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response);
            }
            String responseBody = response.body().string();
            JsonNode rootNode = objectMapper.readTree(responseBody);
            if ("1".equals(rootNode.get("status").asText())) {
                JsonNode geocodes = rootNode.get("geocodes");
                if (geocodes != null && geocodes.size() > 0) {
                    String locationStr = geocodes.get(0).get("location").asText();
                    String[] locationArray = locationStr.split(",");
                    double longitude = Double.parseDouble(locationArray[0]);
                    double latitude = Double.parseDouble(locationArray[1]);
                    return new Location(longitude, latitude);
                }
            }
            throw new IOException("地址解析失败: " + rootNode.get("info").asText());
        }
    }
    /**
     * 获取步行路线和时间
     * @param origin 起点坐标
     * @param destination 终点坐标
     * @return 步行路线结果
     */
    public WalkingRouteResult getWalkingRoute(Location origin, Location destination) {
        WalkingRouteResult result = new WalkingRouteResult();
        try {
            Map<String, String> params = new HashMap<>();
            params.put("key", apiKey);
            params.put("origin", origin.toString());
            params.put("destination", destination.toString());
            String url = buildUrl(AMAP_API_BASE_URL, params);
            Request request = new Request.Builder().url(url).build();
            try (Response response = httpClient.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    result.setSuccess(false);
                    result.setMessage("HTTP请求失败: " + response.code());
                    return result;
                }
                String responseBody = response.body().string();
                JsonNode rootNode = objectMapper.readTree(responseBody);
                String status = rootNode.get("status").asText();
                String info = rootNode.get("info").asText();
                if ("1".equals(status)) {
                    JsonNode route = rootNode.get("route");
                    JsonNode paths = route.get("paths");
                    if (paths != null && paths.size() > 0) {
                        JsonNode firstPath = paths.get(0);
                        int duration = firstPath.get("duration").asInt();
                        int distance = firstPath.get("distance").asInt();
                        result.setDuration(duration);
                        result.setDistance(distance);
                        result.setSuccess(true);
                        result.setMessage("成功获取步行路线");
                        // 尝试获取地址信息
                        result.setOrigin(getAddressByLocation(origin));
                        result.setDestination(getAddressByLocation(destination));
                    } else {
                        result.setSuccess(false);
                        result.setMessage("未找到步行路线");
                    }
                } else {
                    result.setSuccess(false);
                    result.setMessage("API返回错误: " + info);
                }
            }
        } catch (Exception e) {
            result.setSuccess(false);
            result.setMessage("获取步行路线失败: " + e.getMessage());
        }
        return result;
    }
    /**
     * 根据经纬度获取地址(逆地理编码)
     * @param location 经纬度坐标
     * @return 地址信息
     */
    private String getAddressByLocation(Location location) {
        try {
            Map<String, String> params = new HashMap<>();
            params.put("key", apiKey);
            params.put("location", location.toString());
            String url = buildUrl("https://restapi.amap.com/v3/geocode/regeo", params);
            Request request = new Request.Builder().url(url).build();
            try (Response response = httpClient.newCall(request).execute()) {
                if (response.isSuccessful()) {
                    String responseBody = response.body().string();
                    JsonNode rootNode = objectMapper.readTree(responseBody);
                    if ("1".equals(rootNode.get("status").asText())) {
                        JsonNode regeocode = rootNode.get("regeocode");
                        if (regeocode != null) {
                            return regeocode.get("formatted_address").asText();
                        }
                    }
                }
            }
        } catch (Exception e) {
            // 地址解析失败时返回坐标
        }
        return location.toString();
    }
    /**
     * 构建带参数的URL
     * @param baseUrl 基础URL
     * @param params 参数Map
     * @return 完整的URL
     */
    private String buildUrl(String baseUrl, Map<String, String> params) {
        StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?");
        params.forEach((key, value) -> {
            urlBuilder.append(key).append("=").append(value).append("&");
        });
        // 删除最后一个&
        urlBuilder.deleteCharAt(urlBuilder.length() - 1);
        return urlBuilder.toString();
    }
    /**
     * 计算两点间的直线距离(仅供参考,不是实际步行距离)
     * @param loc1 坐标1
     * @param loc2 坐标2
     * @return 直线距离(米)
     */
    public static double calculateStraightDistance(Location loc1, Location loc2) {
        double earthRadius = 6371000; // 地球半径(米)
        double lat1 = Math.toRadians(loc1.getLatitude());
        double lat2 = Math.toRadians(loc2.getLatitude());
        double deltaLat = Math.toRadians(loc2.getLatitude() - loc1.getLatitude());
        double deltaLon = Math.toRadians(loc2.getLongitude() - loc1.getLongitude());
        double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
                   Math.cos(lat1) * Math.cos(lat2) *
                   Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return earthRadius * c;
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/ElectricFenceUtil.java
New file
@@ -0,0 +1,126 @@
package com.xiaopeng.map.utils;
import java.util.ArrayList;
import java.util.List;
/**
 * 电子围栏工具类
 */
public class ElectricFenceUtil {
    /**
     *
     * @param coordinate  电子围栏 20,20:30,20
     *  @param locations  当前点位
     * @return
     */
    public static boolean monitorElectricFenc(String coordinate, String locations){
        return areaMonitoring(coordinate, locations);
    }
    public static boolean areaMonitoring(String coordinate, String lonLat){
        boolean flag = false;
        String[] split = coordinate.split(";");
        List<Double> pointsX = new ArrayList<>();
        List<Double> pointsY = new ArrayList<>();
        for (String s : split) {
            pointsX.add(Double.valueOf(s.split(",")[0]));
            pointsY.add(Double.valueOf(s.split(",")[1]));
        }
        String lon1 = lonLat.split(",")[0];
        String lat1 = lonLat.split(",")[1];
        boolean inPolygon = isInPolygon(Double.valueOf(lon1), Double.valueOf(lat1), pointsX, pointsY);
        if (inPolygon){
            flag = true;
        }
        return flag;
    }
    /**
     //     * 判断点是否在多边形内
     //     * @param px 测试点
     //     * @param  polygonXA  多边形的点
     //     * @return boolean true:在多边形内, false:在多边形外
     //     * @throws
     //     * @Title: IsPointInPoly
     //     */
    public static boolean isInPolygon(double px, double py,List<Double> polygonXA, List<Double> polygonYA) {
        boolean isInside = false;
        double ESP = 1e-9;
        int count = 0;
        double linePoint1x;
        double linePoint1y;
        double linePoint2x = 180;
        double linePoint2y;
        linePoint1x = px;
        linePoint1y = py;
        linePoint2y = py;
        for (int i = 0; i < polygonXA.size() - 1; i++) {
            double cx1 = polygonXA.get(i);
            double cy1 = polygonYA.get(i);
            double cx2 = polygonXA.get(i + 1);
            double cy2 = polygonYA.get(i + 1);
            if (isPointOnLine(px, py, cx1, cy1, cx2, cy2)) {
                return true;
            }
            if (Math.abs(cy2 - cy1) < ESP) {
                continue;
            }
            if (isPointOnLine(cx1, cy1, linePoint1x, linePoint1y, linePoint2x,
                    linePoint2y)) {
                if (cy1 > cy2)
                    count++;
            } else if (isPointOnLine(cx2, cy2, linePoint1x, linePoint1y,
                    linePoint2x, linePoint2y)) {
                if (cy2 > cy1)
                    count++;
            } else if (isIntersect(cx1, cy1, cx2, cy2, linePoint1x,
                    linePoint1y, linePoint2x, linePoint2y)) {
                count++;
            }
        }
        if (count % 2 == 1) {
            isInside = true;
        }
        return isInside;
    }
    public static double Multiply(double px0, double py0, double px1, double py1,
                           double px2, double py2) {
        return ((px1 - px0) * (py2 - py0) - (px2 - px0) * (py1 - py0));
    }
    public static boolean isPointOnLine(double px0, double py0, double px1,
                                 double py1, double px2, double py2) {
        boolean flag = false;
        double ESP = 1e-9;
        if ((Math.abs(Multiply(px0, py0, px1, py1, px2, py2)) < ESP)
                && ((px0 - px1) * (px0 - px2) <= 0)
                && ((py0 - py1) * (py0 - py2) <= 0)) {
            flag = true;
        }
        return flag;
    }
    public static boolean isIntersect(double px1, double py1, double px2, double py2,
                               double px3, double py3, double px4, double py4) {
        boolean flag = false;
        double d = (px2 - px1) * (py4 - py3) - (py2 - py1) * (px4 - px3);
        if (d != 0) {
            double r = ((py1 - py3) * (px4 - px3) - (px1 - px3) * (py4 - py3))
                    / d;
            double s = ((py1 - py3) * (px2 - px1) - (px1 - px3) * (py2 - py1))
                    / d;
            if ((r >= 0) && (r <= 1) && (s >= 0) && (s <= 1)) {
                flag = true;
            }
        }
        return flag;
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/GDMapGeocodingUtil.java
File was renamed from xp-service/xp-operation/src/main/java/com/xiaopeng/operation/utils/map/GDMapGeocodingUtil.java
@@ -1,8 +1,11 @@
package com.xiaopeng.operation.utils.map;
package com.xiaopeng.map.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.map.utils.mangoUtils.MongoGeoFenceUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@@ -23,6 +26,9 @@
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private MongoGeoFenceUtil mongoGeoFenceUtil;
    /**
@@ -125,4 +131,69 @@
        return map;
    }
    /**
     * 根据起始点获取驾车距离 和 时间  以及避让区域
     *
     * @return
     * @throws Exception
     */
    public RouteVo getDistanceAndTime(LonLatDto start, LonLatDto end,List<LonLatDto> avoidRoads,String fenceId)  {
        RouteVo routeVo = new RouteVo();
        String url = "https://tsapi.amap.com/v1/route/price?key=" + key + "&extensions=all&start=" + start.getLongitude() + "," + start.getLatitude();
        HashMap<String, Object> params = new HashMap<>();
        params.put("extensions", "all");
        params.put("start", start.getLongitude() + "," + start.getLatitude());
        params.put("end", end.getLongitude() + "," + end.getLatitude());
        params.put("routeUserType",2);
        params.put("voidPolygons", JSON.toJSONString(avoidRoads));
        String forObject = restTemplate.getForObject(url, String.class, params);
        JSONObject jsonObject = JSON.parseObject(forObject);
        String data = jsonObject.getString("data");
        JSONObject jsonObject1 = JSON.parseObject(data);
        String routeID = jsonObject1.getString("routeID");
        JSONArray path = jsonObject1.getJSONArray("path");
        for (Object o : path) {
            JSONObject jsonObject2 = (JSONObject) o;
            String points = jsonObject2.getString("points");
            String[] split = points.split(";");
            int temp=0;
            // 取5分之一的点位
            for (int i = 0; i < split.length; i++) {
                if (i % 5 == 0) {
                    String[] split1 = split[i].split(",");
                    LonLatDto lonLatDto = new LonLatDto(Double.parseDouble(split1[0]), Double.parseDouble(split1[1]));
                    if (!mongoGeoFenceUtil.isInFence(lonLatDto.getLongitude(), lonLatDto.getLatitude(), fenceId, "operating_area")) {
                        temp++;
                        break;
                    }
                }
            }
            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);
                return routeVo;
            }
        }
        return null;
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/utils/mangoUtils/MongoGeoFenceUtil.java
New file
@@ -0,0 +1,604 @@
package com.xiaopeng.map.utils.mangoUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.client.*;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.geojson.Point;
import com.mongodb.client.model.geojson.Position;
import org.bson.Document;
import org.bson.conversions.Bson;
import java.util.*;
/**
 * MongoDB地理空间查询工具类
 */
public class MongoGeoFenceUtil {
    private final MongoClient mongoClient;
    private final MongoDatabase database;
    private final ObjectMapper objectMapper;
    /**
     * 地理坐标点
     */
    public static class GeoPoint {
        private final double longitude;
        private final double latitude;
        public GeoPoint(double longitude, double latitude) {
            this.longitude = longitude;
            this.latitude = latitude;
        }
        public double getLongitude() {
            return longitude;
        }
        public double getLatitude() {
            return latitude;
        }
        public Position toPosition() {
            return new Position(longitude, latitude);
        }
        public Point toGeoJsonPoint() {
            return new Point(new Position(longitude, latitude));
        }
        public Document toDocument() {
            return new Document()
                    .append("type", "Point")
                    .append("coordinates", Arrays.asList(longitude, latitude));
        }
        @Override
        public String toString() {
            return String.format("[%.6f, %.6f]", longitude, latitude);
        }
    }
    /**
     * 电子围栏类型
     */
    public enum FenceType {
        CIRCLE,      // 圆形
        POLYGON,     // 多边形
        LINESTRING,  // 线形
        MULTI_POLYGON // 多个多边形
    }
    /**
     * 电子围栏
     */
    public static class GeoFence {
        private String id;
        private String name;
        private FenceType type;
        private List<GeoPoint> coordinates;
        private GeoPoint center;
        private double radius; // 半径(米)
        private Map<String, Object> properties;
        public GeoFence(String id, String name, FenceType type) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.coordinates = new ArrayList<>();
            this.properties = new HashMap<>();
        }
        // Getters and Setters
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public FenceType getType() {
            return type;
        }
        public void setType(FenceType type) {
            this.type = type;
        }
        public List<GeoPoint> getCoordinates() {
            return coordinates;
        }
        public void setCoordinates(List<GeoPoint> coordinates) {
            this.coordinates = coordinates;
        }
        public GeoPoint getCenter() {
            return center;
        }
        public void setCenter(GeoPoint center) {
            this.center = center;
        }
        public double getRadius() {
            return radius;
        }
        public void setRadius(double radius) {
            this.radius = radius;
        }
        public Map<String, Object> getProperties() {
            return properties;
        }
        public void setProperties(Map<String, Object> properties) {
            this.properties = properties;
        }
        public void addCoordinate(GeoPoint point) {
            this.coordinates.add(point);
        }
        public void addCoordinate(double lng, double lat) {
            this.coordinates.add(new GeoPoint(lng, lat));
        }
        public void addProperty(String key, Object value) {
            this.properties.put(key, value);
        }
    }
    public MongoGeoFenceUtil(String connectionString, String databaseName) {
        this.mongoClient = MongoClients.create(connectionString);
        this.database = mongoClient.getDatabase(databaseName);
        this.objectMapper = new ObjectMapper();
    }
    /**
     * 创建地理空间索引
     */
    public void create2dSphereIndex(String collectionName, String fieldName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        collection.createIndex(new Document(fieldName, "2dsphere"));
        System.out.println("创建2dsphere索引成功: " + collectionName + "." + fieldName);
    }
    /**
     * 创建2d索引(平面坐标)
     */
    public void create2dIndex(String collectionName, String fieldName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        collection.createIndex(new Document(fieldName, "2d"));
        System.out.println("创建2d索引成功: " + collectionName + "." + fieldName);
    }
    /**
     * 保存电子围栏到数据库
     */
    public void saveGeoFence(String collectionName, GeoFence fence) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        Document geometry = createGeometryDocument(fence);
        Document fenceDoc = new Document()
                .append("_id", fence.getId())
                .append("name", fence.getName())
                .append("type", fence.getType().name())
                .append("geometry", geometry)
                .append("properties", fence.getProperties())
                .append("createdAt", new Date())
                .append("updatedAt", new Date());
        // 添加中心点用于快速查询
        if (fence.getCenter() != null) {
            fenceDoc.append("center", fence.getCenter().toDocument());
        }
        if (fence.getRadius() > 0) {
            fenceDoc.append("radius", fence.getRadius());
        }
        collection.insertOne(fenceDoc);
        System.out.println("保存围栏成功: " + fence.getName());
    }
    /**
     * 创建几何图形文档
     */
    private Document createGeometryDocument(GeoFence fence) {
        Document geometry = new Document();
        switch (fence.getType()) {
            case POLYGON:
                geometry.append("type", "Polygon");
                List<List<Position>> polygonPositions = new ArrayList<>();
                List<Position> positions = new ArrayList<>();
                for (GeoPoint point : fence.getCoordinates()) {
                    positions.add(point.toPosition());
                }
                // 闭合多边形:最后一个点等于第一个点
                if (!positions.isEmpty() && !positions.get(0).equals(positions.get(positions.size() - 1))) {
                    positions.add(positions.get(0));
                }
                polygonPositions.add(positions);
                geometry.append("coordinates", polygonPositions);
                break;
            case CIRCLE:
                // MongoDB原生不支持圆形,使用多边形近似
                geometry.append("type", "Polygon");
                List<List<Position>> circlePositions = new ArrayList<>();
                List<Position> circlePoints = new ArrayList<>();
                int segments = 36; // 36边形近似圆形
                for (int i = 0; i < segments; i++) {
                    double angle = 2 * Math.PI * i / segments;
                    double lat = fence.getCenter().getLatitude() +
                            (fence.getRadius() / 111320.0) * Math.cos(angle); // 1度≈111320米
                    double lng = fence.getCenter().getLongitude() +
                            (fence.getRadius() / (111320.0 * Math.cos(Math.toRadians(fence.getCenter().getLatitude())))) * Math.sin(angle);
                    circlePoints.add(new Position(lng, lat));
                }
                // 闭合
                if (!circlePoints.isEmpty()) {
                    circlePoints.add(circlePoints.get(0));
                }
                circlePositions.add(circlePoints);
                geometry.append("coordinates", circlePositions);
                break;
            case LINESTRING:
                geometry.append("type", "LineString");
                List<Position> linePositions = new ArrayList<>();
                for (GeoPoint point : fence.getCoordinates()) {
                    linePositions.add(point.toPosition());
                }
                geometry.append("coordinates", linePositions);
                break;
            default:
                throw new IllegalArgumentException("不支持的围栏类型: " + fence.getType());
        }
        return geometry;
    }
    /**
     * 方法1:使用$geoWithin判断点是否在多边形内
     */
    public boolean isPointInPolygonGeoWithin(GeoPoint point, String fenceId, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        // 获取围栏的多边形数据
        Document fence = collection.find(Filters.eq("_id", fenceId)).first();
        if (fence == null) {
            throw new IllegalArgumentException("围栏不存在: " + fenceId);
        }
        // 创建查询:点是否在多边形内
        Bson filter = Filters.and(
                Filters.eq("_id", fenceId),
                Filters.geoWithin(
                        "geometry",
                        point.toGeoJsonPoint()
                )
        );
        long count = collection.countDocuments(filter);
        return count > 0;
    }
    /**
     * 方法2:使用$geoIntersects判断点是否与几何图形相交
     */
    public boolean isPointInPolygonGeoIntersects(GeoPoint point, String fenceId, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        Bson filter = Filters.and(
                Filters.eq("_id", fenceId),
                Filters.geoIntersects("geometry", point.toGeoJsonPoint())
        );
        long count = collection.countDocuments(filter);
        return count > 0;
    }
    /**
     * 方法3:查找包含指定点的所有围栏(支持自定义地理字段名称)
     */
    public List<String> findFencesContainingPoint(GeoPoint point, String collectionName, String geoFieldName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        List<String> fenceIds = new ArrayList<>();
        // 查询所有包含该点的围栏
        Bson filter = Filters.geoIntersects(geoFieldName, point.toGeoJsonPoint());
        collection.find(filter).forEach(doc -> {
            fenceIds.add( doc.getString("_id"));
        });
        return fenceIds;
    }
    public List<Document> findPointsInFence(String fenceId,
                                            String pointsCollectionName,
                                            String fencesCollectionName) {
        // 1. 从围栏集合获取围栏的几何数据
        Document fence = getFenceGeometry(fenceId, fencesCollectionName);
        if (fence == null) {
            throw new IllegalArgumentException("围栏不存在: " + fenceId);
        }
        // 2. 构建地理空间查询
        Bson filter = Filters.geoWithin("location", fence.get("geometry", Document.class));
        // 3. 执行查询
        MongoCollection<Document> pointsCollection = database.getCollection(pointsCollectionName);
        List<Document> results = new ArrayList<>();
        try (MongoCursor<Document> cursor = pointsCollection.find(filter).iterator()) {
            while (cursor.hasNext()) {
                results.add(cursor.next());
            }
        }
        return results;
    }
    /**
     * 获取围栏的几何数据
     */
    private Document getFenceGeometry(String fenceId, String fencesCollectionName) {
        MongoCollection<Document> fencesCollection = database.getCollection(fencesCollectionName);
        return fencesCollection.find(Filters.eq("_id", fenceId)).first();
    }
    /**
     * 方法4:使用聚合管道进行复杂的地理查询
     */
    public Map<String, Object> analyzePointInFences(GeoPoint point, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        List<Document> pipeline = Arrays.asList(
                // 阶段1:筛选包含该点的围栏
                new Document("$match",
                        new Document("geometry",
                                new Document("$geoIntersects",
                                        new Document("$geometry", point.toDocument())
                                )
                        )
                ),
                // 阶段2:投影所需字段
                new Document("$project",
                        new Document("_id", 1)
                                .append("name", 1)
                                .append("type", 1)
                                .append("properties", 1)
                                .append("area", 1)
                ),
                // 阶段3:统计
                new Document("$group",
                        new Document("_id", null)
                                .append("totalFences", new Document("$sum", 1))
                                .append("fences", new Document("$push",
                                        new Document("id", "$_id")
                                                .append("name", "$name")
                                                .append("type", "$type")
                                ))
                )
        );
        Document result = collection.aggregate(pipeline).first();
        if (result != null) {
            return result;
        }
        return new Document("totalFences", 0)
                .append("fences", new ArrayList<>());
    }
    /**
     * 方法5:查找距离点最近的围栏
     */
    public Document findNearestFence(GeoPoint point, String collectionName, int maxDistance) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        List<Document> pipeline = Arrays.asList(
                // 阶段1:添加距离字段
                new Document("$geoNear",
                        new Document("near", point.toDocument())
                                .append("distanceField", "distance")
                                .append("spherical", true)
                                .append("maxDistance", maxDistance)
                                .append("query", new Document("type", "Polygon")) // 只查询多边形
                                .append("key", "geometry")
                ),
                // 阶段2:按距离排序
                new Document("$sort", new Document("distance", 1)),
                // 阶段3:限制返回数量
                new Document("$limit", 1)
        );
        return collection.aggregate(pipeline).first();
    }
    /**
     * 方法7:批量判断多个点是否在围栏内
     */
    public Map<GeoPoint, List<String>> batchCheckPointsInFences(List<GeoPoint> points, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        Map<GeoPoint, List<String>> result = new HashMap<>();
        for (GeoPoint point : points) {
            Bson filter = Filters.geoIntersects("geometry", point.toGeoJsonPoint());
            List<String> fenceNames = new ArrayList<>();
            collection.find(filter).forEach(doc -> {
                fenceNames.add(doc.getString("name"));
            });
            result.put(point, fenceNames);
        }
        return result;
    }
    /**
     * 查找与指定点距离在一定范围内的围栏
     */
    public List<Document> findFencesNearPoint(GeoPoint point, double maxDistanceMeters, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        // 将米转换为弧度(适用于球面几何)
        double maxDistanceRadians = maxDistanceMeters / 6378100; // 地球半径
        Bson filter = Filters.nearSphere("geometry", point.toGeoJsonPoint(), maxDistanceRadians, 0.0);
        List<Document> results = new ArrayList<>();
        collection.find(filter).into(results);
        return results;
    }
    /**
     * 创建圆形围栏(使用$centerSphere)
     */
    public void createCircleFenceUsingCenterSphere(String fenceId, String name, GeoPoint center,
                                                   double radiusMeters, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        // 将米转换为弧度
        double radiusRadians = radiusMeters / 6378100;
        Document fenceDoc = new Document()
                .append("_id", fenceId)
                .append("name", name)
                .append("type", "Circle")
                .append("center", Arrays.asList(center.getLongitude(), center.getLatitude()))
                .append("radius", radiusRadians) // 以弧度存储
                .append("radiusMeters", radiusMeters)
                .append("geometry", new Document()
                        .append("type", "Point")
                        .append("coordinates", Arrays.asList(center.getLongitude(), center.getLatitude())))
                .append("createdAt", new Date());
        collection.insertOne(fenceDoc);
    }
    /**
     * 使用$centerSphere查询圆形区域内的点
     */
    public boolean isPointInCircleUsingCenterSphere(GeoPoint point, String fenceId, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        Document fence = collection.find(Filters.eq("_id", fenceId)).first();
        if (fence == null) return false;
        List<Double> center = (List<Double>) fence.get("center");
        double radius = fence.getDouble("radius");
        Bson filter = Filters.geoWithinCenterSphere(
                "location", // 假设点存储在location字段
                center.get(0), // 经度
                center.get(1), // 纬度
                radius
        );
        // 查找该点是否在圆形内
        MongoCollection<Document> pointsCollection = database.getCollection("tracking_points");
        Document pointDoc = pointsCollection.find(
                Filters.and(
                        Filters.eq("location.coordinates",
                                Arrays.asList(point.getLongitude(), point.getLatitude())),
                        filter
                )
        ).first();
        return pointDoc != null;
    }
    /**
     * 关闭连接
     */
    public void close() {
        if (mongoClient != null) {
            mongoClient.close();
        }
    }
    /**
     * 查找指定父围栏ID的禁行区域点位
     * @param parentId 父围栏ID
     * @param parentIdField 父围栏ID字段名
     * @param collectionName 集合名称
     * @return 禁行区域点位列表
     */
    public List<Document> findPointsByParentId(String parentId, String parentIdField, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        List<Document> points = new ArrayList<>();
        // 查询父围栏ID等于指定值的所有文档
        Bson filter = Filters.eq(parentIdField, parentId);
        collection.find(filter).forEach(doc -> {
            points.add(doc);
        });
        return points;
    }
    /**
     * 根据围栏ID获取电子围栏点位
     * @param fenceId 围栏ID
     * @param collectionName 集合名称
     * @return 电子围栏点位
     */
    public Document getFenceById(String fenceId, String collectionName) {
        MongoCollection<Document> collection = database.getCollection(collectionName);
        return collection.find(Filters.eq("_id", fenceId)).first();
    }
    /**
     * 根据经纬度点位判断是否在指定的电子围栏中
     * @param longitude 经度
     * @param latitude 纬度
     * @param fenceId 围栏ID
     * @param collectionName 集合名称
     * @return 是否在围栏中
     */
    public boolean isInFence(double longitude, double latitude, String fenceId, String collectionName) {
        // 创建GeoPoint对象
        MongoGeoFenceUtil.GeoPoint geoPoint = new MongoGeoFenceUtil.GeoPoint(longitude, latitude);
        // 调用MongoGeoFenceUtil的方法查询包含该点的围栏
        List<String> fences = findFencesContainingPoint(geoPoint, collectionName, "points");
        // 检查查询结果中是否包含指定的fenceId
        return fences.contains(fenceId);
    }
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/IsInOperationAreaVo.java
New file
@@ -0,0 +1,13 @@
package com.xiaopeng.map.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "IsInOperationAreaVo对象", description = "判断是否在运营区域")
public class IsInOperationAreaVo {
    @ApiModelProperty(value = "是否在运营区域  \"inside\" — 在围栏内(允许服务) \"outside\" — 在围栏外(禁止服务)")
    private String status="outsize";
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/WalkTimeVo.java
New file
@@ -0,0 +1,19 @@
package com.xiaopeng.map.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("步行时间和距离Vo")
public class WalkTimeVo {
    @ApiModelProperty("步行时间")
    private String walkTime;
    @ApiModelProperty("步行距离")
    private Integer walkDistance;
    @ApiModelProperty("状态 0不可下单 1可下单")
    private Integer status=0;
}
xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/XpGeoFencingPolygon.java
File was renamed from xp-service/xp-map/src/main/java/com/xiaopeng/map/vo/GeoFencingPolygon.java
@@ -14,23 +14,26 @@
 * @date 2023/4/29 23:53
 */
@Data
@Document(collection = "geo_fencing")
public class GeoFencingPolygon implements Serializable {
@Document(collection = "operating_area")
public class XpGeoFencingPolygon implements Serializable {
    private static final long serialVersionUID = 4508868382007529971L;
    private String id;
    /**
     * 站点id
     * 围栏id
     */
    private Integer siteId;
    private String fenceId;
    /**
     * 站点名称
     * 围栏名称
     */
    private String name;
    /**
     * 多边形经纬度
     */
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private GeoJsonPolygon geoJsonPolygon;
    private GeoJsonPolygon points;
}
xp-service/xp-operation/pom.xml
@@ -15,6 +15,14 @@
    </description>
    <dependencies>
        <dependency>
            <groupId>com.github.stuxuhai</groupId>
            <artifactId>jpinyin</artifactId>
            <version>1.1.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java
New file
@@ -0,0 +1,47 @@
package com.xiaopeng.operation.controller;
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.operation.service.XpPricingService;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
 * <p>
 * 费项管理 前端控制器
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
@Slf4j
@RestController
@RequestMapping("/base/v1/mini-program")
@Api(tags = "费用管理")
public class XpPricingController extends BaseController {
    private final XpPricingService xpPricingService;
    public XpPricingController(XpPricingService xpPricingService) {
        this.xpPricingService = xpPricingService;
    }
    @PostMapping("/navigation/route-and-fare")
    @ApiOperation(value = "导航-获取路线和费用",tags = {"小程序-获取路线和费用"},response = RouteAndFareVo.class)
    public AjaxResult<RouteAndFareVo> getRouteAndFare(@RequestBody @Valid RouteAndFareDto routeAndFareDto) {
        return success(xpPricingService.getRouteAndFare(routeAndFareDto));
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpPricingItemController.java
File was deleted
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/controller/XpSystemSettingController.java
@@ -1,5 +1,7 @@
package com.xiaopeng.operation.controller;
import com.xiaopeng.api.model.operation.SysOperLog;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.web.controller.BaseController;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import com.xiaopeng.operation.service.XpSystemSettingService;
@@ -7,9 +9,7 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -34,6 +34,18 @@
    }
//    @GetMapping("/support/getOrderDistance")
//    @ApiOperation(value = "获取下单距离",tags = {"小程序首页-服务调用获取下单距离"},response = Integer.class)
//    public AjaxResult<Integer> getOrderDistance() {
//        return success(xpSystemSettingService.getOrderDistance());
//    }
    @GetMapping("/support/getOrderDistance")
    @ApiOperation(value = "获取下单距离",tags = {"小程序首页-服务调用获取下单距离"},response = Integer.class)
    public R getOrderDistance() {
        return R.ok(xpSystemSettingService.getOrderDistance());
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/mapper/XpAgreementAppUserMapper.java
@@ -2,7 +2,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xiaopeng.api.model.operation.XpAgreementAppUser;
import com.xiaopeng.api.model.maintenance.XpAgreementAppUser;
/**
 * <p>
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpAgreementAppUserService.java
@@ -2,8 +2,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.maintenance.XpAgreementAppUser;
import com.xiaopeng.operation.dto.XpAgreementAppUserDto;
import com.xiaopeng.api.model.operation.XpAgreementAppUser;
/**
 * <p>
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpPricingService.java
@@ -3,6 +3,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
/**
 * <p>
@@ -14,4 +16,8 @@
 */
public interface XpPricingService extends IService<XpPricing> {
    RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto);
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/XpSystemSettingService.java
@@ -20,4 +20,12 @@
     */
    String getCustomerServicePhone();
    /**
     * 获取距离点X米内可下单
     * @return
     */
    Integer getOrderDistance();
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -4,9 +4,11 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.operation.dto.NearestDto;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.operation.mapper.XpPointMapper;
import com.xiaopeng.operation.mapper.XpSystemSettingMapper;
import com.xiaopeng.operation.query.PointMapQuery;
import com.xiaopeng.operation.service.XpPointService;
import com.xiaopeng.operation.utils.map.FastDistanceCalculator;
@@ -14,6 +16,7 @@
import com.xiaopeng.operation.vo.NearestVo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -31,6 +34,9 @@
@Service
public class XpPointServiceImpl extends ServiceImpl<XpPointMapper, XpPoint> implements XpPointService {
    @Resource
    private XpSystemSettingMapper xpSystemSettingMapper;
    @Override
@@ -39,10 +45,13 @@
        List<XpPoint> list = list(new LambdaQueryWrapper<XpPoint>().eq(XpPoint::getOperationStatus, 1));
        NearestVo nearestVo = new NearestVo();
        List<NearestNextVo> nearestNextVos = new ArrayList<>();
        XpSystemSetting xpSystemSetting = xpSystemSettingMapper.selectOne(null);
        Integer orderDistance = xpSystemSetting.getOrderDistance();
        for (XpPoint xpPoint : list) {
            // 计算距离 小于1000m的站点
            // 计算距离 小于的站点
            double v = FastDistanceCalculator.distanceFast(dto.getLatitude(), dto.getLongitude(), xpPoint.getPointLat(), xpPoint.getPointLon());
            if (v < 1000) {
            if (v < orderDistance) {
                NearestNextVo nearestNextVo = new NearestNextVo();
                nearestNextVo.setId(xpPoint.getId());
                nearestNextVo.setName(xpPoint.getPointName());
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java
@@ -1,11 +1,20 @@
package com.xiaopeng.operation.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.feignClient.map.XpMapClient;
import com.xiaopeng.api.vo.map.XpDistanceAndDurationVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.operation.mapper.XpPricingMapper;
import com.xiaopeng.api.model.operation.XpPricing;
import com.xiaopeng.operation.service.XpPricingService;
import com.xiaopeng.operation.utils.XpPricingUtil;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
 * <p>
@@ -18,4 +27,42 @@
@Service
public class XpPricingServiceImpl extends ServiceImpl<XpPricingMapper, XpPricing> implements XpPricingService {
    @Resource
    private XpPricingUtil xpPricingUtil;
    @Resource
    private XpMapClient xpMapClient;
    @Override
    public RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto) {
        XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId()));
        if(xpPricing==null){
            // 抛出异常
            throw new RuntimeException("当前区域未开通服务");
        }
        // 判断2个点是否在同一个经营区域
        R<Boolean> r = xpMapClient.diagnosisArea(routeAndFareDto);
        if(r.getCode()!=R.SUCCESS){
            // 抛出异常
            throw new RuntimeException(r.getMsg());
        }
        if(!r.getData()){
            // 抛出异常
            throw new RuntimeException("当前区域未开通服务");
        }
        // 调用地图
        R<XpDistanceAndDurationVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
        xpPricingUtil.calculationPrice(xpPricing.getBasicPricingJson(), xpPricing.getStartingFareJson(), routeAndFareDto.getDistance(), routeAndFareDto.getDuration(), routeAndFareDto.getWait(), routeAndFareDto.getWaitMoney())
        return null;
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/service/impl/XpSystemSettingServiceImpl.java
@@ -26,4 +26,13 @@
        }
        return "";
    }
    @Override
    public Integer getOrderDistance() {
        XpSystemSetting xpSystemSetting = this.getOne(null);
        if (xpSystemSetting != null) {
            return xpSystemSetting.getOrderDistance();
        }
        return 0;
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/utils/XpPricingUtil.java
@@ -1,9 +1,10 @@
package com.xiaopeng.operation.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.operation.service.XpPricingService;
import com.xiaopeng.operation.utils.map.GDMapElectricFenceUtil;
import com.xiaopeng.operation.utils.holiday.HolidayUtil;
import com.xiaopeng.operation.utils.map.GDMapGeocodingUtil;
import com.xiaopeng.operation.vo.XpPricingCardVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -18,140 +19,244 @@
public class XpPricingUtil {
    @Autowired
    private GDMapGeocodingUtil gdMapGeocodingUtil;
    @Autowired
    private GDMapElectricFenceUtil gdMapElectricFenceUtil;
    @Autowired
    private XpPricingService xpPricingService;
//    public void queryServerCarModel(String startLonLat, String endLonLat, Integer type) throws Exception {
//        //查找与起点匹配的企业(经营范围)
////        Map<String, String> geocode = gdMapGeocodingUtil.geocode(startLonLat.split(",")[0], startLonLat.split(",")[1]);
//
//
//        // TODO: 2023/11/4 无法修改
//        Map<String, String> distance = gdMapElectricFenceUtil.getDistance(startLonLat, endLonLat, 1);
//        if (null == distance) {
//            return;
//        }
//        String distance1 = distance.get("distance");//距离(米)
//        double distance1_ = Double.valueOf(distance1).doubleValue();
//        String duration = distance.get("duration");//时间(秒)
//        long duration_ = Long.valueOf(duration).longValue();
//
//
//        List<ServerCarModelWarpper> price = this.getPrice(query.getId(), distance1_, duration_, 0, 1);
//        //添加出租车车型和价格
//        ServerCarModelWarpper serverCarModelWarpper = this.getTextPrice(query.getId(), distance1_, duration_, 0, 2);
//        price.add(serverCarModelWarpper);
//        return ResultUtil.success(price);
//    }
//
//
//    /**
//     *
//     * @param distance 米
//     * @param duration 秒
//     * @param cityId
//     * @return
//     * @throws Exception
//     */
//    public ServerCarModelWarpper getPrice(double distance, long duration, Long cityId) throws Exception {
//        XpPricing xpPricing = xpPricingService.getOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, cityId));
//        if (null == xpPricing) {
//            return null;
//        }
//        BigDecimal money =BigDecimal.ZERO;
//        String basicPricingJson = xpPricing.getBasicPricingJson();
//        JSONObject jsonObject = JSON.parseObject(basicPricingJson);
//        Double v2 = calculationPrice(jsonObject, distance, duration, 0, 0.0);
//        return v2;
//    }
    private HolidayUtil holidayUtil;
    public Double  calculationPrice(JSONObject rule, double distance, long duration, long wait,Double waitMoney) throws Exception {
    /**
     *  计算基础价格
     * @param rule
     * @param distance
     * @param duration
     * @param wait
     * @param waitMoney
     * @return
     * @throws Exception
     */
    public XpPricingCardVo calculationPrice(JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney) throws Exception {
        XpPricingCardVo xpPricingCardVo = new XpPricingCardVo();
        double amount = 0;
        //等待费
        Date date = new Date();
        double d = distance / 1000;//实际公里
        double t = duration / 60;//实际时间
        double d1 = (d - rule.getDouble("num2")) < 0 ? 0 : d - rule.getDouble("num2");//超出起步里程的公里
        double t1 = (t - rule.getDouble("num3")) < 0 ? 0 : t - rule.getDouble("num3");//超过起步分钟数的时间
        double yt1 = 0;//远途1段
        double yt2 = 0;//远途2段
        double yt3 = 0;//远途3段
        // 计算出是在那个时间段内
        for (Object o : startingFareJson) {
            JSONObject startingFare = (JSONObject) o;
            Calendar calendarStart = Calendar.getInstance();
            calendarStart.setTime(date);
            calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0]));
            calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1]));
            Calendar calendarEnd = Calendar.getInstance();
            calendarEnd.setTime(date);
            calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0]));
            calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1]));
            // 找到符合条件的直接跳出循环
            if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
                rule.put("num1", startingFare.get("num3"));
                rule.put("num2", startingFare.get("num4"));
                rule.put("num3", startingFare.get("num5"));
                rule.put("num4", startingFare.get("num6"));
                rule.put("num5", startingFare.get("num7"));
                rule.put("startTime", startingFare.getString("num1"));
                rule.put("endTime", startingFare.getString("num2"));
                break;
            }
        }
        double realDistance = distance / 1000;//实际公里
        double realTime = duration / 60;//实际时间
        double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里
        double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间
        double longDistanceOne = 0;//远途1段
        double longDistanceTwo = 0;//远途2段
        double longDistanceThree = 0;//远途3段
        //夜间服务处理逻辑
        Calendar s = Calendar.getInstance();
        s.setTime(date);
        s.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0]));
        s.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1]));
        Calendar calendarNight = Calendar.getInstance();
        calendarNight.setTime(date);
        calendarNight.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0]));
        calendarNight.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1]));
        Calendar e = Calendar.getInstance();
        e.setTime(date);
        e.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0]));
        e.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1]));
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0]));
        calendar.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1]));
        if(date.getTime() > s.getTimeInMillis() && date.getTime() < e.getTimeInMillis()){
            if(d > rule.getDouble("num6")){
                yt1 = rule.getDouble("num18") * (d - rule.getDouble("num6"));
        // 先判断今日是否节假日
        boolean holiday = holidayUtil.isHoliday();
        if (holiday) {
            if (realDistance > rule.getDouble("num6")) {
                longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6"));
            }
            if(d >  rule.getDouble("num9")){
                yt2 = rule.getDouble("num19") * (d - rule.getDouble("num9"));
            if (realDistance > rule.getDouble("num9")) {
                longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9"));
            }
            if(d > rule.getDouble("num12")){
                yt3 = rule.getDouble("num20") * (d - rule.getDouble("num12"));
            if (realDistance > rule.getDouble("num12")) {
                longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12"));
            }
            amount = rule.getDouble("num15") + (d1 * rule.getDouble("num16")) + (t1 * rule.getDouble("num17")) + (wait * waitMoney) + yt1 + yt2 + yt3;
        }else{
            Calendar s1 = Calendar.getInstance();
            s1.setTime(date);
            s1.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0]));
            s1.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1]));
            amount = rule.getDouble("num29") + (exceedDistance * rule.getDouble("num30")) + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
            Calendar e1 = Calendar.getInstance();
            e1.setTime(date);
            e1.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0]));
            e1.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1]));
            Calendar s2 = Calendar.getInstance();
            s2.setTime(date);
            s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0]));
            s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1]));
            // 起步价
            xpPricingCardVo.setStartPrice(rule.getDouble("num29"));
            // 时间段
            xpPricingCardVo.setTime("节假日");
            Calendar e2 = Calendar.getInstance();
            e2.setTime(date);
            e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0]));
            e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1]));
            //高峰时段处理逻辑
            if((date.getTime() > s1.getTimeInMillis() && date.getTime() < e1.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())){
                if(d > rule.getDouble("num6")){
                    yt1 = rule.getDouble("num26") * (d - rule.getDouble("num6"));
            // 里程
            xpPricingCardVo.setDistance(realDistance);
            // 里程费
            double distancePrice = (exceedDistance * rule.getDouble("num30")) + longDistanceOne + longDistanceTwo + longDistanceThree;
            xpPricingCardVo.setDistancePrice(distancePrice);
            // 时长
            xpPricingCardVo.setDuration(realTime);
            // 时长费
            xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num31")));
        } else {
            if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) {
                if (realDistance > rule.getDouble("num6")) {
                    longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6"));
                }
                if(d >  rule.getDouble("num9")){
                    yt2 = rule.getDouble("num27") * (d - rule.getDouble("num9"));
                if (realDistance > rule.getDouble("num9")) {
                    longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9"));
                }
                if(d > rule.getDouble("num12")){
                    yt3 = rule.getDouble("num28") * (d - rule.getDouble("num12"));
                if (realDistance > rule.getDouble("num12")) {
                    longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12"));
                }
                amount = rule.getDouble("num23") + (d1 * rule.getDouble("num24")) + (t1 * rule.getDouble("num25")) + (wait * waitMoney) + yt1 + yt2 + yt3;
            }else{
                //其他时间段的计算
                if(d > rule.getDouble("num6")){
                    yt1 = rule.getDouble("num8") * (d - rule.getDouble("num6"));
                amount = rule.getDouble("num15") + (exceedDistance * rule.getDouble("num16")) + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                // 起步价
                xpPricingCardVo.setStartPrice(rule.getDouble("num15"));
                // 时间段
                xpPricingCardVo.setTime(rule.getString("num14"));
                // 里程
                xpPricingCardVo.setDistance(realDistance);
                // 里程费
                double distancePrice = (exceedDistance * rule.getDouble("num16")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                xpPricingCardVo.setDistancePrice(distancePrice);
                // 时长
                xpPricingCardVo.setDuration(realTime);
                // 时长费
                xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num17")));
            } else {
                // 高峰处理
                Calendar calendarStart = Calendar.getInstance();
                calendarStart.setTime(date);
                calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0]));
                calendarStart.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1]));
                Calendar calendarEnd = Calendar.getInstance();
                calendarEnd.setTime(date);
                calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0]));
                calendarEnd.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1]));
                Calendar s2 = Calendar.getInstance();
                s2.setTime(date);
                s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0]));
                s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1]));
                Calendar e2 = Calendar.getInstance();
                e2.setTime(date);
                e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0]));
                e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1]));
                //高峰时段处理逻辑
                if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) {
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6"));
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9"));
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12"));
                    }
                    amount = rule.getDouble("num23") + (exceedDistance * rule.getDouble("num24")) + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
                    xpPricingCardVo.setStartPrice(rule.getDouble("num23"));
                    // 时间段
                    if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) {
                        xpPricingCardVo.setTime(rule.getString("num21"));
                    } else {
                        xpPricingCardVo.setTime(rule.getString("num22"));
                    }
                    // 里程
                    xpPricingCardVo.setDistance(realDistance);
                    // 里程费
                    double distancePrice = (exceedDistance * rule.getDouble("num24")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    xpPricingCardVo.setDistancePrice(distancePrice);
                    // 时长
                    xpPricingCardVo.setDuration(realTime);
                    // 时长费
                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num25")));
                } else {
                    //其他时间段的计算
                    if (realDistance > rule.getDouble("num6")) {
                        longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6"));
                    }
                    if (realDistance > rule.getDouble("num9")) {
                        longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9"));
                    }
                    if (realDistance > rule.getDouble("num12")) {
                        longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12"));
                    }
                    amount = rule.getDouble("num1") + (exceedDistance * rule.getDouble("num4")) + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    // 起步价
                    xpPricingCardVo.setStartPrice(rule.getDouble("num1"));
                    // 时间段
                    xpPricingCardVo.setTime(rule.getString("startTime") + " - " + rule.getString("endTime"));
                    xpPricingCardVo.setDistancePriceNormal(rule.getDouble("num2"));
                    xpPricingCardVo.setDurationPriceNormal(rule.getDouble("num3"));
                    // 里程
                    xpPricingCardVo.setDistance(realDistance);
                    // 里程费
                    double distancePrice = (exceedDistance * rule.getDouble("num4")) + longDistanceOne + longDistanceTwo + longDistanceThree;
                    xpPricingCardVo.setDistancePrice(distancePrice);
                    // 时长
                    xpPricingCardVo.setDuration(realTime);
                    // 时长费
                    xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num5")));
                }
                if(d >  rule.getDouble("num9")){
                    yt2 = rule.getDouble("num11") * (d - rule.getDouble("num9"));
                }
                if(d > rule.getDouble("num12")){
                    yt3 = rule.getDouble("num13") * (d - rule.getDouble("num12"));
                }
                amount = rule.getDouble("num1") + (d1 * rule.getDouble("num4")) + (t1 * rule.getDouble("num5")) + (wait * waitMoney) + yt1 + yt2 + yt3;
            }
        }
        return amount;
        // 订单总额
        xpPricingCardVo.setTotalPrice(amount);
        return xpPricingCardVo;
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/utils/holiday/HolidayUtil.java
New file
@@ -0,0 +1,44 @@
package com.xiaopeng.operation.utils.holiday;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
@Component
public class HolidayUtil {
    @Autowired
    private RedisTemplate redisTemplate;
    public  boolean isHoliday() {
        LocalDate now = LocalDate.now();
        // 判断缓存里面是否有节假日算法
        if (redisTemplate.opsForValue().get("isHoliday_"+now) != null && ("1").equals(redisTemplate.opsForValue().get("isHoliday_"+now).toString())) {
            return true;
        }
        String result = HttpUtil.get("https://publicapi.xiaoai.me/holiday/day?date=" +now);
        JSONObject jsonObject = JSONObject.parseObject(result);
        Object data = jsonObject.get("data");
        JSONArray parse = JSONArray.parse(data.toString());
        for (int i = 0; i < parse.size(); i++) {
            JSONObject jsonObject1 = parse.getJSONObject(i);
            if (jsonObject1.get("daytype").toString().equals("1")) {
                redisTemplate.opsForValue().set("isHoliday_"+now, "1", 24*60*60);
                return true;
            }
        }
        redisTemplate.opsForValue().set("isHoliday_"+now, "0", 24*60*60);
        return false;
    }
}
xp-service/xp-operation/src/main/java/com/xiaopeng/operation/vo/XpPricingCardVo.java
New file
@@ -0,0 +1,48 @@
package com.xiaopeng.operation.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "XpPricingCardVo", description = "冒泡页价格卡片")
public class XpPricingCardVo {
    @ApiModelProperty(value = "价格总额")
    private Double totalPrice;
    @ApiModelProperty(value = "起步价格")
    private Double startPrice;
    @ApiModelProperty(value = "时间段")
    private String time;
    @ApiModelProperty(value = "距离")
    private Double distance;
    @ApiModelProperty(value = "时长")
    private Double duration;
    @ApiModelProperty(value = "里程费")
    private Double distancePrice;
    @ApiModelProperty(value = "普通时段公里数")
    private Double distancePriceNormal;
    @ApiModelProperty(value = "时长费")
    private Double durationPrice;
    @ApiModelProperty(value = "普通时段时长数")
    private Double durationPriceNormal;
    @ApiModelProperty(value = "优惠券")
    private String coupon;
}