From 8f1efdcd51a0a75d3b19704d1487502bf1b60942 Mon Sep 17 00:00:00 2001
From: tjx <t2856754968@163.com>
Date: 星期二, 23 十二月 2025 13:27:58 +0800
Subject: [PATCH] 111

---
 src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java       |  229 +++++++++++++++-----
 src/main/java/com/gs/dingtalk/service/WorkWXService.java                 |  424 ++++++++++++++++++++++++++++++++++++++
 src/main/java/com/gs/dingtalk/service/impl/QwCheckinDataServiceImpl.java |    4 
 3 files changed, 592 insertions(+), 65 deletions(-)

diff --git a/src/main/java/com/gs/dingtalk/service/WorkWXService.java b/src/main/java/com/gs/dingtalk/service/WorkWXService.java
index 493ed70..9c11791 100644
--- a/src/main/java/com/gs/dingtalk/service/WorkWXService.java
+++ b/src/main/java/com/gs/dingtalk/service/WorkWXService.java
@@ -314,6 +314,293 @@
         return allCheckinData;
     }
 
+    /**
+     * 鑾峰彇鎵撳崱鏃ユ姤鏁版嵁
+     * 鎺ュ彛璋冪敤棰戠巼闄愬埗涓�100娆�/鍒嗛挓
+     * @param startTime 寮�濮嬫椂闂存埑锛堢锛�
+     * @param endTime 缁撴潫鏃堕棿鎴筹紙绉掞級
+     * @param useridList 鐢ㄦ埛ID鍒楄〃
+     * @return 鎵撳崱鏃ユ姤鏁版嵁鍒楄〃
+     */
+    public List<CheckinDayData> getCheckinDayData(long startTime, long endTime, List<String> useridList) throws IOException {
+        String accessToken = getAccessToken();
+        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s", accessToken);
+
+        List<CheckinDayData> allDayData = new ArrayList<>();
+
+        int batchSize = 100;
+        int totalUsers = useridList.size();
+        int batchCount = (totalUsers + batchSize - 1) / batchSize;
+
+        log.info("寮�濮嬭幏鍙栨墦鍗℃棩鎶ユ暟鎹紝鎬荤敤鎴锋暟: {}, 鍒嗘壒鏁�: {}, 鏃堕棿鑼冨洿: {} - {}", totalUsers, batchCount, startTime, endTime);
+
+        for (int i = 0; i < batchCount; i++) {
+            int fromIndex = i * batchSize;
+            int toIndex = Math.min((i + 1) * batchSize, totalUsers);
+
+            List<String> batchUserList = useridList.subList(fromIndex, toIndex);
+            log.info("姝e湪鑾峰彇绗� {}/{} 鎵规墦鍗℃棩鎶ユ暟鎹紝鐢ㄦ埛鏁�: {}", i + 1, batchCount, batchUserList.size());
+
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("starttime", startTime);
+            requestBody.put("endtime", endTime);
+            requestBody.put("useridlist", batchUserList);
+
+            MediaType mediaType = MediaType.parse("application/json; charset=UTF-8");
+            String jsonBody = objectMapper.writeValueAsString(requestBody);
+            RequestBody body = RequestBody.create(mediaType, jsonBody);
+
+            Request request = new Request.Builder()
+                    .url(url)
+                    .post(body)
+                    .addHeader("Content-Type", "application/json; charset=UTF-8")
+                    .build();
+
+            try (Response response = client.newCall(request).execute()) {
+                if (!response.isSuccessful()) {
+                    log.error("鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触锛孒TTP鐘舵�佺爜: {}", response.code());
+                    throw new IOException("鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触: " + response.message());
+                }
+
+                String responseBody = response.body().string();
+
+                WorkWXCheckinDayDataResponse dayDataResponse = objectMapper.readValue(responseBody, WorkWXCheckinDayDataResponse.class);
+
+                if (dayDataResponse.getErrcode() != 0) {
+                    log.error("鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触锛岄敊璇爜: {}, 閿欒淇℃伅: {}",
+                            dayDataResponse.getErrcode(), dayDataResponse.getErrmsg());
+                    throw new IOException("鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触: " + dayDataResponse.getErrmsg());
+                }
+
+                if (dayDataResponse.getDatas() != null) {
+                    allDayData.addAll(dayDataResponse.getDatas());
+                    log.info("绗� {}/{} 鎵硅幏鍙栧埌鎵撳崱鏃ユ姤璁板綍鏁�: {}", i + 1, batchCount, dayDataResponse.getDatas().size());
+                }
+            }
+
+            // 鎺ュ彛闄愬埗100娆�/鍒嗛挓锛屾壒娆¢棿绛夊緟600ms纭繚涓嶈秴闄�
+            if (i < batchCount - 1) {
+                try {
+                    Thread.sleep(600);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    log.warn("鎵规闂寸瓑寰呰涓柇");
+                }
+            }
+        }
+
+        log.info("鎵撳崱鏃ユ姤鏁版嵁鑾峰彇瀹屾垚锛屾�昏褰曟暟: {}", allDayData.size());
+        return allDayData;
+    }
+
+    /**
+     * 鏍规嵁QW_STAFF琛ㄨ幏鍙栨墦鍗℃棩鎶ユ暟鎹�
+     */
+    public List<CheckinDayData> getCheckinDayDataByQwStaff(long startTime, long endTime) throws IOException {
+        List<QwStaff> qwStaffList = qwStaffMapper.selectList(new LambdaQueryWrapper<QwStaff>());
+
+        if (qwStaffList == null || qwStaffList.isEmpty()) {
+            log.warn("QW_STAFF琛ㄤ腑娌℃湁鏁版嵁");
+            return new ArrayList<>();
+        }
+
+        List<String> useridList = qwStaffList.stream()
+                .map(QwStaff::getAccount)
+                .filter(account -> account != null && !account.isEmpty())
+                .collect(Collectors.toList());
+
+        if (useridList.isEmpty()) {
+            log.warn("QW_STAFF琛ㄤ腑娌℃湁鏈夋晥鐨刟ccount鏁版嵁");
+            return new ArrayList<>();
+        }
+
+        log.info("浠嶲W_STAFF琛ㄨ幏鍙栧埌 {} 涓敤鎴穉ccount", useridList.size());
+        return getCheckinDayData(startTime, endTime, useridList);
+    }
+
+    //鑾峰彇鎵撳崱鏃ユ姤鏁版嵁
+    //璇锋眰鏂瑰紡: POST(HTTPS)
+    //璇锋眰鍦板潃: https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=ACCESS_TOKEN
+    //鍙傛暟绀轰緥
+    // {
+    //    "starttime": 1599062400,
+    //    "endtime": 1599062400,
+    //    "useridlist": [
+    //        "ZhangSan"
+    //    ]
+    //}
+    //鎺ュ彛璋冪敤棰戠巼闄愬埗涓�100娆�/鍒嗛挓銆�
+    //杩斿洖缁撴灉
+    //{
+    //    "errcode":0,
+    //    "errmsg":"ok",
+    //    "datas":[
+    //        {
+    //            "base_info":{
+    //                "date":1599062400,
+    //                "record_type":1,
+    //                "name":"寮犱笁",
+    //                "name_ex":"Three Zhang",
+    //                "departs_name":"鏈夊浼佷笟/realempty;鏈夊浼佷笟;鏈夊浼佷笟/閮ㄩ棬A4",
+    //                "acctid":"ZhangSan",
+    //                "rule_info":{
+    //                    "groupid":10,
+    //                    "groupname":"瑙勫垯娴嬭瘯",
+    //                    "scheduleid":0,
+    //                    "schedulename":"",
+    //                    "checkintime":[
+    //                        {
+    //                            "work_sec":38760,
+    //                            "off_work_sec":38880
+    //                        }
+    //                    ]
+    //                },
+    //                "day_type":0
+    //            },
+    //            "summary_info":{
+    //                "checkin_count":2,
+    //                "regular_work_sec":31,
+    //                "standard_work_sec":120,
+    //                "earliest_time":38827,
+    //                "lastest_time":38858
+    //            },
+    //            "holiday_infos":[
+    //                {
+    //                    "sp_description":{
+    //                        "data":[
+    //                            {
+    //                                "lang":"zh_CN",
+    //                                "text":"09/03 10:00~09/03 10:01"
+    //                            }
+    //                        ]
+    //                    },
+    //                    "sp_number":"202009030002",
+    //                    "sp_title":{
+    //                        "data":[
+    //                            {
+    //                                "lang":"zh_CN",
+    //                                "text":"璇峰亣0.1灏忔椂"
+    //                            }
+    //                        ]
+    //                    }
+    //                },
+    //                {
+    //                    "sp_description":{
+    //                        "data":[
+    //                            {
+    //                                "lang":"zh_CN",
+    //                                "text":"08/25 14:37~09/10 14:37"
+    //                            }
+    //                        ]
+    //                    },
+    //                    "sp_number":"202008270004",
+    //                    "sp_title":{
+    //                        "data":[
+    //                            {
+    //                                "lang":"zh_CN",
+    //                                "text":"鍔犵彮17.0灏忔椂"
+    //                            }
+    //                        ]
+    //                    }
+    //                }
+    //            ],
+    //            "exception_infos":[
+    //                {
+    //                    "count":1,
+    //                    "duration":60,
+    //                    "exception":1
+    //                },
+    //                {
+    //                    "count":1,
+    //                    "duration":60,
+    //                    "exception":2
+    //                }
+    //            ],
+    //            "ot_info":{
+    //                "ot_status":1,
+    //                "ot_duration":3600,
+    //                "exception_duration":[],
+    //				"workday_over_as_money": 54000
+    //            },
+    //            "sp_items":[
+    //                {
+    //                    "count":1,
+    //                    "duration":360,
+    //                    "time_type":0,
+    //                    "type":1,
+    //                    "vacation_id":2,
+    //					"name":"骞村亣"
+    //                },
+    //                {
+    //                    "count":0,
+    //                    "duration":0,
+    //                    "time_type":0,
+    //                    "type":100,
+    //                    "vacation_id":0,
+    //					"name":"澶栧嫟娆℃暟"
+    //                }
+    //            ]
+    //        }
+    //    ]
+    //}
+    //鍙傛暟璇存槑:
+    //errcode	int32	杩斿洖鐮�
+    //errmsg	string	閿欒鐮佹弿杩�
+    //datas	obj[]	鏃ユ姤鏁版嵁鍒楄〃
+    //datas.base_info	obj	鍩虹淇℃伅
+    //datas.base_info.date	uint32	鏃ユ姤鏃ユ湡
+    //datas.base_info.record_type	uint32	璁板綍绫诲瀷锛�1-鍥哄畾涓婁笅鐝紱2-澶栧嚭锛堟鎶ヨ〃涓笉浼氬嚭鐜板鍑烘墦鍗℃暟鎹級锛�3-鎸夌彮娆′笂涓嬬彮锛�4-鑷敱绛惧埌锛�5-鍔犵彮锛�7-鏃犺鍒�
+    //datas.base_info.name	string	鎵撳崱浜哄憳濮撳悕
+    //datas.base_info.name_ex	string	鎵撳崱浜哄憳鍒悕
+    //datas.base_info.departs_name	string	鎵撳崱浜哄憳鎵�鍦ㄩ儴闂紝浼氭樉绀烘墍鏈夋墍鍦ㄩ儴闂�
+    //datas.base_info.acctid	string	鎵撳崱浜哄憳璐﹀彿锛屽嵆userid
+    //datas.base_info.rule_info	obj	鎵撳崱浜哄憳鎵�灞炶鍒欎俊鎭�
+    //datas.base_info.rule_info.groupid	int32	鎵�灞炶鍒欑殑id
+    //datas.base_info.rule_info.groupname	string	鎵撳崱瑙勫垯鍚�
+    //datas.base_info.rule_info.scheduleid	int32	褰撴棩鎵�灞炵彮娆d锛屼粎鎸夌彮娆′笂涓嬬彮鎵嶆湁鍊硷紝鏄剧ず鍦ㄦ墦鍗℃棩鎶�-鐝鍒�
+    //datas.base_info.rule_info.schedulename	string	褰撴棩鎵�灞炵彮娆″悕绉帮紝浠呮寜鐝涓婁笅鐝墠鏈夊�硷紝鏄剧ず鍦ㄦ墦鍗℃棩鎶�-鐝鍒�
+    //datas.base_info.rule_info.checkintime	obj[]	褰撴棩鎵撳崱鏃堕棿锛屼粎鍥哄畾涓婁笅鐝鍒欐湁鍊硷紝鏄剧ず鍦ㄦ墦鍗℃棩鎶�-鐝鍒�
+    //datas.base_info.rule_info.checkintime.work_sec	uint32	涓婄彮鏃堕棿锛屼负璺濈0鐐圭殑鏃堕棿宸�
+    //datas.base_info.rule_info.checkintime.off_work_sec	uint32	涓嬬彮鏃堕棿锛屼负璺濈0鐐圭殑鏃堕棿宸�
+    //datas.base_info.day_type	uint32	鏃ユ姤绫诲瀷锛�0-宸ヤ綔鏃ユ棩鎶ワ紱1-浼戞伅鏃ユ棩鎶�
+    //datas.summary_info	obj	姹囨�讳俊鎭�
+    //datas.summary_info.checkin_count	int32	褰撴棩鎵撳崱娆℃暟
+    //datas.summary_info.regular_work_sec	int32	褰撴棩瀹為檯宸ヤ綔鏃堕暱锛屽崟浣嶏細绉�
+    //datas.summary_info.standard_work_sec	int32	褰撴棩鏍囧噯宸ヤ綔鏃堕暱锛屽崟浣嶏細绉�
+    //datas.summary_info.earliest_time	int32	褰撴棩鏈�鏃╂墦鍗℃椂闂�
+    //datas.summary_info.lastest_time	int32	褰撴棩鏈�鏅氭墦鍗℃椂闂�
+    //datas.holiday_infos	obj[]	鍋囧嫟鐩稿叧淇℃伅
+    //datas.holiday_infos.sp_number	string	鍋囧嫟鐢宠id锛屽嵆褰撴棩鍏宠仈鐨勫亣鍕ゅ鎵瑰崟id
+    //datas.holiday_infos.sp_title	obj	鍋囧嫟淇℃伅鎽樿-鏍囬淇℃伅
+    //datas.holiday_infos.sp_title.data	obj[]	澶氱璇█鎻忚堪锛岀洰鍓嶅彧鏈変腑鏂囦竴绉�
+    //datas.holiday_infos.sp_title.data.text	string	鍋囧嫟淇℃伅鎽樿-鏍囬鏂囨湰
+    //datas.holiday_infos.sp_title.data.lang	string	璇█绫诲瀷锛�"zh_CN"
+    //datas.holiday_infos.sp_description	obj	鍋囧嫟淇℃伅鎽樿-鎻忚堪淇℃伅
+    //datas.holiday_infos.sp_description.data	obj[]	澶氱璇█鎻忚堪锛岀洰鍓嶅彧鏈変腑鏂囦竴绉�
+    //datas.holiday_infos.sp_description.data.text	string	鍋囧嫟淇℃伅鎽樿-鎻忚堪鏂囨湰
+    //datas.holiday_infos.sp_description.data.lang	string	璇█绫诲瀷锛�"zh_CN"
+    //datas.exception_infos	obj[]	鏍″噯鐘舵�佷俊鎭�
+    //datas.exception_infos.exception	uint32	鏍″噯鐘舵�佺被鍨嬶細1-杩熷埌锛�2-鏃╅��锛�3-缂哄崱锛�4-鏃峰伐锛�5-鍦扮偣寮傚父锛�6-璁惧寮傚父
+    //datas.exception_infos.count	int32	褰撴棩姝ゅ紓甯哥殑娆℃暟
+    //datas.exception_infos.duration	int32	褰撴棩姝ゅ紓甯哥殑鏃堕暱锛堣繜鍒�/鏃╅��/鏃峰伐鎵嶆湁鍊硷級
+    //datas.ot_info	obj	鍔犵彮淇℃伅
+    //datas.ot_info.ot_status	uint32	鐘舵�侊細0-鏃犲姞鐝紱1-姝e父锛�2-缂烘椂闀�
+    //datas.ot_info.ot_duration	uint32	鍔犵彮鏃堕暱
+    //datas.ot_info.exception_duration	uint32[]	ot_status涓�2涓嬶紝鍔犵彮涓嶈冻鐨勬椂闀�
+    //datas.ot_info.workday_over_as_vacation	int32	宸ヤ綔鏃ュ姞鐝涓鸿皟浼戯紝鍗曚綅绉�
+    //datas.ot_info.workday_over_as_money	int32	宸ヤ綔鏃ュ姞鐝涓哄姞鐝垂锛屽崟浣嶇
+    //datas.ot_info.restday_over_as_vacation	int32	浼戞伅鏃ュ姞鐝涓鸿皟浼戯紝鍗曚綅绉�
+    //datas.ot_info.restday_over_as_money	int32	浼戞伅鏃ュ姞鐝涓哄姞鐝垂锛屽崟浣嶇
+    //datas.ot_info.holiday_over_as_vacation	int32	鑺傚亣鏃ュ姞鐝涓鸿皟浼戯紝鍗曚綅绉�
+    //datas.ot_info.holiday_over_as_money	int32	鑺傚亣鏃ュ姞鐝涓哄姞鐝垂锛屽崟浣嶇
+    //datas.sp_items	obj[]	鍋囧嫟缁熻淇℃伅
+    //datas.sp_items.type	uint32	绫诲瀷锛�1-璇峰亣锛�2-琛ュ崱锛�3-鍑哄樊锛�4-澶栧嚭锛�15-瀹℃壒鎵撳崱锛�100-澶栧嫟
+    //datas.sp_items.vacation_id	uint32	鍏蜂綋璇峰亣绫诲瀷锛屽綋type涓�1璇峰亣鏃讹紝鍏蜂綋鐨勮鍋囩被鍨媔d锛屽彲閫氳繃瀹℃壒鐩稿叧鎺ュ彛鑾峰彇鍋囨湡璇︽儏
+    //datas.sp_items.count	uint32	褰撴棩鍋囧嫟娆℃暟
+    //datas.sp_items.duration	uint32	褰撴棩鍋囧嫟鏃堕暱绉掓暟锛屾椂闀垮崟浣嶄负澶╃洿鎺ラ櫎浠�86400鍗充负澶╂暟锛屽崟浣嶄负灏忔椂鐩存帴闄や互3600鍗充负灏忔椂鏁�
+    //datas.sp_items.time_type	uint32	鏃堕暱鍗曚綅锛�0-鎸夊ぉ 1-鎸夊皬鏃�
+    //datas.sp_items.name	string	缁熻椤瑰悕绉�
+
     @Data
     private static class WorkWXTokenResponse {
         private Integer errcode;
@@ -371,6 +658,143 @@
         private List<CheckinData> checkindata;
     }
 
+    // ==================== 鎵撳崱鏃ユ姤鏁版嵁鍝嶅簲缁撴瀯 ====================
+
+    @Data
+    private static class WorkWXCheckinDayDataResponse {
+        private Integer errcode;
+        private String errmsg;
+        private List<CheckinDayData> datas;
+    }
+
+    @Data
+    public static class CheckinDayData {
+        @JsonProperty("base_info")
+        private BaseInfo baseInfo;
+        @JsonProperty("summary_info")
+        private SummaryInfo summaryInfo;
+        @JsonProperty("holiday_infos")
+        private List<HolidayInfo> holidayInfos;
+        @JsonProperty("exception_infos")
+        private List<ExceptionInfo> exceptionInfos;
+        @JsonProperty("ot_info")
+        private OtInfo otInfo;
+        @JsonProperty("sp_items")
+        private List<SpItem> spItems;
+    }
+
+    @Data
+    public static class BaseInfo {
+        private Long date;
+        @JsonProperty("record_type")
+        private Integer recordType;
+        private String name;
+        @JsonProperty("name_ex")
+        private String nameEx;
+        @JsonProperty("departs_name")
+        private String departsName;
+        private String acctid;
+        @JsonProperty("rule_info")
+        private RuleInfo ruleInfo;
+        @JsonProperty("day_type")
+        private Integer dayType;
+    }
+
+    @Data
+    public static class RuleInfo {
+        private Integer groupid;
+        private String groupname;
+        private Integer scheduleid;
+        private String schedulename;
+        private List<CheckinTime> checkintime;
+    }
+
+    @Data
+    public static class CheckinTime {
+        @JsonProperty("work_sec")
+        private Integer workSec;
+        @JsonProperty("off_work_sec")
+        private Integer offWorkSec;
+    }
+
+    @Data
+    public static class SummaryInfo {
+        @JsonProperty("checkin_count")
+        private Integer checkinCount;
+        @JsonProperty("regular_work_sec")
+        private Integer regularWorkSec;
+        @JsonProperty("standard_work_sec")
+        private Integer standardWorkSec;
+        @JsonProperty("earliest_time")
+        private Integer earliestTime;
+        @JsonProperty("lastest_time")
+        private Integer lastestTime;
+    }
+
+    @Data
+    public static class HolidayInfo {
+        @JsonProperty("sp_number")
+        private String spNumber;
+        @JsonProperty("sp_title")
+        private LangData spTitle;
+        @JsonProperty("sp_description")
+        private LangData spDescription;
+    }
+
+    @Data
+    public static class LangData {
+        private List<LangText> data;
+    }
+
+    @Data
+    public static class LangText {
+        private String lang;
+        private String text;
+    }
+
+    @Data
+    public static class ExceptionInfo {
+        private Integer exception;  // 1-杩熷埌锛�2-鏃╅��锛�3-缂哄崱锛�4-鏃峰伐锛�5-鍦扮偣寮傚父锛�6-璁惧寮傚父
+        private Integer count;
+        private Integer duration;
+    }
+
+    @Data
+    public static class OtInfo {
+        @JsonProperty("ot_status")
+        private Integer otStatus;  // 0-鏃犲姞鐝紱1-姝e父锛�2-缂烘椂闀�
+        @JsonProperty("ot_duration")
+        private Integer otDuration;
+        @JsonProperty("exception_duration")
+        private List<Integer> exceptionDuration;
+        @JsonProperty("workday_over_as_vacation")
+        private Integer workdayOverAsVacation;
+        @JsonProperty("workday_over_as_money")
+        private Integer workdayOverAsMoney;
+        @JsonProperty("restday_over_as_vacation")
+        private Integer restdayOverAsVacation;
+        @JsonProperty("restday_over_as_money")
+        private Integer restdayOverAsMoney;
+        @JsonProperty("holiday_over_as_vacation")
+        private Integer holidayOverAsVacation;
+        @JsonProperty("holiday_over_as_money")
+        private Integer holidayOverAsMoney;
+    }
+
+    @Data
+    public static class SpItem {
+        private Integer type;  // 1-璇峰亣锛�2-琛ュ崱锛�3-鍑哄樊锛�4-澶栧嚭锛�15-瀹℃壒鎵撳崱锛�100-澶栧嫟
+        @JsonProperty("vacation_id")
+        private Integer vacationId;
+        private Integer count;
+        private Integer duration;
+        @JsonProperty("time_type")
+        private Integer timeType;  // 0-鎸夊ぉ 1-鎸夊皬鏃�
+        private String name;
+    }
+
+    // ==================== 鎵撳崱鍘熷鏁版嵁缁撴瀯 ====================
+
     @Data
     public static class CheckinData {
         private String userid;
diff --git a/src/main/java/com/gs/dingtalk/service/impl/QwCheckinDataServiceImpl.java b/src/main/java/com/gs/dingtalk/service/impl/QwCheckinDataServiceImpl.java
index 253c852..586ae9c 100644
--- a/src/main/java/com/gs/dingtalk/service/impl/QwCheckinDataServiceImpl.java
+++ b/src/main/java/com/gs/dingtalk/service/impl/QwCheckinDataServiceImpl.java
@@ -21,6 +21,7 @@
  */
 @Service
 @RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
 public class QwCheckinDataServiceImpl extends ServiceImpl<QwCheckinDataMapper, QwCheckinData>
         implements QwCheckinDataService {
 
@@ -85,7 +86,6 @@
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public int saveCheckinDataBatch(List<WorkWXService.CheckinData> checkinDataList) {
         if (checkinDataList == null || checkinDataList.isEmpty()) {
             return 0;
@@ -112,7 +112,6 @@
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public int syncCheckinData(long startTime, long endTime) throws IOException {
         log.info("寮�濮嬪悓姝ユ墦鍗℃暟鎹紝鏃堕棿鑼冨洿: {} - {}", new Date(startTime * 1000), new Date(endTime * 1000));
 
@@ -128,7 +127,6 @@
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public int syncYesterdayCheckinData() throws IOException {
         long currentTime = System.currentTimeMillis() / 1000;
         long oneDaySeconds = 86400;
diff --git a/src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java b/src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java
index 61713ad..71afa7f 100644
--- a/src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java
+++ b/src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java
@@ -72,68 +72,6 @@
         // 4. 鏍囧噯鎵撳崱鏃堕棿鍙浜庡浐瀹氭帓鐝拰鑷畾涔夋帓鐝袱绉嶇被鍨嬫湁鏁�
         // 5. 鎺ュ彛璋冪敤棰戠巼闄愬埗涓�600娆�/鍒嗛挓锛堝凡鍦⊿ervice灞傚疄鐜版壒娆¢棿寤惰繜锛�
 
-        //鎺ュ彛杩斿洖鐨勫師濮嬫暟鎹�
-        //{
-        //   "errcode":0,
-        //   "errmsg":"ok",
-        //   "checkindata": [{
-        //		"userid" : "james",
-        //		"groupname" : "鎵撳崱涓�缁�",
-        //		"checkin_type" : "涓婄彮鎵撳崱",
-        //		"exception_type" : "鍦扮偣寮傚父",
-        //		"checkin_time" : 1492617610,
-        //		"location_title" : "渚濇緶搴�",
-        //		"location_detail" : "鍥涘窛鐪佹垚閮藉競姝︿警鍖虹泭宸炲ぇ閬撲腑娈�784鍙烽檮杩�",
-        //		"wifiname" : "鍔炲叕涓�鍖�",
-        //		"notes" : "璺笂鍫佃溅锛岃繜鍒颁簡5鍒嗛挓",
-        //		"wifimac" : "3c:46:d8:0c:7a:70",
-        //		"mediaids":["WWCISP_G8PYgRaOVHjXWUWFqchpBqqqUpGj0OyR9z6WTwhnMZGCPHxyviVstiv_2fTG8YOJq8L8zJT2T2OvTebANV-2MQ"],
-        //		"sch_checkin_time" : 1492617610,
-        //		"groupid" : 1,
-        //		"schedule_id" : 0,
-        //		"timeline_id" : 2
-        //	},{
-        //		"userid" : "paul",
-        //		"groupname" : "鎵撳崱浜岀粍",
-        //		"checkin_type" : "澶栧嚭鎵撳崱",
-        //		"exception_type" : "鏃堕棿寮傚父",
-        //		"checkin_time" : 1492617620,
-        //		"location_title" : "閲嶅簡鍑哄彛鍔犲伐鍖�",
-        //		"location_detail" : "閲嶅簡甯傛笣鍖楀尯閲戞笣澶ч亾101鍙烽噾娓濆ぇ閬�",
-        //		"wifiname" : "鍔炲叕瀹や簩鍖�",
-        //		"notes" : "",
-        //		"wifimac" : "3c:46:d8:0c:7a:71",
-        //		"mediaids":["WWCISP_G8PYgRaOVHjXWUWFqchpBqqqUpGj0OyR9z6WTwhnMZGCPHxyviVstiv_2fTG8YOJq8L8zJT2T2OvTebANV-2MQ"],
-        //		"lat": 30547645,
-        //        "lng": 104063236,
-        //		"deviceid":"E5FA89F6-3926-4972-BE4F-4A7ACF4701E2",
-        //		"sch_checkin_time" : 1492617610,
-        //		"groupid" : 2,
-        //		"schedule_id" : 3,
-        //		"timeline_id" : 1
-        //	}]
-        //}
-
-        //checkindata鐨勫瓧娈佃鏄�
-        //userid	鐢ㄦ埛id
-        //groupname	鎵撳崱瑙勫垯鍚嶇О
-        //checkin_type	鎵撳崱绫诲瀷銆傚瓧绗︿覆锛岀洰鍓嶆湁锛氫笂鐝墦鍗★紝涓嬬彮鎵撳崱锛屽鍑烘墦鍗★紝浠呰褰曟墦鍗℃椂闂村拰浣嶇疆
-        //exception_type	寮傚父绫诲瀷锛屽瓧绗︿覆锛屽寘鎷細鏃堕棿寮傚父锛屽湴鐐瑰紓甯革紝鏈墦鍗★紝wifi寮傚父锛岄潪甯哥敤璁惧銆傚鏋滄湁澶氫釜寮傚父锛屼互鍒嗗彿闂撮殧
-        //checkin_time	鎵撳崱鏃堕棿銆俇nix鏃堕棿鎴�
-        //location_title	鎵撳崱鍦扮偣title
-        //location_detail	鎵撳崱鍦扮偣璇︽儏
-        //wifiname	鎵撳崱wifi鍚嶇О
-        //notes	鎵撳崱澶囨敞
-        //wifimac	鎵撳崱鐨凪AC鍦板潃/bssid
-        //mediaids	鎵撳崱鐨勯檮浠秏edia_id锛屽彲浣跨敤media/get鑾峰彇闄勪欢
-        //lat	浣嶇疆鎵撳崱鍦扮偣绾害锛屾槸瀹為檯绾害鐨�1000000鍊嶏紝涓庤吘璁湴鍥句竴鑷撮噰鐢℅CJ-02鍧愭爣绯荤粺鏍囧噯
-        //lng	浣嶇疆鎵撳崱鍦扮偣缁忓害锛屾槸瀹為檯缁忓害鐨�1000000鍊嶏紝涓庤吘璁湴鍥句竴鑷撮噰鐢℅CJ-02鍧愭爣绯荤粺鏍囧噯
-        //deviceid	鎵撳崱璁惧id
-        //sch_checkin_time	鏍囧噯鎵撳崱鏃堕棿锛屾寚姝ゆ鎵撳崱鏃堕棿瀵瑰簲鐨勬爣鍑嗕笂鐝椂闂存垨鏍囧噯涓嬬彮鏃堕棿
-        //groupid	瑙勫垯id锛岃〃绀烘墦鍗¤褰曟墍灞炶鍒欑殑id
-        //schedule_id	鐝id锛岃〃绀烘墦鍗¤褰曟墍灞炶鍒欎腑锛屾墍灞炵彮娆$殑id
-        //timeline_id	鏃舵id锛岃〃绀烘墦鍗¤褰曟墍灞炶鍒欎腑锛屾煇涓�鐝涓殑鏌愪竴鏃舵鐨刬d锛屽涓婁笅鐝椂闂翠负9:00-12:00銆�13:00-18:00鐨勭彮娆′腑锛�9:00-12:00涓哄叾涓竴缁勬椂娈�
-
         try {
             long currentTime = System.currentTimeMillis() / 1000;
             long oneDaySeconds = 86400;
@@ -298,4 +236,171 @@
         System.out.println("=== 娴嬭瘯缁撴潫 ===");
     }
 
+    /**
+     * 娴嬭瘯鑾峰彇鎵撳崱鏃ユ姤鏁版嵁
+     * 鎺ュ彛闄愬埗锛�100娆�/鍒嗛挓锛屾椂闂磋法搴︿笉瓒呰繃30澶�
+     */
+    @Test
+    void testGetCheckinDayData() {
+        System.out.println("=== 寮�濮嬫祴璇曡幏鍙栨墦鍗℃棩鎶ユ暟鎹� ===");
+
+        try {
+            long currentTime = System.currentTimeMillis() / 1000;
+            long oneDaySeconds = 86400;
+
+            // 鑾峰彇鏄ㄥぉ鐨勬棩鎶ユ暟鎹紙starttime鍜宔ndtime闇�瑕佹槸鍚屼竴澶╋級
+            long yesterdayStart = ((currentTime / oneDaySeconds) - 1) * oneDaySeconds;
+            long startTime = yesterdayStart;
+            long endTime = yesterdayStart;  // 鏃ユ姤鎺ュ彛starttime鍜宔ndtime闇�鐩稿悓
+
+            System.out.println("  - 鏌ヨ鏃ユ湡: " + new java.util.Date(startTime * 1000));
+
+            // 鑾峰彇鐢ㄦ埛鎬绘暟
+            long totalUsers = qwStaffMapper.selectCount(new LambdaQueryWrapper<QwStaff>()
+                    .isNotNull(QwStaff::getAccount)
+                    .ne(QwStaff::getAccount, ""));
+            System.out.println("  - QW_STAFF琛ㄧ敤鎴锋�绘暟: " + totalUsers);
+
+            List<WorkWXService.CheckinDayData> dayDataList = workWXService.getCheckinDayDataByQwStaff(startTime, endTime);
+
+            if (dayDataList != null && !dayDataList.isEmpty()) {
+                System.out.println("鉁� 鎴愬姛鑾峰彇鎵撳崱鏃ユ姤鏁版嵁");
+                System.out.println("  - 鏃ユ姤璁板綍鎬绘暟: " + dayDataList.size());
+                System.out.println("  - 鍓�5鏉℃暟鎹�:");
+                dayDataList.stream().limit(5).forEach(data -> {
+                    WorkWXService.BaseInfo baseInfo = data.getBaseInfo();
+                    WorkWXService.SummaryInfo summaryInfo = data.getSummaryInfo();
+                    System.out.println("    * 濮撳悕: " + (baseInfo != null ? baseInfo.getName() : "N/A") +
+                            ", 璐﹀彿: " + (baseInfo != null ? baseInfo.getAcctid() : "N/A") +
+                            ", 閮ㄩ棬: " + (baseInfo != null ? baseInfo.getDepartsName() : "N/A") +
+                            ", 鎵撳崱娆℃暟: " + (summaryInfo != null ? summaryInfo.getCheckinCount() : 0) +
+                            ", 瀹為檯宸ユ椂(绉�): " + (summaryInfo != null ? summaryInfo.getRegularWorkSec() : 0) +
+                            ", 鏍囧噯宸ユ椂(绉�): " + (summaryInfo != null ? summaryInfo.getStandardWorkSec() : 0));
+
+                    // 鎵撳嵃寮傚父淇℃伅
+                    if (data.getExceptionInfos() != null && !data.getExceptionInfos().isEmpty()) {
+                        data.getExceptionInfos().forEach(ex -> {
+                            String exType = switch (ex.getException()) {
+                                case 1 -> "杩熷埌";
+                                case 2 -> "鏃╅��";
+                                case 3 -> "缂哄崱";
+                                case 4 -> "鏃峰伐";
+                                case 5 -> "鍦扮偣寮傚父";
+                                case 6 -> "璁惧寮傚父";
+                                default -> "鏈煡";
+                            };
+                            System.out.println("      寮傚父: " + exType + ", 娆℃暟: " + ex.getCount() + ", 鏃堕暱(绉�): " + ex.getDuration());
+                        });
+                    }
+                });
+            } else {
+                System.out.println("鉁� 鑾峰彇鐨勬墦鍗℃棩鎶ユ暟鎹负绌�");
+            }
+        } catch (IOException e) {
+            System.out.println("鉁� 鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触: " + e.getMessage());
+            e.printStackTrace();
+        }
+
+        System.out.println("=== 娴嬭瘯缁撴潫 ===");
+    }
+
+    /**
+     * 娴嬭瘯鑾峰彇鎸囧畾鍛樺伐鐨勬墦鍗℃棩鎶ユ暟鎹�
+     */
+    @Test
+    void testGetCheckinDayDataById() {
+        System.out.println("=== 寮�濮嬫祴璇曡幏鍙栨寚瀹氬憳宸ユ墦鍗℃棩鎶ユ暟鎹� ===");
+
+        QwStaff qwStaff = qwStaffMapper.selectById(3);
+
+        if (qwStaff == null || qwStaff.getAccount() == null || qwStaff.getAccount().isEmpty()) {
+            System.out.println("鉁� 鏈壘鍒癐D涓�3鐨勫憳宸ユ垨鍛樺伐account涓虹┖");
+            return;
+        }
+
+        System.out.println("  - 鍛樺伐濮撳悕: " + qwStaff.getName());
+        System.out.println("  - 鍛樺伐璐﹀彿: " + qwStaff.getAccount());
+
+        try {
+            long currentTime = System.currentTimeMillis() / 1000;
+            long oneDaySeconds = 86400;
+
+            // 鑾峰彇鏄ㄥぉ鐨勬棩鎶�
+            long yesterdayStart = ((currentTime / oneDaySeconds) - 1) * oneDaySeconds;
+            long startTime = yesterdayStart;
+            long endTime = yesterdayStart;
+
+            System.out.println("  - 鏌ヨ鏃ユ湡: " + new java.util.Date(startTime * 1000));
+
+            List<String> useridList = new java.util.ArrayList<>();
+            useridList.add(qwStaff.getAccount());
+
+            List<WorkWXService.CheckinDayData> dayDataList = workWXService.getCheckinDayData(startTime, endTime, useridList);
+
+            if (dayDataList != null && !dayDataList.isEmpty()) {
+                System.out.println("鉁� 鎴愬姛鑾峰彇鎵撳崱鏃ユ姤鏁版嵁");
+                dayDataList.forEach(data -> {
+                    WorkWXService.BaseInfo baseInfo = data.getBaseInfo();
+                    WorkWXService.SummaryInfo summaryInfo = data.getSummaryInfo();
+
+                    System.out.println("  鍩虹淇℃伅:");
+                    System.out.println("    - 濮撳悕: " + (baseInfo != null ? baseInfo.getName() : "N/A"));
+                    System.out.println("    - 閮ㄩ棬: " + (baseInfo != null ? baseInfo.getDepartsName() : "N/A"));
+                    System.out.println("    - 瑙勫垯: " + (baseInfo != null && baseInfo.getRuleInfo() != null ? baseInfo.getRuleInfo().getGroupname() : "N/A"));
+
+                    System.out.println("  姹囨�讳俊鎭�:");
+                    System.out.println("    - 鎵撳崱娆℃暟: " + (summaryInfo != null ? summaryInfo.getCheckinCount() : 0));
+                    System.out.println("    - 瀹為檯宸ユ椂: " + (summaryInfo != null ? formatSeconds(summaryInfo.getRegularWorkSec()) : "0"));
+                    System.out.println("    - 鏍囧噯宸ユ椂: " + (summaryInfo != null ? formatSeconds(summaryInfo.getStandardWorkSec()) : "0"));
+
+                    if (data.getExceptionInfos() != null && !data.getExceptionInfos().isEmpty()) {
+                        System.out.println("  寮傚父淇℃伅:");
+                        data.getExceptionInfos().forEach(ex -> {
+                            String exType = switch (ex.getException()) {
+                                case 1 -> "杩熷埌";
+                                case 2 -> "鏃╅��";
+                                case 3 -> "缂哄崱";
+                                case 4 -> "鏃峰伐";
+                                case 5 -> "鍦扮偣寮傚父";
+                                case 6 -> "璁惧寮傚父";
+                                default -> "鏈煡";
+                            };
+                            System.out.println("    - " + exType + ": " + ex.getCount() + "娆�, 鏃堕暱: " + formatSeconds(ex.getDuration()));
+                        });
+                    }
+
+                    if (data.getOtInfo() != null && data.getOtInfo().getOtStatus() != null && data.getOtInfo().getOtStatus() > 0) {
+                        System.out.println("  鍔犵彮淇℃伅:");
+                        System.out.println("    - 鍔犵彮鏃堕暱: " + formatSeconds(data.getOtInfo().getOtDuration()));
+                    }
+                });
+            } else {
+                System.out.println("鉁� 璇ュ憳宸ュ湪鎸囧畾鏃ユ湡鏃犳墦鍗℃棩鎶ユ暟鎹�");
+            }
+        } catch (IOException e) {
+            System.out.println("鉁� 鑾峰彇鎵撳崱鏃ユ姤鏁版嵁澶辫触: " + e.getMessage());
+            e.printStackTrace();
+        }
+
+        System.out.println("=== 娴嬭瘯缁撴潫 ===");
+    }
+
+    /**
+     * 鏍煎紡鍖栫鏁颁负鏃跺垎绉�
+     */
+    private String formatSeconds(Integer seconds) {
+        if (seconds == null || seconds == 0) {
+            return "0绉�";
+        }
+        int hours = seconds / 3600;
+        int minutes = (seconds % 3600) / 60;
+        int secs = seconds % 60;
+
+        StringBuilder sb = new StringBuilder();
+        if (hours > 0) sb.append(hours).append("灏忔椂");
+        if (minutes > 0) sb.append(minutes).append("鍒嗛挓");
+        if (secs > 0) sb.append(secs).append("绉�");
+        return sb.toString();
+    }
+
 }

--
Gitblit v1.9.3