├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── __test__ ├── .dingtalkMain.go ├── GetCompanyDingTalkClient.go ├── getAuthScopes_test.go ├── getCompanyAccessToken_test.go ├── getCompanyTicket_test.go ├── index.html ├── mediaDownloadFile_test.go ├── mediaUpload_test.go └── wow.jpg ├── dingtalk-tools.go ├── docs ├── .vuepress │ └── config.js ├── README.md ├── guide │ ├── README.md │ ├── getting_started.md │ ├── open_api_auth.md │ ├── open_api_user.md │ └── top_api.md └── weixin.png ├── runner └── runner.go └── src ├── constants.go ├── crypto.go ├── dingtalk.go ├── open_api_auth.go ├── open_api_callback.go ├── open_api_chat.go ├── open_api_cspace.go ├── open_api_data.go ├── open_api_department.go ├── open_api_file.go ├── open_api_media.go ├── open_api_message.go ├── open_api_microapp.go ├── open_api_response.go ├── open_api_smartwork.go ├── open_api_sns.go ├── open_api_sso.go ├── open_api_user.go ├── request.go ├── top_api.go ├── top_api_response.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | .company_access_token_file 16 | .company_ticket_file 17 | debug 18 | debug.test 19 | .idea -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Connect to server", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "remote", 12 | "remotePath": "${workspaceRoot}", 13 | "port": 2345, 14 | "host": "127.0.0.1", 15 | "program": "${workspaceRoot}", 16 | "env": {}, 17 | "args": [] 18 | }, 19 | { 20 | "name": "Launch", 21 | "type": "go", 22 | "request": "launch", 23 | "mode": "debug", 24 | "remotePath": "", 25 | "port": 2345, 26 | "host": "127.0.0.1", 27 | "program": "${fileDirname}", 28 | "env": {}, 29 | "args": [], 30 | "showLog": true 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DingTalk Golang SDK 2 | 3 | DingTalk Golang SDK https://github.com/icepy 4 | 5 | # Feature Overview 6 | 7 | - 支持ISV,企业,SSO,SNS(H5 Web App)免登 8 | - 支持个人小程序免登 9 | - 支持对access_token自动续期过期管理(已使用独占锁,请勿再加锁) 10 | - 支持注册钉钉事件回调 11 | - 支持对钉钉事件回调消息签名的加解密 12 | - 支持全部 Open api 13 | - 支持全部 Top api,并且自动处理生成加密签名 14 | 15 | # Test 16 | 17 | - Test get auth scopes 18 | - Test get company acess_token 19 | - Test get company ticket 20 | - Test upload file 21 | - Test download file 22 | 23 | ```bash 24 | ~ ᐅ cd __test__ 25 | ~ ᐅ go test 26 | ``` 27 | 28 | # Install 29 | 30 | ```bash 31 | ~ ᐅ go get -u github.com/icepy/go-dingtalk 32 | ~ ᐅ go-dingtalk 33 | Current SDK VERSION=0.1 34 | Current SDK OAPIURL=https://oapi.dingtalk.com/ 35 | Current SDK TOPAPIURL=https://eco.taobao.com/router/rest 36 | ~ ᐅ 37 | ``` 38 | 39 | # Guide 40 | 41 | ```bash 42 | ~ ᐅ npm i -g vuepress 43 | ~ ᐅ cd docs 44 | ~ ᐅ vuepress dev 45 | ``` 46 | 47 | # Help 48 | 49 | **Example** 50 | 51 | ```go 52 | package main 53 | 54 | import ( 55 | "os" 56 | "github.com/icepy/go-dingtalk/src" 57 | ) 58 | 59 | func main() { 60 | c := getCompanyDingTalkClient() 61 | c.RefreshCompanyAccessToken() 62 | } 63 | 64 | func getCompanyDingTalkClient() *dingtalk.DingTalkClient { 65 | CorpID := os.Getenv("CorpId") 66 | CorpSecret := os.Getenv("CorpSecret") 67 | config := &dingtalk.DTConfig{ 68 | CorpID: CorpID, 69 | CorpSecret: CorpSecret, 70 | } 71 | c := dingtalk.NewDingTalkCompanyClient(config) 72 | return c 73 | } 74 | 75 | ``` 76 | 77 | **ISV免登讲解** 78 | 79 | - 首先调用RefreshSuiteAccessToken刷新suite_access_token 80 | - 等待钉钉推送临时Code给你 81 | 82 | ```go 83 | // 假设你在服务端部署了get_code服务 84 | package main 85 | 86 | import ( 87 | "fmt" 88 | "log" 89 | "net/http" 90 | ) 91 | 92 | func GetCode(w http.ResponseWriter, req *http.Request) { 93 | // 获取临时授权code 94 | // 并返回{success:true} 95 | } 96 | 97 | func main() { 98 | http.HandleFunc("/get_code", GetCode) 99 | err := http.ListenAndServe("localhost:8080", nil) 100 | if err != nil { 101 | log.Fatal("ListenAndServe: ", err.Error()) 102 | } 103 | } 104 | ``` 105 | - 调用IsvGetPermanentCode 106 | - 调用IsvActivateSuite给企业激活套件 107 | - 调用IsvGetCorpAccessToken获取企业的access_token 108 | 109 | ⚠️至此,你要注意 110 | 111 | - 只有isv的suite_access_token是单例 112 | - corp_access_token是不同的,企业a换取来的corp_access_token是1,企业b换取来的corp_access_token是2 113 | - 建议:**isv可以用Redis,总之是这种key-value的存储来维护corp_access_token,企业的永久授权码是不会变的。在没过期之前,根据永久授权码从map里拿corp_access_token,如果过期了,又走一次流程,换取新的永久授权码,继续存入map,更新key-value系统。** 114 | 115 | # Contribute 116 | 117 | - For a small change, just send a PR. 118 | - For bigger changes open an issue for discussion before sending a PR. 119 | - PR should have: 120 | - Test case 121 | - Documentation 122 | - Example (If it makes sense) 123 | - You can also contribute by: 124 | - Reporting issues 125 | - Suggesting new features or enhancements 126 | - Improve/fix documentation 127 | 128 | # 打赏 129 | 130 |
131 | 132 |
133 | 134 | # License 135 | 136 | MIT License 137 | 138 | Copyright (c) 2018 139 | 140 | Permission is hereby granted, free of charge, to any person obtaining a copy 141 | of this software and associated documentation files (the "Software"), to deal 142 | in the Software without restriction, including without limitation the rights 143 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 144 | copies of the Software, and to permit persons to whom the Software is 145 | furnished to do so, subject to the following conditions: 146 | 147 | The above copyright notice and this permission notice shall be included in all 148 | copies or substantial portions of the Software. 149 | 150 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 151 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 152 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 153 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 154 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 155 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 156 | SOFTWARE. 157 | -------------------------------------------------------------------------------- /__test__/.dingtalkMain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func main() { 8 | c := getCompanyDingTalkClient() 9 | c.RefreshCompanyAccessToken() 10 | } 11 | 12 | func getCompanyDingTalkClient() *dingtalk.DingTalkClient { 13 | CorpID := os.Getenv("CorpId") 14 | CorpSecret := os.Getenv("CorpSecret") 15 | config := &dingtalk.DTCompanyConfig{ 16 | CorpID: CorpID, 17 | CorpSecret: CorpSecret, 18 | } 19 | c := dingtalk.NewDingTalkCompanyClient(config) 20 | return c 21 | } 22 | -------------------------------------------------------------------------------- /__test__/GetCompanyDingTalkClient.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "os" 5 | 6 | "../src" 7 | ) 8 | 9 | func GetCompanyDingTalkClient() *dingtalk.DingTalkClient { 10 | CorpID := os.Getenv("CorpId") 11 | CorpSecret := os.Getenv("CorpSecret") 12 | AgentID := os.Getenv("AgentID") 13 | SSOSecret := os.Getenv("SSOSecret") 14 | SNSAppID := os.Getenv("SNSAppID") 15 | SNSSecret := os.Getenv("SNSSecret") 16 | config := &dingtalk.DTConfig{ 17 | CorpID: CorpID, 18 | CorpSecret: CorpSecret, 19 | AgentID: AgentID, 20 | SSOSecret: SSOSecret, 21 | SNSAppID: SNSAppID, 22 | SNSSecret: SNSSecret, 23 | } 24 | c := dingtalk.NewDingTalkCompanyClient(config) 25 | return c 26 | } 27 | -------------------------------------------------------------------------------- /__test__/getAuthScopes_test.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_GetAuthScopes(t *testing.T) { 8 | c := GetCompanyDingTalkClient() 9 | c.RefreshCompanyAccessToken() 10 | data, err := c.GetAuthScopes() 11 | if err != nil { 12 | t.Error("测试获取Auth Scopes 未通过", err) 13 | } else { 14 | t.Log("测试获取Auth Scopes 通过", data) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /__test__/getCompanyAccessToken_test.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_GetCompanyAccessToken(t *testing.T) { 8 | c := GetCompanyDingTalkClient() 9 | c.RefreshCompanyAccessToken() 10 | if c.AccessToken != "" { 11 | t.Log("测试获取access_token通过") 12 | } else { 13 | t.Error("测试获取access_token未通过") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /__test__/getCompanyTicket_test.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_GetCompanyTicket(t *testing.T) { 8 | c := GetCompanyDingTalkClient() 9 | c.RefreshCompanyAccessToken() 10 | ticket, err := c.GetJSAPITicket() 11 | if err != nil { 12 | t.Error("测试未能获取JSAPI Ticket") 13 | } else { 14 | t.Log("测试获取JSAPI Ticket通过", ticket) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /__test__/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /__test__/mediaDownloadFile_test.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "io" 7 | "os" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 13 | 14 | func Test_MediaDownloadFile(t *testing.T) { 15 | mediaID := "@lADPBY0V4zNROQfNBUbNCWA" //填写你刚刚上传的mediaID 16 | name := randStringBytesRmndr() 17 | c := GetCompanyDingTalkClient() 18 | c.RefreshCompanyAccessToken() 19 | ce, ok := os.Create(name + ".jpg") 20 | if ok == nil { 21 | err := c.MediaDownloadFile(mediaID, ce) 22 | if err == nil { 23 | t.Log("测试下载图片通过") 24 | } else { 25 | t.Error("测试下载图片未通过", err) 26 | } 27 | } else { 28 | t.Error("创建图片未通过", ok) 29 | } 30 | } 31 | 32 | func randStringBytesRmndr() string { 33 | t := time.Now() 34 | h := md5.New() 35 | io.WriteString(h, "crazyof.me") 36 | io.WriteString(h, t.String()) 37 | passwd := fmt.Sprintf("%x", h.Sum(nil)) 38 | return passwd 39 | } 40 | -------------------------------------------------------------------------------- /__test__/mediaUpload_test.go: -------------------------------------------------------------------------------- 1 | package dingtalkTest 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func Test_MediaUpload(t *testing.T) { 9 | c := GetCompanyDingTalkClient() 10 | c.RefreshCompanyAccessToken() 11 | o, ok := os.Open("wow.jpg") 12 | if ok == nil { 13 | data, err := c.MediaUpload("image", "wow.jpg", o) 14 | if err != nil { 15 | t.Error("测试图片上传未通过", err) 16 | } else { 17 | if data.MediaID != "" { 18 | t.Log("测试图片上传通过", data) 19 | } else { 20 | t.Error("测试图片上传未能获取到media_id") 21 | } 22 | } 23 | } else { 24 | t.Error("os.Open文件错误", ok) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /__test__/wow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/go-dingtalk/e8997a39181445159113232fb96fab3c2d189289/__test__/wow.jpg -------------------------------------------------------------------------------- /dingtalk-tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/icepy/go-dingtalk/src" 9 | ) 10 | 11 | func main() { 12 | configPath := flag.String("c", "", "config file path") 13 | flag.Parse() 14 | if *configPath != "" { 15 | if _, err := os.Stat(*configPath); err != nil { 16 | fmt.Printf("Can't find config file `%s`\n", *configPath) 17 | os.Exit(1) 18 | } else { 19 | os.Setenv("RUNNER_CONFIG_PATH", *configPath) 20 | } 21 | } 22 | 23 | fmt.Printf("Current SDK VERSION=%s\n", dingtalk.VERSION) 24 | fmt.Printf("Current SDK OAPIURL=%s\n", dingtalk.OAPIURL) 25 | fmt.Printf("Current SDK TOPAPIURL=%s\n", dingtalk.TOPAPIURL) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'DingTalk Golang SDK', 3 | description: '简单,易用,稳定', 4 | themeConfig: { 5 | sidebar: [ 6 | ['/guide/', '介绍'], 7 | ['/guide/getting_started', '起步'], 8 | ['/guide/open_api_auth', '授权'], 9 | ['/guide/open_api_user', '用户相关'], 10 | ['guide/top_api', 'TOP'] 11 | ], 12 | // 假定 GitHub。也可以是一个完整的 GitLab 网址 13 | repo: 'icepy/go-dingtalk', 14 | // 如果你的文档不在仓库的根部 15 | docsDir: 'docs', 16 | // 可选,默认为 master 17 | docsBranch: 'master', 18 | // 默认为 true,设置为 false 来禁用 19 | editLinks: true 20 | } 21 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | actionText: Get Started → 4 | actionLink: /guide/ 5 | features: 6 | - title: 简单 7 | details: 导入SDK,立刻开发基于钉钉的应用 8 | - title: 易用 9 | details: 化繁为简,将复杂的细节问题隐藏,给予你的是简单的输入输出式api 10 | - title: 稳定 11 | details: 编写了完备的测试用例,保障SDK的稳定性 12 | footer: MIT Licensed | Copyright © 2018-present icepy 13 | --- 14 | 15 | ### 起步就像数 1, 2, 3 一样容易 16 | 17 | ``` bash 18 | # 安装 19 | $ go get -u github.com/icepy/go-dingtalk 20 | ``` 21 | 22 | ``` go 23 | package main 24 | 25 | import ( 26 | "os" 27 | "github.com/icepy/go-dingtalk/src" 28 | ) 29 | 30 | func main() { 31 | c := getCompanyDingTalkClient() 32 | c.RefreshCompanyAccessToken() 33 | } 34 | 35 | func getCompanyDingTalkClient() *dingtalk.DingTalkClient { 36 | CorpID := os.Getenv("CorpId") 37 | CorpSecret := os.Getenv("CorpSecret") 38 | config := &dingtalk.DTCompanyConfig{ 39 | CorpID: CorpID, 40 | CorpSecret: CorpSecret, 41 | } 42 | c := dingtalk.NewDingTalkCompanyClient(config) 43 | return c 44 | } 45 | ``` 46 | 47 | ::: warning 注意事项 48 | 请正确的配置你的GOPATH 49 | ::: 50 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | `go-dingtalk`是用Golang编写的钉钉开放平台服务端SDK,着重解决了开发者面临的一些问题,如: 4 | 5 | - `access_token` 续期过期管理 6 | - `TOP API` 生成签名 7 | - 回调消息加解密 8 | - SSO,SNS,企业免登处理 9 | 10 | ... 上述的举例仅仅涉及到一部分,在使用的过程中,你会发现使用`go-dingtalk`SDK开发基于钉钉的服务,变的那么简单,易用。 11 | 12 | ## 运行原理 13 | 14 | `go-dingtalk`实际上是基于go标准库`net/http`开发的一套Api,一个Api的使用就像输入输出式的简单,在输入参数和输出结果上,定义了大量的结构体,你只需要遵循规则,就能很方便的进行开发,而无须关注实现细节。 15 | 16 | ## 已支持 17 | 18 | - 企业`access_token`的续期过期管理以及免登流程 19 | - 企业`sso_access_token`的续期过期管理以及免登流程 20 | - 企业`sns_access_token`的续期过期管理以及免登流程 21 | - 支持对钉钉事件回调消息签名的加解密 22 | - 支持全部 Open api 23 | - 支持全部 Top api,并且自动处理生成加密签名 24 | 25 | ## 待支持 26 | 27 | - ISV`access_token`的续期过期管理以及免登流程 28 | - 小程序`access_token`的续期过期管理以及免登流程 -------------------------------------------------------------------------------- /docs/guide/getting_started.md: -------------------------------------------------------------------------------- 1 | # 起步 2 | 3 | ## 安装 4 | 5 | ```bash 6 | $ go get -u github.com/icepy/go-dingtalk 7 | ``` 8 | 9 | ## 企业 10 | 11 | 使用`NewDingTalkCompanyClient`,并且传入一个`DTCompanyConfig`类型的指针,`DTCompanyConfig`用于用户自己组装配置,如:`CorpID`,`CorpSecret`等,`NewDingTalkCompanyClient`会返回一个`DingTalkClient`类型指针给用户使用。 12 | 13 | **示例** 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "os" 20 | "github.com/icepy/go-dingtalk/src" 21 | ) 22 | 23 | func main() { 24 | c := getCompanyDingTalkClient() 25 | c.RefreshCompanyAccessToken() 26 | } 27 | 28 | func getCompanyDingTalkClient() *dingtalk.DingTalkClient { 29 | CorpID := os.Getenv("CorpId") 30 | CorpSecret := os.Getenv("CorpSecret") 31 | config := &dingtalk.DTCompanyConfig{ 32 | CorpID: CorpID, 33 | CorpSecret: CorpSecret, 34 | } 35 | c := dingtalk.NewDingTalkCompanyClient(config) 36 | return c 37 | } 38 | 39 | ``` 40 | 41 | ## ISV 42 | 43 | 待... 44 | 45 | ## 小程序 46 | 47 | 待... -------------------------------------------------------------------------------- /docs/guide/open_api_auth.md: -------------------------------------------------------------------------------- 1 | # 授权 2 | 3 | 授权是钉钉来验证合法性的一种方式,也是为了保护用户的信息数据安全,当你需要使用免登来拿用户的信息时,授权是非常重要的一个步骤。 4 | 5 | `说明c是调用NewDingTalkCompanyClient返回的一个指针` 6 | 7 | ## RefreshCompanyAccessToken 8 | 9 | 用于获取企业`access_token`,使用者不需要关心续期过期管理。 10 | 11 | **示例** 12 | 13 | ```go 14 | c.RefreshCompanyAccessToken() 15 | c.AccessToken 16 | ``` 17 | 18 | ## RefreshCompanySSOAccessToken 19 | 20 | 用于获取企业`sso_access_token`,使用者不需要关心续期过期管理。 21 | 22 | **示例** 23 | 24 | ```go 25 | c.RefreshCompanySSOAccessToken() 26 | c.SSOAccessToken 27 | ``` 28 | 29 | ## RefreshSNSAccessToken 30 | 31 | 用于获取企业`sns_access_token`,使用者不需要关心续期过期管理。 32 | 33 | **示例** 34 | 35 | ```go 36 | c.RefreshSNSAccessToken() 37 | c.SNSAccessToken 38 | ``` 39 | 40 | ## GetJSAPITicket 41 | 42 | 用于获取企业`Ticket`,使用者不需要关心续期过期管理 43 | 44 | **示例** 45 | 46 | ```go 47 | ticket, err := c.GetJSAPITicket() 48 | if err == nil{ 49 | // ticket 50 | } 51 | ``` 52 | 53 | ## GetConfig 54 | 55 | 配置企业的config,使用者不需要关心签名,返回结果为 `字符串JSON` 56 | 57 | **示例** 58 | 59 | ```go 60 | c.GetConfig(nonceStr, timestamp, url) 61 | ``` 62 | 63 | ## GetAuthScopes 64 | 65 | 用于获取企业的授权范围,返回结果为 `ScopesResponse` 66 | 67 | ```go 68 | type ScopesResponse struct { 69 | OpenAPIResponse 70 | AuthUserField []string 71 | ConditionField []string 72 | AuthOrgScopes 73 | } 74 | 75 | type AuthOrgScopes struct { 76 | AuthedDept []int 77 | AuthedUser []string 78 | } 79 | ``` 80 | 81 | **示例** 82 | 83 | ```go 84 | scopes, err := c.GetAuthScopes() 85 | if err == nil{ 86 | // scopes 87 | } 88 | ``` -------------------------------------------------------------------------------- /docs/guide/open_api_user.md: -------------------------------------------------------------------------------- 1 | # 用户相关 2 | 3 | ## UserIdByCode 4 | 5 | 通过Code换取userid,返回结果为 `UserIdResponse` 6 | 7 | ```go 8 | type UserIdResponse struct { 9 | OpenAPIResponse 10 | UserId string `json:"userid"` 11 | DeviceId string `json:"deviceId"` 12 | IsSys bool `json:"is_sys"` 13 | SysLevel int `json:"sys_level"` 14 | } 15 | ``` 16 | 17 | **示例** 18 | 19 | ```go 20 | c.UserIdByCode(code) 21 | ``` 22 | 23 | ## UserIdByUnionId 24 | 25 | 通过UnionId获取UserId,返回结果为 `UserIdByUnionIdResponse` 26 | 27 | ```go 28 | type UserIdByUnionIdResponse struct { 29 | OpenAPIResponse 30 | UserId string `json:"userid"` 31 | ContactType int `json:"contactType"` 32 | } 33 | ``` 34 | 35 | **示例** 36 | 37 | ```go 38 | c.UserIdByUnionId(unionId) 39 | ``` 40 | 41 | ## UserInfoByUserId 42 | 43 | 通过userid换取用户详细信息,返回结果为 `UserInfoResponse` 44 | 45 | ```go 46 | type UserInfoResponse struct { 47 | OpenAPIResponse 48 | UserId string `json:"userid"` 49 | OpenId string `json:"openid"` 50 | Name string 51 | Tel string 52 | WorkPlace string 53 | Remark string 54 | Mobile string 55 | Email string 56 | OrgEmail string 57 | Active bool 58 | IsAdmin bool 59 | IsBoos bool 60 | DingId string 61 | UnionId string 62 | IsHide bool 63 | Department []int 64 | Position string 65 | Avatar string 66 | Jobnumber string 67 | IsSenior bool 68 | StateCode string 69 | OrderInDepts string 70 | IsLeaderInDepts string 71 | Extattr interface{} 72 | Roles []Roles 73 | } 74 | 75 | type Roles struct { 76 | Id int 77 | Name string 78 | GroupName string 79 | } 80 | ``` 81 | 82 | **示例** 83 | 84 | ```go 85 | c.UserInfoByUserId(userId, lang) 86 | ``` -------------------------------------------------------------------------------- /docs/guide/top_api.md: -------------------------------------------------------------------------------- 1 | # TOP Api 2 | 3 | -------------------------------------------------------------------------------- /docs/weixin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightningminers/go-dingtalk/e8997a39181445159113232fb96fab3c2d189289/docs/weixin.png -------------------------------------------------------------------------------- /runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | func run() bool { 4 | return true 5 | } 6 | -------------------------------------------------------------------------------- /src/constants.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | // 包外部可用的常量 4 | const ( 5 | VERSION = "0.1" 6 | OAPIURL = "https://oapi.dingtalk.com/" 7 | TOPAPIURL = "https://eco.taobao.com/router/rest" 8 | MessageTypeText = "text" 9 | MessageTypeActionCard = "action_card" 10 | MessageTypeImage = "image" 11 | MessageTypeVoice = "voice" 12 | MessageTypeFile = "file" 13 | MessageTypeLink = "link" 14 | MessageTypeOA = "oa" 15 | MessageTypeMarkdown = "markdown" 16 | ) 17 | 18 | // 包内部用的常量 19 | const ( 20 | signMD5 = "MD5" 21 | signHMAC = "HMAC" 22 | topFormat = "json" 23 | topV = "2.0" 24 | topSimplify = false 25 | topSecret = "github.com/icepy" 26 | topSignMethod = signMD5 27 | typeJSON = "application/json" 28 | typeForm = "application/x-www-form-urlencoded" 29 | typeMultipart = "multipart/form-data" 30 | aesEncodeKeyLen = 43 31 | ) 32 | 33 | // Top接口 34 | const ( 35 | corpRoleSimpleList = "dingtalk.corp.role.simplelist" 36 | corpRoleList = "dingtalk.corp.role.list" 37 | corpHealthStepinfoGetuserstatus = "dingtalk.corp.health.stepinfo.getuserstatus" 38 | corpHealthStepinfoListByUserid = "dingtalk.corp.health.stepinfo.listbyuserid" 39 | corpRoleAddRolesForemps = "dingtalk.corp.role.addrolesforemps" 40 | corpRoleRemoveRolesForemps = "dingtalk.corp.role.removerolesforemps" 41 | corpRoleDeleteRole = "dingtalk.corp.role.deleterole" 42 | corpRoleGetRoleGroup = "dingtalk.corp.role.getrolegroup" 43 | corpMessageCorpconversationAsyncsend = "dingtalk.corp.message.corpconversation.asyncsend" 44 | corpMessageCorpconversationAsyncsendbycode = "dingtalk.corp.message.corpconversation.asyncsendbycode" 45 | corpMessageCorpconversationGetsendprogress = "dingtalk.corp.message.corpconversation.getsendprogress" 46 | corpMessageCorpconversationGetsendresult = "dingtalk.corp.message.corpconversation.getsendresult" 47 | smartworkAttendsListschedule = "dingtalk.smartwork.attends.listschedule" 48 | smartworkAttendsGetsimplegroups = "dingtalk.smartwork.attends.getsimplegroups" 49 | smartworkCheckinRecordGet = "dingtalk.smartwork.checkin.record.get" 50 | smartworkBpmsProcessCopy = "dingtalk.smartwork.bpms.process.copy" 51 | smartworkBpmsProcessSync = "dingtalk.smartwork.bpms.process.sync" 52 | smartworkBpmsProcessinstanceCreate = "dingtalk.smartwork.bpms.processinstance.create" 53 | smartworkBpmsProcessinstanceList = "dingtalk.smartwork.bpms.processinstance.list" 54 | corpExtcontactCreate = "dingtalk.corp.extcontact.create" 55 | corpExtcontactUpdate = "dingtalk.corp.extcontact.update" 56 | corpExtcontactList = "dingtalk.corp.extcontact.list" 57 | corpExtcontactGet = "dingtalk.corp.extcontact.get" 58 | corpExtcontactListlabelgroups = "dingtalk.corp.extcontact.listlabelgroups" 59 | ) 60 | -------------------------------------------------------------------------------- /src/crypto.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "encoding/binary" 10 | "errors" 11 | r "math/rand" 12 | "sort" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type DingTalkCrypto struct { 18 | Token string 19 | EncodingAESKey string 20 | SuiteKey string 21 | BKey []byte 22 | Block cipher.Block 23 | } 24 | 25 | func NewDingTalkCrypto(token, encodingAESKey, suiteKey string) *DingTalkCrypto { 26 | if len(encodingAESKey) != aesEncodeKeyLen { 27 | panic("不合法的EncodingAESKey") 28 | } 29 | bkey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=") 30 | if err != nil { 31 | panic(err.Error()) 32 | } 33 | block, err := aes.NewCipher(bkey) 34 | if err != nil { 35 | panic(err.Error()) 36 | } 37 | c := &DingTalkCrypto{ 38 | Token: token, 39 | EncodingAESKey: encodingAESKey, 40 | SuiteKey: suiteKey, 41 | BKey: bkey, 42 | Block: block, 43 | } 44 | return c 45 | } 46 | 47 | func (c *DingTalkCrypto) GetDecryptMsg(signature, timestamp, nonce, secretMsg string) (string, error) { 48 | if !c.VerificationSignature(c.Token, timestamp, nonce, secretMsg, signature) { 49 | return "", errors.New("ERROR: 签名不匹配") 50 | } 51 | decode, err := base64.StdEncoding.DecodeString(secretMsg) 52 | if err != nil { 53 | return "", err 54 | } 55 | if len(decode) < aes.BlockSize { 56 | return "", errors.New("ERROR: 密文太短") 57 | } 58 | blockMode := cipher.NewCBCDecrypter(c.Block, c.BKey[:c.Block.BlockSize()]) 59 | plantText := make([]byte, len(decode)) 60 | blockMode.CryptBlocks(plantText, decode) 61 | plantText = pkCS7UnPadding(plantText) 62 | size := binary.BigEndian.Uint32(plantText[16:20]) 63 | plantText = plantText[20:] 64 | corpID := plantText[size:] 65 | if string(corpID) != c.SuiteKey { 66 | return "", errors.New("ERROR: CorpID匹配不正确") 67 | } 68 | return string(plantText[:size]), nil 69 | } 70 | 71 | func (c *DingTalkCrypto) GetEncryptMsg(msg, timestamp, nonce string) (string, string, error) { 72 | size := make([]byte, 4) 73 | binary.BigEndian.PutUint32(size, uint32(len(msg))) 74 | msg = randomString(16) + string(size) + msg + c.SuiteKey 75 | plantText := pkCS7Padding([]byte(msg), c.Block.BlockSize()) 76 | if len(plantText)%aes.BlockSize != 0 { 77 | return "", "", errors.New("ERROR: 消息体size不为16的倍数") 78 | } 79 | blockMode := cipher.NewCBCEncrypter(c.Block, c.BKey[:c.Block.BlockSize()]) 80 | chipherText := make([]byte, len(plantText)) 81 | blockMode.CryptBlocks(chipherText, plantText) 82 | outMsg := base64.StdEncoding.EncodeToString(chipherText) 83 | signature := c.CreateSignature(c.Token, timestamp, nonce, string(outMsg)) 84 | return string(outMsg), signature, nil 85 | } 86 | 87 | // 数据签名 88 | func (c *DingTalkCrypto) CreateSignature(token, timestamp, nonce, msg string) string { 89 | params := make([]string, 0) 90 | params = append(params, token) 91 | params = append(params, timestamp) 92 | params = append(params, nonce) 93 | params = append(params, msg) 94 | sort.Strings(params) 95 | return sha1Sign(strings.Join(params, "")) 96 | } 97 | 98 | // 验证数据签名 99 | func (c *DingTalkCrypto) VerificationSignature(token, timestamp, nonce, msg, sigture string) bool { 100 | return c.CreateSignature(token, timestamp, nonce, msg) == sigture 101 | } 102 | 103 | // 解密补位 104 | func pkCS7UnPadding(plantText []byte) []byte { 105 | length := len(plantText) 106 | unpadding := int(plantText[length-1]) 107 | return plantText[:(length - unpadding)] 108 | } 109 | 110 | // 加密补位 111 | func pkCS7Padding(ciphertext []byte, blockSize int) []byte { 112 | padding := blockSize - len(ciphertext)%blockSize 113 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 114 | return append(ciphertext, padtext...) 115 | } 116 | 117 | // 随机字符串 118 | func randomString(n int, alphabets ...byte) string { 119 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 120 | var bytes = make([]byte, n) 121 | var randby bool 122 | if num, err := rand.Read(bytes); num != n || err != nil { 123 | r.Seed(time.Now().UnixNano()) 124 | randby = true 125 | } 126 | for i, b := range bytes { 127 | if len(alphabets) == 0 { 128 | if randby { 129 | bytes[i] = alphanum[r.Intn(len(alphanum))] 130 | } else { 131 | bytes[i] = alphanum[b%byte(len(alphanum))] 132 | } 133 | } else { 134 | if randby { 135 | bytes[i] = alphabets[r.Intn(len(alphabets))] 136 | } else { 137 | bytes[i] = alphabets[b%byte(len(alphabets))] 138 | } 139 | } 140 | } 141 | return string(bytes) 142 | } 143 | -------------------------------------------------------------------------------- /src/dingtalk.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | /* 10 | * date: 2018/05/20 11 | * version: 0.1 12 | * author: xiangwenwen(icepy) 13 | * description: DingTalk Golang SDK https://github.com/icepy 14 | * 15 | * ^_^ 想了很久,还是准备用中文写一些话,以后的日子打算做一个山野隐居的佛系程序员 16 | * 17 | * 平静的生活 18 | * 平凡的人生 19 | * 心中的自由 20 | * 21 | * 我们经营了一家很小的团队,五个人,曾经都来自大家常说的BAT 22 | * 23 | * 钉钉是我曾经工作过的地方,留下了很多回忆 24 | * 25 | * 企业服务市场对我而言,就像人每天要吃的饭 26 | * 27 | * 我们很乐意将我们的专业知识,服务于一些企业 28 | * 29 | * 如果你的公司有企业定制,技术咨询,技术培训等需求,不妨联系我们(钉钉搜索群号“21794502”) 30 | */ 31 | 32 | type DingTalkClient struct { 33 | DTConfig *DTConfig 34 | TopConfig *TopConfig 35 | HTTPClient *http.Client 36 | AccessToken string 37 | SSOAccessToken string 38 | SNSAccessToken string 39 | SuiteAccessToken string 40 | AccessTokenCache Cache 41 | TicketCache Cache 42 | SSOAccessTokenCache Cache 43 | SNSAccessTokenCache Cache 44 | SuiteAccessTokenCache Cache 45 | DevType string 46 | Locker *sync.Mutex 47 | } 48 | 49 | type TopConfig struct { 50 | TopFormat string // json xml byte 51 | TopV string 52 | TopSimplify bool 53 | TopSecret string 54 | TopSignMethod string 55 | } 56 | 57 | type DTConfig struct { 58 | TopConfig 59 | CorpID string 60 | CorpSecret string 61 | AgentID string 62 | SuiteKey string 63 | SuiteSecret string 64 | SuiteTicket string 65 | ChannelSecret string 66 | SSOSecret string 67 | SNSAppID string 68 | SNSSecret string 69 | } 70 | 71 | type DTIsvGetCompanyInfo struct { 72 | AuthCorpID string 73 | PermanentCode string 74 | AuthAccessToken string 75 | } 76 | 77 | func NewDingTalkClient(devType string, config *DTConfig) *DingTalkClient { 78 | c := &DingTalkClient{ 79 | DTConfig: &DTConfig{}, 80 | HTTPClient: &http.Client{ 81 | Timeout: 10 * time.Second, 82 | }, 83 | TopConfig: &TopConfig{ 84 | TopFormat: topFormat, 85 | TopSecret: topSecret, 86 | TopSignMethod: topSignMethod, 87 | TopSimplify: topSimplify, 88 | TopV: topV, 89 | }, 90 | AccessTokenCache: NewFileCache("." + devType + "_access_token_file"), 91 | TicketCache: NewFileCache("." + devType + "_ticket_file"), 92 | SSOAccessTokenCache: NewFileCache("." + devType + "_sso_acess_token_file"), 93 | SNSAccessTokenCache: NewFileCache("." + devType + "_sns_access_token_file"), 94 | SuiteAccessTokenCache: NewFileCache("." + devType + "_suite_access_token_file"), 95 | Locker: new(sync.Mutex), 96 | DevType: devType, 97 | } 98 | if config != nil { 99 | if config.TopFormat != "" { 100 | c.TopConfig.TopFormat = config.TopFormat 101 | } 102 | if config.TopV != "" { 103 | c.TopConfig.TopV = config.TopV 104 | } 105 | if config.TopSecret != "" { 106 | c.TopConfig.TopSecret = config.TopSecret 107 | } 108 | if config.TopSignMethod != "" { 109 | c.TopConfig.TopSignMethod = config.TopSignMethod 110 | } 111 | if config.TopSimplify { 112 | c.TopConfig.TopSimplify = config.TopSimplify 113 | } 114 | c.DTConfig.CorpID = config.CorpID 115 | c.DTConfig.AgentID = config.AgentID 116 | c.DTConfig.CorpSecret = config.CorpSecret 117 | c.DTConfig.SSOSecret = config.SSOSecret 118 | c.DTConfig.ChannelSecret = config.ChannelSecret 119 | c.DTConfig.SNSAppID = config.SNSAppID 120 | c.DTConfig.SNSSecret = config.SNSSecret 121 | c.DTConfig.SuiteKey = config.SuiteKey 122 | c.DTConfig.SuiteSecret = config.SuiteSecret 123 | c.DTConfig.SuiteTicket = config.SuiteTicket 124 | } 125 | return c 126 | } 127 | 128 | func NewDingTalkISVClient(config *DTConfig) *DingTalkClient { 129 | return NewDingTalkClient("isv", config) 130 | } 131 | 132 | func NewDingTalkCompanyClient(config *DTConfig) *DingTalkClient { 133 | return NewDingTalkClient("company", config) 134 | } 135 | 136 | func NewDingTalkMiniClient(config *DTConfig) *DingTalkClient { 137 | return NewDingTalkClient("personalMini", config) 138 | } 139 | -------------------------------------------------------------------------------- /src/open_api_auth.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | "time" 8 | ) 9 | 10 | type AccessTokenResponse struct { 11 | OpenAPIResponse 12 | AccessToken string `json:"access_token"` 13 | Expires int `json:"expires_in"` 14 | Created int64 15 | } 16 | 17 | type SSOAccessTokenResponse struct { 18 | OpenAPIResponse 19 | SSOAccessToken string `json:"access_token"` 20 | Expires int `json:"expires_in"` 21 | Created int64 22 | } 23 | 24 | type SNSAccessTokenResponse struct { 25 | OpenAPIResponse 26 | SNSAccessToken string `json:"access_token"` 27 | Expires int `json:"expires_in"` 28 | Created int64 29 | } 30 | 31 | type SuiteAccessTokenResponse struct { 32 | OpenAPIResponse 33 | SuiteAccessToken string `json:"suite_access_token"` 34 | Expires int `json:"expires_in"` 35 | Created int64 36 | } 37 | 38 | type TicketResponse struct { 39 | OpenAPIResponse 40 | Ticket string `json:"ticket"` 41 | Expires int `json:"expires_in"` 42 | Created int64 43 | } 44 | 45 | type GetPermanentCodeResponse struct { 46 | OpenAPIResponse 47 | PermanentCode string `json:"permanent_code"` 48 | ChPermanentCode string `json:"ch_permanent_code"` 49 | AuthCorpInfo AuthCorpInfo `json:"auth_corp_info"` 50 | } 51 | 52 | type AuthCorpInfo struct { 53 | CorpID string `json:"corpid"` 54 | CorpName string `json:"corp_name"` 55 | } 56 | 57 | type ActivateSuiteResponse struct { 58 | OpenAPIResponse 59 | } 60 | 61 | type GetCorpAccessTokenResponse struct { 62 | OpenAPIResponse 63 | AccessToken string `json:"access_token"` 64 | Expires int `json:"expires_in"` 65 | } 66 | 67 | type GetCompanyInfoResponse struct { 68 | OpenAPIResponse 69 | AuthCorpInfo GCIRAuthCorpInfo `json:"auth_corp_info"` 70 | AuthUserInfo GCIRAuthUserInfo `json:"auth_user_info"` 71 | AuthInfo interface{} `json:"auth_info"` 72 | ChannelAuthInfo interface{} `json:"channel_auth_info"` 73 | } 74 | 75 | type GCIRAuthCorpInfo struct { 76 | CorpLogoURL string `json:"corp_logo_url"` 77 | CorpName string `json:"corp_name"` 78 | CorpID string `json:"corpid"` 79 | Industry string `json:"industry"` 80 | InviteCode string `json:"invite_code"` 81 | LicenseCode string `json:"license_code"` 82 | AuthChannel string `json:"auth_channel"` 83 | AuthChannelType string `json:"auth_channel_type"` 84 | IsAuthenticated bool `json:"is_authenticated"` 85 | AuthLevel int `json:"auth_level"` 86 | InviteURL string `json:"invite_url"` 87 | } 88 | 89 | type GCIRAuthUserInfo struct { 90 | UserID string `json:"userId"` 91 | } 92 | 93 | type ScopesResponse struct { 94 | OpenAPIResponse 95 | AuthUserField []string 96 | ConditionField []string 97 | AuthOrgScopes 98 | } 99 | 100 | type AuthOrgScopes struct { 101 | AuthedDept []int 102 | AuthedUser []string 103 | } 104 | 105 | func (e *AccessTokenResponse) CreatedAt() int64 { 106 | return e.Created 107 | } 108 | 109 | func (e *AccessTokenResponse) ExpiresIn() int { 110 | return e.Expires 111 | } 112 | 113 | func (e *TicketResponse) CreatedAt() int64 { 114 | return e.Created 115 | } 116 | 117 | func (e *TicketResponse) ExpiresIn() int { 118 | return e.Expires 119 | } 120 | 121 | func (e *SSOAccessTokenResponse) CreatedAt() int64 { 122 | return e.Created 123 | } 124 | 125 | func (e *SSOAccessTokenResponse) ExpiresIn() int { 126 | return e.Expires 127 | } 128 | 129 | func (e *SNSAccessTokenResponse) CreatedAt() int64 { 130 | return e.Created 131 | } 132 | 133 | func (e *SNSAccessTokenResponse) ExpiresIn() int { 134 | return e.Expires 135 | } 136 | 137 | func (e *SuiteAccessTokenResponse) CreatedAt() int64 { 138 | return e.Created 139 | } 140 | 141 | func (e *SuiteAccessTokenResponse) ExpiresIn() int { 142 | return e.Expires 143 | } 144 | 145 | // 刷新企业获取的access_token 146 | func (dtc *DingTalkClient) RefreshCompanyAccessToken() error { 147 | dtc.Locker.Lock() 148 | var data AccessTokenResponse 149 | err := dtc.AccessTokenCache.Get(&data) 150 | if err == nil { 151 | dtc.AccessToken = data.AccessToken 152 | fmt.Printf("Get access_token To Local Cache=%s\n", dtc.AccessToken) 153 | dtc.Locker.Unlock() 154 | return nil 155 | } 156 | params := url.Values{} 157 | params.Add("corpid", dtc.DTConfig.CorpID) 158 | params.Add("corpsecret", dtc.DTConfig.CorpSecret) 159 | err = dtc.httpRPC("gettoken", params, nil, &data) 160 | if err == nil { 161 | dtc.AccessToken = data.AccessToken 162 | data.Expires = data.Expires | 7200 163 | data.Created = time.Now().Unix() 164 | err = dtc.AccessTokenCache.Set(&data) 165 | dtc.Locker.Unlock() 166 | } 167 | return err 168 | } 169 | 170 | // 刷新企业获取的sso_access_token 171 | func (dtc *DingTalkClient) RefreshSSOAccessToken() error { 172 | dtc.Locker.Lock() 173 | var data SSOAccessTokenResponse 174 | err := dtc.SSOAccessTokenCache.Get(&data) 175 | if err == nil { 176 | dtc.SSOAccessToken = data.SSOAccessToken 177 | fmt.Printf("Get sso_access_token To Local Cache=%s\n", dtc.SSOAccessToken) 178 | dtc.Locker.Unlock() 179 | return nil 180 | } 181 | params := url.Values{} 182 | params.Add("corpid", dtc.DTConfig.CorpID) 183 | params.Add("corpsecret", dtc.DTConfig.SSOSecret) 184 | err = dtc.httpSSO("sso/gettoken", params, nil, &data) 185 | if err == nil { 186 | dtc.SSOAccessToken = data.SSOAccessToken 187 | data.Expires = data.Expires | 7200 188 | data.Created = time.Now().Unix() 189 | err = dtc.SSOAccessTokenCache.Set(&data) 190 | dtc.Locker.Unlock() 191 | } 192 | return err 193 | } 194 | 195 | // 刷新 SNS access_token 196 | func (dtc *DingTalkClient) RefreshSNSAccessToken() error { 197 | dtc.Locker.Lock() 198 | var data SNSAccessTokenResponse 199 | err := dtc.SNSAccessTokenCache.Get(&data) 200 | if err == nil { 201 | dtc.SNSAccessToken = data.SNSAccessToken 202 | fmt.Printf("Get sns_access_token To Local Cache=%s\n", dtc.SNSAccessToken) 203 | dtc.Locker.Unlock() 204 | return nil 205 | } 206 | params := url.Values{} 207 | params.Add("appid", dtc.DTConfig.SNSAppID) 208 | params.Add("appsecret", dtc.DTConfig.SNSSecret) 209 | err = dtc.httpSNS("sns/gettoken", params, nil, &data) 210 | if err == nil { 211 | dtc.SNSAccessToken = data.SNSAccessToken 212 | data.Expires = data.Expires | 7200 213 | data.Created = time.Now().Unix() 214 | err = dtc.SNSAccessTokenCache.Set(&data) 215 | dtc.Locker.Unlock() 216 | } 217 | return err 218 | } 219 | 220 | // 刷新 isv suite_access_token 221 | func (dtc *DingTalkClient) RefreshSuiteAccessToken() error { 222 | dtc.Locker.Lock() 223 | var data SuiteAccessTokenResponse 224 | err := dtc.SuiteAccessTokenCache.Get(&data) 225 | if err == nil { 226 | dtc.SuiteAccessToken = data.SuiteAccessToken 227 | fmt.Printf("Get suite_access_token To Local Cache=%s\n", dtc.SuiteAccessToken) 228 | dtc.Locker.Unlock() 229 | return nil 230 | } 231 | info := map[string]string{ 232 | "suite_key": dtc.DTConfig.SuiteKey, 233 | "suite_secret": dtc.DTConfig.SuiteSecret, 234 | "suite_ticket": dtc.DTConfig.SuiteTicket, 235 | } 236 | err = dtc.httpSNS("service/get_suite_token", nil, info, &data) 237 | if err == nil { 238 | dtc.SuiteAccessToken = data.SuiteAccessToken 239 | data.Expires = data.Expires | 7200 240 | data.Created = time.Now().Unix() 241 | err = dtc.SuiteAccessTokenCache.Set(&data) 242 | dtc.Locker.Unlock() 243 | } 244 | return err 245 | } 246 | 247 | // 获取Ticket 248 | func (dtc *DingTalkClient) GetJSAPITicket() (ticket string, err error) { 249 | dtc.Locker.Lock() 250 | var data TicketResponse 251 | err = dtc.TicketCache.Get(&data) 252 | if err == nil { 253 | dtc.Locker.Unlock() 254 | return data.Ticket, err 255 | } 256 | err = dtc.httpRPC("get_jsapi_ticket", nil, nil, &data) 257 | if err == nil { 258 | ticket = data.Ticket 259 | dtc.TicketCache.Set(&data) 260 | dtc.Locker.Unlock() 261 | } 262 | return ticket, err 263 | } 264 | 265 | // 配置config信息 266 | func (dtc *DingTalkClient) GetConfig(nonceStr string, timestamp string, url string) string { 267 | var config map[string]string 268 | ticket, _ := dtc.GetJSAPITicket() 269 | config = map[string]string{ 270 | "url": url, 271 | "nonceStr": nonceStr, 272 | "agentId": dtc.DTConfig.AgentID, 273 | "timeStamp": timestamp, 274 | "corpId": dtc.DTConfig.CorpID, 275 | "ticket": ticket, 276 | "signature": sign(ticket, nonceStr, timestamp, url), 277 | } 278 | bytes, _ := json.Marshal(&config) 279 | return string(bytes) 280 | } 281 | 282 | // 签名 283 | func sign(ticket string, nonceStr string, timeStamp string, url string) string { 284 | s := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", ticket, nonceStr, timeStamp, url) 285 | return sha1Sign(s) 286 | } 287 | 288 | // 获取授权范围 289 | func (dtc *DingTalkClient) GetAuthScopes() (ScopesResponse, error) { 290 | var data ScopesResponse 291 | err := dtc.httpRPC("auth/scopes", nil, nil, &data) 292 | return data, err 293 | } 294 | 295 | // 获取企业授权的永久授权码 296 | func (dtc *DingTalkClient) IsvGetPermanentCode(tmpAuthCode string) (GetPermanentCodeResponse, error) { 297 | var data GetPermanentCodeResponse 298 | requestData := map[string]string{ 299 | "tmp_auth_code": tmpAuthCode, 300 | } 301 | err := dtc.httpRPC("service/get_permanent_code", nil, requestData, &data) 302 | return data, err 303 | } 304 | 305 | // 激活套件 306 | func (dtc *DingTalkClient) IsvActivateSuite(authCorpID string, permanentCode string) (ActivateSuiteResponse, error) { 307 | var data ActivateSuiteResponse 308 | requestData := map[string]string{ 309 | "suite_key": dtc.DTConfig.SuiteKey, 310 | "auth_corpid": authCorpID, 311 | "permanent_code": permanentCode, 312 | } 313 | err := dtc.httpRPC("service/activate_suite", nil, requestData, &data) 314 | return data, err 315 | } 316 | 317 | // 获取企业授权的凭证 318 | func (dtc *DingTalkClient) IsvGetCorpAccessToken(authCorpID string, permanentCode string) (GetCorpAccessTokenResponse, error) { 319 | var data GetCorpAccessTokenResponse 320 | requestData := map[string]string{ 321 | "auth_corpid": authCorpID, 322 | "permanent_code": permanentCode, 323 | } 324 | err := dtc.httpRPC("service/get_corp_token", nil, requestData, &data) 325 | return data, err 326 | } 327 | 328 | // 直接获取企业授权的凭证 329 | func (dtc *DingTalkClient) IsvGetCAT(tmpAuthCode string) { 330 | 331 | } 332 | 333 | // 获取企业的基本信息 334 | func (dtc *DingTalkClient) IsvGetCompanyInfo(authCorpID string) (GetCompanyInfoResponse, error) { 335 | var data GetCompanyInfoResponse 336 | requestData := map[string]string{ 337 | "auth_corpid": authCorpID, 338 | "suite_key": dtc.DTConfig.SuiteKey, 339 | } 340 | err := dtc.httpRPC("service/get_auth_info", nil, requestData, &data) 341 | return data, err 342 | } 343 | -------------------------------------------------------------------------------- /src/open_api_callback.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | type CBCallBackRequest struct { 4 | CallbackTag []string `json:"call_back_tag"` 5 | Token string `json:"token"` 6 | AesKey string `json:"aes_key"` 7 | URL string `json:"url"` 8 | } 9 | 10 | type CBCallBackResponse struct { 11 | OpenAPIResponse 12 | } 13 | 14 | type CBQueryCallbackResponse struct { 15 | OpenAPIResponse 16 | CallbackTag []string `json:"call_back_tag"` 17 | Token string `json:"token"` 18 | AesKey string `json:"aes_key"` 19 | URL string `json:"url"` 20 | } 21 | 22 | type CBGetFailedCallbackResponse struct { 23 | OpenAPIResponse 24 | HasMore bool `json:"has_more"` 25 | FailedList []FailedCallbacks `json:"failed_list"` 26 | } 27 | 28 | type FailedCallbacks struct { 29 | EventTime int `json:"event_time"` 30 | CallbackTag string `json:"call_back_tag"` 31 | UserID []string `json:"userid"` 32 | CorpID string `json:"corpid"` 33 | } 34 | 35 | // 注册事件回调接口 36 | func (dtc *DingTalkClient) CBRegisterCallback(info *CBCallBackRequest) (CBCallBackResponse, error) { 37 | var data CBCallBackResponse 38 | err := dtc.httpRPC("call_back/register_call_back", nil, info, &data) 39 | return data, err 40 | } 41 | 42 | // 查询事件回调接口 43 | func (dtc *DingTalkClient) CBQueryCallback() (CBQueryCallbackResponse, error) { 44 | var data CBQueryCallbackResponse 45 | err := dtc.httpRPC("call_back/get_call_back", nil, nil, &data) 46 | return data, err 47 | } 48 | 49 | // 更新事件回调接口 50 | func (dtc *DingTalkClient) CBUpdateCallback(info *CBCallBackRequest) (CBCallBackResponse, error) { 51 | var data CBCallBackResponse 52 | err := dtc.httpRPC("call_back/update_call_back", nil, info, &data) 53 | return data, err 54 | } 55 | 56 | // 删除事件回调接口 57 | func (dtc *DingTalkClient) CBDeleteCallback() (CBCallBackResponse, error) { 58 | var data CBCallBackResponse 59 | err := dtc.httpRPC("call_back/delete_call_back", nil, nil, &data) 60 | return data, err 61 | } 62 | 63 | // 获取回调失败的结果 64 | func (dtc *DingTalkClient) CBGetFailedCallbacks() (CBGetFailedCallbackResponse, error) { 65 | var data CBGetFailedCallbackResponse 66 | err := dtc.httpRPC("call_back/get_call_back_failed_result", nil, nil, &data) 67 | return data, err 68 | } 69 | -------------------------------------------------------------------------------- /src/open_api_chat.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | type ChatCreateResponse struct { 8 | OpenAPIResponse 9 | ChatId string `json:"chatid"` 10 | } 11 | 12 | type ChatCreateRequest struct { 13 | Name string `json:"name"` 14 | Owner string `json:"owner"` 15 | UserIdList []string `json:"useridlist"` 16 | ShowHistoryType int `json:"showHistoryType,omitempty"` 17 | } 18 | 19 | type ChatUpdateResponse struct { 20 | OpenAPIResponse 21 | } 22 | 23 | type ChatUpdateRequest struct { 24 | ChatId string `json:"chatid"` 25 | Name string `json:"name,omitempty"` 26 | Owner string `json:"owner,omitempty"` 27 | AddUserIdList []string `json:"add_useridlist,omitempty"` 28 | DelUserIdList []string `json:"del_useridlist,omitempty"` 29 | } 30 | 31 | type ChatGetResponse struct { 32 | OpenAPIResponse 33 | ChatInfo ChatGetInfo `json:"chat_info"` 34 | } 35 | 36 | type ChatGetInfo struct { 37 | Name string 38 | Owner string 39 | UserIdList []string `json:"useridlist"` 40 | } 41 | 42 | type ChatSendResponse struct { 43 | OpenAPIResponse 44 | } 45 | 46 | type ChatSendRequest struct { 47 | ChatId string `json:"chatid"` 48 | MsgType string `json:"msgtype"` 49 | } 50 | 51 | type ChatSendActionCardRequest struct { 52 | ChatSendRequest 53 | ActionCard *ChatSendActionCard `json:"action_card"` 54 | } 55 | 56 | type ChatSendActionCard struct { 57 | Title string `json:"title"` 58 | Markdown string `json:"markdown"` 59 | SingleTitle string `json:"single_title,omitempty"` 60 | SingleUrl string `json:"single_url,omitempty"` 61 | BtnOrientation string `json:"btn_orientation,omitempty"` 62 | BtnJSONList interface{} `json:"btn_json_list,omitempty"` 63 | AgentId string `json:"agentid,omitempty"` 64 | } 65 | 66 | type ChatSendTextRequest struct { 67 | ChatSendRequest 68 | Text *ChatSendText `json:"text"` 69 | } 70 | 71 | type ChatSendText struct { 72 | Content string `json:"content"` 73 | } 74 | 75 | type ChatSendImageRequest struct { 76 | ChatSendRequest 77 | Image *ChatSendImage `json:"image"` 78 | } 79 | 80 | type ChatSendImage struct { 81 | MediaId string `json:"media_id"` 82 | } 83 | 84 | type ChatSendVoiceRequest struct { 85 | ChatSendRequest 86 | Voice *ChatSendVoice `json:"voice"` 87 | } 88 | 89 | type ChatSendVoice struct { 90 | MediaId string `json:"media_id"` 91 | Duration string `json:"duration"` 92 | } 93 | 94 | type ChatSendFileRequest struct { 95 | ChatSendRequest 96 | File *ChatSendFile `json:"file"` 97 | } 98 | 99 | type ChatSendFile struct { 100 | MediaId string `json:"media_id"` 101 | } 102 | 103 | type ChatSendLinkRequest struct { 104 | ChatSendRequest 105 | Link *ChatSendLink `json:"link"` 106 | } 107 | 108 | type ChatSendLink struct { 109 | Title string `json:"title"` 110 | Text string `json:"text"` 111 | PicUrl string `json:"pic_url"` 112 | MessageUrl string `json:"message_url"` 113 | } 114 | 115 | type ChatSendOARequest struct { 116 | ChatSendRequest 117 | Oa *ChatSendOA `json:"oa"` 118 | } 119 | 120 | type ChatSendOA struct { 121 | MessageUrl string `json:"message_url"` 122 | PcMessageUrl string `json:"pc_message_url,omitempty"` 123 | Head interface{} `json:"head"` 124 | Body interface{} `json:"body"` 125 | } 126 | 127 | type ChatSendMarkdownRequest struct { 128 | ChatSendRequest 129 | Markdown *ChatSendMarkdown `json:"markdown"` 130 | } 131 | 132 | type ChatSendMarkdown struct { 133 | Title string `json:"title"` 134 | Text string `json:"text"` 135 | } 136 | 137 | // 创建会话 138 | func (dtc *DingTalkClient) ChatCreate(info *ChatCreateRequest) (ChatCreateResponse, error) { 139 | var data ChatCreateResponse 140 | err := dtc.httpRPC("chat/create", nil, info, &data) 141 | return data, err 142 | } 143 | 144 | // 修改会话 145 | func (dtc *DingTalkClient) ChatUpdate(info *ChatUpdateRequest) (ChatUpdateResponse, error) { 146 | var data ChatUpdateResponse 147 | err := dtc.httpRPC("chat/update", nil, info, &data) 148 | return data, err 149 | } 150 | 151 | // 获取会话 152 | func (dtc *DingTalkClient) ChatGet(chatId string) (ChatGetResponse, error) { 153 | var data ChatGetResponse 154 | params := url.Values{} 155 | params.Add("chatid", chatId) 156 | err := dtc.httpRPC("chat/get", params, nil, &data) 157 | return data, err 158 | } 159 | 160 | // 发送群消息 161 | func (dtc *DingTalkClient) ChatSend(info interface{}) (ChatSendResponse, error) { 162 | var data ChatSendResponse 163 | err := dtc.httpRPC("chat/send", nil, info, &data) 164 | return data, err 165 | } 166 | -------------------------------------------------------------------------------- /src/open_api_cspace.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type CspaceAddToSingleChatRequest struct { 9 | AgentID string `json:"agent_id"` 10 | UserID string `json:"userid"` 11 | MediaID string `json:"media_id"` 12 | FileName string `json:"file_name"` 13 | } 14 | 15 | type CspaceAddToSingleChatResponse struct { 16 | OpenAPIResponse 17 | } 18 | 19 | type CspaceAddRequest struct { 20 | AgentID string `json:"agent_id,omitempty"` 21 | Code string `json:"code"` 22 | MediaID string `json:"media_id"` 23 | SpaceID string `json:"space_id"` 24 | FolderID string `json:"folder_id"` 25 | Name string `json:"name"` 26 | Overwrite bool `json:"overwrite,omitempty"` 27 | } 28 | 29 | type CspaceAddResponse struct { 30 | OpenAPIResponse 31 | Dentry string 32 | } 33 | 34 | type CspaceGetCustomSpaceRequest struct { 35 | Domain string `json:"domain"` 36 | AgentID string `json:"agent_id"` 37 | } 38 | 39 | type CspaceGetCustomSpaceResponse struct { 40 | OpenAPIResponse 41 | SpaceID string 42 | } 43 | 44 | type CspaceGrantCustomSpaceRequest struct { 45 | AgentID string `json:"agent_id"` 46 | Domain string `json:"domain"` 47 | IType string `json:"type"` 48 | UserID string `json:"userid"` 49 | Path string `json:"path"` 50 | Fileids string `json:"fileids"` 51 | Duration int64 `json:"Duration"` 52 | } 53 | 54 | type CspaceGrantCustomSpaceResponse struct { 55 | OpenAPIResponse 56 | } 57 | 58 | // 发送文件给指定用户 59 | func (dtc *DingTalkClient) CspaceAddToSingleChat(info *CspaceAddToSingleChatRequest) (CspaceAddToSingleChatResponse, error) { 60 | /* 61 | 这一块要么是文档没写好,要么就是有bug,目前处理的方式是URL即拼接,也发送byte形式的POST 62 | */ 63 | var data CspaceAddToSingleChatResponse 64 | params := url.Values{} 65 | params.Add("agent_id", info.AgentID) 66 | params.Add("userid", info.UserID) 67 | params.Add("media_id", info.MediaID) 68 | params.Add("file_name", info.FileName) 69 | err := dtc.httpRPC("cspace/add_to_single_chat", params, info, &data) 70 | return data, err 71 | } 72 | 73 | // 新增文件到用户钉盘 74 | func (dtc *DingTalkClient) CspaceAdd(info *CspaceAddRequest) (CspaceAddResponse, error) { 75 | var data CspaceAddResponse 76 | params := url.Values{} 77 | if info.AgentID != "" { 78 | params.Add("agent_id", info.AgentID) 79 | } 80 | params.Add("code", info.Code) 81 | params.Add("media_id", info.MediaID) 82 | params.Add("space_id", info.SpaceID) 83 | params.Add("folder_id", info.FolderID) 84 | params.Add("name", info.Name) 85 | if info.Overwrite { 86 | params.Add("overwrite", fmt.Sprintf("%s", info.Overwrite)) 87 | } 88 | err := dtc.httpRPC("cspace/add", params, nil, &data) 89 | return data, err 90 | } 91 | 92 | // 获取企业下的自定义空间 93 | func (dtc *DingTalkClient) CspaceGetCustomSpace(info *CspaceGetCustomSpaceRequest) (CspaceGetCustomSpaceResponse, error) { 94 | var data CspaceGetCustomSpaceResponse 95 | params := url.Values{} 96 | if info.AgentID != "" { 97 | params.Add("agent_id", info.AgentID) 98 | } 99 | if info.Domain != "" { 100 | params.Add("domain", info.Domain) 101 | } 102 | err := dtc.httpRPC("cspace/get_custom_space", params, nil, &data) 103 | return data, err 104 | } 105 | 106 | // 授权用户访问企业下的自定义空间 107 | func (dtc *DingTalkClient) CspaceGrantCustomSpace(info *CspaceGrantCustomSpaceRequest) (CspaceGrantCustomSpaceResponse, error) { 108 | var data CspaceGrantCustomSpaceResponse 109 | params := url.Values{} 110 | if info.AgentID != "" { 111 | params.Add("agent_id", info.AgentID) 112 | } 113 | if info.Domain != "" { 114 | params.Add("domain", info.Domain) 115 | } 116 | params.Add("type", info.IType) 117 | params.Add("userid", info.UserID) 118 | if info.Path != "" { 119 | params.Add("path", info.Path) 120 | } 121 | if info.Fileids != "" { 122 | params.Add("fileids", info.Fileids) 123 | } 124 | params.Add("duration", fmt.Sprintf("%d", info.Duration)) 125 | err := dtc.httpRPC("cspace/grant_custom_space", params, nil, &data) 126 | return data, err 127 | } 128 | -------------------------------------------------------------------------------- /src/open_api_data.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | type DataRecordResponse struct { 4 | OpenAPIResponse 5 | ID string `json:"id"` 6 | } 7 | 8 | type DataRequest struct { 9 | ID string `json:"id,omitempty"` 10 | StartTimeMs string `json:"startTimeMs"` 11 | EndTimeMs string `json:"endTimeMs"` 12 | Module string `json:"module,omitempty"` 13 | OriginID string `json:"originId,omitempty"` 14 | UserID string `json:"userid"` 15 | AgentID string `json:"agentId"` 16 | CallbackUrl string `json:"callbackUrl"` 17 | Extension interface{} `json:"extension,omitempty"` 18 | } 19 | 20 | type DataUpdateResponse struct { 21 | OpenAPIResponse 22 | } 23 | 24 | // 记录统计数据 25 | func (dtc *DingTalkClient) DataRecord(info *DataRequest) (DataRecordResponse, error) { 26 | var data DataRecordResponse 27 | err := dtc.httpRPC("data/record", nil, info, &data) 28 | return data, err 29 | } 30 | 31 | // 更新统计数据 32 | func (dtc *DingTalkClient) DataUpdate(info *DataRequest) (DataUpdateResponse, error) { 33 | var data DataUpdateResponse 34 | err := dtc.httpRPC("data/update", nil, info, &data) 35 | return data, err 36 | } 37 | -------------------------------------------------------------------------------- /src/open_api_department.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type SubDepartmentListResponse struct { 9 | OpenAPIResponse 10 | SubDeptIdList []int 11 | } 12 | 13 | type DepartmentListResponse struct { 14 | OpenAPIResponse 15 | Department []Department 16 | } 17 | 18 | type Department struct { 19 | Id int 20 | Name string 21 | ParentId int 22 | CreateDeptGroup bool 23 | AutoAddUser bool 24 | } 25 | 26 | type DepartmentDetailResponse struct { 27 | OpenAPIResponse 28 | Id int 29 | Name string 30 | Order int 31 | ParentId int 32 | CreateDeptGroup bool 33 | AutoAddUser bool 34 | DeptHiding bool 35 | DeptPerimits string 36 | UserPerimits string 37 | OuterDept bool 38 | OuterPermitDepts string 39 | OuterPermitUsers string 40 | OrgDeptOwner string 41 | DeptManagerUserIdList string 42 | SourceIdentifier string 43 | } 44 | 45 | type DepartmentCreateResponse struct { 46 | OpenAPIResponse 47 | Id int 48 | } 49 | 50 | type DepartmentCreateRequest struct { 51 | Name string `json:"name"` 52 | ParentId string `json:"parentid"` 53 | Order string `json:"order,omitempty"` 54 | CreateDeptGroup bool `json:"createDeptGroup,omitempty"` 55 | DeptHiding bool `json:"deptHiding,omitempty"` 56 | DeptPerimits string `json:"deptPerimits,omitempty"` 57 | UserPerimits string `json:"userPerimits,omitempty"` 58 | OuterDept string `json:"outerDept,omitempty"` 59 | OuterPermitDepts string `json:"outerPermitDepts,omitempty"` 60 | OuterPermitUsers string `json:"outerPermitUsers,omitempty"` 61 | SourceIdentifier string `json:"sourceIdentifier,omitempty"` 62 | } 63 | 64 | type DepartmentUpdateResponse struct { 65 | OpenAPIResponse 66 | Id int 67 | } 68 | 69 | type DepartmentUpdateRequest struct { 70 | Lang string `json:"lang,omitempty"` 71 | Name string `json:"name,omitempty"` 72 | ParentId string `json:"parentid,omitempty"` 73 | Order string `json:"order,omitempty"` 74 | Id string `json:"id"` 75 | CreateDeptGroup bool `json:"createDeptGroup,omitempty"` 76 | AutoAddUser bool `json:"autoAddUser,omitempty"` 77 | DeptManagerUseridList string `json:"deptManagerUseridList,omitempty"` 78 | DeptHiding bool `json:"deptHiding,omitempty"` 79 | DeptPerimits string `json:"deptPerimits,omitempty"` 80 | UserPerimits string `json:"userPerimits,omitempty"` 81 | OuterDept string `json:"outerDept,omitempty"` 82 | OuterPermitDepts string `json:"outerPermitDepts,omitempty"` 83 | OuterPermitUsers string `json:"outerPermitUsers,omitempty"` 84 | OrgDeptOwner string `json:"orgDeptOwner,omitempty"` 85 | SourceIdentifier string `json:"sourceIdentifier,omitempty"` 86 | } 87 | 88 | type DepartmentDeleteResponse struct { 89 | OpenAPIResponse 90 | } 91 | 92 | type DepartmentListParentDeptsByDeptResponse struct { 93 | OpenAPIResponse 94 | ParentIds []int `json:"parentIds"` 95 | } 96 | 97 | type DepartmentListParentDeptsResponse struct { 98 | OpenAPIResponse 99 | ParentIds interface{} `json:"dep"` 100 | } 101 | 102 | // 获取子部门Id列表 103 | func (dtc *DingTalkClient) SubDepartmentList(id interface{}) (SubDepartmentListResponse, error) { 104 | var data SubDepartmentListResponse 105 | params := url.Values{} 106 | if id != nil { 107 | if v, ok := id.(string); ok { 108 | params.Add("id", v) 109 | } 110 | } 111 | err := dtc.httpRPC("department/list_ids", params, nil, &data) 112 | return data, err 113 | } 114 | 115 | // 获取部门id列表 116 | func (dtc *DingTalkClient) DepartmentList(id interface{}, lang interface{}) (DepartmentListResponse, error) { 117 | var data DepartmentListResponse 118 | params := url.Values{} 119 | if id != nil { 120 | if v, ok := id.(string); ok { 121 | params.Add("id", v) 122 | } 123 | } 124 | if lang != nil { 125 | if v, ok := lang.(string); ok { 126 | params.Add("lang", v) 127 | } 128 | } 129 | err := dtc.httpRPC("department/list", params, nil, &data) 130 | return data, err 131 | } 132 | 133 | // 获取部门详情 134 | func (dtc *DingTalkClient) DepartmentDetail(id interface{}, lang interface{}) (DepartmentDetailResponse, error) { 135 | var data DepartmentDetailResponse 136 | params := url.Values{} 137 | if id != nil { 138 | if v, ok := id.(string); ok { 139 | params.Add("id", v) 140 | } 141 | } 142 | if lang != nil { 143 | if v, ok := lang.(string); ok { 144 | params.Add("lang", v) 145 | } 146 | } 147 | err := dtc.httpRPC("department/get", params, nil, &data) 148 | return data, err 149 | } 150 | 151 | // 创建部门 152 | func (dtc *DingTalkClient) DepartmentCreate(info *DepartmentCreateRequest) (DepartmentCreateResponse, error) { 153 | var data DepartmentCreateResponse 154 | err := dtc.httpRPC("department/create", nil, info, &data) 155 | return data, err 156 | } 157 | 158 | // 更新部门 159 | func (dtc *DingTalkClient) DepartmentUpdate(info *DepartmentUpdateRequest) (DepartmentUpdateResponse, error) { 160 | var data DepartmentUpdateResponse 161 | err := dtc.httpRPC("department/update", nil, info, &data) 162 | return data, err 163 | } 164 | 165 | // 删除部门 166 | func (dtc *DingTalkClient) DepartmentDelete(id int) (DepartmentDeleteResponse, error) { 167 | var data DepartmentDeleteResponse 168 | params := url.Values{} 169 | params.Add("id", fmt.Sprintf("%d", id)) 170 | err := dtc.httpRPC("department/delete", params, nil, &data) 171 | return data, err 172 | } 173 | 174 | // 查询部门的所有上级父部门路径 175 | func (dtc *DingTalkClient) DepartmentListParentDeptsByDept(id int) (DepartmentListParentDeptsByDeptResponse, error) { 176 | var data DepartmentListParentDeptsByDeptResponse 177 | params := url.Values{} 178 | params.Add("id", fmt.Sprintf("%d", id)) 179 | err := dtc.httpRPC("department/list_parent_depts_by_dept", params, nil, &data) 180 | return data, err 181 | } 182 | 183 | // 查询指定用户的所有上级父部门路径 184 | func (dtc *DingTalkClient) DepartmentListParentDepts(userId string) (DepartmentListParentDeptsResponse, error) { 185 | var data DepartmentListParentDeptsResponse 186 | params := url.Values{} 187 | params.Add("userId", userId) 188 | err := dtc.httpRPC("department/list_parent_depts", params, nil, &data) 189 | return data, err 190 | } 191 | -------------------------------------------------------------------------------- /src/open_api_file.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/url" 7 | ) 8 | 9 | type FileUploadStartTransactionResponse struct { 10 | OpenAPIResponse 11 | UploadID string `json:"upload_id"` 12 | } 13 | 14 | type FileUploadSingleResponse struct { 15 | OpenAPIResponse 16 | MediaID string `json:"media_id"` 17 | } 18 | 19 | type FileUploadEndTransactionRequest struct { 20 | AgentID string 21 | FileSize int64 22 | ChunkNumbers int64 23 | UploadDI string 24 | } 25 | 26 | type FileUploadEndTransactionResponse struct { 27 | OpenAPIResponse 28 | MediaID string `json:"media_id"` 29 | } 30 | 31 | type FileUploadChunkRequest struct { 32 | AgentID string 33 | UploadID string 34 | ChunkSequence int64 35 | FileName string 36 | Reader io.Reader 37 | } 38 | 39 | type FileUploadChunkResponse struct { 40 | OpenAPIResponse 41 | } 42 | 43 | // 开启文件上传事务 44 | func (dtc *DingTalkClient) FileUploadStartTransaction(agentID string, fileSize int, chunkNumbers int) (FileUploadStartTransactionResponse, error) { 45 | var data FileUploadStartTransactionResponse 46 | params := url.Values{} 47 | params.Add("agent_id", agentID) 48 | params.Add("file_size", fmt.Sprintf("%d", fileSize)) 49 | params.Add("chunk_numbers", fmt.Sprintf("%d", chunkNumbers)) 50 | err := dtc.httpRPC("/file/upload/transaction", params, nil, &data) 51 | return data, err 52 | } 53 | 54 | // 提交文件上传事务 55 | func (dtc *DingTalkClient) FileUploadEndTransaction(info *FileUploadEndTransactionRequest) (FileUploadEndTransactionResponse, error) { 56 | var data FileUploadEndTransactionResponse 57 | params := url.Values{} 58 | params.Add("agent_id", info.AgentID) 59 | params.Add("file_size", fmt.Sprintf("%d", info.FileSize)) 60 | params.Add("chunk_numbers", fmt.Sprintf("%d", info.ChunkNumbers)) 61 | params.Add("upload_id", info.UploadDI) 62 | err := dtc.httpRPC("file/upload/transaction", params, nil, &data) 63 | return data, err 64 | } 65 | 66 | // 上传文件块 67 | func (dtc *DingTalkClient) FileUploadChunk(info *FileUploadChunkRequest) (FileUploadChunkResponse, error) { 68 | var data FileUploadChunkResponse 69 | params := url.Values{} 70 | params.Add("agent_id", info.AgentID) 71 | params.Add("upload_id", info.UploadID) 72 | params.Add("chunk_sequence", fmt.Sprintf("%d", info.ChunkSequence)) 73 | upload := &uploadFile{ 74 | FieldName: "file", 75 | FileName: info.FileName, 76 | Reader: info.Reader, 77 | } 78 | err := dtc.httpRPC("file/upload/chunk", params, upload, &data) 79 | return data, err 80 | } 81 | 82 | // 上传单个文件 83 | func (dtc *DingTalkClient) FileUploadSingle(agentID string, fileSize int64, fileName string, reader io.Reader) (FileUploadSingleResponse, error) { 84 | var data FileUploadSingleResponse 85 | upload := &uploadFile{ 86 | FieldName: "file", 87 | FileName: fileName, 88 | Reader: reader, 89 | } 90 | params := url.Values{} 91 | params.Add("agent_id", agentID) 92 | params.Add("file_size", fmt.Sprintf("%d", fileSize)) 93 | err := dtc.httpRPC("file/upload/single", params, upload, &data) 94 | return data, err 95 | } 96 | -------------------------------------------------------------------------------- /src/open_api_media.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | ) 7 | 8 | type MediaUploadResponse struct { 9 | OpenAPIResponse 10 | Type string 11 | MediaID string `json:"media_id"` 12 | CreatedAt int64 `json:"created_at"` 13 | } 14 | 15 | type MediaDownloadFileResponse struct { 16 | OpenAPIResponse 17 | MediaID string 18 | Writer io.Writer 19 | } 20 | 21 | type uploadFile struct { 22 | FileName string 23 | FieldName string 24 | Reader io.Reader 25 | } 26 | 27 | // 上传媒体文件 28 | func (dtc *DingTalkClient) MediaUpload(mediaType string, fileName string, reader io.Reader) (MediaUploadResponse, error) { 29 | var data MediaUploadResponse 30 | upload := &uploadFile{ 31 | FieldName: "media", 32 | FileName: fileName, 33 | Reader: reader, 34 | } 35 | params := url.Values{} 36 | params.Add("type", mediaType) 37 | err := dtc.httpRPC("media/upload", params, upload, &data) 38 | return data, err 39 | } 40 | 41 | // 获取媒体文件 42 | func (dtc *DingTalkClient) MediaDownloadFile(mediaID string, write io.Writer) error { 43 | var data MediaDownloadFileResponse 44 | data.MediaID = mediaID 45 | data.Writer = write 46 | params := url.Values{} 47 | params.Add("media_id", mediaID) 48 | err := dtc.httpRPC("media/downloadFile", params, nil, &data) 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /src/open_api_message.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | type MessageSendToConversationResponse struct { 4 | OpenAPIResponse 5 | Receiver string 6 | } 7 | 8 | type MessageSendRequest struct { 9 | Sender string `json:"sender"` 10 | Cid string `json:"cid"` 11 | MsgType string `json:"msgtype"` 12 | } 13 | 14 | type MessageSendActionCardRequest struct { 15 | MessageSendRequest 16 | ActionCard *MessageSendActionCard `json:"action_card"` 17 | } 18 | 19 | type MessageSendActionCard struct { 20 | Title string `json:"title"` 21 | Markdown string `json:"markdown"` 22 | SingleTitle string `json:"single_title,omitempty"` 23 | SingleUrl string `json:"single_url,omitempty"` 24 | BtnOrientation string `json:"btn_orientation,omitempty"` 25 | BtnJSONList interface{} `json:"btn_json_list,omitempty"` 26 | AgentId string `json:"agentid,omitempty"` 27 | } 28 | 29 | type MessageSendTextRequest struct { 30 | MessageSendRequest 31 | Text *MessageSendText `json:"text"` 32 | } 33 | 34 | type MessageSendText struct { 35 | Content string `json:"content"` 36 | } 37 | 38 | type MessageSendImageRequest struct { 39 | MessageSendRequest 40 | Image *MessageSendImage `json:"image"` 41 | } 42 | 43 | type MessageSendImage struct { 44 | MediaId string `json:"media_id"` 45 | } 46 | 47 | type MessageSendVoiceRequest struct { 48 | MessageSendRequest 49 | Voice *MessageSendVoice `json:"voice"` 50 | } 51 | 52 | type MessageSendVoice struct { 53 | MediaId string `json:"media_id"` 54 | Duration string `json:"duration"` 55 | } 56 | 57 | type MessageSendFileRequest struct { 58 | MessageSendRequest 59 | File *MessageSendFile `json:"file"` 60 | } 61 | 62 | type MessageSendFile struct { 63 | MediaId string `json:"media_id"` 64 | } 65 | 66 | type MessageSendLinkRequest struct { 67 | MessageSendRequest 68 | Link *MessageSendLink `json:"link"` 69 | } 70 | 71 | type MessageSendLink struct { 72 | Title string `json:"title"` 73 | Text string `json:"text"` 74 | PicUrl string `json:"pic_url"` 75 | MessageUrl string `json:"message_url"` 76 | } 77 | 78 | type MessageSendOARequest struct { 79 | MessageSendRequest 80 | Oa *MessageSendOA `json:"oa"` 81 | } 82 | 83 | type MessageSendOA struct { 84 | MessageUrl string `json:"message_url"` 85 | PcMessageUrl string `json:"pc_message_url,omitempty"` 86 | Head interface{} `json:"head"` 87 | Body interface{} `json:"body"` 88 | } 89 | 90 | type MessageSendMarkdownRequest struct { 91 | MessageSendRequest 92 | Markdown *MessageSendMarkdown `json:"markdown"` 93 | } 94 | 95 | type MessageSendMarkdown struct { 96 | Title string `json:"title"` 97 | Text string `json:"text"` 98 | } 99 | 100 | // 发送普通消息 101 | func (dtc *DingTalkClient) MessageSendToConversation(info interface{}) (MessageSendToConversationResponse, error) { 102 | var data MessageSendToConversationResponse 103 | err := dtc.httpRPC("message/send_to_conversation", nil, info, &data) 104 | return data, err 105 | } 106 | -------------------------------------------------------------------------------- /src/open_api_microapp.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type MicroAppCreateResponse struct { 9 | OpenAPIResponse 10 | AgentId int `json:"agentId"` 11 | } 12 | 13 | type MicroAppCreateRequest struct { 14 | AppIcon string `json:"appIcon"` 15 | AppName string `json:"appName"` 16 | AppDesc string `json:"appDesc"` 17 | HomePageUrl string `json:"homepageUrl"` 18 | PcHomePageUrl string `json:"pcHomepageUrl,omitempty"` 19 | OmpLink string `json:"ompLink,omitempty"` 20 | } 21 | 22 | type MicroAppUpdateResponse struct { 23 | OpenAPIResponse 24 | AgentId int `json:"agentId"` 25 | } 26 | 27 | type MicroAppUpdateRequest struct { 28 | AppIcon string `json:"appIcon,omitempty"` 29 | AppName string `json:"appName,omitempty"` 30 | AppDesc string `json:"appDesc,omitempty"` 31 | HomePageUrl string `json:"homepageUrl,omitempty"` 32 | PcHomePageUrl string `json:"pcHomepageUrl,omitempty"` 33 | OmpLink string `json:"ompLink,omitempty"` 34 | AgentId int `json:"agentId"` 35 | } 36 | 37 | type MicroAppDeleteResponse struct { 38 | OpenAPIResponse 39 | } 40 | 41 | type MicroAppListResponse struct { 42 | OpenAPIResponse 43 | AppList []MALBUIAppList 44 | } 45 | 46 | type MicroAppListByUserIdResponse struct { 47 | OpenAPIResponse 48 | AppList []MALBUIAppList 49 | } 50 | 51 | type MALBUIAppList struct { 52 | AppIcon string `json:"appIcon"` 53 | AgentId int `json:"agentId"` 54 | AppDesc string `json:"appDesc"` 55 | IsSelf bool `json:"isSelf"` 56 | Name string `json:"name"` 57 | HomePageUrl string `json:"homepageUrl"` 58 | PcHomePageUrl string `json:"pcHomepageUrl"` 59 | AppStatus int `json:"appStatus"` 60 | OmpLink string `json:"ompLink"` 61 | } 62 | 63 | type MicroAppVisibleScopesResponse struct { 64 | OpenAPIResponse 65 | IsHidden bool `json:"isHidden"` 66 | DeptVisibleScopes []int `json:"deptVisibleScopes"` 67 | UserVisibleScopes []string `json:"userVisibleScopes"` 68 | } 69 | 70 | type MicroAppSetVisibleScopesResponse struct { 71 | OpenAPIResponse 72 | } 73 | 74 | type MicroAppSetVisibleScopesRequest struct { 75 | AgentId int `json:"agentId"` 76 | IsHiddent bool `json:"isHiddent,omitempty"` 77 | DeptVisibleScopes []int `json:"deptVisibleScopes,omitempty"` 78 | UserVisibleScopes []string `json:"userVisibleScopes,omitempty"` 79 | } 80 | 81 | type MicroAppRuleGetRuleListResponse struct { 82 | OpenAPIResponse 83 | RuleIdList []int `json:"ruleIdList"` 84 | } 85 | 86 | type MicroAppRuleGetUserTotaResponse struct { 87 | OpenAPIResponse 88 | Result []MicroAppRGUTResult 89 | } 90 | 91 | type MicroAppRGUTResult struct { 92 | RuleId int 93 | UserTotal int 94 | } 95 | 96 | type MicroAppRuleDeleteResponse struct { 97 | OpenAPIResponse 98 | } 99 | 100 | // 创建微应用 101 | func (dtc *DingTalkClient) MicroAppCreate(info *MicroAppCreateRequest) (MicroAppCreateResponse, error) { 102 | var data MicroAppCreateResponse 103 | err := dtc.httpRPC("microapp/create", nil, info, &data) 104 | return data, err 105 | } 106 | 107 | // 更新微应用 108 | func (dtc *DingTalkClient) MicroAppUpdate(info *MicroAppUpdateRequest) (MicroAppUpdateResponse, error) { 109 | var data MicroAppUpdateResponse 110 | err := dtc.httpRPC("microapp/update", nil, info, &data) 111 | return data, err 112 | } 113 | 114 | // 删除微应用 115 | func (dtc *DingTalkClient) MicroAppDelete(agentId int) (MicroAppDeleteResponse, error) { 116 | var data MicroAppDeleteResponse 117 | params := url.Values{} 118 | params.Add("agentId", fmt.Sprintf("%s", agentId)) 119 | err := dtc.httpRPC("microapp/delete", params, nil, &data) 120 | return data, err 121 | } 122 | 123 | // 列出微应用 124 | func (dtc *DingTalkClient) MicroAppList() (MicroAppListResponse, error) { 125 | var data MicroAppListResponse 126 | err := dtc.httpRPC("microapp/list", nil, map[string]string{}, &data) 127 | return data, err 128 | } 129 | 130 | // 列出员工可见的微应用 131 | func (dtc *DingTalkClient) MicroAppListByUserId(userId string) (MicroAppListByUserIdResponse, error) { 132 | var data MicroAppListByUserIdResponse 133 | params := url.Values{} 134 | params.Add("userid", userId) 135 | err := dtc.httpRPC("microapp/list_by_userid", params, nil, &data) 136 | return data, err 137 | } 138 | 139 | // 获取企业设置的微应用可见范围 140 | func (dtc *DingTalkClient) MicroAppVisibleScopes(agentId int) (MicroAppVisibleScopesResponse, error) { 141 | var data MicroAppVisibleScopesResponse 142 | params := url.Values{} 143 | params.Add("agentId", fmt.Sprintf("%s", agentId)) 144 | err := dtc.httpRPC("microapp/visible_scopes", params, nil, &data) 145 | return data, err 146 | } 147 | 148 | // 设置微应用的可见范围 149 | func (dtc *DingTalkClient) MicroAppSetVisibleScopes(info *MicroAppSetVisibleScopesRequest) (MicroAppSetVisibleScopesResponse, error) { 150 | var data MicroAppSetVisibleScopesResponse 151 | err := dtc.httpRPC("microapp/set_visible_scopes", nil, info, &data) 152 | return data, err 153 | } 154 | 155 | // 获取指定微应用下指定用户绑定的全部规则(定向开放接口) 156 | func (dtc *DingTalkClient) MicroAppRuleGetRuleList(userId string, agentId string) (MicroAppRuleGetRuleListResponse, error) { 157 | var data MicroAppRuleGetRuleListResponse 158 | err := dtc.httpRPC("microapp/rule/get_rule_list", nil, map[string]string{ 159 | "userid": userId, 160 | "agentId": agentId, 161 | }, &data) 162 | return data, err 163 | } 164 | 165 | // 获取规则绑定的用户数(定向开放接口) 166 | func (dtc *DingTalkClient) MicroAppRuleGetUserTota(agentId int, ruleIdList []int) (MicroAppRuleGetUserTotaResponse, error) { 167 | var data MicroAppRuleGetUserTotaResponse 168 | err := dtc.httpRPC("microapp/rule/get_user_total", nil, map[string]interface{}{ 169 | "agentId": agentId, 170 | "ruleIdList": ruleIdList, 171 | }, &data) 172 | return data, err 173 | } 174 | 175 | // 删除规则(定向开放接口) 176 | func (dtc *DingTalkClient) MicroAppRuleDelete(agentId int, ruleId int) (MicroAppRuleDeleteResponse, error) { 177 | var data MicroAppRuleDeleteResponse 178 | err := dtc.httpRPC("microapp/rule/delete", nil, map[string]int{ 179 | "agentId": agentId, 180 | "ruleId": ruleId, 181 | }, &data) 182 | return data, err 183 | } 184 | -------------------------------------------------------------------------------- /src/open_api_response.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import "fmt" 4 | 5 | type OpenAPIResponse struct { 6 | ErrCode int `json:"errcode"` 7 | ErrMsg string `json:"errmsg"` 8 | } 9 | 10 | type Unmarshallable interface { 11 | checkError() error 12 | } 13 | 14 | func (oar *OpenAPIResponse) checkError() error { 15 | var err error 16 | if oar.ErrCode != 0 { 17 | err = fmt.Errorf("errcode: %d\nerrmsg: %s", oar.ErrCode, oar.ErrMsg) 18 | } 19 | return err 20 | } 21 | -------------------------------------------------------------------------------- /src/open_api_smartwork.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type SmartworkAttendanceListRecordRequest struct { 9 | UserIds []string `json:"userIds"` 10 | CheckDateFrom string `json:"checkDateFrom"` 11 | CheckDateTo string `json:"checkDateTo"` 12 | } 13 | 14 | type SmartworkAttendanceListRecordResponse struct { 15 | OpenAPIResponse 16 | RecordResult []SmartworkALRRecordResult `json:"recordresult"` 17 | } 18 | 19 | type SmartworkALRRecordResult struct { 20 | GmtModified int64 `json:"gmtModified"` 21 | IsLegal string `json:"isLegal"` 22 | BaseCheckTime int64 `json:"baseCheckTime"` 23 | ID int64 `json:"id"` 24 | UserAddress string `json:"userAddress"` 25 | UserID string `json:"userId"` 26 | CheckType string `json:"checkType"` 27 | TimeResult string `json:"timeResult"` 28 | DeviceID string `json:"deviceId"` 29 | CorpID string `json:"corpId"` 30 | SourceType string `json:"sourceType"` 31 | WorkDate int64 `json:"workDate"` 32 | PlanCheckTime int64 `json:"planCheckTime"` 33 | GmtCreate int64 `json:"gmtCreate"` 34 | LocationMethod string `json:"locationMethod"` 35 | LocationResult string `json:"locationResult"` 36 | UserLongitude float64 `json:"userLongitude"` 37 | PlanID int64 `json:"planId"` 38 | GroupID int64 `json:"groupId"` 39 | UserAccuracy int `json:"userAccuracy"` 40 | UserCheckTime int64 `json:"userCheckTime"` 41 | UserLatitude float64 `json:"userLatitude"` 42 | ProcInstId string `json:"procInstId"` 43 | } 44 | 45 | type SmartworkCheckinRecordRequest struct { 46 | DepartmentID string 47 | StartTime int64 48 | EndTime int64 49 | Offset int 50 | Size int 51 | Order string 52 | } 53 | 54 | type SmartworkCheckinRecordResponse struct { 55 | OpenAPIResponse 56 | Data []SmartworkCheckinRecordData `json:"data"` 57 | } 58 | 59 | type SmartworkCheckinRecordData struct { 60 | Name string `json:"name"` 61 | UserID string `json:"userId"` 62 | Timestamp int64 `json:"timestamp"` 63 | Avatar string `json:"avatar"` 64 | Place string `json:"place"` 65 | DetailPlace string `json:"detailPlace"` 66 | Remark string `json:"remark"` 67 | Latitude float64 `json:"latitude"` 68 | Longitude float64 `json:"longitude"` 69 | ImageList []string `json:"imageList"` 70 | } 71 | 72 | // 考勤打卡记录开放 73 | func (dtc *DingTalkClient) SmartworkAttendanceListRecord(info *SmartworkAttendanceListRecordRequest) (SmartworkAttendanceListRecordResponse, error) { 74 | var data SmartworkAttendanceListRecordResponse 75 | err := dtc.httpRPC("attendance/listRecord", nil, info, &data) 76 | return data, err 77 | } 78 | 79 | // 获得签到数据 80 | func (dtc *DingTalkClient) SmartworkCheckinRecord(info *SmartworkCheckinRecordRequest) (SmartworkCheckinRecordResponse, error) { 81 | var data SmartworkCheckinRecordResponse 82 | params := url.Values{} 83 | params.Add("department_id", info.DepartmentID) 84 | params.Add("start_time", fmt.Sprintf("%d", info.StartTime)) 85 | params.Add("end_time", fmt.Sprintf("%d", info.EndTime)) 86 | if info.Offset >= 0 { 87 | params.Add("offset", fmt.Sprintf("%d", info.Offset)) 88 | } 89 | if info.Size > 0 { 90 | params.Add("size", fmt.Sprintf("%d", info.Size)) 91 | } 92 | if info.Order != "" { 93 | params.Add("order", info.Order) 94 | } 95 | err := dtc.httpRPC("checkin/record", params, nil, &data) 96 | return data, err 97 | } 98 | -------------------------------------------------------------------------------- /src/open_api_sns.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | type SNSGetPersistentCodeResponse struct { 8 | OpenAPIResponse 9 | OpenID string `json:"openid"` 10 | PersistentCode string `json:"persistent_code"` 11 | UnionID string `json:"unionid"` 12 | } 13 | 14 | type SNSGetSNSTokenResponse struct { 15 | OpenAPIResponse 16 | Expires int `json:"expires_in"` 17 | SnsToken string `json:"sns_token"` 18 | } 19 | 20 | type SNSGetUserInfoResponse struct { 21 | OpenAPIResponse 22 | UserInfo SNSGetUserInfo `json:"user_info"` 23 | } 24 | 25 | type SNSGetUserInfo struct { 26 | MaskedMobile string 27 | Nick string 28 | OpenID string 29 | UnionID string 30 | DingID string 31 | } 32 | 33 | func (dtc *DingTalkClient) SNSGetPersistentCode(code string) (SNSGetPersistentCodeResponse, error) { 34 | var data SNSGetPersistentCodeResponse 35 | requestData := map[string]string{ 36 | "tmp_auth_code": code, 37 | } 38 | err := dtc.httpSNS("sns/get_persistent_code", nil, requestData, &data) 39 | return data, err 40 | } 41 | 42 | func (dtc *DingTalkClient) SNSGetSNSToken(openID string, persistentCode string) (SNSGetSNSTokenResponse, error) { 43 | var data SNSGetSNSTokenResponse 44 | requestData := map[string]string{ 45 | "openid": openID, 46 | "persistent_code": persistentCode, 47 | } 48 | err := dtc.httpSNS("sns/get_sns_token", nil, requestData, &data) 49 | return data, err 50 | } 51 | 52 | func (dtc *DingTalkClient) SNSGetUserInfo(snsToken string) (SNSGetUserInfoResponse, error) { 53 | var data SNSGetUserInfoResponse 54 | params := url.Values{} 55 | params.Add("sns_token", snsToken) 56 | err := dtc.httpSNS("sns/getuserinfo", params, nil, &data) 57 | return data, err 58 | } 59 | -------------------------------------------------------------------------------- /src/open_api_sso.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import "net/url" 4 | 5 | type SSOAdminInfoByCodeResponse struct { 6 | OpenAPIResponse 7 | CorpInfo SSOCorpInfo `json:"corp_info"` 8 | IsSys bool `json:"is_sys"` 9 | } 10 | 11 | type SSOCorpInfo struct { 12 | CorpName string `json:"corp_name"` 13 | CorpID string `json:"corpid"` 14 | } 15 | 16 | type SSOUserInfo struct { 17 | Avatar string 18 | Email string 19 | Name string 20 | UserID string `json:"userid"` 21 | } 22 | 23 | // 通过CODE换取微应用管理员的身份信息 24 | func (dtc *DingTalkClient) SSOAdminInfoByCode(code string) (SSOAdminInfoByCodeResponse, error) { 25 | var data SSOAdminInfoByCodeResponse 26 | params := url.Values{} 27 | params.Add("code", code) 28 | err := dtc.httpSSO("sso/getuserinfo", params, nil, &data) 29 | return data, err 30 | } 31 | -------------------------------------------------------------------------------- /src/open_api_user.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type UserIdResponse struct { 9 | OpenAPIResponse 10 | UserID string `json:"userid"` 11 | DeviceID string `json:"deviceId"` 12 | IsSys bool `json:"is_sys"` 13 | SysLevel int `json:"sys_level"` 14 | } 15 | 16 | type UserIdByUnionIdResponse struct { 17 | OpenAPIResponse 18 | UserID string `json:"userid"` 19 | ContactType int `json:"contactType"` 20 | } 21 | 22 | type UserInfoResponse struct { 23 | OpenAPIResponse 24 | UserID string `json:"userid"` 25 | OpenID string `json:"openid"` 26 | Name string 27 | Tel string 28 | WorkPlace string 29 | Remark string 30 | Mobile string 31 | Email string 32 | OrgEmail string 33 | Active bool 34 | IsAdmin bool 35 | IsBoos bool 36 | DingID string 37 | UnionID string 38 | IsHide bool 39 | Department []int 40 | Position string 41 | Avatar string 42 | Jobnumber string 43 | IsSenior bool 44 | StateCode string 45 | OrderInDepts string 46 | IsLeaderInDepts string 47 | Extattr interface{} 48 | Roles []Roles 49 | } 50 | 51 | type Roles struct { 52 | ID int `json:"id"` 53 | Name string 54 | GroupName string 55 | } 56 | 57 | type UserSimpleListResponse struct { 58 | OpenAPIResponse 59 | HasMore bool 60 | UserList []USimpleList 61 | } 62 | 63 | type USimpleList struct { 64 | UserID string 65 | Name string 66 | } 67 | 68 | type UserListResponse struct { 69 | OpenAPIResponse 70 | HasMore bool 71 | UserList []UDetailedList 72 | } 73 | 74 | type UDetailedList struct { 75 | UserID string `json:"userid"` 76 | Order int 77 | DingID string 78 | UnionID string 79 | Mobile string 80 | Tel string 81 | WorkPlace string 82 | Remark string 83 | IsAdmin bool 84 | IsBoss bool 85 | IsHide bool 86 | IsLeader bool 87 | Name string 88 | Active bool 89 | Department []int 90 | Position string 91 | Email string 92 | Avatar string 93 | Jobnumber string 94 | Extattr interface{} 95 | } 96 | 97 | type UserAdminListResponse struct { 98 | OpenAPIResponse 99 | AdminList []Admins 100 | } 101 | 102 | type Admins struct { 103 | UserID string `json:"userid"` 104 | SysLevel int `json:"sys_level"` 105 | } 106 | 107 | type UserCanAccessMicroappResponse struct { 108 | OpenAPIResponse 109 | CanAccess bool 110 | } 111 | 112 | type UserCreateResponse struct { 113 | OpenAPIResponse 114 | UserID string 115 | } 116 | 117 | type UserCreateRequest struct { 118 | UserID string `json:"userid,omitempty"` 119 | Name string `json:"name"` 120 | OrderInDepts string `json:"orderInDepts,omitempty"` 121 | Department []int `json:"department"` 122 | Position string `json:"position,omitempty"` 123 | Mobile string `json:"mobile"` 124 | Tel string `json:"tel,omitempty"` 125 | WorkPlace string `json:"workPlace,omitempty"` 126 | Remark string `json:"remark,omitempty"` 127 | Email string `json:"email,omitempty"` 128 | OrgEmail string `json:"orgEmail,omitempty"` 129 | JobNumber string `json:"jobnumber,omitempty"` 130 | IsHide bool `json:"isHide,omitempty"` 131 | IsSenior bool `json:"isSenior,omitempty"` 132 | Extattr interface{} `json:"extattr,omitempty"` 133 | } 134 | 135 | type UserUpdateResponse struct { 136 | OpenAPIResponse 137 | } 138 | 139 | type UserUpdateRequest struct { 140 | Lang string `json:"lang,omitempty"` 141 | UserID string `json:"userid"` 142 | Name string `json:"name"` 143 | OrderInDepts string `json:"orderInDepts,omitempty"` 144 | Department []int `json:"department,omitempty"` 145 | Position string `json:"position,omitempty"` 146 | Mobile string `json:"mobile,omitempty"` 147 | Tel string `json:"tel,omitempty"` 148 | WorkPlace string `json:"workPlace,omitempty"` 149 | Remark string `json:"remark,omitempty"` 150 | Email string `json:"email,omitempty"` 151 | OrgEmail string `json:"orgEmail,omitempty"` 152 | JobNumber string `json:"jobnumber,omitempty"` 153 | IsHide bool `json:"isHide,omitempty"` 154 | IsSenior bool `json:"isSenior,omitempty"` 155 | Extattr interface{} `json:"extattr,omitempty"` 156 | } 157 | 158 | type UserDeleteResponse struct { 159 | OpenAPIResponse 160 | } 161 | 162 | type UserBatchDeleteResponse struct { 163 | OpenAPIResponse 164 | } 165 | 166 | type UserGetOrgUserCountResponse struct { 167 | OpenAPIResponse 168 | Count int 169 | } 170 | 171 | // 通过Code换取userid 172 | func (dtc *DingTalkClient) UserIdByCode(code string) (UserIdResponse, error) { 173 | var data UserIdResponse 174 | params := url.Values{} 175 | params.Add("code", code) 176 | err := dtc.httpRPC("user/getuserinfo", params, nil, &data) 177 | return data, err 178 | } 179 | 180 | // 通过UnionId获取UserId 181 | func (dtc *DingTalkClient) UserIdByUnionId(unionID string) (UserIdByUnionIdResponse, error) { 182 | var data UserIdByUnionIdResponse 183 | params := url.Values{} 184 | params.Add("unionid", unionID) 185 | err := dtc.httpRPC("user/getUseridByUnionid", params, nil, &data) 186 | return data, err 187 | } 188 | 189 | // 通过userid 换取 用户详细信息 190 | func (dtc *DingTalkClient) UserInfoByUserId(userID string, lang string, isvGetCompanyInfo *DTIsvGetCompanyInfo) (UserInfoResponse, error) { 191 | var data UserInfoResponse 192 | params := url.Values{} 193 | params.Add("lang", lang) 194 | params.Add("userid", userID) 195 | err := dtc.httpRPC("user/get", params, nil, &data, isvGetCompanyInfo) 196 | return data, err 197 | } 198 | 199 | // 获取部门成员(简化版) 200 | func (dtc *DingTalkClient) UserSimpleList(departmentID int) (UserSimpleListResponse, error) { 201 | var data UserSimpleListResponse 202 | params := url.Values{} 203 | params.Add("department_id", fmt.Sprintf("%d", departmentID)) 204 | err := dtc.httpRPC("user/simplelist", params, nil, &data) 205 | return data, err 206 | } 207 | 208 | // 获取部门成员(详情版) 209 | func (dtc *DingTalkClient) UserList(departmentID int) (UserListResponse, error) { 210 | var data UserListResponse 211 | params := url.Values{} 212 | params.Add("department_id", fmt.Sprintf("%d", departmentID)) 213 | err := dtc.httpRPC("user/list", params, nil, &data) 214 | return data, err 215 | } 216 | 217 | // 获取管理员列表 218 | func (dtc *DingTalkClient) UserAdminList() (UserAdminListResponse, error) { 219 | var data UserAdminListResponse 220 | err := dtc.httpRPC("user/get_admin", nil, nil, &data) 221 | return data, err 222 | } 223 | 224 | // 获取管理员的微应用管理权限 225 | func (dtc *DingTalkClient) UserCanAccessMicroapp(appID string, userID string) (UserCanAccessMicroappResponse, error) { 226 | var data UserCanAccessMicroappResponse 227 | params := url.Values{} 228 | params.Add("appId", appID) 229 | params.Add("userId", userID) 230 | err := dtc.httpRPC("user/can_access_microap", params, nil, &data) 231 | return data, err 232 | } 233 | 234 | // 创建成员 235 | func (dtc *DingTalkClient) UserCreate(info *UserCreateRequest) (UserCreateResponse, error) { 236 | var data UserCreateResponse 237 | err := dtc.httpRPC("user/create", nil, info, &data) 238 | return data, err 239 | } 240 | 241 | // 更新成员 242 | func (dtc *DingTalkClient) UserUpdate(info *UserUpdateRequest) (UserUpdateResponse, error) { 243 | var data UserUpdateResponse 244 | err := dtc.httpRPC("user/update", nil, info, &data) 245 | return data, err 246 | } 247 | 248 | // 删除成员 249 | func (dtc *DingTalkClient) UserDelete(userID string) (UserDeleteResponse, error) { 250 | var data UserDeleteResponse 251 | params := url.Values{} 252 | params.Add("userid", userID) 253 | err := dtc.httpRPC("user/delete", params, nil, &data) 254 | return data, err 255 | } 256 | 257 | // 批量删除成员 258 | func (dtc *DingTalkClient) UserBatchDelete(userIdList []string) (UserBatchDeleteResponse, error) { 259 | var data UserBatchDeleteResponse 260 | body := map[string][]string{ 261 | "useridlist": userIdList, 262 | } 263 | err := dtc.httpRPC("user/batchdelete", nil, body, &data) 264 | return data, err 265 | } 266 | 267 | // 获取企业员工人数 268 | func (dtc *DingTalkClient) UserGetOrgUserCount(onlyActive int) (UserGetOrgUserCountResponse, error) { 269 | var data UserGetOrgUserCountResponse 270 | params := url.Values{} 271 | params.Add("onlyActive", fmt.Sprintf("%d", onlyActive)) 272 | err := dtc.httpRPC("user/get_org_user_count", params, nil, &data) 273 | return data, err 274 | } 275 | -------------------------------------------------------------------------------- /src/request.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/md5" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "mime/multipart" 13 | "net/http" 14 | "net/url" 15 | "reflect" 16 | "sort" 17 | "strconv" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | func (dtc *DingTalkClient) httpRPC(path string, params url.Values, requestData interface{}, responseData Unmarshallable, isvGetCInfo ...interface{}) error { 23 | if dtc.DevType == "company" { 24 | if dtc.AccessToken != "" { 25 | if params == nil { 26 | params = url.Values{} 27 | } 28 | if params.Get("access_token") == "" { 29 | params.Set("access_token", dtc.AccessToken) 30 | } 31 | } 32 | } 33 | if dtc.DevType == "isv" { 34 | cur := isvGetCInfo[0] 35 | switch v := cur.(type) { 36 | case *DTIsvGetCompanyInfo: 37 | switch path { 38 | case "service/get_permanent_code", "service/activate_suite", "service/get_corp_token", "service/get_auth_info": 39 | if dtc.SuiteAccessToken != "" { 40 | if params == nil { 41 | params = url.Values{} 42 | } 43 | if params.Get("suite_access_token") == "" { 44 | params.Set("suite_access_token", dtc.SuiteAccessToken) 45 | } 46 | } 47 | default: 48 | if v.AuthAccessToken != "" { 49 | if params == nil { 50 | params = url.Values{} 51 | } 52 | if params.Get("access_token") == "" { 53 | params.Set("access_token", v.AuthAccessToken) 54 | } 55 | } 56 | } 57 | default: 58 | panic(errors.New("ERROR: *DTIsvGetCompanyInfo Error")) 59 | } 60 | } 61 | if dtc.DevType == "personalMini"{ 62 | if dtc.SNSAccessToken != "" && path != "sns/getuserinfo" { 63 | if params == nil { 64 | params = url.Values{} 65 | } 66 | if params.Get("access_token") == "" { 67 | params.Set("access_token", dtc.SNSAccessToken) 68 | } 69 | } 70 | } 71 | return dtc.httpRequest("oapi", path, params, requestData, responseData) 72 | } 73 | 74 | func (dtc *DingTalkClient) httpSNS(path string, params url.Values, requestData interface{}, responseData Unmarshallable) error { 75 | if dtc.SNSAccessToken != "" && path != "sns/getuserinfo" { 76 | if params == nil { 77 | params = url.Values{} 78 | } 79 | if params.Get("access_token") == "" { 80 | params.Set("access_token", dtc.SNSAccessToken) 81 | } 82 | } 83 | return dtc.httpRequest("oapi", path, params, requestData, responseData) 84 | } 85 | 86 | func (dtc *DingTalkClient) httpSSO(path string, params url.Values, requestData interface{}, responseData Unmarshallable) error { 87 | if dtc.SSOAccessToken != "" { 88 | if params == nil { 89 | params = url.Values{} 90 | } 91 | if params.Get("access_token") == "" { 92 | params.Set("access_token", dtc.SSOAccessToken) 93 | } 94 | } 95 | return dtc.httpRequest("oapi", path, params, requestData, responseData) 96 | } 97 | 98 | func (dtc *DingTalkClient) httpTOP(requestData interface{}, responseData interface{}) error { 99 | var params []string 100 | var paramsJoin string 101 | var cipher []byte 102 | var cipherString string 103 | if body, ok := requestData.(TopMapRequest); ok { 104 | body["sign_method"] = dtc.TopConfig.TopSignMethod 105 | if dtc.DevType == "company" { 106 | body["session"] = dtc.AccessToken 107 | } 108 | body["format"] = dtc.TopConfig.TopFormat 109 | body["v"] = dtc.TopConfig.TopV 110 | timestamp := time.Now().Unix() 111 | tm := time.Unix(timestamp, 0) 112 | body["timestamp"] = tm.Format("2006-01-02 03:04:05 PM") 113 | if dtc.TopConfig.TopFormat == "json" { 114 | body["simplify"] = dtc.TopConfig.TopSimplify 115 | } 116 | params = sortParamsKey(body) 117 | paramsJoin = strings.Join(params, "") 118 | if dtc.TopConfig.TopSignMethod == signMD5 { 119 | paramsJoin = dtc.TopConfig.TopSecret + paramsJoin + dtc.TopConfig.TopSecret 120 | cipher = encryptMD5(paramsJoin) 121 | } 122 | if dtc.TopConfig.TopSignMethod == signHMAC { 123 | cipher = encryptHMAC(paramsJoin, dtc.TopConfig.TopSecret) 124 | } 125 | cipherString = byteToHex(cipher) 126 | body["sign"] = cipherString 127 | fmt.Printf("Top Params=%s\n", body) 128 | return dtc.httpRequest("tapi", nil, addPostBody(body), nil, responseData) 129 | } 130 | return errors.New("requestData Not TopMapRequest Type") 131 | } 132 | 133 | func addPostBody(topMap TopMapRequest) url.Values { 134 | body := url.Values{} 135 | for k, v := range topMap { 136 | switch v.(type) { 137 | case []string: 138 | for _, h := range v.([]string) { 139 | body.Add(k, h) 140 | } 141 | case []int: 142 | for _, h := range v.([]int) { 143 | body.Add(k, string(h)) 144 | } 145 | default: 146 | body.Add(k, fmt.Sprintf("%s", v)) 147 | } 148 | } 149 | return body 150 | } 151 | 152 | func encryptMD5(paramsJoin string) []byte { 153 | hMd5 := md5.New() 154 | hMd5.Write([]byte(paramsJoin)) 155 | return hMd5.Sum(nil) 156 | } 157 | 158 | func encryptHMAC(paramsJoin string, secret string) []byte { 159 | hHmac := hmac.New(md5.New, []byte(secret)) 160 | hHmac.Write([]byte(paramsJoin)) 161 | return hHmac.Sum([]byte("")) 162 | } 163 | 164 | func byteToHex(data []byte) string { 165 | buffer := new(bytes.Buffer) 166 | for _, b := range data { 167 | s := strconv.FormatInt(int64(b&0xff), 16) 168 | if len(s) == 1 { 169 | buffer.WriteString("0") 170 | } 171 | buffer.WriteString(strings.ToUpper(s)) 172 | } 173 | return buffer.String() 174 | } 175 | 176 | func sortParamsKey(topParams TopMapRequest) []string { 177 | var t []string 178 | keys := topParams.keys() 179 | sort.Strings(keys) 180 | for _, k := range keys { 181 | t = append(t, k+fmt.Sprintf("%s", topParams[k])) 182 | } 183 | return t 184 | } 185 | 186 | func (dtc *DingTalkClient) httpRequest(tagType string, path interface{}, params url.Values, requestData interface{}, responseData interface{}) error { 187 | var request *http.Request 188 | var requestUrl string 189 | client := dtc.HTTPClient 190 | 191 | if tagType == "oapi" { 192 | requestUrl = OAPIURL + path.(string) + "?" + params.Encode() 193 | fmt.Printf("requestUrl=%s\n", requestUrl) 194 | if requestData != nil { 195 | switch v := requestData.(type) { 196 | case *uploadFile: 197 | var b bytes.Buffer 198 | if v.Reader == nil { 199 | return errors.New("upload file is empty") 200 | } 201 | w := multipart.NewWriter(&b) 202 | fw, err := w.CreateFormFile(v.FieldName, v.FileName) 203 | if err != nil { 204 | return err 205 | } 206 | if _, err = io.Copy(fw, v.Reader); err != nil { 207 | return err 208 | } 209 | if err = w.Close(); err != nil { 210 | return err 211 | } 212 | request, _ = http.NewRequest("POST", requestUrl, &b) 213 | request.Header.Set("Content-Type", w.FormDataContentType()) 214 | default: 215 | d, _ := json.Marshal(requestData) 216 | request, _ = http.NewRequest("POST", requestUrl, bytes.NewReader(d)) 217 | request.Header.Set("Content-Type", typeJSON+"; charset=UTF-8") 218 | } 219 | } else { 220 | request, _ = http.NewRequest("GET", requestUrl, nil) 221 | } 222 | } 223 | if tagType == "tapi" { 224 | requestUrl = TOPAPIURL 225 | request, _ = http.NewRequest("POST", requestUrl, strings.NewReader(params.Encode())) 226 | request.Header.Set("Content-Type", typeForm+"; charset=UTF-8") 227 | } 228 | resp, err := client.Do(request) 229 | if err != nil { 230 | return err 231 | } 232 | if resp.StatusCode != 200 { 233 | return errors.New("Server Error: " + resp.Status) 234 | } 235 | defer resp.Body.Close() 236 | contentType := resp.Header.Get("Content-Type") 237 | pos := len(typeJSON) 238 | 239 | if tagType == "oapi" { 240 | if len(contentType) >= pos && contentType[0:pos] == typeJSON { 241 | if content, err := ioutil.ReadAll(resp.Body); err == nil { 242 | json.Unmarshal(content, responseData) 243 | switch responseData.(type) { 244 | case Unmarshallable: 245 | resData := responseData.(Unmarshallable) 246 | return resData.checkError() 247 | } 248 | } 249 | } else { 250 | switch v := responseData.(type) { 251 | case *MediaDownloadFileResponse: 252 | io.Copy(v.Writer, resp.Body) 253 | } 254 | } 255 | } 256 | if tagType == "tapi" { 257 | if content, err := ioutil.ReadAll(resp.Body); err == nil { 258 | v := reflect.ValueOf(responseData) 259 | v = v.Elem() 260 | v.SetBytes(content) 261 | } 262 | } 263 | return err 264 | } 265 | -------------------------------------------------------------------------------- /src/top_api.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type TopMapRequest map[string]interface{} 8 | 9 | func (t TopMapRequest) keys() []string { 10 | keys := make([]string, 0, len(t)) 11 | for k := range t { 12 | keys = append(keys, k) 13 | } 14 | return keys 15 | } 16 | 17 | // 查询用户是否开启了钉钉运动 18 | func (dtc *DingTalkClient) TopCorpHealthStepinfoGetUserStatus(userId string) ([]byte, error) { 19 | var data []byte 20 | general := TopMapRequest{ 21 | "method": corpHealthStepinfoGetuserstatus, 22 | "userid": userId, 23 | } 24 | err := dtc.httpTOP(general, &data) 25 | return data, err 26 | } 27 | 28 | // 批量查询多个用户的钉钉运动步数 29 | func (dtc *DingTalkClient) TopCorpHealthStepinfoListByUserid(userIds []string, statDate string) ([]byte, error) { 30 | var data []byte 31 | general := TopMapRequest{ 32 | "method": corpHealthStepinfoListByUserid, 33 | "userids": userIds, 34 | "stat_date": statDate, 35 | } 36 | err := dtc.httpTOP(general, &data) 37 | return data, err 38 | } 39 | 40 | // 获取角色的员工列表 41 | func (dtc *DingTalkClient) TopCorpRoleSimpleList(roleId int, size int, offset int) ([]byte, error) { 42 | var data []byte 43 | general := TopMapRequest{ 44 | "method": corpRoleSimpleList, 45 | "role_id": roleId, 46 | "size": size, 47 | "offset": offset, 48 | } 49 | err := dtc.httpTOP(general, &data) 50 | return data, err 51 | } 52 | 53 | // 获取企业角色列表 54 | func (dtc *DingTalkClient) TopCorpRoleList(size int, offset int) ([]byte, error) { 55 | var data []byte 56 | general := TopMapRequest{ 57 | "method": corpRoleList, 58 | "size": size, 59 | "offset": offset, 60 | } 61 | err := dtc.httpTOP(general, &data) 62 | return data, err 63 | } 64 | 65 | // 批量为员工增加角色信息 66 | func (dtc *DingTalkClient) TopCorpRoleAddRolesForemps(rolelIdList []int, userIdList []string) ([]byte, error) { 67 | var data []byte 68 | general := TopMapRequest{ 69 | "method": corpRoleAddRolesForemps, 70 | "rolelid_list": rolelIdList, 71 | "userid_list": userIdList, 72 | } 73 | err := dtc.httpTOP(general, &data) 74 | return data, err 75 | } 76 | 77 | // 批量删除员工角的色信息 78 | func (dtc *DingTalkClient) TopCorpRoleRemoveRolesForemps(roleIdList []int, userIdList []string) ([]byte, error) { 79 | var data []byte 80 | general := TopMapRequest{ 81 | "method": corpRoleRemoveRolesForemps, 82 | "roleid_list": roleIdList, 83 | "userid_list": userIdList, 84 | } 85 | err := dtc.httpTOP(general, &data) 86 | return data, err 87 | } 88 | 89 | // 删除角色信息 90 | func (dtc *DingTalkClient) TopCorpRoleDeleteRole(roleId int) ([]byte, error) { 91 | var data []byte 92 | general := TopMapRequest{ 93 | "method": corpRoleDeleteRole, 94 | "role_id": roleId, 95 | } 96 | err := dtc.httpTOP(general, &data) 97 | return data, err 98 | } 99 | 100 | // 获取角色组信息 101 | func (dtc *DingTalkClient) TopCorpRoleGetRoleGroup(groupId int) ([]byte, error) { 102 | var data []byte 103 | general := TopMapRequest{ 104 | "method": corpRoleGetRoleGroup, 105 | "group_id": groupId, 106 | } 107 | err := dtc.httpTOP(general, &data) 108 | return data, err 109 | } 110 | 111 | // 企业会话消息异步发送 112 | func (dtc *DingTalkClient) TopCorpMessageCorpconversationAsyncsend(info *TopCorpMessageCorpconversationAsyncsendRequest) ([]byte, error) { 113 | var data []byte 114 | general := TopMapRequest{ 115 | "method": corpMessageCorpconversationAsyncsend, 116 | "msgtype": info.MsgType, 117 | "agent_id": info.AgentId, 118 | } 119 | if info.ToAllUser { 120 | general["to_all_user"] = info.ToAllUser 121 | } 122 | if content, err := json.Marshal(info.Msgcontent); err == nil { 123 | general["msgcontent"] = string(content) 124 | } 125 | if len(info.UserIdList) > 0 { 126 | general["userid_list"] = info.UserIdList 127 | } 128 | if len(info.DeptIdList) > 0 { 129 | general["dept_id_list"] = info.DeptIdList 130 | } 131 | err := dtc.httpTOP(general, &data) 132 | return data, err 133 | } 134 | 135 | // 通过用户授权码异步向企业会话发送消息 136 | func (dtc *DingTalkClient) TopCorpMessageCorpconversationAsyncsendbycode(info *TopCorpMessageCorpconversationAsyncsendbycodeRequest) ([]byte, error) { 137 | var data []byte 138 | general := TopMapRequest{ 139 | "method": corpMessageCorpconversationAsyncsendbycode, 140 | "code": info.Code, 141 | "msgtype": info.MsgType, 142 | "agent_id": info.AgentId, 143 | } 144 | if info.ToAllUser { 145 | general["to_all_user"] = info.ToAllUser 146 | } 147 | if content, err := json.Marshal(info.Msgcontent); err == nil { 148 | general["msgcontent"] = string(content) 149 | } 150 | if len(info.UserIdList) > 0 { 151 | general["userid_list"] = info.UserIdList 152 | } 153 | if len(info.DeptIdList) > 0 { 154 | general["dept_id_list"] = info.DeptIdList 155 | } 156 | err := dtc.httpTOP(general, &data) 157 | return data, err 158 | } 159 | 160 | // 获取异步发送企业会话消息的发送进度 161 | func (dtc *DingTalkClient) TopCorpMessageCorpconversationGetsendprogress(agentId int, taskId int) ([]byte, error) { 162 | var data []byte 163 | general := TopMapRequest{ 164 | "method": corpMessageCorpconversationGetsendprogress, 165 | "agent_id": agentId, 166 | "task_id": taskId, 167 | } 168 | err := dtc.httpTOP(general, &data) 169 | return data, err 170 | } 171 | 172 | // 获取异步向企业会话发送消息的结果 173 | func (dtc *DingTalkClient) TopCorpMessageCorpconversationGetsendresult(agentId interface{}, taskId interface{}) ([]byte, error) { 174 | var data []byte 175 | general := TopMapRequest{ 176 | "method": corpMessageCorpconversationGetsendresult, 177 | } 178 | if agentId != nil { 179 | if v, ok := agentId.(int); ok { 180 | general["agent_id"] = v 181 | } 182 | } 183 | if taskId != nil { 184 | if v, ok := taskId.(int); ok { 185 | general["task_id"] = v 186 | } 187 | } 188 | err := dtc.httpTOP(general, &data) 189 | return data, err 190 | } 191 | 192 | // 考勤排班信息按天全量查询接口 193 | func (dtc *DingTalkClient) TopSmartworkAttendsListschedule(workDate string, offset int, size int) ([]byte, error) { 194 | var data []byte 195 | general := TopMapRequest{ 196 | "method": smartworkAttendsListschedule, 197 | "work_date": workDate, 198 | "offset": offset, 199 | "size": size, 200 | } 201 | err := dtc.httpTOP(general, &data) 202 | return data, err 203 | } 204 | 205 | // 获取考勤组列表详情 206 | func (dtc *DingTalkClient) TopSmartworkAttendsGetsimplegroups(offset int, size int) ([]byte, error) { 207 | var data []byte 208 | general := TopMapRequest{ 209 | "method": smartworkAttendsGetsimplegroups, 210 | "offset": offset, 211 | "size": size, 212 | } 213 | err := dtc.httpTOP(general, &data) 214 | return data, err 215 | } 216 | 217 | // 获取多个用户的签到记录 218 | func (dtc *DingTalkClient) TopSmartworkCheckinRecordGet(info *SmartworkCheckinRecordGetRequest) ([]byte, error) { 219 | var data []byte 220 | general := TopMapRequest{ 221 | "method": smartworkCheckinRecordGet, 222 | "userid_list": info.UserIDList, 223 | "start_time": info.StartTime, 224 | "end_time": info.EndTime, 225 | "cursor": info.Cursor, 226 | "size": info.Size, 227 | } 228 | err := dtc.httpTOP(general, &data) 229 | return data, err 230 | } 231 | 232 | // 复制审批流 233 | func (dtc *DingTalkClient) TopSmartworkBpmsProcessCopy(info *SmartworkBpmsProcessCopyRequest) ([]byte, error) { 234 | var data []byte 235 | general := TopMapRequest{ 236 | "method": smartworkBpmsProcessCopy, 237 | "agent_id": info.AgentID, 238 | "process_code": info.ProcessCode, 239 | } 240 | if info.BizCategoryID != "" { 241 | general["biz_category_id"] = info.BizCategoryID 242 | } 243 | if info.ProcessName != "" { 244 | general["process_name"] = info.ProcessName 245 | } 246 | if info.Description != "" { 247 | general["description"] = info.Description 248 | } 249 | err := dtc.httpTOP(general, &data) 250 | return data, err 251 | } 252 | 253 | // 更新审批流 254 | func (dtc *DingTalkClient) TopSmartworkBpmsProcessSync(info *SmartworkBpmsProcessSyncRequest) ([]byte, error) { 255 | var data []byte 256 | general := TopMapRequest{ 257 | "method": smartworkBpmsProcessSync, 258 | "agent_id": info.AgentID, 259 | "src_process_code": info.SrcProcessCode, 260 | "target_process_code": info.TargetProcessCode, 261 | } 262 | if info.BizCategoryID != "" { 263 | general["biz_category_id"] = info.BizCategoryID 264 | } 265 | if info.ProcessName != "" { 266 | general["process_name"] = info.ProcessName 267 | } 268 | err := dtc.httpTOP(general, &data) 269 | return data, err 270 | } 271 | 272 | // 发起审批实例 273 | func (dtc *DingTalkClient) TopSmartworkBpmsProcessinstanceCreate(info *SmartworkBpmsProcessinstanceCreateRequest) ([]byte, error) { 274 | var data []byte 275 | general := TopMapRequest{ 276 | "method": smartworkBpmsProcessinstanceCreate, 277 | "process_code": info.ProcessCode, 278 | "originator_user_id": info.OriginatorUserID, 279 | "dept_id": info.DeptID, 280 | "approvers": info.Approvers, 281 | } 282 | if info.AgentID > 0 { 283 | general["agent_id"] = info.AgentID 284 | } 285 | if len(info.CCList) > 0 { 286 | general["cc_list"] = info.CCList 287 | } 288 | if len(info.CCPosition) > 0 { 289 | general["cc_position"] = info.CCPosition 290 | } 291 | b, e := json.Marshal(info.FormComponentValueVo) 292 | if e == nil { 293 | general["form_component_values"] = string(b) 294 | } else { 295 | panic(e) 296 | } 297 | err := dtc.httpTOP(general, &data) 298 | return data, err 299 | } 300 | 301 | // 获取审批实例列表 302 | func (dtc *DingTalkClient) TopSmartworkBpmsProcessinstanceList(info *SmartworkBpmsProcessinstanceListRequest) ([]byte, error) { 303 | var data []byte 304 | general := TopMapRequest{ 305 | "method": smartworkBpmsProcessinstanceList, 306 | "process_code": info.ProcessCode, 307 | "start_time": info.StartTime, 308 | } 309 | if info.EndTime > 0 { 310 | general["end_time"] = info.EndTime 311 | } 312 | if info.Size > 0 { 313 | general["size"] = info.Size 314 | } 315 | if len(info.UserIDList) > 0 { 316 | general["userid_list"] = info.UserIDList 317 | } 318 | if info.Cursor >= 0 { 319 | general["cursor"] = info.Cursor 320 | } 321 | err := dtc.httpTOP(general, &data) 322 | return data, err 323 | } 324 | 325 | // 添加企业外部联系人 326 | func (dtc *DingTalkClient) TopCorpExtcontactCreate(info *CorpExtcontactRequest) ([]byte, error) { 327 | var data []byte 328 | general := TopMapRequest{ 329 | "method": corpExtcontactCreate, 330 | } 331 | b, e := json.Marshal(info) 332 | if e == nil { 333 | general["contace"] = string(b) 334 | } else { 335 | panic(e) 336 | } 337 | err := dtc.httpTOP(general, &data) 338 | return data, err 339 | } 340 | 341 | // 更新外部联系人 342 | func (dtc *DingTalkClient) TopCorpExtcontactUpdate(info *CorpExtcontactRequest) ([]byte, error) { 343 | var data []byte 344 | general := TopMapRequest{ 345 | "method": corpExtcontactUpdate, 346 | } 347 | b, e := json.Marshal(info) 348 | if e == nil { 349 | general["contace"] = string(b) 350 | } else { 351 | panic(e) 352 | } 353 | err := dtc.httpTOP(general, &data) 354 | return data, err 355 | } 356 | 357 | // 获取外部联系人列表 358 | func (dtc *DingTalkClient) TopCorpExtcontactList(size int, offset int) ([]byte, error) { 359 | var data []byte 360 | general := TopMapRequest{ 361 | "method": corpExtcontactList, 362 | "size": size, 363 | "offset": offset, 364 | } 365 | err := dtc.httpTOP(general, &data) 366 | return data, err 367 | } 368 | 369 | // 外部单个联系人详情 370 | func (dtc *DingTalkClient) TopCorpExtcontactGet(userID string) ([]byte, error) { 371 | var data []byte 372 | general := TopMapRequest{ 373 | "method": corpExtcontactGet, 374 | "user_id": userID, 375 | } 376 | err := dtc.httpTOP(general, &data) 377 | return data, err 378 | } 379 | 380 | // 外部联系人标签列表 381 | func (dtc *DingTalkClient) TopCorpExtcontactListlabelgroups(size int, offset int) ([]byte, error) { 382 | var data []byte 383 | general := TopMapRequest{ 384 | "method": corpExtcontactListlabelgroups, 385 | "size": size, 386 | "offset": offset, 387 | } 388 | err := dtc.httpTOP(general, &data) 389 | return data, err 390 | } 391 | -------------------------------------------------------------------------------- /src/top_api_response.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | type TopErrorResponse struct { 4 | SubMsg string `json:"sub_msg"` 5 | Code int `json:"code"` 6 | SubCode string `json:"sub_code"` 7 | Msg string `json:"msg"` 8 | } 9 | 10 | // start --- 查询用户是否开启了钉钉运动 11 | 12 | type TopCorpHealthStepinfoGetUserStatusResponse struct { 13 | ErrorResponse TopErrorResponse `json:"error_response"` 14 | DingTalkCorpHealthStepinfoGetuserstatusResponse TopCHSGUSResponse `json:"dingtalk_corp_health_stepinfo_getuserstatus_response"` 15 | } 16 | 17 | type TopCHSGUSResponse struct { 18 | Result TopCHSGUSResult 19 | RequestId string `json:"request_id"` 20 | } 21 | 22 | type TopCHSGUSResult struct { 23 | DingOpenErrCode int `json:"ding_open_errcode"` 24 | ErrorMsg string `json:"error_msg"` 25 | Success bool 26 | Status bool 27 | } 28 | 29 | // end --- 查询用户是否开启了钉钉运动 30 | 31 | // start --- 批量查询多个用户的钉钉运动步数 32 | 33 | type TopCorpHealthStepinfoListByUseridResponse struct { 34 | ErrorResponse TopErrorResponse `json:"error_response"` 35 | DingtalkCorpHealthStepinfoListByUseridResponse TopCHSLBUResponse `json:"dingtalk_corp_health_stepinfo_listbyuserid_response"` 36 | } 37 | 38 | type TopCHSLBUResponse struct { 39 | Result TopCHSLBUResult 40 | RequestId string `json:"request_id"` 41 | } 42 | 43 | type TopCHSLBUResult struct { 44 | DingOpenErrCode int `json:"ding_open_errcode"` 45 | ErrorMsg string `json:"error_msg"` 46 | Success bool 47 | StepInfoList interface{} `json:"stepinfo_list"` 48 | } 49 | 50 | // end --- 批量查询多个用户的钉钉运动步数 51 | 52 | // start --- 获取角色的员工列表 53 | 54 | type TopCorpRoleSimpleListResponse struct { 55 | ErrorResponse TopErrorResponse `json:"error_response"` 56 | DingtalkCorpRoleSimpleListResponse TopCRSLResponse `json:"dingtalk_corp_role_simplelist_response"` 57 | } 58 | 59 | type TopCRSLResponse struct { 60 | Result TopCRSLResult 61 | RequestId string `json:"request_id"` 62 | } 63 | 64 | type TopCRSLResult struct { 65 | HasMore bool `json:"has_more"` 66 | List interface{} 67 | } 68 | 69 | // end --- 获取角色的员工列表 70 | 71 | // start --- 企业会话消息异步发送 72 | 73 | type TopCorpMessageCorpconversationAsyncsendRequest struct { 74 | MsgType string 75 | AgentId int 76 | UserIdList []string 77 | DeptIdList []int 78 | ToAllUser bool 79 | Msgcontent interface{} 80 | } 81 | 82 | type TopCorpMessageCorpconversationAsyncsendResponse struct { 83 | ErrorResponse TopErrorResponse `json:"error_response"` 84 | DingtalkCorpMessageCorpconversationAsyncsendResponse TopCMCAResponse `json:"dingtalk_corp_message_corpconversation_asyncsend_response"` 85 | } 86 | 87 | type TopCMCAResponse struct { 88 | Result TopCMCAResult 89 | RequestId string `json:"request_id"` 90 | } 91 | 92 | type TopCMCAResult struct { 93 | DingOpenErrCode int `json:"ding_open_errcode"` 94 | ErrorMsg string `json:"error_msg"` 95 | Success bool 96 | TaskId int `json:"task_id"` 97 | } 98 | 99 | // end --- 企业会话消息异步发送 100 | 101 | // start --- 通过用户授权码异步向企业会话发送消息 102 | 103 | type TopCorpMessageCorpconversationAsyncsendbycodeRequest struct { 104 | MsgType string 105 | AgentId int 106 | UserIdList []string 107 | DeptIdList []int 108 | ToAllUser bool 109 | Msgcontent interface{} 110 | Code string 111 | } 112 | 113 | type TopCorpMessageCorpconversationAsyncsendbycodeResponse struct { 114 | ErrorResponse TopErrorResponse `json:"error_response"` 115 | DingtalkCorpMessageCorpconversationAsyncsendbycodeResponse TopCMCACodeResponse `json:"dingtalk_corp_message_corpconversation_asyncsendbycode_response"` 116 | } 117 | 118 | type TopCMCACodeResponse struct { 119 | Result TopCMCACodeResult 120 | } 121 | 122 | type TopCMCACodeResult struct { 123 | DingOpenErrCode int `json:"ding_open_errcode"` 124 | ErrorMsg string `json:"error_msg"` 125 | Success bool 126 | TaskId int `json:"task_id"` 127 | } 128 | 129 | // end --- 通过用户授权码异步向企业会话发送消息 130 | 131 | // start --- 获取异步发送企业会话消息的发送进度 132 | 133 | type TopCorpMessageCorpconversationGetsendprogressResponse struct { 134 | ErrorResponse TopErrorResponse `json:"error_response"` 135 | DingtalkCorpMessageCorpconversationGetsendprogressResponse TopCMCGRResponse `json:"dingtalk_corp_message_corpconversation_getsendprogress_response"` 136 | } 137 | 138 | type TopCMCGRResponse struct { 139 | Result TopCMCGRResult 140 | } 141 | 142 | type TopCMCGRResult struct { 143 | DingOpenErrCode int `json:"ding_open_errcode"` 144 | ErrorMsg string `json:"error_msg"` 145 | Success bool 146 | Progress TopCMCGRProgress `json:"progress"` 147 | } 148 | 149 | type TopCMCGRProgress struct { 150 | ProgressInPercent int `json:"progress_in_percent"` 151 | Status int `json:"status"` 152 | } 153 | 154 | // end --- 获取异步发送企业会话消息的发送进度 155 | 156 | // start --- 获取异步向企业会话发送消息的结果 157 | 158 | type TopCorpMessageCorpconversationGetsendresultResponse struct { 159 | ErrorResponse TopErrorResponse `json:"error_response"` 160 | DingtalkCorpMessageCorpconversationGetsendresultResponse TopCMCGResponse `json:"dingtalk_corp_message_corpconversation_getsendresult_response"` 161 | } 162 | 163 | type TopCMCGResponse struct { 164 | Result TopCMCGRResult 165 | } 166 | 167 | type TopCMCGResult struct { 168 | DingOpenErrCode int `json:"ding_open_errcode"` 169 | ErrorMsg string `json:"error_msg"` 170 | Success bool 171 | SendResult interface{} `json:"send_result"` 172 | } 173 | 174 | // end --- 获取异步向企业会话发送消息的结果 175 | 176 | // start --- 获取多个用户的签到记录 177 | 178 | type SmartworkCheckinRecordGetRequest struct { 179 | UserIDList []string `json:"userid_list"` 180 | StartTime int64 `json:"start_time"` 181 | EndTime int64 `json:"end_time"` 182 | Cursor int `json:"cursor"` 183 | Size int `json:"size"` 184 | } 185 | 186 | // end --- 获取多个用户的签到记录 187 | 188 | // start --- 复制审批流 189 | 190 | type SmartworkBpmsProcessCopyRequest struct { 191 | AgentID int64 `json:"agent_id"` 192 | ProcessCode string `json:"process_code"` 193 | BizCategoryID string `json:"biz_category_id"` 194 | ProcessName string `json:"process_name"` 195 | Description string `json:"description"` 196 | } 197 | 198 | // end --- 复制审批流 199 | 200 | // start --- 更新审批流 201 | 202 | type SmartworkBpmsProcessSyncRequest struct { 203 | AgentID int64 `json:"agent_id"` 204 | SrcProcessCode string `json:"src_process_code"` 205 | TargetProcessCode string `json:"target_process_code"` 206 | BizCategoryID string `json:"biz_category_id"` 207 | ProcessName string `json:"process_name"` 208 | } 209 | 210 | // end --- 211 | 212 | // start --- 发起审批实例 213 | 214 | type SmartworkBpmsProcessinstanceCreateRequest struct { 215 | AgentID int64 `json:"agent_id"` 216 | ProcessCode string `json:"process_code"` 217 | OriginatorUserID string `json:"originator_user_id"` 218 | DeptID int `json:"dept_id"` 219 | Approvers []string `json:"approvers"` 220 | CCList []string `json:"cc_list"` 221 | CCPosition []string `json:"cc_position"` 222 | FormComponentValueVo []map[string]string `json:"form_component_values"` 223 | } 224 | 225 | // end --- 226 | 227 | // start --- 获取审批实例列表 228 | 229 | type SmartworkBpmsProcessinstanceListRequest struct { 230 | ProcessCode string `json:"process_code"` 231 | StartTime int64 `json:"start_time"` 232 | EndTime int64 `json:"end_time"` 233 | Size int `json:"size"` 234 | Cursor int `json:"cursor"` 235 | UserIDList []string `json:"userid_list"` 236 | } 237 | 238 | // end --- 获取审批实例列表 239 | 240 | // start --- 添加外部联系人|更新外部联系人 241 | 242 | type CorpExtcontactRequest struct { 243 | Title string `json:"title,omitempty"` 244 | LabelIDs []int `json:"label_ids"` 245 | ShareDeptIDs []int `json:"share_dept_ids,omitempty"` 246 | Address string `json:"address,omitempty"` 247 | Remark string `json:"remark,omitempty"` 248 | FollowerUserID string `json:"follower_user_id"` 249 | Name string `json:"name"` 250 | UserID string `json:"user_id,omitempty"` 251 | StateCode string `json:"state_code"` 252 | CompanyName string `json:"company_name,omitempty"` 253 | ShareUserIDs []string `json:"share_user_ids,omitempty"` 254 | Mobile string `json:"mobile"` 255 | } 256 | 257 | // end --- 添加外部联系人|更新外部联系人 258 | -------------------------------------------------------------------------------- /src/util.go: -------------------------------------------------------------------------------- 1 | package dingtalk 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/json" 6 | "encoding/xml" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "time" 11 | ) 12 | 13 | type Expirable interface { 14 | CreatedAt() int64 15 | ExpiresIn() int 16 | } 17 | 18 | type Cache interface { 19 | Set(data Expirable) error 20 | Get(data Expirable) error 21 | } 22 | 23 | type FileCache struct { 24 | Path string 25 | } 26 | 27 | func NewFileCache(path string) *FileCache { 28 | return &FileCache{ 29 | Path: path, 30 | } 31 | } 32 | 33 | func (f *FileCache) Set(data Expirable) error { 34 | bytes, err := json.Marshal(data) 35 | if err == nil { 36 | ioutil.WriteFile(f.Path, bytes, 0644) 37 | } 38 | return err 39 | } 40 | 41 | func (f *FileCache) Get(data Expirable) error { 42 | bytes, err := ioutil.ReadFile(f.Path) 43 | if err == nil { 44 | err = json.Unmarshal(bytes, data) 45 | if err == nil { 46 | created := data.CreatedAt() 47 | expires := data.ExpiresIn() 48 | if err == nil && time.Now().Unix() > created+int64(expires-60) { 49 | err = errors.New("Data is already expired") 50 | } 51 | } 52 | } 53 | return err 54 | } 55 | 56 | func sha1Sign(s string) string { 57 | // The pattern for generating a hash is `sha1.New()`, 58 | // `sha1.Write(bytes)`, then `sha1.Sum([]byte{})`. 59 | // Here we start with a new hash. 60 | h := sha1.New() 61 | 62 | // `Write` expects bytes. If you have a string `s`, 63 | // use `[]byte(s)` to coerce it to bytes. 64 | h.Write([]byte(s)) 65 | 66 | // This gets the finalized hash result as a byte 67 | // slice. The argument to `Sum` can be used to append 68 | // to an existing byte slice: it usually isn't needed. 69 | bs := h.Sum(nil) 70 | 71 | // SHA1 values are often printed in hex, for example 72 | // in git commits. Use the `%x` format verb to convert 73 | // a hash results to a hex string. 74 | return fmt.Sprintf("%x", bs) 75 | } 76 | 77 | func HandJSONTopResponse(responseData interface{}, content []byte) { 78 | json.Unmarshal(content, responseData) 79 | } 80 | 81 | func HandXMLTopResponse(responseData interface{}, content []byte) { 82 | xml.Unmarshal(content, responseData) 83 | } 84 | --------------------------------------------------------------------------------