diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/attendance/FaceUtil.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/attendance/FaceUtil.java new file mode 100644 index 00000000..8dfeb0fa --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/attendance/FaceUtil.java @@ -0,0 +1,89 @@ +package org.dromara.common.utils.attendance; + + +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class FaceUtil { + + private static final String FACE_URL = "http://192.168.110.5:1224"; + + /** + * 创建人脸记录 post + * @param name 姓名 + * @param card 身份证 + * @param path 人脸图片HTTP地址(需可公开访问) + */ + public static void createFaceRecord(String name, String card, String path) { + String url = FACE_URL+"/api/faces"; + + HashMap param = new HashMap<>() {{ + put("name", name); + put("card", card); + put("path", path); + }}; + HttpUtil.post(url, param); + } + + + /** + * 人脸检测 + * @param path 图片HTTP地址(需可公开访问) + */ + public static Map faceDetect(String path) { + String url = FACE_URL+"/api/faces/detect"; + + HashMap param = new HashMap<>() {{ + put("path", path); + put("similarity_threshold", 0.8); + }}; + //转成json + + String post = HttpUtil.post(url, param); + Map map = new HashMap<>(); + // 遍历检测到的人脸数据 + try { + // 解析返回的JSON数据 + JSONObject response = JSON.parseObject(post); + JSONObject data = response.getJSONObject("data"); + JSONArray detectedFaces = data.getJSONArray("detected_faces"); + + for (int i = 0; i < detectedFaces.size(); i++) { + JSONObject face = detectedFaces.getJSONObject(i); + JSONObject matchInfo = face.getJSONObject("match_info"); + + // 检查相似度是否大于等于阈值 + double similarity = matchInfo.getDoubleValue("similarity"); + double threshold = matchInfo.getDoubleValue("threshold"); + + if (similarity >= threshold) { + // 提取用户信息 + JSONObject userInfo = face.getJSONObject("user_info"); + String name = userInfo.getString("name"); + String idCard = userInfo.getString("id_card"); + map.put(idCard, name); + } + } + }catch (Exception e){ + log.error("人脸检测失败",e); + } + return map; + } + + + public static void main(String[] args) { +// Map map = faceDetect("http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/10/12/9688ce2474ad47e7bf59c641848cdf8f.jpg"); +// System.out.println(map); +// createFaceRecord("石志强","513022111145632652","http://xny.yj-3d.com:9000/xinnengyuan-dev/2025/10/12/9688ce2474ad47e7bf59c641848cdf8f.jpg"); + } + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/BusAttendanceDeviceController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/BusAttendanceDeviceController.java index 3fb101df..d7c45f25 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/BusAttendanceDeviceController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/BusAttendanceDeviceController.java @@ -69,6 +69,7 @@ public class BusAttendanceDeviceController extends BaseController { req.setProjectId(one.getProjectId()); req.setUserId(userId); req.setPunchTime(localDateTime); + req.setSource("1"); //打印req log.info("请求参数:{}", req); //base64转MultipartFile diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/BusAttendance.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/BusAttendance.java index c91b7679..aef885a8 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/BusAttendance.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/BusAttendance.java @@ -113,4 +113,9 @@ public class BusAttendance extends BaseEntity { * 代打卡人员Id */ private Long replaceId; + + /** + * 来源 + */ + private String source; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/BusAttendancePunchCardByFaceReq.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/BusAttendancePunchCardByFaceReq.java index de4e976e..3f295f09 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/BusAttendancePunchCardByFaceReq.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/domain/dto/attendance/BusAttendancePunchCardByFaceReq.java @@ -47,4 +47,10 @@ public class BusAttendancePunchCardByFaceReq implements Serializable { */ private LocalDateTime punchTime; + + /** + * 来源 (0-app,1-考勤机 ) + */ + private String source; + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java index 9df87d92..4d4e1156 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java @@ -391,63 +391,105 @@ public class BusAttendanceServiceImpl extends ServiceImpl outAttendances = attendances.stream().filter(attendance -> BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList(); - if (clockTypeByTime == 1 && CollectionUtils.isEmpty(inAttendances)) { - BusAttendance attendance = new BusAttendance(); - // 上班打卡 - attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue()); - //打卡时间 - attendance.setRuleTime(busAttendanceRuleVo.getClockInTime()); - // 判断是否为迟到 - if (isLate(now, busAttendanceRuleVo)) { - attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue()); - attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime())); - } else { - attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue()); - } - //只要请假,直接归为请假 - LocalDateTime localDateTime = localDate.atTime(busAttendanceRuleVo.getClockInTime()); - if (leaveService.isLeave(localDateTime, userId)) { - attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue()); - } - - // 填充信息 - attendance.setUserId(userId); - attendance.setProjectId(req.getProjectId()); - attendance.setClockDate(localDate); - attendance.setClockTime(now); - attendance.setUserName(constructionUser.getUserName()); - attendance.setReplaceId(replaceId); - // 记录打卡坐标 - attendance.setLat(req.getLat()); - attendance.setLng(req.getLng()); - try { - attendance.setClockLocation(JSTUtil.getLocationName(req.getLat(), req.getLng())); - } catch (Exception e) { - log.error("获取打卡位置失败", e); - } - - // 上传人脸照 - SysOssVo upload = ossService.upload(file); - attendance.setFacePic(upload.getOssId().toString()); - Long finalUserId = userId; - CompletableFuture.runAsync(() -> { - try { - chatServerHandler.sendSystemMessageToUser(finalUserId, "打卡成功", "1"); - } catch (Exception e) { - log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", finalUserId, "打卡成功", e); + if (clockTypeByTime == 1) { + if(CollectionUtils.isEmpty(inAttendances)){ + BusAttendance attendance = new BusAttendance(); + // 上班打卡 + attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue()); + //打卡时间 + attendance.setRuleTime(busAttendanceRuleVo.getClockInTime()); + // 判断是否为迟到 + if (isLate(now, busAttendanceRuleVo)) { + attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue()); + attendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime())); + } else { + attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue()); } - }); - //计算工资 - attendance.setSalary(computeSalary(constructionUser, inAttendances)); - return this.save(attendance); + //只要请假,直接归为请假 + LocalDateTime localDateTime = localDate.atTime(busAttendanceRuleVo.getClockInTime()); + if (leaveService.isLeave(localDateTime, userId)) { + attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue()); + } + + // 填充信息 + attendance.setUserId(userId); + attendance.setProjectId(req.getProjectId()); + attendance.setClockDate(localDate); + attendance.setClockTime(now); + attendance.setUserName(constructionUser.getUserName()); + attendance.setReplaceId(replaceId); + if(req.getSource() != null){ + attendance.setSource(req.getSource()); + } + // 记录打卡坐标 + attendance.setLat(req.getLat()); + attendance.setLng(req.getLng()); + try { + attendance.setClockLocation(JSTUtil.getLocationName(req.getLat(), req.getLng())); + } catch (Exception e) { + log.error("获取打卡位置失败", e); + } + + // 上传人脸照 + SysOssVo upload = ossService.upload(file); + attendance.setFacePic(upload.getOssId().toString()); + Long finalUserId = userId; + CompletableFuture.runAsync(() -> { + try { + chatServerHandler.sendSystemMessageToUser(finalUserId, "打卡成功", "1"); + } catch (Exception e) { + log.error("异步发送系统消息失败,用户ID: {}, 消息: {}", finalUserId, "打卡成功", e); + } + }); + //计算工资 + attendance.setSalary(computeSalary(constructionUser, inAttendances)); + return this.save(attendance); + } + //考勤机打卡会有历史记录,需要更新状态 + if(CollectionUtil.isNotEmpty(outAttendances) && "1".equals(req.getSource())){ + BusAttendance busAttendance = outAttendances.getFirst(); + String oldStatus = busAttendance.getClockStatus(); + //更新打卡时间 + busAttendance.setClockTime(now); + // 判断是否为迟到 + if (isLate(now, busAttendanceRuleVo)) { + busAttendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue()); + busAttendance.setMinuteCount(getMinutesDifference(now, busAttendanceRuleVo.getClockInTime())); + } else { + busAttendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue()); + } + //只要请假,直接归为请假 + LocalDateTime localDateTime = localDate.atTime(busAttendanceRuleVo.getClockInTime()); + if (leaveService.isLeave(localDateTime, userId)) { + busAttendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue()); + } + + busAttendance.setSource(req.getSource()); + //如果是缺卡需要上传人脸 + if(oldStatus.equals(BusAttendanceClockStatusEnum.UNCLOCK.getValue())){ + SysOssVo upload = ossService.upload(file); + busAttendance.setFacePic(upload.getOssId().toString()); + } + updateById(busAttendance); + } + } else if (clockTypeByTime == 2 || CollectionUtils.isNotEmpty(inAttendances)) { if (CollectionUtil.isNotEmpty(outAttendances)) { BusAttendance busAttendance = outAttendances.getFirst(); - if (busAttendance.getClockStatus().equals(BusAttendanceClockStatusEnum.UNCLOCK.getValue())) { - throw new ServiceException("下班缺卡记录已生成,不能更新"); + if("1".equals(req.getSource())){ + busAttendance.setSource(req.getSource()); + if(busAttendance.getClockStatus().equals(BusAttendanceClockStatusEnum.UNCLOCK.getValue())){ + SysOssVo upload = ossService.upload(file); + busAttendance.setFacePic(upload.getOssId().toString()); + } + }else { + if (busAttendance.getClockStatus().equals(BusAttendanceClockStatusEnum.UNCLOCK.getValue())) { + throw new ServiceException("下班缺卡记录已生成,不能更新"); + } } + //更新打卡时间 busAttendance.setClockTime(now); // 判断是否为早退 @@ -488,6 +530,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl> dateAttendanceMap = attendanceList.stream() .filter(a -> validStatusList.contains(a.getClockStatus())) - .collect(Collectors.groupingBy(BusAttendanceVo::getClockDate)); + .collect(Collectors.groupingBy(BusAttendanceVo::getClockDate, + LinkedHashMap::new, + Collectors.toList())); List workList = new ArrayList<>(); for (Map.Entry> entry : dateAttendanceMap.entrySet()) { LocalDate key = entry.getKey();