├── .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 |
--------------------------------------------------------------------------------