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 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -31,4 +31,9 @@ public class FaceRecognizeWithSourcesResp {
|
|||||||
* 匹配到的图像素材列表(type=2)
|
* 匹配到的图像素材列表(type=2)
|
||||||
*/
|
*/
|
||||||
private List<SourceEntity> sources;
|
private List<SourceEntity> sources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二维码URL
|
||||||
|
*/
|
||||||
|
private String qrcodeUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询待审核的打印任务
|
* 查询待审核的打印任务
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user