做企业微信接口调用这件事,我忍了很久。官方提供的HTTP API本身不复杂,但每次都要拿Token、拼JSON、处理返回码,脚本里堆一堆curl真的很痛苦。尤其当你有多个企业微信应用、多个环境要切换的时候,简直是灾难。所以我花了些业余时间,把一个支持通过命令行调用企业微信接口能力的CLI开源项目整理了出来,用了一段时间,稳定性和效率都还不错,今天就把这个项目的设计和用法完整拆给你。
这套CLI把企业微信常用的接口能力封装成了子命令,比如发送消息、管理通讯录、操作客户联系、处理外部联系人,底层只需要一个配置文件和各种环境变量,运行在Linux、macOS、Windows上都能用。特别适合运维告警、CI/CD通知、自动化脚本里需要快速操作企业微信的场景,也适合做嵌入式Linux板卡或服务器上的轻量接入。它的核心价值在于:你不再需要写一坨又一坨的Python或Java代码去对接,也不需要每次手动去搞Token,各种接口统一走一套命令语法。
如果你是刚接触企业微信API的小白,或者被多应用多环境切换折腾过的老手,这个项目都能帮你把复杂度降下来。项目开源在GitHub上,纯Go编写,无额外运行时依赖,拿过去改一改也能二次集成。
下面我从设计思路、核心功能、实操过程、故障排查四个方向,把这个CLI开源项目讲透。
1. 项目背景与整体设计思路
1.1 为什么非要用CLI调用企业微信接口
企业微信目前提供了一套完整的HTTP接口,官方也给了多种语言的SDK,比如Go、Java、Python都有。那CLI的意义在哪里?我的判断是两个字:轻量。
很多运维场景其实只是想“发一条消息”“查一个成员”“同步一个部门”,你却要写一个独立的服务,引入SDK依赖,还要处理各种异常重试。这种方式放在正式的微服务架构里没问题,但在边缘脚本、定时任务、Shell编排里就非常笨重。
CLI的存在,相当于把接口调用变成了命令行的原子操作。你可以直接写出这样的管道逻辑:
echo "磁盘告警:/dev/sda1使用率95%" | wework-cli msg send --to "@all" --type text这条命令放到任何脚本里都能跑,没有SDK版本冲突,也不需要额外起进程。它还天然支持“一次性执行”,用完即走,非常适合无状态的任务型场景。
另外,CLI天然适合做“接口能力的快速验证”。我经常在排查企业微信回调问题时,直接在终端里调一下接口,看看是参数问题还是权限问题,比打开Postman选环境、填Token要快得多。
1.2 与官方SDK、HTTP直连的对比选型
在设计这个项目之前,我认真对比了三种方案:
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 官方SDK | 类型安全、功能全面、社区支持好 | 依赖重、需要写代码、调试验证麻烦 | 业务系统开发 |
| HTTP直连(curl) | 零依赖、灵活 | Token管理繁琐、参数容易出错、代码复用性差 | 临时调试 |
| CLI封装 | 即装即用、脚本友好、统一鉴权 | 需要一次封装成本、特定接口扩展需更新 | 自动化脚本、告警、轻量接入 |
选型结论很明确:CLI是SDK和curl之间的最佳折中。它保留了curl的直接,又吸收了SDK的封装思想。对于“用完即走”的场景,CLI的体验完全压倒其他两种。
1.3 整体架构与目录设计
项目逻辑上分为四层:
- 配置层:负责读取配置文件、环境变量,统一管理corpId、agentId、secret、API地址前缀。
- 鉴权层:自动获取并缓存access_token,处理过期刷新。
- 命令层:负责解析命令行参数,生成接口请求。
- 输出层:统一处理响应,格式化输出JSON,支持错误码解释。
目录结构大致如下:
wework-cli/ ├── cmd/ │ ├── msg.go # 消息发送命令 │ ├── user.go # 成员管理命令 │ ├── dept.go # 部门管理命令 │ └── customer.go # 客户联系命令 ├── internal/ │ ├── config/ # 配置加载 │ ├── auth/ # Token管理 │ ├── httpclient/ # HTTP请求封装 │ └── output/ # 结果格式化 ├── main.go └── README.md这个结构非常简单,只要你了解过企业微信API,基本看一眼就能修改扩展。
核心设计原则有三个:一是“无状态优先”,每次命令行执行都独立携带自己的配置和鉴权上下文,这样多环境切换只需要改环境变量而不是重装软件;二是“错误可读”,遇到企业微信返回的错误码时,直接给出手册里对应的解释,而不是抛出一串数字;三是“管道友好”,输入输出都尽量兼容stdin/stdout,方便和grep、jq等工具配合。
2. 核心功能与接口能力拆解
2.1 覆盖了哪些企业微信接口
企业微信API接口非常多,这个CLI项目最初版本优先覆盖了日常使用频率最高的几类:
- 消息推送:文本、文本卡片、Markdown、图片、图文、语音、视频、文件等消息类型,支持发送给成员、部门或标签。
- 通讯录管理:创建/更新/删除部门,获取部门列表;创建/更新/删除成员,获取成员详情,批量监听成员变更。
- 客户联系:获取客户列表、客户详情,配置客户联系规则,管理群发消息。
- 媒体素材:上传临时素材到企业微信服务器,获取素材地址。
- 发送应用消息:以应用身份推送消息到个人或全员,常用来做内部通知告警。
比如发送Markdown消息的命令格式是:
wework-cli msg send-markdown --to "@all" --content "# 版本发布通知\n发布分支: dev-1.2.3"这里边的关键设计是把企业微信的复杂入参扁平化。企业微信的消息体里,Markdown消息需要构造一个markdown对象,CLI直接把它拆成--content参数,你在Shell里不用再组装嵌套JSON。
2.2 鉴权流程是怎么自动化的
企业微信接口调用的第一个拦路虎是access_token。旧的做法是每次请求之前手动调一次gettoken接口拿到Token,然后复制粘贴到请求里。CLI项目里我设计了一套自动鉴权机制:
- 首次执行时,读取配置文件里的
corp_id、agent_id、secret。 - 自动向
/cgi-bin/gettoken接口发起请求,获取access_token。 - 将Token和过期时间缓存到本地文件(默认存储在
~/.wework-cli/token.json)。 - 每次命令执行时先检查本地缓存是否有效,过期再刷新。
这里有一个细节值得注意:企业微信的access_token有效期为7200秒,但官方建议每7200秒刷新一次,并且要避免频繁调用。CLI里做了预判,提前5分钟判断Token是否过期,这样可以避免临界问题。
如果你搭建了企业微信网关,也可以把获取Token的操作委托给网管,CLI只需要从环境变量WEWORK_TOKEN_URL里读取一个自定义获取Token的URL,这样就能无缝嵌入到企业内部的统一鉴权体系中。
2.3 配置管理的三种方式
CLI支持三种配置来源,优先级从高到低为:命令行参数 > 环境变量 > 配置文件。这是很多用CLI工具的老朋友的共识,因为要保证临时覆盖和长期默认都存在。
配置文件默认路径是~/.wework-cli/config.yaml,内容大致如下:
corp_id: "ww1234567890" agent_id: "1000002" secret: "your-secret" api_base: "https://qyapi.weixin.qq.com"对应的环境变量是WEWORK_CORP_ID、WEWORK_AGENT_ID、WEWORK_SECRET。如果你在CI/CD里用,推荐用环境变量的方式,避免在代码仓库里直接暴露密钥。
命令行参数优先级最高,比如你某一次临时想用另一个应用的密钥:
wework-cli --corp-id ww1111 --agent-id 1000003 --secret xxxx msg send --to "user1" --content "test"这种设计的好处是,一个CLI二进制能同时操作多个企业微信应用,不用反复修改配置。
3. 实操过程与核心环节实现
3.1 安装方式
因为是Go语言项目,编译后只有一个二进制文件,安装非常方便。我日常使用的方式有几种:
# 方式一:使用Go安装 go install github.com/yourname/wework-cli@latest # 方式二:直接用发布页面的预编译二进制 wget https://github.com/yourname/wework-cli/releases/download/v0.1.0/wework-cli_linux_amd64.tar.gz tar -zxvf wework-cli_linux_amd64.tar.gz sudo mv wework-cli /usr/local/bin/安装完成后验证:
wework-cli version会输出当前的版本号和编译信息。如果你在麒麟系统或Ubuntu等Linux环境里用,预编译的静态二进制基本可以直接跑,不用额外依赖glibc。
3.2 快速发送第一条消息
假设你已经有一个企业微信自建应用,下面直接从零开始发一条文本消息。
第一步,创建配置文件:
mkdir -p ~/.wework-cli cat > ~/.wework-cli/config.yaml <<EOF corp_id: "ww1234567890" agent_id: "1000002" secret: "your-secret" EOF第二步,发送消息:
wework-cli msg send --to "user1" --type text --content "你好,企业微信CLI"如果一切正常,你应该在预先设置的应用可见范围内收到这条消息。
这里必踩的一个坑是:企业微信要求应用需要有对应成员的可见权限,否则即使接口返回成功,成员也看不到消息。有时候你调接口时返回成功,但用户没收到,多半就是可见范围没配置。
3.3 发送不同类型的消息
消息发送是CLI最核心的使用场景,下面列举几个常用类型。
文本卡片消息:
wework-cli msg send-card --title "告警通知" --desc "服务器CPU使用率超过90%" --url "http://monitor.internal/alert/123" --btntxt "查看详情"Markdown消息:
echo -e "## 部署成功\n<font color=\"info\">版本</font> 1.2.3" | wework-cli msg send-markdown --to "@all"这种从stdin读内容的用法在管道脚本里特别好使。比如你想把测试结果直接发出来:
go test ./... 2>&1 | wework-cli msg send-markdown --to "dev_group"值得一提的是,企业微信Markdown消息的语法是简化版,不支持完整的HTML,比如红色字体需要用<font color="warning">标签,且不能嵌套,这部分我在命令的帮助文档里给了对照表。
3.4 通讯录管理的典型操作
通讯录接口在企业微信API里权限等级要求比较高,通常需要在管理后台开启“通讯录同步”API接口权限。CLI封装了最常用的几个操作。
创建部门:
wework-cli dept create --name "研发部" --parent-id 1 --order 10创建后返回部门ID,可以用于后续成员归属。更新成员:
wework-cli user update --userid "zhangsan" --name "张三" --department "[1,2]" --mobile "13800138000"获取所有成员列表:
wework-cli user list --dept-id 1 --fetch-child 1注意这里的--mobile参数,它是企业微信通讯录里的敏感字段,读取时可能需要“明文展示”权限,如果没有权限,接口会返回带掩码的手机号。我最初实现时直接把它转存到本地,导致后续同步数据缺失,后来在文档里特别标注了要申请敏感字段权限。
3.5 对接告警机器人和消息通知
这个CLI做告警通知的接入非常合适。以Zabbix、Prometheus告警脚本为例,原来你要写一个Python脚本用SDK发消息,现在只需要一行:
/usr/local/bin/wework-cli msg send --to "operation_group" --type markdown --content "$(cat /tmp/alert.md)"再配合企业微信自建应用的“群机器人”Webhook,你甚至可以不申请通讯录权限,只用到群机器人的URL即可。我在CLI里加了一个隐藏命令msg send-robot,专门对接群机器人:
wework-cli msg send-robot --webhook-url "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx" --content "自定义消息"这种方式适合“只想发消息,不想管企业微信应用配置”的场景。两条路径各有优劣:应用消息可以精准触达个人,群机器人只能发到特定群里,两者可以各取所需。
3.6 关键参数的计算与边界处理
在做CLI参数设计时,有几个细节值得在这里展开。
第一个是企业微信的消息长度限制。普通文本消息最长不超过2048字节,Markdown消息最长不超过4096字节,超出会被直接截断或报错。CLI在发送前会做一次字节长度检查,超长时自动截断并追加[...],避免脚本里printf出来的长文本直接接口报错。
第二个是发送对象格式。企业微信接口的touser字段,当发送给多人时需要用竖线分隔,比如user1|user2|user3。CLI支持多次传参,也支持按逗号分隔,然后在内部统一拼接:
wework-cli msg send --to "user1,user2" --content "hello"这样从习惯上更接近命令行风格,减少了不必要的转义错误。
第三个是编码问题。Windows环境下,控制台默认GBK编码,直接传中文内容给CLI容易出现乱码。我在内部做了UTF-8规范化,但更推荐在Windows下用PowerShell执行时先设置[Console]::OutputEncoding = [System.Text.Encoding]::UTF8。
4. 常见问题与排查技巧实录
这半年里我收到过不少issue和私信,绝大多数问题集中在下面几个点,我把它们整理成了一份速查表,基本覆盖了实际使用中会遇到的坑。
4.1 报错码速查与应对
| 企业微信错误码 | 含义 | 应对方法 |
|---|---|---|
| 40001 | access_token无效或过期 | 检查secret和应用ID,删除本地token缓存后重试 |
| 40002 | 不合法的凭证类型 | 检查是否把应用的secret和通讯录同步的secret搞混了 |
| 40014 | 不合法的access_token | 确认应用是否停用,或是否被其他请求抢占导致刷新失败 |
| 42001 | access_token已过期 | 重新获取,CLI会在日志里提示刷新时间 |
| 60011 | 成员无权限访问 | 检查应用可见范围,是否包含了该成员 |
| 60020 | 成员不在通讯录中 | 确认userid是否准确,是否开启了通讯录加密传输 |
| 85013 | 无效的自建应用 | 确认agentId是否属于该corpId |
| 44004 | 文件素材不存在 | 上传临时素材后再发送,CLI的素材命令可以自动处理3天内有效期 |
这些错误码在CLI输出里会直接翻译成中文提示,并附带排查建议。实际使用中,最让人困惑的是40014和42001同时出现,这时候一般是你本地缓存了旧Token,但服务器端Token已经因为多次获取而失效。CLI启动时如果是单实例调用还好,如果在脚本里并发调用同一个CLI,就一定要预留Token锁机制。
4.2 Token缓存并发冲突
这是我自己踩得最深的坑。早期版本里,如果多个Shell进程同时执行wework-cli msg send,多个进程会同时读取到同一个即将过期的Token,然后同时刷新,企业微信同一时间只允许一个有效Token,后刷新的会把前面的顶掉,导致部分请求出现401错误。
解决方案是加了一个进程级文件锁。具体实现很直接:在缓存Token之前,创建~/.wework-cli/token.lock文件,通过O_CREATE|O_EXCL的方式保证只有一个进程能拿到锁,拿不到的进程等待100毫秒后再重新读取缓存。这样即使你有二三十个并发告警同时触发,也不会打爆企业微信的Token接口。
4.3 回调URL验证失败
如果你用CLI来做企业微信回调验证,比如接收消息回调,需要在验证URL时计算签名。CLI里提供了util verify-callback子命令,输入msg_signature、timestamp、nonce、echostr,它会直接输出解密后的内容。
实际遇到最多的原因是签名算法里的字典序排序乱了。企业微信的加密算法要求将token、timestamp、nonce、echostr四个参数按字典序排序后再拼接,然后做SHA1。很多人会漏掉echostr。我的CLI实现里把这个过程完全内置,你只需要提供四个参数即可。
4.4 中文乱码与数据丢失
在同一台机器上,如果你直接用echo输出内容给CLI,恰好系统locale是C或者GBK,中文内容就会变成乱码。排查方法非常简单,先跑一下locale看看系统当前编码。如果是UTF-8,基本不会乱。若还是乱码,可以用file命令检查输入文件编码:
file /tmp/alert.md如果显示ISO-8859,建议先转码:
iconv -f gbk -t utf-8 /tmp/alert.md | wework-cli msg send-markdown --to "@all"这个转码的过程可以放进你的告警脚本模板里,防止企业微信后台显示乱码。
4.5 接口返回成功但没效果
一种比较隐蔽的问题:接口返回errmsg: ok,但你的消息没有发送成功。这种情况基本都出在应用可见范围身上。企业微信的权限体系比较讲究,发送消息时它虽然不会直接报错,但它会静默地把没有权限的对象忽略掉。
排查思路:
- 访问企业微信管理后台,进入“应用管理”,找到对应自建应用,查看“可见范围”。
- 确认你send的
userid或者deptid确实在这个范围内。 - 如果发送给部门,确认部门下的子部门成员是否也在应用可见范围。
还有一种情况是“第三方应用”和“自建应用”的接口能力不一样,部分接口需要企业微信认证后才有权限。比如获取客户联系客户列表,未认证的主体调用时返回的就是空列表。
5. 项目扩展方向与个人实操体会
5.1 如何二次开发接入自己的业务
CLI项目本身是一个较好的代码模板。如果你所在的团队有自己的内部系统,需要打通企业微信,可以直接在cmd/目录下增加一个子命令,然后复用底层的auth和httpclient包。
比如增加一个“查询审批单状态”的命令,只需要三步:
- 在
cmd/下新建approval.go。 - 定义
--sp_no参数。 - 调用企业微信的审批相关接口,返回结果。
整个过程不到30分钟。这也是我把鉴权、输出和请求三部分拆开的原因,业务代码只关心接口逻辑就行了。
5.2 在CI/CD流水线中的使用
我目前在公司内部已经把这套CLI集成进了Jenkins和GitLab CI。构建完成后自动推送消息到发布群:
wework-cli msg send-card \ --to "devops_group" \ --title "构建成功" \ --desc "项目: $CI_PROJECT_NAME\n分支: $CI_COMMIT_BRANCH\n提交: $CI_COMMIT_SHORT_SHA" \ --url "$CI_PIPELINE_URL"这里注意,流水线环境里密钥不要写在配置文件里,全部用环境变量注入:
export WEWORK_CORP_ID=${WEWORK_CORP_ID} export WEWORK_AGENT_ID=${WEWORK_AGENT_ID} export WEWORK_SECRET=${WEWORK_SECRET}这样配置不进代码库,安全风险小很多。
5.3 周边生态与待办事项
开源之后,有朋友帮项目贡献了一个bash-completion脚本,现在安装后你可以直接敲wework-cli msg send --to <TAB>来自动补全成员ID。这是一个很实用的功能。
后续计划里,我准备做几个事情:
- 支持通过配置文件批量发送消息(比如定时向不同人推送不同内容的周报)。
- 把输出格式支持
--output json,方便和其他自动化工具集成。 - 增加对智能机器人、企业微信客服接口的封装。
最后一个我个人的体会:做这种CLI工具,最难的不是实现接口,而是把接口的“脾气”摸透。企业微信的API文档虽然全,但参数之间的隐形约束特别多。比如发图片时先要上传素材,素材有效期是3天;再比如文本卡片消息的按钮文字字数不能超过4个汉字。这些细节如果不做进CLI的校验逻辑,光靠使用者在脚本里规避,迟早会踩坑。
所以我的建议是:如果你打算把企业微信接口做成内部工具链的一部分,一定要在CLI层多做一些参数预检和错误解释,把官方文档里的限制条件转化成用户能看懂的提示。这套开源CLI虽然是我从解决自己运维痛点出发做的,但从现在的反馈看,它确实替很多人省掉了重复造轮子的时间。你也可以直接基于它改出一个更符合自己团队习惯的版本。