三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

钉钉待办API迁移实战:从旧版接口升级到新版待办任务接口

钉钉待办API迁移实战:从旧版接口升级到新版待办任务接口

1. 项目缘起:从“旧”到“新”的待办接口迁移之痛

最近在重构一个内部任务协同系统,核心功能之一就是自动将系统内的任务同步到钉钉待办,方便团队成员在钉钉里统一查看和处理。这个功能原本跑得好好的,用的是钉钉开放平台提供的“创建待办”接口。直到上周,测试同学突然反馈,所有新创建的待办任务在钉钉App里都看不到了,但接口调用却返回成功。我心里咯噔一下,知道该来的终于来了——钉钉的待办接口升级了。

这其实不是个例,如果你也在用钉钉的待办API,很可能已经遇到了类似问题。钉钉官方已经逐步将旧版待办接口(/topapi/workrecord/add)迁移至新版待办任务接口(/v1.0/todo/tasks)。旧接口虽然目前还能调用成功,但创建的任务可能无法在客户端正常展示,属于“静默失效”。对于依赖此功能的应用来说,这是个必须立刻解决的“暗礁”。今天,我就结合这次迁移实战,把新版待办任务接口的调用细节、避坑要点以及如何平滑过渡,一次性讲透。无论你是Java、Python还是其他语言的开发者,这篇文章都能帮你快速搞定这个升级。

2. 新旧接口对比:不仅仅是URL变了

在动手改代码之前,我们必须先搞清楚新旧两套接口到底有哪些不同。这不仅仅是换个请求地址那么简单,其设计理念和数据结构都有显著差异。理解这些差异,是避免后续踩坑的关键。

2.1 旧版接口:简单直接的任务记录

旧版接口topapi/workrecord/add的设计更偏向于“工作记录”或“任务提醒”。它的核心字段相对简单:

  • userid: 接收任务的员工ID。
  • create_time: 任务创建时间。
  • title: 任务标题。
  • url: 点击任务后跳转的链接。
  • formItemList: 一个可选的表单列表,用于展示任务的一些附加信息(如:内容、优先级等)。

它的工作流程是:创建一个任务记录,然后钉钉会向对应用户发送一条待办通知。这个接口的权限校验依赖于微应用的管理员权限,调用相对直接。

2.2 新版接口:面向协同的待办任务体系

新版接口v1.0/todo/tasks则属于“钉钉待办”这个更独立、功能更丰富的产品体系。它的设计更加精细和强大:

  1. 独立的权限体系:调用新版接口,必须使用“待办”应用所对应的AppKey和AppSecret,而不再是旧版那个微应用的凭证。这是第一个,也是最重要的不同点。你需要在钉钉开放平台,为你的应用开通“待办”能力,并获取对应的凭证。
  2. 更丰富的任务模型
    • 执行者(executorIds):一个任务可以指定多个执行者,支持协同处理。
    • 参与者(participantIds):可以设置任务的关注者或参与者,他们能看到任务但未必需要处理。
    • 详情页(detailUrl):任务卡片本身的跳转链接。
    • 操作栏(actionList):可以自定义任务卡片下方的操作按钮,例如“完成”、“转交”、“评论”等,每个按钮可以绑定不同的跳转链接(bizCallBack)。
    • 来源信息(sourceId, source):用于标识任务来自哪个外部系统,便于归类和同步。
    • 截止时间(dueTime)与提醒时间(reminder):支持更精细的时间管理。
  3. 不同的API网关和域名:新版接口通常通过https://api.dingtalk.com这个网关进行调用,而旧版接口可能走的是oapi.dingtalk.com。HTTP方法也从旧的POST变成了新的POST,但路径和参数结构完全不同。

为了更直观,我将核心差异整理成了下表:

特性维度旧版接口 (/topapi/workrecord/add)新版接口 (/v1.0/todo/tasks)影响与注意事项
权限凭证微应用的AppKey/Secret或企业自建应用的SuiteKey/Secret必须使用“待办”应用的AppKey/Secret迁移第一步:创建待办应用并获取新凭证。旧凭证完全失效。
API网关oapi.dingtalk.comapi.dingtalk.com代码中请求的基地址需要修改。
任务接收方userid(单个)executorIds(数组,可多个)从单用户任务升级为可多人执行的任务。
任务跳转url(单一链接)detailUrl(详情页) +actionList(操作按钮回调)交互更丰富,可以为一个任务定义多个操作入口。
任务来源无明确字段source,sourceId(必填)用于标识外部系统,必须合理设置,建议用“系统名:业务类型”的格式。
通知能力创建即发送通知创建后,可通过单独的/v1.0/todo/tasks/{taskId}/send接口发送通知创建任务和发送通知解耦,控制更灵活。
状态同步较弱支持完成(done)、删除(delete)等状态操作,并有回调通知可以实现外部系统与钉钉待办的状态同步。

注意:上表中的“待办应用”是指在开放平台“应用开发”中创建的、类型为“待办”的应用。它和你的“微应用”或“H5应用”是独立的,需要单独配置和授权。

3. 实战:三步走搞定新版待办任务集成

了解了理论差异,我们开始动手。整个迁移过程可以概括为三个核心步骤:准备新凭证、重构请求体、处理响应与通知。

3.1 第一步:在开放平台创建与配置待办应用

这是所有工作的前提,没有正确的应用和凭证,一切调用都是徒劳。

  1. 登录钉钉开放平台:进入开发者后台。
  2. 创建待办应用:在“应用开发”页面,点击创建应用,选择“待办”类型。填写应用名称、描述等信息。创建成功后,你会获得这个待办应用的AppKeyAppSecret。请妥善保存,这将是后续所有API调用的钥匙。
  3. 配置应用权限:在应用详情页的“权限管理”中,确保已添加“待办任务读写权限”(todo:task:write)。通常创建待办应用时默认已添加。
  4. 授权给企业:在“版本管理与发布”中,将应用发布到线上,并确保需要使用的企业组织已经授权了该应用。只有被授权企业的员工才能被成功创建待办任务。

3.2 第二步:获取访问令牌与构造请求

新版接口使用标准的OAuth 2.0客户端凭证模式获取Token,与旧版获取access_token的方式类似,但域名和参数略有不同。

获取 Access Token

# 请求示例 (使用 curl) curl -X POST \ 'https://api.dingtalk.com/v1.0/oauth2/accessToken' \ -H 'Content-Type: application/json' \ -d '{ "appKey": "你的待办应用AppKey", "appSecret": "你的待办应用AppSecret" }' # 响应示例 { "expireIn": 7200, "accessToken": "xxxxxx", "corpId": "xxxx" }

Token有效期为7200秒(2小时),需要在自己的服务端做好缓存和刷新机制,避免频繁请求。

构造创建任务的请求体这是最核心的部分,一个最小化但可用的创建任务请求体如下:

{ "subject": "【系统提醒】请审核月度报销单", "creatorId": "manager123", // 创建者工号,需在接收方企业内 "description": "员工张三提交了2023年10月的报销单,总金额为1250元,请尽快处理。", "executorIds": ["zhangsan", "lisi"], // 执行者工号列表,至少一个 "participantIds": ["wangwu"], // 参与者工号列表,可选 "detailUrl": { "appUrl": "https://your-internal-system.com/task/12345", // PC端跳转地址 "pcUrl": "https://your-internal-system.com/task/12345" // 移动端跳转地址,可与appUrl相同 }, "sourceId": "finance_audit:12345", // 外部系统任务唯一ID,建议包含业务类型 "source": "财务审核系统", // 外部系统来源名称 "dueTime": 1698768000000, // 截止时间戳(毫秒),可选 "priority": 20 // 优先级,可选,默认20 }

关键字段解读与避坑点:

  • creatorId:必须是钉钉企业内的有效员工ID,且该员工所在企业必须已授权你的待办应用。否则任务创建会失败。
  • executorIds:任务执行人列表。即使你只想指定一个人,也必须用数组格式,如["zhangsan"]。这是新手最容易出错的地方之一。
  • detailUrl:这里的appUrlpcUrl必填项。如果你没有独立的移动端页面,可以填同一个URL。这个链接是点击任务主标题区域的跳转地址。
  • sourcesourceId强烈建议认真规划这两个字段source用于标识你的系统(如“CRM”、“OA”),sourceId是你系统内部任务的唯一标识。钉钉会使用source+sourceId作为去重依据。如果你用同一个组合重复调用,钉钉会更新已有的任务,而不是新建。这既是优点(避免重复任务),也可能是坑(如果sourceId生成逻辑有误,会导致任务被意外覆盖)。
  • priority:优先级数值,越低越优先。默认是20。你可以根据业务需要设置,例如紧急任务设为10。

3.3 第三步:发送请求与处理通知

拿到Token和构造好请求体后,就可以调用创建接口了。

调用创建任务接口

curl -X POST \ 'https://api.dingtalk.com/v1.0/todo/tasks' \ -H 'Content-Type: application/json' \ -H 'x-acs-dingtalk-access-token: 上一步获取的accessToken' \ -d '上面构造的JSON请求体'

如果成功,响应如下:

{ "id": "5c5f-4b3a-...", // 钉钉侧生成的待办任务ID "subject": "【系统提醒】请审核月度报销单", "creatorId": "manager123", "executorIds": ["zhangsan", "lisi"], "sourceId": "finance_audit:12345", "source": "财务审核系统" }

请务必保存返回的id,这是后续更新、完成或删除该任务的唯一依据。

发送待办通知任务创建成功后,在钉钉待办列表里就有了,但用户不会立即收到通知。需要调用另一个接口来发送提醒:

curl -X POST \ 'https://api.dingtalk.com/v1.0/todo/tasks/{taskId}/send' \ // 将{taskId}替换为上一步返回的id -H 'x-acs-dingtalk-access-token: your_access_token' \ -H 'Content-Type: application/json' \ -d '{ "operatorId": "manager123" // 操作者ID,通常是创建者 }'

调用成功后,指定的执行者(executorIds)就会在钉钉上收到待办任务的通知了。这种“创建”与“通知”分离的设计,给了开发者在业务流程上更大的控制权。例如,你可以先创建任务草稿,等某个条件满足后再发送通知。

4. 深度踩坑与排查指南

在实际迁移和开发过程中,我遇到了不少报错和诡异的问题。下面我把这些坑和排查思路梳理出来,希望能帮你节省大量时间。

4.1 错误码 400:参数校验不通过

这是最常见的一类错误,响应体里通常会给出具体的错误信息。

  • “executorIds” is required: 没传executorIds,或者传了空数组[]。必须保证数组里至少有一个有效的用户ID。
  • “detailUrl” is required: 忘记传detailUrl对象,或者里面的appUrl/pcUrl为空。
  • “source” is required“sourceId” is required: 漏填了这两个字段中的任何一个。它们都是必填项。
  • User not found: 指定的creatorIdexecutorIds中的用户ID在当前企业不存在,或者该企业未授权你的待办应用。请特别注意:即使该用户存在于钉钉,但如果他/她不在你已经授权了的那个企业里,也会报这个错。你需要确认调用接口时使用的accessToken所对应的企业(即待办应用授权给的企业),是否包含了这些用户。
  • Invalid JSON format: 请求体不是合法的JSON。常见于字符串拼接构造JSON时,忘了转义引号或处理换行符。建议在代码中始终使用JSON库来序列化对象。

4.2 错误码 403:权限不足

  • No permission to access this API: 使用的accessToken对应的应用,没有待办任务的读写权限。请回到开放平台,检查你的待办应用是否已添加todo:task:write权限。
  • Invalid authenticationaccessToken无效或已过期。检查你的Token获取逻辑和缓存刷新机制。确保调用业务接口时,使用的是最新有效的Token。

4.3 错误码 500 或连接问题

  • Internal server errorConnection closed mid-response: 通常是钉钉服务端临时问题。首先检查你的请求参数是否超大(虽然待办任务接口对字段长度限制较宽,但超长的描述或URL也可能引发问题)。如果参数正常,可以稍后重试。如果持续失败,需要检查钉钉开放平台的状态公告。
  • 网络超时或不可达: 确认你的服务器能正常访问api.dingtalk.com域名。有些公司内网环境可能有出口防火墙限制。

4.4 任务创建成功但客户端不显示

这是从旧接口迁移过来时最典型的问题,症状是接口返回成功(200),有任务ID,但在执行者的钉钉App里就是找不到这个待办。

  1. 首先检查应用授权:百分之八十的问题出在这里。请务必确认:你调用接口时使用的appKeyappSecret,是来自一个已经成功授权给目标企业待办应用。用旧微应用的凭证调用新接口,即使返回成功,任务也是“幽灵”状态。
  2. 检查用户ID有效性: 确认executorIds里的用户ID,在当前accessToken所代表的企业内是真实存在的。可以用钉钉的获取用户信息接口先验证一下。
  3. 检查通知是否发送: 任务创建后,默认不会出现在用户的“今日”或“待办”列表,直到调用“发送通知”接口。请确认你是否调用了/v1.0/todo/tasks/{taskId}/send
  4. 查看“已隐藏”或“其他来源”: 在钉钉待办界面,有时任务会被归类或过滤。让用户检查一下待办列表的“全部”标签,或者看看是否有按来源分类的筛选。

4.5 状态同步与回调配置

新版接口支持任务状态变化(如完成、删除)时,向你的服务器发送回调通知。这对于保持外部系统与钉钉待办状态一致非常有用。

  1. 配置回调地址:在待办应用的后台,“事件与回调”页面,配置一个HTTPS的接收地址。钉钉会向这个地址推送事件。
  2. 订阅事件:你需要订阅todo_task_change事件类型。
  3. 处理回调:当用户在钉钉里完成或删除任务时,钉钉会发送一个加密的POST请求到你的回调地址。你需要:
    • 解密请求体(钉钉提供了各语言的解密SDK)。
    • 解析出事件类型(eventType)和任务ID(taskId)等信息。
    • 根据eventType(例如todo_task_completedtodo_task_deleted) 更新你自己系统中对应任务的状态。
  4. 注意签名和重试:务必验证回调请求的签名,以确保请求来自钉钉。同时,你的回调接口需要快速返回成功(HTTP 200),否则钉钉会认为推送失败并进行重试。

5. 进阶:打造更佳用户体验的待办集成

完成了基础接入,我们可以看看如何利用新接口的特性,做出体验更好的集成。

5.1 自定义操作按钮与业务回调

新版待办任务卡片底部可以配置一组操作按钮,比如“查看详情”、“开始处理”、“确认完成”等。这比旧版只有一个跳转链接强大得多。

在创建任务的请求体中,可以添加actionList字段:

{ // ... 其他字段同上 "actionList": [ { "name": "处理", "actionUrl": { "appUrl": "https://your-system.com/task/12345/action/process", "pcUrl": "https://your-system.com/task/12345/action/process" } }, { "name": "完成", "actionUrl": { "appUrl": "https://your-system.com/task/12345/action/finish", "pcUrl": "https://your-system.com/task/12345/action/finish" } } ] }

当用户点击“完成”按钮时,会跳转到你配置的actionUrl。你可以在自己的页面处理完业务逻辑(例如,在你的系统里标记任务完成)后,再调用钉钉的接口(/v1.0/todo/tasks/{taskId}/done)来同步更新钉钉待办的任务状态。这样就形成了一个闭环的业务流。

5.2 任务更新、完成与删除

任务不是一成不变的,我们需要更新它。

  • 更新任务:使用PATCH /v1.0/todo/tasks/{taskId}接口。你可以更新标题(subject)、描述(description)、截止时间(dueTime)、执行者(executorIds)等大部分字段。注意:更新执行者时,新的列表会完全覆盖旧的列表。
  • 标记完成任务:使用POST /v1.0/todo/tasks/{taskId}/done接口。需要传递operatorId(操作者ID)。这会将任务状态改为完成。
  • 删除任务:使用DELETE /v1.0/todo/tasks/{taskId}接口。同样需要operatorId

5.3 关于“虚拟位置”、“打卡”等热词的无关性澄清

在分析网络热词时,我注意到“钉钉打卡虚拟位置”等词频繁出现。这里必须明确:本文讨论的“待办任务”API与“打卡”、“定位”等功能毫无关系。钉钉打卡涉及的是完全不同的另一套权限和接口(主要与考勤相关),并且任何讨论或提供“虚拟位置”、“修改定位”以实现虚假打卡的技术内容,不仅违反钉钉平台规则,也可能涉及不当行为。作为开发者,我们应该专注于利用开放平台提供的合法接口,创造提升工作效率的工具,而不是钻营漏洞。待办任务API是一个纯粹用于任务管理和协同的正向工具。

6. 迁移策略与代码示例片段

最后,分享一下从旧接口迁移到新接口的平滑策略,并提供一段Java Spring Boot风格的代码示例,供大家参考。

平滑迁移策略:

  1. 并行运行期:在旧接口完全失效前,同时实现新旧两套接口的调用逻辑。根据配置或特性开关决定使用哪一套。这样即使新版接口遇到问题,可以快速回退。
  2. 数据映射与补偿:将旧系统的任务数据(尤其是sourceId)按照新规则进行映射。对于已经通过旧接口创建且仍在进行中的任务,可以考虑通过新接口重新创建一次,并通过消息通知用户关注新任务,逐步淘汰旧任务。
  3. 全面测试:务必在测试环境,用真实的测试企业号,覆盖单人多任务、多人协同、更新、完成、回调等全流程。

Java代码示例(使用HttpClient):

import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.*; @Component public class DingTalkTodoService { private String appKey = "your_todo_app_key"; private String appSecret = "your_todo_app_secret"; private String accessToken; private long tokenExpireTime; private final RestTemplate restTemplate = new RestTemplate(); private final ObjectMapper objectMapper = new ObjectMapper(); // 1. 获取AccessToken (带缓存) private String getAccessToken() { if (accessToken != null && System.currentTimeMillis() < tokenExpireTime) { return accessToken; } String url = "https://api.dingtalk.com/v1.0/oauth2/accessToken"; ObjectNode requestBody = objectMapper.createObjectNode(); requestBody.put("appKey", appKey); requestBody.put("appSecret", appSecret); Map response = restTemplate.postForObject(url, requestBody, Map.class); this.accessToken = (String) response.get("accessToken"); long expireIn = Long.parseLong(response.get("expireIn").toString()); this.tokenExpireTime = System.currentTimeMillis() + (expireIn - 300) * 1000; // 提前5分钟过期 return this.accessToken; } // 2. 创建待办任务 public String createTodoTask(String creatorId, List<String> executorIds, String subject, String description, String source, String sourceId, String detailUrl) { String url = "https://api.dingtalk.com/v1.0/todo/tasks"; String token = getAccessToken(); ObjectNode requestBody = objectMapper.createObjectNode(); requestBody.put("creatorId", creatorId); requestBody.putArray("executorIds").addAll(executorIds); requestBody.put("subject", subject); requestBody.put("description", description); requestBody.put("source", source); requestBody.put("sourceId", sourceId); ObjectNode urlNode = objectMapper.createObjectNode(); urlNode.put("appUrl", detailUrl); urlNode.put("pcUrl", detailUrl); requestBody.set("detailUrl", urlNode); // 可以添加更多字段,如 dueTime, priority, actionList 等 // requestBody.put("dueTime", System.currentTimeMillis() + 86400000L); // 截止时间:1天后 // requestBody.put("priority", 10); HttpHeaders headers = new HttpHeaders(); headers.set("x-acs-dingtalk-access-token", token); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<String> entity = new HttpEntity<>(requestBody.toString(), headers); Map response = restTemplate.postForObject(url, entity, Map.class); return (String) response.get("id"); // 返回钉钉任务ID } // 3. 发送待办通知 public void sendTodoNotification(String taskId, String operatorId) { String url = "https://api.dingtalk.com/v1.0/todo/tasks/" + taskId + "/send"; String token = getAccessToken(); ObjectNode requestBody = objectMapper.createObjectNode(); requestBody.put("operatorId", operatorId); HttpHeaders headers = new HttpHeaders(); headers.set("x-acs-dingtalk-access-token", token); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<String> entity = new HttpEntity<>(requestBody.toString(), headers); restTemplate.postForObject(url, entity, Void.class); } }

这段代码提供了最核心的Token管理和任务创建功能。在实际项目中,你需要将其纳入你的服务治理框架(如加入断路器、重试机制),并处理好异常。最关键的是,管理好你的appKeyappSecret,不要硬编码在代码里,应该使用配置中心或环境变量。迁移到新版待办接口,虽然初期有学习成本,但其更清晰的数据模型、更强大的协同能力和更完善的状态管理,对于构建严肃的企业协同功能来说,无疑是更长期和可靠的选择。

← 返回列表