feat(print): 实现照片自动裁剪与优先打印功能

- 人脸上传后自动将关联照片添加到优先打印列表
- 根据景区和设备配置自动处理type=2的照片
- 支持按设备分组处理并限制打印数量
- 实现智能图片裁剪功能,支持自动旋转以减少裁切损失
- 添加图片尺寸配置读取和默认值处理
- 完善异常处理确保不影响主流程执行
-优化打印服务中照片上传和裁剪逻辑
- 增加详细的日志记录便于问题追踪
This commit is contained in:
2025-11-02 09:12:01 +08:00
parent 222f974ad5
commit 78a2a74fa6
3 changed files with 403 additions and 5 deletions

View File

@@ -58,6 +58,7 @@ import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.service.task.TaskService;
import com.ycwl.basic.storage.StorageFactory;
@@ -152,6 +153,8 @@ public class FaceServiceImpl implements FaceService {
private MemberRelationRepository memberRelationRepository;
@Autowired
private TemplateRepository templateRepository;
@Autowired
private PrinterService printerService;
@Override
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
@@ -215,16 +218,16 @@ public class FaceServiceImpl implements FaceService {
SearchFaceRespVo userDbSearchResult = faceService.searchFace(faceBodyAdapter, USER_FACE_DB_NAME+scenicId, faceUrl, "判断是否为用户上传过的人脸");
float strictScore = 0.6F;
if (userDbSearchResult == null) {
// 都是null了那得是新的
// 都是null了,那得是新的
faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
} else if (userDbSearchResult.getSampleListIds() == null || userDbSearchResult.getSampleListIds().isEmpty()) {
// 没有匹配到过也得是新的
// 没有匹配到过,也得是新的
faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
} else if (userDbSearchResult.getFirstMatchRate() < strictScore) {
// 有匹配结果但是不匹配旧的
// 有匹配结果,但是不匹配旧的
faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
} else {
// 有匹配结果且能匹配旧的数据
// 有匹配结果,且能匹配旧的数据
Optional<Long> faceAny = userDbSearchResult.getSampleListIds().stream().filter(_faceId -> {
FaceEntity face = faceRepository.getFace(_faceId);
if (face == null) {
@@ -265,6 +268,11 @@ public class FaceServiceImpl implements FaceService {
resp.setUrl(faceUrl);
resp.setFaceId(newFaceId);
matchFaceId(newFaceId, oldFaceId == null);
// 异步执行自动添加打印
Long finalFaceId = newFaceId;
new Thread(() -> autoAddPhotosToPreferPrint(finalFaceId), "auto-add-print-" + newFaceId).start();
return resp;
}
@@ -1719,4 +1727,114 @@ public class FaceServiceImpl implements FaceService {
log.error("记录低阈值检测人脸失败:faceId={}", faceId, e);
}
}
/**
* 自动将人脸关联的照片添加到优先打印列表
* 根据景区和设备配置自动添加type=2的照片到用户打印列表
*
* @param faceId 人脸ID
*/
private void autoAddPhotosToPreferPrint(Long faceId) {
try {
// 1. 获取人脸信息
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
log.warn("人脸不存在,无法自动添加打印: faceId={}", faceId);
return;
}
Long scenicId = face.getScenicId();
Long memberId = face.getMemberId();
// 2. 获取景区配置
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
if (scenicConfig == null) {
log.warn("景区配置不存在,跳过自动添加打印: scenicId={}", scenicId);
return;
}
// 3. 检查景区是否启用打印功能
Boolean printEnable = scenicConfig.getBoolean("print_enable");
if (printEnable == null || !printEnable) {
log.debug("景区未启用打印功能,跳过自动添加: scenicId={}", scenicId);
return;
}
// 4. 查询该faceId关联的所有type=2的照片
List<SourceEntity> imageSources = sourceMapper.listImageSourcesByFaceId(faceId);
if (imageSources == null || imageSources.isEmpty()) {
log.debug("该人脸没有关联的照片,跳过自动添加: faceId={}", faceId);
return;
}
// 5. 按照deviceId分组处理
Map<Long, List<SourceEntity>> sourcesByDevice = imageSources.stream()
.filter(source -> source.getDeviceId() != null)
.collect(Collectors.groupingBy(SourceEntity::getDeviceId));
int totalAdded = 0;
for (Map.Entry<Long, List<SourceEntity>> entry : sourcesByDevice.entrySet()) {
Long deviceId = entry.getKey();
List<SourceEntity> deviceSources = entry.getValue();
// 6. 获取设备配置
DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(deviceId);
if (deviceConfig == null) {
log.debug("设备配置不存在,跳过该设备: deviceId={}", deviceId);
continue;
}
// 7. 检查是否启用优先打印
Boolean preferPrintEnable = deviceConfig.getBoolean("prefer_print_enable");
if (preferPrintEnable == null || !preferPrintEnable) {
log.debug("设备未启用优先打印,跳过: deviceId={}", deviceId);
continue;
}
// 8. 获取优先打印数量配置
Integer preferPrintCount = deviceConfig.getInteger("prefer_print_count");
if (preferPrintCount == null) {
log.debug("设备未配置优先打印数量,跳过: deviceId={}", deviceId);
continue;
}
// 9. 根据配置添加照片到打印列表
List<SourceEntity> sourcesToAdd;
if (preferPrintCount > 0) {
// 如果大于0,按照数量限制添加
sourcesToAdd = deviceSources.stream()
.limit(preferPrintCount)
.collect(Collectors.toList());
log.info("设备{}配置优先打印{}张,实际添加{}张",
deviceId, preferPrintCount, sourcesToAdd.size());
} else {
// 如果小于等于0,添加该设备的所有照片
sourcesToAdd = deviceSources;
log.info("设备{}配置优先打印所有照片,实际添加{}张",
deviceId, sourcesToAdd.size());
}
// 10. 批量添加到打印列表
for (SourceEntity source : sourcesToAdd) {
try {
printerService.addUserPhoto(memberId, scenicId, source.getUrl());
totalAdded++;
} catch (Exception e) {
log.warn("添加照片到打印列表失败: sourceId={}, url={}, error={}",
source.getId(), source.getUrl(), e.getMessage());
}
}
}
if (totalAdded > 0) {
log.info("自动添加打印完成: faceId={}, 成功添加{}张照片", faceId, totalAdded);
} else {
log.debug("自动添加打印完成: faceId={}, 无符合条件的照片", faceId);
}
} catch (Exception e) {
// 出现异常则放弃,不影响主流程
log.error("自动添加打印失败,已忽略: faceId={}", faceId, e);
}
}
}

View File

@@ -241,7 +241,58 @@ public class PrinterServiceImpl implements PrinterService {
entity.setMemberId(memberId);
entity.setScenicId(scenicId);
entity.setOrigUrl(url);
entity.setCropUrl(url);
// 获取打印尺寸
String cropUrl = url; // 默认使用原图
try {
// 从打印机表获取尺寸
Integer printWidth = null;
Integer printHeight = null;
List<PrinterResp> printers = printerMapper.listByScenicId(scenicId);
if (printers != null && !printers.isEmpty()) {
PrinterResp firstPrinter = printers.get(0);
printWidth = firstPrinter.getPreferW();
printHeight = firstPrinter.getPreferH();
log.debug("从打印机获取尺寸: scenicId={}, printerId={}, width={}, height={}",
scenicId, firstPrinter.getId(), printWidth, printHeight);
}
// 如果打印机没有配置或配置无效,使用默认值
if (printWidth == null || printWidth <= 0) {
printWidth = 1020;
log.debug("打印机宽度未配置或无效,使用默认值: width={}", printWidth);
}
if (printHeight == null || printHeight <= 0) {
printHeight = 1520;
log.debug("打印机高度未配置或无效,使用默认值: height={}", printHeight);
}
// 使用smartCropAndFill裁剪图片
File croppedFile = ImageUtils.smartCropAndFill(url, printWidth, printHeight);
try {
// 上传裁剪后的图片(使用File版本的uploadFile方法)
String[] split = url.split("\\.");
String ext = split.length > 0 ? split[split.length - 1] : "jpg";
cropUrl = StorageFactory.use().uploadFile(null, croppedFile, "printer", UUID.randomUUID() + "." + ext);
log.info("照片裁剪成功: memberId={}, scenicId={}, 原图={}, 裁剪后={}, 尺寸={}x{}",
memberId, scenicId, url, cropUrl, printWidth, printHeight);
} finally {
// 清理临时文件
if (croppedFile != null && croppedFile.exists()) {
croppedFile.delete();
}
}
} catch (Exception e) {
log.error("照片裁剪失败,使用原图: memberId={}, scenicId={}, url={}", memberId, scenicId, url, e);
// 出现异常则使用原图
cropUrl = url;
}
entity.setCropUrl(cropUrl);
entity.setStatus(0);
printerMapper.addUserPhoto(entity);
return entity.getId();