| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- package com.kingdee.eas.custom.ats.handler;
- import com.alibaba.fastjson.JSONObject;
- import com.kingdee.bos.BOSException;
- import com.kingdee.bos.Context;
- import com.kingdee.bos.ctrl.swing.StringUtils;
- import com.kingdee.bos.metadata.entity.FilterInfo;
- import com.kingdee.eas.util.app.DbUtil;
- import com.kingdee.jdbc.rowset.IRowSet;
- import com.kingdee.shr.base.syssetting.context.SHRContext;
- import com.kingdee.shr.base.syssetting.exception.SHRWebException;
- import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
- import com.kingdee.shr.base.syssetting.json.GridDataEntity;
- import com.kingdee.shr.base.syssetting.web.handler.ListHandler;
- import com.kingdee.util.DateTimeUtils;
- import org.apache.log4j.Logger;
- import org.springframework.ui.ModelMap;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.sql.SQLException;
- import java.util.*;
- /**
- * 类名称: AttendancePunchStatListHandler
- * 功能描述: 考勤打卡统计列表处理器(结合排班:有排班未打卡显示未打卡,未排班显示空)
- * 创建日期: 2026-05-29
- * 作 者: 青梧
- * 版 本: 1.0
- */
- public class AttendancePunchStatListHandler extends ListHandler {
- private static Logger logger = Logger.getLogger(AttendancePunchStatListHandler.class);
- /** 有排班但未打卡时的展示文案 */
- private static final String NOT_PUNCHED_LABEL = "未打卡";
- /**
- * 排班日类型:工作日(需打卡)。若与现场枚举不一致可调整此值。
- */
- private static final int SCHEDULE_WORK_DAY_TYPE = 0;
- Context ctx = SHRContext.getInstance().getContext();
- @Override
- protected GridDataEntity getGridRequestData(HttpServletRequest request, HttpServletResponse response,
- ModelMap modelMap) throws SHRWebException {
- logger.info("getGridRequestData------------");
- // 1. 获取快速过滤条件(直接从request参数获取框架生成的SQL字符串)
- String filterItemsStr = request.getParameter("filterItems");
- logger.info("filterItems from parameter: " + filterItemsStr);
- // 2. 获取年月信息(从fastFilterItems JSON中解析)
- String fastFilterItems = request.getParameter("fastFilterItems");
- String yearMonth = "";
- if (!StringUtils.isEmpty(fastFilterItems)) {
- try {
- JSONObject filterJson = JSONObject.parseObject(fastFilterItems);
- if (filterJson.containsKey("yearMonth")) {
- Object yearMonthObj = filterJson.get("yearMonth");
- if (yearMonthObj instanceof JSONObject) {
- JSONObject yearMonthJson = (JSONObject) yearMonthObj;
- // 尝试获取 values 字段
- Object valuesObj = yearMonthJson.get("values");
- if (valuesObj instanceof JSONObject) {
- // 还有一层嵌套,继续解析
- JSONObject valuesJson = (JSONObject) valuesObj;
- if (valuesJson.containsKey("date")) {
- yearMonth = valuesJson.getString("date");
- }
- } else if (valuesObj instanceof String) {
- yearMonth = (String) valuesObj;
- }
- } else if (yearMonthObj instanceof String) {
- yearMonth = (String) yearMonthObj;
- }
- }
- } catch (Exception e) {
- logger.error("解析fastFilterItems失败", e);
- }
- }
- // 3. 如果年月为空,使用当前年月
- if (StringUtils.isEmpty(yearMonth)) {
- yearMonth = DateTimeUtils.format(new Date(), "yyyy-MM");
- }
- logger.info("最终yearMonth: " + yearMonth);
- try {
- GridDataEntity gridDataEntity = new GridDataEntity();
- int rows = Integer.parseInt(request.getParameter("rows"));// 行数
- int page = Integer.parseInt(request.getParameter("page"));// 页数
- int rowsNum = rows;
- // 解析年月,支持 "yyyy-MM" 或 "yyyy-MM-dd" 格式
- String[] parts = yearMonth.split("-");
- int year = Integer.parseInt(parts[0]);
- int month = Integer.parseInt(parts[1]);
- int daysInMonth = getDaysInMonth(year, month);
- // 查询打卡记录(使用filterItems进行过滤)
- String sql = getSql(year, month, filterItemsStr);
- logger.info("考勤打卡统计SQL: " + sql);
- IRowSet rs = DbUtil.executeQuery(ctx, sql);
- // 封装打卡数据
- Map<String, Object> packagedData = packagingPunchData(rs);
- Map<String, Map<Integer, String>> personPunchDataMap = (Map<String, Map<Integer, String>>) packagedData
- .get("punchData");
- Map<String, String> personCodeMap = (Map<String, String>) packagedData.get("personCodeMap");
- Map<String, String> personNameMap = (Map<String, String>) packagedData.get("personNameMap");
- // 查询当月排班(工作日),与打卡数据合并
- String scheduleSql = getScheduleSql(year, month, filterItemsStr);
- logger.info("排班查询SQL: " + scheduleSql);
- IRowSet scheduleRs = DbUtil.executeQuery(ctx, scheduleSql);
- Map<String, Set<Integer>> personScheduleDays = packagingScheduleData(scheduleRs, personCodeMap,
- personNameMap);
- // 处理数据:有排班无打卡显示「未打卡」,未排班显示空
- List<Map<String, Object>> list = disposeData(personPunchDataMap, personScheduleDays, personCodeMap,
- personNameMap, daysInMonth);
- int dataCount = list.size();
- int total = dataCount % rowsNum == 0 ? dataCount / rowsNum : dataCount / rowsNum + 1;
- if (list != null && list.size() > 0) {
- gridDataEntity.setTotal(total);// 总页数
- gridDataEntity.setPage(page);// 当前页数
- gridDataEntity.setRecords(dataCount);// 总记录数
- gridDataEntity.setRows(list);
- gridDataEntity.setUserdata(new HashMap<String, Object>());
- }
- return gridDataEntity;
- } catch (Exception e) {
- e.printStackTrace();
- throw new ShrWebBizException(e.getMessage());
- }
- }
- /**
- * 构建SQL查询语句
- *
- * @param year 年
- * @param month 月
- * @param filterItemsStr 框架生成的SQL过滤条件字符串,如: "( personCode like '%阿萨德%' )"
- */
- private String getSql(int year, int month, String filterItemsStr) {
- // 计算当月的起始和结束日期
- Calendar cal = Calendar.getInstance();
- cal.set(year, month - 1, 1, 0, 0, 0);
- cal.set(Calendar.MILLISECOND, 0);
- String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- cal.set(year, month, 1, 0, 0, 0); // 直接设置下个月第一天
- cal.set(Calendar.MILLISECOND, 0);
- String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT ");
- sql.append(" p.FNUMBER AS personCode, ");
- sql.append(" p.FNAME_L2 AS personName, ");
- sql.append(" p.FID AS personId, ");
- sql.append(" pcr.FPUNCHCARDDATE AS fpunchcarddate, ");
- sql.append(" pcr.FPUNCHCARDTIME AS punchTime ");
- sql.append("FROM T_HR_ATS_PunchCardRecord pcr ");
- sql.append("LEFT JOIN T_BD_PERSON p ON p.FID = pcr.FPROPOSERID ");
- sql.append("WHERE pcr.FPUNCHCARDDATE >= {ts '").append(startDate).append("'} ");
- sql.append("AND pcr.FPUNCHCARDDATE < {ts '").append(endDate).append("'} ");
- // 添加框架生成的过滤条件(员工编码、姓名等)
- // 注意:需要将前端字段名映射为数据库字段名
- if (!StringUtils.isEmpty(filterItemsStr)) {
- // 替换字段名:personCode -> p.FNUMBER, personName -> p.FNAME_L2
- String mappedFilterItems = filterItemsStr
- .replace("personCode", "p.FNUMBER")
- .replace("personName", "p.FNAME_L2");
- sql.append(" AND ").append(mappedFilterItems);
- }
- sql.append(" ORDER BY p.FNUMBER, pcr.FPUNCHCARDDATE, pcr.FPUNCHCARDTIME");
- return sql.toString();
- }
- /**
- * 构建排班查询SQL(查询时间范围内人员的工作日排班)
- */
- private String getScheduleSql(int year, int month, String filterItemsStr) {
- Calendar cal = Calendar.getInstance();
- cal.set(year, month - 1, 1, 0, 0, 0);
- cal.set(Calendar.MILLISECOND, 0);
- String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- cal.set(year, month, 1, 0, 0, 0);
- cal.set(Calendar.MILLISECOND, 0);
- String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT ");
- sql.append(" p.FID AS personId, ");
- sql.append(" p.FNUMBER AS personCode, ");
- sql.append(" p.FNAME_L2 AS personName, ");
- sql.append(" ss.FATTENDDATE AS attendDate ");
- sql.append("FROM T_HR_ATS_SCHEDULESHIFT ss ");
- sql.append("INNER JOIN T_BD_PERSON p ON p.FID = ss.FPROPOSERID ");
- sql.append("WHERE ss.FATTENDDATE >= {ts '").append(startDate).append("'} ");
- sql.append("AND ss.FATTENDDATE < {ts '").append(endDate).append("'} ");
- sql.append("AND ss.FDAYTYPE = ").append(SCHEDULE_WORK_DAY_TYPE).append(" ");
- if (!StringUtils.isEmpty(filterItemsStr)) {
- String mappedFilterItems = filterItemsStr
- .replace("personCode", "p.FNUMBER")
- .replace("personName", "p.FNAME_L2");
- sql.append(" AND ").append(mappedFilterItems);
- }
- sql.append(" ORDER BY p.FNUMBER, ss.FATTENDDATE");
- return sql.toString();
- }
- /**
- * 封装排班数据:personId -> 当月有工作排班的日期集合(1-31)
- */
- private Map<String, Set<Integer>> packagingScheduleData(IRowSet rs, Map<String, String> personCodeMap,
- Map<String, String> personNameMap) throws SQLException {
- Map<String, Set<Integer>> scheduleMap = new HashMap<>();
- while (rs.next()) {
- String personId = rs.getString("personId");
- if (StringUtils.isEmpty(personId)) {
- continue;
- }
- String personCode = rs.getString("personCode");
- String personName = rs.getString("personName");
- if (!personCodeMap.containsKey(personId)) {
- personCodeMap.put(personId, personCode);
- }
- if (!personNameMap.containsKey(personId)) {
- personNameMap.put(personId, personName);
- }
- java.sql.Date attendDate = rs.getDate("attendDate");
- if (attendDate == null) {
- continue;
- }
- Calendar dateCal = Calendar.getInstance();
- dateCal.setTime(attendDate);
- int day = dateCal.get(Calendar.DAY_OF_MONTH);
- if (day < 1 || day > 31) {
- continue;
- }
- if (!scheduleMap.containsKey(personId)) {
- scheduleMap.put(personId, new HashSet<Integer>());
- }
- scheduleMap.get(personId).add(day);
- }
- return scheduleMap;
- }
- private int getDaysInMonth(int year, int month) {
- Calendar cal = Calendar.getInstance();
- cal.set(year, month - 1, 1);
- return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
- }
- /**
- * 封装打卡数据(返回打卡数据和人员信息)
- *
- * @return Map<"punchData"=打卡数据Map, "personCodeMap"=人员编码Map, "personNameMap"=
- * 人员姓名Map>
- */
- private Map<String, Object> packagingPunchData(IRowSet rs) throws SQLException {
- // 临时存储每个人的原始打卡记录
- Map<String, Map<Integer, List<String>>> tempDataMap = new HashMap<>();
- // 存储人员编码
- Map<String, String> personCodeMap = new HashMap<>();
- // 存储人员姓名
- Map<String, String> personNameMap = new HashMap<>();
- while (rs.next()) {
- // 人员ID
- String personId = rs.getString("personId");
- if (StringUtils.isEmpty(personId)) {
- continue;
- }
- // 人员编码
- String personCode = rs.getString("personCode");
- // 人员姓名
- String personName = rs.getString("personName");
- // 存储人员编码
- if (!personCodeMap.containsKey(personId)) {
- personCodeMap.put(personId, personCode);
- }
- // 存储人员姓名
- if (!personNameMap.containsKey(personId)) {
- personNameMap.put(personId, personName);
- }
- // 从完整日期中提取日期(当月第几天,1-31)
- java.sql.Date punchCardDate = rs.getDate("fpunchcarddate");
- if (punchCardDate == null) {
- continue;
- }
- java.util.Calendar dateCal = java.util.Calendar.getInstance();
- dateCal.setTime(punchCardDate);
- int punchDay = dateCal.get(java.util.Calendar.DAY_OF_MONTH);
- if (punchDay < 1 || punchDay > 31) {
- continue;
- }
- // 打卡时间
- if (rs.getDate("punchTime") == null) {
- continue;
- }
- // 格式化为 HH:mm:ss
- String punchTime = DateTimeUtils.format(rs.getDate("punchTime"), "HH:mm:ss");
- if (!tempDataMap.containsKey(personId)) {
- tempDataMap.put(personId, new HashMap<>());
- }
- if (!tempDataMap.get(personId).containsKey(punchDay)) {
- tempDataMap.get(personId).put(punchDay, new ArrayList<>());
- }
- tempDataMap.get(personId).get(punchDay).add(punchTime);
- }
- // 处理每个人的打卡记录,过滤一分钟内的重复打卡
- Map<String, Map<Integer, String>> resultMap = new HashMap<>();
- for (Map.Entry<String, Map<Integer, List<String>>> personEntry : tempDataMap.entrySet()) {
- String personId = personEntry.getKey();
- Map<Integer, List<String>> dayDataMap = personEntry.getValue();
- if (!resultMap.containsKey(personId)) {
- resultMap.put(personId, new HashMap<>());
- }
- for (Map.Entry<Integer, List<String>> dayEntry : dayDataMap.entrySet()) {
- int day = dayEntry.getKey();
- List<String> punchTimes = dayEntry.getValue();
- // 对打卡时间排序
- Collections.sort(punchTimes);
- // 过滤一分钟内的重复打卡,保留最早的
- List<String> filteredTimes = filterDuplicatePunches(punchTimes);
- // 将过滤后的打卡时间用换行符分隔
- String punchTimeStr = String.join("\n", filteredTimes);
- resultMap.get(personId).put(day, punchTimeStr);
- }
- }
- // 返回打卡数据和人员信息
- Map<String, Object> result = new HashMap<>();
- result.put("punchData", resultMap);
- result.put("personCodeMap", personCodeMap);
- result.put("personNameMap", personNameMap);
- return result;
- }
- /**
- * 处理数据,转换为列表格式。
- * 规则:有打卡显示打卡时间;有排班无打卡显示「未打卡」;未排班显示空。
- */
- private List<Map<String, Object>> disposeData(Map<String, Map<Integer, String>> personPunchDataMap,
- Map<String, Set<Integer>> personScheduleDays, Map<String, String> personCodeMap,
- Map<String, String> personNameMap, int daysInMonth) {
- List<Map<String, Object>> list = new ArrayList<>();
- Set<String> allPersonIds = new HashSet<String>();
- if (personPunchDataMap != null) {
- allPersonIds.addAll(personPunchDataMap.keySet());
- }
- if (personScheduleDays != null) {
- allPersonIds.addAll(personScheduleDays.keySet());
- }
- for (String personId : allPersonIds) {
- Map<Integer, String> punchData = personPunchDataMap != null && personPunchDataMap.containsKey(personId)
- ? personPunchDataMap.get(personId) : null;
- Set<Integer> scheduleDays = personScheduleDays != null && personScheduleDays.containsKey(personId)
- ? personScheduleDays.get(personId) : null;
- Map<String, Object> rowData = new HashMap<>();
- rowData.put("id", personId);
- rowData.put("personId", personId);
- if (personCodeMap.containsKey(personId)) {
- rowData.put("personCode", personCodeMap.get(personId));
- }
- if (personNameMap.containsKey(personId)) {
- rowData.put("personName", personNameMap.get(personId));
- }
- for (int day = 1; day <= daysInMonth; day++) {
- String punchTime = (punchData != null && punchData.containsKey(day)) ? punchData.get(day) : "";
- boolean hasSchedule = scheduleDays != null && scheduleDays.contains(day);
- if (!StringUtils.isEmpty(punchTime)) {
- rowData.put("day" + day, punchTime);
- } else if (hasSchedule) {
- rowData.put("day" + day, NOT_PUNCHED_LABEL);
- } else {
- rowData.put("day" + day, "");
- }
- }
- // 超出当月天数的列保持为空(如2月 day29-31)
- for (int day = daysInMonth + 1; day <= 31; day++) {
- rowData.put("day" + day, "");
- }
- list.add(rowData);
- }
- return list;
- }
- /**
- * 重写快速过滤方法,正确处理所有快速过滤条件
- * 注意:
- * 1. filterItems 参数包含框架生成的 SQL 过滤条件字符串,如: "( personCode like '%阿萨德%' )"
- * 2. fastFilterItems 参数包含原始 JSON 数据,用于获取 yearMonth 等业务参数
- * 3. 不能返回空的FilterInfo,否则会丢失所有过滤条件
- */
- @Override
- protected FilterInfo getFastFilter(HttpServletRequest request) throws SHRWebException {
- try {
- // 1. 从请求参数中获取filterItems(框架已生成的SQL过滤条件)
- String filterItemsStr = request.getParameter("filterItems");
- logger.info("filterItems from request: " + filterItemsStr);
- // 2. 从fastFilterItems中获取yearMonth等业务参数(用于日期查询)
- String fastFilterItems = request.getParameter("fastFilterItems");
- if (!StringUtils.isEmpty(fastFilterItems)) {
- try {
- JSONObject filterJson = JSONObject.parseObject(fastFilterItems);
- // 将年月信息设置到request属性中(供getGridRequestData使用)
- if (filterJson.containsKey("yearMonth")) {
- Object yearMonthObj = filterJson.get("yearMonth");
- if (yearMonthObj instanceof JSONObject) {
- JSONObject yearMonthJson = (JSONObject) yearMonthObj;
- Object valuesObj = yearMonthJson.get("values");
- if (valuesObj != null) {
- request.setAttribute("yearMonth", valuesObj.toString());
- }
- } else if (yearMonthObj instanceof String) {
- request.setAttribute("yearMonth", yearMonthObj);
- }
- }
- } catch (Exception e) {
- logger.error("解析fastFilterItems失败", e);
- }
- }
- // 3. 将filterItems设置到request属性中(供getGridRequestData使用)
- if (!StringUtils.isEmpty(filterItemsStr)) {
- request.setAttribute("filterItems", filterItemsStr);
- }
- // 4. 返回空的FilterInfo,因为我们使用自定义SQL,直接拼接filterItems字符串
- return new FilterInfo();
- } catch (Exception e) {
- logger.error("处理快速过滤条件失败", e);
- return new FilterInfo();
- }
- }
- /**
- * 查询考勤打卡数据
- *
- * @param yearMonth 年月 yyyy-MM
- * @return Map<人员ID, Map<日期, 打卡时间列表>>
- */
- public Map<String, Map<Integer, String>> getAttendancePunchData(String yearMonth)
- throws SQLException, BOSException {
- Map<String, Map<Integer, String>> resultMap = new HashMap<>();
- // 解析年月
- String[] parts = yearMonth.split("-");
- int year = Integer.parseInt(parts[0]);
- int month = Integer.parseInt(parts[1]);
- // 构建SQL查询语句 - 查询所有打卡记录
- // 计算当月的起始和结束日期
- Calendar cal = Calendar.getInstance();
- cal.set(year, month - 1, 1, 0, 0, 0);
- cal.set(Calendar.MILLISECOND, 0);
- String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- cal.set(year, month, 1, 0, 0, 0); // 直接设置下个月第一天
- cal.set(Calendar.MILLISECOND, 0);
- String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT ");
- sql.append(" p.FNUMBER AS personCode, ");
- sql.append(" p.FNAME_L2 AS personName, ");
- sql.append(" p.FID AS personId, ");
- sql.append(" pcr.FPUNCHCARDDATE AS fpunchcarddate, ");
- sql.append(" pcr.FPUNCHCARDTIME AS punchTime ");
- sql.append("FROM T_HR_ATS_PunchCardRecord pcr ");
- sql.append("LEFT JOIN T_BD_PERSON p ON p.FID = pcr.FPROPOSERID ");
- sql.append("WHERE pcr.FPUNCHCARDDATE >= {ts '").append(startDate).append("'} ");
- sql.append("AND pcr.FPUNCHCARDDATE < {ts '").append(endDate).append("'} ");
- sql.append("ORDER BY p.FNUMBER, pcr.FPUNCHCARDDATE, pcr.FPUNCHCARDTIME");
- logger.info("考勤打卡统计SQL: " + sql.toString());
- logger.info("参数: year=" + year + ", month=" + month);
- // 执行查询(不需要参数)
- IRowSet rs = com.kingdee.eas.util.app.DbUtil.executeQuery(
- SHRContext.getInstance().getContext(),
- sql.toString());
- // 临时存储每个人的原始打卡记录
- Map<String, Map<Integer, List<String>>> tempDataMap = new HashMap<>();
- while (rs.next()) {
- String personId = rs.getString("personId");
- // 从完整日期中提取日期(当月第几天,1-31)
- java.sql.Date punchCardDate = rs.getDate("fpunchcarddate");
- if (punchCardDate == null) {
- continue;
- }
- java.util.Calendar dateCal = java.util.Calendar.getInstance();
- dateCal.setTime(punchCardDate);
- int punchDay = dateCal.get(java.util.Calendar.DAY_OF_MONTH);
- String punchTime = rs.getDate("punchTime") != null
- ? DateTimeUtils.format(rs.getDate("punchTime"), "HH:mm:ss")
- : "";
- if (!tempDataMap.containsKey(personId)) {
- tempDataMap.put(personId, new HashMap<>());
- }
- if (!tempDataMap.get(personId).containsKey(punchDay)) {
- tempDataMap.get(personId).put(punchDay, new ArrayList<>());
- }
- tempDataMap.get(personId).get(punchDay).add(punchTime);
- }
- // 处理每个人的打卡记录,过滤一分钟内的重复打卡
- for (Map.Entry<String, Map<Integer, List<String>>> personEntry : tempDataMap.entrySet()) {
- String personId = personEntry.getKey();
- Map<Integer, List<String>> dayDataMap = personEntry.getValue();
- if (!resultMap.containsKey(personId)) {
- resultMap.put(personId, new HashMap<>());
- }
- for (Map.Entry<Integer, List<String>> dayEntry : dayDataMap.entrySet()) {
- int day = dayEntry.getKey();
- List<String> punchTimes = dayEntry.getValue();
- // 对打卡时间排序
- Collections.sort(punchTimes);
- // 过滤一分钟内的重复打卡,保留最早的
- List<String> filteredTimes = filterDuplicatePunches(punchTimes);
- // 将过滤后的打卡时间用换行符分隔
- String punchTimeStr = String.join("\n", filteredTimes);
- resultMap.get(personId).put(day, punchTimeStr);
- }
- }
- return resultMap;
- }
- /**
- * 过滤一分钟内的重复打卡,保留最早的一条
- *
- * @param punchTimes 已排序的打卡时间列表
- * @return 过滤后的打卡时间列表
- */
- private List<String> filterDuplicatePunches(List<String> punchTimes) {
- if (punchTimes == null || punchTimes.isEmpty()) {
- return punchTimes;
- }
- List<String> result = new ArrayList<>();
- for (int i = 0; i < punchTimes.size(); i++) {
- String currentTime = punchTimes.get(i);
- // 如果是第一个元素,直接添加(最早的)
- if (i == 0) {
- result.add(currentTime);
- continue;
- }
- String previousTime = result.get(result.size() - 1);
- // 计算两个时间之间的秒数差
- long secondsDiff = calculateTimeDifference(previousTime, currentTime);
- // 如果时间差大于60秒,添加当前记录
- if (secondsDiff > 60) {
- result.add(currentTime);
- }
- // 如果在同一分钟内,跳过当前记录(保留最早的一条)
- }
- return result;
- }
- /**
- * 计算两个时间字符串之间的秒数差
- *
- * @param time1 时间1 (HH:mm:ss)
- * @param time2 时间2 (HH:mm:ss)
- * @return 秒数差
- */
- private long calculateTimeDifference(String time1, String time2) {
- try {
- String[] parts1 = time1.split(":");
- String[] parts2 = time2.split(":");
- int seconds1 = Integer.parseInt(parts1[0]) * 3600 +
- Integer.parseInt(parts1[1]) * 60 +
- Integer.parseInt(parts1[2]);
- int seconds2 = Integer.parseInt(parts2[0]) * 3600 +
- Integer.parseInt(parts2[1]) * 60 +
- Integer.parseInt(parts2[2]);
- return Math.abs(seconds2 - seconds1);
- } catch (Exception e) {
- logger.error("计算时间差失败: " + time1 + ", " + time2, e);
- return 0;
- }
- }
- }
|