// DownloadToWriter 下载多媒体到 io.Writer. // 对于视频素材, 先通过 GetVideo 得到 Video 信息, 然后通过 Video.DownloadURL 来下载 func DownloadToWriter(clt *core.Client, mediaId string, writer io.Writer) (written int64, err error) { httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } var request = struct { MediaId string `json:"media_id"` }{ MediaId: mediaId, } requestBodyBytes, err := json.Marshal(&request) if err != nil { return } var errorResult core.Error // 先读取 64bytes 内容来判断返回的是不是错误信息 // {"errcode":40007,"errmsg":"invalid media_id"} var buf = make([]byte, 64) token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=" + url.QueryEscape(token) written, err = httpDownloadToWriter(httpClient, finalURL, requestBodyBytes, buf, writer, &errorResult) if err != nil { return } if written > 0 { return } switch errorResult.ErrCode { case core.ErrCodeOK: return // 基本不会出现 case core.ErrCodeInvalidCredential, core.ErrCodeAccessTokenExpired: retry.DebugPrintError(errorResult.ErrCode, errorResult.ErrMsg, token) if !hasRetried { hasRetried = true errorResult = core.Error{} if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: err = &errorResult return } }
// PostJSON 用 encoding/json 把 request marshal 为 JSON, HTTP POST 到微信服务器, // 然后将微信服务器返回的 JSON 用 encoding/json 解析到 response. // // NOTE: // 1. 一般不需要调用这个方法, 请直接调用高层次的封装函数; // 2. 最终的 URL == incompleteURL + access_token; // 3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field): // struct { // Error // ... // } func (clt *Client) PostJSON(incompleteURL string, request interface{}, response interface{}) (err error) { ErrorStructValue, ErrorErrCodeValue := checkResponse(response) bodyBuf := textBufferPool.Get().(*bytes.Buffer) bodyBuf.Reset() defer textBufferPool.Put(bodyBuf) if err = json.NewEncoder(bodyBuf).Encode(request); err != nil { return } requestBodyBytes := bodyBuf.Bytes() if i := len(requestBodyBytes) - 1; i >= 0 && requestBodyBytes[i] == '\n' { requestBodyBytes = requestBodyBytes[:i] // 去掉最后的 '\n', 这样能统一log格式, 不然可能多一个空白行 } httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := incompleteURL + url.QueryEscape(token) if err = httpPostJSON(httpClient, finalURL, requestBodyBytes, response); err != nil { return } switch errCode := ErrorErrCodeValue.Int(); errCode { case ErrCodeOK: return case ErrCodeInvalidCredential, ErrCodeAccessTokenExpired: errMsg := ErrorStructValue.Field(errorErrMsgIndex).String() retry.DebugPrintError(errCode, errMsg, token) if !hasRetried { hasRetried = true ErrorStructValue.Set(errorZeroValue) if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: return } }
// DownloadToWriter 下载多媒体到 io.Writer. // 请注意, 视频文件不支持下载 func DownloadToWriter(clt *core.Client, mediaId string, writer io.Writer) (written int64, err error) { httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } var incompleteURL = "https://api.weixin.qq.com/cgi-bin/media/get?media_id=" + url.QueryEscape(mediaId) + "&access_token=" var errorResult core.Error token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := incompleteURL + url.QueryEscape(token) written, err = httpDownloadToWriter(httpClient, finalURL, writer, &errorResult) if err != nil { return } if written > 0 { return } switch errorResult.ErrCode { case core.ErrCodeOK: return // 基本不会出现 case core.ErrCodeInvalidCredential, core.ErrCodeAccessTokenExpired: retry.DebugPrintError(errorResult.ErrCode, errorResult.ErrMsg, token) if !hasRetried { hasRetried = true errorResult = core.Error{} if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: err = &errorResult return } }
// GetJSON HTTP GET 微信资源, 然后将微信服务器返回的 JSON 用 encoding/json 解析到 response. // // NOTE: // 1. 一般不需要调用这个方法, 请直接调用高层次的封装函数; // 2. 最终的 URL == incompleteURL + access_token; // 3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field): // struct { // Error // ... // } func (clt *Client) GetJSON(incompleteURL string, response interface{}) (err error) { ErrorStructValue, ErrorErrCodeValue := checkResponse(response) httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := incompleteURL + url.QueryEscape(token) if err = httpGetJSON(httpClient, finalURL, response); err != nil { return } switch errCode := ErrorErrCodeValue.Int(); errCode { case ErrCodeOK: return case ErrCodeInvalidCredential, ErrCodeAccessTokenExpired: errMsg := ErrorStructValue.Field(errorErrMsgIndex).String() retry.DebugPrintError(errCode, errMsg, token) if !hasRetried { hasRetried = true ErrorStructValue.Set(errorZeroValue) if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: return } }
// PostMultipartForm 通用上传接口. // // --BOUNDARY // Content-Disposition: form-data; name="FIELDNAME"; filename="FILENAME" // Content-Type: application/octet-stream // // FILE-CONTENT // --BOUNDARY // Content-Disposition: form-data; name="FIELDNAME" // // JSON-DESCRIPTION // --BOUNDARY-- // // // NOTE: // 1. 一般不需要调用这个方法, 请直接调用高层次的封装函数; // 2. 最终的 URL == incompleteURL + access_token; // 3. response 格式有要求, 要么是 *Error, 要么是下面结构体的指针(注意 Error 必须是第一个 Field): // struct { // Error // ... // } func (clt *Client) PostMultipartForm(incompleteURL string, fields []MultipartFormField, response interface{}) (err error) { ErrorStructValue, ErrorErrCodeValue := checkResponse(response) bodyBuf := mediaBufferPool.Get().(*bytes.Buffer) bodyBuf.Reset() defer mediaBufferPool.Put(bodyBuf) multipartWriter := multipart.NewWriter(bodyBuf) for i := 0; i < len(fields); i++ { if field := &fields[i]; field.IsFile { partWriter, err3 := multipartWriter.CreateFormFile(field.Name, field.FileName) if err3 != nil { return err3 } if _, err3 = io.Copy(partWriter, field.Value); err3 != nil { return err3 } } else { partWriter, err3 := multipartWriter.CreateFormField(field.Name) if err3 != nil { return err3 } if _, err3 = io.Copy(partWriter, field.Value); err3 != nil { return err3 } } } if err = multipartWriter.Close(); err != nil { return } requestBodyBytes := bodyBuf.Bytes() requestBodyType := multipartWriter.FormDataContentType() httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := incompleteURL + url.QueryEscape(token) if err = httpPostMultipartForm(httpClient, finalURL, requestBodyType, requestBodyBytes, response); err != nil { return } switch errCode := ErrorErrCodeValue.Int(); errCode { case ErrCodeOK: return case ErrCodeInvalidCredential, ErrCodeAccessTokenExpired: errMsg := ErrorStructValue.Field(errorErrMsgIndex).String() retry.DebugPrintError(errCode, errMsg, token) if !hasRetried { hasRetried = true ErrorStructValue.Set(errorZeroValue) if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: return } }