feat(printer):优化人脸样本使用逻辑并增强景区列表查询

- 修改 useSample 接口返回类型为 FaceRecognizeResp
- 增加根据样本ID和类型查询来源实体的逻辑
- 在景区列表查询中添加参数校验和异常处理
- 完善景区信息处理流程,增加设备数量统计
-优化景区距离计算与筛选逻辑
- 增加人脸匹配后自动添加照片到用户相册的功能
- 添加 XML 映射文件中新的查询语句实现
This commit is contained in:
2025-11-08 15:04:50 +08:00
parent fb75cbf230
commit 88c31d4fdc
6 changed files with 96 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ package com.ycwl.basic.controller.mobile;
import com.ycwl.basic.annotation.IgnoreToken;
import com.ycwl.basic.model.jwt.JwtInfo;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.pc.printer.resp.MemberPrintResp;
import com.ycwl.basic.model.pc.printer.resp.PrinterResp;
import com.ycwl.basic.model.printer.req.FromSourceReq;
@@ -37,7 +38,7 @@ public class AppPrinterController {
}
@PostMapping("/useSample/{sampleId}")
public ApiResponse<?> useSample(@PathVariable("sampleId") Long sampleId) throws IOException {
public ApiResponse<FaceRecognizeResp> useSample(@PathVariable("sampleId") Long sampleId) throws IOException {
JwtInfo worker = JwtTokenUtil.getWorker();
return ApiResponse.success(printerService.useSample(worker.getUserId(), sampleId));
}

View File

@@ -105,4 +105,6 @@ public interface SourceMapper {
* @return 影响行数
*/
int addFromZTSource(SourceEntity source);
SourceEntity getBySampleIdAndType(Long faceSampleId, Integer type);
}

View File

@@ -220,58 +220,98 @@ public class AppScenicServiceImpl implements AppScenicService {
@Override
public List<ScenicAppVO> scenicListByLnLa(ScenicIndexVO scenicIndexVO) {
// 参数校验
if (scenicIndexVO == null) {
log.warn("scenicListByLnLa 接收到空参数");
return Collections.emptyList();
}
if (scenicIndexVO.getLatitude() == null || scenicIndexVO.getLongitude() == null) {
log.warn("scenicListByLnLa 缺少必要的经纬度参数, latitude={}, longitude={}",
scenicIndexVO.getLatitude(), scenicIndexVO.getLongitude());
return Collections.emptyList();
}
// 从 scenicRepository 获取所有景区(1000个)
ScenicReqQuery query = new ScenicReqQuery();
query.setPageNum(1);
query.setPageSize(1000);
List<ScenicV2DTO> scenicList = scenicRepository.list(query);
if (scenicList == null || scenicList.isEmpty()) {
log.info("未查询到任何景区数据");
return Collections.emptyList();
}
List<ScenicAppVO> list = new ArrayList<>();
// 为每个景区获取详细信息(包含经纬度)
for (ScenicV2DTO scenicDTO : scenicList) {
try {
// ID 格式校验
if (StringUtils.isBlank(scenicDTO.getId())) {
log.warn("景区 ID 为空,跳过该景区");
continue;
}
// 获取景区详细信息(包含经纬度)
ScenicEntity scenicEntity = scenicRepository.getScenic(Long.parseLong(scenicDTO.getId()));
if (scenicEntity != null && scenicEntity.getLatitude() != null && scenicEntity.getLongitude() != null) {
// 计算距离
BigDecimal distance = calculateDistance(
scenicIndexVO.getLatitude(),
scenicIndexVO.getLongitude(),
scenicEntity.getLatitude(),
scenicEntity.getLongitude()
);
// 根据距离和范围筛选景区
if (scenicEntity.getRadius() != null &&
distance.compareTo(scenicEntity.getRadius().multiply(BigDecimal.valueOf(1_000L))) < 0) {
// 转换为 ScenicAppVO
ScenicAppVO scenicAppVO = new ScenicAppVO();
scenicAppVO.setId(scenicEntity.getId());
scenicAppVO.setName(scenicEntity.getName());
scenicAppVO.setPhone(scenicEntity.getPhone());
scenicAppVO.setIntroduction(scenicEntity.getIntroduction());
scenicAppVO.setCoverUrl(scenicEntity.getCoverUrl());
scenicAppVO.setLongitude(scenicEntity.getLongitude());
scenicAppVO.setLatitude(scenicEntity.getLatitude());
scenicAppVO.setRadius(scenicEntity.getRadius());
scenicAppVO.setProvince(scenicEntity.getProvince());
scenicAppVO.setCity(scenicEntity.getCity());
scenicAppVO.setArea(scenicEntity.getArea());
scenicAppVO.setAddress(scenicEntity.getAddress());
scenicAppVO.setDistance(distance);
scenicAppVO.setDeviceNum(deviceRepository.getAllDeviceByScenicId(scenicEntity.getId()).size());
list.add(scenicAppVO);
}
if (scenicEntity == null) {
log.warn("景区详情查询失败, scenicId={}", scenicDTO.getId());
continue;
}
if (scenicEntity.getLatitude() == null || scenicEntity.getLongitude() == null) {
log.warn("景区缺少经纬度信息, scenicId={}, scenicName={}",
scenicEntity.getId(), scenicEntity.getName());
continue;
}
// 计算距离
BigDecimal distance = calculateDistance(
scenicIndexVO.getLatitude(),
scenicIndexVO.getLongitude(),
scenicEntity.getLatitude(),
scenicEntity.getLongitude()
);
// 根据距离和范围筛选景区
if (scenicEntity.getRadius() != null &&
distance.compareTo(scenicEntity.getRadius().multiply(BigDecimal.valueOf(1_000L))) < 0) {
// 转换为 ScenicAppVO
ScenicAppVO scenicAppVO = new ScenicAppVO();
scenicAppVO.setId(scenicEntity.getId());
scenicAppVO.setName(scenicEntity.getName());
scenicAppVO.setPhone(scenicEntity.getPhone());
scenicAppVO.setIntroduction(scenicEntity.getIntroduction());
scenicAppVO.setCoverUrl(scenicEntity.getCoverUrl());
scenicAppVO.setLongitude(scenicEntity.getLongitude());
scenicAppVO.setLatitude(scenicEntity.getLatitude());
scenicAppVO.setRadius(scenicEntity.getRadius());
scenicAppVO.setProvince(scenicEntity.getProvince());
scenicAppVO.setCity(scenicEntity.getCity());
scenicAppVO.setArea(scenicEntity.getArea());
scenicAppVO.setAddress(scenicEntity.getAddress());
scenicAppVO.setDistance(distance);
// 获取设备数量
List<DeviceV2DTO> devices = deviceRepository.getAllDeviceByScenicId(scenicEntity.getId());
scenicAppVO.setDeviceNum(devices != null ? devices.size() : 0);
list.add(scenicAppVO);
}
} catch (NumberFormatException e) {
log.error("景区 ID 格式错误,无法转换为 Long 类型, scenicId={}, error={}",
scenicDTO.getId(), e.getMessage());
} catch (Exception e) {
// 单个景区获取失败,继续处理下一个
continue;
log.error("处理景区信息时发生异常, scenicId={}, error={}",
scenicDTO != null ? scenicDTO.getId() : "unknown", e.getMessage(), e);
}
}
log.info("根据经纬度筛选景区完成, 输入坐标=({}, {}), 符合条件的景区数量={}",
scenicIndexVO.getLatitude(), scenicIndexVO.getLongitude(), list.size());
return list;
}

View File

@@ -1,5 +1,6 @@
package com.ycwl.basic.service.printer;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.order.PriceObj;
import com.ycwl.basic.model.pc.printer.entity.PrinterEntity;
import com.ycwl.basic.model.pc.printer.resp.MemberPrintResp;
@@ -56,7 +57,7 @@ public interface PrinterService {
void setUserIsBuyItem(Long memberId, Long id, Long orderId);
Object useSample(Long userId, Long sampleId);
FaceRecognizeResp useSample(Long userId, Long sampleId);
void autoAddPhotosToPreferPrint(Long faceId);
}

View File

@@ -752,9 +752,10 @@ public class PrinterServiceImpl implements PrinterService {
}
@Override
public Object useSample(Long userId, Long sampleId) {
public FaceRecognizeResp useSample(Long userId, Long sampleId) {
// 1. 查询 faceSample 获取其 URL
FaceSampleEntity faceSample = faceSampleMapper.getEntity(sampleId);
SourceEntity sourceEntity = sourceMapper.getBySampleIdAndType(sampleId, 2);
if (faceSample == null) {
throw new BaseException("人脸样本不存在");
}
@@ -807,6 +808,12 @@ public class PrinterServiceImpl implements PrinterService {
resp.setScenicId(scenicId);
faceService.matchFaceId(faceId);
autoAddPhotosToPreferPrint(faceId);
List<MemberPrintResp> userPhotoList = getUserPhotoList(userId, scenicId, faceId);
boolean noneMatch = userPhotoList.stream()
.noneMatch(item -> Strings.CI.equals(item.getOrigUrl(), sourceEntity.getUrl()));
if (noneMatch) {
addUserPhoto(userId, scenicId, sourceEntity.getUrl(), faceId);
}
return resp;
}

View File

@@ -354,4 +354,9 @@
inner join member_source ms on s.id = ms.source_id
where ms.face_id = #{faceId} and s.type = 2
</select>
<select id="getBySampleIdAndType" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity">
select *
from source
where face_sample_id = #{faceSampleId} and type = #{type}
</select>
</mapper>