| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.gs.dingtalk.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.gs.dingtalk.config.DataAcquisitionConfiguration; |
| | | import com.gs.dingtalk.entity.QwStaff; |
| | | import com.gs.dingtalk.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<WorkWXUser> 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<WorkWXUser> allUsers = new ArrayList<>(); |
| | | String cursor = null; |
| | | |
| | | do { |
| | | Map<String, Object> 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<CheckinData> getCheckinDataByQwStaff(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 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<CheckinData> getCheckinData(long startTime, long endTime, List<String> useridList) throws IOException { |
| | | String accessToken = getAccessToken(); |
| | | String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token=%s", accessToken); |
| | | |
| | | List<CheckinData> 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("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<WorkWXUser> 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> 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; |
| | | } |
| | | } |