From 801432202c6827552334210194ed555fc307de59 Mon Sep 17 00:00:00 2001 From: haozq <1611483981@qq.com> Date: Tue, 10 Feb 2026 18:54:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=BA=E8=84=B8=E6=88=AA?= =?UTF-8?q?=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bonus/common/core/constant/Constants.java | 1 + .../bmw/config/FaceServiceInitializer.java | 26 --- .../bmw/config/FaceServiceProperties.java | 2 +- .../bmw/controller/PmWorkerController.java | 11 +- .../bmw/controller/ZipDownloadController.java | 29 ++-- .../impl/AppRecognitionServiceImpl.java | 9 +- .../bmw/service/impl/AppServiceImpl.java | 6 +- .../bmw/service/impl/PmWorkerServiceImpl.java | 103 ++++++++++-- .../bonus/bmw/utils/CustomMultipartFile.java | 97 +++++++++++ .../bmw/utils/FaceFeatureExtractorUtil.java | 152 ++++++++++++------ .../com/bonus/bmw/utils/ImageCropper.java | 49 ++++++ .../com/bonus/urk/utils/ImageCropper.java | 125 ++++++++++++++ 12 files changed, 498 insertions(+), 112 deletions(-) delete mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceInitializer.java create mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/CustomMultipartFile.java create mode 100644 bonus-modules/bonus-urk/src/main/java/com/bonus/urk/utils/ImageCropper.java diff --git a/bonus-common/bonus-common-core/src/main/java/com/bonus/common/core/constant/Constants.java b/bonus-common/bonus-common-core/src/main/java/com/bonus/common/core/constant/Constants.java index 87c669a..28ce23b 100644 --- a/bonus-common/bonus-common-core/src/main/java/com/bonus/common/core/constant/Constants.java +++ b/bonus-common/bonus-common-core/src/main/java/com/bonus/common/core/constant/Constants.java @@ -155,6 +155,7 @@ public class Constants */ public static final String FILE_UPLOAD_WORKER = "pm_worker"; + /** * 人员入场表 */ diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceInitializer.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceInitializer.java deleted file mode 100644 index e58cfd9..0000000 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceInitializer.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.bonus.bmw.config; - -import com.bonus.bmw.utils.FaceFeatureExtractorUtil; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; - -@Component -public class FaceServiceInitializer implements ApplicationRunner { - - private final FaceServiceProperties properties; - - public FaceServiceInitializer(FaceServiceProperties properties) { - this.properties = properties; - } - @Override - public void run(ApplicationArguments args) { - // 在 Spring 启动完成后,注入值到静态工具类 - if (properties.getBaseUrl() != null) { - FaceFeatureExtractorUtil.setBaseUrl(properties.getBaseUrl()); - } - if (properties.getFaceUrl() != null) { - FaceFeatureExtractorUtil.setFaceUrl(properties.getFaceUrl()); - } - } -} diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceProperties.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceProperties.java index aa1805f..cfbfd4e 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceProperties.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceProperties.java @@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration; @ConfigurationProperties(prefix = "face.service") @Data public class FaceServiceProperties { - private String baseUrl; + private String faceUrl; } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmWorkerController.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmWorkerController.java index e40c558..3fe541c 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmWorkerController.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmWorkerController.java @@ -46,6 +46,9 @@ public class PmWorkerController extends BaseController { @Autowired private PmWorkerService service; + @Autowired + FaceFeatureExtractorUtil faceFeatureExtractorUtil; + /** * 查询列表 * @param o @@ -193,8 +196,8 @@ public class PmWorkerController extends BaseController { } File fileData = null; try { - fileData = FaceFeatureExtractorUtil.multipartToFile(file); - FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData); + fileData = faceFeatureExtractorUtil.multipartToFile(file); + FaceResult faceResult = faceFeatureExtractorUtil.extractFeature(fileData); if (200 == faceResult.getCode()){ return AjaxResult.success(faceResult.getMsg()); }else{ @@ -224,8 +227,8 @@ public class PmWorkerController extends BaseController { } File fileData = null; try { - fileData =FaceFeatureExtractorUtil.base64ToFile(base64file, "face_detection");; - FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData); + fileData =faceFeatureExtractorUtil.base64ToFile(base64file, "face_detection");; + FaceResult faceResult = faceFeatureExtractorUtil.extractFeature(fileData); if (200 == faceResult.getCode()){ return AjaxResult.success(faceResult.getMsg()); }else{ diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/ZipDownloadController.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/ZipDownloadController.java index f3a2691..e8c850e 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/ZipDownloadController.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/ZipDownloadController.java @@ -128,17 +128,26 @@ public class ZipDownloadController { */ @Scheduled(fixedRate = 3600000) // 每小时清理一次 public void cleanOldZips() throws IOException { - try (Stream files = Files.list(Paths.get("/tmp/downloads"))) { - files.filter(p -> p.toString().endsWith(".zip")) - .filter(p -> { - try { - return System.currentTimeMillis() - Files.getLastModifiedTime(p).toMillis() > 2 * 3600_000; // 2 小时过期 - } catch (IOException e) { return false; } - }) - .forEach(p -> { - try { Files.delete(p); } catch (IOException ignored) {} - }); + String path="/tmp/downloads/"; + Path dirPath = Paths.get(path); + // 2. 检查目录是否存在,不存在则创建(或直接跳过) + if (!Files.exists(dirPath)) { + log.warn("清理目录不存在:{},尝试创建目录", dirPath); + }else{ + try (Stream files = Files.list(Paths.get(path))) { + files.filter(p -> p.toString().endsWith(".zip")) + .filter(p -> { + try { + return System.currentTimeMillis() - Files.getLastModifiedTime(p).toMillis() > 2 * 3600_000; // 2 小时过期 + } catch (IOException e) { return false; } + }) + .forEach(p -> { + try { Files.delete(p); } catch (IOException ignored) {} + }); + } } + + } } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppRecognitionServiceImpl.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppRecognitionServiceImpl.java index 26a82bb..0b382ef 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppRecognitionServiceImpl.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppRecognitionServiceImpl.java @@ -47,6 +47,9 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { @Autowired private FileUploadUtils fileUploadUtils; + @Autowired + private FaceFeatureExtractorUtil faceFeatureExtractorUtil; + @Value("${face.groupId}") private Long groupId; @@ -59,7 +62,7 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { @Override public String uploadFaceRecognition(MultipartFile facePhoto, FaceRecognitionBean bean) { try { - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(bean.getIdNumber(), groupId, FaceFeatureExtractorUtil.multipartToFile(facePhoto)); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(bean.getIdNumber(), groupId, faceFeatureExtractorUtil.multipartToFile(facePhoto)); if(faceResult.getCode() == 200) { // 解析 data 数组 return "人脸入库成功"; @@ -75,8 +78,8 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { @Override public AjaxResult getFaceRecognition(MultipartFile facePhoto, String proId) { try { - File file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); - FaceResult faceResult = FaceFeatureExtractorUtil.faceRecognition(groupId, file); + File file = faceFeatureExtractorUtil.multipartToFile(facePhoto); + FaceResult faceResult = faceFeatureExtractorUtil.faceRecognition(groupId, file); BmWorkerEinUserVo sysFile = new BmWorkerEinUserVo(); if(faceResult.getCode() == 200) { // 解析 data 数组 diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppServiceImpl.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppServiceImpl.java index c1ba0f3..63732f6 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppServiceImpl.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/AppServiceImpl.java @@ -58,6 +58,8 @@ public class AppServiceImpl implements AppService { private BmWorkerWageCardMapper wageCardMapper; @Autowired private BmWorkerContractMapper contractMapper; + @Autowired + private FaceFeatureExtractorUtil faceFeatureExtractorUtil; @Autowired private PmWorkerExitMapper pmWorkerExitMapper; @@ -152,7 +154,7 @@ public class AppServiceImpl implements AppService { //下发人脸到人脸库 UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, ""); if (fileBast64 != null) { - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); sb.append(faceResult.getMsg()); } } @@ -231,7 +233,7 @@ public class AppServiceImpl implements AppService { FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, ""); if (fileBast64 != null) { - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); sb.append(faceResult.getMsg()); } } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerServiceImpl.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerServiceImpl.java index d7f2890..07c78e9 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerServiceImpl.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerServiceImpl.java @@ -1,6 +1,7 @@ package com.bonus.bmw.service.impl; import cn.hutool.core.date.DateUtil; +import cn.hutool.json.JSONArray; import com.bonus.bmw.domain.dto.PmWorkerDto; import com.bonus.bmw.domain.dto.WebFileDto; import com.bonus.bmw.domain.face.FaceResult; @@ -8,7 +9,9 @@ import com.bonus.bmw.domain.po.MapBeanPo; import com.bonus.bmw.domain.vo.*; import com.bonus.bmw.mapper.PmWorkerMapper; import com.bonus.bmw.service.*; +import com.bonus.bmw.utils.CustomMultipartFile; import com.bonus.bmw.utils.FaceFeatureExtractorUtil; +import com.bonus.bmw.utils.ImageCropper; import com.bonus.common.core.constant.Constants; import com.bonus.common.core.exception.ServiceException; import com.bonus.common.core.utils.StringUtils; @@ -53,7 +56,7 @@ public class PmWorkerServiceImpl implements PmWorkerService{ private BmWorkerContractService contractService; @Resource - private AppRecognitionService appRecognitionService; + private FaceFeatureExtractorUtil faceFeatureExtractorUtil; @Autowired private TbProConfigService configService; @@ -115,7 +118,9 @@ public class PmWorkerServiceImpl implements PmWorkerService{ } record.setCreateUser(SecurityUtils.getLoginUser().getSysUser().getUserName()); int insert = mapper.insert(record); + if(insert > 0){ + File fileData=null; List uploadFileVos = new ArrayList<>(); boolean isBase64 = false; MultipartFile facePhoto = null; @@ -127,16 +132,46 @@ public class PmWorkerServiceImpl implements PmWorkerService{ isBase64 = true; }else{ List collect = fileMsg.stream().filter(data -> "faceImg".equals(data.getName())).collect(Collectors.toList()); - //组装数据 MultipartFile[] workerFiles = new MultipartFile[collect.size()]; String[] type = new String[collect.size()]; for (int i = 0; i < collect.size(); i++) { workerFiles[i] = collect.get(i).getFile(); type[i] = collect.get(i).getType(); + } + facePhoto = collect.get(0).getFile(); + fileData = faceFeatureExtractorUtil.multipartToFile(facePhoto); + Map faceResult = faceFeatureExtractorUtil.extractFaceImage(fileData); + File file=null; + if(faceResult!=null && !faceResult.isEmpty()){ + String filePath; + String path=fileData.getPath(); + String newPath=null; + if(StringUtils.isNotEmpty(path)){ + filePath=path; + newPath=ImageCropper.addNewSuffixToFileName(path); + }else{ + filePath=fileData.getAbsolutePath(); + newPath=ImageCropper.addNewSuffixToFileName(filePath); + } + if(newPath!=null){ + //临时文件 + ImageCropper.cropImage(filePath, newPath, faceResult.get("x1"), faceResult.get("y1"), faceResult.get("x2"), faceResult.get("y2")); + file = new File(newPath); + //转成file + MultipartFile multipartFile = CustomMultipartFile.convert(file); + if (file.exists()) { + boolean delete = file.delete(); + System.out.println("删除临时文件--->"+delete); + } + workerFiles[0] = multipartFile; + } } //人脸的数据添加 uploadFileVos = fileUploadUtils.uploadFile(workerFiles, Constants.FILE_UPLOAD_WORKER, record.getId().toString(), type,"", ""); - facePhoto = collect.get(0).getFile(); + if (file!=null && file.exists()) { + boolean delete = file.delete(); + System.out.println("删除临时文件--->"+delete); + } } //入场相关数据添加 addWorkerEinData(record,fileMsg); @@ -150,17 +185,15 @@ public class PmWorkerServiceImpl implements PmWorkerService{ //下发人脸到人脸库 if(!uploadFileVos.isEmpty()){ if(isBase64){ - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); sb.append(faceResult.getMsg()); }else{ - File file = null; try { - file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, fileData); sb.append(faceResult.getMsg()); } finally { - if (file != null && file.exists()) { - boolean delete = file.delete(); + if (fileData != null && fileData.exists()) { + boolean delete = fileData.delete(); // 或使用 Hutool: FileUtil.del(file); } } @@ -171,6 +204,8 @@ public class PmWorkerServiceImpl implements PmWorkerService{ return AjaxResult.success(sb.append("--基础数据添加成功").toString(),record.getId()); } + + /** * 基础数据添加完毕,在进行入场数据添加 * @param record @@ -433,6 +468,7 @@ public class PmWorkerServiceImpl implements PmWorkerService{ //人脸照片是否修改 List collect = fileMsg.stream().filter(data -> "faceImg".equals(data.getName())).collect(Collectors.toList()); if(!collect.isEmpty() || record.getFacePhotoBase64() != null){ + File fileData=null; String s = fileUploadUtils.delFileListById("", record.getId().toString(), Constants.FILE_UPLOAD_WORKER, ""); List uploadFileVos = new ArrayList<>(); boolean isBase64 = false; @@ -450,9 +486,39 @@ public class PmWorkerServiceImpl implements PmWorkerService{ workerFiles[i] = collect.get(i).getFile(); type[i] = collect.get(i).getType(); } + + facePhoto = collect.get(0).getFile(); + fileData = faceFeatureExtractorUtil.multipartToFile(facePhoto); + Map faceResult = faceFeatureExtractorUtil.extractFaceImage(fileData); + File file=null; + if(faceResult!=null && !faceResult.isEmpty()){ + String filePath; + String path=fileData.getPath(); + String newPath=null; + if(StringUtils.isNotEmpty(path)){ + filePath=path; + newPath=ImageCropper.addNewSuffixToFileName(path); + }else{ + filePath=fileData.getAbsolutePath(); + newPath=ImageCropper.addNewSuffixToFileName(filePath); + } + if(newPath!=null){ + //临时文件 + ImageCropper.cropImage(filePath, newPath, faceResult.get("x1"), faceResult.get("y1"), faceResult.get("x2"), faceResult.get("y2")); + file = new File(newPath); + //转成file + MultipartFile multipartFile = CustomMultipartFile.convert(file); + + workerFiles[0] = multipartFile; + } + } + //人脸的数据添加 uploadFileVos = fileUploadUtils.uploadFile(workerFiles, Constants.FILE_UPLOAD_WORKER, record.getId().toString(), type,"", ""); - facePhoto = collect.get(0).getFile(); + if (file!=null &&file.exists()) { + boolean delete = file.delete(); + System.out.println("删除临时文件--->"+delete); + } } //下发人脸到考勤机 try { @@ -470,17 +536,15 @@ public class PmWorkerServiceImpl implements PmWorkerService{ //下发人脸到人脸库 if(!uploadFileVos.isEmpty()){ if(isBase64){ - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); sb.append(faceResult.getMsg()); }else{ - File file = null; try { - file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); - FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file); + FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, fileData); sb.append(faceResult.getMsg()); } finally { - if (file != null && file.exists()) { - boolean delete = file.delete(); + if (fileData != null && fileData.exists()) { + boolean delete = fileData.delete(); } } } @@ -508,6 +572,13 @@ public class PmWorkerServiceImpl implements PmWorkerService{ return AjaxResult.success(sb.append("--基础数据更新成功").toString()); } + + + + + + + /** * 查询人员列表 * @param o diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/CustomMultipartFile.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/CustomMultipartFile.java new file mode 100644 index 0000000..396dff4 --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/CustomMultipartFile.java @@ -0,0 +1,97 @@ +package com.bonus.bmw.utils; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Files; + +public class CustomMultipartFile implements MultipartFile { + + private final File file; + private final String fileName; + private final String contentType; + + public CustomMultipartFile(File file) { + this.file = file; + this.fileName = file.getName(); + this.contentType = getContentTypeByFileName(this.fileName); + } + + @Override + public String getName() { + return "file"; // 自定义参数名 + } + + @Override + public String getOriginalFilename() { + return fileName; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return file.length() == 0; + } + + @Override + public long getSize() { + return file.length(); + } + + @Override + public byte[] getBytes() throws IOException { + return Files.readAllBytes(file.toPath()); + } + + @Override + public InputStream getInputStream() throws IOException { + return new FileInputStream(file); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + Files.copy(file.toPath(), dest.toPath()); + } + + // JDK 1.8兼容的Content-Type获取方法 + private static String getContentTypeByFileName(String fileName) { + if (fileName == null) { + return "application/octet-stream"; + } + int lastDotIndex = fileName.lastIndexOf("."); + String extension = lastDotIndex == -1 ? "" : fileName.substring(lastDotIndex + 1).toLowerCase(); + + if ("xlsx".equals(extension)) { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } else if ("xls".equals(extension)) { + return "application/vnd.ms-excel"; + } else if ("jpg".equals(extension) || "jpeg".equals(extension)) { + return "image/jpeg"; + } else if ("png".equals(extension)) { + return "image/png"; + } else if ("pdf".equals(extension)) { + return "application/pdf"; + } else { + return "application/octet-stream"; + } + } + + // 工具方法:创建CustomMultipartFile + public static MultipartFile convert(File file) { + if (file == null || !file.exists()) { + throw new IllegalArgumentException("文件不存在"); + } + return new CustomMultipartFile(file); + } + + // 测试 + public static void main(String[] args) { + File file = new File("D:/test.png"); + MultipartFile multipartFile = CustomMultipartFile.convert(file); + System.out.println("转换成功:" + multipartFile.getOriginalFilename()); + } +} \ No newline at end of file diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java index 5438f31..5e8e232 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java @@ -9,11 +9,18 @@ import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; +import com.bonus.bmw.config.FaceServiceProperties; import com.bonus.bmw.domain.face.FaceResult; import com.bonus.bmw.domain.face.GroupInfo; import com.bonus.bmw.domain.face.UserInfo; +import com.bonus.common.core.utils.StringUtils; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.units.qual.C; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.File; @@ -21,6 +28,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Base64; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,42 +36,37 @@ import java.util.regex.Pattern; * 人脸识别算法服务(Python 微服务)工具类 * 统一管理 API 地址,使用 Hutool 封装 HTTP 调用 */ +@Component +@Slf4j public class FaceFeatureExtractorUtil { - private static final Logger log = LoggerFactory.getLogger(FaceFeatureExtractorUtil.class); - // ========== 基础配置 ========== - private static String BASE_URL = "http://192.168.0.37:18080/api"; - private static String FACE_URL = "http://192.168.0.37:18000"; + @Autowired + public FaceServiceProperties properties; + // ========== API 路径常量 ========== private static final String EXTRACT_FEATURE_PATH = "/api/extract_feature"; + private static final String HEALTH_CHECK_PATH = "/health"; - public static final String GROUP_ADD_PATH = "/groups/add"; - public static final String GROUP_LIST_PATH = "/groups/list"; - public static final String USER_ADD_PATH = "/users/add"; - public static final String USER_UPDATE_PATH = "/users/update"; - public static final String USER_SEARCH_PATH = "/users/search"; - public static final String USER_LIST_PATH = "/users/list"; + public static final String GROUP_ADD_PATH = "/api/groups/add"; + + public static final String GROUP_LIST_PATH = "/api/groups/list"; + + public static final String USER_ADD_PATH = "/api/users/add"; + /** + * 更新人脸库 + */ + public static final String USER_UPDATE_PATH = "/api/users/update"; + + public static final String USER_SEARCH_PATH = "/api/users/search"; + + public static final String USER_LIST_PATH = "/api/users/list"; /** - * 设置算法服务的基础 URL + * 获取图片人脸 -只需要人脸部分 */ - public static void setBaseUrl(String baseUrl) { - if (baseUrl != null && !baseUrl.trim().isEmpty()) { - BASE_URL = baseUrl.trim(); - } - } - - /** - * 设置算法服务的人脸 URL - */ - public static void setFaceUrl(String faceUrl) { - if (faceUrl != null && !faceUrl.trim().isEmpty()) { - FACE_URL = faceUrl.trim(); - } - } - + public static final String FACE_IMAGE_SIZE = "/api/detect_face"; /** * 提取人脸特征向量 @@ -71,13 +74,13 @@ public class FaceFeatureExtractorUtil { * @param imageFile 人脸图片文件(jpg/png) * @return ExtractResult 包含成功状态、特征向量、错误信息等 */ - public static FaceResult extractFeature(File imageFile) { + public FaceResult extractFeature(File imageFile) { if (imageFile == null || !imageFile.exists()) { return new FaceResult(500, "Image file is null or not exists", null); } try { - System.out.println("开始调用算法服务:"+FACE_URL + EXTRACT_FEATURE_PATH); - HttpResponse response = HttpRequest.post(FACE_URL + EXTRACT_FEATURE_PATH).form("image", imageFile).timeout(10000) // 10秒超时 + System.out.println("开始调用算法服务:"+properties.getFaceUrl() + EXTRACT_FEATURE_PATH); + HttpResponse response = HttpRequest.post(properties.getFaceUrl() + EXTRACT_FEATURE_PATH).form("image", imageFile).timeout(10000) // 10秒超时 .execute(); int status = response.getStatus(); String body = response.body(); @@ -103,12 +106,61 @@ public class FaceFeatureExtractorUtil { } } + public Map extractFaceImage(File imageFile) { + Map maps= Maps.newHashMap(); + if (imageFile == null || !imageFile.exists()) { + return null; + } + try { + System.out.println("开始调用算法服务:"+properties.getFaceUrl() + FACE_IMAGE_SIZE); + HttpResponse response = HttpRequest.post(properties.getFaceUrl() + FACE_IMAGE_SIZE).form("image", imageFile).form("expand_scale", "0.3") .timeout(10000) // 10秒超时 + .execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return null; + } + JSONObject json = JSONUtil.parseObj(body); + boolean success = json.getBool("success", false); + String message = json.getStr("message", "Unknown error"); + if (!success) { + return null; + } + // 解析 1024 维特征 + + JSONArray featureList = json.getJSONArray("faces"); + for (int i = 0; i < featureList.size(); i++) { + JSONObject obj = featureList.getJSONObject(i); + String x1 = obj.getStr("x1"); + String x2 = obj.getStr("x2"); + String y1 = obj.getStr("y1"); + String y2 = obj.getStr("y2"); + if (StringUtils.isNotEmpty(x1)) { + maps.put("x1", obj.getDouble("x1")); + } + if (StringUtils.isNotEmpty(x2)) { + maps.put("x2", obj.getDouble("x2")); + } + if (StringUtils.isNotEmpty(y1)) { + maps.put("y1", obj.getDouble("y1")); + } + if (StringUtils.isNotEmpty(y2)) { + maps.put("y2", obj.getDouble("y2")); + } + } + return maps; + } catch (Exception e) { + String errorMsg = "Exception during feature extraction: " + ExceptionUtil.stacktraceToString(e); + } + return null; + } + /** * 健康检查 */ - public static FaceResult healthCheck() { + public FaceResult healthCheck() { try { - HttpResponse response = HttpRequest.get(FACE_URL + HEALTH_CHECK_PATH).timeout(3000).execute(); + HttpResponse response = HttpRequest.get(properties.getFaceUrl() + HEALTH_CHECK_PATH).timeout(3000).execute(); if (response.getStatus() == 200) { JSONObject json = JSONUtil.parseObj(response.body()); return "healthy".equals(json.getStr("status")) ? new FaceResult(200, "服务正常", null): new FaceResult(500, "服务异常", null); @@ -126,14 +178,14 @@ public class FaceFeatureExtractorUtil { * @param description 分组描述(可选,可传 null) * @return 返回结果对象,包含 id、name、createTime 等 */ - public static FaceResult addGroup(String name, String description) { + public FaceResult addGroup(String name, String description) { if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("分组名称不能为空"); } // 构建请求体 JSON JSONObject requestBody = JSONUtil.createObj().set("name", name).set("description", description); // Hutool 会自动忽略 null 值(不会序列化) try { - HttpResponse response = HttpRequest.post(BASE_URL + GROUP_ADD_PATH).body(requestBody.toString()) // 发送 JSON 字符串 + HttpResponse response = HttpRequest.post(properties.getFaceUrl() + GROUP_ADD_PATH).body(requestBody.toString()) // 发送 JSON 字符串 .contentType("application/json") // 设置 Content-Type .timeout(5000) // 超时 5 秒 .execute(); @@ -165,10 +217,10 @@ public class FaceFeatureExtractorUtil { * * @return 返回结果对象,包含 id、name、createTime 等 */ - public static FaceResult getGroupList() { + public FaceResult getGroupList() { try { // 构建请求体 JSON - HttpResponse response = HttpRequest.get(BASE_URL + GROUP_LIST_PATH).timeout(5000).execute(); + HttpResponse response = HttpRequest.get(properties.getFaceUrl() + GROUP_LIST_PATH).timeout(5000).execute(); // 解析响应 int status = response.getStatus(); String body = response.body(); @@ -210,7 +262,7 @@ public class FaceFeatureExtractorUtil { * @param photoFile 图片 * @return 添加结果 */ - public static FaceResult addUser(String idNumber, Long groupId, File photoFile) { + public FaceResult addUser(String idNumber, Long groupId, File photoFile) { if (idNumber == null || idNumber.trim().isEmpty()) { return new FaceResult(500, "人员主键不能为空", null); } @@ -218,7 +270,7 @@ public class FaceFeatureExtractorUtil { return new FaceResult(500, "照片文件无效", null); } try { - HttpRequest request = HttpRequest.post(BASE_URL + USER_ADD_PATH) + HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_ADD_PATH) .form("name", idNumber) // Hutool 自动处理 multipart .form("photo", photoFile); @@ -260,7 +312,7 @@ public class FaceFeatureExtractorUtil { * @param photoFile 新照片文件(可为 null,表示不修改) * @return 更新结果 */ - public static FaceResult updateUser(Long id, String idNumber, Long groupId, File photoFile) { + public FaceResult updateUser(Long id, String idNumber, Long groupId, File photoFile) { if (id == null || id <= 0) { return new FaceResult(500, "人员ID无效", null); } @@ -268,7 +320,7 @@ public class FaceFeatureExtractorUtil { return new FaceResult(500, "人员主键不能为空", null); } try { - HttpRequest request = HttpRequest.post(BASE_URL + USER_UPDATE_PATH +"/"+ id) + HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_UPDATE_PATH +"/"+ id) .form("name", idNumber); // 必填 if (groupId != null) { request.form("groupId", groupId.toString()); @@ -303,7 +355,7 @@ public class FaceFeatureExtractorUtil { * @param groupId 分组ID(可为 null,表示不筛选) * @return 查询结果 */ - public static FaceResult faceRecognition(Long groupId, File photoFile) { + public FaceResult faceRecognition(Long groupId, File photoFile) { if (photoFile == null || !photoFile.exists()) { return new FaceResult(500, "照片文件无效", null); } @@ -311,7 +363,7 @@ public class FaceFeatureExtractorUtil { return new FaceResult(500, "分组Id不能为空", null); } try { - HttpRequest request = HttpRequest.post(BASE_URL + USER_SEARCH_PATH) + HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_SEARCH_PATH) .form("photo", photoFile) .form("groupId", groupId); HttpResponse response = request.timeout(10000).execute(); @@ -349,7 +401,7 @@ public class FaceFeatureExtractorUtil { * @param groupId 分组ID(可为 null,表示不筛选) * @return 查询结果 */ - public static FaceResult updateUserByIdNumber(String idNumber, Long groupId, File photoFile) { + public FaceResult updateUserByIdNumber(String idNumber, Long groupId, File photoFile) { try { FaceResult faceResult = getFaceUserList(idNumber, groupId); if(faceResult.getCode() !=200){ @@ -376,9 +428,9 @@ public class FaceFeatureExtractorUtil { * @param groupId 分组ID(可为 null,表示不筛选) * @return 查询结果 */ - public static FaceResult getFaceUserList(String idNumber, Long groupId) { + public FaceResult getFaceUserList(String idNumber, Long groupId) { try { - HttpRequest request = HttpRequest.get(BASE_URL + USER_LIST_PATH); + HttpRequest request = HttpRequest.get(properties.getFaceUrl() + USER_LIST_PATH); // 动态添加查询参数(仅当值有效时) if (idNumber != null && !idNumber.trim().isEmpty()) { request.form("name", idNumber.trim()); // Hutool GET 也支持 .form() 自动转 query @@ -428,7 +480,7 @@ public class FaceFeatureExtractorUtil { * @param base64Image Base64 编码的图片 * @return 添加结果 */ - public static FaceResult addUserWithBase64(String idNumber, Long groupId, String base64Image) { + public FaceResult addUserWithBase64(String idNumber, Long groupId, String base64Image) { if (base64Image == null || base64Image.trim().isEmpty()) { return new FaceResult(500, "Base64 图片为空", null); } @@ -459,7 +511,7 @@ public class FaceFeatureExtractorUtil { * @param base64Image Base64 编码的图片(可为 null) * @return 更新结果 */ - public static FaceResult updateUserWithBase64(Long id, String idNumber, Long groupId, String base64Image) { + public FaceResult updateUserWithBase64(Long id, String idNumber, Long groupId, String base64Image) { File tempFile = null; try { if (base64Image != null && !base64Image.trim().isEmpty()) { @@ -488,7 +540,7 @@ public class FaceFeatureExtractorUtil { * @param groupId 分组ID(可为 null,表示不筛选) * @return 查询结果 */ - public static FaceResult updateUserByIdNumberBase64(String idNumber, Long groupId, String photoFileBase64) { + public FaceResult updateUserByIdNumberBase64(String idNumber, Long groupId, String photoFileBase64) { try { FaceResult faceResult = getFaceUserList(idNumber, groupId); if(faceResult.getCode() !=200){ @@ -516,7 +568,7 @@ public class FaceFeatureExtractorUtil { * @throws IllegalArgumentException Base64 格式非法 * @throws IORuntimeException 写入临时文件失败 */ - public static File base64ToFile(String base64Image, String prefix) { + public File base64ToFile(String base64Image, String prefix) { if (base64Image == null || base64Image.trim().isEmpty()) { return null; } @@ -537,7 +589,7 @@ public class FaceFeatureExtractorUtil { return FileUtil.writeBytes(imageBytes, tempPath); } - public static File multipartToFile(MultipartFile multipartFile) throws IOException { + public File multipartToFile(MultipartFile multipartFile) throws IOException { // 创建临时文件(自动在系统临时目录) File tempFile = File.createTempFile( "upload_", @@ -548,7 +600,7 @@ public class FaceFeatureExtractorUtil { return tempFile; } // 辅助方法:获取文件扩展名 - private static String getExtension(String fileName) { + private String getExtension(String fileName) { if (fileName == null || fileName.lastIndexOf(".") == -1) { return ""; // 无扩展名 } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/ImageCropper.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/ImageCropper.java index 16b5dcc..c5c8657 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/ImageCropper.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/ImageCropper.java @@ -5,12 +5,61 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; /** * Java根据浮点型左上角/右下角坐标截图图片工具类 */ public class ImageCropper { + + public static String addNewSuffixToFileName(String originalFilePath) { + try{ + // 1. 非空校验 + if (originalFilePath == null || originalFilePath.trim().isEmpty()) { + throw new IllegalArgumentException("文件路径不能为空"); + } + + // 2. 解析文件路径为Path对象(跨平台兼容) + Path originalPath = Paths.get(originalFilePath); + // 获取文件名(如:upload_8693937242647132891.jpg) + String originalFileName = originalPath.getFileName().toString(); + // 获取文件所在目录(如:C:\Users\86157\AppData\Local\Temp) + String parentDir = originalPath.getParent() == null ? "" : originalPath.getParent().toString(); + + // 3. 拆分文件名和扩展名 + int lastDotIndex = originalFileName.lastIndexOf("."); + String fileNameWithoutExt; // 无扩展名的文件名 + String extension; // 文件扩展名(含.) + + if (lastDotIndex == -1) { + // 无扩展名的情况(如:readme) + fileNameWithoutExt = originalFileName; + extension = ""; + } else { + // 有扩展名的情况(如:upload_123.jpg 拆分为 upload_123 和 .jpg) + fileNameWithoutExt = originalFileName.substring(0, lastDotIndex); + extension = originalFileName.substring(lastDotIndex); + } + + // 4. 拼接新文件名(添加_new后缀) + String newFileName = fileNameWithoutExt + "_new" + extension; + + // 5. 拼接完整的新文件路径 + if (parentDir.isEmpty()) { + return newFileName; // 无目录时直接返回新文件名 + } else { + // 兼容Windows和Linux路径分隔符 + return Paths.get(parentDir, newFileName).toString(); + } + }catch(Exception e){ + e.printStackTrace(); + } + return null; + } + + /** * 截取图片指定区域(适配浮点型坐标) * @param srcImagePath 源图片路径 (如: "D:/test.jpg") diff --git a/bonus-modules/bonus-urk/src/main/java/com/bonus/urk/utils/ImageCropper.java b/bonus-modules/bonus-urk/src/main/java/com/bonus/urk/utils/ImageCropper.java new file mode 100644 index 0000000..a64f714 --- /dev/null +++ b/bonus-modules/bonus-urk/src/main/java/com/bonus/urk/utils/ImageCropper.java @@ -0,0 +1,125 @@ +package com.bonus.urk.utils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/** + * Java根据浮点型左上角/右下角坐标截图图片工具类 + */ +public class ImageCropper { + + /** + * 截取图片指定区域(适配浮点型坐标) + * @param srcImagePath 源图片路径 (如: "D:/test.jpg") + * @param destImagePath 截取后图片保存路径 (如: "D:/crop.jpg") + * @param x1 截取区域左上角x坐标(浮点型) + * @param y1 截取区域左上角y坐标(浮点型) + * @param x2 截取区域右下角x坐标(浮点型) + * @param y2 截取区域右下角y坐标(浮点型) + * @throws IOException 图片读取/写入异常 + */ + public static void cropImage(String srcImagePath, String destImagePath, + double x1, double y1, double x2, double y2) throws IOException { + // 1. 读取源图片 + File srcFile = new File(srcImagePath); + if (!srcFile.exists()) { + throw new IOException("源图片文件不存在: " + srcImagePath); + } + + BufferedImage srcImage = ImageIO.read(srcFile); + + // 2. 获取图片原始尺寸 + int imageWidth = srcImage.getWidth(); + int imageHeight = srcImage.getHeight(); + + // 3. 浮点坐标转整数(四舍五入保留精度) + int intX1 = (int) Math.round(x1); + int intY1 = (int) Math.round(y1); + int intX2 = (int) Math.round(x2); + int intY2 = (int) Math.round(y2); + + // 4. 校验坐标合法性 + // 检查左上角坐标 + if (intX1 < 0 || intY1 < 0 || intX1 >= imageWidth || intY1 >= imageHeight) { + throw new IllegalArgumentException( + String.format("左上角坐标越界!图片尺寸: %dx%d, 转换后坐标: (%d, %d) (原始: %.4f, %.4f)", + imageWidth, imageHeight, intX1, intY1, x1, y1) + ); + } + // 检查右下角坐标 + if (intX2 < 0 || intY2 < 0 || intX2 >= imageWidth || intY2 >= imageHeight) { + throw new IllegalArgumentException( + String.format("右下角坐标越界!图片尺寸: %dx%d, 转换后坐标: (%d, %d) (原始: %.4f, %.4f)", + imageWidth, imageHeight, intX2, intY2, x2, y2) + ); + } + // 检查坐标逻辑 + if (intX2 <= intX1) { + throw new IllegalArgumentException( + String.format("右下角x坐标必须大于左上角x坐标!转换后: x1=%d, x2=%d (原始: %.4f, %.4f)", + intX1, intX2, x1, x2) + ); + } + if (intY2 <= intY1) { + throw new IllegalArgumentException( + String.format("右下角y坐标必须大于左上角y坐标!转换后: y1=%d, y2=%d (原始: %.4f, %.4f)", + intY1, intY2, y1, y2) + ); + } + + // 5. 计算截取区域的宽高 + int cropWidth = intX2 - intX1; + int cropHeight = intY2 - intY1; + + // 6. 创建截取后的图片缓冲区 + BufferedImage destImage = new BufferedImage(cropWidth, cropHeight, srcImage.getType()); + + // 7. 绘制截取的区域 + Graphics2D g = destImage.createGraphics(); + // 增强截图清晰度(抗锯齿+插值) + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(srcImage, 0, 0, cropWidth, cropHeight, intX1, intY1, intX2, intY2, null); + g.dispose(); // 释放资源 + + // 8. 获取目标文件格式 + String format = destImagePath.substring(destImagePath.lastIndexOf(".") + 1).toUpperCase(); + if (!format.matches("(JPG|JPEG|PNG|BMP)")) { + format = "PNG"; + } + + // 9. 保存截取后的图片 + File destFile = new File(destImagePath); + File parentDir = destFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + + ImageIO.write(destImage, format, destFile); + System.out.printf("截图成功!\n原始坐标: (%.4f,%.4f) -> (%.4f,%.4f)\n转换后坐标: (%d,%d) -> (%d,%d)\n保存路径: %s\n", + x1, y1, x2, y2, intX1, intY1, intX2, intY2, destImagePath); + } + + // 测试方法(使用你提供的浮点坐标) + public static void main(String[] args) { + try { + // 替换为你的实际图片路径 + String srcPath = "D:\\File\\dd.jpg"; + String destPath = "D:\\File\\dd3.jpg"; + + // 你提供的浮点坐标 + double x1 = 1300.4857177734375; + double y1 = 722.174560546875; + double x2 =1936.892578125; + double y2 = 1726.121337890625; + // 调用截图方法 + cropImage(srcPath, destPath, x1, y1, x2, y2); + } catch (Exception e) { + System.err.println("截图失败: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file