diff --git a/miniprogram/qrcode/qrcode.go b/miniprogram/qrcode/qrcode.go index 41e67e5..bb51b03 100644 --- a/miniprogram/qrcode/qrcode.go +++ b/miniprogram/qrcode/qrcode.go @@ -54,6 +54,8 @@ type QRCoder struct { IsHyaline bool `json:"is_hyaline,omitempty"` // envVersion 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" EnvVersion string `json:"env_version,omitempty"` + // ShowSplashAd 控制通过该小程序码进入小程序是否展示封面广告1、默认为true,展示封面广告2、传入为false时,不展示封面广告 + ShowSplashAd bool `json:"show_splash_ad,omitempty"` } // fetchCode 请求并返回二维码二进制数据 diff --git a/officialaccount/material/material.go b/officialaccount/material/material.go index 5b5f1c6..18f5225 100644 --- a/officialaccount/material/material.go +++ b/officialaccount/material/material.go @@ -4,6 +4,9 @@ import ( "encoding/json" "errors" "fmt" + "io" + "os" + "path" "github.com/silenceper/wechat/v2/officialaccount/context" "github.com/silenceper/wechat/v2/util" @@ -160,8 +163,8 @@ type resAddMaterial struct { URL string `json:"url"` } -// AddMaterial 上传永久性素材(处理视频需要单独上传) -func (material *Material) AddMaterial(mediaType MediaType, filename string) (mediaID string, url string, err error) { +// AddMaterialFromReader 上传永久性素材(处理视频需要单独上传),从 io.Reader 中读取 +func (material *Material) AddMaterialFromReader(mediaType MediaType, filePath string, reader io.Reader) (mediaID string, url string, err error) { if mediaType == MediaTypeVideo { err = errors.New("永久视频素材上传使用 AddVideo 方法") return @@ -173,8 +176,10 @@ func (material *Material) AddMaterial(mediaType MediaType, filename string) (med } uri := fmt.Sprintf("%s?access_token=%s&type=%s", addMaterialURL, accessToken, mediaType) + // 获取文件名 + filename := path.Base(filePath) var response []byte - response, err = util.PostFile("media", filename, uri) + response, err = util.PostFileFromReader("media", filePath, filename, uri, reader) if err != nil { return } @@ -192,13 +197,24 @@ func (material *Material) AddMaterial(mediaType MediaType, filename string) (med return } +// AddMaterial 上传永久性素材(处理视频需要单独上传) +func (material *Material) AddMaterial(mediaType MediaType, filename string) (mediaID string, url string, err error) { + f, err := os.Open(filename) + if err != nil { + return + } + defer func() { _ = f.Close() }() + + return material.AddMaterialFromReader(mediaType, filename, f) +} + type reqVideo struct { Title string `json:"title"` Introduction string `json:"introduction"` } -// AddVideo 永久视频素材文件上传 -func (material *Material) AddVideo(filename, title, introduction string) (mediaID string, url string, err error) { +// AddVideoFromReader 永久视频素材文件上传,从 io.Reader 中读取 +func (material *Material) AddVideoFromReader(filePath, title, introduction string, reader io.Reader) (mediaID string, url string, err error) { var accessToken string accessToken, err = material.GetAccessToken() if err != nil { @@ -216,16 +232,19 @@ func (material *Material) AddVideo(filename, title, introduction string) (mediaI if err != nil { return } - + fileName := path.Base(filePath) fields := []util.MultipartFormField{ { - IsFile: true, - Fieldname: "media", - Filename: filename, + IsFile: true, + Fieldname: "media", + FilePath: filePath, + Filename: fileName, + FileReader: reader, }, { IsFile: false, Fieldname: "description", + Filename: fileName, Value: fieldValue, }, } @@ -250,6 +269,17 @@ func (material *Material) AddVideo(filename, title, introduction string) (mediaI return } +// AddVideo 永久视频素材文件上传 +func (material *Material) AddVideo(directory, title, introduction string) (mediaID string, url string, err error) { + f, err := os.Open(directory) + if err != nil { + return "", "", err + } + defer func() { _ = f.Close() }() + + return material.AddVideoFromReader(directory, title, introduction, f) +} + type reqDeleteMaterial struct { MediaID string `json:"media_id"` } diff --git a/officialaccount/material/media.go b/officialaccount/material/media.go index 316758f..b7d6573 100644 --- a/officialaccount/material/media.go +++ b/officialaccount/material/media.go @@ -3,6 +3,7 @@ package material import ( "encoding/json" "fmt" + "io" "github.com/silenceper/wechat/v2/util" ) @@ -62,6 +63,38 @@ func (material *Material) MediaUpload(mediaType MediaType, filename string) (med return } +// MediaUploadFromReader 临时素材上传 +func (material *Material) MediaUploadFromReader(mediaType MediaType, filename string, reader io.Reader) (media Media, err error) { + var accessToken string + accessToken, err = material.GetAccessToken() + if err != nil { + return + } + + uri := fmt.Sprintf("%s?access_token=%s&type=%s", mediaUploadURL, accessToken, mediaType) + + var byteData []byte + byteData, err = io.ReadAll(reader) + if err != nil { + return + } + + var response []byte + response, err = util.PostFileByStream("media", filename, uri, byteData) + if err != nil { + return + } + err = json.Unmarshal(response, &media) + if err != nil { + return + } + if media.ErrCode != 0 { + err = fmt.Errorf("MediaUpload error : errcode=%v , errmsg=%v", media.ErrCode, media.ErrMsg) + return + } + return +} + // GetMediaURL 返回临时素材的下载地址供用户自己处理 // NOTICE: URL 不可公开,因为含access_token 需要立即另存文件 func (material *Material) GetMediaURL(mediaID string) (mediaURL string, err error) { diff --git a/officialaccount/message/template.go b/officialaccount/message/template.go index 1c657ea..1717635 100644 --- a/officialaccount/message/template.go +++ b/officialaccount/message/template.go @@ -61,15 +61,15 @@ func (tpl *Template) Send(msg *TemplateMessage) (msgID int64, err error) { if err != nil { return } - uri := fmt.Sprintf("%s?access_token=%s", templateSendURL, accessToken) - var response []byte - response, err = util.PostJSON(uri, msg) - if err != nil { + var ( + uri = fmt.Sprintf("%s?access_token=%s", templateSendURL, accessToken) + response []byte + ) + if response, err = util.PostJSON(uri, msg); err != nil { return } var result resTemplateSend - err = json.Unmarshal(response, &result) - if err != nil { + if err = json.Unmarshal(response, &result); err != nil { return } if result.ErrCode != 0 { @@ -103,10 +103,11 @@ func (tpl *Template) List() (templateList []*TemplateItem, err error) { if err != nil { return } - uri := fmt.Sprintf("%s?access_token=%s", templateListURL, accessToken) - var response []byte - response, err = util.HTTPGet(uri) - if err != nil { + var ( + uri = fmt.Sprintf("%s?access_token=%s", templateListURL, accessToken) + response []byte + ) + if response, err = util.HTTPGet(uri); err != nil { return } var res resTemplateList @@ -121,22 +122,23 @@ type resTemplateAdd struct { } // Add 添加模板. -func (tpl *Template) Add(shortID string) (templateID string, err error) { +func (tpl *Template) Add(shortID string, keyNameList []string) (templateID string, err error) { var accessToken string accessToken, err = tpl.GetAccessToken() if err != nil { return } - var msg = struct { - ShortID string `json:"template_id_short"` - }{ShortID: shortID} - uri := fmt.Sprintf("%s?access_token=%s", templateAddURL, accessToken) - var response []byte - response, err = util.PostJSON(uri, msg) - if err != nil { + var ( + msg = struct { + ShortID string `json:"template_id_short"` + KeyNameList []string `json:"keyword_name_list"` + }{ShortID: shortID, KeyNameList: keyNameList} + uri = fmt.Sprintf("%s?access_token=%s", templateAddURL, accessToken) + response []byte + ) + if response, err = util.PostJSON(uri, msg); err != nil { return } - var result resTemplateAdd err = util.DecodeWithError(response, &result, "AddTemplate") return result.TemplateID, err @@ -149,14 +151,14 @@ func (tpl *Template) Delete(templateID string) (err error) { if err != nil { return } - var msg = struct { - TemplateID string `json:"template_id"` - }{TemplateID: templateID} - - uri := fmt.Sprintf("%s?access_token=%s", templateDelURL, accessToken) - var response []byte - response, err = util.PostJSON(uri, msg) - if err != nil { + var ( + msg = struct { + TemplateID string `json:"template_id"` + }{TemplateID: templateID} + uri = fmt.Sprintf("%s?access_token=%s", templateDelURL, accessToken) + response []byte + ) + if response, err = util.PostJSON(uri, msg); err != nil { return } return util.DecodeWithCommonError(response, "DeleteTemplate") diff --git a/util/http.go b/util/http.go index 26e4a7b..948e38f 100644 --- a/util/http.go +++ b/util/http.go @@ -146,13 +146,40 @@ func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, e return responseData, contentType, err } +// PostFileByStream 上传文件 +func PostFileByStream(fieldName, fileName, uri string, byteData []byte) ([]byte, error) { + fields := []MultipartFormField{ + { + IsFile: false, + Fieldname: fieldName, + Filename: fileName, + Value: byteData, + }, + } + return PostMultipartForm(fields, uri) +} + // PostFile 上传文件 -func PostFile(fieldName, filename, uri string) ([]byte, error) { +func PostFile(fieldName, filePath, uri string) ([]byte, error) { fields := []MultipartFormField{ { IsFile: true, Fieldname: fieldName, - Filename: filename, + FilePath: filePath, + }, + } + return PostMultipartForm(fields, uri) +} + +// PostFileFromReader 上传文件,从 io.Reader 中读取 +func PostFileFromReader(filedName, filePath, fileName, uri string, reader io.Reader) ([]byte, error) { + fields := []MultipartFormField{ + { + IsFile: true, + Fieldname: filedName, + FilePath: filePath, + Filename: fileName, + FileReader: reader, }, } return PostMultipartForm(fields, uri) @@ -160,10 +187,12 @@ func PostFile(fieldName, filename, uri string) ([]byte, error) { // MultipartFormField 保存文件或其他字段信息 type MultipartFormField struct { - IsFile bool - Fieldname string - Value []byte - Filename string + IsFile bool + Fieldname string + Value []byte + FilePath string + Filename string + FileReader io.Reader } // PostMultipartForm 上传文件或其他多个字段 @@ -182,18 +211,24 @@ func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte return } - fh, e := os.Open(field.Filename) - if e != nil { - err = fmt.Errorf("error opening file , err=%v", e) - return - } - defer fh.Close() - - if _, err = io.Copy(fileWriter, fh); err != nil { - return + if field.FileReader == nil { + fh, e := os.Open(field.FilePath) + if e != nil { + err = fmt.Errorf("error opening file , err=%v", e) + return + } + _, err = io.Copy(fileWriter, fh) + _ = fh.Close() + if err != nil { + return + } + } else { + if _, err = io.Copy(fileWriter, field.FileReader); err != nil { + return + } } } else { - partWriter, e := bodyWriter.CreateFormField(field.Fieldname) + partWriter, e := bodyWriter.CreateFormFile(field.Fieldname, field.Filename) if e != nil { err = e return @@ -215,7 +250,7 @@ func PostMultipartForm(fields []MultipartFormField, uri string) (respBody []byte } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, err + return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, resp.StatusCode) } respBody, err = io.ReadAll(resp.Body) return diff --git a/work/material/media.go b/work/material/media.go index b32785a..b9647ad 100644 --- a/work/material/media.go +++ b/work/material/media.go @@ -2,6 +2,7 @@ package material import ( "fmt" + "io" "github.com/silenceper/wechat/v2/util" ) @@ -96,3 +97,54 @@ func (r *Client) UploadAttachment(filename string, mediaType string, attachmentT err = util.DecodeWithError(response, result, "UploadAttachment") return result, err } + +// UploadTempFileFromReader 上传临时素材 +// @see https://developer.work.weixin.qq.com/document/path/90253 +// @mediaType 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file) +func (r *Client) UploadTempFileFromReader(filename, mediaType string, reader io.Reader) (*UploadTempFileResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var byteData []byte + byteData, err = io.ReadAll(reader) + if err != nil { + return nil, err + } + var response []byte + if response, err = util.PostFileByStream("media", filename, fmt.Sprintf(uploadTempFile, accessToken, mediaType), byteData); err != nil { + return nil, err + } + result := &UploadTempFileResponse{} + err = util.DecodeWithError(response, result, "UploadTempFile") + return result, err +} + +// UploadAttachmentFromReader 上传附件资源 +// @see https://developer.work.weixin.qq.com/document/path/95098 +// @mediaType 媒体文件类型,分别有图片(image)、视频(video)、普通文件(file) +// @attachment_type 附件类型,不同的附件类型用于不同的场景。1:朋友圈;2:商品图册 +func (r *Client) UploadAttachmentFromReader(filename, mediaType string, reader io.Reader, attachmentType int) (*UploadAttachmentResponse, error) { + var ( + accessToken string + err error + ) + if accessToken, err = r.GetAccessToken(); err != nil { + return nil, err + } + var byteData []byte + byteData, err = io.ReadAll(reader) + if err != nil { + return nil, err + } + var response []byte + if response, err = util.PostFileByStream("media", filename, fmt.Sprintf(uploadAttachment, accessToken, mediaType, attachmentType), byteData); err != nil { + return nil, err + } + result := &UploadAttachmentResponse{} + err = util.DecodeWithError(response, result, "UploadAttachment") + return result, err +}