feat(ai-cam): 新增使用人脸样本创建或获取Face记录功能

- 在AppAiCamController中新增/useSample/{faceSampleId}接口
- 实现通过人脸样本ID查找或创建Face记录的业务逻辑
- 自动关联AI相机照片到用户人脸记录
- 支持AI_CAM设备类型的二维码路径配置
- 完善人脸匹配及日志记录功能
- 添加相关实体类和工具类导入依赖
This commit is contained in:
2025-12-09 16:20:50 +08:00
parent 917cb37ccf
commit 6e7b4729a8
4 changed files with 150 additions and 6 deletions

View File

@@ -1,8 +1,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.goods.GoodsDetailVO;
import com.ycwl.basic.service.mobile.AppAiCamService;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.JwtTokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@@ -58,4 +61,21 @@ public class AppAiCamController {
return ApiResponse.fail("添加关联失败");
}
}
/**
* 使用人脸样本创建或获取Face记录
* @param faceSampleId 人脸样本ID
* @return 人脸识别响应
*/
@GetMapping("/useSample/{faceSampleId}")
public ApiResponse<FaceRecognizeResp> useSample(@PathVariable Long faceSampleId) {
try {
JwtInfo worker = JwtTokenUtil.getWorker();
FaceRecognizeResp resp = appAiCamService.useSample(worker.getUserId(), faceSampleId);
return ApiResponse.success(resp);
} catch (Exception e) {
log.error("使用人脸样本失败: faceSampleId={}", faceSampleId, e);
return ApiResponse.fail("使用人脸样本失败: " + e.getMessage());
}
}
}

View File

@@ -4,6 +4,7 @@ package com.ycwl.basic.controller.printer;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
@@ -99,14 +100,20 @@ public class PrinterTvController {
@GetMapping("/{sampleId}/qrcode")
public void getQrcode(@PathVariable("sampleId") Long sampleId, HttpServletResponse response) throws Exception {
File qrcode = new File("qrcode_"+sampleId+".jpg");
FaceSampleEntity faceSample = faceRepository.getFaceSample(sampleId);
if (faceSample == null) {
response.setStatus(404);
return;
}
String targetPath = "pages/printer/from_sample";
DeviceV2DTO device = deviceRepository.getDeviceBasic(faceSample.getDeviceId());
if (device.getType().equals("AI_CAM")) {
// AI_CAM,需要修改path
targetPath = "pages/ai-cam/from_sample";
}
try {
FaceSampleEntity faceSample = faceRepository.getFaceSample(sampleId);
if (faceSample == null) {
response.setStatus(404);
return;
}
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(faceSample.getScenicId());
WxMpUtil.generateUnlimitedWXAQRCode(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), "pages/printer/from_sample", sampleId.toString(), qrcode);
WxMpUtil.generateUnlimitedWXAQRCode(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), targetPath, sampleId.toString(), qrcode);
// 设置响应头
response.setContentType("image/jpeg");

View File

@@ -1,5 +1,6 @@
package com.ycwl.basic.service.mobile;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
import java.util.List;
@@ -23,4 +24,12 @@ public interface AppAiCamService {
* @return 添加成功的数量
*/
int addMemberSourceRelations(Long faceId, List<Long> sourceIds);
/**
* 使用人脸样本创建或获取Face记录
* @param userId 用户ID
* @param faceSampleId 人脸样本ID
* @return 人脸识别响应
*/
FaceRecognizeResp useSample(Long userId, Long faceSampleId);
}

View File

@@ -1,21 +1,36 @@
package com.ycwl.basic.service.mobile.impl;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.facebody.FaceBodyFactory;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.entity.SearchFaceResp;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.mapper.FaceDetectLogAiCamMapper;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceDetectLog.entity.FaceDetectLogAiCamEntity;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.AppAiCamService;
import com.ycwl.basic.service.pc.FaceDetectLogAiCamService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.utils.JacksonUtil;
import com.ycwl.basic.utils.SnowFlakeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.*;
@@ -33,10 +48,14 @@ public class AppAiCamServiceImpl implements AppAiCamService {
private final SourceMapper sourceMapper;
private final FaceMapper faceMapper;
private final DeviceRepository deviceRepository;
private final FaceSampleMapper faceSampleMapper;
private static final float DEFAULT_SCORE_THRESHOLD = 0.8f;
private static final int DEFAULT_PHOTO_LIMIT = 10;
private static final int AI_CAM_SOURCE_TYPE = 3;
private final FaceService faceService;
private final FaceDetectLogAiCamService faceDetectLogAiCamService;
private final ScenicService scenicService;
@Override
public List<GoodsDetailVO> getAiCamGoodsByFaceId(Long faceId) {
@@ -272,4 +291,93 @@ public class AppAiCamServiceImpl implements AppAiCamService {
return inserted;
}
@Override
public FaceRecognizeResp useSample(Long userId, Long faceSampleId) {
// 1. 查询 faceSample 获取其 URL
FaceSampleEntity faceSample = faceSampleMapper.getEntity(faceSampleId);
if (faceSample == null) {
throw new BaseException("人脸样本不存在");
}
String faceUrl = faceSample.getFaceUrl();
if (StringUtils.isBlank(faceUrl)) {
throw new BaseException("人脸样本URL为空");
}
Long scenicId = faceSample.getScenicId();
// 2. 检查face数据库中有没有同用户、同URL的face记录
FaceEntity existingFace = null;
Long faceId = null;
// 查询该用户在该景区的所有人脸记录
List<FaceRespVO> userFaces = faceMapper.listByScenicAndUserId(scenicId, userId);
// 查找是否存在相同URL的记录
for (FaceRespVO faceResp : userFaces) {
if (faceUrl.equals(faceResp.getFaceUrl())) {
existingFace = faceMapper.get(faceResp.getId());
faceId = existingFace.getId();
break;
}
}
// 3. 如果不存在,则新建一个face记录
if (existingFace == null) {
faceId = SnowFlakeUtil.getLongId();
FaceEntity newFace = new FaceEntity();
newFace.setId(faceId);
newFace.setCreateAt(new Date());
newFace.setScenicId(scenicId);
newFace.setMemberId(userId);
newFace.setFaceUrl(faceUrl);
faceMapper.add(newFace);
log.info("创建新的face记录, userId: {}, faceSampleId: {}, faceId: {}, faceUrl: {}",
userId, faceSampleId, faceId, faceUrl);
} else {
log.info("使用已存在的face记录, userId: {}, faceSampleId: {}, faceId: {}, faceUrl: {}",
userId, faceSampleId, faceId, faceUrl);
}
// 4. 查询对应的 type=3 的 source 记录并自动添加关联
SourceEntity sourceEntity = sourceMapper.getBySampleIdAndType(faceSampleId, AI_CAM_SOURCE_TYPE);
if (sourceEntity != null && existingFace == null) {
// 检查是否已存在该source的关联
List<GoodsDetailVO> existingGoods = getAiCamGoodsByFaceId(faceId);
boolean alreadyExists = existingGoods.stream()
.anyMatch(item -> Objects.equals(item.getGoodsId(), sourceEntity.getId()));
if (!alreadyExists) {
// 添加关联
MemberSourceEntity relation = new MemberSourceEntity();
relation.setMemberId(userId);
relation.setScenicId(scenicId);
relation.setFaceId(faceId);
relation.setSourceId(sourceEntity.getId());
relation.setType(AI_CAM_SOURCE_TYPE);
relation.setIsBuy(0);
relation.setIsFree(0);
sourceMapper.addRelations(Collections.singletonList(relation));
log.info("自动添加AI相机照片关联: userId={}, faceId={}, sourceId={}",
userId, faceId, sourceEntity.getId());
}
}
// 5. 返回结果
FaceRecognizeResp resp = new FaceRecognizeResp();
resp.setUrl(faceUrl);
resp.setFaceId(faceId);
resp.setScenicId(scenicId);
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId);
try {
faceService.matchFaceId(faceId);
faceDetectLogAiCamService.searchAndLog(scenicId, faceId, faceUrl, faceBodyAdapter);
} catch (Exception e) {
// 人脸匹配失败不可以阻止正常流程
log.error("人脸匹配失败", e);
}
return resp;
}
}