mirror of https://gitee.com/godoos/godoos.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.3 KiB
191 lines
4.3 KiB
package dingtalk
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"godocms/pkg/dingtalk/payload"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type DingTalk struct {
|
|
// 企业内部应用对应:AgentId,如果是应套件:SuiteId
|
|
//id int
|
|
|
|
// 企业内部应用对应:AppKey,套件对应:SuiteKey
|
|
key string
|
|
|
|
// 企业内部对应:AppSecret,套件对应:SuiteSecret
|
|
secret string
|
|
|
|
// isv 钉钉开放平台会向应用的回调URL推送的suite_ticket(约5个小时推送一次)
|
|
ticket string
|
|
|
|
// 授权企业的id
|
|
corpid string
|
|
|
|
// 在开发者后台的基本信息 > 开发信息(旧版)页面获取微应用管理后台SSOSecret
|
|
ssoSecret string
|
|
|
|
client *http.Client
|
|
cache string
|
|
}
|
|
|
|
type OptionFunc func(*DingTalk)
|
|
|
|
func WithTicket(ticket string) OptionFunc {
|
|
return func(dt *DingTalk) {
|
|
dt.ticket = ticket
|
|
}
|
|
}
|
|
|
|
func WithCorpid(corpid string) OptionFunc {
|
|
return func(dt *DingTalk) {
|
|
dt.corpid = corpid
|
|
}
|
|
}
|
|
|
|
func WithSSOSecret(secret string) OptionFunc {
|
|
return func(dt *DingTalk) {
|
|
dt.ssoSecret = secret
|
|
}
|
|
}
|
|
|
|
func (ding *DingTalk) validate() error {
|
|
if ding.key == "" {
|
|
return errors.New("key is empty")
|
|
}
|
|
if ding.secret == "" {
|
|
return errors.New("secret is empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NewClient(key, secret string, opts ...OptionFunc) (ding *DingTalk, err error) {
|
|
ding = &DingTalk{key: key, secret: secret}
|
|
|
|
for _, opt := range opts {
|
|
opt(ding)
|
|
}
|
|
|
|
if err = ding.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ding.client = &http.Client{Timeout: 10 * time.Second}
|
|
|
|
return
|
|
}
|
|
|
|
func isNewApi(path string) bool {
|
|
return strings.HasPrefix(path, "/v1.0/")
|
|
}
|
|
|
|
func (ding *DingTalk) Request(method, path string, query url.Values, body interface{}, data payload.Unmarshalled) (err error) {
|
|
if body != nil {
|
|
if err := ding.validate(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if query == nil {
|
|
query = url.Values{}
|
|
}
|
|
|
|
if query.Get("access_token") == "" && path != GetTokenKey && path != CorpAccessToken &&
|
|
path != SuiteAccessToken && path != GetAuthInfo && path != GetAgentKey &&
|
|
path != ActivateSuiteKey && path != GetSSOTokenKey && path != GetUnactiveCorpKey &&
|
|
path != ReauthCorpKey && path != GetCorpPermanentCodeKey && path != GetUserAccessToken {
|
|
|
|
var token string
|
|
var err error
|
|
if token, err = ding.GetAccessToken(); err != nil {
|
|
return err
|
|
}
|
|
// set token
|
|
query.Set("access_token", token)
|
|
}
|
|
|
|
return ding.httpRequest(method, path, query, body, data)
|
|
}
|
|
|
|
func (ding *DingTalk) httpRequest(method, path string, query url.Values, body interface{}, response payload.Unmarshalled) (err error) {
|
|
var uri *url.URL
|
|
var token string
|
|
|
|
newApi := isNewApi(path)
|
|
if newApi {
|
|
token = query.Get("access_token")
|
|
query.Del("access_token")
|
|
uri, _ = url.Parse(NewApi + path)
|
|
} else {
|
|
uri, _ = url.Parse(Api + path)
|
|
}
|
|
uri.RawQuery = query.Encode()
|
|
|
|
// 处理请求体
|
|
var reqBody io.Reader
|
|
var contentType string
|
|
if body != nil {
|
|
switch b := body.(type) {
|
|
case string:
|
|
reqBody = strings.NewReader(b)
|
|
contentType = "text/plain"
|
|
case url.Values:
|
|
reqBody = strings.NewReader(b.Encode())
|
|
contentType = "application/x-www-form-urlencoded"
|
|
default:
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal JSON: %w", err)
|
|
}
|
|
reqBody = bytes.NewReader(jsonBody)
|
|
contentType = "application/json; charset=utf-8"
|
|
}
|
|
}
|
|
|
|
// 创建请求
|
|
req, err := http.NewRequest(method, uri.String(), reqBody)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", contentType)
|
|
|
|
if newApi {
|
|
req.Header.Set("x-acs-dingtalk-access-token", token)
|
|
}
|
|
|
|
// 发起请求
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// 处理响应
|
|
switch resp.StatusCode {
|
|
case http.StatusOK:
|
|
err = json.NewDecoder(resp.Body).Decode(&response)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return response.CheckError()
|
|
// case http.StatusBadRequest:
|
|
// case http.StatusNotFound:
|
|
// case http.StatusInternalServerError:
|
|
default:
|
|
var result map[string]interface{}
|
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
return fmt.Errorf("dingtalk server error, unexpected status code: %v, res: %v", resp.StatusCode, result)
|
|
}
|
|
}
|
|
|