添加人脸截取功能

This commit is contained in:
haozq 2026-02-10 18:54:16 +08:00
parent 4c9ddf7251
commit 801432202c
12 changed files with 498 additions and 112 deletions

View File

@ -155,6 +155,7 @@ public class Constants
*/
public static final String FILE_UPLOAD_WORKER = "pm_worker";
/**
* 人员入场表
*/

View File

@ -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());
}
}
}

View File

@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "face.service")
@Data
public class FaceServiceProperties {
private String baseUrl;
private String faceUrl;
}

View File

@ -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{

View File

@ -128,17 +128,26 @@ public class ZipDownloadController {
*/
@Scheduled(fixedRate = 3600000) // 每小时清理一次
public void cleanOldZips() throws IOException {
try (Stream<Path> 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<Path> 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) {}
});
}
}
}
}

View File

@ -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 数组

View File

@ -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());
}
}

View File

@ -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<UploadFileVo> uploadFileVos = new ArrayList<>();
boolean isBase64 = false;
MultipartFile facePhoto = null;
@ -127,16 +132,46 @@ public class PmWorkerServiceImpl implements PmWorkerService{
isBase64 = true;
}else{
List<WebFileDto> 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<String,Double> 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<WebFileDto> 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<UploadFileVo> 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<String,Double> 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

View File

@ -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());
}
}

View File

@ -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<String,Double> extractFaceImage(File imageFile) {
Map<String,Double> 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 返回结果对象包含 idnamecreateTime
*/
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 返回结果对象包含 idnamecreateTime
*/
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 ""; // 无扩展名
}

View File

@ -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")

View File

@ -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();
}
}
}