@@ -0,0 +1,59 @@ | |||
package com.ruoyi.zhushi.controller; | |||
import com.ruoyi.common.core.controller.BaseController; | |||
import com.ruoyi.common.core.domain.R; | |||
import com.ruoyi.zhushi.entity.AppDTO; | |||
import com.ruoyi.zhushi.entity.DkCheckInRecord; | |||
import com.ruoyi.zhushi.service.DkAppService; | |||
import com.ruoyi.zhushi.service.DkAttendanceGroupService; | |||
import lombok.RequiredArgsConstructor; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.validation.annotation.Validated; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RestController; | |||
import java.util.List; | |||
/** | |||
* 打卡控制台 | |||
*/ | |||
@Validated | |||
@RequiredArgsConstructor | |||
@RestController | |||
@RequestMapping("/dk/app") | |||
@Slf4j | |||
public class DkAppController extends BaseController { | |||
@Autowired | |||
private DkAttendanceGroupService dkAttendanceGroupService; | |||
@Autowired | |||
private DkAppService dkAppService; | |||
@GetMapping("/queryAttendanceGroupBYUserId") | |||
public R<AppDTO> queryAttendanceGroupBYUserId(long userId) { | |||
return R.ok(dkAttendanceGroupService.queryAttendanceGroupBYUserId(userId)); | |||
} | |||
// 上班打卡 | |||
@GetMapping("/checkIn") | |||
public R<String> checkIn(AppDTO appDTO) { | |||
return R.ok(dkAppService.checkIn(appDTO)); | |||
} | |||
// 下班打卡 | |||
@GetMapping("/checkOut") | |||
public R<String> checkOut(AppDTO appDTO) { | |||
return R.ok(dkAppService.checkOut(appDTO)); | |||
} | |||
// 获取当日当前员工考勤状态 | |||
@GetMapping("/getCurrentDayRecord") | |||
public R<List<DkCheckInRecord>> getAttendanceStatus(AppDTO appDTO) { | |||
return R.ok(dkAppService.getCurrentDayRecord(appDTO)); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
package com.ruoyi.zhushi.entity; | |||
import com.ruoyi.common.core.domain.BaseEntity; | |||
import lombok.Data; | |||
import java.util.List; | |||
@Data | |||
public class AppDTO extends BaseEntity { | |||
// 用户id | |||
private long id; | |||
// 考勤组名称 | |||
private String attendanceGroupName; | |||
// 用户id | |||
private long userId; | |||
// 用户名 | |||
private String userName; | |||
// 打卡描述 | |||
private String description; | |||
// 工作日 | |||
private List<String> workDays; | |||
// private List<String> workDaysList; | |||
// 上班开始时间打卡 | |||
private String workStartTime; | |||
// 上班结束时间打卡 | |||
private String workEndTime; | |||
// 是否允许迟到 | |||
private Boolean allowLate; | |||
// 允许迟到范围 | |||
private Integer lateRange; | |||
// 是否允许早退 | |||
private Boolean allowEarly; | |||
// 允许早退范围 | |||
private Integer earlyRange; | |||
// 关联区域id | |||
private Long areaId; | |||
// 成员数据 | |||
private List<DkAttendanceGroupAndUser> members; | |||
// 经度 | |||
private double lng; | |||
// 纬度 | |||
private double lat; | |||
// 允许打卡半径 | |||
private int radius; | |||
// 是否迟到打卡 | |||
private String isCheckInLate; | |||
} |
@@ -0,0 +1,15 @@ | |||
package com.ruoyi.zhushi.service; | |||
import com.ruoyi.zhushi.entity.AppDTO; | |||
import com.ruoyi.zhushi.entity.DkCheckInRecord; | |||
import java.util.List; | |||
public interface DkAppService { | |||
String checkIn(AppDTO appDTO); | |||
String checkOut(AppDTO appDTO); | |||
List<DkCheckInRecord> getCurrentDayRecord(AppDTO appDTO); | |||
} |
@@ -0,0 +1,173 @@ | |||
package com.ruoyi.zhushi.service.impl; | |||
import com.ruoyi.zhushi.entity.AppDTO; | |||
import com.ruoyi.zhushi.entity.DkCheckInRecord; | |||
import com.ruoyi.zhushi.entity.DkCheckInRecordDTO; | |||
import com.ruoyi.zhushi.mapper.DkAttendanceGroupMapper; | |||
import com.ruoyi.zhushi.mapper.DkRecordMapper; | |||
import com.ruoyi.zhushi.service.DkAppService; | |||
import com.ruoyi.zhushi.util.GeoDistanceUtil; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import java.time.Duration; | |||
import java.time.LocalDateTime; | |||
import java.time.LocalTime; | |||
import java.time.format.DateTimeFormatter; | |||
import java.time.format.DateTimeParseException; | |||
import java.util.List; | |||
@Service | |||
public class DkAppServiceImpl implements DkAppService { | |||
@Autowired | |||
private DkAttendanceGroupMapper dkAttendanceGroupMapper; | |||
@Autowired | |||
private DkRecordMapper dkRecordMapper; | |||
@Override | |||
public String checkIn(AppDTO appDTO) { | |||
AppDTO appDTO1 = dkAttendanceGroupMapper.queryAttendanceGroupBYUserId(appDTO.getUserId()); | |||
// 打卡开始时间 | |||
String workStartTime = appDTO1.getWorkStartTime(); | |||
if(workStartTime == null){ | |||
return "系统设置错误"; | |||
} | |||
// 根据经纬度计算两点之间的距离 | |||
double distanceKm = GeoDistanceUtil.getDistanceKm(appDTO.getLat(), appDTO.getLng(), appDTO1.getLat(), appDTO1.getLng()); | |||
if (distanceKm > appDTO.getRadius()) { | |||
return "超出范围"; | |||
} | |||
// 解析给定的时分秒(格式:HH:mm:ss 或 HH:mm) | |||
boolean result = canSignIn(workStartTime); | |||
DkCheckInRecord dkCheckInRecord = new DkCheckInRecord(); | |||
if(!result){ | |||
boolean b = allowCheckIn(workStartTime, appDTO.getLateRange()); | |||
if(!b){ | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("2"); | |||
dkCheckInRecord.setCheckInType("startCheckIn"); | |||
dkRecordMapper.insert(dkCheckInRecord); | |||
return "迟到打卡"; | |||
} | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("1"); | |||
dkCheckInRecord.setCheckInType("startCheckIn"); | |||
dkRecordMapper.insert(dkCheckInRecord); | |||
return "打卡成功"; | |||
} | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("1"); | |||
dkCheckInRecord.setCheckInType("startCheckIn"); | |||
dkRecordMapper.insert(dkCheckInRecord); | |||
return "打卡成功"; | |||
} | |||
// 判断是否可以签到(给定时间是否在当前时间之前) | |||
public static boolean canSignIn(String timeStr) { | |||
// 解析给定的时分秒(格式:HH:mm:ss 或 HH:mm) | |||
DateTimeFormatter formatter = timeStr.contains(":") ? | |||
(timeStr.length() > 5 ? DateTimeFormatter.ofPattern("HH:mm:ss") : | |||
DateTimeFormatter.ofPattern("HH:mm")) : | |||
DateTimeFormatter.ofPattern("HHmmss"); | |||
LocalTime givenTime = LocalTime.parse(timeStr, formatter); | |||
LocalTime currentTime = LocalTime.now(); | |||
// 如果给定时间在当前时间之前,则可以签到 | |||
return givenTime.isBefore(currentTime); | |||
} | |||
public static boolean allowCheckIn(String timeStr, long maxDifferenceMinutes) { | |||
try { | |||
// 解析输入的时间字符串 | |||
DateTimeFormatter formatter = timeStr.contains(":") ? | |||
(timeStr.length() > 5 ? DateTimeFormatter.ofPattern("HH:mm:ss") : | |||
DateTimeFormatter.ofPattern("HH:mm")) : | |||
DateTimeFormatter.ofPattern("HHmmss"); | |||
LocalTime inputTime = LocalTime.parse(timeStr, formatter); | |||
LocalTime currentTime = LocalTime.now(); | |||
// 计算时间差(绝对值) | |||
Duration duration = Duration.between(inputTime, currentTime); | |||
long diffMinutes = Math.abs(duration.toMinutes()); | |||
// 判断是否超过最大允许时间差 | |||
return diffMinutes <= maxDifferenceMinutes; | |||
} catch (DateTimeParseException e) { | |||
System.err.println("时间格式解析错误: " + e.getMessage()); | |||
return false; // 格式错误时默认不允许打卡 | |||
} | |||
} | |||
@Override | |||
public String checkOut(AppDTO appDTO) { | |||
AppDTO appDTO1 = dkAttendanceGroupMapper.queryAttendanceGroupBYUserId(appDTO.getUserId()); | |||
// 根据经纬度计算两点之间的距离 | |||
double distanceKm = GeoDistanceUtil.getDistanceKm(appDTO.getLat(), appDTO.getLng(), appDTO1.getLat(), appDTO1.getLng()); | |||
if (distanceKm > appDTO.getRadius()){ | |||
return "超出范围"; | |||
} | |||
// 打卡开始时间 | |||
String workEndTime = appDTO1.getWorkEndTime(); | |||
if(workEndTime == null){ | |||
return "系统设置错误"; | |||
} | |||
// 解析给定的时分秒(格式:HH:mm:ss 或 HH:mm) | |||
boolean result = canSignIn(workEndTime); | |||
if(result){ | |||
if(appDTO1.getAllowEarly()){ | |||
boolean b = allowCheckIn(workEndTime, appDTO.getEarlyRange()); | |||
if(!b){ | |||
return "不允许超出提前打卡范围"; | |||
} | |||
// 签到成功 | |||
DkCheckInRecord dkCheckInRecord = new DkCheckInRecord(); | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("0"); | |||
dkCheckInRecord.setCheckInType("endCheckIn"); | |||
dkRecordMapper.insertOrUpdate(dkCheckInRecord); | |||
return "打卡成功"; | |||
} | |||
DkCheckInRecord dkCheckInRecord = new DkCheckInRecord(); | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("0"); | |||
dkCheckInRecord.setCheckInType("endCheckIn"); | |||
dkRecordMapper.insertOrUpdate(dkCheckInRecord); | |||
return "提前打卡"; | |||
}else{ | |||
DkCheckInRecord dkCheckInRecord = new DkCheckInRecord(); | |||
dkCheckInRecord.setSysUserId(appDTO.getUserId()); | |||
dkCheckInRecord.setSysUserName(appDTO.getUserName()); | |||
dkCheckInRecord.setCheckInTime(LocalDateTime.now()); | |||
dkCheckInRecord.setCheckInStatus("0"); | |||
dkCheckInRecord.setCheckInType("endCheckIn"); | |||
dkRecordMapper.insertOrUpdate(dkCheckInRecord); | |||
return "打卡成功"; | |||
} | |||
} | |||
@Override | |||
public List<DkCheckInRecord> getCurrentDayRecord(AppDTO appDTO) { | |||
List<DkCheckInRecord> currentDayRecords = dkRecordMapper.getCurrentDayRecord(appDTO.getUserId()); | |||
return currentDayRecords; | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
package com.ruoyi.zhushi.util; | |||
/** | |||
* 经纬度距离计算工具类 | |||
*/ | |||
public class GeoDistanceUtil { | |||
private static final double EARTH_RADIUS = 6371000; // 地球半径(米) | |||
/** | |||
* 计算两点间的距离(米) | |||
*/ | |||
public static double getDistance(double lat1, double lng1, double lat2, double lng2) { | |||
double dLat = Math.toRadians(lat2 - lat1); | |||
double dLng = Math.toRadians(lng2 - lng1); | |||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + | |||
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * | |||
Math.sin(dLng / 2) * Math.sin(dLng / 2); | |||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | |||
return EARTH_RADIUS * c; | |||
} | |||
/** | |||
* 计算两点间的距离(公里) | |||
*/ | |||
public static double getDistanceKm(double lat1, double lng1, double lat2, double lng2) { | |||
return getDistance(lat1, lng1, lat2, lng2) / 1000; | |||
} | |||
/** | |||
* 判断两点是否在指定范围内 | |||
* @param lat1 点1纬度 | |||
* @param lng1 点1经度 | |||
* @param lat2 点2纬度 | |||
* @param lng2 点2经度 | |||
* @param maxDistance 最大距离(米) | |||
* @return 是否在范围内 | |||
*/ | |||
public static boolean isInRange(double lat1, double lng1, double lat2, double lng2, double maxDistance) { | |||
double distance = getDistance(lat1, lng1, lat2, lng2); | |||
return distance <= maxDistance; | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<!DOCTYPE mapper | |||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" | |||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
<mapper namespace="com.ruoyi.zhushi.mapper.DkAttendanceGroupMapper"> | |||
<resultMap id="DkAttendanceGroupRusult" type="com.ruoyi.zhushi.entity.AppDTO"> | |||
<id property="id" column="id"/> | |||
<result property="attendanceGroupName" column="name"/> | |||
<result property="description" column="description"/> | |||
<result property="workStartTime" column="workStartTime"/> | |||
<result property="workEndTime" column="workEndTime"/> | |||
<result property="lng" column="lng"/> | |||
<result property="lat" column="lat"/> | |||
<result property="radius" column="radius"/> | |||
</resultMap> | |||
<select id="queryAttendanceGroupBYUserId" resultMap="DkAttendanceGroupRusult"> | |||
select DISTINCT t.`name`, t.description, | |||
t.work_start_time workStartTime, | |||
t.work_end_time workEndTime, | |||
t.allow_early allowEarly, | |||
t.early_range earlyRange, | |||
t.allow_late allowLate, | |||
t.late_range lateRange, | |||
c.lng lng, c.lat lat, c.radius | |||
from dk_check_in_attendance_team_and_user u | |||
LEFT JOIN dk_check_in_attendance_team t on t.id = u.attendance_team_id | |||
left JOIN dk_check_in_config c on c.id = t.area_id | |||
<where> | |||
t.work_start_time is not null | |||
<if test="userId != null and userId != ''"> | |||
AND u.user_id = #{userId} | |||
</if> | |||
</where> | |||
</select> | |||
</mapper> |
@@ -0,0 +1,38 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<!DOCTYPE mapper | |||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" | |||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||
<mapper namespace="com.ruoyi.zhushi.mapper.DkRecordMapper"> | |||
<resultMap id="DkRecordResult" type="com.ruoyi.zhushi.entity.DkCheckInRecord"> | |||
<id property="id" column="id"/> | |||
<result property="sysUserId" column="sys_user_id"/> | |||
<result property="sysUserName" column="sys_user_name"/> | |||
<result property="checkInTime" column="check_in_time"/> | |||
<result property="latitude" column="latitude"/> | |||
<result property="longitude" column="longitude"/> | |||
<result property="checkInStatus" column="check_in_status"/> | |||
<result property="checkInType" column="check_in_type"/> | |||
</resultMap> | |||
<select id="getCurrentDayRecord" resultMap="DkRecordResult"> | |||
select DISTINCT t.`name`, t.description, | |||
t.work_start_time workStartTime, | |||
t.work_end_time workEndTime, | |||
t.allow_early allowEarly, | |||
t.early_range earlyRange, | |||
t.allow_late allowLate, | |||
t.late_range lateRange, | |||
c.lng lng, c.lat lat, c.radius | |||
from dk_check_in_attendance_team_and_user u | |||
LEFT JOIN dk_check_in_attendance_team t on t.id = u.attendance_team_id | |||
left JOIN dk_check_in_config c on c.id = t.area_id | |||
<where> | |||
check_in_time >= CURDATE() AND create_time < CURDATE() + INTERVAL 1 DAY | |||
<if test="userId != null and userId != ''"> | |||
AND u.user_id = #{userId} | |||
</if> | |||
</where> | |||
</select> | |||
</mapper> |