關注點:
- 1、釘釘AccessToken的獲取和防止過期
- 2、使用TPL並行編程調用釘釘接口
需求詳解
公司前台有個大屏,領導想顯示全部員工的考勤結果統計情況和車間的實時監控視頻,還有車間的看板。簡單說就是把大屏分割成幾個區域。現在遇到的難題是釘釘獲取考勤結果的api是只有明細記錄,比如你公司1000人,那么可能回給你2000條考勤結果。分別是上班考勤和下班考勤的。沒有整個公司的,我就需要這么一條數據就行了。但人家沒有這樣的接口提供。卷起袖子,干!
趟坑過程
考勤打卡數據開放
業務場景
該接口僅限企業接入使用,用於返回企業內員工的實際打卡結果。比如,企業給一個員工設定的排班是上午9點和下午6點各打一次卡,即使員工在這期間打了多次,該接口也只會返回兩條記錄,包括上午的打卡結果和下午的打卡結果
考勤打卡數據開放
請求說明(ISV無調用權限)
如果你是ISV(應用服務商,將開發的應用上架到釘釘應用市場,提供給釘釘其他企業用戶使用),則無調用權限
如果你是企業內部開發者(將自己公司的HR、OA、客戶管理、業務管理等系統接入釘釘),有權限調用
2017-10-16更新:新增用戶userId列表參數(userIdList)和分頁參數(offset,limit),提升接口穩定性。
Https請求方式: POST
https://oapi.dingtalk.com/attendance/list?access_token=ACCESS_TOKEN
請求包結構體
1
2
3
4
5
6
7
|
{
"workDateFrom"
:
"yyyy-MM-dd hh:mm:ss"
,
"workDateTo"
:
"yyyy-MM-dd hh:mm:ss"
,
"userIdList"
:[
"員工UserId列表"
],
// 必填,與offset和limit配合使用,不傳表示分頁獲取全員的數據
"offset"
:
0
,
// 必填,第一次傳0,如果還有多余數據,下次傳之前的offset加上limit的值
"limit"
:
1
,
// 必填,表示數據條數,最大不能超過50條
}
|
參數說明
參數 | 參數類型 | 必須 | 說明 |
---|---|---|---|
access_token | String | 是 | 調用接口憑證 |
workDateFrom | String | 是 | 查詢考勤打卡記錄的起始工作日 |
workDateTo | String | 是 | 查詢考勤打卡記錄的結束工作日 |
userIdList | List | 是 | 員工在企業內的UserID列表,企業用來唯一標識用戶的字段 |
offset | Long | 是 | 表示獲取考勤數據的起始點,第一次傳0,如果還有多余數據,下次獲取傳的offset值為之前的offset+limit |
limit | Long | 是 | 表示獲取考勤數據的條數,最大不能超過50條 |
1)獲取AccessToken
釘釘的服務器很牛X可以並行訪問的。但是獲取回來的數據集有點亂,其中有個關於分頁的參數需要順序讀取,就是“hasMore”還有沒有下一頁。所以最終沒有使用TPL。
/// <summary> /// 獲取AccessToken /// </summary> /// <returns></returns> private AccessToken GetAccessToken() { string URL_GetToken = "https://oapi.dingtalk.com/gettoken?corpid={0}&corpsecret={1}"; string fileName = AppDomain.CurrentDomain.BaseDirectory + "AccessToken.xml"; if (!System.IO.File.Exists(fileName)) { string respon = Program.HttpGetRequest(string.Format(URL_GetToken, Program.CorpId, Program.CorpSecret)); DingTalkRespond obj = JsonConvert.DeserializeObject<DingTalkRespond>(respon); if (obj != null && obj.errcode == 0) { var result = new AccessToken { access_token = obj.access_token, expires_in = GetCurrentTimeStamp() }; SerializerHelper.SerializerToXML(fileName, result); return result; } else return new AccessToken { access_token = obj.access_token, expires_in = GetCurrentTimeStamp() }; } else { var fileResult = SerializerHelper.LoadFromXML<AccessToken>(fileName); long ts = (GetCurrentTimeStamp() - fileResult.expires_in) / 1000; if (ts >= 7200) { string respon = Program.HttpGetRequest(string.Format(URL_GetToken, Program.CorpId, Program.CorpSecret)); DingTalkRespond obj = JsonConvert.DeserializeObject<DingTalkRespond>(respon); if (obj != null && obj.errcode == 0) { fileResult.access_token = obj.access_token; fileResult.expires_in = GetCurrentTimeStamp(); SerializerHelper.SerializerToXML(fileName, fileResult); } } return fileResult; } }
2)獲取企業總人數
/// <summary> /// 獲取公司總人數 /// </summary> /// <returns></returns> private int GetFactoryEmployeeCount(AccessToken token) { int result = 0; string url = string.Format("https://oapi.dingtalk.com/user/get_org_user_count?access_token={0}&onlyActive=0", token.access_token); string jsonObj = Program.HttpGetRequest(url); var objResult = JsonConvert.DeserializeObject<DingTalkRespond>(jsonObj); if (objResult != null && objResult.errcode == 0) result = objResult.count; return result; }
3)獲取全體員工的考勤結果
/// <summary> /// 獲取指定頁的考勤結果 /// </summary> /// <param name="offset"></param> /// <returns></returns> private void GetAttendance(AccessToken token) { EmployeeAttendanceList.Clear(); string url = string.Format("https://oapi.dingtalk.com/attendance/list?access_token={0}", token.access_token); int offset = 0; bool bHasMore = true; while (bHasMore) { var request = new AttendanceRequest { workDateFrom = DateTime.Now.ToString("yyyy-MM-dd") + " 00:00:00", workDateTo = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), limit = 50, userIdList = null, offset = offset * 50 }; string jsonRequest = JsonConvert.SerializeObject(request).Replace("null", "[]"); string jsonResult = Program.PostJsonData(url, jsonRequest); var objResult = JsonConvert.DeserializeObject<DingTalkRespond>(jsonResult); if (objResult != null && objResult.errcode == 0) { foreach (var item in objResult.recordresult) { if (!EmployeeAttendanceList.Any(a => a.id == item.id)) EmployeeAttendanceList.Add(item); } offset++; bHasMore = objResult.hasMore; } else break; } EmployeeAttendanceList = EmployeeAttendanceList.Where(a => a.checkType == "OnDuty").OrderBy(e => e.id).ToList(); }
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { AccessToken token = GetAccessToken(); GetAttendance(token); var report = from q in EmployeeAttendanceList group q by q.timeResult into g select new AttendanceReportItemDTO { TimeResult = g.Key, PersonNumber = g.Count() }; BulletinBoardInit(); #region 考勤統計 attendanceReport.Total = GetFactoryEmployeeCount(token); var normalObj = report.Where(p => p.TimeResult == "正常").FirstOrDefault(); attendanceReport.Normal = normalObj == null ? 0 : normalObj.PersonNumber; var lateObj = report.Where(p => p.TimeResult == "遲到").FirstOrDefault(); attendanceReport.Late = lateObj == null ? 0 : lateObj.PersonNumber; var earlyObj = report.Where(p => p.TimeResult == "早退").FirstOrDefault(); attendanceReport.Early = earlyObj == null ? 0 : earlyObj.PersonNumber; var seriousLateObj = report.Where(p => p.TimeResult == "嚴重遲到").FirstOrDefault(); attendanceReport.SeriousLate = seriousLateObj == null ? 0 : seriousLateObj.PersonNumber; var absenteeismObj = report.Where(p => p.TimeResult == "曠工").FirstOrDefault(); attendanceReport.Absenteeism = absenteeismObj == null ? 0 : absenteeismObj.PersonNumber; int total_temp = report.Sum(p => p.PersonNumber); var notSignedObj = report.Where(p => p.TimeResult == "未打卡").FirstOrDefault(); attendanceReport.NotSigned = notSignedObj == null ? 0 : notSignedObj.PersonNumber; attendanceReport.NotSigned = attendanceReport.NotSigned + (attendanceReport.Total - total_temp); #endregion #region 判斷有沒有公告信息 if (BulletinImages != null && BulletinImages.Length > 0) { if (BulletinImages.Length == 1) { pbxNotity.Image = Image.FromFile(BulletinImages[0]); } if (BulletinImages.Length > 1) { if (notifyShowIndex == BulletinImages.Length - 1) { notifyShowIndex = 0; } pbxNotity.Image = Image.FromFile(BulletinImages[notifyShowIndex]); notifyShowIndex++; } } else { Action task = () => { BulletinBoardInit(); }; task.BeginInvoke(null, null); string dir = AppDomain.CurrentDomain.BaseDirectory + "公告欄圖片\\"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } string[] bgFiles = Directory.GetFiles(dir); if (bgFiles != null && bgFiles.Length > 0) { int imgIndex = rd.Next(bgFiles.Length); pbxNotity.BackgroundImage = Image.FromFile(bgFiles[imgIndex]); } } #endregion }