Przeglądaj źródła

Merge branch 'v2' of github.com:silenceper/wechat into feature/remove-redis

# Conflicts:
#	cache/redis.go
houseme 1 rok temu
rodzic
commit
f62fd8de8e

+ 2 - 0
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 请求并返回二维码二进制数据

+ 39 - 9
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"`
 }

+ 33 - 0
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) {

+ 29 - 27
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")

+ 52 - 17
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

+ 52 - 0
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
+}