You've already forked FrameTour-BE
feat(ai-cam): 新增使用人脸样本创建或获取Face记录功能
- 在AppAiCamController中新增/useSample/{faceSampleId}接口
- 实现通过人脸样本ID查找或创建Face记录的业务逻辑
- 自动关联AI相机照片到用户人脸记录
- 支持AI_CAM设备类型的二维码路径配置
- 完善人脸匹配及日志记录功能
- 添加相关实体类和工具类导入依赖
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
try {
|
||||
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 {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user