From 71d85458a4848b31c3b565fec5b24e814a4bc66d Mon Sep 17 00:00:00 2001 From: "lj7788@126.com" Date: Thu, 4 Sep 2025 17:46:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E8=80=83=E5=8B=A4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yanzhu-bigscreen/src/views/bimManage.vue | 2 +- .../core/utils/ImageSimilarityUtils.java | 55 +++++++++++++++++-- .../domain/ProMobileAttendanceData.java | 10 ++++ .../mapper/manage/AttendanceUbiDataMapper.xml | 4 +- yanzhu-modules/yanzhu-manage/pom.xml | 1 - .../ProMobileAttendanceConfigController.java | 27 ++++++--- .../service/IAttendanceUbiDataService.java | 6 ++ .../impl/AttendanceUbiDataServiceImpl.java | 48 ++++++++++++++++ yanzhu-ui-app/miniprogram/api/project.js | 11 ++++ .../mobile_attendance/attendance/add/index.js | 15 ++++- .../attendance/add/index.wxml | 2 +- .../attendance/list/index.js | 1 - .../attendance/list/index.wxml | 5 -- .../src/views/bim/bimSetting/Viewpoint.vue | 2 +- 14 files changed, 161 insertions(+), 28 deletions(-) diff --git a/yanzhu-bigscreen/src/views/bimManage.vue b/yanzhu-bigscreen/src/views/bimManage.vue index 1ad4c2a7..abdbb725 100644 --- a/yanzhu-bigscreen/src/views/bimManage.vue +++ b/yanzhu-bigscreen/src/views/bimManage.vue @@ -849,7 +849,7 @@ export default { this.$message.error("暂无模型,请先关联模型"); } else { bimTools.addModelList(window.bimMgrApi, this.bimCfg, this.models, (hideParts) => { - console.log(":--->", hideParts); debugger + console.log(":--->", hideParts); this.loadDevicePosition(); setTimeout(() => { bimTools.setDefaultViewPoint(window.bimMgrApi, this.bimCfg, this.viewPoint) diff --git a/yanzhu-common/yanzhu-common-core/src/main/java/com/yanzhu/common/core/utils/ImageSimilarityUtils.java b/yanzhu-common/yanzhu-common-core/src/main/java/com/yanzhu/common/core/utils/ImageSimilarityUtils.java index 4a4fe306..de5994c9 100644 --- a/yanzhu-common/yanzhu-common-core/src/main/java/com/yanzhu/common/core/utils/ImageSimilarityUtils.java +++ b/yanzhu-common/yanzhu-common-core/src/main/java/com/yanzhu/common/core/utils/ImageSimilarityUtils.java @@ -34,15 +34,45 @@ public class ImageSimilarityUtils { // OpenCV人脸检测器 private static CascadeClassifier faceDetector; + // OpenCV是否成功加载的标志 + public static boolean openCVLoaded = false; + static { try { - // 加载OpenCV库(使用openpnp的OpenCV依赖) + // 尝试加载OpenCV库(使用openpnp的OpenCV依赖) + System.out.println("开始加载OpenCV库..."); nu.pattern.OpenCV.loadLocally(); - // 初始化人脸检测器(使用相对路径) - String cascadePath = ImageSimilarityUtils.class.getClassLoader().getResource("opencv/haarcascade_frontalface_default.xml").getPath(); - faceDetector = new CascadeClassifier(cascadePath); + openCVLoaded = true; + System.out.println("OpenCV库加载成功"); + + try { + // 初始化人脸检测器(使用相对路径) + String cascadePath = ImageSimilarityUtils.class.getClassLoader().getResource("opencv/haarcascade_frontalface_default.xml").getPath(); + System.out.println("人脸检测器模型路径: " + cascadePath); + faceDetector = new CascadeClassifier(cascadePath); + + if (faceDetector.empty()) { + System.err.println("人脸检测器初始化失败,将使用基础相似度计算方法"); + faceDetector = null; + } else { + System.out.println("人脸检测器初始化成功"); + } + } catch (Exception e) { + System.err.println("人脸检测器初始化失败: " + e.getMessage()); + faceDetector = null; + } + } catch (UnsatisfiedLinkError e) { + System.err.println("OpenCV库加载失败( UnsatisfiedLinkError),将使用基础相似度计算方法: " + e.getMessage()); + openCVLoaded = false; + faceDetector = null; } catch (Exception e) { System.err.println("OpenCV加载失败,将使用基础相似度计算方法: " + e.getMessage()); + openCVLoaded = false; + faceDetector = null; + } catch (Throwable t) { + System.err.println("OpenCV加载失败(未知错误),将使用基础相似度计算方法: " + t.getMessage()); + t.printStackTrace(); + openCVLoaded = false; faceDetector = null; } } @@ -190,6 +220,7 @@ public class ImageSimilarityUtils { private static double calculateImageSimilarity(BufferedImage image1, BufferedImage image2) { // 如果OpenCV初始化失败,直接使用基础方法 if (faceDetector == null) { + System.out.println("使用基础相似度计算方法"); return calculateImageSimilarityBasic(image1, image2); } @@ -272,6 +303,12 @@ public class ImageSimilarityUtils { } catch (Exception e) { e.printStackTrace(); // 发生异常时使用原始方法 + System.out.println("OpenCV处理出现异常,使用基础相似度计算方法: " + e.getMessage()); + return calculateImageSimilarityBasic(image1, image2); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + // OpenCV链接错误时使用原始方法 + System.out.println("OpenCV链接错误,使用基础相似度计算方法: " + e.getMessage()); return calculateImageSimilarityBasic(image1, image2); } } @@ -306,6 +343,11 @@ public class ImageSimilarityUtils { * @return 人脸区域矩形,如果未检测到人脸则返回null */ private static Rect detectFace(BufferedImage image) { + // 如果OpenCV未正确加载,直接返回null + if (faceDetector == null || !openCVLoaded) { + return null; + } + try { // 将BufferedImage转换为OpenCV Mat Mat mat = bufferedImageToMat(image); @@ -335,9 +377,12 @@ public class ImageSimilarityUtils { return largestFace; } + return null; + } catch (UnsatisfiedLinkError e) { + System.err.println("OpenCV链接错误,无法检测人脸: " + e.getMessage()); return null; } catch (Exception e) { - e.printStackTrace(); + System.err.println("人脸检测出现异常: " + e.getMessage()); return null; } } diff --git a/yanzhu-common/yanzhu-common-mapper/src/main/java/com/yanzhu/manage/domain/ProMobileAttendanceData.java b/yanzhu-common/yanzhu-common-mapper/src/main/java/com/yanzhu/manage/domain/ProMobileAttendanceData.java index 72b3cc94..c7e7184a 100644 --- a/yanzhu-common/yanzhu-common-mapper/src/main/java/com/yanzhu/manage/domain/ProMobileAttendanceData.java +++ b/yanzhu-common/yanzhu-common-mapper/src/main/java/com/yanzhu/manage/domain/ProMobileAttendanceData.java @@ -51,6 +51,16 @@ public class ProMobileAttendanceData extends BaseEntity @Excel(name = "考勤时间", width = 30, dateFormat = "yyyy-MM-dd") private Date attDate; + private String basePath; + + public String getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = basePath; + } + /** 考勤照片 */ @Excel(name = "考勤照片") private String attImg; diff --git a/yanzhu-common/yanzhu-common-mapper/src/main/resources/mapper/manage/AttendanceUbiDataMapper.xml b/yanzhu-common/yanzhu-common-mapper/src/main/resources/mapper/manage/AttendanceUbiDataMapper.xml index b05e8d9a..74a74936 100644 --- a/yanzhu-common/yanzhu-common-mapper/src/main/resources/mapper/manage/AttendanceUbiDataMapper.xml +++ b/yanzhu-common/yanzhu-common-mapper/src/main/resources/mapper/manage/AttendanceUbiDataMapper.xml @@ -80,7 +80,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and userId = #{userId} and admitGuid = #{admitGuid} and userName like concat('%', #{userName}, '%') - and date(inTime) = date(#{inTime}) + + and (date(inTime) = date(#{inTime}) or date(outTime) = date(#{inTime})) + and date(outTime) = date(#{outTime}) and deviceNo = #{deviceNo} diff --git a/yanzhu-modules/yanzhu-manage/pom.xml b/yanzhu-modules/yanzhu-manage/pom.xml index 556fbaac..fbdb28ca 100644 --- a/yanzhu-modules/yanzhu-manage/pom.xml +++ b/yanzhu-modules/yanzhu-manage/pom.xml @@ -195,7 +195,6 @@ org.openpnp opencv - 4.5.1-2 diff --git a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/controller/remoteAttendance/ProMobileAttendanceConfigController.java b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/controller/remoteAttendance/ProMobileAttendanceConfigController.java index 5736545a..9bd29b5d 100644 --- a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/controller/remoteAttendance/ProMobileAttendanceConfigController.java +++ b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/controller/remoteAttendance/ProMobileAttendanceConfigController.java @@ -13,6 +13,8 @@ import com.yanzhu.common.log.annotation.Log; import com.yanzhu.common.log.enums.BusinessType; import com.yanzhu.common.security.annotation.RequiresPermissions; import com.yanzhu.manage.domain.ProMobileAttendanceData; +import com.yanzhu.manage.service.IAttendanceUbiDataService; +import com.yanzhu.manage.service.IProMobileAttendanceDataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -39,6 +41,10 @@ public class ProMobileAttendanceConfigController extends BaseController @Autowired private IProMobileAttendanceConfigService proMobileAttendanceConfigService; + @Autowired + private IAttendanceUbiDataService attendanceUbiDataService; + @Autowired + private IProMobileAttendanceDataService proMobileAttendanceDataService; /** * 查询移动端考勤配置列表 */ @@ -123,21 +129,24 @@ public class ProMobileAttendanceConfigController extends BaseController @PostMapping("/attendance") public AjaxResult attendance(@RequestBody ProMobileAttendanceData attData){ //根据用户上传的照片与用户信息的照片计算相似度 - String userPicture=attData.getUserPicture(); - String attImg=attData.getAttImg(); + String userPicture =attData.getBasePath()+ attData.getUserPicture(); + String attImg=attData.getBasePath()+attData.getAttImg(); // 使用专门为人脸识别考勤优化的相似度计算 double similarity = ImageSimilarityUtils.calculateFaceSimilarity(userPicture, attImg); - if (similarity>=0.8) { - // 相似度达标,增加考勤数据 - // TODO: 增加考勤数据逻辑 - // TODO: 增加考勤历史记录逻辑 - - return AjaxResult.success("考勤成功,人脸匹配"); + // 根据是否使用OpenCV调整阈值 + // OpenCV场景下阈值为0.8,基础算法场景下阈值为0.75 + double threshold = ImageSimilarityUtils.openCVLoaded ? 0.8 : 0.75; + + if (similarity >= threshold) { + // 相似度达标,允许考勤 + int cnt=attendanceUbiDataService.addMobiileAttendanceData(attData); + cnt+=proMobileAttendanceDataService.insertProMobileAttendanceData(attData); + return AjaxResult.success("考勤成功,人脸匹配 (相似度: " + String.format("%.2f", similarity) + ")"); } else { // 相似度不达标,拒绝考勤 - return AjaxResult.error("考勤失败,人脸不匹配"); + return AjaxResult.error("考勤失败,人脸不匹配 (相似度: " + String.format("%.2f", similarity) + ", 阈值: " + threshold + ")"); } } } \ No newline at end of file diff --git a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/IAttendanceUbiDataService.java b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/IAttendanceUbiDataService.java index 681c9684..ae191859 100644 --- a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/IAttendanceUbiDataService.java +++ b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/IAttendanceUbiDataService.java @@ -7,6 +7,7 @@ import java.util.Map; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.yanzhu.manage.domain.AttendanceUbiData; +import com.yanzhu.manage.domain.ProMobileAttendanceData; import com.yanzhu.manage.domain.ProProjectInfoSubdeptsUsers; /** @@ -116,4 +117,9 @@ public interface IAttendanceUbiDataService * @return */ List getRealAttendance(Long prjId); + + /** + * 增加移动端考勤数据 + */ + int addMobiileAttendanceData(ProMobileAttendanceData attData); } diff --git a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/impl/AttendanceUbiDataServiceImpl.java b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/impl/AttendanceUbiDataServiceImpl.java index 021c7101..44d994db 100644 --- a/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/impl/AttendanceUbiDataServiceImpl.java +++ b/yanzhu-modules/yanzhu-manage/src/main/java/com/yanzhu/manage/service/impl/AttendanceUbiDataServiceImpl.java @@ -12,10 +12,12 @@ import com.yanzhu.common.core.utils.DateUtils; import com.yanzhu.common.core.utils.StringUtils; import com.yanzhu.common.security.utils.SecurityUtils; import com.yanzhu.manage.domain.AttendanceUbiData; +import com.yanzhu.manage.domain.ProMobileAttendanceData; import com.yanzhu.manage.domain.ProProjectInfoSubdeptsUsers; import com.yanzhu.manage.mapper.AttendanceUbiDataMapper; import com.yanzhu.manage.mapper.ProProjectInfoSubdeptsUsersMapper; import com.yanzhu.manage.service.IAttendanceUbiDataService; +import com.yanzhu.manage.service.IProProjectInfoSubdeptsUsersService; import com.yanzhu.system.mapper.SysDictDataMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -42,6 +44,8 @@ public class AttendanceUbiDataServiceImpl implements IAttendanceUbiDataService @Autowired private ProProjectInfoSubdeptsUsersMapper proProjectInfoSubdeptsUsersMapper; + @Autowired + private IProProjectInfoSubdeptsUsersService proProjectInfoSubdeptsUsersService; /** * 查询考勤管理 * @@ -309,5 +313,49 @@ public class AttendanceUbiDataServiceImpl implements IAttendanceUbiDataService return attendanceUbiDataMapper.getRealAttendance(prjId); } + @Override + public int addMobiileAttendanceData(ProMobileAttendanceData attData) { + AttendanceUbiData attendance = new AttendanceUbiData(); + ProProjectInfoSubdeptsUsers user=proProjectInfoSubdeptsUsersService.findProSubDeptsUserInfo(attData.getProjectId(),attData.getUserId()); + if(user==null){ + return 0; + } + attendance.setProjectId(attData.getProjectId()); + attendance.setUserId(user.getUserId()); + attendance.setIsDel(0L); + attendance.setComId(user.getComId()); + attendance.setComName(user.getComName()); + attendance.setProjectName(user.getProjectName()); + attendance.setSubDeptId(user.getSubDeptId()); + attendance.setSubDeptName(user.getSubDeptName()); + attendance.setUserName(user.getUserName()); + attendance.setSubDeptGroup(user.getSubDeptGroup()); + attendance.setSubDeptName(user.getSubDeptName()); + attendance.setCraftType(user.getCraftType()); + attendance.setCraftPost(user.getCraftPost()); + attendance.setDeviceNo("mobile"); + if("in".equals(attData.getInOut())){ + attendance.setInTime(attData.getAttDate()); + attendance.setInPhoto(attData.getAttImg()); + }else{ + attendance.setOutTime(attData.getAttDate()); + attendance.setOutPhoto(attData.getAttImg()); + } + attendance.setCreateBy(SecurityContextHolder.getUserName()); + attendance.setCreateTime(DateUtils.getNowDate()); + + AttendanceUbiData where = new AttendanceUbiData(); + where.setProjectId(attData.getProjectId()); + where.setUserId(attData.getUserId()); + where.setInTime(attData.getAttDate()); + List list=queryAttendaceInfo(where); + if(list.size()>0){ + attendance.setId(list.get(0).getId()); + return updateAttendanceUbiData(attendance); + }else{ + return insertAttendanceUbiData(attendance); + } + } + } diff --git a/yanzhu-ui-app/miniprogram/api/project.js b/yanzhu-ui-app/miniprogram/api/project.js index 35961ae3..107f5ffb 100644 --- a/yanzhu-ui-app/miniprogram/api/project.js +++ b/yanzhu-ui-app/miniprogram/api/project.js @@ -482,3 +482,14 @@ export function getMobileAttendanceConfigById(id) { method: "get", }); } + +/** + * 移动端考勤 + */ +export function mobileAttendance(data) { + return request({ + url: "/manage/mobileAttendConfig/attendance", + method: "post", + data: data, + }); +} diff --git a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.js b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.js index 20928fc6..37580e07 100644 --- a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.js +++ b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.js @@ -2,9 +2,9 @@ import fmt from "../../../utils/date.js"; import { getToken, getUserInfo } from "../../../../utils/auth.js"; import { securityFileUpload } from "../../../../utils/request.js"; // 导入文件上传工具 const app = getApp(); -import { getMobileAttendanceConfigById } from "../../../../api/project.js"; +import { mobileAttendance } from "../../../../api/project.js"; import { calculateDistance } from "../../../../utils/location.js"; // 导入计算距离的工具函数 - +import config from "../../../../config.js"; Page({ /** * 页面的初始数据 @@ -224,7 +224,6 @@ Page({ }); }, doSave() { - debugger; if (!this.data.faceImage) { app.toast("未获取到照片!"); return; @@ -374,6 +373,16 @@ Page({ attDate: cfgData.attDate, attImg: this.data.faceImageUrl, cfgInfo: cfgData, + basePath: config.baseImgUrl, }; + console.log("考勤数据", postData); + mobileAttendance(postData).then((res) => { + if (res.code == 200) { + app.toast("考勤成功"); + this.returnToPage(); + } else { + app.toast("考勤失败: " + res.msg); + } + }); }, }); diff --git a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.wxml b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.wxml index a6e058da..12d4030e 100644 --- a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.wxml +++ b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/add/index.wxml @@ -53,7 +53,7 @@ {{cfgData.attAddress}} - diff --git a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.js b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.js index a307dff4..3665c4f8 100644 --- a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.js +++ b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.js @@ -49,7 +49,6 @@ Page({ * 获取用户当前位置 */ getUserLocation() { - debugger; wx.getLocation({ type: "gcj02", // 使用国测局坐标 success: (res) => { diff --git a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.wxml b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.wxml index d5cd6c3e..aaec85c7 100644 --- a/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.wxml +++ b/yanzhu-ui-app/miniprogram/pageage/mobile_attendance/attendance/list/index.wxml @@ -72,11 +72,6 @@ - - - 当前位置: - {{item.attAddress}} - diff --git a/yanzhu-ui-vue3/src/views/bim/bimSetting/Viewpoint.vue b/yanzhu-ui-vue3/src/views/bim/bimSetting/Viewpoint.vue index c627392f..34cd0ccf 100644 --- a/yanzhu-ui-vue3/src/views/bim/bimSetting/Viewpoint.vue +++ b/yanzhu-ui-vue3/src/views/bim/bimSetting/Viewpoint.vue @@ -276,7 +276,7 @@ export default { } }); }, - DelViewpoint(item, index) {debugger + DelViewpoint(item, index) { let that = this; ElMessageBox.confirm(`确定要删除漫游 “${item.name}” 吗?`, "提示", { confirmButtonText: "确定",