feat(printer): 新增人脸绑定二维码生成功能

- 添加获取人脸绑定二维码接口,支持生成小程序二维码
- 实现二维码文件流输出及临时文件清理
- 修改人脸识别流程,保存人脸数据并返回faceId
- 调整自动添加照片到打印列表逻辑,返回添加数量
- 更新响应模型,增加二维码URL字段
- 优化人脸匹配逻辑,使用memberRelationRepository查询关联照片
- 修复BCE适配器图片下载地址问题,去除内网地址替换逻辑
This commit is contained in:
2025-12-04 18:21:18 +08:00
parent 15dda645b9
commit 42540e2dc4
5 changed files with 109 additions and 27 deletions

View File

@@ -3,7 +3,13 @@ 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.model.pc.face.entity.FaceEntity;
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;
import com.ycwl.basic.repository.MemberRelationRepository;
import com.ycwl.basic.repository.SourceRepository;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.service.task.TaskFaceService; import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.annotation.IgnoreToken;
@@ -23,6 +29,7 @@ import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.storage.utils.StorageUtil; import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.WxMpUtil; import com.ycwl.basic.utils.WxMpUtil;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -55,8 +62,13 @@ public class PrinterTvController {
private final ScenicRepository scenicRepository; private final ScenicRepository scenicRepository;
private final FaceRepository faceRepository; private final FaceRepository faceRepository;
private final TaskFaceService faceService; private final TaskFaceService faceService;
private final FaceService pcFaceService;
private final ScenicService scenicService; private final ScenicService scenicService;
private final SourceMapper sourceMapper; private final SourceMapper sourceMapper;
private final FaceMapper faceMapper;
private final MemberRelationRepository memberRelationRepository;
private final SourceRepository sourceRepository;
private final PrinterService printerService;
/** /**
* 获取景区列表 * 获取景区列表
@@ -118,6 +130,57 @@ public class PrinterTvController {
} }
} }
/**
* 获取人脸绑定二维码
* 生成小程序二维码,用于绑定人脸到用户账号
*
* @param faceId 人脸ID
* @param response HTTP响应
*/
@GetMapping("/face/{faceId}/qrcode")
public void getFaceQrcode(@PathVariable("faceId") Long faceId, HttpServletResponse response) throws Exception {
File qrcode = new File("qrcode_face_" + faceId + ".jpg");
try {
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
response.setStatus(404);
return;
}
MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(face.getScenicId());
if (scenicMpConfig == null) {
response.setStatus(500);
return;
}
WxMpUtil.generateUnlimitedWXAQRCode(
scenicMpConfig.getAppId(),
scenicMpConfig.getAppSecret(),
"pages/videoSynthesis/bind_face",
faceId.toString(),
qrcode
);
// 设置响应头
response.setContentType("image/jpeg");
response.setHeader("Content-Disposition", "inline; filename=\"" + qrcode.getName() + "\"");
// 将二维码文件写入响应输出流
try (FileInputStream fis = new FileInputStream(qrcode);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
} finally {
// 删除临时文件
if (qrcode.exists()) {
qrcode.delete();
}
}
}
/** /**
* 根据人脸样本ID查询图像素材 * 根据人脸样本ID查询图像素材
* *
@@ -154,35 +217,44 @@ public class PrinterTvController {
String fileName = UUID.randomUUID() + "." + suffix; String fileName = UUID.randomUUID() + "." + suffix;
String faceUrl = adapter.uploadFile(file, filePath, fileName); String faceUrl = adapter.uploadFile(file, filePath, fileName);
// 2. 获取景区人脸识别适配器 // 2. 保存人脸数据到数据库
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId); Long faceId = SnowFlakeUtil.getLongId();
FaceEntity faceEntity = new FaceEntity();
faceEntity.setId(faceId);
faceEntity.setScenicId(scenicId);
faceEntity.setFaceUrl(faceUrl);
faceEntity.setCreateAt(new Date());
faceEntity.setMemberId(0L); // 打印机大屏端没有用户ID
faceMapper.add(faceEntity);
// 3. 在景区人脸库中搜索(注意:这里使用scenicId作为数据库名,搜索的是景区内的人脸样本) // 3. 在景区人脸库中搜索(注意:这里使用scenicId作为数据库名,搜索的是景区内的人脸样本)
SearchFaceRespVo searchResult = faceService.searchFace( pcFaceService.matchFaceId(faceId);
faceBodyAdapter,
String.valueOf(scenicId),
faceUrl,
"大屏端人脸识别"
);
// 4. 查询匹配到的图像素材(type=2) // 4. 自动添加照片到打印列表
int addedCount = printerService.autoAddPhotosToPreferPrint(faceId);
// 5. 查询匹配到的图像素材(type=2)
List<SourceEntity> sources = new ArrayList<>(); List<SourceEntity> sources = new ArrayList<>();
if (searchResult != null && searchResult.getSampleListIds() != null && !searchResult.getSampleListIds().isEmpty()) { List<MemberSourceEntity> memberSourceEntities = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
// 遍历匹配到的人脸样本ID,查询对应的图像素材 for (MemberSourceEntity memberSourceEntity : memberSourceEntities) {
for (Long sampleId : searchResult.getSampleListIds()) { SourceEntity source = sourceRepository.getSource(memberSourceEntity.getSourceId());
SourceEntity source = sourceMapper.getBySampleIdAndType(sampleId, 2); if (source != null) {
if (source != null) { sources.add(source);
sources.add(source);
}
} }
} }
// 5. 构造响应 // 6. 构造响应
FaceRecognizeWithSourcesResp resp = new FaceRecognizeWithSourcesResp(); FaceRecognizeWithSourcesResp resp = new FaceRecognizeWithSourcesResp();
resp.setUrl(faceUrl); resp.setUrl(faceUrl);
resp.setFaceId(null); // 不保存人脸,所以没有faceId resp.setFaceId(faceId);
resp.setScenicId(scenicId); resp.setScenicId(scenicId);
resp.setSources(sources); resp.setSources(sources);
// 只有当添加了照片时才返回二维码URL
if (addedCount > 0) {
resp.setQrcodeUrl("https://zhentuai.com/printer/v1/tv/face/" + faceId + "/qrcode");
} else {
resp.setQrcodeUrl(null);
}
return ApiResponse.success(resp); return ApiResponse.success(resp);
} }

View File

@@ -418,7 +418,7 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
ByteArrayOutputStream baos = null; ByteArrayOutputStream baos = null;
try { try {
// 下载图片 // 下载图片
URL url = new URL(imageUrl.replace("oss-cn-shanghai.aliyuncs.com", "oss-cn-shanghai-internal.aliyuncs.com")); URL url = new URL(imageUrl);
image = ImageIO.read(url); image = ImageIO.read(url);
if (image == null) { if (image == null) {
log.error("无法读取图片,URL: {}", imageUrl); log.error("无法读取图片,URL: {}", imageUrl);

View File

@@ -31,4 +31,9 @@ public class FaceRecognizeWithSourcesResp {
* 匹配到的图像素材列表(type=2) * 匹配到的图像素材列表(type=2)
*/ */
private List<SourceEntity> sources; private List<SourceEntity> sources;
/**
* 二维码URL
*/
private String qrcodeUrl;
} }

View File

@@ -61,7 +61,7 @@ public interface PrinterService {
FaceRecognizeResp useSample(Long userId, Long sampleId); FaceRecognizeResp useSample(Long userId, Long sampleId);
void autoAddPhotosToPreferPrint(Long faceId); int autoAddPhotosToPreferPrint(Long faceId);
/** /**
* 查询待审核的打印任务 * 查询待审核的打印任务

View File

@@ -1126,15 +1126,16 @@ public class PrinterServiceImpl implements PrinterService {
* 根据景区和设备配置自动添加type=2的照片到用户打印列表 * 根据景区和设备配置自动添加type=2的照片到用户打印列表
* *
* @param faceId 人脸ID * @param faceId 人脸ID
* @return 成功添加的照片数量
*/ */
@Override @Override
public void autoAddPhotosToPreferPrint(Long faceId) { public int autoAddPhotosToPreferPrint(Long faceId) {
try { try {
// 1. 获取人脸信息 // 1. 获取人脸信息
FaceEntity face = faceRepository.getFace(faceId); FaceEntity face = faceRepository.getFace(faceId);
if (face == null) { if (face == null) {
log.warn("人脸不存在,无法自动添加打印: faceId={}", faceId); log.warn("人脸不存在,无法自动添加打印: faceId={}", faceId);
return; return 0;
} }
Long scenicId = face.getScenicId(); Long scenicId = face.getScenicId();
@@ -1144,21 +1145,21 @@ public class PrinterServiceImpl implements PrinterService {
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
if (scenicConfig == null) { if (scenicConfig == null) {
log.warn("景区配置不存在,跳过自动添加打印: scenicId={}", scenicId); log.warn("景区配置不存在,跳过自动添加打印: scenicId={}", scenicId);
return; return 0;
} }
// 3. 检查景区是否启用打印功能 // 3. 检查景区是否启用打印功能
Boolean printEnable = scenicConfig.getBoolean("print_enable"); Boolean printEnable = scenicConfig.getBoolean("print_enable");
if (printEnable == null || !printEnable) { if (printEnable == null || !printEnable) {
log.debug("景区未启用打印功能,跳过自动添加: scenicId={}", scenicId); log.debug("景区未启用打印功能,跳过自动添加: scenicId={}", scenicId);
return; return 0;
} }
// 4. 查询该faceId关联的所有type=2的照片 // 4. 查询该faceId关联的所有type=2的照片
List<SourceEntity> imageSources = sourceMapper.listImageSourcesByFaceId(faceId); List<SourceEntity> imageSources = sourceMapper.listImageSourcesByFaceId(faceId);
if (imageSources == null || imageSources.isEmpty()) { if (imageSources == null || imageSources.isEmpty()) {
log.debug("该人脸没有关联的照片,跳过自动添加: faceId={}", faceId); log.debug("该人脸没有关联的照片,跳过自动添加: faceId={}", faceId);
return; return 0;
} }
// 5. 按照deviceId分组处理 // 5. 按照deviceId分组处理
@@ -1246,15 +1247,19 @@ public class PrinterServiceImpl implements PrinterService {
log.error("等待照片添加任务完成时发生异常: faceId={}", faceId, e); log.error("等待照片添加任务完成时发生异常: faceId={}", faceId, e);
} }
if (totalAdded.get() > 0) { int added = totalAdded.get();
log.info("自动添加打印完成: faceId={}, 成功添加{}张照片", faceId, totalAdded.get()); if (added > 0) {
log.info("自动添加打印完成: faceId={}, 成功添加{}张照片", faceId, added);
} else { } else {
log.debug("自动添加打印完成: faceId={}, 无符合条件的照片", faceId); log.debug("自动添加打印完成: faceId={}, 无符合条件的照片", faceId);
} }
return added;
} catch (Exception e) { } catch (Exception e) {
// 出现异常则放弃,不影响主流程 // 出现异常则放弃,不影响主流程
log.error("自动添加打印失败,已忽略: faceId={}", faceId, e); log.error("自动添加打印失败,已忽略: faceId={}", faceId, e);
return 0;
} }
} }
/** /**