feat: add dingtalk-log skill for querying work reports

- Add SKILL.md with full API documentation
- Add shell script for quick queries
- Update TOOLS.md with reference to new skill
This commit is contained in:
root
2026-03-29 14:15:03 +08:00
parent f8d9936d56
commit 1a9fdc7274
8 changed files with 1177 additions and 0 deletions

8
.mcp.json Normal file
View File

@@ -0,0 +1,8 @@
{
"mcpServers": {
"钉钉文档": {
"type": "streamable-http",
"url": "https://mcp-gw.dingtalk.com/server/fcf2405ec27cd4428220e0515ff8e04ee52ba7e11578aabbd1ef57651ad9c4d4?key=70fb66762bdf5b10a304a3d1a270aebb"
}
}
}

View File

@@ -38,3 +38,29 @@ Skills are shared. Your setup is yours. Keeping them apart means you can update
--- ---
Add whatever helps you do your job. This is your cheat sheet. Add whatever helps you do your job. This is your cheat sheet.
## 钉钉应用配置(企业内部应用)
用于操作日历、查看日志、文档等企业钉钉功能。
```markdown
AgentId: 4404185308
Client ID (AppKey): dingklemniq8uqk5qbgx
Client Secret (AppSecret): _8EHgyhvHRHRMx6fZbh9LNpQoxyYl3At0b-fXXlQiahwupbt9oY5P6Grj8IM9Dx8
```
## 钉钉 API 注意事项
### ⚠️ 时间参数单位毫秒ms
钉钉绝大名接口的时间参数(如 `start_time``end_time``created_at` 等)都是**毫秒**,不是秒!
- ❌ 错误:`1743206400`(秒)
- ✅ 正确:`1743206400000`(毫秒)
### ⚠️ 字段命名:全小写 `userid`
很多接口参数名是 `userid`(全小写),不是 `userId`(驼峰)。具体以接口文档为准。
## 日志查询
参考文档https://open.dingtalk.com/document/development/query-logs-sent-by-an-employee
详细调用方式见 skill`skills/dingtalk-log/SKILL.md`

View File

@@ -0,0 +1,193 @@
# 罗国财 工作周报记录
## 2026-03-24 经理人周报
**本周完成工作:**
1. 标杆名单项目需求推进4月份规划内容
2. 大模型落地项目(导购及智能装修功能的思考和方案探索)
3. 3月份迭代事项推进AI需求的目标落地
4. 客如云项目(关注高价值需求的推进)
5. 前端员工面谈
**本周工作总结:**
关于AI + 多维表格的思考在AI创新小组的思考分享给大家
1多维表格背景
①、低代码平台演变Paas平台 -> aPaas平台 ->低代码平台 ->无代码平台 -> 多维表格
②、aPaas与低代码平台区别aPaas平台和低代码平台就有一些重合网上也有人说他们是一个东西两个名称而已有人说他们完全不是一回事并列了一些不止所谓的区别我认为就是搞概念而已。在我看来低代码平台应该是aPaas平台的一种而且大部分时候他们是可以看作是一个事物若真有不一样我认为是在代码介入的程度就是有多低代码。
③、无代码平台和多维表格区别。它们同样类似上面的aPaas和低代码平台这样我认为多维表格是无代码平台的一种也可以现在所谓的无代码平台基本就是指多维表格这种特定的应用。
④、产品演变方向。从演变过程来看,就是从复杂庞大专业到简单大众化,尤其在近这一两年来,多维表格这种无代码平台完胜低代码平台。从钉钉在主菜单的最中间位置上去放多维表格的入口就可见一斑。
⑤、从代码平台演变看PMF。从功能上来看低代码平台可以做无代码实现的任何事情但是无代码不能做部分低代码平台的事情。例如钉钉前几年大力发展的宜搭可以做飞书多维表格实现的任何事情但是事实就是被打很惨。大家可能说不是同一个赛道但是问题是在10个用户里面或许有8个客户的问题是可以通过多维表格就可以解决只有2个才是用宜搭去解。那结果就很显然易见那8个客户不会花那么大的学习曲线去学习复杂的宜搭。另外一个就是多维表格的展现形式就是一张我们最常见的Excel表格这也让大部分人可以直接上手使用可以从浅到深去使用产品受众并不是只有企业专业用户。相反类似宜搭的产品复杂问题用它去解很麻烦成本也不少他们的受众IT相关人员也不爱用限制太多。所以最终造就了一个尴尬的局面简单的问题多维表格解决就够了复杂的问题貌似宜搭也解决不了。
2多维表格 + AI
①、国外厂商Airtable创立于2013年在2021年12月完成7.35亿美元F轮融资迄今为止Airtable共计获得13.6亿美元融资估值117亿美元。飞书官网谈多维表格竞品就谈它
②、国内厂商:飞书多维表格、钉钉多维表格、维格,这三家不多介绍,其中维格是创业公司,上次见到他们其中一个创始人,说是他们先做的多维表格,后面飞书
**下周工作计划:**
1. 标杆名单项目需求推进4月份规划内容落地执行
2. 大模型落地项目(解决经营分析助手的回答准确率问题)
3. 4月份迭代目标确认关注AI的4月目标
4. 客如云项目(已明确的需求交付周期确定)
5. 前端员工面谈(下周关闭)
---
## 2026-03-17 经理人周报
**本周完成工作:**
1. 标杆名单项目需求推进4月份规划内容
2. 大模型落地项目(导购及智能装修功能的思考和方案探索)
3. 3月份迭代事项推进AI需求的目标落地
4. 客如云项目(关注高价值需求的推进)
5. 前端员工面谈
**本周工作总结:**
1、精细化管理
最近一阵都有接触客如云相关的项目,在和对方去沟通客户分层和商机等相关事项,我会发现对方在在这两点上的管理有一些地方可以值得我们去学习。
1客户分层定位。客如云把餐饮连锁行业分成15种客群每个都会有一个客户画像编号对应有这个客户群的特点精准描述。在我们一开始看来是觉得比较不可思议一个的餐饮连锁可以细分出那么多客群来。在我们最初的行业划分里面我们只是把整个行业划分成13个行业像餐饮连锁我们根本没有关注里面到底怎么去区分。也就是在上一年我们才把二级行业也做了一个细分部分行业也分得更加细。如果从产品规划的角度去看行业细分最终可以让我们在产品上做对应的隔离针对不同的细分行业可以有更加专业实用的功能输出。不过这一个主要还是取决于我们是否可以有对应的资源去隔离。但是至少我们可以有这个选项。例如当下我们在自己擅长的行业就可以去做对应的事情而不是自己的甜蜜行业那应该就是按照之前的标准哪怕是区分了更细的二级行业但是我们还是暂时不去处理。而在销售管理的这个优势就更加明显了针对不一样的细分客户分层我们可以采用不一样的销售术语提供对应的解决方案给客户展示对应相同类型的客户案例等等。
2商机管理。我们每一次和客如云进行周会客如云都会给我们同步最近时间的商机情况我们可以很清晰看到在这段时间一共有多少商机有效的多少无效的多少。有效里面已经完成审批的有多少审批通过的有多少是符合铱云供应链 + 客如云解决方案的,其中符合画像的有多少,不符合的有多少。对于那些不符合画像的客户里面,我们也能清楚看到分类原因是:产品功能原因 这个一个分类。这样我们很清楚,这些客户因为那些产品的功能而没有买单。回到我们产品规划上,我们就可以有更加清晰的导向,到底去规划哪些需求哪些内容。我们现在其实是比较缺失这样的管理。我们大多时候是听到有客户有抱怨某个功能没有导致客户没有成交,但是总是只是听到这样的声音和甚至有时候是情绪,也不知道这个声音到底是怎样的一个比例,所以我们根本无法看到商客户市场的一个全貌。如果这些信息我们产研都不够清晰,那后续规划如何去做到更加贴合客户呢?我希望后续产研这边可以和朱文建把这个事情梳理一下,是否有持续的商品精细化数据分析。
PS下面几个图是客如云内的关键数据已经打了马赛克
[图片]
[图片]
[图片]
2、常牧客户客户案例
**下周工作计划:**
1. 标杆名单项目需求推进4月份规划内容
2. 大模型落地项目(导购及智能装修功能的思考和方案探索)
3. 3月份迭代事项推进AI需求的目标落地
4. 客如云项目(关注高价值需求的推进)
5. 前端员工面谈
---
## 2026-03-10 经理人周报
**本周完成工作:**
1. 标杆名单调研报告阅读及分析明确第一批12家
2. 大模型落地项目(关闭内审)
3. 3月份迭代事项关注AI需求的目标落地
4. 前端员工面谈(延后持续)
**本周工作总结:**
1、流失客户复盘
本周收到刘程通知告知我们一个知名的冻品客户鸿海将要放弃易订货去采用一个叫金灯塔的软件。我当时第一反应是金灯塔是哪家公司的产品。然后上网收集了一些信息结果什么都没有查到只看到一个比较简陋的软件登录页面。后面刘程也拉着相关同学做了一个复盘会议。复盘会议里面我们复盘到有两个我们的功能点AI营销和库存管理相关。
这两个功能自己有一些看法。
AI营销我个人会感觉竞品的这个功能是偏营销噱头目前我们观察到市面上的SaaS产品也没有一家公司在这个点上有一个非常好的应用。以当下AI大模型的能力去构建智能营销功能有很好的落地还需要一段路要走需要在Agent工程化上面做很多投入才可以。但是换一句话来说是不是产品的AI营销功能是可以先有至于真正的业务价值还是其次
库存管理相关这里面最主要的应该是库位这是专业软件WMS的范畴我们之前拜访包老弟这个客户也是提了这个问题他们希望我们可以提供WMS相关的功能。对于我们一些规模比较大客户会有这样的需求。但是WMS本来就是一个专业软件以我们的资源情况很难短时间去做到这样的能力而且WMS在市面上价格是比较贵的。对于金灯塔做到那种程度我是比较好奇的。未来我们可能会有简单的库位管理但是肯定也会是简单的库位管理而不是完整的WMS。另外像指掌这种竞品也号称自己是有WMS模块但是我们粗略看了一下也是一个很简单的实现很难真正实现大型企业的库位管理。
抛开一些具象的功能需求,我想谈一些客户流失的看法:
1客户流失大多数都是各种因素掺杂在一起导致的结果应该很少应该1到2个很具体的功能点而放弃。如果有若真的有那应该有机制在很早的阶段同步给到产研产研开始一些介入然后给出产研的结论要么去迭代这些功能要么基于ROI不做就向公司抛出风险。我尝试在我们产研需求池和运营平台里面找海鸿的需求只找到零星的一两个需求而且还不是上面提及的内容。重点客户很在意某些功能在意到可能要去更换系统而我们竟然没有相关的需求录入
2产研在关键客户流失后应该有对应的信息收集机制而不是单方面的获取二手信息。当然不是去说二手信息不准确而是不一样的位置的人对信息的理解和处理程度也会有不一样。所以这需要我们在后续建立这样的机制当公司关键客户可以以服务的L1和L2作为标准流失产研需要去做调研分析。
3
**下周工作计划:**
1. 标杆名单调研报告阅读及分析(关注需求落地)
2. 大模型落地项目(智能营销及客户分析需求外审)
3. 3月份迭代事项推进AI需求的目标落地
4. 前端员工面谈
---
## 2026-03-03 经理人周报
**本周完成工作:**
1. 标杆名单调研报告阅读及分析(持续)
2. 大模型落地项目(已完成智能营销及智能客户分析的方案输出)
3. 3月份迭代事项关注AI需求的目标落地
4. 经理人面谈(关闭)
**本周工作总结:**
1、智能营销及智能客户分析方案
上周我花了不少时间在输出智能营销及智能客户分析两个功能的方案。对于这两个AI大模型的功能我自己是想有更多的参与所以我花了一些时间去写了这两个方案。两个方案结构都基本一致就是对于AI agent开发的一些关键要点做了说明我分别按照以下自己要点去做输出
1功能说明
2关键时序图大模型、AI Agent、易订货产品三者的关键交互过程
3Prompt说明每一次交互过程用到的Prompt如何编写
4关键数据范围喂给大模型的的数据包括有哪些
5本地知识说明提供我们铱云在这个领域的一些业务知识
6工具列表我们可以提供给大模型的API列表
我上次年会就分享过我们所谓的AI Agent = 大模型 + 记忆 + 技能规划 + 工具使用。而我上次说的几点其实就是围绕这四个内容去做说明。例如说我们本地知识说明 + 关键数据范围其实就是上面提及的记忆而技能规划对应的就是Prompt说明。
这次产研把AI相关功能真正作产品功能去落地很多事情都是第一次接触。所以我也特意去写了这样一个方案说明这种方案说明有AI技术侧的内容说明也有业务侧的一些思考。而最后产品经理可以根据这个方案内容说明去进行客户场景调研输出最终的需求原型。我们之所以先输出一个这样的AI技术方案主要的考虑是AI agent的一些技术方案及限制以及如何最大限度发挥大模型能力这些信息产品经理目前还是不够清晰。最后我们在这个输出原型的过程整个公司AI创新小组的同学也不断去介入实时去调整需求原型已达到我们认知的最匹配客户场景的状态。上述这个过程应该在未来一段时间都是我们去迭代产品AI功能的大致流程。
另外我想和大家分享几个点。我们讨论过后也发现在AI时代去做AI Agent的产品产品经理关注的点跟过去有都不少变化。
1更强的数据因果分析能力。我们作为一个有大量客户商品及交易数据的公司很多时候想要做到功能的大致逻辑就是大模型去分析一些数据然后得到一些结论最终根据结论去执行一些事情。这里我们就要去思考我们的结论是那方面的结论我们就要考虑去投喂那些业务数据给大模型这里就有很强的数据因果关系分析。例如说我要做智能营销那我可能要把客户的登录数据也要给客户这里面存在登录频次及登录时间这些数据可以让我做智能优惠券可以更精准投放给对应的客户
**下周工作计划:**
1. 标杆名单调研报告阅读及分析(明确初步标杆名单)
2. 大模型落地项目(智能营销及客户分析需求原型的内审)
3. 3月份迭代事项关注AI需求的目标落地
4. 前端员工面谈
---
## 2026-02-24 经理人周报
**本周完成工作:**
1. 标杆名单第一阶段调研关闭(关注报告数据整理)
2. 大模型落地项目推进Q1目标
3. 年会报告内部细化分享
4. 经理人面谈
**本周工作总结:**
1、客户拜访
本周二去拜访深圳一家冻品客户尚为食品也是我们打造冻品标杆行动里面的列名客户。客户是冻品食材行业日单量大概在150-200左右GMV大概在3700W左右在线支付流水1300W。客户主要的业务是冻品这一块IT系统用了金蝶软件做财务除财务以外的内容都是用易订货1.0旗舰版当前已经付费升级了但是还没有在2.0上面跑业务。客户还有发展另外一块新业务鲜品现在这一块业务使用管家婆在处理但客户想统一整合在2.0上。客户还有发展另外一块新业务鲜品现在这一块业务使用管家婆在处理但客户想统一整合在2.0上。客户和我们上海的客户上奉食品有一定的投资合作关系,所以很多使用系统的方式都参考了上奉。分享两个点:
1在线支付。从客户的支付流水来看我们也看到客户只有大概40%左右的GMV是走我们的在线支付其他都是走线下的模式。我们也在这个点上问询了客户。客户没有全部使用在线支付并不是因为手续费或其他一些因素而是因为下游小B在支付的时候不方便。客户的下游基本都是走月结或许半月结所以他们支付都是希望可以一次过支付一批订单而不是一张张订单支付。每次这种按每一个订单进行支付还经常出现漏了订单没有支付的情况。而我们账单支付功能正正是可以解决这个问题我们同步给客户之后客户表示这个功能是他们想要的后续会引导小B使用这个功能进行支付。客户的下游都是小商家所以他们月结或许半月结的账单也大概在5000左右并不会触发微信支付的一些限额控制
我们在支付这个事情是不是也存在很多类似尚为这种类型的客户我年会里面提到支付里面我们需要对已经使用了在线支付但是在线支付占比不高的客户进行重点关注。在未来我觉得应该列名去跑这里面GMV比较大客户去做深度的支付挖掘。
2AI。我在交流中提及到AI这个点并且向他们老板李总展示了我们的AI的原型功能。李总是一个心态很开放的人很认同AI大模型在未来会给各行各业赋能这个点。所以他很早就自己使用了ChatGPT用它来解决自己的一些问题。当我去展示一些我们的原型功能的时候他是非常感兴趣。并且在我去讲述未来我们将来迭代的一些类似AI营销的功能时候他表示很认同认为这些AI营销的功能是他们想要的功能。不过出乎我自己意料之外在展示这种通过自然语言去查询报表能力的时候李总会觉得可能他会更习惯自己通过传统方式去查询。而在展示知识问答助手的时候他表示这种当然可以有但是他更希望是赋能给到他们的下游例如下
**下周工作计划:**
1. 标杆名单调研报告阅读及分析
2. 大模型落地项目(完成智能营销功能方案输出)
3. 3月份迭代事项关注AI需求的目标落地
4. 经理人面谈(持续)
---
## 2026-02-17 经理人周报
**本周完成工作:**
1. 标杆名单调研拜访(关注进度)
2. 大模型落地项目推进Q1目标
3. 25年年会报告
**本周工作总结:**
1、AI具象化功能点
[表格]
经过我们内部的一些讨论以及对外部场景的一些洞察,我们基本会落在两个大场景:营销及销售管理上去进行具象功能挖掘。这两个场景的功能,我们已经有大致方案和功能框架,但是一些细节的问题还需要讨论以及和需要与客户做一些相关交流,这样我们确保我们的功能是去解决客户在意的场景问题。至于两个两个功能知识助手和业务数据助手,我们会在这两个月持续迭代优化,在三月末迭代内侧版本发布上线。
2、年会报告后续事项
周末到公司跟区域同学做一个关于年会报告的重点内容分享,主要围绕两个事情:
1产品场景化设计。年会报告我提及我们产研在过去拜访的客户使用我们产品的去解决业务场景覆盖非常低的问题。若场景解决低那意味后续的续费、增购、升级都无法顺利去进行更不要去谈及圈层经营这一类的事情。所以这也是今年产研是非常重要的事情。首先今年我们加强新功能调研及标杆客户调研这个事情把客户的业务场景都摸一遍让我们自身清晰产品的解决场景的问题都在哪些点上然后根据我们核心 3 +1 策略去逐个解决问题。其次我们今年也会在内部组织结构上匹配我们产品场景化。在Q1我们产品会重新设计迭代结构去做到团队和场景挂钩最终团队考核与客户导向的场景直接相关。
2AI大模型产品创新。年会报告结束后有不少一线的同学对AI的功能明显非常感兴趣也有同学已经问了我账号去体验。后续产研会重点投入去加速一些功能的迭代。以此同时我们也不能去忽略AI产品功能的客户验证这些功能同样需要去找客户做调研无论是已经成型的功能还是将要迭代的功能。
这两个事项在本周我还有拉一个产研全员的会议去做一个加持说明尤其在AI这个事情上我需要动员到全产研的同学有更高的参与度以及阐述一些AI创新组织及机制说明公司对这个事情的重视程度。
**下周工作计划:**
1. 标杆名单第一阶段调研关闭(关注报告数据整理)
2. 大模型落地项目推进Q1目标
3. 年会报告内部细化分享
4. 经理人面谈
---
*记录时间2026-03-29*
*数据来源:钉钉工作日志 API*

View File

@@ -0,0 +1,88 @@
---
name: dingtalk-document
description: 钉钉知识库和文档管理操作。当用户提到"钉钉文档"、"知识库"、"新建文档"、"查看文档目录"、"读取文档内容"、"写入文档"、"更新文档"、"文档成员"、"dingtalk doc"、"knowledge base"时使用此技能。支持:创建知识库、查询知识库列表、新建文档/文件夹、读取/写入文档正文内容、管理成员权限等全部文档类操作。
---
# 钉钉文档技能
负责钉钉知识库和文档的所有操作。本文件为**策略指南**,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。
> `dt_helper.sh` 位于本 `SKILL.md` 同级目录的 `scripts/dt_helper.sh`。
## 核心概念
- **知识库Workspace**:文档容器,有 `workspaceId``rootNodeId`
- **节点Node**:文件或文件夹,`type``FILE``FOLDER`
- **文档标识(用于 `/v1.0/doc/suites/documents/{id}`**:可用 `docKey``dentryUuid`
- 创建文档响应会返回:`docKey``dentryUuid``nodeId`
- 其中 `docKey` / `dentryUuid` 可用于读写正文;`nodeId` 用于删除和文档管理类接口
- `wiki/nodes` 返回的 `nodeId` 实际上是 `dentryUuid`,可直接用于正文读写
- **operatorId**:所有接口必须的 unionId 参数,通过 `bash scripts/dt_helper.sh --to-unionid` 自动转换
## 工作流程(每次执行前)
1. **先识别本次任务类型** → 例如:列知识库、读文档、写文档、创建文档、成员管理
2. **按本次任务校验所需配置** → 通过 `bash scripts/dt_helper.sh --get KEY` 读取;仅校验本任务必须项
3. **仅收集缺失配置** → 若缺少某项,**一次性询问用户**所有缺失值,用 `bash scripts/dt_helper.sh --set KEY=VALUE` 写入
4. **获取 Token / operatorId** → 直接调用 `bash scripts/dt_helper.sh`token 获取与缓存细节无需关心
5. **执行操作** → 凡是包含变量替换、管道或多行逻辑的命令,写入 `/tmp/<task>.sh``bash /tmp/<task>.sh` 执行。不要把多行命令直接粘到终端里(终端工具会截断),也不要用 `<<'EOF'` 语法heredoc 在工具中同样会被截断导致变量丢失)
### 按任务校验配置(必须先做)
- **所有任务通用必需**`DINGTALK_APP_KEY``DINGTALK_APP_SECRET``DINGTALK_MY_USER_ID`
- **涉及任何文档/知识库 API 调用**:必须有 `DINGTALK_MY_OPERATOR_ID`(若缺失,先用 `bash scripts/dt_helper.sh --to-unionid` 自动转换并写回)
- **创建/读取/写入/删除/成员管理**:除上述通用项外,无额外固定配置键;`workspaceId`/`nodeId`/`docKey` 属于任务参数,运行时从用户输入或 API 响应中获取
> 规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。
> 凭证禁止在输出中完整打印,确认时仅显示前 4 位 + `****`
### 所需配置
| 配置键 | 必填 | 说明 | 如何获取 |
|---|---|---|---|
| `DINGTALK_APP_KEY` | ✅ | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 |
| `DINGTALK_APP_SECRET` | ✅ | 应用 AppSecret | 同上 |
| `DINGTALK_MY_USER_ID` | ✅ | 当前用户的企业员工 IDuserId | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看 |
| `DINGTALK_MY_OPERATOR_ID` | ✅ | 当前用户的 unionIdoperatorId | 首次由 `bash scripts/dt_helper.sh --to-unionid` 自动转换并写入 |
### 身份标识说明
| 标识 | 说明 |
|---|---|
| `userId`= `staffId` | 企业内部员工 ID可通过管理后台 -> 通讯录 -> 成员管理 -> 点击姓名查看 |
| `unionId` | 跨企业/跨应用唯一标识,可通过 `bash scripts/dt_helper.sh --to-unionid <userid>` 获取 |
### 执行脚本模板
```bash
#!/bin/bash
set -e
HELPER="./scripts/dt_helper.sh"
NEW_TOKEN=$(bash "$HELPER" --token)
OPERATOR_ID=$(bash "$HELPER" --get DINGTALK_MY_OPERATOR_ID)
# 在此追加具体 API 调用,例如查询知识库列表:
WORKSPACES=$(curl -s -X GET "https://api.dingtalk.com/v2.0/wiki/workspaces?operatorId=${OPERATOR_ID}&maxResults=20" \
-H "x-acs-dingtalk-access-token: $NEW_TOKEN")
echo "知识库列表: $WORKSPACES"
```
> **Token 失效处理**dt_helper 仅按时间缓存,无法感知 token 被提前吊销。若 API 返回 401token 无效/过期),用 `--nocache` 跳过缓存强制重新获取:
> ```bash
> NEW_TOKEN=$(bash "$HELPER" --token --nocache)
> ```
## references/api.md 查阅索引
确定好要做什么之后,用以下命令从 `references/api.md` 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):
```bash
grep -A 30 "^## 1. 查询知识库列表" references/api.md
grep -A 10 "^## 2. 查询知识库信息" references/api.md
grep -A 35 "^## 3. 查询节点列表" references/api.md
grep -A 10 "^## 4. 查询单个节点" references/api.md
grep -A 15 "^## 5. 通过 URL 查询节点" references/api.md
grep -A 28 "^## 6. 创建文档" references/api.md
grep -A 10 "^## 7. 删除文档" references/api.md
grep -A 30 "^## 8. 读取文档内容" references/api.md
grep -A 15 "^## 9. 覆盖写入文档内容" references/api.md
grep -A 12 "^## 10. 追加文本到段落" references/api.md
grep -A 18 "^## 11. 添加文档成员" references/api.md
grep -A 12 "^## 12. 更新文档成员权限" references/api.md
grep -A 10 "^## 13. 移除文档成员" references/api.md
grep -A 10 "^## 错误码" references/api.md
grep -A 10 "^## 所需应用权限" references/api.md
```

View File

@@ -0,0 +1,341 @@
# dingtalk-document API 参考
> 所有接口均已验证可用。
> `NEW_TOKEN` = 新版 token`api.dingtalk.com` 用),获取方式 `bash scripts/dt_helper.sh --token`
> `OPERATOR_ID` = 用户 unionId获取方式 `bash scripts/dt_helper.sh --get DINGTALK_MY_OPERATOR_ID`
> ⚠️ **重要**:所有接口均需传 `operatorId`unionId缺少则返回 `MissingoperatorId` 错误。
---
## 1. 查询知识库列表
```
GET https://api.dingtalk.com/v2.0/wiki/workspaces?operatorId={OPERATOR_ID}&maxResults=20&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `operatorId` | string | ✅ | 用户 unionId |
| `maxResults` | int | ❌ | 每页数量,默认 20 |
| `nextToken` | string | ❌ | 分页令牌,首次为空 |
响应:
```json
{
"workspaces": [
{
"workspaceId": "QXvd5SLN2AxOQz0Z",
"name": "团队知识库",
"description": "...",
"rootNodeId": "P0MALyR8kl3qpB7qTkM1xn3mW3bzYmDO",
"type": "TEAM",
"url": "https://alidocs.dingtalk.com/i/spaces/.../overview",
"createTime": "2024-01-01T00:00Z",
"modifiedTime": "2024-06-01T00:00Z"
}
],
"nextToken": "..."
}
```
> 翻页:`nextToken` 非空时传入下次请求继续获取。
---
## 2. 查询知识库信息
```
GET https://api.dingtalk.com/v2.0/wiki/workspaces/{workspaceId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
响应:单个 workspace 对象(结构同列表项)
---
## 3. 查询节点列表
```
GET https://api.dingtalk.com/v2.0/wiki/nodes?parentNodeId={nodeId}&operatorId={OPERATOR_ID}&maxResults=50&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `parentNodeId` | string | ✅ | 父节点 ID传知识库的 `rootNodeId` 可列出顶层内容 |
| `operatorId` | string | ✅ | 用户 unionId |
| `maxResults` | int | ❌ | 每页数量,默认 20 |
| `nextToken` | string | ❌ | 分页令牌 |
响应:
```json
{
"nodes": [
{
"nodeId": "LeBq413JAw31yaz1fB0BBdLGWDOnGvpb",
"name": "使用文档.adoc",
"type": "FILE",
"category": "ALIDOC",
"extension": "adoc",
"workspaceId": "QXvd5SnBnzmZdZ0Z",
"url": "https://alidocs.dingtalk.com/i/nodes/...",
"createTime": "2026-03-04T16:58Z",
"modifiedTime": "2026-03-04T17:51Z"
}
],
"nextToken": "..."
}
```
> `type``FILE`(文档/文件)| `FOLDER`(文件夹)
---
## 4. 查询单个节点
```
GET https://api.dingtalk.com/v2.0/wiki/nodes/{nodeId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
响应:`{ "node": { nodeId, name, type, category, workspaceId, url, ... } }`
---
## 5. 通过 URL 查询节点
```
POST https://api.dingtalk.com/v2.0/wiki/nodes/queryByUrl?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
请求体:
```json
{
"url": "https://alidocs.dingtalk.com/i/nodes/<nodeId>",
"operatorId": "{OPERATOR_ID}"
}
```
响应:与 GET 单个节点相同的 node 结构。
---
## 6. 创建文档
```
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
请求体:
```json
{
"operatorId": "{OPERATOR_ID}",
"docType": "DOC",
"name": "文档标题"
}
```
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `operatorId` | string | ✅ | 用户 unionId |
| `docType` | string | ✅ | 固定填 `"DOC"`ALIDOC 富文本格式) |
| `name` | string | ✅ | 文档标题 |
响应:
```json
{
"dentryUuid": "aaa",
"nodeId": "xxx",
"docKey": "yyy",
"workspaceId": "zzz",
"url": "https://..."
}
```
> **重要**
> - `docKey` / `dentryUuid`:用于 `/v1.0/doc/suites/documents/{id}` 内容读写
> - `nodeId`:用于 `/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}` 删除/管理
---
## 7. 删除文档
```
DELETE https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
> `workspaceId` 和 `nodeId` 均使用创建文档响应中的值。成功返回 `200 {}`。
---
## 8. 读取文档内容Block 结构)
```
GET https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/blocks?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
> **docKey 的来源**
> - 通过 wiki nodes 接口查到的 `nodeId` 本质是 `dentryUuid`,可直接用于正文读写
> - 通过创建文档接口新建的文档:可使用响应中的 `docKey` 或 `dentryUuid`
> - 创建响应中的 `nodeId`(通常出现在 `/docs/<nodeId>` 链接中)不能直接用于正文读写
响应:
```json
{
"result": {
"data": [
{ "blockType": "heading", "heading": { "level": "heading-2", "text": "快速开始" }, "index": 0, "id": "xxx" },
{ "blockType": "paragraph", "paragraph": { "text": "正文内容..." }, "index": 1, "id": "yyy" },
{ "blockType": "table", "table": { "colSize": 2, "rowSize": 10 }, "index": 2, "id": "zzz" },
{ "blockType": "unknown", "index": 3, "id": "aaa", "unknown": {} }
]
},
"success": true
}
```
`blockType` 枚举:`heading``paragraph``unorderedList``orderedList``table``blockquote``unknown`(代码块/图片等)
---
## 9. 覆盖写入文档内容
```
POST https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/overwriteContent?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
> ⚠️ `operatorId` 必须**同时**作为 query param 和请求体字段传入,缺一会报 `Missingcontent` 错误。
请求体:
```json
{
"operatorId": "{OPERATOR_ID}",
"content": "# 新标题\n\n新的正文内容支持 Markdown 格式。",
"contentType": "markdown"
}
```
> ⚠️ 此操作**全量覆盖**文档内容,不可撤销。
---
## 10. 追加文本到段落
```
POST https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/blocks/{blockId}/paragraph/appendText
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
请求体:
```json
{ "operatorId": "{OPERATOR_ID}", "text": "追加的文字" }
```
---
## 11. 添加文档成员
```
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
请求体:
```json
{
"operatorId": "{OPERATOR_ID}",
"members": [
{ "id": "<userId>", "roleType": "viewer" }
]
}
```
| 参数 | 说明 |
|---|---|
| `id` | 用户 userId**注意**:这里用 userId不是 unionId|
| `roleType` | `viewer`(只读)| `editor`(可编辑)|
---
## 12. 更新文档成员权限
```
PUT https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members/{memberId}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
请求体:
```json
{ "operatorId": "{OPERATOR_ID}", "roleType": "viewer" }
```
---
## 13. 移除文档成员
```
DELETE https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members/{memberId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
---
## 14. 搜索文档(用于找回可读写 ID
```
GET https://api.dingtalk.com/v1.0/doc/docs?operatorId={OPERATOR_ID}&workspaceId={workspaceId}&keyword={keyword}&maxResults=20&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}
```
说明:
- 需要权限:`Document.WorkspaceDocument.Read`
- 返回结果中的 `docs[].nodeBO.nodeId` 即可用于 `/v1.0/doc/suites/documents/{id}` 的读写(该值是 `dentryUuid` 风格 ID
示例响应(节选):
```json
{
"docs": [
{
"nodeBO": {
"name": "测试创建.adoc",
"nodeId": "np9zOoBVBYwB2OZBIn4y0G1vW1DK0g6l",
"url": "https://alidocs.dingtalk.com/i/nodes/np9zOoBVBYwB2OZBIn4y0G1vW1DK0g6l"
},
"workspaceBO": {
"workspaceId": "QXvd5SLN2AxOQz0Z"
}
}
],
"hasMore": false
}
```
---
## 错误码
| HTTP状态码 | 错误码 | 说明 | 处理建议 |
|---|---|---|---|
| 400 | `MissingoperatorId` | operatorId 未传 | 补充 operatorIdunionId|
| 400 | `paramError` | 参数类型错误 | operatorId 必须是 unionId不是 userId |
| 403 | `Forbidden.AccessDenied.AccessTokenPermissionDenied` | 应用缺少权限 | 错误中有 `requiredScopes`,开通对应权限 |
| 404 | `InvalidAction.NotFound` | 接口路径不存在 | 检查版本号v1.0/v2.0)和路径 |
| 429 | — | QPS 限流 | 1 秒后重试 |
---
## 所需应用权限
| 功能 | 权限 scope |
|---|---|
| 查询知识库/节点 | `Wiki.Node.Read` |
| 读取文档正文 | `Storage.File.Read` |
| 写入文档正文 | `Storage.File.Write` |
| 创建/删除文档 | `Storage.File.Write` |
| 查询用户 unionId | `Contact.User.Read` |

View File

@@ -0,0 +1,381 @@
#!/bin/bash
# =============================================================================
# dt_helper.sh — 钉钉开放平台辅助工具
# 路径: scripts/common/dt_helper.sh
# 用法: bash scripts/common/dt_helper.sh <命令> [参数]
# =============================================================================
set -e
CONFIG="${DINGTALK_CONFIG:-$HOME/.dingtalk-skills/config}"
# ─────────────────────────────────────────────────────────────────────────────
# 帮助信息
# ─────────────────────────────────────────────────────────────────────────────
show_help() {
cat <<'EOF'
钉钉开放平台辅助工具 (dt_helper.sh)
用法: bash scripts/common/dt_helper.sh <命令> [参数]
Token 管理(两种 token 互不兼容,按域名区分):
--token [--nocache] 获取新版 accessToken用于 api.dingtalk.com 域名的所有接口)
适用待办、文档、AI 表格等 api.dingtalk.com 域名下所有版本的接口
请求头x-acs-dingtalk-access-token: <token>
有缓存且未过期则直接返回,否则自动刷新并缓存
--nocache跳过缓存强制重新获取token 被提前吊销时使用)
--token-info 查看新版 token 缓存状态(是否有效、剩余有效秒数)
--clear-token 清除缓存的新版 token下次 --token 时强制重新获取)
--old-token [--nocache]
获取旧版 access_token用于 oapi.dingtalk.com 域名的所有接口)
适用:群消息/工作通知/userId↔unionId 转换等 oapi.dingtalk.com 接口
不适用api.dingtalk.com 接口如待办、文档、AI表格
⚠️ 新旧两种 token 互不兼容,混用会导致 401/403
--nocache跳过缓存强制重新获取token 被提前吊销时使用)
身份转换:
--to-unionid [userId] 将 userId 转换为 unionId
不传参数:转换配置中的 DINGTALK_MY_USER_ID操作者自身
结果首次自动写入 DINGTALK_MY_OPERATOR_ID
传入参数:动态转换指定 userId仅返回结果不写入配置
--to-userid [unionId] 将 unionId 反向转换为 userId需传入参数
配置管理:
--config 查看 ~/.dingtalk-skills/config 中的所有配置项(敏感项脱敏显示)
--get KEY [KEY...] 获取一个或多个配置项的值(敏感项脱敏显示)
--set KEY=VALUE 将配置项持久化写入配置文件(已存在则更新,不存在则追加,目录自动创建)
帮助:
--help, -h 显示此帮助信息
环境变量:
DINGTALK_CONFIG 覆盖默认配置文件路径(默认 ~/.dingtalk-skills/config
配置文件:
~/.dingtalk-skills/config key=value 格式,存储以下键:
DINGTALK_APP_KEY 应用 Client IDAppKey
DINGTALK_APP_SECRET 应用 Client SecretAppSecret
DINGTALK_MY_USER_ID 企业员工 IDuserId管理后台通讯录可查
DINGTALK_MY_OPERATOR_ID 操作者 unionId由 --to-unionid 自动生成)
DINGTALK_ACCESS_TOKEN 新版 token 缓存
DINGTALK_TOKEN_EXPIRY 新版 token 过期时间戳Unix 秒)
DINGTALK_OLD_TOKEN 旧版 token 缓存
DINGTALK_OLD_TOKEN_EXPIRY 旧版 token 过期时间戳Unix 秒)
EOF
}
# ─────────────────────────────────────────────────────────────────────────────
# 工具函数
# ─────────────────────────────────────────────────────────────────────────────
# 从配置文件读取指定键的值
cfg_get() {
local key="$1"
grep "^${key}=" "$CONFIG" 2>/dev/null | head -1 | cut -d= -f2-
}
# 写入或更新配置文件中的键值
cfg_set() {
local key="$1"
local value="$2"
mkdir -p "$(dirname "$CONFIG")"
touch "$CONFIG"
if grep -q "^${key}=" "$CONFIG" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${value}|" "$CONFIG"
else
echo "${key}=${value}" >> "$CONFIG"
fi
}
# 从配置文件删除指定键
cfg_del() {
local key="$1"
sed -i "/^${key}=/d" "$CONFIG" 2>/dev/null || true
}
# 确保必须的配置项存在,否则报错退出
require_cfg() {
local key="$1"
local val
val=$(cfg_get "$key")
if [ -z "$val" ]; then
echo "❌ 缺少配置项 ${key},请先运行: bash scripts/common/dt_helper.sh --set ${key}=<值>" >&2
exit 1
fi
echo "$val"
}
# ─────────────────────────────────────────────────────────────────────────────
# Token 管理
# ─────────────────────────────────────────────────────────────────────────────
cmd_token() {
local force="${1:-}" app_key app_secret cached expiry now resp token expire_in
app_key=$(require_cfg DINGTALK_APP_KEY)
app_secret=$(require_cfg DINGTALK_APP_SECRET)
now=$(date +%s)
if [ "$force" != "--nocache" ]; then
cached=$(cfg_get DINGTALK_ACCESS_TOKEN)
expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)
if [ -n "$cached" ] && [ -n "$expiry" ] && [ "$now" -lt "$expiry" ]; then
echo "$cached"
return 0
fi
fi
# 过期或无缓存,重新获取
resp=$(curl -s -X POST "https://api.dingtalk.com/v1.0/oauth2/accessToken" \
-H "Content-Type: application/json" \
-d "{\"appKey\":\"${app_key}\",\"appSecret\":\"${app_secret}\"}")
token=$(echo "$resp" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
expire_in=$(echo "$resp" | grep -o '"expireIn":[0-9]*' | cut -d: -f2)
if [ -z "$token" ]; then
echo "❌ 获取 token 失败: $resp" >&2
exit 1
fi
cfg_set DINGTALK_ACCESS_TOKEN "$token"
cfg_set DINGTALK_TOKEN_EXPIRY "$((now + expire_in - 200))"
echo "$token"
}
cmd_token_info() {
local cached expiry now remaining
cached=$(cfg_get DINGTALK_ACCESS_TOKEN)
expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)
now=$(date +%s)
if [ -z "$cached" ]; then
echo "状态: 无缓存(从未获取或已清除)"
return 0
fi
if [ -z "$expiry" ] || [ "$now" -ge "$expiry" ]; then
echo "状态: 已过期"
echo "Token: ${cached:0:20}..."
else
remaining=$((expiry - now))
echo "状态: 有效"
echo "Token: ${cached:0:20}..."
echo "剩余: ${remaining} 秒(约 $((remaining / 60)) 分钟)"
fi
}
cmd_clear_token() {
cfg_del DINGTALK_ACCESS_TOKEN
cfg_del DINGTALK_TOKEN_EXPIRY
echo "✅ 新版 Token 缓存已清除"
}
cmd_old_token() {
# 旧版 access_token用于所有 oapi.dingtalk.com 接口:
# - 群消息、工作通知、互动卡片dingtalk-message
# - userId ↔ unionId 转换
# ⚠️ 不可用于 api.dingtalk.com 接口待办、文档、AI表格等
local force="${1:-}" app_key app_secret resp token cached expiry now
app_key=$(require_cfg DINGTALK_APP_KEY)
app_secret=$(require_cfg DINGTALK_APP_SECRET)
now=$(date +%s)
if [ "$force" != "--nocache" ]; then
cached=$(cfg_get DINGTALK_OLD_TOKEN)
expiry=$(cfg_get DINGTALK_OLD_TOKEN_EXPIRY)
if [ -n "$cached" ] && [ -n "$expiry" ] && [ "$now" -lt "$expiry" ]; then
echo "$cached"
return 0
fi
fi
resp=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${app_key}&appsecret=${app_secret}")
token=$(echo "$resp" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
expires_in=$(echo "$resp" | grep -o '"expires_in":[0-9]*' | cut -d: -f2)
if [ -z "$token" ]; then
echo "❌ 获取旧版 token 失败: $resp" >&2
exit 1
fi
cfg_set DINGTALK_OLD_TOKEN "$token"
cfg_set DINGTALK_OLD_TOKEN_EXPIRY "$((now + expires_in - 200))"
echo "$token"
}
# ─────────────────────────────────────────────────────────────────────────────
# 身份转换
# ─────────────────────────────────────────────────────────────────────────────
cmd_to_unionid() {
local user_id="$1"
local is_self=false
local old_token resp union_id
# 未传参 → 使用配置中的操作者自身 userId转换结果写入配置
if [ -z "$user_id" ]; then
user_id=$(require_cfg DINGTALK_MY_USER_ID)
is_self=true
fi
old_token=$(cmd_old_token)
resp=$(curl -s -X POST \
"https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${old_token}" \
-H "Content-Type: application/json" \
-d "{\"userid\":\"${user_id}\"}")
# 注意:使用无下划线的 unionid 字段(有下划线的 union_id 可能为空)
union_id=$(echo "$resp" | grep -o '"unionid":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$union_id" ]; then
echo "❌ userId→unionId 转换失败: $resp" >&2
exit 1
fi
# 仅当转换的是操作者自身时,才写入配置(动态转换他人 userId 不写入)
if "$is_self" && [ -z "$(cfg_get DINGTALK_MY_OPERATOR_ID)" ]; then
cfg_set DINGTALK_MY_OPERATOR_ID "$union_id"
echo "✅ 自身 unionId 已写入配置 DINGTALK_MY_OPERATOR_ID" >&2
fi
echo "$union_id"
}
cmd_to_userid() {
local union_id="$1"
local old_token resp user_id
if [ -z "$union_id" ]; then
echo "❌ 请提供 unionId 参数" >&2
exit 1
fi
old_token=$(cmd_old_token)
resp=$(curl -s -X POST \
"https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${old_token}" \
-H "Content-Type: application/json" \
-d "{\"unionid\":\"${union_id}\"}")
user_id=$(echo "$resp" | grep -o '"userid":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$user_id" ]; then
echo "❌ unionId→userId 转换失败: $resp" >&2
exit 1
fi
echo "$user_id"
}
# ─────────────────────────────────────────────────────────────────────────────
# 配置管理
# ─────────────────────────────────────────────────────────────────────────────
cmd_config() {
if [ ! -f "$CONFIG" ]; then
echo "配置文件不存在: $CONFIG"
echo "使用 --set KEY=VALUE 写入配置项"
return 0
fi
echo "配置文件: $CONFIG"
echo "─────────────────────────────────"
# 脱敏显示 SECRET 和 TOKEN
while IFS= read -r line; do
key="${line%%=*}"
val="${line#*=}"
case "$key" in
DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)
echo "${key}=${val:0:6}***(已脱敏)"
;;
*)
echo "$line"
;;
esac
done < "$CONFIG"
}
cmd_get() {
if [ $# -eq 0 ]; then
echo "❌ 请提供至少一个键名,用法: --get KEY [KEY2 ...]" >&2
exit 1
fi
for key in "$@"; do
val=$(cfg_get "$key")
if [ -z "$val" ]; then
echo "${key}=(未设置)"
else
case "$key" in
DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)
echo "${key}=${val:0:6}***(脱敏)"
;;
*)
echo "${key}=${val}"
;;
esac
fi
done
}
cmd_set() {
local kv="$1"
if [ -z "$kv" ] || [[ "$kv" != *"="* ]]; then
echo "❌ 格式错误,用法: --set KEY=VALUE" >&2
exit 1
fi
local key="${kv%%=*}"
local value="${kv#*=}"
cfg_set "$key" "$value"
echo "✅ 已设置 ${key}"
}
# ─────────────────────────────────────────────────────────────────────────────
# 入口:解析命令
# ─────────────────────────────────────────────────────────────────────────────
CMD="${1:-}"
case "$CMD" in
--help|-h|"")
show_help
;;
--token)
cmd_token "${2:-}"
;;
--token-info)
cmd_token_info
;;
--clear-token)
cmd_clear_token
;;
--old-token)
cmd_old_token "${2:-}"
;;
--to-unionid)
cmd_to_unionid "${2:-}"
;;
--to-userid)
cmd_to_userid "${2:-}"
;;
--config)
cmd_config
;;
--get)
shift
cmd_get "$@"
;;
--set)
cmd_set "${2:-}"
;;
*)
echo "❌ 未知命令: $CMD" >&2
echo "运行 --help 查看用法" >&2
exit 1
;;
esac

View File

@@ -0,0 +1,86 @@
# 钉钉工作日志查询
查询钉钉工作日志(周报/日报等)。
## 接口信息
- **接口地址**: `POST https://oapi.dingtalk.com/topapi/report/list`
- **认证方式**: access_token基于企业内部应用
- **时间单位**: ⚠️ **毫秒**(不是秒!)
## 前置条件
1. 获取 access_token
```bash
curl -s -X POST 'https://api.dingtalk.com/v1.0/oauth2/accessToken' \
-H 'Content-Type: application/json' \
-d '{
"appKey": "dingklemniq8uqk5qbgx",
"appSecret": "_8EHgyhvHRHRMx6fZbh9LNpQoxyYl3At0b-fXXlQiahwupbt9oY5P6Grj8IM9Dx8"
}'
```
2. 凭证存储在 `TOOLS.md`
- AgentId: 4404185308
- Client ID (AppKey): dingklemniq8uqk5qbgx
- Client Secret (AppSecret): _8EHgyhvHRHRMx6fZbh9LNpQoxyYl3At0b-fXXlQiahwupbt9oY5P6Grj8IM9Dx8
## 调用示例
```bash
# 查询指定用户的日志(时间单位:毫秒)
curl -s 'https://oapi.dingtalk.com/topapi/report/list?access_token={TOKEN}' \
-H 'Content-Type: application/json' \
-d '{
"userid": "121922510028034588",
"offset": 0,
"size": 10,
"start_time": 1738329600000,
"end_time": 1743292800000,
"cursor": 0
}'
```
## 参数说明
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userid | string | ✅ | 用户的钉钉 userid |
| start_time | number | ✅ | 开始时间,**毫秒**时间戳 |
| end_time | number | ✅ | 结束时间,**毫秒**时间戳 |
| cursor | number | ✅ | 分页游标(首次查询传 0 |
| size | number | ✅ | 每页数量(建议 10 |
| offset | number | ❌ | 偏移量(兼容旧版,可不传) |
## 返回字段说明
日志内容在 `result.data_list` 数组中,每个元素的 `contents` 数组包含各字段:
| contents.key | 说明 |
|-------------|------|
| 本周完成工作 / 本日完成工作 | 完成事项 |
| 本周工作总结 / 本日工作总结 | 详细总结 |
| 下周工作计划 / 下日工作计划 | 后续计划 |
| 需协调与帮助 | 协调事项 |
| 图片 | 图片列表 |
| 附件 | 附件列表 |
其他字段:
- `report_id`: 日志ID
- `template_name`: 日志模板名称(如"经理人周报"
- `creator_name`: 创建人姓名
- `dept_name`: 部门名称
- `create_time`: 创建时间(毫秒)
## 分页查询
通过 `cursor` 分页:
1. 首次查询 `cursor: 0`
2. 返回 `next_cursor` 作为下次查询的游标
3. `has_more: false` 表示最后一页
## 注意事项
1. ⚠️ **时间单位必须是毫秒**:如 `1743292800000`(不能用 `1743292800`
2. ⚠️ **字段名是 `userid`(全小写)**,不是 `userId`
3. 部分日志内容可能超长被截断,返回可能不完整

View File

@@ -0,0 +1,54 @@
#!/bin/bash
# 钉钉工作日志查询脚本
# 配置(从 TOOLS.md 读取)
APP_KEY="dingklemniq8uqk5qbgx"
APP_SECRET="_8EHgyhvHRHRMx6fZbh9LNpQoxyYl3At0b-fXXlQiahwupbt9oY5P6Grj8IM9Dx8"
API_BASE="https://oapi.dingtalk.com"
# 获取 access_token
get_token() {
curl -s -X POST 'https://api.dingtalk.com/v1.0/oauth2/accessToken' \
-H 'Content-Type: application/json' \
-d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}" | \
grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4
}
# 查询日志
query_logs() {
local userid=$1
local start_time=$2 # 毫秒
local end_time=$3 # 毫秒
local size=${4:-10}
local cursor=${5:-0}
local token=$6
curl -s "$API_BASE/topapi/report/list?access_token=$token" \
-H 'Content-Type: application/json' \
-d "{
\"userid\": \"$userid\",
\"start_time\": $start_time,
\"end_time\": $end_time,
\"cursor\": $cursor,
\"size\": $size
}"
}
# 解析毫秒时间戳为日期
ms_to_date() {
local ms=$1
date -d @$((ms / 1000)) "+%Y-%m-%d %H:%M:%S"
}
# 帮助信息
usage() {
echo "钉钉工作日志查询"
echo ""
echo "用法: $0 <userid> <start_time_ms> <end_time_ms> [size] [cursor]"
echo ""
echo "示例:"
echo " $0 121922510028034588 1738329600000 1743292800000"
echo " $0 121922510028034588 1738329600000 1743292800000 20 0"
echo ""
echo "时间戳获取: date +%s000000 (mac/linux)"
}