package com.gs.xky.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.gs.xky.config.DataAcquisitionConfiguration; import com.gs.xky.entity.QwStaff; import com.gs.xky.mapper.QwStaffMapper; import lombok.Data; import lombok.RequiredArgsConstructor; import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class WorkWXService { private static final Logger log = LoggerFactory.getLogger(WorkWXService.class); private final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(90, TimeUnit.SECONDS) .readTimeout(90, TimeUnit.SECONDS) .build(); private final ObjectMapper objectMapper = new ObjectMapper(); private final QwStaffMapper qwStaffMapper; public String getAccessToken() throws IOException { String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", DataAcquisitionConfiguration.CORPID, DataAcquisitionConfiguration.CORPSECRET); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { log.error("获取企业微信access_token失败,HTTP状态码: {}", response.code()); throw new IOException("获取access_token失败: " + response.message()); } String responseBody = response.body().string(); WorkWXTokenResponse tokenResponse = objectMapper.readValue(responseBody, WorkWXTokenResponse.class); if (tokenResponse.getErrcode() != 0) { log.error("获取企业微信access_token失败,错误码: {}, 错误信息: {}", tokenResponse.getErrcode(), tokenResponse.getErrmsg()); throw new IOException("获取access_token失败: " + tokenResponse.getErrmsg()); } log.info("成功获取企业微信access_token,有效期: {}秒", tokenResponse.getExpiresIn()); log.info("access_token : {}", tokenResponse.getAccessToken()); return tokenResponse.getAccessToken(); } } public String getContactAccessToken() throws IOException { String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", DataAcquisitionConfiguration.CORPID, DataAcquisitionConfiguration.TXL_CORPSECRET); Request request = new Request.Builder() .url(url) .get() .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { log.error("获取企业微信通讯录access_token失败,HTTP状态码: {}", response.code()); throw new IOException("获取通讯录access_token失败: " + response.message()); } String responseBody = response.body().string(); WorkWXTokenResponse tokenResponse = objectMapper.readValue(responseBody, WorkWXTokenResponse.class); if (tokenResponse.getErrcode() != 0) { log.error("获取企业微信通讯录access_token失败,错误码: {}, 错误信息: {}", tokenResponse.getErrcode(), tokenResponse.getErrmsg()); throw new IOException("获取通讯录access_token失败: " + tokenResponse.getErrmsg()); } log.info("成功获取企业微信通讯录access_token,有效期: {}秒", tokenResponse.getExpiresIn()); return tokenResponse.getAccessToken(); } } public List getUserList() throws IOException { String accessToken = getContactAccessToken(); String url = String.format( "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s", accessToken); List allUsers = new ArrayList<>(); String cursor = null; do { Map requestBody = new HashMap<>(); requestBody.put("limit", 10000); if (cursor != null) { requestBody.put("cursor", cursor); } 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(); log.info("获取用户列表响应: {}", responseBody); WorkWXUserListIdResponse userListResponse = objectMapper.readValue(responseBody, WorkWXUserListIdResponse.class); if (userListResponse.getErrcode() != 0) { log.error("获取企业微信用户列表失败,错误码: {}, 错误信息: {}", userListResponse.getErrcode(), userListResponse.getErrmsg()); throw new IOException("获取用户列表失败: " + userListResponse.getErrmsg()); } if (userListResponse.getDeptUser() != null && !userListResponse.getDeptUser().isEmpty()) { allUsers.addAll(userListResponse.getDeptUser()); } cursor = userListResponse.getNextCursor(); } catch (IOException e) { log.error("解析用户列表响应失败", e); throw e; } } while (cursor != null && !cursor.isEmpty()); log.info("成功获取企业微信用户列表,用户数量: {}", allUsers.size()); return allUsers; } public List getCheckinDataByQwStaff(long startTime, long endTime) throws IOException { List qwStaffList = qwStaffMapper.selectList(new LambdaQueryWrapper()); if (qwStaffList == null || qwStaffList.isEmpty()) { log.warn("QW_STAFF表中没有数据"); return new ArrayList<>(); } List 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 getCheckinData(startTime, endTime, useridList); } public WorkWXUserDetail getUserDetail(String userid) throws IOException { String accessToken = getContactAccessToken(); String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userid); Request request = new Request.Builder() .url(url) .get() .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(); log.info("获取用户详情响应: {}", responseBody); WorkWXUserDetailResponse userDetailResponse = objectMapper.readValue(responseBody, WorkWXUserDetailResponse.class); if (userDetailResponse.getErrcode() != 0) { log.error("获取用户详情失败,错误码: {}, 错误信息: {}", userDetailResponse.getErrcode(), userDetailResponse.getErrmsg()); throw new IOException("获取用户详情失败: " + userDetailResponse.getErrmsg()); } log.info("成功获取用户 {} 的详情", userid); return userDetailResponse; } } public List getCheckinData(long startTime, long endTime, List useridList) throws IOException { String accessToken = getAccessToken(); String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token=%s", accessToken); List 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 batchUserList = useridList.subList(fromIndex, toIndex); log.info("正在获取第 {}/{} 批打卡数据,用户数: {}", i + 1, batchCount, batchUserList.size()); Map requestBody = new HashMap<>(); requestBody.put("opencheckindatatype", 3); 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(); WorkWXCheckinResponse checkinResponse = objectMapper.readValue(responseBody, WorkWXCheckinResponse.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()); } } if (i < batchCount - 1) { try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.warn("批次间等待被中断"); } } } log.info("打卡数据获取完成,总记录数: {}", allCheckinData.size()); return allCheckinData; } @Data private static class WorkWXTokenResponse { private Integer errcode; private String errmsg; @JsonProperty("access_token") private String accessToken; @JsonProperty("expires_in") private Integer expiresIn; } @Data private static class WorkWXUserIdResponse { private Integer errcode; private String errmsg; private String userid; } @Data private static class WorkWXUserListIdResponse { private Integer errcode; private String errmsg; @JsonProperty("next_cursor") private String nextCursor; @JsonProperty("dept_user") private List deptUser; } @Data public static class WorkWXUser { private String userid; private Integer department; private String name; @JsonProperty("open_userid") private String openUserid; } @Data public static class WorkWXUserDetail { private Integer errcode; private String errmsg; private String userid; private String name; private String mobile; private String position; } @Data private static class WorkWXUserDetailResponse extends WorkWXUserDetail { } @Data private static class WorkWXCheckinResponse { private Integer errcode; private String errmsg; private List checkindata; } @Data public static class CheckinData { private String userid; private String groupname; @JsonProperty("checkin_type") private String checkinType; @JsonProperty("exception_type") private String exceptionType; @JsonProperty("checkin_time") private Long checkinTime; @JsonProperty("location_title") private String locationTitle; @JsonProperty("location_detail") private String locationDetail; private String wifiname; private String notes; private String wifimac; private String mediaids; private Double lat; private Double lng; @JsonProperty("sch_checkin_time") private Long schCheckinTime; private Integer groupid; @JsonProperty("schedule_id") private Integer scheduleId; @JsonProperty("timeline_id") private Integer timelineId; private String deviceid; } }