From aba1ce5635af560d69b4e3adcf6ecdd025cea8fb Mon Sep 17 00:00:00 2001
From: tjx <t2856754968@163.com>
Date: 星期三, 24 十二月 2025 18:39:25 +0800
Subject: [PATCH] 获取设备打卡数据

---
 src/main/java/com/gs/dingtalk/service/WorkWXService.java                         |  132 ++++++++++++++++++++++
 src/main/java/com/gs/dingtalk/mapper/QwHardwareCheckinDataMapper.java            |   12 ++
 src/main/java/com/gs/dingtalk/service/impl/QwHardwareCheckinDataServiceImpl.java |  107 +++++++++++++++++
 src/main/java/com/gs/dingtalk/service/QwHardwareCheckinDataService.java          |   47 +++++++
 src/main/resources/mapper/QwHardwareCheckinDataMapper.xml                        |    7 +
 src/main/java/com/gs/dingtalk/entity/QwHardwareCheckinData.java                  |   55 +++++++++
 6 files changed, 360 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/gs/dingtalk/entity/QwHardwareCheckinData.java b/src/main/java/com/gs/dingtalk/entity/QwHardwareCheckinData.java
new file mode 100644
index 0000000..b93e60a
--- /dev/null
+++ b/src/main/java/com/gs/dingtalk/entity/QwHardwareCheckinData.java
@@ -0,0 +1,55 @@
+package com.gs.dingtalk.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 浼佷笟寰俊璁惧鎵撳崱鏁版嵁
+ * @TableName QW_HARDWARE_CHECKIN_DATA
+ */
+@TableName(value = "QW_HARDWARE_CHECKIN_DATA")
+@Data
+@KeySequence(value = "SEQ_QW_HARDWARE_CHECKIN", dbType = DbType.ORACLE)
+public class QwHardwareCheckinData implements Serializable {
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 鐢ㄦ埛id
+     */
+    private String userid;
+
+    /**
+     * 鎵撳崱鏃堕棿锛圲nix鏃堕棿鎴筹級
+     */
+    private Long checkinTime;
+
+    /**
+     * 鎵撳崱鏃堕棿锛堣浆鎹㈠悗鐨勬棩鏈熸椂闂达級
+     */
+    private Date checkinDatetime;
+
+    /**
+     * 鎵撳崱璁惧搴忓垪鍙�
+     */
+    private String deviceSn;
+
+    /**
+     * 鎵撳崱璁惧鍚嶇О
+     */
+    private String deviceName;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    private Date createTime;
+}
diff --git a/src/main/java/com/gs/dingtalk/mapper/QwHardwareCheckinDataMapper.java b/src/main/java/com/gs/dingtalk/mapper/QwHardwareCheckinDataMapper.java
new file mode 100644
index 0000000..1a67960
--- /dev/null
+++ b/src/main/java/com/gs/dingtalk/mapper/QwHardwareCheckinDataMapper.java
@@ -0,0 +1,12 @@
+package com.gs.dingtalk.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.gs.dingtalk.entity.QwHardwareCheckinData;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 浼佷笟寰俊璁惧鎵撳崱鏁版嵁Mapper
+ */
+@Mapper
+public interface QwHardwareCheckinDataMapper extends BaseMapper<QwHardwareCheckinData> {
+}
diff --git a/src/main/java/com/gs/dingtalk/service/QwHardwareCheckinDataService.java b/src/main/java/com/gs/dingtalk/service/QwHardwareCheckinDataService.java
new file mode 100644
index 0000000..5e859fa
--- /dev/null
+++ b/src/main/java/com/gs/dingtalk/service/QwHardwareCheckinDataService.java
@@ -0,0 +1,47 @@
+package com.gs.dingtalk.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.gs.dingtalk.entity.QwHardwareCheckinData;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 浼佷笟寰俊璁惧鎵撳崱鏁版嵁Service
+ */
+public interface QwHardwareCheckinDataService extends IService<QwHardwareCheckinData> {
+
+    /**
+     * 灏咹ardwareCheckinData杞崲涓篞wHardwareCheckinData瀹炰綋
+     *
+     * @param data 浼佷笟寰俊璁惧鎵撳崱鏁版嵁
+     * @return QwHardwareCheckinData瀹炰綋
+     */
+    QwHardwareCheckinData convertToEntity(WorkWXService.HardwareCheckinData data);
+
+    /**
+     * 鎵归噺淇濆瓨璁惧鎵撳崱鏁版嵁锛堣嚜鍔ㄥ幓閲嶏紝鎸塽serid+checkin_time+device_sn锛�
+     *
+     * @param dataList 璁惧鎵撳崱鏁版嵁鍒楄〃
+     * @return 鏂板璁板綍鏁�
+     */
+    int saveHardwareDataBatch(List<WorkWXService.HardwareCheckinData> dataList);
+
+    /**
+     * 鍚屾鎸囧畾鏃堕棿鑼冨洿鐨勮澶囨墦鍗℃暟鎹埌鏁版嵁搴�
+     *
+     * @param startTime 寮�濮嬫椂闂达紙Unix鏃堕棿鎴筹紝绉掞級
+     * @param endTime   缁撴潫鏃堕棿锛圲nix鏃堕棿鎴筹紝绉掞級
+     * @return 鏂板璁板綍鏁�
+     * @throws IOException 鑾峰彇璁惧鎵撳崱鏁版嵁寮傚父
+     */
+    int syncHardwareData(long startTime, long endTime) throws IOException;
+
+    /**
+     * 鍚屾鏄ㄥぉ鐨勮澶囨墦鍗℃暟鎹埌鏁版嵁搴�
+     *
+     * @return 鏂板璁板綍鏁�
+     * @throws IOException 鑾峰彇璁惧鎵撳崱鏁版嵁寮傚父
+     */
+    int syncYesterdayHardwareData() throws IOException;
+}
diff --git a/src/main/java/com/gs/dingtalk/service/WorkWXService.java b/src/main/java/com/gs/dingtalk/service/WorkWXService.java
index 5163e6d..99a8ac5 100644
--- a/src/main/java/com/gs/dingtalk/service/WorkWXService.java
+++ b/src/main/java/com/gs/dingtalk/service/WorkWXService.java
@@ -825,4 +825,136 @@
         private Integer timelineId;
         private String deviceid;
     }
+
+    // ==================== 璁惧鎵撳崱鏁版嵁缁撴瀯 ====================
+
+    @Data
+    private static class WorkWXHardwareCheckinResponse {
+        private Integer errcode;
+        private String errmsg;
+        private List<HardwareCheckinData> checkindata;
+    }
+
+    @Data
+    public static class HardwareCheckinData {
+        private String userid;
+        @JsonProperty("checkin_time")
+        private Long checkinTime;
+        @JsonProperty("device_sn")
+        private String deviceSn;
+        @JsonProperty("device_name")
+        private String deviceName;
+    }
+
+    /**
+     * 鑾峰彇璁惧鎵撳崱鏁版嵁
+     * 鎺ュ彛鏂囨。: https://developer.work.weixin.qq.com/document/path/94126
+     * 1. 鑾峰彇璁板綍鏃堕棿璺ㄥ害涓嶈秴杩囦竴涓湀
+     * 2. 鐢ㄦ埛鍒楄〃涓嶈秴杩�100涓紝鑻ョ敤鎴疯秴杩�100涓紝璇峰垎鎵硅幏鍙�
+     *
+     * @param startTime  寮�濮嬫椂闂存埑锛堢锛�
+     * @param endTime    缁撴潫鏃堕棿鎴筹紙绉掞級
+     * @param useridList 鐢ㄦ埛ID鍒楄〃
+     * @return 璁惧鎵撳崱鏁版嵁鍒楄〃
+     */
+    public List<HardwareCheckinData> getHardwareCheckinData(long startTime, long endTime, List<String> useridList) throws IOException {
+        String accessToken = getAccessToken();
+        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/hardware/get_hardware_checkin_data?access_token=%s", accessToken);
+
+        List<HardwareCheckinData> allCheckinData = 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("filter_type", 1);  // 1琛ㄧず鎸塽serid杩囨护
+            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();
+
+                WorkWXHardwareCheckinResponse checkinResponse = objectMapper.readValue(responseBody, WorkWXHardwareCheckinResponse.class);
+
+                if (checkinResponse.getErrcode() != 0) {
+                    log.error("鑾峰彇璁惧鎵撳崱鏁版嵁澶辫触锛岄敊璇爜: {}, 閿欒淇℃伅: {}",
+                            checkinResponse.getErrcode(), checkinResponse.getErrmsg());
+                    throw new IOException("鑾峰彇璁惧鎵撳崱鏁版嵁澶辫触: " + checkinResponse.getErrmsg());
+                }
+
+                if (checkinResponse.getCheckindata() != null) {
+                    allCheckinData.addAll(checkinResponse.getCheckindata());
+                    log.info("绗� {}/{} 鎵硅幏鍙栧埌璁惧鎵撳崱璁板綍鏁�: {}", i + 1, batchCount, checkinResponse.getCheckindata().size());
+                }
+            }
+
+            // 鎵规闂寸瓑寰�500ms锛岄伩鍏嶈姹傝繃浜庨绻�
+            if (i < batchCount - 1) {
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    log.warn("鎵规闂寸瓑寰呰涓柇");
+                }
+            }
+        }
+
+        log.info("璁惧鎵撳崱鏁版嵁鑾峰彇瀹屾垚锛屾�昏褰曟暟: {}", allCheckinData.size());
+        return allCheckinData;
+    }
+
+    /**
+     * 鏍规嵁QW_STAFF琛ㄨ幏鍙栬澶囨墦鍗℃暟鎹�
+     *
+     * @param startTime 寮�濮嬫椂闂存埑锛堢锛�
+     * @param endTime   缁撴潫鏃堕棿鎴筹紙绉掞級
+     * @return 璁惧鎵撳崱鏁版嵁鍒楄〃
+     */
+    public List<HardwareCheckinData> getHardwareCheckinDataByQwStaff(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 getHardwareCheckinData(startTime, endTime, useridList);
+    }
 }
diff --git a/src/main/java/com/gs/dingtalk/service/impl/QwHardwareCheckinDataServiceImpl.java b/src/main/java/com/gs/dingtalk/service/impl/QwHardwareCheckinDataServiceImpl.java
new file mode 100644
index 0000000..b87aea8
--- /dev/null
+++ b/src/main/java/com/gs/dingtalk/service/impl/QwHardwareCheckinDataServiceImpl.java
@@ -0,0 +1,107 @@
+package com.gs.dingtalk.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.gs.dingtalk.entity.QwHardwareCheckinData;
+import com.gs.dingtalk.mapper.QwHardwareCheckinDataMapper;
+import com.gs.dingtalk.service.QwHardwareCheckinDataService;
+import com.gs.dingtalk.service.WorkWXService;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 浼佷笟寰俊璁惧鎵撳崱鏁版嵁Service瀹炵幇
+ */
+@Service
+@RequiredArgsConstructor
+public class QwHardwareCheckinDataServiceImpl extends ServiceImpl<QwHardwareCheckinDataMapper, QwHardwareCheckinData>
+        implements QwHardwareCheckinDataService {
+
+    private static final Logger log = LoggerFactory.getLogger(QwHardwareCheckinDataServiceImpl.class);
+
+    private final WorkWXService workWXService;
+
+    @Override
+    public QwHardwareCheckinData convertToEntity(WorkWXService.HardwareCheckinData data) {
+        if (data == null) {
+            return null;
+        }
+
+        QwHardwareCheckinData entity = new QwHardwareCheckinData();
+        entity.setUserid(data.getUserid());
+        entity.setCheckinTime(data.getCheckinTime());
+        if (data.getCheckinTime() != null) {
+            entity.setCheckinDatetime(new Date(data.getCheckinTime() * 1000));
+        }
+        entity.setDeviceSn(data.getDeviceSn());
+        entity.setDeviceName(data.getDeviceName());
+        entity.setCreateTime(new Date());
+
+        return entity;
+    }
+
+    @Override
+    public int saveHardwareDataBatch(List<WorkWXService.HardwareCheckinData> dataList) {
+        if (dataList == null || dataList.isEmpty()) {
+            return 0;
+        }
+
+        int insertCount = 0;
+        for (WorkWXService.HardwareCheckinData data : dataList) {
+            QwHardwareCheckinData entity = convertToEntity(data);
+            if (entity == null || entity.getUserid() == null || entity.getCheckinTime() == null) {
+                continue;
+            }
+
+            // 妫�鏌ユ槸鍚﹀凡瀛樺湪锛堟寜userid + checkin_time + device_sn鍘婚噸锛�
+            LambdaQueryWrapper<QwHardwareCheckinData> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(QwHardwareCheckinData::getUserid, entity.getUserid())
+                    .eq(QwHardwareCheckinData::getCheckinTime, entity.getCheckinTime())
+                    .eq(QwHardwareCheckinData::getDeviceSn, entity.getDeviceSn());
+
+            QwHardwareCheckinData existing = this.getOne(wrapper);
+            if (existing != null) {
+                // 宸插瓨鍦ㄥ垯鏇存柊
+                entity.setId(existing.getId());
+                this.updateById(entity);
+                log.debug("鏇存柊璁惧鎵撳崱鏁版嵁: userid={}, time={}, device={}",
+                        entity.getUserid(), entity.getCheckinDatetime(), entity.getDeviceSn());
+            } else {
+                // 涓嶅瓨鍦ㄥ垯鎻掑叆
+                this.save(entity);
+                insertCount++;
+                log.debug("鏂板璁惧鎵撳崱鏁版嵁: userid={}, time={}, device={}",
+                        entity.getUserid(), entity.getCheckinDatetime(), entity.getDeviceSn());
+            }
+        }
+
+        log.info("璁惧鎵撳崱鏁版嵁淇濆瓨瀹屾垚锛屾柊澧�: {}, 鎬诲鐞�: {}", insertCount, dataList.size());
+        return insertCount;
+    }
+
+    @Override
+    public int syncHardwareData(long startTime, long endTime) throws IOException {
+        List<WorkWXService.HardwareCheckinData> dataList = workWXService.getHardwareCheckinDataByQwStaff(startTime, endTime);
+        return saveHardwareDataBatch(dataList);
+    }
+
+    @Override
+    public int syncYesterdayHardwareData() throws IOException {
+        long currentTime = System.currentTimeMillis() / 1000;
+        long oneDaySeconds = 86400;
+        // 鏄ㄥぉ0鐐�
+        long yesterdayStart = ((currentTime / oneDaySeconds) - 1) * oneDaySeconds;
+        // 鏄ㄥぉ23:59:59
+        long yesterdayEnd = yesterdayStart + oneDaySeconds - 1;
+
+        log.info("寮�濮嬪悓姝ユ槰澶�({} ~ {})鐨勮澶囨墦鍗℃暟鎹�",
+                new Date(yesterdayStart * 1000), new Date(yesterdayEnd * 1000));
+        return syncHardwareData(yesterdayStart, yesterdayEnd);
+    }
+}
diff --git a/src/main/resources/mapper/QwHardwareCheckinDataMapper.xml b/src/main/resources/mapper/QwHardwareCheckinDataMapper.xml
new file mode 100644
index 0000000..76696ac
--- /dev/null
+++ b/src/main/resources/mapper/QwHardwareCheckinDataMapper.xml
@@ -0,0 +1,7 @@
+<?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.gs.dingtalk.mapper.QwHardwareCheckinDataMapper">
+
+     
+
+</mapper>

--
Gitblit v1.9.3