Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
2025-10-20 19:26:18 +08:00
7 changed files with 202 additions and 51 deletions

View File

@ -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<String, Object> param = new HashMap<>() {{
put("name", name);
put("card", card);
put("path", path);
}};
HttpUtil.post(url, param);
}
/**
* 人脸检测
* @param path 图片HTTP地址需可公开访问
*/
public static Map<String, String> faceDetect(String path) {
String url = FACE_URL+"/api/faces/detect";
HashMap<String, Object> param = new HashMap<>() {{
put("path", path);
put("similarity_threshold", 0.8);
}};
//转成json
String post = HttpUtil.post(url, param);
Map<String, String> 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<String, String> 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");
}
}

View File

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

View File

@ -113,4 +113,9 @@ public class BusAttendance extends BaseEntity {
* 代打卡人员Id
*/
private Long replaceId;
/**
* 来源
*/
private String source;
}

View File

@ -47,4 +47,10 @@ public class BusAttendancePunchCardByFaceReq implements Serializable {
*/
private LocalDateTime punchTime;
/**
* 来源 0-app,1-考勤机
*/
private String source;
}

View File

@ -391,7 +391,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
List<BusAttendance> outAttendances = attendances.stream().filter(attendance ->
BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())).toList();
if (clockTypeByTime == 1 && CollectionUtils.isEmpty(inAttendances)) {
if (clockTypeByTime == 1) {
if(CollectionUtils.isEmpty(inAttendances)){
BusAttendance attendance = new BusAttendance();
// 上班打卡
attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
@ -417,6 +418,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
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());
@ -440,14 +444,52 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
//计算工资
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("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<BusAttendanceMapper, B
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());
@ -1109,7 +1154,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
// 过滤有效考勤记录并按日期分组
Map<LocalDate, List<BusAttendanceVo>> dateAttendanceMap = attendanceList.stream()
.filter(a -> validStatusList.contains(a.getClockStatus()))
.collect(Collectors.groupingBy(BusAttendanceVo::getClockDate));
.collect(Collectors.groupingBy(BusAttendanceVo::getClockDate,
LinkedHashMap::new,
Collectors.toList()));
List<AttendanceUserDataDetailVo> workList = new ArrayList<>();
for (Map.Entry<LocalDate, List<BusAttendanceVo>> entry : dateAttendanceMap.entrySet()) {
LocalDate key = entry.getKey();

View File

@ -1418,6 +1418,7 @@ public class BusProjectServiceImpl extends ServiceImpl<BusProjectMapper, BusProj
map2.put("lng", project.getLng());
map2.put("lat", project.getLat());
map2.put("position", project.getPosition());
map2.put("plan", project.getPlan());
map2.put("projectId", project.getId().toString());
map1.put(project.getProjectName(), map2);
//当满足条件时删除该元素

View File

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