当前位置: 首页 > backend >正文

钉钉补卡事件处理方案

文章提供了两种方案, 及相关消息用例供大家参考

  1. 通过员工打卡事件实现
  2. 通过审批实例开始, 结束 结合审批单详情接口实现

注: 下方操作的应用类型为企业内部应用开发

image-20250829091709198
一, 员工打卡事件实现

首先需要订阅员工打卡事件, 如图

在这里插入图片描述

钉钉 Stream 模式SpringBoot接入配置与事件监听_钉钉stream依赖添加-CSDN博客

平台对员工打卡事件的描述文档如下:
员工打卡事件文档

官方推送消息示例(data部分):

{"eventId": "3b15cb8159204bfe9e60f4f276fdc29c","dataList": [{"checkTime": 1756083600000,"corpId": "dinged281b9ee2134221a39a90f97fcb1e09","locationResult": "Normal","groupId": "8901A759EB795D24C8A1FBEA24FE5CC7","bizId": "23244E2D4ADBBC2BBFA475EBA6303735","locationMethod": "OTHER","checkByUser": true,"userId": "02253726682920171716"}]
}

通过官方推送的消息可以知道打卡(补卡)时间checkTime (也就可以知道是上班还是下班), 可知道用户userId, 知道修改的状态locationResult, 通过这三个值即可对补卡做处理

注:

这里有一个问题, 这里会把所有的消息都推送过来, 打卡, 补卡等, 所以比较消耗Webhook 和 Stream 用量一个月只有5000次, 可以根据实际情况算一下, 如果够用, 这种方案肯定最简便

二.通过审批实例开始, 结束事件 和 审批单详情接口实现

首先需要订阅审批实例开始,结束, 如图

在这里插入图片描述

然后通过, 上一篇文章中介绍的接入进行配置及开发,后面就是处理消息的部分, 根据自己的业务逻辑就行处理即可

钉钉 Stream 模式SpringBoot接入配置与事件监听_钉钉stream依赖添加-CSDN博客

官方说明: 审批实例开始、结束、终止、删除钉钉文档地址

官方推送消息示例(data部分):

{"processInstanceId": "qWU68--WSIasZMhIzLHxuA04431756109057","eventId": "eff61099a455461b81b3d9229e980b33","finishTime": 1756110624000,"resource": "/v1.0/event/bpms_instance_change/bizCategoryId/attendance.supply/processCode/PROC-8A8DFDCF-12B4-4CC7-9983-A91DD2F1D2D0/type/finish","businessId": "202508251604000176498","title": "大帅哥提交的补卡申请","type": "finish","url": "https://aflow.dingtalk.com/dingtalk/mobile/homepage.htm?corpid=dinged281b9ee2134221a39a90f97fcb1e09&dd_share=false&showmenu=false&dd_progress=false&back=native&procInstId=qWU68--WSIasZMhIzLHxuA04431756109057&taskId=&swfrom=isv&dinghash=approval&dtaction=os&dd_from=#approval","result": "agree","createTime": 1756109058000,"processCode": "PROC-8A8DFDCF-12B4-4CC7-9983-A91DD2F1D2D0","bizCategoryId": "attendance.supply","staffId": "02253726682920171716"
}

当看到这个json的时候可以看到没有明确的打卡时间, 经查阅可以通过审批单ID.再调用审批单详情接口来获取 补卡时间, 如下:

官方文档地址:
获取单个审批实例详情

java代码如下:

    /*** 获取钉钉工作流客户端** @return 钉钉工作流客户端*/public static com.aliyun.dingtalkworkflow_1_0.Client createClient2() {com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();config.protocol = "https";config.regionId = "central";try {return new com.aliyun.dingtalkworkflow_1_0.Client(config);} catch (Exception e) {log.error("初始化钉钉工作流Client 失败, 原因: {}", e.getMessage());}return null;}    /*** 获取补卡时间** @return 补卡时间 yyyy-MM-dd HH:mm*/public String getReplacementCardDate(String processInstanceId) {com.aliyun.dingtalkworkflow_1_0.Client client = createClient2();if (Objects.isNull(client)) {return null;}com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken();com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest().setProcessInstanceId(processInstanceId);try {GetProcessInstanceResponse processInstanceWithOptions = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new RuntimeOptions());GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result = processInstanceWithOptions.getBody().getResult();log.info("事件标题:{}", result.getTitle());GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues dateFieldComponentValue = new GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues();for (GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues formComponentValue : result.getFormComponentValues()) {if ("DDDateField".equals(formComponentValue.getComponentType())) {dateFieldComponentValue = formComponentValue;break;}}// 补卡时间// 时间格式: yyyy-MM-dd HH:mm  示例: 2025-08-25 12:00return dateFieldComponentValue.getValue();} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("code:{},  message:{}", err.code, err.message);}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("code:{}, message:{}", err.code, err.message);}}return null;}

现在就可以知道, 补卡时间示例: 2025-08-25 09:00, 用户ID, 然后做修改补卡操作

这里可能会问一个问题, 为什么一定是成功, 按照逻辑来说, 只要能提交补卡审批, 就说明还有补交次数, 只要系统能收到消息, 就说明肯定的是成功了, 这是其一, 其二就是可以看到通过"result": "agree",这个参数判断, 表示通过, 通过则状态就是Normal.


最后还有一点需要注意和修改的, 可以指定订阅的地址, 因为钉钉中的审批单有很多很多, 如果没有配置, 会接收到很多暂时没有用的数据(审批单), 同时也会消耗Webhook 和 Stream 用量, 所以可以通过通配符, 或者指定所需审批中的确定事件

补卡事件订阅地址:

/v1.0/event/bpms_instance_change/bizCategoryId/attendance.supply/processCode/{processCode}/type/finish// 这里只订阅通过的事件

事件订阅的匹配规则采用Glob语法进行模式匹配可以在图片中的5点击查阅, 或点击下方链接

事件订阅的匹配规则

在这里插入图片描述

具体的可以在官方文档中查看

在这里插入图片描述


附:

自定义工作类:

package com.gkl.attendance.utils.aliyun;import com.aliyun.dingtalkcontact_1_0.models.SearchUserResponse;
import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponse;
import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.teautil.models.RuntimeOptions;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiAttendanceListRequest;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.response.OapiAttendanceListResponse;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.List;
import java.util.Objects;/*** @author : Cookie* date : 2025/8/22* desc: 钉钉API工具类*/
@Slf4j
@Component
public class DingDingAPIUtils {@Value("${dingtalk.appKey}")private String appKey;@Value("${dingtalk.appSecret}")private String appSecret;/*** 通过appKey和appSecret获取access_token** @return access_token*/public String getAccessToken() {try {DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");OapiGettokenRequest req = new OapiGettokenRequest();req.setAppkey(appKey);req.setAppsecret(appSecret);req.setHttpMethod("GET");OapiGettokenResponse rsp = client.execute(req);return rsp.getAccessToken();} catch (ApiException e) {log.error(e.getErrMsg());}return null;}/*** 获取指定时间打卡结果** @param dingDingIdList 钉钉用户ID 列表* @param checkDateFrom  开始日期* @param checkDateTo    结束日期*/public OapiAttendanceListResponse getCheckRecord(List<String> dingDingIdList, String checkDateFrom, String checkDateTo) {DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/attendance/list");OapiAttendanceListRequest req = new OapiAttendanceListRequest();req.setWorkDateFrom(checkDateFrom);req.setWorkDateTo(checkDateTo);req.setUserIdList(dingDingIdList);req.setOffset(0L);req.setLimit(50L);try {OapiAttendanceListResponse rsp = client.execute(req, getAccessToken());log.info("获取考勤记录响应结果:{}", rsp.getBody());return rsp;} catch (ApiException e) {log.error("获取考勤记录失败, 原因: {}", e.getErrMsg());throw new RuntimeException(e);}}/*** 获取钉钉连接客户端** @return 钉钉连接客户端*/public com.aliyun.dingtalkcontact_1_0.Client createClient() {com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();config.protocol = "https";config.regionId = "central";try {return new com.aliyun.dingtalkcontact_1_0.Client(config);} catch (Exception e) {log.error("初始化钉钉Client 失败, 原因: {}", e.getMessage());}return null;}/*** 根据用户昵称模糊/精确查询钉钉用户ID列表** @param nickName 用户昵称(模糊/精确匹配)* @return 钉钉用户ID列表,如果查询失败返回 null*/public List<String> getDingIdListByNickName(String nickName) {com.aliyun.dingtalkcontact_1_0.Client client = createClient();if (Objects.isNull(client)) {return Collections.emptyList();}com.aliyun.dingtalkcontact_1_0.models.SearchUserHeaders searchUserHeaders =new com.aliyun.dingtalkcontact_1_0.models.SearchUserHeaders();// 设置 AccessTokensearchUserHeaders.xAcsDingtalkAccessToken = getAccessToken();// 构造查询请求com.aliyun.dingtalkcontact_1_0.models.SearchUserRequest searchUserRequest =new com.aliyun.dingtalkcontact_1_0.models.SearchUserRequest().setQueryWord(nickName).setOffset(1).setSize(10).setFullMatchField(1);try {SearchUserResponse searchUserResponse =client.searchUserWithOptions(searchUserRequest, searchUserHeaders, new RuntimeOptions());log.info("钉钉用户昵称查询结果:{}", searchUserResponse.getBody().getList());return searchUserResponse.getBody().getList();} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("通过用户名获取用户钉钉ID 失败, code:{}, message:{}", err.code, err.message);}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("通过用户名获取用户钉钉ID失败, code:{}, message:{}", err.code, err.message);}}return Collections.emptyList();}/*** 获取钉钉工作流客户端** @return 钉钉工作流客户端*/public static com.aliyun.dingtalkworkflow_1_0.Client createClient2() {com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();config.protocol = "https";config.regionId = "central";try {return new com.aliyun.dingtalkworkflow_1_0.Client(config);} catch (Exception e) {log.error("初始化钉钉工作流Client 失败, 原因: {}", e.getMessage());}return null;}/*** 获取补卡时间** @return 补卡时间*/public String getReplacementCardDate(String processInstanceId) {com.aliyun.dingtalkworkflow_1_0.Client client = createClient2();if (Objects.isNull(client)) {return null;}com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();getProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken();com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest().setProcessInstanceId(processInstanceId);try {GetProcessInstanceResponse processInstanceWithOptions = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new RuntimeOptions());GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult result = processInstanceWithOptions.getBody().getResult();log.info("事件标题:{}", result.getTitle());GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues dateFieldComponentValue = new GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues();for (GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues formComponentValue : result.getFormComponentValues()) {if ("DDDateField".equals(formComponentValue.getComponentType())) {dateFieldComponentValue = formComponentValue;break;}}// 补卡时间// 时间格式: yyyy-MM-dd HH:mm  示例: 2025-08-25 12:00return dateFieldComponentValue.getValue();} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("code:{},  message:{}", err.code, err.message);}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {log.error("code:{}, message:{}", err.code, err.message);}}return null;}
}
http://www.xdnf.cn/news/19051.html

相关文章:

  • 算法---字符串
  • FDTD_mie散射_仿真学习(2)
  • 【机器人概念设计软件操作手册】 建模技巧与最佳实践
  • 自适应RAG架构:智能检索增强生成的演进与实现
  • 前端如何使用canvas实现截图
  • Python OpenCV图像处理与深度学习:Python OpenCV入门-图像处理基础
  • Linux之Docker虚拟化技术(二)
  • Mysql系列--11、使用c/c++访问mysql服务
  • 软件安装教程(二):Pycharm安装与配置(Windows)
  • DeepSeek大模型风靡云平台,百度智能云、阿里云、腾讯云等多个平台宣布上线DeepSeek模型
  • java_web 日志配置
  • 瑞芯微RK3576开发板Android14三屏异显开发教程
  • 【项目思维】通过编写一个贪吃蛇小程序,并移植到嵌入式设备上,解析编程思维的本质
  • SAP-ABAP:SAP 数值显示格式控制:负号前置方法与最佳实践总结
  • 一般纳税人
  • JavaScript 数组核心操作实战:最值获取与排序实现(从基础到优化)
  • CSS text-decoration-thickness:精细控制文本装饰线粗细的新属性
  • 光学设计中干涉现象难预测?OAS 软件多结构干涉来解决
  • Word文档怎么打印?Word打印技巧?【图文详解】单面/双面/指定页面/逆序等Word打印选项
  • Linux学习——sqlite3
  • 【系列01】端侧AI:构建与部署高效的本地化AI模型
  • 【Linux】Make/Makefile (自动化构建):从“是什么”到“会用它”
  • 软考-系统架构设计师 专家系统(ES)详细讲解
  • Azure、RDP、NTLM 均现高危漏洞,微软发布2025年8月安全更新
  • PlotJuggler如何安装和使用
  • AI 自动化编程 trae 体验3 开发小程序
  • (Nginx)基于Nginx+PHP 驱动 Web 应用(上):配置文件与虚拟主机篇
  • 网络编程(2)—多客户端交互
  • Uniapp + UView + FastAdmin 性格测试小程序方案
  • Qt类-扩充_xiaozuo