tjx
11 小时以前 aba1ce5635af560d69b4e3adcf6ecdd025cea8fb
获取设备打卡数据
已修改1个文件
已添加5个文件
360 ■■■■■ 文件已修改
src/main/java/com/gs/dingtalk/entity/QwHardwareCheckinData.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/QwHardwareCheckinDataMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/QwHardwareCheckinDataService.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/WorkWXService.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/QwHardwareCheckinDataServiceImpl.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/QwHardwareCheckinDataMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
    /**
     * æ‰“卡时间(Unix时间戳)
     */
    private Long checkinTime;
    /**
     * æ‰“卡时间(转换后的日期时间)
     */
    private Date checkinDatetime;
    /**
     * æ‰“卡设备序列号
     */
    private String deviceSn;
    /**
     * æ‰“卡设备名称
     */
    private String deviceName;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
}
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> {
}
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> {
    /**
     * å°†HardwareCheckinData转换为QwHardwareCheckinData实体
     *
     * @param data ä¼ä¸šå¾®ä¿¡è®¾å¤‡æ‰“卡数据
     * @return QwHardwareCheckinData实体
     */
    QwHardwareCheckinData convertToEntity(WorkWXService.HardwareCheckinData data);
    /**
     * æ‰¹é‡ä¿å­˜è®¾å¤‡æ‰“卡数据(自动去重,按userid+checkin_time+device_sn)
     *
     * @param dataList è®¾å¤‡æ‰“卡数据列表
     * @return æ–°å¢žè®°å½•æ•°
     */
    int saveHardwareDataBatch(List<WorkWXService.HardwareCheckinData> dataList);
    /**
     * åŒæ­¥æŒ‡å®šæ—¶é—´èŒƒå›´çš„设备打卡数据到数据库
     *
     * @param startTime å¼€å§‹æ—¶é—´ï¼ˆUnix时间戳,秒)
     * @param endTime   ç»“束时间(Unix时间戳,秒)
     * @return æ–°å¢žè®°å½•æ•°
     * @throws IOException èŽ·å–è®¾å¤‡æ‰“å¡æ•°æ®å¼‚å¸¸
     */
    int syncHardwareData(long startTime, long endTime) throws IOException;
    /**
     * åŒæ­¥æ˜¨å¤©çš„设备打卡数据到数据库
     *
     * @return æ–°å¢žè®°å½•æ•°
     * @throws IOException èŽ·å–è®¾å¤‡æ‰“å¡æ•°æ®å¼‚å¸¸
     */
    int syncYesterdayHardwareData() throws IOException;
}
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("正在获取第 {}/{} æ‰¹è®¾å¤‡æ‰“卡数据,用户数: {}", i + 1, batchCount, batchUserList.size());
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("filter_type", 1);  // 1表示按userid过滤
            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("获取设备打卡数据失败,HTTP状态码: {}", 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表中没有有效的account数据");
            return new ArrayList<>();
        }
        log.info("从QW_STAFF表获取到 {} ä¸ªç”¨æˆ·account", useridList.size());
        return getHardwareCheckinData(startTime, endTime, useridList);
    }
}
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);
    }
}
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>