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;
|
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.model.mobile.goods.GoodsDetailVO;
|
||||||
import com.ycwl.basic.service.mobile.AppAiCamService;
|
import com.ycwl.basic.service.mobile.AppAiCamService;
|
||||||
import com.ycwl.basic.utils.ApiResponse;
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
|
import com.ycwl.basic.utils.JwtTokenUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -58,4 +61,21 @@ public class AppAiCamController {
|
|||||||
return ApiResponse.fail("添加关联失败");
|
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 cn.hutool.core.date.DateUtil;
|
||||||
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
||||||
import com.ycwl.basic.mapper.FaceMapper;
|
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.face.entity.FaceEntity;
|
||||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||||
@@ -99,14 +100,20 @@ public class PrinterTvController {
|
|||||||
@GetMapping("/{sampleId}/qrcode")
|
@GetMapping("/{sampleId}/qrcode")
|
||||||
public void getQrcode(@PathVariable("sampleId") Long sampleId, HttpServletResponse response) throws Exception {
|
public void getQrcode(@PathVariable("sampleId") Long sampleId, HttpServletResponse response) throws Exception {
|
||||||
File qrcode = new File("qrcode_"+sampleId+".jpg");
|
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 {
|
try {
|
||||||
FaceSampleEntity faceSample = faceRepository.getFaceSample(sampleId);
|
|
||||||
if (faceSample == null) {
|
|
||||||
response.setStatus(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(faceSample.getScenicId());
|
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");
|
response.setContentType("image/jpeg");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ycwl.basic.service.mobile;
|
package com.ycwl.basic.service.mobile;
|
||||||
|
|
||||||
|
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
|
||||||
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
|
import com.ycwl.basic.model.mobile.goods.GoodsDetailVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -23,4 +24,12 @@ public interface AppAiCamService {
|
|||||||
* @return 添加成功的数量
|
* @return 添加成功的数量
|
||||||
*/
|
*/
|
||||||
int addMemberSourceRelations(Long faceId, List<Long> sourceIds);
|
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;
|
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.SearchFaceResp;
|
||||||
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
|
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
|
||||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
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.FaceDetectLogAiCamMapper;
|
||||||
import com.ycwl.basic.mapper.FaceMapper;
|
import com.ycwl.basic.mapper.FaceMapper;
|
||||||
|
import com.ycwl.basic.mapper.FaceSampleMapper;
|
||||||
import com.ycwl.basic.mapper.SourceMapper;
|
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.mobile.goods.GoodsDetailVO;
|
||||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
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.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.MemberSourceEntity;
|
||||||
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
|
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
|
||||||
import com.ycwl.basic.repository.DeviceRepository;
|
import com.ycwl.basic.repository.DeviceRepository;
|
||||||
|
import com.ycwl.basic.repository.ScenicRepository;
|
||||||
import com.ycwl.basic.service.mobile.AppAiCamService;
|
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.JacksonUtil;
|
||||||
|
import com.ycwl.basic.utils.SnowFlakeUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -33,10 +48,14 @@ public class AppAiCamServiceImpl implements AppAiCamService {
|
|||||||
private final SourceMapper sourceMapper;
|
private final SourceMapper sourceMapper;
|
||||||
private final FaceMapper faceMapper;
|
private final FaceMapper faceMapper;
|
||||||
private final DeviceRepository deviceRepository;
|
private final DeviceRepository deviceRepository;
|
||||||
|
private final FaceSampleMapper faceSampleMapper;
|
||||||
|
|
||||||
private static final float DEFAULT_SCORE_THRESHOLD = 0.8f;
|
private static final float DEFAULT_SCORE_THRESHOLD = 0.8f;
|
||||||
private static final int DEFAULT_PHOTO_LIMIT = 10;
|
private static final int DEFAULT_PHOTO_LIMIT = 10;
|
||||||
private static final int AI_CAM_SOURCE_TYPE = 3;
|
private static final int AI_CAM_SOURCE_TYPE = 3;
|
||||||
|
private final FaceService faceService;
|
||||||
|
private final FaceDetectLogAiCamService faceDetectLogAiCamService;
|
||||||
|
private final ScenicService scenicService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<GoodsDetailVO> getAiCamGoodsByFaceId(Long faceId) {
|
public List<GoodsDetailVO> getAiCamGoodsByFaceId(Long faceId) {
|
||||||
@@ -272,4 +291,93 @@ public class AppAiCamServiceImpl implements AppAiCamService {
|
|||||||
|
|
||||||
return inserted;
|
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