package dingdingapi import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "net/http" "strings" "sync" "time" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) const ( prodURL = "https://oapi.dingtalk.com" ) const ( getTokenAction = "gettoken" snsGetTokenAction = "sns/gettoken" ) const ( ResponseCodeSuccess = 0 ResponseCodeCallbackAlreadyExist = 71006 ResponseCodeCallbackNotExist = 71007 ResponseCodeCallbackURLNotExist = 71012 ) type API struct { agentID int64 corpID string token string appID string secret string client *http.Client config *platformapi.APIConfig locker sync.RWMutex callbackToken string callbackAESKey string } func New(corpID, appID, secret string, config ...*platformapi.APIConfig) *API { return NewWithAgentID(0, corpID, appID, secret, "", "", config...) } func NewWithAgentID(agentID int64, corpID, appID, secret, callbackToken, callbackAESKey string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ agentID: agentID, corpID: corpID, appID: appID, secret: secret, callbackToken: callbackToken, callbackAESKey: callbackAESKey, client: &http.Client{Timeout: curConfig.ClientTimeout}, config: &curConfig, } } func (a *API) GetAppID() string { return a.appID } func (a *API) GetSecret() string { return a.secret } func isSNSAction(action string) bool { return strings.Index(action, "sns/") == 0 } func (a *API) SetToken(newToken string) bool { curToken := a.GetToken() if curToken != newToken { a.locker.Lock() defer a.locker.Unlock() a.token = newToken return true } return false } func (a *API) GetToken() string { a.locker.RLock() defer a.locker.RUnlock() return a.token } func (a *API) RetrieveToken() (token string, err error) { result, err := a.AccessAPI(getTokenAction, nil, nil) if err != nil { return "", err } token = utils.Interface2String(result["access_token"]) a.SetToken(token) return token, nil } func (a *API) signParams(timestamp int64) string { mac := hmac.New(sha256.New, []byte(a.GetSecret())) mac.Write([]byte(utils.Int64ToStr(timestamp))) // return url.QueryEscape(base64.StdEncoding.EncodeToString(mac.Sum(nil))) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) } func (a *API) AccessAPI(action string, params map[string]interface{}, bodyMap map[string]interface{}) (retVal map[string]interface{}, err error) { params2 := make(map[string]interface{}) for k, v := range params { params2[k] = v } if action == getTokenAction { params2["appkey"] = a.GetAppID() params2["appsecret"] = a.GetSecret() } else if action == snsGetTokenAction { params2["appid"] = a.GetAppID() params2["appsecret"] = a.GetSecret() } else if !isSNSAction(action) { accessToken := a.GetToken() if accessToken == "" { panic("token is empty!") } params2["access_token"] = accessToken } else { params2["accessKey"] = a.GetAppID() timestamp := time.Now().UnixNano() / 1000000 params2["timestamp"] = timestamp params2["signature"] = a.signParams(timestamp) } fullURL := utils.GenerateGetURL(prodURL, action, params2) // baseapi.SugarLogger.Debug(fullURL) err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { var request *http.Request if bodyMap == nil { request, _ = http.NewRequest(http.MethodGet, fullURL, nil) } else { request, _ = http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(utils.MustMarshal(bodyMap))) } return request }, a.config, func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { if jsonResult1 == nil { return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") } errCode := int(utils.MustInterface2Int64(jsonResult1["errcode"])) if errCode == ResponseCodeSuccess { retVal = jsonResult1 return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorIntCode(utils.Interface2String(jsonResult1["errmsg"]), errCode) return platformapi.ErrLevelCodeIsNotOK, newErr }) return retVal, err } func (a *API) GetJSAPITicket(getType string) (expiresIn int, ticket string, err error) { if getType == "" { getType = "jsapi" } result, err := a.AccessAPI("get_jsapi_ticket", map[string]interface{}{ "type": getType, }, nil) if err != nil { return 0, "", err } return int(utils.MustInterface2Int64(result["expires_in"])), utils.Interface2String(result["ticket"]), nil }