From 4c9ddf725105974ff3bb9415644f06dfe87c57a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E4=BA=AE?= Date: Tue, 10 Feb 2026 16:29:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=BA=E8=84=B8=E4=BD=BF=E7=94=A8=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bmw/config/FaceServiceInitializer.java | 26 + .../bmw/config/FaceServiceProperties.java | 13 + .../bmw/controller/PmProjectController.java | 13 +- .../bmw/controller/PmWorkerController.java | 104 +--- .../controller/RepairCardApplyController.java | 1 - .../com/bonus/bmw/domain/dto/PmWorkerDto.java | 2 + .../com/bonus/bmw/domain/face/FaceResult.java | 15 + .../com/bonus/bmw/domain/face/GroupInfo.java | 16 + .../com/bonus/bmw/domain/face/UserInfo.java | 16 + .../bmw/service/AppRecognitionService.java | 2 - .../impl/AppRecognitionServiceImpl.java | 149 +---- .../bmw/service/impl/AppServiceImpl.java | 21 +- .../service/impl/PmWorkerExitServiceImpl.java | 10 - .../bmw/service/impl/PmWorkerServiceImpl.java | 51 +- .../impl/RepairCardApplyServiceImpl.java | 2 +- .../bmw/utils/FaceFeatureExtractorUtil.java | 558 ++++++++++++++++++ .../src/main/resources/bootstrap-local.yml | 5 +- .../mapper/bmw/BmWorkerAttMapper.xml | 6 +- .../mapper/bmw/PmWorkerExitMapper.xml | 2 +- .../resources/mapper/bmw/PmWorkerMapper.xml | 4 +- .../job/controller/SysJobController.java | 27 +- .../bonus/job/service/SysJobServiceImpl.java | 45 +- 22 files changed, 799 insertions(+), 289 deletions(-) create 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/config/FaceServiceProperties.java create mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/FaceResult.java create mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/GroupInfo.java create mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/UserInfo.java create mode 100644 bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java 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 new file mode 100644 index 0000000..e58cfd9 --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceInitializer.java @@ -0,0 +1,26 @@ +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 new file mode 100644 index 0000000..aa1805f --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/config/FaceServiceProperties.java @@ -0,0 +1,13 @@ +package com.bonus.bmw.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@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/PmProjectController.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmProjectController.java index 2dea437..99015b1 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmProjectController.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/PmProjectController.java @@ -24,9 +24,7 @@ import com.bonus.common.security.annotation.RequiresPermissionsOrInnerAuth; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -34,7 +32,6 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.OutputStream; import java.net.URLEncoder; import java.util.*; @@ -862,11 +859,8 @@ public class PmProjectController extends BaseController { String fileName = URLEncoder.encode("三表一册", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); - // 3. 创建全局Workbook(复用,用于多Sheet) -// Workbook wb = new SXSSFWorkbook(); // SXSSFWorkbook:大数据量导出(避免OOM) XSSFWorkbook wb = new XSSFWorkbook(); try { - // 4. 为每个Sheet创建ExcelUtil并添加到Workbook // 4.1 Sheet1:农民工花名册 ExportProMonthTableRoster eptr = new ExportProMonthTableRoster(); Map map1 = new HashMap<>(); @@ -877,7 +871,7 @@ public class PmProjectController extends BaseController { itemMap.put("subName", vo.getSubName()); itemMap.put("teamName", vo.getTeamName()); itemMap.put("userName", vo.getUserName()); - itemMap.put("sex", vo.getSex()); + itemMap.put("sex", "1".equals(vo.getSex()) ? "男" : "女"); itemMap.put("workName", vo.getWorkName()); itemMap.put("unitAndCode", vo.getUnitAndCode()); itemMap.put("period", vo.getPeriod()); @@ -886,7 +880,7 @@ public class PmProjectController extends BaseController { itemMap.put("phone", vo.getPhone()); itemMap.put("inTime", vo.getInTime()); itemMap.put("outTime", vo.getOutTime()); - itemMap.put("onDuty", vo.getOnDuty()); + itemMap.put("onDuty", "1".equals(vo.getOnDuty()) ? "在岗" : "离岗"); itemMap.put("remark", vo.getRemark()); mapList1.add(itemMap); } @@ -981,9 +975,6 @@ public class PmProjectController extends BaseController { map4.put("proName",userWagePayVo.getProName()); euwp.createSheet(wb,map4); - - - // 5. 将Workbook写入响应流 wb.write(response.getOutputStream()); response.flushBuffer(); // 强制刷新流 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 40fd083..e40c558 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 @@ -2,11 +2,12 @@ package com.bonus.bmw.controller; import com.bonus.bmw.domain.dto.PmWorkerDto; import com.bonus.bmw.domain.dto.WebFileDto; +import com.bonus.bmw.domain.face.FaceResult; import com.bonus.bmw.domain.vo.PmWorker; import com.bonus.bmw.domain.vo.PmWorkerImport; import com.bonus.bmw.service.PmWorkerService; +import com.bonus.bmw.utils.FaceFeatureExtractorUtil; import com.bonus.common.core.utils.encryption.Sm4Utils; -import com.bonus.common.core.utils.face.ArcFaceHelper; import com.bonus.common.core.utils.json.FastJsonHelper; import com.bonus.common.core.utils.poi.ExcelUtil; import com.bonus.common.core.web.controller.BaseController; @@ -24,7 +25,6 @@ import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -188,42 +188,26 @@ public class PmWorkerController extends BaseController { @PostMapping("/faceDetection") @SysLog(title = "人脸照片合格检测", businessType = OperaType.UPDATE, logType = 0, module = "施工人员->出入场管理->人员入场管理", details = "人脸照片合格检测") public AjaxResult faceDetection(@RequestParam(value = "file") MultipartFile file) { - if (file == null || file.isEmpty()) { - return AjaxResult.error("文件为空:" ); + if (file == null || file.isEmpty()) { + return AjaxResult.error("文件为空" ); + } + File fileData = null; + try { + fileData = FaceFeatureExtractorUtil.multipartToFile(file); + FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData); + if (200 == faceResult.getCode()){ + return AjaxResult.success(faceResult.getMsg()); + }else{ + return AjaxResult.error(faceResult.getMsg()); } - // 创建临时文件 - File tempFile = null; - try { - // 获取文件后缀 - String originalFilename = file.getOriginalFilename(); - String suffix = ""; - if (originalFilename != null && originalFilename.contains(".")) { - suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); - } - // 创建临时文件 - tempFile = Files.createTempFile("temp-image-", suffix).toFile(); - tempFile.deleteOnExit(); // JVM退出时自动删除 - // 将 MultipartFile 写入临时文件 - file.transferTo(tempFile); - ArcFaceHelper arcFaceHelper = new ArcFaceHelper(); - // 调用原方法,传入临时文件的路径 - String faceFeatures; - try { - faceFeatures = arcFaceHelper.getIsFaceImage(tempFile.getAbsolutePath()); - } catch (Exception e) { - return AjaxResult.error("人脸照片识别有问题"); - } - String[] split = faceFeatures.split(","); - if ("200".equals(split[0])){ - return AjaxResult.success(split[1]); - }else{ - return AjaxResult.error(split[1]); - } - } catch (IOException e) { - // 处理异常(如磁盘满、权限不足等) - e.printStackTrace(); - return AjaxResult.error("人脸检测失败:"+e.getMessage()); + } catch (IOException e) { + logger.error("人脸检测失败:", e); + return AjaxResult.error("人脸检测失败:"+e.getMessage()); + } finally { + if (fileData != null && fileData.exists()) { + boolean delete = fileData.delete(); } + } } /** @@ -238,46 +222,22 @@ public class PmWorkerController extends BaseController { if (base64file == null || base64file.isEmpty()) { return AjaxResult.error("文件为空"); } - File tempFile = null; + File fileData = null; try { - // 解码base64数据 - String base64Data = base64file; - if (base64file.startsWith("data:image")) { - // 如果是data URL格式,提取base64部分 - base64Data = base64file.substring(base64file.indexOf(",") + 1); - } - // 确定文件扩展名(简单处理,可根据实际需求调整) - String suffix = ".jpg"; // 默认jpg格式 - if (base64file.contains("png")) { - suffix = ".png"; - } else if (base64file.contains("jpeg")) { - suffix = ".jpeg"; - } - // 创建临时文件 - tempFile = Files.createTempFile("temp-image-", suffix).toFile(); - tempFile.deleteOnExit(); // JVM退出时自动删除 - // 将base64数据写入临时文件 - byte[] imageBytes = java.util.Base64.getDecoder().decode(base64Data); - java.nio.file.Files.write(tempFile.toPath(), imageBytes); - - ArcFaceHelper arcFaceHelper = new ArcFaceHelper(); - // 调用原方法,传入临时文件的路径 - String faceFeatures = null; - try { - faceFeatures = arcFaceHelper.getIsFaceImage(tempFile.getAbsolutePath()); - } catch (Exception e) { - return AjaxResult.error("人脸照片识别有问题"); - } - String[] split = faceFeatures.split(","); - if ("200".equals(split[0])){ - return AjaxResult.success(split[1]); + fileData =FaceFeatureExtractorUtil.base64ToFile(base64file, "face_detection");; + FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData); + if (200 == faceResult.getCode()){ + return AjaxResult.success(faceResult.getMsg()); }else{ - return AjaxResult.error(split[1]); + return AjaxResult.error(faceResult.getMsg()); } - } catch (IOException e) { - // 处理异常(如磁盘满、权限不足等) - e.printStackTrace(); + } catch (Exception e) { + logger.error("人脸检测失败:", e); return AjaxResult.error("人脸检测失败:"+e.getMessage()); + } finally { + if (fileData != null && fileData.exists()) { + boolean delete = fileData.delete(); + } } } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/RepairCardApplyController.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/RepairCardApplyController.java index a7dac90..6c026b0 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/RepairCardApplyController.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/controller/RepairCardApplyController.java @@ -187,7 +187,6 @@ public class RepairCardApplyController extends BaseController { */ @PostMapping("/updateRepairCardApply") public AjaxResult updateRepairCardApply(@RequestParam(value = "file", required = false) MultipartFile[] files, @RequestParam(value = "fileMsg", required = false) String fileMsg, @RequestParam(value = "params") String params) { - try { params= Sm4Utils.decrypt(params); fileMsg= Sm4Utils.decrypt(fileMsg); diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/dto/PmWorkerDto.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/dto/PmWorkerDto.java index c985de1..3a5b2b3 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/dto/PmWorkerDto.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/dto/PmWorkerDto.java @@ -103,4 +103,6 @@ public class PmWorkerDto { */ private String lightStatus; + private String workerName; + } diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/FaceResult.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/FaceResult.java new file mode 100644 index 0000000..7257000 --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/FaceResult.java @@ -0,0 +1,15 @@ +package com.bonus.bmw.domain.face; + +import lombok.Data; + +@Data +public class FaceResult { + private int code; + private String msg; + private Object data; + public FaceResult(int code, String msg, Object o) { + this.code = code; + this.msg = msg; + this.data = o; + } +} diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/GroupInfo.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/GroupInfo.java new file mode 100644 index 0000000..474b97b --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/GroupInfo.java @@ -0,0 +1,16 @@ +package com.bonus.bmw.domain.face; + +import lombok.Data; + +@Data +public class GroupInfo { + private Long id; + private String name; + private String createTime; + + public GroupInfo(Long id, String name, String createTime) { + this.id = id; + this.name = name; + this.createTime = createTime; + } +} diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/UserInfo.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/UserInfo.java new file mode 100644 index 0000000..632e813 --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/domain/face/UserInfo.java @@ -0,0 +1,16 @@ +package com.bonus.bmw.domain.face; + +import lombok.Data; + +@Data +public class UserInfo { + private Long id; + private String name; + private Long groupId; + + public UserInfo(Long id, String name, Long groupId) { + this.id = id; + this.name = name; + this.groupId = groupId; + } +} diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/AppRecognitionService.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/AppRecognitionService.java index 1e70d6e..b9227e1 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/AppRecognitionService.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/AppRecognitionService.java @@ -11,8 +11,6 @@ public interface AppRecognitionService { String uploadFaceRecognition(MultipartFile facePhoto, FaceRecognitionBean bean); - String uploadFaceRecognition(String facePhoto, FaceRecognitionBean bean); - AjaxResult getFaceRecognition(MultipartFile facePhoto, String proId); AjaxResult bankCardRecognition(BankCardBean bean); 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 3d0c404..26a82bb 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 @@ -1,21 +1,19 @@ package com.bonus.bmw.service.impl; import cn.hutool.core.date.DateUtil; -import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; import com.bonus.bmw.domain.dto.BankCardBean; import com.bonus.bmw.domain.dto.BmWorkerEinUserVo; import com.bonus.bmw.domain.dto.IdCardBean; +import com.bonus.bmw.domain.face.FaceResult; +import com.bonus.bmw.domain.face.UserInfo; import com.bonus.bmw.domain.po.FaceRecognitionBean; import com.bonus.bmw.mapper.AppRecognitionMapper; import com.bonus.bmw.service.AppRecognitionService; import com.bonus.bmw.utils.BaiduRecognitionUtils; -import com.bonus.bmw.utils.FaceStatusCodeReturn; +import com.bonus.bmw.utils.FaceFeatureExtractorUtil; import com.bonus.common.core.constant.Constants; import com.bonus.common.core.utils.DateUtils; import com.bonus.common.core.utils.StringUtils; -import com.bonus.common.core.utils.json.FastJsonHelper; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.system.api.model.UploadFileVo; import lombok.extern.slf4j.Slf4j; @@ -30,8 +28,8 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.File; import java.net.URLEncoder; -import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -49,8 +47,8 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { @Autowired private FileUploadUtils fileUploadUtils; - @Value("${face.path}") - public String faceUrl; + @Value("${face.groupId}") + private Long groupId; /** * 人脸识别-人脸照片采集入库 @@ -61,128 +59,41 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { @Override public String uploadFaceRecognition(MultipartFile facePhoto, FaceRecognitionBean bean) { try { - // 最多尝试两次,避免无限循环 ,删除无需重复 - int maxRetries = 1; - if(!"delete".equals(bean.getOptMode())){ - // 1. 获取文件字节数组 - byte[] bytes = facePhoto.getBytes(); - // 2. 使用 Base64 编码 - String fileBase64 = Base64.getEncoder().encodeToString(bytes); - // 根据文件类型添加相应的前缀 - String originalFilename = facePhoto.getOriginalFilename(); - if (originalFilename != null && originalFilename.toLowerCase().endsWith(".png")) { - bean.setImg("data:image/png;base64," + fileBase64); - } else { - bean.setImg("data:image/jpeg;base64," + fileBase64); - } - maxRetries = 2; + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(bean.getIdNumber(), groupId, FaceFeatureExtractorUtil.multipartToFile(facePhoto)); + if(faceResult.getCode() == 200) { + // 解析 data 数组 + return "人脸入库成功"; }else{ - bean.setImg("data:image/jpeg;base64,fddsdafretrthwuwtrewyewtrweet"); - } - for (int i = 0; i < maxRetries; i++) { - String body = HttpUtil.post(faceUrl + "/updatedb", - FastJsonHelper.beanToJsonStr(bean)); - log.error(body); - JSONObject jsonObject = FastJsonHelper.jsonStrToJsonObj(body); - String code = jsonObject.getString("code"); - if(StringUtils.isEmpty(code)){ - log.error("公司人脸识别大傻逼,code不放一起"); - code = jsonObject.getJSONObject("detail").getString("code"); - } - if ("30019".equals(code) && i == 0) { - // 第一次遇到30019错误时,设置replace模式再试一次 - bean.setOptMode("replace"); - } else { - // 其他情况直接返回结果 - return "30002".equals(code) ? FaceStatusCodeReturn.faceStatusCodeReturn(30002) - : FaceStatusCodeReturn.faceStatusCodeReturn(Integer.parseInt(code)); - } + return faceResult.getMsg(); } } catch (Exception e) { log.error("上传文件失败", e); - return "公司内部人脸识别服务处理异常"; + return "人脸识别服务处理异常"; } - return "公司内部人脸识别服务处理异常"; - } - - - /** - * 人脸识别-人脸照片采集入库 - * @param facePhoto url - * @param bean - * @return - */ - @Override - public String uploadFaceRecognition(String facePhoto, FaceRecognitionBean bean) { - try { - // 最多尝试两次,避免无限循环 ,删除无需重复 - int maxRetries = 1; - if(!"delete".equals(bean.getOptMode())){ - log.error("进人脸服务器的人脸图片:{}", facePhoto); - bean.setImg("data:image/png;base64,"+facePhoto); - maxRetries = 2; - }else{ - bean.setImg("data:image/png;base64,fddsdafretrthwuwtrewyewtrweet"); - } - for (int i = 0; i < maxRetries; i++) { - String body = HttpUtil.post(faceUrl + "/updatedb", - FastJsonHelper.beanToJsonStr(bean)); - log.error(body); - JSONObject jsonObject = FastJsonHelper.jsonStrToJsonObj(body); - String code = jsonObject.getString("code"); - if(StringUtils.isEmpty(code)){ - log.error("公司人脸识别大傻逼,code不放一起"); - code = jsonObject.getJSONObject("detail").getString("code"); - } - if ("30019".equals(code) && i == 0) { - // 第一次遇到30019错误时,设置replace模式再试一次 - bean.setOptMode("replace"); - } else { - // 其他情况直接返回结果 - return "30002".equals(code) ? FaceStatusCodeReturn.faceStatusCodeReturn(30002) - : FaceStatusCodeReturn.faceStatusCodeReturn(Integer.parseInt(code)); - } - } - } catch (Exception e) { - log.error("上传文件失败", e); - return "公司内部人脸识别服务处理异常"; - } - return "公司内部人脸识别服务处理异常"; } @Override public AjaxResult getFaceRecognition(MultipartFile facePhoto, String proId) { try { - // 1. 获取文件字节数组 - byte[] bytes = facePhoto.getBytes(); - // 2. 使用 Base64 编码 - String fileBase64 = Base64.getEncoder().encodeToString(bytes); - // 根据文件类型添加相应的前缀 - String originalFilename = facePhoto.getOriginalFilename(); - if (originalFilename != null && originalFilename.toLowerCase().endsWith(".png")) { - fileBase64 = "data:image/png;base64," + fileBase64; - } else { - fileBase64 = "data:image/jpeg;base64," + fileBase64; - } - String body = HttpUtil.post(faceUrl + "/facerecognition", - FastJsonHelper.beanToJsonStr(new FaceRecognitionBean(fileBase64))); - log.info("人脸识别响应内容: {}", body); - JSONObject jsonObject = FastJsonHelper.jsonStrToJsonObj(body); - String code = jsonObject.getString("code"); + File file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); + FaceResult faceResult = FaceFeatureExtractorUtil.faceRecognition(groupId, file); BmWorkerEinUserVo sysFile = new BmWorkerEinUserVo(); - if(!"30000".equals(code)){ - sysFile.setFaceResult(false); - sysFile.setFaceRemark(body); + if(faceResult.getCode() == 200) { + // 解析 data 数组 + UserInfo obj = (UserInfo) faceResult.getData(); + if (obj != null) { + String idNumber = obj.getName(); + // 1. 获取用户信息 + sysFile = mapper.getOnUserInfoByIdNumber(idNumber,proId); + sysFile.setFaceResult(true); + sysFile.setImage(faceResult.getMsg()); + return AjaxResult.success(sysFile); + }else { + return AjaxResult.error("人脸接口返回异常"); + } }else{ - String data = jsonObject.getString("data"); - JSONArray jsonArray = FastJsonHelper.jsonArrStrToJsonArr(data); - String idNumber = (String) jsonArray.get(0); - // 1. 获取用户信息 - sysFile = mapper.getOnUserInfoByIdNumber(idNumber,proId); - sysFile.setFaceResult(true); + return AjaxResult.error(faceResult.getMsg()); } - return !"30000".equals(code) ? AjaxResult.error(FaceStatusCodeReturn.faceStatusCodeReturn(Integer.parseInt(code))) - : AjaxResult.success(sysFile); } catch (Exception e) { log.error("上传文件失败", e); return AjaxResult.error("未找到人脸信息"); @@ -295,8 +206,4 @@ public class AppRecognitionServiceImpl implements AppRecognitionService { } } - - - - } 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 2f2f725..c1ba0f3 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 @@ -2,12 +2,13 @@ package com.bonus.bmw.service.impl; import cn.hutool.core.date.DateUtil; import com.bonus.bmw.domain.dto.PmWorkerDto; +import com.bonus.bmw.domain.face.FaceResult; import com.bonus.bmw.domain.po.FaceRecognitionBean; import com.bonus.bmw.domain.vo.*; import com.bonus.bmw.mapper.*; -import com.bonus.bmw.service.AppRecognitionService; import com.bonus.bmw.service.AppService; import com.bonus.bmw.service.UrkSendService; +import com.bonus.bmw.utils.FaceFeatureExtractorUtil; import com.bonus.common.core.constant.Constants; import com.bonus.common.core.utils.StringUtils; import com.bonus.common.core.web.domain.AjaxResult; @@ -18,6 +19,7 @@ import com.bonus.system.api.model.UploadFileVo; import com.github.pagehelper.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -43,15 +45,15 @@ public class AppServiceImpl implements AppService { @Autowired private PmWorkerMapper pmWorkerMapper; + @Value("${face.groupId}") + private Long groupId; + /** * 引入urk服务 调用考勤机服务 */ @Autowired private UrkSendService urkSendService; - @Resource - private AppRecognitionService appRecognitionService; - @Resource private BmWorkerWageCardMapper wageCardMapper; @Autowired @@ -148,12 +150,10 @@ public class AppServiceImpl implements AppService { //下发人脸到考勤机 urkSendService.sendUserToDevice(record.getId(), record.getProId(), record.getSubId(), record.getTeamId(),"0"); //下发人脸到人脸库 - FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); - faceRecognitionBean.setUniqueKey(record.getIdNumber()); - faceRecognitionBean.setOptMode("add"); UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, ""); if (fileBast64 != null) { - sb.append(appRecognitionService.uploadFaceRecognition(fileBast64.getBast64(), faceRecognitionBean)); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); + sb.append(faceResult.getMsg()); } } //入场相关数据添加 @@ -229,11 +229,10 @@ public class AppServiceImpl implements AppService { } //下发人脸到人脸库 FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); - faceRecognitionBean.setUniqueKey(record.getIdNumber()); - faceRecognitionBean.setOptMode("add"); UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, ""); if (fileBast64 != null) { - sb.append(appRecognitionService.uploadFaceRecognition(fileBast64.getBast64(), faceRecognitionBean)); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64()); + sb.append(faceResult.getMsg()); } } if(record.getEinStatus() == 1){ diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerExitServiceImpl.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerExitServiceImpl.java index d37b333..ec31cd3 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerExitServiceImpl.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/PmWorkerExitServiceImpl.java @@ -2,7 +2,6 @@ package com.bonus.bmw.service.impl; import com.bonus.bmw.domain.dto.PmWorkerDto; import com.bonus.bmw.domain.dto.WebFileDto; -import com.bonus.bmw.domain.po.FaceRecognitionBean; import com.bonus.bmw.domain.vo.MapBeanVo; import com.bonus.bmw.domain.vo.PmWorker; import com.bonus.bmw.mapper.PmWorkerExitMapper; @@ -96,15 +95,6 @@ public class PmWorkerExitServiceImpl implements PmWorkerExitService { } } - private void delAppFace(Integer workerId) { - String idNumber = mapper.getIdNumberByWorkerId(workerId); - //下发人脸到人脸库 - FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); - faceRecognitionBean.setUniqueKey(idNumber); - faceRecognitionBean.setOptMode("delete"); - appRecognitionService.uploadFaceRecognition("", faceRecognitionBean); - } - @Override public AjaxResult updateWorkerBatchExit(List list) throws Exception { StringBuilder sb = new StringBuilder(); 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 f1dd83c..d7f2890 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 @@ -3,11 +3,12 @@ package com.bonus.bmw.service.impl; import cn.hutool.core.date.DateUtil; import com.bonus.bmw.domain.dto.PmWorkerDto; import com.bonus.bmw.domain.dto.WebFileDto; -import com.bonus.bmw.domain.po.FaceRecognitionBean; +import com.bonus.bmw.domain.face.FaceResult; 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.FaceFeatureExtractorUtil; import com.bonus.common.core.constant.Constants; import com.bonus.common.core.exception.ServiceException; import com.bonus.common.core.utils.StringUtils; @@ -20,16 +21,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.validation.Validator; +import java.io.File; import java.time.LocalDate; import java.time.Period; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Service @@ -58,6 +64,9 @@ public class PmWorkerServiceImpl implements PmWorkerService{ @Autowired protected Validator validator; + @Value("${face.groupId}") + private Long groupId; + /** * 引入urk服务 调用考勤机服务 */ @@ -140,13 +149,21 @@ public class PmWorkerServiceImpl implements PmWorkerService{ } //下发人脸到人脸库 if(!uploadFileVos.isEmpty()){ - FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); - faceRecognitionBean.setUniqueKey(record.getIdNumber()); - faceRecognitionBean.setOptMode("add"); if(isBase64){ - sb.append(appRecognitionService.uploadFaceRecognition(record.getFacePhotoBase64(), faceRecognitionBean)); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); + sb.append(faceResult.getMsg()); }else{ - sb.append(appRecognitionService.uploadFaceRecognition(facePhoto, faceRecognitionBean)); + File file = null; + try { + file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file); + sb.append(faceResult.getMsg()); + } finally { + if (file != null && file.exists()) { + boolean delete = file.delete(); + // 或使用 Hutool: FileUtil.del(file); + } + } } } } @@ -452,20 +469,27 @@ public class PmWorkerServiceImpl implements PmWorkerService{ } //下发人脸到人脸库 if(!uploadFileVos.isEmpty()){ - FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean(); - faceRecognitionBean.setUniqueKey(record.getIdNumber()); - faceRecognitionBean.setOptMode("add"); if(isBase64){ - sb.append(appRecognitionService.uploadFaceRecognition(record.getFacePhotoBase64(), faceRecognitionBean)); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64()); + sb.append(faceResult.getMsg()); }else{ - sb.append(appRecognitionService.uploadFaceRecognition(facePhoto, faceRecognitionBean)); + File file = null; + try { + file = FaceFeatureExtractorUtil.multipartToFile(facePhoto); + FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file); + sb.append(faceResult.getMsg()); + } finally { + if (file != null && file.exists()) { + boolean delete = file.delete(); + } + } } } } if(record.getEinStatus() == 1){ //已经入过场了 //是否修改工种 - if(record.getPostId() != null && record.getPostId() > 0){ + if(record.getPostId() != null && record.getPostId() > 0 && record.getPostName() != null && !record.getPostName().isEmpty()){ mapper.updateWorkerPost(record); } addWorkerWageCardDataAndContract(record,fileMsg); @@ -479,7 +503,6 @@ public class PmWorkerServiceImpl implements PmWorkerService{ log.error("人员下发考勤机失败:",e); sb.append("人员下发考勤机失败--"); } - addWorkerEinData(record,fileMsg); } return AjaxResult.success(sb.append("--基础数据更新成功").toString()); diff --git a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/RepairCardApplyServiceImpl.java b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/RepairCardApplyServiceImpl.java index 2ba8ecb..5e2acf6 100644 --- a/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/RepairCardApplyServiceImpl.java +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/service/impl/RepairCardApplyServiceImpl.java @@ -168,7 +168,7 @@ public class RepairCardApplyServiceImpl implements RepairCardApplyService { @Override public AjaxResult updateRepairCardApply(RepairCardApplyDto cardApplyDto, FileBasicMsgDto fileBasicMsgDto) { - cardApplyDto.setApplyUser(SecurityUtils.getUsername()); + cardApplyDto.setApplyUser(SecurityUtils.getLoginUser().getUsername()); Integer num = repairCardApplyMapper.updateRepairCardApply(cardApplyDto); if (num > 0) { repairCardApplyMapper.delRepairCardRecord(cardApplyDto); 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 new file mode 100644 index 0000000..5438f31 --- /dev/null +++ b/bonus-modules/bonus-bmw/src/main/java/com/bonus/bmw/utils/FaceFeatureExtractorUtil.java @@ -0,0 +1,558 @@ +package com.bonus.bmw.utils; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.IdUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.bonus.bmw.domain.face.FaceResult; +import com.bonus.bmw.domain.face.GroupInfo; +import com.bonus.bmw.domain.face.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 人脸识别算法服务(Python 微服务)工具类 + * 统一管理 API 地址,使用 Hutool 封装 HTTP 调用 + */ +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"; + + // ========== 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"; + + /** + * 设置算法服务的基础 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(); + } + } + + + /** + * 提取人脸特征向量 + * + * @param imageFile 人脸图片文件(jpg/png) + * @return ExtractResult 包含成功状态、特征向量、错误信息等 + */ + public static 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秒超时 + .execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "HTTP error: " + status, null); + } + JSONObject json = JSONUtil.parseObj(body); + boolean success = json.getBool("success", false); + String message = json.getStr("message", "Unknown error"); + if (!success) { + return new FaceResult(500, message, null); + } + // 解析 1024 维特征 + List featureList = json.getJSONArray("feature").toList(Double.class); + double[] featureArray = new double[featureList.size()]; + for (int i = 0; i < featureList.size(); i++) { + featureArray[i] = featureList.get(i); + } + return new FaceResult(200, "人脸验证成功", featureArray); + } catch (Exception e) { + String errorMsg = "Exception during feature extraction: " + ExceptionUtil.stacktraceToString(e); + return new FaceResult(500, errorMsg, null); + } + } + + /** + * 健康检查 + */ + public static FaceResult healthCheck() { + try { + HttpResponse response = HttpRequest.get(FACE_URL + 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); + } + return new FaceResult(500, "服务健康检查异常", null); + } catch (Exception e) { + return new FaceResult(500, "服务健康检查异常", null); + } + } + + /** + * 创建分组 + * + * @param name 分组名称(必填) + * @param description 分组描述(可选,可传 null) + * @return 返回结果对象,包含 id、name、createTime 等 + */ + public static 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 字符串 + .contentType("application/json") // 设置 Content-Type + .timeout(5000) // 超时 5 秒 + .execute(); + // 解析响应 + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "创建分组-人脸库返回异常", null); + } + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + if (code != 200) { + return new FaceResult(status, msg, null); + } + // 提取 data 部分 + JSONObject data = respJson.getJSONObject("data"); + Long id = data.getLong("id"); + String groupName = data.getStr("name"); + String createTime = data.getStr("createTime"); + return new FaceResult(status, "success", new GroupInfo(id, groupName, createTime)); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + /** + * 获取所有分组列表 + * + * @return 返回结果对象,包含 id、name、createTime 等 + */ + public static FaceResult getGroupList() { + try { + // 构建请求体 JSON + HttpResponse response = HttpRequest.get(BASE_URL + GROUP_LIST_PATH).timeout(5000).execute(); + // 解析响应 + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "获取分组列表-人脸库返回异常", null); + } + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + if (code != 200) { + return new FaceResult(status, msg, null); + } + // 解析 data 数组 + JSONArray dataArray = respJson.getJSONArray("data"); + List groupList = new ArrayList<>(); + if (dataArray != null) { + for (Object obj : dataArray) { + if (obj instanceof JSONObject) { + JSONObject item = (JSONObject) obj; + Long id = item.getLong("id"); + String name = item.getStr("name"); + // 注意:list 接口不返回 createTime,所以只取 id 和 name + groupList.add(new GroupInfo(id, name, null)); + } + } + } + return new FaceResult(status, "success", groupList); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + + /** + * 【核心方法】新增人员 + * 支持格式: + * @param idNumber 人员主键 + * @param groupId 分组ID(可为 null) + * @param photoFile 图片 + * @return 添加结果 + */ + public static FaceResult addUser(String idNumber, Long groupId, File photoFile) { + if (idNumber == null || idNumber.trim().isEmpty()) { + return new FaceResult(500, "人员主键不能为空", null); + } + if (photoFile == null || !photoFile.exists()) { + return new FaceResult(500, "照片文件无效", null); + } + try { + HttpRequest request = HttpRequest.post(BASE_URL + USER_ADD_PATH) + .form("name", idNumber) + // Hutool 自动处理 multipart + .form("photo", photoFile); + if (groupId != null) { + request.form("groupId", groupId.toString()); + } + + HttpResponse response = request.timeout(10000).execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(500, "HTTP Error: " + status, null); + } + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + if (code != 200) { + return new FaceResult(status, msg, null); + } + JSONObject data = respJson.getJSONObject("data"); + Long id = data.getLong("id"); + String savedName = data.getStr("name"); + Long savedGroupId = data.getLong("groupId", null); + UserInfo userInfo = new UserInfo(id, savedName, savedGroupId); + return new FaceResult(status, "success", userInfo); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + + + /** + * 【核心方法】通过文件上传更新人员信息 + * + * @param id 人员ID(必填) + * @param idNumber 人员主键(必填) + * @param groupId 分组ID(可为 null,表示不修改) + * @param photoFile 新照片文件(可为 null,表示不修改) + * @return 更新结果 + */ + public static FaceResult updateUser(Long id, String idNumber, Long groupId, File photoFile) { + if (id == null || id <= 0) { + return new FaceResult(500, "人员ID无效", null); + } + if (idNumber == null || idNumber.trim().isEmpty()) { + return new FaceResult(500, "人员主键不能为空", null); + } + try { + HttpRequest request = HttpRequest.post(BASE_URL + USER_UPDATE_PATH +"/"+ id) + .form("name", idNumber); // 必填 + if (groupId != null) { + request.form("groupId", groupId.toString()); + } + if (photoFile != null && photoFile.exists()) { + request.form("photo", photoFile); + } + HttpResponse response = request.timeout(10000).execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "HTTP Error: " + status, null); + } + + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + + if (code != 200) { + return new FaceResult(status, msg, null); + } + // 可根据实际返回结构解析 data,此处简化为只返回成功状态 + return new FaceResult(status, "success", null); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + /** + * 用于上传一张人脸照片,并在指定分组内检索最相似的人 + * @param photoFile 人员主键关键词(可为 null 或空,表示不筛选) + * @param groupId 分组ID(可为 null,表示不筛选) + * @return 查询结果 + */ + public static FaceResult faceRecognition(Long groupId, File photoFile) { + if (photoFile == null || !photoFile.exists()) { + return new FaceResult(500, "照片文件无效", null); + } + if (groupId == null || groupId <= 0) { + return new FaceResult(500, "分组Id不能为空", null); + } + try { + HttpRequest request = HttpRequest.post(BASE_URL + USER_SEARCH_PATH) + .form("photo", photoFile) + .form("groupId", groupId); + HttpResponse response = request.timeout(10000).execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "HTTP Error: " + status, null); + } + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + if (code != 200) { + return new FaceResult(status, msg, null); + } + // 解析 data 数组 + JSONObject obj = respJson.getJSONObject("data"); + UserInfo userInfo = null; + if (obj != null) { + Long id = obj.getLong("id"); + String userName = obj.getStr("name"); + Long gid = obj.getLong("groupId", null); + userInfo = new UserInfo(id,userName,gid); + } + // 可根据实际返回结构解析 data,此处简化为只返回成功状态 + return new FaceResult(status, "检测成功", userInfo); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + /** + * 通过主键去更新人员数据 + * + * @param idNumber 人员主键关键词(可为 null 或空,表示不筛选) + * @param groupId 分组ID(可为 null,表示不筛选) + * @return 查询结果 + */ + public static FaceResult updateUserByIdNumber(String idNumber, Long groupId, File photoFile) { + try { + FaceResult faceResult = getFaceUserList(idNumber, groupId); + if(faceResult.getCode() !=200){ + return faceResult; + } + List userList = (List) faceResult.getData(); + if(userList.isEmpty()){ + return addUser(idNumber, groupId, photoFile); + }else if(userList.size()>1){ + return new FaceResult(500, "查询到多个人员主键,请去人脸库查找问题", null); + }else { + return updateUser(userList.get(0).getId(), idNumber, groupId, photoFile); + } + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + + /** + * 获取人员列表(支持按人员主键模糊查询、按分组筛选) + * + * @param idNumber 人员主键关键词(可为 null 或空,表示不筛选) + * @param groupId 分组ID(可为 null,表示不筛选) + * @return 查询结果 + */ + public static FaceResult getFaceUserList(String idNumber, Long groupId) { + try { + HttpRequest request = HttpRequest.get(BASE_URL + USER_LIST_PATH); + // 动态添加查询参数(仅当值有效时) + if (idNumber != null && !idNumber.trim().isEmpty()) { + request.form("name", idNumber.trim()); // Hutool GET 也支持 .form() 自动转 query + } + if (groupId != null && groupId > 0) { + request.form("groupId", groupId.toString()); + } + HttpResponse response = request.timeout(5000).execute(); + int status = response.getStatus(); + String body = response.body(); + if (status != 200) { + return new FaceResult(status, "HTTP Error: " + status, null); + } + JSONObject respJson = JSONUtil.parseObj(body); + int code = respJson.getInt("code", -1); + String msg = respJson.getStr("msg", "Unknown"); + if (code != 200) { + return new FaceResult(status, msg, null); + } + // 解析 data 数组 + JSONArray dataArray = respJson.getJSONArray("data"); + List userList = new ArrayList<>(); + if (dataArray != null) { + for (Object obj : dataArray) { + if (obj instanceof JSONObject) { + JSONObject item = (JSONObject) obj; + Long id = item.getLong("id"); + String userName = item.getStr("name"); + Long gid = item.getLong("groupId", null); + userList.add(new UserInfo(id, userName, gid)); + } + } + } + return new FaceResult(status, "success", userList); + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + /** + * 【便捷方法】通过 Base64 字符串新增人员 + * 支持格式: + * - 纯 Base64: "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAg..." + * - Data URL: "data:image/jpeg;base64,/9j/4AAQ..." + * @param idNumber 人员主键 + * @param groupId 分组ID(可为 null) + * @param base64Image Base64 编码的图片 + * @return 添加结果 + */ + public static FaceResult addUserWithBase64(String idNumber, Long groupId, String base64Image) { + if (base64Image == null || base64Image.trim().isEmpty()) { + return new FaceResult(500, "Base64 图片为空", null); + } + File tempFile = null; + try { + tempFile = base64ToFile(base64Image, "face_add_"); + if (tempFile == null) { + return new FaceResult(500, "无法创建临时文件", null); + } + return addUser(idNumber, groupId, tempFile); + } catch (IllegalArgumentException e) { + return new FaceResult(500, "Base64 格式无效: " + e.getMessage(), null); + } catch (IORuntimeException e) { + return new FaceResult(500, "写入临时文件失败: " + e.getMessage(), null); + } finally { + if (tempFile != null && tempFile.exists()) { + FileUtil.del(tempFile); + } + } + } + + /** + * 【便捷方法】通过 Base64 更新人员信息 + * + * @param id 人员ID + * @param idNumber idNumber(必填) + * @param groupId 分组ID(可为 null) + * @param base64Image Base64 编码的图片(可为 null) + * @return 更新结果 + */ + public static FaceResult updateUserWithBase64(Long id, String idNumber, Long groupId, String base64Image) { + File tempFile = null; + try { + if (base64Image != null && !base64Image.trim().isEmpty()) { + tempFile = base64ToFile(base64Image, "face_update_"); + if (tempFile == null) { + return new FaceResult(500, "无法创建临时文件", null); + } + } + // 若 base64Image 为空,则 tempFile 为 null,表示不更新照片 + return updateUser(id, idNumber, groupId, tempFile); + } catch (IllegalArgumentException e) { + return new FaceResult(500, "Base64 格式无效: " + e.getMessage(), null); + } catch (IORuntimeException e) { + return new FaceResult(500, "写入临时文件失败: " + e.getMessage(), null); + } finally { + if (tempFile != null && tempFile.exists()) { + FileUtil.del(tempFile); + } + } + } + + /** + * 通过主键去更新人员数据 + * + * @param idNumber 人员主键关键词(可为 null 或空,表示不筛选) + * @param groupId 分组ID(可为 null,表示不筛选) + * @return 查询结果 + */ + public static FaceResult updateUserByIdNumberBase64(String idNumber, Long groupId, String photoFileBase64) { + try { + FaceResult faceResult = getFaceUserList(idNumber, groupId); + if(faceResult.getCode() !=200){ + return faceResult; + } + List userList = (List) faceResult.getData(); + if(userList.isEmpty()){ + return addUserWithBase64(idNumber, groupId, photoFileBase64); + }else if(userList.size()>1){ + return new FaceResult(500, "查询到多个人员主键,请去人脸库查找问题", null); + }else { + return updateUserWithBase64(userList.get(0).getId(), idNumber, groupId, photoFileBase64); + } + } catch (Exception e) { + return new FaceResult(500, "请求异常: " + e.getMessage(), null); + } + } + + /** + * 将 Base64 编码的图片字符串转换为临时文件 + * + * @param base64Image 支持纯 Base64 或 Data URL 格式(如 data:image/jpeg;base64,...) + * @param prefix 临时文件前缀(如 "face_add_", "face_update_"),用于区分用途 + * @return 临时 File 对象;若输入无效则返回 null + * @throws IllegalArgumentException Base64 格式非法 + * @throws IORuntimeException 写入临时文件失败 + */ + public static File base64ToFile(String base64Image, String prefix) { + if (base64Image == null || base64Image.trim().isEmpty()) { + return null; + } + String base64Data = base64Image.trim(); + // 去除 Data URL 前缀 + Pattern pattern = Pattern.compile("^data:image/(jpeg|jpg|png);base64,(.*)$", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(base64Data); + if (matcher.matches()) { + base64Data = matcher.group(2); + } + // 解码 + byte[] imageBytes = Base64.getDecoder().decode(base64Data); + // 生成唯一文件名 + String fileName = (prefix != null ? prefix : "temp_") + IdUtil.fastSimpleUUID() + ".jpg"; + String tempPath = System.getProperty("java.io.tmpdir") + "/" + fileName; + + // 写入临时文件 + return FileUtil.writeBytes(imageBytes, tempPath); + } + + public static File multipartToFile(MultipartFile multipartFile) throws IOException { + // 创建临时文件(自动在系统临时目录) + File tempFile = File.createTempFile( + "upload_", + "." + getExtension(multipartFile.getOriginalFilename()) + ); + // 将 MultipartFile 写入临时文件 + multipartFile.transferTo(tempFile); + return tempFile; + } + // 辅助方法:获取文件扩展名 + private static String getExtension(String fileName) { + if (fileName == null || fileName.lastIndexOf(".") == -1) { + return ""; // 无扩展名 + } + return fileName.substring(fileName.lastIndexOf(".") + 1); + } + +} diff --git a/bonus-modules/bonus-bmw/src/main/resources/bootstrap-local.yml b/bonus-modules/bonus-bmw/src/main/resources/bootstrap-local.yml index 8136429..dba5e22 100644 --- a/bonus-modules/bonus-bmw/src/main/resources/bootstrap-local.yml +++ b/bonus-modules/bonus-bmw/src/main/resources/bootstrap-local.yml @@ -28,4 +28,7 @@ jasypt: password: Encrypt #人脸 face: - path: http://112.29.103.165:1616/faceIdentification \ No newline at end of file + service: + base-url: http://192.168.0.37:18080/api + face-url: http://192.168.0.37:18000 + groupId: 5 diff --git a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/BmWorkerAttMapper.xml b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/BmWorkerAttMapper.xml index 72cb366..4129738 100644 --- a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/BmWorkerAttMapper.xml +++ b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/BmWorkerAttMapper.xml @@ -36,9 +36,9 @@ SELECT psc.id AS sub_com_id, psc.sub_company_name, - SUM(CASE WHEN pp.pro_status = '0' THEN 1 ELSE 0 END) AS buildProNum, - SUM(CASE WHEN pp.pro_status = '2' THEN 1 ELSE 0 END) AS preProNum, - SUM(CASE WHEN pp.pro_status = '4' THEN 1 ELSE 0 END) AS completedProNum, + COUNT(DISTINCT CASE WHEN pp.pro_status = '0' THEN pp.id ELSE null END) AS buildProNum, + COUNT(DISTINCT CASE WHEN pp.pro_status = '2' THEN pp.id ELSE null END) AS preProNum, + COUNT(DISTINCT CASE WHEN pp.pro_status = '4' THEN pp.id ELSE null END) AS completedProNum, COUNT(DISTINCT bsc.sub_id) AS subNum, COUNT(DISTINCT bstc.team_id) AS teamNum, COUNT(DISTINCT bwem.worker_id) AS einNum, diff --git a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerExitMapper.xml b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerExitMapper.xml index 02cc7c1..f4bff3d 100644 --- a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerExitMapper.xml +++ b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerExitMapper.xml @@ -275,7 +275,7 @@ count(1) FROM bm_worker_ein_msg bwem - INNER JOIN bm_att_person bap ON bap.worker_id + INNER JOIN bm_att_person bap ON bap.worker_id = bwem.worker_id AND bap.pro_id = bwem.pro_id WHERE bwem.worker_id = #{workerId} diff --git a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerMapper.xml b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerMapper.xml index 6a4967a..81fd64c 100644 --- a/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerMapper.xml +++ b/bonus-modules/bonus-bmw/src/main/resources/mapper/bmw/PmWorkerMapper.xml @@ -435,8 +435,8 @@ and locate(#{teamName}, bwem.team_name) > 0 - - and pw.name like concat('%',#{name},'%') + + and pw.name like concat('%',#{workerName},'%') GROUP BY pw.id,bwem.pro_id diff --git a/bonus-modules/bonus-job/src/main/java/com/bonus/job/controller/SysJobController.java b/bonus-modules/bonus-job/src/main/java/com/bonus/job/controller/SysJobController.java index 658583a..bb4ee89 100644 --- a/bonus-modules/bonus-job/src/main/java/com/bonus/job/controller/SysJobController.java +++ b/bonus-modules/bonus-job/src/main/java/com/bonus/job/controller/SysJobController.java @@ -1,20 +1,5 @@ package com.bonus.job.controller; -import java.util.List; -import javax.servlet.http.HttpServletResponse; - -import com.bonus.common.log.annotation.SysLog; -import com.bonus.common.log.enums.OperaType; -import org.quartz.SchedulerException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; import com.bonus.common.core.constant.Constants; import com.bonus.common.core.exception.job.TaskException; import com.bonus.common.core.utils.StringUtils; @@ -22,16 +7,24 @@ import com.bonus.common.core.utils.poi.ExcelUtil; import com.bonus.common.core.web.controller.BaseController; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.common.core.web.page.TableDataInfo; +import com.bonus.common.log.annotation.SysLog; +import com.bonus.common.log.enums.OperaType; import com.bonus.common.security.annotation.RequiresPermissions; import com.bonus.common.security.utils.SecurityUtils; import com.bonus.job.domain.SysJob; import com.bonus.job.service.ISysJobService; import com.bonus.job.util.CronUtils; import com.bonus.job.util.ScheduleUtils; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; /** * 调度任务信息操作处理 - * + * * @author bonus */ @RestController @@ -165,7 +158,7 @@ public class SysJobController extends BaseController * 定时任务立即执行一次 */ @RequiresPermissions("monitor:job:changeStatus") - @PostMapping("/run") + @PostMapping("/run") @SysLog(title = "定时任务", businessType = OperaType.UPDATE,logType = 0,module = "系统监控->定时任务",details = "定时任务立即执行一次") public AjaxResult run(@RequestBody SysJob job) throws SchedulerException { diff --git a/bonus-modules/bonus-job/src/main/java/com/bonus/job/service/SysJobServiceImpl.java b/bonus-modules/bonus-job/src/main/java/com/bonus/job/service/SysJobServiceImpl.java index 157f9d0..9ce6adc 100644 --- a/bonus-modules/bonus-job/src/main/java/com/bonus/job/service/SysJobServiceImpl.java +++ b/bonus-modules/bonus-job/src/main/java/com/bonus/job/service/SysJobServiceImpl.java @@ -1,7 +1,11 @@ package com.bonus.job.service; -import java.util.List; -import javax.annotation.PostConstruct; +import com.bonus.common.core.constant.ScheduleConstants; +import com.bonus.common.core.exception.job.TaskException; +import com.bonus.job.domain.SysJob; +import com.bonus.job.mapper.SysJobMapper; +import com.bonus.job.util.CronUtils; +import com.bonus.job.util.ScheduleUtils; import org.quartz.JobDataMap; import org.quartz.JobKey; import org.quartz.Scheduler; @@ -9,16 +13,13 @@ import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.bonus.common.core.constant.ScheduleConstants; -import com.bonus.common.core.exception.job.TaskException; -import com.bonus.job.domain.SysJob; -import com.bonus.job.mapper.SysJobMapper; -import com.bonus.job.util.CronUtils; -import com.bonus.job.util.ScheduleUtils; + +import javax.annotation.PostConstruct; +import java.util.List; /** * 定时任务调度信息 服务层 - * + * * @author bonus */ @Service @@ -46,7 +47,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 获取quartz调度器的计划任务列表 - * + * * @param job 调度信息 * @return */ @@ -58,7 +59,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 通过调度任务ID查询调度信息 - * + * * @param jobId 调度任务ID * @return 调度任务对象信息 */ @@ -70,7 +71,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 暂停任务 - * + * * @param job 调度信息 */ @Override @@ -90,7 +91,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 恢复任务 - * + * * @param job 调度信息 */ @Override @@ -110,7 +111,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 删除任务后,所对应的trigger也将被删除 - * + * * @param job 调度信息 */ @Override @@ -129,7 +130,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 批量删除调度信息 - * + * * @param jobIds 需要删除的任务ID * @return 结果 */ @@ -146,7 +147,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 任务调度状态修改 - * + * * @param job 调度信息 */ @Override @@ -168,7 +169,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 立即运行任务 - * + * * @param job 调度信息 */ @Override @@ -193,7 +194,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 新增任务 - * + * * @param job 调度信息 调度信息 */ @Override @@ -211,7 +212,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 更新任务的时间表达式 - * + * * @param job 调度信息 */ @Override @@ -229,7 +230,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 更新任务 - * + * * @param job 任务对象 * @param jobGroup 任务组名 */ @@ -248,7 +249,7 @@ public class SysJobServiceImpl implements ISysJobService /** * 校验cron表达式是否有效 - * + * * @param cronExpression 表达式 * @return 结果 */ @@ -257,4 +258,4 @@ public class SysJobServiceImpl implements ISysJobService { return CronUtils.isValid(cronExpression); } -} \ No newline at end of file +}