You've already forked FrameTour-BE
feat(face): 增加人脸状态查询功能
- 新增 FaceStatusResp 类用于人脸状态响应- 在 AppFaceController 中添加人脸状态查询相关接口 - 在 FaceService 接口中定义相关方法- 实现 FaceServiceImpl 中的人脸状态查询逻辑 - 优化 ContentPageVO 类,增加 group 字段
This commit is contained in:
@@ -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<FaceStatusResp> status(@PathVariable Long faceId) {
|
||||
return ApiResponse.success(faceService.getFaceStatus(faceId));
|
||||
}
|
||||
|
||||
@GetMapping("/{faceId}/extraCheck")
|
||||
public ApiResponse<Boolean> hasExtraCheck(@PathVariable Long faceId) {
|
||||
return ApiResponse.success(faceService.checkHasExtraCheck(faceId));
|
||||
}
|
||||
|
||||
@GetMapping("/{faceId}/queryOtherFace")
|
||||
public ApiResponse<List<FaceSampleEntity>> queryOtherFace(@PathVariable Long faceId) {
|
||||
return ApiResponse.success(faceService.getLowMatchedFaceSamples(faceId));
|
||||
}
|
||||
|
||||
@PostMapping("/{faceId}/queryOtherFace")
|
||||
public ApiResponse<String> queryOtherFace(@PathVariable Long faceId, @RequestBody List<Long> faceIds) {
|
||||
faceService.matchCustomFaceId(faceId, faceIds);
|
||||
return ApiResponse.success("OK");
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -13,6 +13,7 @@ import java.math.BigDecimal;
|
||||
public class ContentPageVO {
|
||||
// 内容名称
|
||||
private String name;
|
||||
private String group;
|
||||
// 景区id
|
||||
private Long scenicId;
|
||||
// 景区名称
|
||||
|
@@ -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<FaceSampleEntity> getLowMatchedFaceSamples(Long faceId);
|
||||
|
||||
void matchCustomFaceId(Long faceId, List<Long> faceSampleIds);
|
||||
}
|
||||
|
@@ -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<String, String> redisTemplate;
|
||||
@Autowired
|
||||
private FaceSampleMapper faceSampleMapper;
|
||||
|
||||
@Override
|
||||
public ApiResponse<PageInfo<FaceRespVO>> 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<MemberVideoEntity> 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<FaceSampleEntity> 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<SearchFaceResultItem> resultItems = JacksonUtil.fromJsonToList(matchResult, SearchFaceResultItem.class);
|
||||
if (resultItems == null || resultItems.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<Long> 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<FaceSampleEntity> sampleEntities = faceSampleMapper.listByIds(idList);
|
||||
return sampleEntities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void matchCustomFaceId(Long faceId, List<Long> faceSampleIds) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录人脸识别次数到Redis
|
||||
*
|
||||
|
@@ -114,7 +114,7 @@
|
||||
order by sort
|
||||
</select>
|
||||
<select id="listFor" resultType="com.ycwl.basic.model.mobile.scenic.content.ContentPageVO">
|
||||
select t.id templateId, t.scenic_id, t.`name`, pid, t.cover_url templateCoverUrl,
|
||||
select t.id templateId, t.scenic_id, t.`group`, t.`name`, pid, t.cover_url templateCoverUrl,
|
||||
0 as sourceType, sort,
|
||||
t.create_time, t.price
|
||||
from template t
|
||||
|
Reference in New Issue
Block a user