From 535262d721b71840e5408103f23343781b74f9d5 Mon Sep 17 00:00:00 2001 From: lg Date: Mon, 20 Oct 2025 16:32:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/dromara/xzd/enums/ZxdEnum.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/xzd/enums/ZxdEnum.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/xzd/enums/ZxdEnum.java index 31161b73..7158a531 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/xzd/enums/ZxdEnum.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/xzd/enums/ZxdEnum.java @@ -9,6 +9,8 @@ public enum ZxdEnum { HETONGLEIXING("合同外清单","1"), HETONGLEIXINGNEI("合同内清单","2"), PURCHASE_PREFIX("采购合同信息前缀","CCTEG(CWZ)"), + SFWJSD_S("决算单","1"), + SFWJSD_TZ("决算单调整","2"), FENBAOHETONG_PREFIX("分包合同信息前缀","CCTEG(CSG)"); From 99f002655201e846664ee57c05ca35f3ac1acea3 Mon Sep 17 00:00:00 2001 From: dfdg <2710245601@qq.com> Date: Mon, 20 Oct 2025 17:12:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=A4=A7=E5=B1=8F=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=AE=A1=E5=88=92=E5=AE=B9=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/dromara/project/service/impl/BusProjectServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectServiceImpl.java index 46f3c5bd..563f5eb4 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectServiceImpl.java @@ -1418,6 +1418,7 @@ public class BusProjectServiceImpl extends ServiceImpl Date: Mon, 20 Oct 2025 19:24:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BA=BA=E8=84=B8=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/utils/attendance/FaceUtil.java | 89 +++++++++++ .../BusAttendanceDeviceController.java | 1 + .../dromara/project/domain/BusAttendance.java | 5 + .../BusAttendancePunchCardByFaceReq.java | 6 + .../impl/BusAttendanceServiceImpl.java | 149 ++++++++++++------ 5 files changed, 199 insertions(+), 51 deletions(-) create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/common/utils/attendance/FaceUtil.java 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();