Files
baseapi/platformapi/weixinapi/weixinapi.go
邹宗楠 3f97450f12 1
2024-04-03 15:10:26 +08:00

216 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package weixinapi
import (
"bytes"
"fmt"
"math/rand"
"mime/multipart"
"net/http"
"net/textproto"
"strings"
"sync"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
prodURL = "https://api.weixin.qq.com"
)
const (
ResponseCodeBusy = -1
ResponseCodeSuccess = 0
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
type TokenInfo struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
type ErrorInfo struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
type API struct {
token string
appID string
secret string
client *http.Client
config *platformapi.APIConfig
locker sync.RWMutex
msgToken string
msgKey string
}
func New(appID, secret string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
appID: appID,
secret: secret,
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) AccessAPI(action string, params map[string]interface{}, body string) (retVal map[string]interface{}, err error) {
if params == nil && body == "" {
panic("params and body can not all be empty")
}
params2 := make(map[string]interface{})
for k, v := range params {
params2[k] = v
}
if params2["grant_type"] != nil {
params2["appid"] = a.appID
params2["secret"] = a.secret
} else if !isSNSAction(action) {
accessToken := a.CBGetToken()
if accessToken == "" {
panic("token is empty!")
}
params2["access_token"] = accessToken
}
fullURL := utils.GenerateGetURL(prodURL, action, params2)
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
var request *http.Request
if body == "" {
request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
} else {
request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(body))
if strings.Contains(fullURL, "wxa/generate_urllink") {
request.Header.Set("Content-Type", "application/json")
}
}
request.Close = true // todo try to fix EOF error when accessing weixin api.
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")
}
var errInfo *ErrorInfo
// 微信的返回值,在错误与正常情况下,结构是完全不一样的
if errCode, ok := jsonResult1["errcode"]; ok {
errInfo = &ErrorInfo{
ErrCode: int(utils.MustInterface2Int64(errCode)),
ErrMsg: jsonResult1["errmsg"].(string),
}
if errInfo.ErrCode == 0 {
retVal = jsonResult1
}
} else {
retVal = jsonResult1
}
if retVal != nil {
return platformapi.ErrLevelSuccess, nil
}
newErr := utils.NewErrorIntCode(errInfo.ErrMsg, errInfo.ErrCode)
if errInfo.ErrCode == ResponseCodeBusy {
return platformapi.ErrLevelRecoverableErr, newErr
}
return platformapi.ErrLevelCodeIsNotOK, newErr
})
return retVal, err
}
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func getBoundary() (boundary string) {
boundary = "----WebKitFormBoundary"
boundary += RandStringBytes(16)
return boundary
}
func (a *API) AccessAPIUpload(url string, data []byte, fileName, contentType, materialType string) (retVal map[string]interface{}, err error) {
params2 := make(map[string]interface{})
accessToken := a.CBGetToken()
if accessToken == "" {
panic("token is empty!")
}
params2["access_token"] = accessToken
fullURL := utils.GenerateGetURL(prodURL, url, params2)
// baseapi.SugarLogger.Debug(fullURL)
// 实例化multipart
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.SetBoundary(getBoundary())
// 创建multipart 文件字段
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
"media", fileName))
h.Set("Content-Type", contentType)
part, err := writer.CreatePart(h)
// 写入文件数据到multipart和读取本地文件方法的唯一区别
_, err = part.Write(data)
//将额外参数也写入到multipart
if materialType != "" {
_ = writer.WriteField("type", materialType)
}
err = writer.Close()
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodPost, fullURL, body)
request.Header.Set("Content-Type", writer.FormDataContentType())
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")
}
var errInfo *ErrorInfo
// 微信的返回值,在错误与正常情况下,结构是完全不一样的
if errCode, ok := jsonResult1["errcode"]; ok {
errInfo = &ErrorInfo{
ErrCode: int(utils.MustInterface2Int64(errCode)),
ErrMsg: jsonResult1["errmsg"].(string),
}
if errInfo.ErrCode == 0 {
retVal = jsonResult1
}
} else {
retVal = jsonResult1
}
if retVal != nil {
return platformapi.ErrLevelSuccess, nil
}
newErr := utils.NewErrorIntCode(errInfo.ErrMsg, errInfo.ErrCode)
if errInfo.ErrCode == ResponseCodeBusy {
return platformapi.ErrLevelRecoverableErr, newErr
}
return platformapi.ErrLevelCodeIsNotOK, newErr
})
return retVal, err
}