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)); | |||||
} | |||||
} |
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; | |||||
} |
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); | |||||
} |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
<?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> |
<?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> |