From c5f7003077b739254ab0dd538775ddb7974daf6d Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 15 Sep 2025 10:13:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(face):=20=E5=A2=9E=E5=8A=A0=E4=BA=BA?= =?UTF-8?q?=E8=84=B8=E7=8A=B6=E6=80=81=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 FaceStatusResp 类用于人脸状态响应- 在 AppFaceController 中添加人脸状态查询相关接口 - 在 FaceService 接口中定义相关方法- 实现 FaceServiceImpl 中的人脸状态查询逻辑 - 优化 ContentPageVO 类,增加 group 字段 --- .../controller/mobile/AppFaceController.java | 27 +++- .../model/mobile/face/FaceStatusResp.java | 34 +++++ .../mobile/scenic/content/ContentPageVO.java | 1 + .../ycwl/basic/service/pc/FaceService.java | 11 ++ .../service/pc/impl/FaceServiceImpl.java | 126 ++++++++++++++++++ src/main/resources/mapper/TemplateMapper.xml | 2 +- 6 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/model/mobile/face/FaceStatusResp.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppFaceController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppFaceController.java index 9f6e1a86..37dbf937 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppFaceController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppFaceController.java @@ -2,8 +2,11 @@ package com.ycwl.basic.controller.mobile; import com.ycwl.basic.model.jwt.JwtInfo; import com.ycwl.basic.model.mobile.face.FaceRecognizeResp; +import com.ycwl.basic.model.mobile.face.FaceStatusResp; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; +import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; +import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO; import com.ycwl.basic.service.pc.FaceService; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.JwtTokenUtil; @@ -20,8 +23,7 @@ import java.util.List; @RestController @RequestMapping("/api/mobile/face/v1") // 用户人脸相关接口 -public class -AppFaceController { +public class AppFaceController { @Autowired private FaceService faceService; @@ -85,4 +87,25 @@ AppFaceController { faceService.bindFace(faceId, userId); return ApiResponse.success("OK"); } + + @GetMapping("/{faceId}/status") + public ApiResponse status(@PathVariable Long faceId) { + return ApiResponse.success(faceService.getFaceStatus(faceId)); + } + + @GetMapping("/{faceId}/extraCheck") + public ApiResponse hasExtraCheck(@PathVariable Long faceId) { + return ApiResponse.success(faceService.checkHasExtraCheck(faceId)); + } + + @GetMapping("/{faceId}/queryOtherFace") + public ApiResponse> queryOtherFace(@PathVariable Long faceId) { + return ApiResponse.success(faceService.getLowMatchedFaceSamples(faceId)); + } + + @PostMapping("/{faceId}/queryOtherFace") + public ApiResponse queryOtherFace(@PathVariable Long faceId, @RequestBody List faceIds) { + faceService.matchCustomFaceId(faceId, faceIds); + return ApiResponse.success("OK"); + } } diff --git a/src/main/java/com/ycwl/basic/model/mobile/face/FaceStatusResp.java b/src/main/java/com/ycwl/basic/model/mobile/face/FaceStatusResp.java new file mode 100644 index 00000000..58cdbc4c --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/mobile/face/FaceStatusResp.java @@ -0,0 +1,34 @@ +package com.ycwl.basic.model.mobile.face; + +import lombok.Data; + +/** + * 人脸状态响应对象 + * + * @author Claude Code + */ +@Data +public class FaceStatusResp { + /** + * 人脸ID + */ + private Long faceId; + private String faceUrl; + + /** + * 识别次数,0表示未识别过 + */ + private Long recognitionCount; + + /** + * 是否触发过低阈值检测 + */ + private Boolean hasLowThreshold; + private String displayText; + private boolean step1Status; +// private String step1Text; + private boolean step2Status; +// private String step2Text; + private boolean step3Status; +// private String step3Text; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/model/mobile/scenic/content/ContentPageVO.java b/src/main/java/com/ycwl/basic/model/mobile/scenic/content/ContentPageVO.java index 1dab0135..7a5f1ff5 100644 --- a/src/main/java/com/ycwl/basic/model/mobile/scenic/content/ContentPageVO.java +++ b/src/main/java/com/ycwl/basic/model/mobile/scenic/content/ContentPageVO.java @@ -13,6 +13,7 @@ import java.math.BigDecimal; public class ContentPageVO { // 内容名称 private String name; + private String group; // 景区id private Long scenicId; // 景区名称 diff --git a/src/main/java/com/ycwl/basic/service/pc/FaceService.java b/src/main/java/com/ycwl/basic/service/pc/FaceService.java index 2037016e..6d720067 100644 --- a/src/main/java/com/ycwl/basic/service/pc/FaceService.java +++ b/src/main/java/com/ycwl/basic/service/pc/FaceService.java @@ -2,10 +2,13 @@ package com.ycwl.basic.service.pc; import com.github.pagehelper.PageInfo; import com.ycwl.basic.model.mobile.face.FaceRecognizeResp; +import com.ycwl.basic.model.mobile.face.FaceStatusResp; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.face.req.FaceReqQuery; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; +import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; +import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO; import com.ycwl.basic.model.task.resp.SearchFaceRespVo; import com.ycwl.basic.utils.ApiResponse; import org.springframework.web.multipart.MultipartFile; @@ -40,4 +43,12 @@ public interface FaceService { void bindFace(Long faceId, Long memberId); String bindWxaCode(Long faceId); + + FaceStatusResp getFaceStatus(Long faceId); + + Boolean checkHasExtraCheck(Long faceId); + + List getLowMatchedFaceSamples(Long faceId); + + void matchCustomFaceId(Long faceId, List faceSampleIds); } diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java index 351bf714..1a3bad93 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java @@ -9,7 +9,9 @@ import com.ycwl.basic.constant.BaseContextHandler; import com.ycwl.basic.enums.StatisticEnum; import com.ycwl.basic.exception.BaseException; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; +import com.ycwl.basic.facebody.entity.SearchFaceResultItem; import com.ycwl.basic.integration.common.manager.DeviceConfigManager; +import com.ycwl.basic.mapper.FaceSampleMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.mapper.FaceMapper; @@ -17,6 +19,7 @@ import com.ycwl.basic.mapper.TemplateMapper; import com.ycwl.basic.mapper.VideoMapper; import com.ycwl.basic.mapper.OrderMapper; import com.ycwl.basic.model.mobile.face.FaceRecognizeResp; +import com.ycwl.basic.model.mobile.face.FaceStatusResp; import com.ycwl.basic.model.mobile.order.IsBuyRespVO; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq; @@ -25,6 +28,7 @@ import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.face.req.FaceReqQuery; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; +import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO; import com.ycwl.basic.model.pc.mp.MpConfigEntity; import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity; @@ -52,6 +56,7 @@ import com.ycwl.basic.storage.utils.StorageUtil; import com.ycwl.basic.task.VideoPieceGetter; import com.ycwl.basic.utils.*; import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -116,6 +121,8 @@ public class FaceServiceImpl implements FaceService { private OrderMapper orderMapper; @Autowired private RedisTemplate redisTemplate; + @Autowired + private FaceSampleMapper faceSampleMapper; @Override public ApiResponse> pageQuery(FaceReqQuery faceReqQuery) { @@ -698,6 +705,8 @@ public class FaceServiceImpl implements FaceService { sourceImageContent.setContentType(2); sourceVideoContent.setLockType(-1); sourceImageContent.setLockType(-1); + sourceVideoContent.setGroup("直出原片"); + sourceImageContent.setGroup("直出原片"); ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(faceRespVO.getScenicId()); if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"))) { IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 2, faceId); @@ -816,6 +825,123 @@ public class FaceServiceImpl implements FaceService { } } + @Override + public FaceStatusResp getFaceStatus(Long faceId) { + if (faceId == null) { + return null; + } + + FaceStatusResp statusResp = new FaceStatusResp(); + statusResp.setFaceId(faceId); + + FaceEntity face = faceRepository.getFace(faceId); + if (face == null) { + statusResp.setStep1Status(false); + statusResp.setDisplayText("请重新录入人脸"); + return statusResp; + } + statusResp.setStep1Status(true); + + statusResp.setFaceUrl(face.getFaceUrl()); + + // 查询识别次数 + String countKey = FACE_RECOGNITION_COUNT_PFX + faceId; + String countStr = redisTemplate.opsForValue().get(countKey); + long recognitionCount = 0L; + if (countStr != null) { + try { + recognitionCount = Long.parseLong(countStr); + } catch (NumberFormatException e) { + log.warn("识别次数解析失败,faceId={}, count={}", faceId, countStr); + } + } + statusResp.setRecognitionCount(recognitionCount); + + // 查询是否触发过低阈值检测 + String lowThresholdKey = FACE_LOW_THRESHOLD_PFX + faceId; + Boolean hasLowThreshold = redisTemplate.hasKey(lowThresholdKey); + statusResp.setHasLowThreshold(hasLowThreshold); + + log.debug("查询人脸状态:faceId={}, recognitionCount={}, hasLowThreshold={}", + faceId, recognitionCount, hasLowThreshold); + + SourceReqQuery sourceReqQuery = new SourceReqQuery(); + sourceReqQuery.setMemberId(face.getMemberId()); + sourceReqQuery.setFaceId(faceId); + sourceReqQuery.setType(2); + Integer countUser = sourceMapper.countUser(sourceReqQuery); + if (countUser != null && countUser > 0) { + statusResp.setStep2Status(true); + } else { + statusResp.setStep2Status(false); + statusResp.setDisplayText("Hey,快去智能机位打卡吧"); + } + List videoEntities = videoMapper.listRelationByFace(face.getMemberId(), faceId); + if (videoEntities != null && !videoEntities.isEmpty()) { + statusResp.setStep3Status(true); + statusResp.setDisplayText("帧途AI已为您渲染"+ videoEntities.size() +"个vlog"); + } else { + statusResp.setStep3Status(false); + statusResp.setDisplayText("帧途AI将会为您渲染vlog,请稍后"); + } + return statusResp; + } + + @Override + public Boolean checkHasExtraCheck(Long faceId) { + FaceEntity face = faceRepository.getFace(faceId); + if (face == null) { + return false; + } + String countKey = FACE_RECOGNITION_COUNT_PFX + faceId; + String countStr = redisTemplate.opsForValue().get(countKey); + long recognitionCount = 0L; + if (countStr != null) { + try { + recognitionCount = Long.parseLong(countStr); + } catch (NumberFormatException e) { + log.warn("识别次数解析失败,faceId={}, count={}", faceId, countStr); + } + } + // 查询是否触发过低阈值检测 + String lowThresholdKey = FACE_LOW_THRESHOLD_PFX + faceId; + Boolean hasLowThreshold = redisTemplate.hasKey(lowThresholdKey); + return true; + } + + @Override + public List getLowMatchedFaceSamples(Long faceId) { + FaceEntity face = faceMapper.get(faceId); + if (face == null) { + return List.of(); + } + String matchResult = face.getMatchResult(); + if (matchResult == null || Strings.isBlank(matchResult)) { + return List.of(); + } + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId()); + if (scenicConfig == null) { + return List.of(); + } + Float lowThreshold = scenicConfig.getFloat("face_score_low_threshold", 30.0F); + List resultItems = JacksonUtil.fromJsonToList(matchResult, SearchFaceResultItem.class); + if (resultItems == null || resultItems.isEmpty()) { + return List.of(); + } + List idList = resultItems.stream().filter(item -> item.getScore() > lowThreshold/100F) + .map(SearchFaceResultItem::getExtData).limit(9).map(Long::valueOf).toList(); + if (idList.isEmpty()) { + return List.of(); + } + List sampleEntities = faceSampleMapper.listByIds(idList); + return sampleEntities; + } + + @Override + public void matchCustomFaceId(Long faceId, List faceSampleIds) { + + } + /** * 记录人脸识别次数到Redis * diff --git a/src/main/resources/mapper/TemplateMapper.xml b/src/main/resources/mapper/TemplateMapper.xml index 5d3e01f5..16562b29 100644 --- a/src/main/resources/mapper/TemplateMapper.xml +++ b/src/main/resources/mapper/TemplateMapper.xml @@ -114,7 +114,7 @@ order by sort