You've already forked FrameTour-BE
feat(printer): 新增人脸绑定二维码生成功能
- 添加获取人脸绑定二维码接口,支持生成小程序二维码 - 实现二维码文件流输出及临时文件清理 - 修改人脸识别流程,保存人脸数据并返回faceId - 调整自动添加照片到打印列表逻辑,返回添加数量 - 更新响应模型,增加二维码URL字段 - 优化人脸匹配逻辑,使用memberRelationRepository查询关联照片 - 修复BCE适配器图片下载地址问题,去除内网地址替换逻辑
This commit is contained in:
@@ -3,7 +3,13 @@ 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.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
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.storage.adapters.IStorageAdapter;
|
||||
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.storage.utils.StorageUtil;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import com.ycwl.basic.utils.SnowFlakeUtil;
|
||||
import com.ycwl.basic.utils.WxMpUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -55,8 +62,13 @@ public class PrinterTvController {
|
||||
private final ScenicRepository scenicRepository;
|
||||
private final FaceRepository faceRepository;
|
||||
private final TaskFaceService faceService;
|
||||
private final FaceService pcFaceService;
|
||||
private final ScenicService scenicService;
|
||||
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查询图像素材
|
||||
*
|
||||
@@ -154,35 +217,44 @@ public class PrinterTvController {
|
||||
String fileName = UUID.randomUUID() + "." + suffix;
|
||||
String faceUrl = adapter.uploadFile(file, filePath, fileName);
|
||||
|
||||
// 2. 获取景区人脸识别适配器
|
||||
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId);
|
||||
// 2. 保存人脸数据到数据库
|
||||
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作为数据库名,搜索的是景区内的人脸样本)
|
||||
SearchFaceRespVo searchResult = faceService.searchFace(
|
||||
faceBodyAdapter,
|
||||
String.valueOf(scenicId),
|
||||
faceUrl,
|
||||
"大屏端人脸识别"
|
||||
);
|
||||
pcFaceService.matchFaceId(faceId);
|
||||
|
||||
// 4. 查询匹配到的图像素材(type=2)
|
||||
// 4. 自动添加照片到打印列表
|
||||
int addedCount = printerService.autoAddPhotosToPreferPrint(faceId);
|
||||
|
||||
// 5. 查询匹配到的图像素材(type=2)
|
||||
List<SourceEntity> sources = new ArrayList<>();
|
||||
if (searchResult != null && searchResult.getSampleListIds() != null && !searchResult.getSampleListIds().isEmpty()) {
|
||||
// 遍历匹配到的人脸样本ID,查询对应的图像素材
|
||||
for (Long sampleId : searchResult.getSampleListIds()) {
|
||||
SourceEntity source = sourceMapper.getBySampleIdAndType(sampleId, 2);
|
||||
if (source != null) {
|
||||
sources.add(source);
|
||||
}
|
||||
List<MemberSourceEntity> memberSourceEntities = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
|
||||
for (MemberSourceEntity memberSourceEntity : memberSourceEntities) {
|
||||
SourceEntity source = sourceRepository.getSource(memberSourceEntity.getSourceId());
|
||||
if (source != null) {
|
||||
sources.add(source);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 构造响应
|
||||
// 6. 构造响应
|
||||
FaceRecognizeWithSourcesResp resp = new FaceRecognizeWithSourcesResp();
|
||||
resp.setUrl(faceUrl);
|
||||
resp.setFaceId(null); // 不保存人脸,所以没有faceId
|
||||
resp.setFaceId(faceId);
|
||||
resp.setScenicId(scenicId);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ public class BceFaceBodyAdapter implements IFaceBodyAdapter {
|
||||
ByteArrayOutputStream baos = null;
|
||||
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);
|
||||
if (image == null) {
|
||||
log.error("无法读取图片,URL: {}", imageUrl);
|
||||
|
||||
@@ -31,4 +31,9 @@ public class FaceRecognizeWithSourcesResp {
|
||||
* 匹配到的图像素材列表(type=2)
|
||||
*/
|
||||
private List<SourceEntity> sources;
|
||||
|
||||
/**
|
||||
* 二维码URL
|
||||
*/
|
||||
private String qrcodeUrl;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public interface PrinterService {
|
||||
|
||||
FaceRecognizeResp useSample(Long userId, Long sampleId);
|
||||
|
||||
void autoAddPhotosToPreferPrint(Long faceId);
|
||||
int autoAddPhotosToPreferPrint(Long faceId);
|
||||
|
||||
/**
|
||||
* 查询待审核的打印任务
|
||||
|
||||
@@ -1126,15 +1126,16 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
* 根据景区和设备配置自动添加type=2的照片到用户打印列表
|
||||
*
|
||||
* @param faceId 人脸ID
|
||||
* @return 成功添加的照片数量
|
||||
*/
|
||||
@Override
|
||||
public void autoAddPhotosToPreferPrint(Long faceId) {
|
||||
public int autoAddPhotosToPreferPrint(Long faceId) {
|
||||
try {
|
||||
// 1. 获取人脸信息
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
log.warn("人脸不存在,无法自动添加打印: faceId={}", faceId);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Long scenicId = face.getScenicId();
|
||||
@@ -1144,21 +1145,21 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
|
||||
if (scenicConfig == null) {
|
||||
log.warn("景区配置不存在,跳过自动添加打印: scenicId={}", scenicId);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 3. 检查景区是否启用打印功能
|
||||
Boolean printEnable = scenicConfig.getBoolean("print_enable");
|
||||
if (printEnable == null || !printEnable) {
|
||||
log.debug("景区未启用打印功能,跳过自动添加: scenicId={}", scenicId);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 4. 查询该faceId关联的所有type=2的照片
|
||||
List<SourceEntity> imageSources = sourceMapper.listImageSourcesByFaceId(faceId);
|
||||
if (imageSources == null || imageSources.isEmpty()) {
|
||||
log.debug("该人脸没有关联的照片,跳过自动添加: faceId={}", faceId);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 5. 按照deviceId分组处理
|
||||
@@ -1246,15 +1247,19 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
log.error("等待照片添加任务完成时发生异常: faceId={}", faceId, e);
|
||||
}
|
||||
|
||||
if (totalAdded.get() > 0) {
|
||||
log.info("自动添加打印完成: faceId={}, 成功添加{}张照片", faceId, totalAdded.get());
|
||||
int added = totalAdded.get();
|
||||
if (added > 0) {
|
||||
log.info("自动添加打印完成: faceId={}, 成功添加{}张照片", faceId, added);
|
||||
} else {
|
||||
log.debug("自动添加打印完成: faceId={}, 无符合条件的照片", faceId);
|
||||
}
|
||||
|
||||
return added;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 出现异常则放弃,不影响主流程
|
||||
log.error("自动添加打印失败,已忽略: faceId={}", faceId, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user