跳到主要内容

企业微信 x Swagger

· 5 分钟阅读
刘贵立

在我们对接企业微信的过程中,我们发现企业微信的 API 非常多,且参数各异。为了最大程度的减少重复工作,减少编码中的低级错误,我们决定借助工具来自动生成企业微信的 API 调用代码。

这个时候就看到了 Swagger 这个项目,一番尝试之后,竟然得到了如此方便好用的一套工具……

Github: github.com/juzibot/wecom-openapi

Demo: wecom-openapi.juzibot.com

OpenAPI 规范

OpenAPI 规范(OAS)定义了一个标准的、语言无关的 RESTful API 接口规范,它可以同时允许开发人员和计算机查看并理解某个服务的功能,而无需访问源代码、文档或网络流量检查,既方便人类学习和阅读,也方便机器阅读。正确定义 OAS 后,开发者可以使用最少的实现逻辑来理解远程服务并与之交互。

此外,文档生成工具可以使用 OpenAPI 规范来生成 API 文档,代码生成工具可以生成各种编程语言下的服务端和客户端代码,测试代码和其他用例。详见:OpenAPI Specification

WeCom-OpenAPI

企业微信并没有提供符合 OpenAPI 规范的文档,所以 WeCom-OpenAPI 项目应运而生。

WeCom-OpenAPI 是利用 NestJS 的 Swagger 集成能力来实现的,@nestjs/swagger 提供了一些里的装饰器(Decorators)来声明 Swagger 属性,方便管理 API 之间的关联关系。

使用方式

把代码克隆下来,安装依赖后启动项目。启动后会在根目录生成 swagger spec 文件 openapi.yaml,同时可以用浏览器打开 http://localhost:3000/openapi 查看 Swagger UI

$ git clone git@github.com:juzibot/wecom-openapi.git

$ cd wecom-opwenapi && npm install

$ npm run start

WeCom-OpenAPI 提供了对企业微信 API 的代理转发,所以你可以在 Swagger UI 上直接对 API 进行调试。首先将你的 AccessToken 配置好。

然后选择本地代理地址即可,这样就不会出现跨域的问题。

代码生成

主流的代码生成工具是 swagger-codegen,但是它生成的 golang 代码不太友好,所以这里推荐使用 go-swagger 项目,但是目前只支持 swagger 2.0 版本,所以需要将上面得到的 openapi.yaml 文件转换成一下版本。这里使用 api-spec-converter 来做版本转换。

$ npm install -g api-spec-converter

# 转换文档版本
$ api-spec-converter --from=openapi_3 --to=swagger_2 \
--syntax=yaml \
--order=alpha \
./openapi.yaml > swagger.yaml

# 安装 go-swaager
$ brew install go-swagger

$ mkdir wecom-api

# 生成 golang 代码,要求目标目录必须在 GO_PATH 中
$ cd wecom-api && go mod init wecom-api

# 生成 golang 代码
$ swagger generate client -f swagger.yaml -t wecom-api

至此,在 wecom-api 目录中会得到生成的代码文件

./wecom-api/
├── client
└── models
  • client 目录中存放 Client 对象和 Parameter、Response 包装对象
  • models 目录存放 Dto、Response 的实体定义

Example

package main

import (
"log"
"wecom-api/client"
"wecom-api/client/department"

"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
)

// 定义鉴权对象
type auth struct{}

var token = "token"

// 实现 ClientAuthInfoWriter 接口
func (a *auth) AuthenticateRequest(req runtime.ClientRequest, reg strfmt.Registry) error {
// 将 token 放在正确的位置
return req.SetQueryParam("access_token", token)
}

func main() {
departmentID := float64(1)
params := department.NewListDepartmentParams()
params.SetID(&departmentID)

resp, err := client.Default.Department.ListDepartment(params, &auth{})
if err != nil {
log.Fatalf("err: %v", err)
}

log.Printf("%v, %s", resp.Payload.Errcode, resp.Payload.Errmsg)

for _, v := range resp.Payload.Department {
log.Printf("%v", v)
}
}