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