procedure TDingtalkController.GetAttendanceRecords; var UserDataSet: TDataSet; UserIds: TList<string>; Cursor, PageSize, BatchIndex, UserIndex: Integer; RestClient: IMVCRESTClient; Response: IMVCRESTResponse; AccessToken: string; RequestBody, ResponseJSON: TJsonObject; UserIdArray, RecordArray: TJsonArray; StartDate, EndDate: string; NextCursor: Integer; AttendanceList: TObjectList<TAttendanceRecord>; Attendance: TAttendanceRecord; HasMoreRecords: Boolean; Serializer: TMVCJsonDataObjectsSerializer; begin ActiveRecordConnectionsRegistry.AddConnection('attendanceDB', 'DB'); try ActiveRecordConnectionsRegistry.SetCurrent('attendanceDB'); UserIds := TList<string>.Create; try // 获取所有用户ID UserDataSet := TMVCActiveRecord.SelectDataSet('SELECT userid FROM dingtalk_userlist', [], True); try while not UserDataSet.Eof do begin UserIds.Add(Trim(UserDataSet.FieldByName('userid').AsString)); UserDataSet.Next; end; finally UserDataSet.Free; end; // 设置全天查询范围 (00:00:00 - 23:59:59) StartDate := FormatDateTime('yyyy-mm-dd', Now) + ' 00:00:00'; EndDate := FormatDateTime('yyyy-mm-dd', Now) + ' 23:59:59'; // 分批次处理用户(每批50人) BatchIndex := 0; while BatchIndex < UserIds.Count do begin // 创建当前批次的用户ID数组 UserIdArray := TJsonArray.Create; try // 添加当前批次用户 for UserIndex := BatchIndex to Min(BatchIndex + 49, UserIds.Count - 1) do if not UserIds[UserIndex].IsEmpty then UserIdArray.Add(UserIds[UserIndex]); Cursor := 0; // 重置游标 PageSize := 50; // 分页大小 HasMoreRecords := True; TMVCActiveRecord.CurrentConnection.StartTransaction; try while HasMoreRecords do begin RequestBody := TJsonObject.Create; try // 构建请求体 RequestBody.S['workDateFrom'] := StartDate; RequestBody.S['workDateTo'] := EndDate; RequestBody.I['offset'] := Cursor; RequestBody.I['limit'] := PageSize; RequestBody.A['userIdList'] := UserIdArray.Clone; // 正确的数组格式 logd(RequestBody); // 获取访问令牌 AccessToken := Context.Data['GetAccessToken']; // 发送API请求 RestClient := TMVCRESTClient.New.BaseURL(DINGTALK_BASE_URL); Response := RestClient.AddQueryStringParam('access_token', AccessToken).Post('/attendance/list', RequestBody); // 检查响应状态 if not ParseResponse(Response) then Break; // logw('Response.Content:' + Response.Content); // 解析响应数据 Serializer := TMVCJsonDataObjectsSerializer.Create; try ResponseJSON := Serializer.ParseObject(Response.Content); try RecordArray := ResponseJSON.A['recordresult']; // logd(RecordArray); if RecordArray.Count > 0 then begin AttendanceList := TObjectList<TAttendanceRecord>.Create(False); try // 反序列化JSON数据 Serializer.JsonArrayToList(RecordArray, WrapAsList(AttendanceList), TAttendanceRecord, TMVCSerializationType.stProperties, nil); // 批量插入记录 for Attendance in AttendanceList do begin Attendance.PrimaryKeyIsAutogenerated := False; Attendance.Insert; // logd(Attendance); end; finally AttendanceList.Free; end; end; // 检查是否还有更多数据 HasMoreRecords := ResponseJSON.O['result'].B['has_more']; if HasMoreRecords then NextCursor := ResponseJSON.O['result'].I['next_cursor'] else NextCursor := -1; finally ResponseJSON.Free; end; finally Serializer.Free; end; // 更新游标 Cursor := NextCursor; if NextCursor <= 0 then Break; finally //RequestBody.Free; end; end; TMVCActiveRecord.CurrentConnection.Commit; // 提交整个批次的事务 except on E: Exception do begin TMVCActiveRecord.CurrentConnection.Rollback; LogE('Error processing batch: ' + E.Message); raise; end; end; finally UserIdArray.Free; end; // 准备处理下一批用户 BatchIndex := BatchIndex + 50; if BatchIndex < UserIds.Count then Sleep(200); // 添加延迟避免请求过快 end; finally UserIds.Free; end; finally ActiveRecordConnectionsRegistry.RemoveConnection('attendanceDB'); end; end;
unit Entity.Dingtalk.Attendance_records; interface uses MVCFramework.Serializer.Commons, MVCFramework.ActiveRecord, System.Classes; type [MVCNameCase(ncCamelCase)] //必须反序列化成指定格式 [MVCTable('attendance_records')] TAttendanceRecord = class(TMVCActiveRecord) private [MVCTableField('id', [foPrimaryKey, foAutoGenerated])] fID: Int64; [MVCTableField('checkType')] fChecktype: string; [MVCTableField('corpId')] fCorpid: string; [MVCTableField('locationResult')] fLocationresult: string; [MVCTableField('baseCheckTime')] fBasechecktime: Int64; [MVCTableField('groupId')] fGroupid: Int64; [MVCTableField('timeResult')] fTimeresult: string; [MVCTableField('userId')] fUserid: string; [MVCTableField('workDate')] fWorkdate: Int64; [MVCTableField('sourceType')] fSourcetype: string; [MVCTableField('userCheckTime')] fUserchecktime: Int64; [MVCTableField('planId')] fPlanid: Int64; public constructor Create; override; destructor Destroy; override; [MVCNameAs('id')] property ID: Int64 read fID write fID; [MVCNameAs('checkType')] property Checktype: string read fChecktype write fChecktype; [MVCNameAs('corpId')] property Corpid: string read fCorpid write fCorpid; [MVCNameAs('locationResult')] property Locationresult: string read fLocationresult write fLocationresult; [MVCNameAs('baseCheckTime')] property Basechecktime: Int64 read fBasechecktime write fBasechecktime; [MVCNameAs('groupId')] property Groupid: Int64 read fGroupid write fGroupid; [MVCNameAs('timeResult')] property Timeresult: string read fTimeresult write fTimeresult; [MVCNameAs('userId')] property Userid: string read fUserid write fUserid; [MVCNameAs('workDate')] property Workdate: Int64 read fWorkdate write fWorkdate; [MVCNameAs('sourceType')] property Sourcetype: string read fSourcetype write fSourcetype; [MVCNameAs('userCheckTime')] property Userchecktime: Int64 read fUserchecktime write fUserchecktime; [MVCNameAs('planId')] property Planid: Int64 read fPlanid write fPlanid; end; implementation constructor TAttendanceRecord.Create; begin inherited Create; end; destructor TAttendanceRecord.Destroy; begin inherited; end; initialization ActiveRecordMappingRegistry.AddEntity('attendance_records', TAttendanceRecord); end.
中年大叔学Delphi
浙公网安备 33010602011771号