liujie
2026-04-12 fa8e441c82c9d336d1144dd1ec925551d07b2299
1.0小程序功能  个人中心
14个文件已修改
831 ■■■■■ 已修改文件
sql/增量sql/xprt_account.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCollectAddrController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpAppUserCollectAddrMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/dto/CollectAddrDTO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/vo/XpAppUserCollectAddrVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCollectAddrServiceImpl.java 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCollectAddrMapper.xml 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpAppUserCollectAddrClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpAppUserCollectAddrClient.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpAppUserCollectAddr.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpPointsListVo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/mapper/XpPointMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/resources/mapper/operation/XpPointMapper.xml 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/增量sql/xprt_account.sql
@@ -0,0 +1,9 @@
-- 用户收藏地址:显式区分“站点收藏”和“场站收藏”
-- 1. item_type: 1=站点 2=场站
-- 2. station_id: 场站收藏时保留场站关系,避免后续查询再从 point_id 反推
alter table xp_app_user_collect_addr
    add column item_type tinyint not null default 1 comment '收藏对象类型 1=站点 2=场站' after latitude,
    add column station_id bigint null comment '场站id,站点收藏时为空' after point_id;
create index idx_app_user_collect_addr_item_type on xp_app_user_collect_addr (item_type);
create index idx_app_user_collect_addr_station_id on xp_app_user_collect_addr (station_id);
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/controller/XpAppUserCollectAddrController.java
@@ -95,7 +95,31 @@
     */
    @GetMapping("/logan/rt/v1/appUser/collectAddr/getPointIds")
    public R<List<Long>> getPointIdsByUserId(@RequestParam Long uid){
        List<Long> ids = xpAppUserCollectAddrService.list(new LambdaQueryWrapper<XpAppUserCollectAddr>().eq(XpAppUserCollectAddr::getAppUserId, uid)).stream().map(XpAppUserCollectAddr::getPointId).collect(Collectors.toList());
        List<Long> ids = xpAppUserCollectAddrService.list(new LambdaQueryWrapper<XpAppUserCollectAddr>()
                        .eq(XpAppUserCollectAddr::getAppUserId, uid).eq(XpAppUserCollectAddr::getItemType,1)
                        .eq(XpAppUserCollectAddr::getDeleted, 0))
                .stream()
                .map(XpAppUserCollectAddr::getPointId)
                .distinct()
                .collect(Collectors.toList());
        return R.ok(ids);
    }
    /**
     * 获取用户收藏的stationIds
     * 仅返回“场站收藏”记录,避免和站点收藏共用 point_id 产生歧义。
     */
    @GetMapping("/logan/rt/v1/appUser/collectAddr/getStationIds")
    public R<List<Long>> getStationIdsByUserId(@RequestParam Long uid){
        List<Long> ids = xpAppUserCollectAddrService.list(new LambdaQueryWrapper<XpAppUserCollectAddr>()
                        .eq(XpAppUserCollectAddr::getAppUserId, uid)
                        .eq(XpAppUserCollectAddr::getDeleted, 0)
                        .eq(XpAppUserCollectAddr::getItemType, 2))
                .stream()
                .map(XpAppUserCollectAddr::getStationId)
                .filter(java.util.Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        return R.ok(ids);
    }
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/mapper/XpAppUserCollectAddrMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xiaopeng.account.model.query.XpUserCollectAddrListQuery;
import com.xiaopeng.account.model.vo.CollectAddrTargetVO;
import com.xiaopeng.account.model.vo.XpAppUserCollectAddrVO;
import com.xiaopeng.api.model.account.XpAppUserCollectAddr;
import org.apache.ibatis.annotations.Param;
@@ -18,4 +19,8 @@
 */
public interface XpAppUserCollectAddrMapper extends BaseMapper<XpAppUserCollectAddr> {
    Page<XpAppUserCollectAddrVO> userCollectAddrPageList(@Param("page") Page<XpAppUserCollectAddrVO> page, @Param("query") XpUserCollectAddrListQuery query, @Param("uid") Long uid);
    CollectAddrTargetVO selectPointCollectTarget(@Param("pointId") Long pointId);
    CollectAddrTargetVO selectStationCollectTarget(@Param("stationId") Long stationId);
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/dto/CollectAddrDTO.java
@@ -1,6 +1,5 @@
package com.xiaopeng.account.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -11,19 +10,22 @@
@ApiModel("收藏地址/取消收藏参数")
public class CollectAddrDTO {
    @ApiModelProperty("1=收藏 2=取消收藏")
    @NotNull(message = "类型不能为空")
    @ApiModelProperty("操作类型 1=收藏 2=取消收藏")
    @NotNull(message = "操作类型不能为空")
    private Integer type;
    @ApiModelProperty(value = "经度")
    @NotNull(message = "经度不能为空")
    @ApiModelProperty("收藏对象类型 1=站点 2=场站,不传时默认按站点处理")
    private Integer itemType;
    @ApiModelProperty("站点id。收藏站点时必传;收藏场站时可传代表点位id作为兜底")
    private Long pointId;
    @ApiModelProperty("场站id。收藏/取消收藏场站时传入")
    private Long stationId;
    @ApiModelProperty("经度。兼容旧参数,最终以服务端解析结果为准")
    private Double longitude;
    @ApiModelProperty(value = "纬度")
    @NotNull(message = "纬度不能为空")
    @ApiModelProperty("纬度。兼容旧参数,最终以服务端解析结果为准")
    private Double latitude;
    @ApiModelProperty(value = "站点id")
    private Long pointId;
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/model/vo/XpAppUserCollectAddrVO.java
@@ -1,32 +1,34 @@
package com.xiaopeng.account.model.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("用户收藏地址-列表Vo")
@ApiModel("用户收藏地址列表")
public class XpAppUserCollectAddrVO {
    @ApiModelProperty(value = "经度")
    @ApiModelProperty("收藏记录id")
    private Long id;
    @ApiModelProperty("收藏对象类型 1=站点 2=场站")
    private Integer itemType;
    @ApiModelProperty("经度")
    private Double longitude;
    @ApiModelProperty(value = "纬度")
    @ApiModelProperty("纬度")
    private Double latitude;
    @ApiModelProperty(value = "站点id")
    @ApiModelProperty("落库使用的点位id")
    private Long pointId;
    @ApiModelProperty("所属场站id,无场站时为空")
    private Long stationId;
    @ApiModelProperty(value = "站点名称")
    @ApiModelProperty("展示名称。站点收藏展示点位名,场站收藏展示场站名")
    private String pointName;
    @ApiModelProperty(value = "站点地址")
    @ApiModelProperty("展示地址。站点收藏展示点位地址,场站收藏展示代表点位地址")
    private String pointAddress;
}
xp-robotaxi-account-boot/src/main/java/com/xiaopeng/account/service/impl/XpAppUserCollectAddrServiceImpl.java
@@ -6,19 +6,13 @@
import com.xiaopeng.account.mapper.XpAppUserCollectAddrMapper;
import com.xiaopeng.account.model.dto.CollectAddrDTO;
import com.xiaopeng.account.model.query.XpUserCollectAddrListQuery;
import com.xiaopeng.account.model.vo.CollectAddrTargetVO;
import com.xiaopeng.account.model.vo.XpAppUserCollectAddrVO;
import com.xiaopeng.account.service.IXpAppUserCollectAddrService;
import com.xiaopeng.api.dto.map.XpPointExportExcelQuery;
import com.xiaopeng.api.feignClient.operation.XpPointClient;
import com.xiaopeng.api.model.account.XpAppUserCollectAddr;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.web.page.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
/**
 * <p>
@@ -28,84 +22,117 @@
 * @author ck
 * @since 2026-04-10
 */
@Service
public class XpAppUserCollectAddrServiceImpl extends ServiceImpl<XpAppUserCollectAddrMapper, XpAppUserCollectAddr> implements IXpAppUserCollectAddrService {
    @Autowired
    private XpPointClient xpPointClient;
    private static final int COLLECT_TYPE_ADD = 1;
    private static final int COLLECT_TYPE_CANCEL = 2;
    private static final int ITEM_TYPE_STATION = 2;
    private static final int COLLECT_LIMIT = 50;
    /**
     * 收藏地址
     *
     * @param dto
     * @param uid
     * @return
     * 收藏地址。
     * 收藏表显式保存 item_type / station_id:
     * 1. 站点收藏:item_type=1,point_id 保存站点自身。
     * 2. 场站收藏:item_type=2,station_id 保存场站id,point_id 保存代表点位id。
     * 这样取消收藏、列表回显和后续扩展都不需要再做隐式推断。
     */
    @Override
    public Boolean collectAddr(CollectAddrDTO dto, String uid) {
        if (dto.getType() == 1) {
        Long userId = Long.parseLong(uid);
        CollectAddrTargetVO target = resolveCollectTarget(dto);
        if (target == null || target.getPointId() == null || target.getItemType() == null) {
            throw new ServiceException("收藏目标不存在");
        }
        LambdaQueryWrapper<XpAppUserCollectAddr> sameTargetWrapper = buildSameTargetWrapper(userId, target);
        if (COLLECT_TYPE_ADD == dto.getType()) {
            Long count = this.count(new LambdaQueryWrapper<XpAppUserCollectAddr>()
                    .eq(XpAppUserCollectAddr::getAppUserId, Long.parseLong(uid))
                    .eq(XpAppUserCollectAddr::getAppUserId, userId)
                    .eq(XpAppUserCollectAddr::getDeleted, 0));
            if (count >= 50) {
            if (count >= COLLECT_LIMIT) {
                throw new ServiceException("收藏夹已满,请先删除旧地址");
            }
            Long old = this.count(new LambdaQueryWrapper<XpAppUserCollectAddr>()
                    .eq(XpAppUserCollectAddr::getAppUserId, Long.parseLong(uid))
                    .eq(XpAppUserCollectAddr::getDeleted, 0).eq(XpAppUserCollectAddr::getPointId, dto.getPointId()));
            if (old > 0) {
            if (this.count(sameTargetWrapper) > 0) {
                return true;
            }
            XpAppUserCollectAddr xpAppUserCollectAddr = new XpAppUserCollectAddr();
            xpAppUserCollectAddr.setAppUserId(Long.parseLong(uid));
            xpAppUserCollectAddr.setLatitude(dto.getLatitude());
            xpAppUserCollectAddr.setLongitude(dto.getLongitude());
            xpAppUserCollectAddr.setPointId(dto.getPointId());
            return this.save(xpAppUserCollectAddr);
        } else {
            XpAppUserCollectAddr xpAppUserCollectAddr = this.getOne(new LambdaQueryWrapper<XpAppUserCollectAddr>()
                    .eq(XpAppUserCollectAddr::getAppUserId, Long.parseLong(uid))
                    .eq(XpAppUserCollectAddr::getDeleted, 0).eq(XpAppUserCollectAddr::getPointId, dto.getPointId()).last("limit 1"));
            if (xpAppUserCollectAddr != null) {
                return this.removeById(xpAppUserCollectAddr.getId());
            }
            XpAppUserCollectAddr collectAddr = new XpAppUserCollectAddr();
            collectAddr.setAppUserId(userId);
            collectAddr.setItemType(target.getItemType());
            collectAddr.setPointId(target.getPointId());
            collectAddr.setStationId(target.getStationId());
            collectAddr.setLongitude(target.getLongitude());
            collectAddr.setLatitude(target.getLatitude());
            return this.save(collectAddr);
        }
        return false;
        if (COLLECT_TYPE_CANCEL == dto.getType()) {
            XpAppUserCollectAddr collectAddr = this.getOne(sameTargetWrapper.last("limit 1"));
            if (collectAddr == null) {
                return true;
            }
            return this.removeById(collectAddr.getId());
        }
        throw new ServiceException("不支持的操作类型");
    }
    /**
     * 收藏地址列表
     *
     * @param uid
     * @return
     * 收藏地址列表。
     * 列表查询直接联表回显,场站/站点通过 item_type 直接区分。
     */
    @Override
    public PageInfo<XpAppUserCollectAddrVO> userCollectAddrList(XpUserCollectAddrListQuery query, String uid) {
        Page<XpAppUserCollectAddrVO> page = new Page<>(query.getPageNum(), query.getPageSize());
        Page<XpAppUserCollectAddrVO> pageData = this.baseMapper.userCollectAddrPageList(page, query, Long.parseLong(uid));
        if(!pageData.getRecords().isEmpty()){
            List<Long> pointIds = pageData.getRecords().stream().map(XpAppUserCollectAddrVO::getPointId).collect(Collectors.toList());
            XpPointExportExcelQuery xpPointExportExcelQuery = new XpPointExportExcelQuery();
            xpPointExportExcelQuery.setPointIds(pointIds);
            R<List<XpPoint>> pointClientByIds = xpPointClient.getByIds(xpPointExportExcelQuery);
            if(R.isSuccess(pointClientByIds)){
                List<XpPoint> data = pointClientByIds.getData();
                pageData.getRecords().forEach(item -> {
                    data.forEach(point -> {
                        if(item.getPointId().equals(point.getId())){
                            item.setPointName(point.getPointName());
                            item.setPointAddress(point.getPointAddress());
                        }
                    });
                });
            }
        }
        PageInfo<XpAppUserCollectAddrVO> resultPage = new PageInfo<>(query.getPageNum(), query.getPageSize());
        resultPage.setTotal(pageData.getTotal());
        resultPage.setRecords(pageData.getRecords());
        return resultPage;
    }
    /**
     * 将前端传入的站点/场站目标解析成统一收藏目标。
     * 优先按 itemType 判断,未传时兼容旧逻辑按 pointId 处理。
     */
    private CollectAddrTargetVO resolveCollectTarget(CollectAddrDTO dto) {
        if (isStationItem(dto)) {
            if (dto.getStationId() == null) {
                throw new ServiceException("场站id不能为空");
            }
            return this.baseMapper.selectStationCollectTarget(dto.getStationId());
        }
        if (dto.getPointId() == null && dto.getStationId() != null) {
            return this.baseMapper.selectStationCollectTarget(dto.getStationId());
        }
        if (dto.getPointId() == null) {
            throw new ServiceException("站点id不能为空");
        }
        return this.baseMapper.selectPointCollectTarget(dto.getPointId());
    }
    /**
     * 收藏唯一键规则:
     * 1. 站点收藏按 user_id + item_type + point_id 去重。
     * 2. 场站收藏按 user_id + item_type + station_id 去重。
     * point_id 在场站收藏里仍保留,方便联动现有“已收藏点位”能力。
     */
    private LambdaQueryWrapper<XpAppUserCollectAddr> buildSameTargetWrapper(Long userId, CollectAddrTargetVO target) {
        LambdaQueryWrapper<XpAppUserCollectAddr> wrapper = new LambdaQueryWrapper<XpAppUserCollectAddr>()
                .eq(XpAppUserCollectAddr::getAppUserId, userId)
                .eq(XpAppUserCollectAddr::getDeleted, 0)
                .eq(XpAppUserCollectAddr::getItemType, target.getItemType());
        if (target.getItemType() == ITEM_TYPE_STATION) {
            wrapper.eq(XpAppUserCollectAddr::getStationId, target.getStationId());
        } else {
            wrapper.eq(XpAppUserCollectAddr::getPointId, target.getPointId());
        }
        return wrapper;
    }
    private boolean isStationItem(CollectAddrDTO dto) {
        return dto.getItemType() != null && dto.getItemType() == ITEM_TYPE_STATION;
    }
}
xp-robotaxi-account-boot/src/main/resources/mapper/account/XpAppUserCollectAddrMapper.xml
@@ -2,11 +2,90 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaopeng.account.mapper.XpAppUserCollectAddrMapper">
    <select id="userCollectAddrPageList" resultType="com.xiaopeng.account.model.vo.XpAppUserCollectAddrVO">
        select
            c.id,
            c.item_type as itemType,
            c.longitude,
            c.latitude,
            c.point_id as pointId,
            c.station_id as stationId,
            case
                when c.item_type = 2 then s.station_name
                else p.point_name
            end as pointName,
            case
                when c.item_type = 2 then coalesce(dp.point_address, fp.point_address, p.point_address)
                else p.point_address
            end as pointAddress
        from xp_app_user_collect_addr c
        left join xp_point p on p.id = c.point_id and p.is_deleted = 0
        left join xp_station s on s.id = c.station_id and s.is_deleted = 0
        left join xp_point dp on dp.id = s.default_point_id and dp.is_deleted = 0
        left join xp_point fp on fp.id = (
            select p1.id
            from xp_point p1
            where p1.station_id = s.id
              and p1.is_deleted = 0
              and p1.point_type = 1
              and p1.operation_status = 2
            order by case when s.default_point_id is not null and p1.id = s.default_point_id then 0 else 1 end,
                     p1.station_sort_no asc,
                     p1.id asc
            limit 1
        )
        where c.app_user_id = #{uid}
          and c.is_deleted = 0
        order by c.create_time asc
    </select>
        select longitude,latitude,point_id from xp_app_user_collect_addr
    <select id="selectPointCollectTarget" resultType="com.xiaopeng.account.model.vo.CollectAddrTargetVO">
        select
            1 as itemType,
            p.id as pointId,
            p.station_id as stationId,
            p.point_lon as longitude,
            p.point_lat as latitude
        from xp_point p
        inner join xp_electronic_fence_area a on a.id = p.electronic_area_id
        where p.is_deleted = 0
          and p.point_type = 1
          and p.operation_status = 2
          and a.is_deleted = 0
          and a.area_status = 1
          and p.id = #{pointId}
    </select>
         WHERE app_user_id = #{uid}  and is_deleted = 0 order by create_time asc
    <select id="selectStationCollectTarget" resultType="com.xiaopeng.account.model.vo.CollectAddrTargetVO">
        select
            2 as itemType,
            coalesce(dp.id, fp.id) as pointId,
            s.id as stationId,
            coalesce(s.center_lon, dp.point_lon, fp.point_lon) as longitude,
            coalesce(s.center_lat, dp.point_lat, fp.point_lat) as latitude
        from xp_station s
        inner join xp_electronic_fence_area a on a.id = s.electronic_area_id
        left join xp_point dp on dp.id = s.default_point_id
            and dp.is_deleted = 0
            and dp.point_type = 1
            and dp.operation_status = 2
        left join xp_point fp on fp.id = (
            select p1.id
            from xp_point p1
            where p1.station_id = s.id
              and p1.is_deleted = 0
              and p1.point_type = 1
              and p1.operation_status = 2
            order by case when s.default_point_id is not null and p1.id = s.default_point_id then 0 else 1 end,
                     p1.station_sort_no asc,
                     p1.id asc
            limit 1
        )
        where s.is_deleted = 0
          and s.status = 6
          and a.is_deleted = 0
          and a.area_status = 1
          and s.id = #{stationId}
          and coalesce(dp.id, fp.id) is not null
    </select>
</mapper>
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/account/XpAppUserCollectAddrClientFallbackFactory.java
@@ -13,8 +13,12 @@
            @Override
            public R<List<Long>> getPointIdsByUserId(Long uid) {
                R.fail("获取用户收藏地址失败" + cause.getMessage());
                return null;
                return R.fail("获取用户收藏站点失败:" + cause.getMessage());
            }
            @Override
            public R<List<Long>> getStationIdsByUserId(Long uid) {
                return R.fail("获取用户收藏场站失败:" + cause.getMessage());
            }
        };
    }
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/account/XpAppUserCollectAddrClient.java
@@ -1,22 +1,16 @@
package com.xiaopeng.api.feignClient.account;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
import com.xiaopeng.api.dto.statistics.UserStatisticsDTO;
import com.xiaopeng.api.factory.account.XpAppUserClientFallbackFactory;
import com.xiaopeng.api.factory.account.XpAppUserCollectAddrClientFallbackFactory;
import com.xiaopeng.api.model.account.XpAppUser;
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;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
 * 小程序用户Feign客户端
 * 小程序用户收藏地址 Feign 客户端
 *
 * @author mitao
 */
@@ -26,9 +20,14 @@
public interface XpAppUserCollectAddrClient {
    /**
     * 获取用户收藏的pointIds
     * 获取用户收藏的站点 pointIds
     */
    @GetMapping("/logan/rt/v1/appUser/collectAddr/getPointIds")
    R<List<Long>> getPointIdsByUserId(@RequestParam Long uid);
    /**
     * 获取用户收藏的场站 stationIds
     */
    @GetMapping("/logan/rt/v1/appUser/collectAddr/getStationIds")
    R<List<Long>> getStationIdsByUserId(@RequestParam Long uid);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/account/XpAppUserCollectAddr.java
@@ -10,8 +10,6 @@
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
 * <p>
 * 用户收藏地址
@@ -24,7 +22,7 @@
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xp_app_user_collect_addr")
@ApiModel(value="XpAppUserCollectAddr对象", description="用户收藏地址")
@ApiModel(value = "XpAppUserCollectAddr对象", description = "用户收藏地址")
public class XpAppUserCollectAddr extends BaseModel {
    private static final long serialVersionUID = 1L;
@@ -41,6 +39,12 @@
    @ApiModelProperty(value = "纬度")
    private Double latitude;
    @ApiModelProperty(value = "站点id")
    @ApiModelProperty(value = "收藏对象类型 1=站点 2=场站")
    private Integer itemType;
    @ApiModelProperty(value = "点位id。场站收藏时保存代表点位id")
    private Long pointId;
    @ApiModelProperty(value = "场站id。站点收藏时为空")
    private Long stationId;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpPointsListVo.java
@@ -4,9 +4,14 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel("搜索点位Vo")
@ApiModel("搜索点位结果")
public class XpPointsListVo {
    @ApiModelProperty("结果类型 1=站点 2=场站")
    private Integer itemType;
    @ApiModelProperty("名称")
    private String name;
@@ -20,28 +25,39 @@
    @ApiModelProperty("经度")
    private Double longitude;
    @ApiModelProperty("距离m")
    @ApiModelProperty("距离,单位米")
    private Double distance;
    @ApiModelProperty("站点id")
    @ApiModelProperty("结果id,站点时为point.id,场站时为station.id")
    private Long id;
    @ApiModelProperty("所属场站id,无场站时为空")
    private Long stationId;
    @ApiModelProperty("电子围栏区域id")
    private Long electronicAreaId;
    @ApiModelProperty(value = "运营类型 1=与运营区同步 2=自定义时间段")
    @ApiModelProperty("运营类型 1=与运营区同步 2=自定义时间段")
    private Integer operationType;
    @ApiModelProperty("是否收藏 1是 2否")
    private Integer isCollect=2;
    @ApiModelProperty("是否收藏 1=是 2=否")
    private Integer isCollect = 2;
    @ApiModelProperty("收藏绑定的点位id,场站收藏时用于落到具体point_id")
    private Long collectPointId;
    @ApiModelProperty("点位在场站内排序")
    private Integer stationSortNo;
    @ApiModelProperty("生效周期")
    private Integer effectivePeriod;
    @ApiModelProperty("生效时间段")
    private String effectiveTimePeriod;
    private Integer state=1;
    @ApiModelProperty("状态 1=可用 0=不可用")
    private Integer state = 1;
    @ApiModelProperty("场站子点位列表")
    private List<XpPointsListVo> children;
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/mapper/XpPointMapper.java
@@ -28,7 +28,11 @@
    List<XpPoint> listAll(@Param("query") PointListQuery query);
    List<XpPointsListVo> queryPointByName(@Param("query") PointByNameQuery query);
    List<XpPointsListVo> queryPointByName(@Param("query") PointByNameQuery query, @Param("excludeStationIds") List<Long> excludeStationIds);
    List<XpPointsListVo> queryStationByName(@Param("query") PointByNameQuery query);
    List<XpPointsListVo> queryStationChildren(@Param("stationIds") List<Long> stationIds);
    List<XpPoint> pageList(@Param("pageInfo") PageInfo<XpPoint> pageInfo, @Param("query") PointPageListListQuery query);
}
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -470,85 +470,242 @@
    @Override
    public XpPointsSearchVo queryPointByName(PointByNameQuery query,String uid) {
        XpPointsSearchVo searchVo = new XpPointsSearchVo();
        List<XpPointsListVo> list = this.baseMapper.queryPointByName(query);
        if (list.isEmpty()) {
        List<XpPointsListVo> stationList = this.baseMapper.queryStationByName(query);
        List<Long> stationIds = stationList.stream()
                .map(this::getSearchResultStationId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        // 先把命中的场站id传给点位查询,避免同一场站下的点位和场站父胶囊重复返回。
        List<XpPointsListVo> pointList = this.baseMapper.queryPointByName(query, stationIds);
        List<XpPointsListVo> stationChildren = CollUtil.isEmpty(stationIds) ? Collections.emptyList() : this.baseMapper.queryStationChildren(stationIds);
        if (CollUtil.isEmpty(stationList) && CollUtil.isEmpty(pointList)) {
            return searchVo;
        }
        LocalTime now = LocalTime.now();
        List<Long> areaIds = list.stream().map(XpPointsListVo::getElectronicAreaId).collect(Collectors.toList());
        List<XpElectronicFenceArea> xpElectronicFenceAreas = xpElectronicFenceAreaService.listByIds(areaIds);
        List<Long> fenceIds = xpElectronicFenceAreas.stream().map(XpElectronicFenceArea::getElectronicId).collect(Collectors.toList());
        // 搜索页需要拆分两套收藏态:
        // 1. 站点胶囊按 pointId 判定。
        // 2. 场站胶囊按 stationId 判定。
        Set<Long> collectedPointIds = getCollectedPointIds(uid);
        Set<Long> collectedStationIds = getCollectedStationIds(uid);
        List<XpPointsListVo> allRows = new ArrayList<>(stationList.size() + pointList.size() + stationChildren.size());
        allRows.addAll(stationList);
        allRows.addAll(pointList);
        allRows.addAll(stationChildren);
        List<XpElectronicFence> xpElectronicFences = new ArrayList<>();
        if (CollUtil.isNotEmpty(fenceIds)) {
            xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds);
        }
        Map<Long, XpElectronicFenceArea> electronicFenceAreaMap = buildElectronicFenceAreaMap(allRows);
        Map<Long, XpElectronicFence> electronicFenceMap = buildElectronicFenceMap(electronicFenceAreaMap);
        markSearchRows(allRows, collectedPointIds, collectedStationIds, electronicFenceAreaMap, electronicFenceMap);
        List<XpPointsListVo> resultList = new ArrayList<>();
        List<XpPointsListVo> stationResults = buildStationSearchResults(stationList, stationChildren, query);
        Set<Long> displayedStationIds = stationResults.stream()
                .map(this::getSearchResultStationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        resultList.addAll(stationResults);
        resultList.addAll(pointList.stream()
                .filter(vo -> vo.getState() == 1)
                .filter(vo -> vo.getStationId() == null || !displayedStationIds.contains(vo.getStationId()))
                .peek(vo -> fillDistance(vo, query))
                .collect(Collectors.toList()));
        R<List<Long>> pointIdsByUserId =null;
        // 获取用户收藏的地址
        if(uid!=null){
            pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(Long.valueOf(uid));
        }
        for (XpPointsListVo xpPointsListVo : list) {
            // 判断是否收藏
            if(pointIdsByUserId!=null && !pointIdsByUserId.getData().isEmpty()){
                if(pointIdsByUserId.getData().contains(xpPointsListVo.getId())){
                    xpPointsListVo.setIsCollect(1);
                }
            }
            Integer operationType = xpPointsListVo.getOperationType();
            if(1 == operationType){
                //与运营区同步
                XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreas.stream().filter(s -> s.getId().equals(xpPointsListVo.getElectronicAreaId())).findFirst().orElse(null);
                if(null == xpElectronicFenceArea){
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
                XpElectronicFence xpElectronicFence = xpElectronicFences.stream().filter(s -> s.getId().equals(xpElectronicFenceArea.getElectronicId())).findFirst().orElse(null);
                if(null == xpElectronicFence){
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
                //失效类型 1=永不失效 2=定时失效
                Integer failureType = xpElectronicFence.getFailureType();
                //永不失效,但是还未到开始时间
                if(1 == failureType && LocalDateTime.now().isBefore(xpElectronicFence.getEffectTime())){
                    xpPointsListVo.setState(0);
                }else if(2 == failureType && (LocalDateTime.now().isBefore(xpElectronicFence.getEffectTime()) || LocalDateTime.now().isAfter(xpElectronicFence.getFailureTime()))){
                    //定时失效,但是当前时间不在时间范围内
                    xpPointsListVo.setState(0);
                }
            }else{
                //自定义时间段
                // 判断是否在时间段内
                boolean intime = getInTime(now, xpPointsListVo.getEffectivePeriod(), xpPointsListVo.getEffectiveTimePeriod());
                if (!intime) {
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
            }
        }
        // 组装数据 并排序 剔除不在运营段的 数据
        List<XpPointsListVo> resultList = list.stream()
                .peek(vo -> vo.setDistance(BigDecimal.valueOf(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), vo.getLatitude(), vo.getLongitude())).setScale(2, RoundingMode.HALF_UP).doubleValue()))
                .filter(vo -> vo.getState() == 1).collect(Collectors.toList());
        // 可用点位
        List<XpPointsListVo> availablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());//
        // 不可用点位
        List<XpPointsListVo> unavailablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() == null || !e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());
        List<XpPointsListVo> availablePoints = resultList.stream()
                .filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId()))
                .sorted(Comparator.comparing(this::distanceOrMax))
                .collect(Collectors.toList());
        List<XpPointsListVo> unavailablePoints = resultList.stream()
                .filter(e -> query.getElectronicAreaId() == null || !e.getElectronicAreaId().equals(query.getElectronicAreaId()))
                .sorted(Comparator.comparing(this::distanceOrMax))
                .collect(Collectors.toList());
        searchVo.setAvailablePoints(availablePoints);
        searchVo.setUnavailablePoints(unavailablePoints);
        return searchVo;
    }
    private List<XpPointsListVo> buildStationSearchResults(List<XpPointsListVo> stationList,
                                                           List<XpPointsListVo> stationChildren,
                                                           PointByNameQuery query) {
        if (CollUtil.isEmpty(stationList)) {
            return Collections.emptyList();
        }
        Map<Long, List<XpPointsListVo>> childrenMap = stationChildren.stream()
                .filter(vo -> vo.getState() == 1)
                .peek(vo -> fillDistance(vo, query))
                .collect(Collectors.groupingBy(this::getSearchResultStationId, LinkedHashMap::new, Collectors.toList()));
        List<XpPointsListVo> result = new ArrayList<>();
        for (XpPointsListVo station : stationList) {
            if (station.getState() != 1) {
                continue;
            }
            Long stationId = getSearchResultStationId(station);
            List<XpPointsListVo> children = childrenMap.getOrDefault(stationId, Collections.emptyList());
            if (CollUtil.isEmpty(children)) {
                continue;
            }
            XpPointsListVo firstChild = children.get(0);
            if (station.getCollectPointId() == null) {
                station.setCollectPointId(firstChild.getId());
            }
            if (station.getLongitude() == null || station.getLatitude() == null) {
                station.setLongitude(firstChild.getLongitude());
                station.setLatitude(firstChild.getLatitude());
            }
            if (!StringUtils.hasLength(station.getAddress())) {
                station.setAddress(firstChild.getAddress());
            }
            fillDistance(station, query);
            station.setChildren(children);
            result.add(station);
        }
        return result;
    }
    /**
     * 仅查询“站点收藏”。
     * nearest / 历史上下车点等老接口仍然只对 pointId 打星,所以这里不能混入场站收藏。
     */
    private Set<Long> getCollectedPointIds(String uid) {
        if (uid == null) {
            return Collections.emptySet();
        }
        R<List<Long>> pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(Long.valueOf(uid));
        if (pointIdsByUserId == null || CollUtil.isEmpty(pointIdsByUserId.getData())) {
            return Collections.emptySet();
        }
        return new HashSet<>(pointIdsByUserId.getData());
    }
    /**
     * 场站收藏需要单独查询。
     * 搜索结果里的场站父级行应按 stationId 判断收藏态,不能继续复用 pointId 集合。
     */
    private Set<Long> getCollectedStationIds(String uid) {
        if (uid == null) {
            return Collections.emptySet();
        }
        R<List<Long>> stationIdsByUserId = xpAppUserCollectAddrClient.getStationIdsByUserId(Long.valueOf(uid));
        if (stationIdsByUserId == null || CollUtil.isEmpty(stationIdsByUserId.getData())) {
            return Collections.emptySet();
        }
        return new HashSet<>(stationIdsByUserId.getData());
    }
    private Map<Long, XpElectronicFenceArea> buildElectronicFenceAreaMap(List<XpPointsListVo> rows) {
        List<Long> areaIds = rows.stream()
                .map(XpPointsListVo::getElectronicAreaId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(areaIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceAreaService.listByIds(areaIds).stream()
                .collect(Collectors.toMap(XpElectronicFenceArea::getId, area -> area, (left, right) -> left));
    }
    private Map<Long, XpElectronicFence> buildElectronicFenceMap(Map<Long, XpElectronicFenceArea> electronicFenceAreaMap) {
        if (electronicFenceAreaMap.isEmpty()) {
            return Collections.emptyMap();
        }
        List<Long> fenceIds = electronicFenceAreaMap.values().stream()
                .map(XpElectronicFenceArea::getElectronicId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(fenceIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceService.listByIds(fenceIds).stream()
                .collect(Collectors.toMap(XpElectronicFence::getId, fence -> fence, (left, right) -> left));
    }
    private void markSearchRows(List<XpPointsListVo> rows,
                                Set<Long> collectedPointIds,
                                Set<Long> collectedStationIds,
                                Map<Long, XpElectronicFenceArea> electronicFenceAreaMap,
                                Map<Long, XpElectronicFence> electronicFenceMap) {
        LocalTime now = LocalTime.now();
        for (XpPointsListVo row : rows) {
            if (isCollectedSearchRow(row, collectedPointIds, collectedStationIds)) {
                row.setIsCollect(1);
            }
            if (!isSearchRowAvailable(row, electronicFenceAreaMap, electronicFenceMap, now)) {
                row.setState(0);
            }
        }
    }
    /**
     * 搜索结果收藏态判定规则:
     * 1. 站点行按 pointId / collectPointId 判定。
     * 2. 场站行按 stationId 判定。
     */
    private boolean isCollectedSearchRow(XpPointsListVo row,
                                         Set<Long> collectedPointIds,
                                         Set<Long> collectedStationIds) {
        if (row.getItemType() != null && row.getItemType() == 2) {
            Long stationId = getSearchResultStationId(row);
            return stationId != null && collectedStationIds.contains(stationId);
        }
        Long collectPointId = row.getCollectPointId() != null ? row.getCollectPointId() : row.getId();
        return collectPointId != null && collectedPointIds.contains(collectPointId);
    }
    private boolean isSearchRowAvailable(XpPointsListVo row,
                                         Map<Long, XpElectronicFenceArea> electronicFenceAreaMap,
                                         Map<Long, XpElectronicFence> electronicFenceMap,
                                         LocalTime now) {
        Integer operationType = row.getOperationType();
        if (operationType == null) {
            return false;
        }
        if (operationType == 1) {
            Long electronicAreaId = row.getElectronicAreaId();
            if (electronicAreaId == null) {
                return false;
            }
            XpElectronicFenceArea electronicFenceArea = electronicFenceAreaMap.get(electronicAreaId);
            if (electronicFenceArea == null || electronicFenceArea.getElectronicId() == null) {
                return false;
            }
            XpElectronicFence electronicFence = electronicFenceMap.get(electronicFenceArea.getElectronicId());
            if (electronicFence == null) {
                return false;
            }
            Integer failureType = electronicFence.getFailureType();
            if (failureType == null) {
                return true;
            }
            if (failureType == 1 && LocalDateTime.now().isBefore(electronicFence.getEffectTime())) {
                return false;
            }
            return failureType != 2 || (!LocalDateTime.now().isBefore(electronicFence.getEffectTime())
                    && !LocalDateTime.now().isAfter(electronicFence.getFailureTime()));
        }
        if (row.getEffectivePeriod() == null || !StringUtils.hasLength(row.getEffectiveTimePeriod())) {
            return true;
        }
        return getInTime(now, row.getEffectivePeriod(), row.getEffectiveTimePeriod());
    }
    private void fillDistance(XpPointsListVo row, PointByNameQuery query) {
        if (row.getLatitude() == null || row.getLongitude() == null) {
            return;
        }
        row.setDistance(BigDecimal.valueOf(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), row.getLatitude(), row.getLongitude()))
                .setScale(2, RoundingMode.HALF_UP)
                .doubleValue());
    }
    private Long getSearchResultStationId(XpPointsListVo row) {
        return row.getStationId() != null ? row.getStationId() : row.getId();
    }
    private Double distanceOrMax(XpPointsListVo row) {
        return row.getDistance() == null ? Double.MAX_VALUE : row.getDistance();
    }
    @Override
    public OrderDistanceVo getOrderDistance() {
        OrderDistanceVo orderDistanceVo = new OrderDistanceVo();
xp-robotaxi-operation-boot/src/main/resources/mapper/operation/XpPointMapper.xml
@@ -94,19 +94,148 @@
    <select id="queryPointByName" resultType="com.xiaopeng.api.vo.order.XpPointsListVo">
        select p.id,p.operation_type as operationType ,p.electronic_area_id as electronicAreaId,p.point_name as name,p.point_address as address,p.point_lon as longitude,p.point_lat as latitude,p.effective_period,p.effective_time_period
        select
            1 as itemType,
            p.id,
            p.station_id as stationId,
            p.id as collectPointId,
            p.operation_type as operationType,
            p.electronic_area_id as electronicAreaId,
            p.point_name as name,
            p.point_address as address,
            p.point_lon as longitude,
            p.point_lat as latitude,
            p.station_sort_no as stationSortNo,
            p.effective_period as effectivePeriod,
            p.effective_time_period as effectiveTimePeriod
        from xp_electronic_fence_area xefa
        left join xp_point p on p.electronic_area_id = xefa.id
        where p.is_deleted = 0 and p.operation_status =2 and xefa.is_deleted = 0
        and xefa.area_status = 1 and p.electronic_area_id is not null
        and xefa.city_code = #{query.cityCode}
        inner join xp_point p on p.electronic_area_id = xefa.id
        where p.is_deleted = 0
          and p.point_type = 1
          and p.operation_status = 2
          and xefa.is_deleted = 0
          and xefa.area_status = 1
          and p.electronic_area_id is not null
          and xefa.city_code = #{query.cityCode}
        <if test="query.pointPoi != null and query.pointPoi != ''">
            and ( p.point_address like concat('%',#{query.pointPoi},'%') or p.point_name like concat('%',#{query.pointPoi},'%') )
            and (
                p.point_address like concat('%',#{query.pointPoi},'%')
                or p.point_name like concat('%',#{query.pointPoi},'%')
                or p.point_poi like concat('%',#{query.pointPoi},'%')
            )
        </if>
        <if test="query.electronicAreaId != null">
            and p.electronic_area_id = #{query.electronicAreaId}
        </if>
        <if test="excludeStationIds != null and excludeStationIds.size() > 0">
            and (p.station_id is null or p.station_id not in
            <foreach collection="excludeStationIds" item="stationId" separator="," open="(" close=")">
                #{stationId}
            </foreach>
            )
        </if>
    </select>
    <select id="queryStationByName" resultType="com.xiaopeng.api.vo.order.XpPointsListVo">
        select
            2 as itemType,
            s.id,
            s.id as stationId,
            coalesce(s.default_point_id, dp.id, fp.id) as collectPointId,
            s.operation_type as operationType,
            s.electronic_area_id as electronicAreaId,
            s.station_name as name,
            coalesce(dp.point_address, fp.point_address) as address,
            coalesce(s.center_lon, dp.point_lon, fp.point_lon) as longitude,
            coalesce(s.center_lat, dp.point_lat, fp.point_lat) as latitude,
            s.effective_period as effectivePeriod,
            s.effective_time_period as effectiveTimePeriod
        from xp_station s
        inner join xp_electronic_fence_area xefa on xefa.id = s.electronic_area_id
        left join xp_point dp on dp.id = s.default_point_id
            and dp.is_deleted = 0
            and dp.point_type = 1
            and dp.operation_status = 2
        left join xp_point fp on fp.id = (
            select p1.id
            from xp_point p1
            where p1.station_id = s.id
              and p1.is_deleted = 0
              and p1.point_type = 1
              and p1.operation_status = 2
            order by case when s.default_point_id is not null and p1.id = s.default_point_id then 0 else 1 end,
                     p1.station_sort_no asc,
                     p1.id asc
            limit 1
        )
        where s.is_deleted = 0
          and s.status = 6
          and xefa.is_deleted = 0
          and xefa.area_status = 1
          and s.city_code = #{query.cityCode}
          and exists (
              select 1
              from xp_point p2
              where p2.station_id = s.id
                and p2.is_deleted = 0
                and p2.point_type = 1
                and p2.operation_status = 2
          )
        <if test="query.pointPoi != null and query.pointPoi != ''">
            and (
                s.station_name like concat('%',#{query.pointPoi},'%')
                or exists (
                    select 1
                    from xp_point p3
                    where p3.station_id = s.id
                      and p3.is_deleted = 0
                      and p3.point_type = 1
                      and p3.operation_status = 2
                      and (
                          p3.point_name like concat('%',#{query.pointPoi},'%')
                          or p3.point_address like concat('%',#{query.pointPoi},'%')
                          or p3.point_poi like concat('%',#{query.pointPoi},'%')
                      )
                )
            )
        </if>
        <if test="query.electronicAreaId != null">
            and s.electronic_area_id = #{query.electronicAreaId}
        </if>
        order by s.sort_no asc, s.id asc
    </select>
    <select id="queryStationChildren" resultType="com.xiaopeng.api.vo.order.XpPointsListVo">
        select
            1 as itemType,
            p.id,
            p.station_id as stationId,
            p.id as collectPointId,
            p.operation_type as operationType,
            p.electronic_area_id as electronicAreaId,
            p.point_name as name,
            p.point_address as address,
            p.point_lon as longitude,
            p.point_lat as latitude,
            p.station_sort_no as stationSortNo,
            p.effective_period as effectivePeriod,
            p.effective_time_period as effectiveTimePeriod
        from xp_point p
        inner join xp_station s on s.id = p.station_id
        inner join xp_electronic_fence_area xefa on xefa.id = p.electronic_area_id
        where p.is_deleted = 0
          and p.point_type = 1
          and p.operation_status = 2
          and s.is_deleted = 0
          and s.status = 6
          and xefa.is_deleted = 0
          and xefa.area_status = 1
          and p.station_id in
          <foreach collection="stationIds" item="stationId" separator="," open="(" close=")">
              #{stationId}
          </foreach>
        order by p.station_id asc,
                 case when s.default_point_id is not null and p.id = s.default_point_id then 0 else 1 end asc,
                 p.station_sort_no asc,
                 p.id asc
    </select>
    <select id="pageList" resultType="com.xiaopeng.api.model.operation.XpPoint">
        select xp.id,xp.point_number as pointNumber,xp.point_name as pointName,xp.point_poi as pointPoi,