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 int syncUsersToQwStaff() throws IOException {
|
List<WorkWXUser> userList = getUserList();
|
|
if (userList == null || userList.isEmpty()) {
|
log.warn("获取到的用户列表为空");
|
return 0;
|
}
|
|
int insertCount = 0;
|
for (WorkWXUser user : userList) {
|
if (user.getUserid() == null || user.getUserid().isEmpty()) {
|
continue;
|
}
|
|
qwStaffMapper.delete(
|
new LambdaQueryWrapper<QwStaff>().eq(QwStaff::getAccount, user.getUserid())
|
);
|
|
QwStaff qwStaff = new QwStaff();
|
qwStaff.setName(user.getName());
|
qwStaff.setAccount(user.getUserid());
|
qwStaff.setDept(user.getDepartment() != null ? user.getDepartment().toString() : null);
|
|
qwStaffMapper.insert(qwStaff);
|
insertCount++;
|
}
|
|
log.info("同步用户到QW_STAFF表完成,同步用户数: {}", insertCount);
|
return insertCount;
|
}
|
|
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;
|
}
|
|
/**
|
* 获取打卡日报数据
|
* 接口调用频率限制为100次/分钟
|
* @param startTime 开始时间戳(秒)
|
* @param endTime 结束时间戳(秒)
|
* @param useridList 用户ID列表
|
* @return 打卡日报数据列表
|
*/
|
public List<CheckinDayData> getCheckinDayData(long startTime, long endTime, List<String> useridList) throws IOException {
|
String accessToken = getAccessToken();
|
String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=%s", accessToken);
|
|
List<CheckinDayData> allDayData = 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("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();
|
|
WorkWXCheckinDayDataResponse dayDataResponse = objectMapper.readValue(responseBody, WorkWXCheckinDayDataResponse.class);
|
|
if (dayDataResponse.getErrcode() != 0) {
|
log.error("获取打卡日报数据失败,错误码: {}, 错误信息: {}",
|
dayDataResponse.getErrcode(), dayDataResponse.getErrmsg());
|
throw new IOException("获取打卡日报数据失败: " + dayDataResponse.getErrmsg());
|
}
|
|
if (dayDataResponse.getDatas() != null) {
|
allDayData.addAll(dayDataResponse.getDatas());
|
log.info("第 {}/{} 批获取到打卡日报记录数: {}", i + 1, batchCount, dayDataResponse.getDatas().size());
|
}
|
}
|
|
// 接口限制100次/分钟,批次间等待600ms确保不超限
|
if (i < batchCount - 1) {
|
try {
|
Thread.sleep(600);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
log.warn("批次间等待被中断");
|
}
|
}
|
}
|
|
log.info("打卡日报数据获取完成,总记录数: {}", allDayData.size());
|
return allDayData;
|
}
|
|
/**
|
* 根据QW_STAFF表获取打卡日报数据
|
*/
|
public List<CheckinDayData> getCheckinDayDataByQwStaff(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 getCheckinDayData(startTime, endTime, useridList);
|
}
|
|
//获取打卡日报数据
|
//请求方式: POST(HTTPS)
|
//请求地址: https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckin_daydata?access_token=ACCESS_TOKEN
|
//参数示例
|
// {
|
// "starttime": 1599062400,
|
// "endtime": 1599062400,
|
// "useridlist": [
|
// "ZhangSan"
|
// ]
|
//}
|
//接口调用频率限制为100次/分钟。
|
//返回结果
|
//{
|
// "errcode":0,
|
// "errmsg":"ok",
|
// "datas":[
|
// {
|
// "base_info":{
|
// "date":1599062400,
|
// "record_type":1,
|
// "name":"张三",
|
// "name_ex":"Three Zhang",
|
// "departs_name":"有家企业/realempty;有家企业;有家企业/部门A4",
|
// "acctid":"ZhangSan",
|
// "rule_info":{
|
// "groupid":10,
|
// "groupname":"规则测试",
|
// "scheduleid":0,
|
// "schedulename":"",
|
// "checkintime":[
|
// {
|
// "work_sec":38760,
|
// "off_work_sec":38880
|
// }
|
// ]
|
// },
|
// "day_type":0
|
// },
|
// "summary_info":{
|
// "checkin_count":2,
|
// "regular_work_sec":31,
|
// "standard_work_sec":120,
|
// "earliest_time":38827,
|
// "lastest_time":38858
|
// },
|
// "holiday_infos":[
|
// {
|
// "sp_description":{
|
// "data":[
|
// {
|
// "lang":"zh_CN",
|
// "text":"09/03 10:00~09/03 10:01"
|
// }
|
// ]
|
// },
|
// "sp_number":"202009030002",
|
// "sp_title":{
|
// "data":[
|
// {
|
// "lang":"zh_CN",
|
// "text":"请假0.1小时"
|
// }
|
// ]
|
// }
|
// },
|
// {
|
// "sp_description":{
|
// "data":[
|
// {
|
// "lang":"zh_CN",
|
// "text":"08/25 14:37~09/10 14:37"
|
// }
|
// ]
|
// },
|
// "sp_number":"202008270004",
|
// "sp_title":{
|
// "data":[
|
// {
|
// "lang":"zh_CN",
|
// "text":"加班17.0小时"
|
// }
|
// ]
|
// }
|
// }
|
// ],
|
// "exception_infos":[
|
// {
|
// "count":1,
|
// "duration":60,
|
// "exception":1
|
// },
|
// {
|
// "count":1,
|
// "duration":60,
|
// "exception":2
|
// }
|
// ],
|
// "ot_info":{
|
// "ot_status":1,
|
// "ot_duration":3600,
|
// "exception_duration":[],
|
// "workday_over_as_money": 54000
|
// },
|
// "sp_items":[
|
// {
|
// "count":1,
|
// "duration":360,
|
// "time_type":0,
|
// "type":1,
|
// "vacation_id":2,
|
// "name":"年假"
|
// },
|
// {
|
// "count":0,
|
// "duration":0,
|
// "time_type":0,
|
// "type":100,
|
// "vacation_id":0,
|
// "name":"外勤次数"
|
// }
|
// ]
|
// }
|
// ]
|
//}
|
//参数说明:
|
//errcode int32 返回码
|
//errmsg string 错误码描述
|
//datas obj[] 日报数据列表
|
//datas.base_info obj 基础信息
|
//datas.base_info.date uint32 日报日期
|
//datas.base_info.record_type uint32 记录类型:1-固定上下班;2-外出(此报表中不会出现外出打卡数据);3-按班次上下班;4-自由签到;5-加班;7-无规则
|
//datas.base_info.name string 打卡人员姓名
|
//datas.base_info.name_ex string 打卡人员别名
|
//datas.base_info.departs_name string 打卡人员所在部门,会显示所有所在部门
|
//datas.base_info.acctid string 打卡人员账号,即userid
|
//datas.base_info.rule_info obj 打卡人员所属规则信息
|
//datas.base_info.rule_info.groupid int32 所属规则的id
|
//datas.base_info.rule_info.groupname string 打卡规则名
|
//datas.base_info.rule_info.scheduleid int32 当日所属班次id,仅按班次上下班才有值,显示在打卡日报-班次列
|
//datas.base_info.rule_info.schedulename string 当日所属班次名称,仅按班次上下班才有值,显示在打卡日报-班次列
|
//datas.base_info.rule_info.checkintime obj[] 当日打卡时间,仅固定上下班规则有值,显示在打卡日报-班次列
|
//datas.base_info.rule_info.checkintime.work_sec uint32 上班时间,为距离0点的时间差
|
//datas.base_info.rule_info.checkintime.off_work_sec uint32 下班时间,为距离0点的时间差
|
//datas.base_info.day_type uint32 日报类型:0-工作日日报;1-休息日日报
|
//datas.summary_info obj 汇总信息
|
//datas.summary_info.checkin_count int32 当日打卡次数
|
//datas.summary_info.regular_work_sec int32 当日实际工作时长,单位:秒
|
//datas.summary_info.standard_work_sec int32 当日标准工作时长,单位:秒
|
//datas.summary_info.earliest_time int32 当日最早打卡时间
|
//datas.summary_info.lastest_time int32 当日最晚打卡时间
|
//datas.holiday_infos obj[] 假勤相关信息
|
//datas.holiday_infos.sp_number string 假勤申请id,即当日关联的假勤审批单id
|
//datas.holiday_infos.sp_title obj 假勤信息摘要-标题信息
|
//datas.holiday_infos.sp_title.data obj[] 多种语言描述,目前只有中文一种
|
//datas.holiday_infos.sp_title.data.text string 假勤信息摘要-标题文本
|
//datas.holiday_infos.sp_title.data.lang string 语言类型:"zh_CN"
|
//datas.holiday_infos.sp_description obj 假勤信息摘要-描述信息
|
//datas.holiday_infos.sp_description.data obj[] 多种语言描述,目前只有中文一种
|
//datas.holiday_infos.sp_description.data.text string 假勤信息摘要-描述文本
|
//datas.holiday_infos.sp_description.data.lang string 语言类型:"zh_CN"
|
//datas.exception_infos obj[] 校准状态信息
|
//datas.exception_infos.exception uint32 校准状态类型:1-迟到;2-早退;3-缺卡;4-旷工;5-地点异常;6-设备异常
|
//datas.exception_infos.count int32 当日此异常的次数
|
//datas.exception_infos.duration int32 当日此异常的时长(迟到/早退/旷工才有值)
|
//datas.ot_info obj 加班信息
|
//datas.ot_info.ot_status uint32 状态:0-无加班;1-正常;2-缺时长
|
//datas.ot_info.ot_duration uint32 加班时长
|
//datas.ot_info.exception_duration uint32[] ot_status为2下,加班不足的时长
|
//datas.ot_info.workday_over_as_vacation int32 工作日加班记为调休,单位秒
|
//datas.ot_info.workday_over_as_money int32 工作日加班记为加班费,单位秒
|
//datas.ot_info.restday_over_as_vacation int32 休息日加班记为调休,单位秒
|
//datas.ot_info.restday_over_as_money int32 休息日加班记为加班费,单位秒
|
//datas.ot_info.holiday_over_as_vacation int32 节假日加班记为调休,单位秒
|
//datas.ot_info.holiday_over_as_money int32 节假日加班记为加班费,单位秒
|
//datas.sp_items obj[] 假勤统计信息
|
//datas.sp_items.type uint32 类型:1-请假;2-补卡;3-出差;4-外出;15-审批打卡;100-外勤
|
//datas.sp_items.vacation_id uint32 具体请假类型,当type为1请假时,具体的请假类型id,可通过审批相关接口获取假期详情
|
//datas.sp_items.count uint32 当日假勤次数
|
//datas.sp_items.duration uint32 当日假勤时长秒数,时长单位为天直接除以86400即为天数,单位为小时直接除以3600即为小时数
|
//datas.sp_items.time_type uint32 时长单位:0-按天 1-按小时
|
//datas.sp_items.name string 统计项名称
|
|
@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
|
private static class WorkWXCheckinDayDataResponse {
|
private Integer errcode;
|
private String errmsg;
|
private List<CheckinDayData> datas;
|
}
|
|
@Data
|
public static class CheckinDayData {
|
@JsonProperty("base_info")
|
private BaseInfo baseInfo;
|
@JsonProperty("summary_info")
|
private SummaryInfo summaryInfo;
|
@JsonProperty("holiday_infos")
|
private List<HolidayInfo> holidayInfos;
|
@JsonProperty("exception_infos")
|
private List<ExceptionInfo> exceptionInfos;
|
@JsonProperty("ot_info")
|
private OtInfo otInfo;
|
@JsonProperty("sp_items")
|
private List<SpItem> spItems;
|
}
|
|
@Data
|
public static class BaseInfo {
|
private Long date;
|
@JsonProperty("record_type")
|
private Integer recordType;
|
private String name;
|
@JsonProperty("name_ex")
|
private String nameEx;
|
@JsonProperty("departs_name")
|
private String departsName;
|
private String acctid;
|
@JsonProperty("rule_info")
|
private RuleInfo ruleInfo;
|
@JsonProperty("day_type")
|
private Integer dayType;
|
}
|
|
@Data
|
public static class RuleInfo {
|
private Integer groupid;
|
private String groupname;
|
private Integer scheduleid;
|
private String schedulename;
|
private List<CheckinTime> checkintime;
|
}
|
|
@Data
|
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
|
public static class CheckinTime {
|
@JsonProperty("work_sec")
|
private Integer workSec;
|
@JsonProperty("off_work_sec")
|
private Integer offWorkSec;
|
}
|
|
@Data
|
public static class SummaryInfo {
|
@JsonProperty("checkin_count")
|
private Integer checkinCount;
|
@JsonProperty("regular_work_sec")
|
private Integer regularWorkSec;
|
@JsonProperty("standard_work_sec")
|
private Integer standardWorkSec;
|
@JsonProperty("earliest_time")
|
private Integer earliestTime;
|
@JsonProperty("lastest_time")
|
private Integer lastestTime;
|
}
|
|
@Data
|
public static class HolidayInfo {
|
@JsonProperty("sp_number")
|
private String spNumber;
|
@JsonProperty("sp_title")
|
private LangData spTitle;
|
@JsonProperty("sp_description")
|
private LangData spDescription;
|
}
|
|
@Data
|
public static class LangData {
|
private List<LangText> data;
|
}
|
|
@Data
|
public static class LangText {
|
private String lang;
|
private String text;
|
}
|
|
@Data
|
public static class ExceptionInfo {
|
private Integer exception; // 1-迟到;2-早退;3-缺卡;4-旷工;5-地点异常;6-设备异常
|
private Integer count;
|
private Integer duration;
|
}
|
|
@Data
|
public static class OtInfo {
|
@JsonProperty("ot_status")
|
private Integer otStatus; // 0-无加班;1-正常;2-缺时长
|
@JsonProperty("ot_duration")
|
private Integer otDuration;
|
@JsonProperty("exception_duration")
|
private List<Integer> exceptionDuration;
|
@JsonProperty("workday_over_as_vacation")
|
private Integer workdayOverAsVacation;
|
@JsonProperty("workday_over_as_money")
|
private Integer workdayOverAsMoney;
|
@JsonProperty("restday_over_as_vacation")
|
private Integer restdayOverAsVacation;
|
@JsonProperty("restday_over_as_money")
|
private Integer restdayOverAsMoney;
|
@JsonProperty("holiday_over_as_vacation")
|
private Integer holidayOverAsVacation;
|
@JsonProperty("holiday_over_as_money")
|
private Integer holidayOverAsMoney;
|
}
|
|
@Data
|
public static class SpItem {
|
private Integer type; // 1-请假;2-补卡;3-出差;4-外出;15-审批打卡;100-外勤
|
@JsonProperty("vacation_id")
|
private Integer vacationId;
|
private Integer count;
|
private Integer duration;
|
@JsonProperty("time_type")
|
private Integer timeType; // 0-按天 1-按小时
|
private String name;
|
}
|
|
// ==================== 打卡原始数据结构 ====================
|
|
@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 List<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;
|
}
|
}
|