فهرست منبع

带参数的二维码

yaotian 8 سال پیش
والد
کامیت
c162203c17
7فایلهای تغییر یافته به همراه221 افزوده شده و 94 حذف شده
  1. 6 0
      mp.go
  2. 166 0
      mp/account/qrcode.go
  3. 34 0
      mp/base/base.go
  4. 4 20
      mp/material/material.go
  5. 4 60
      mp/menu/menu.go
  6. 4 11
      mp/template/template.go
  7. 3 3
      util/http.go

+ 6 - 0
mp.go

@@ -5,6 +5,7 @@ package gowechat
 import (
 import (
 	"net/http"
 	"net/http"
 
 
+	"github.com/yaotian/gowechat/mp/account"
 	"github.com/yaotian/gowechat/mp/bridge"
 	"github.com/yaotian/gowechat/mp/bridge"
 	"github.com/yaotian/gowechat/mp/jssdk"
 	"github.com/yaotian/gowechat/mp/jssdk"
 	"github.com/yaotian/gowechat/mp/material"
 	"github.com/yaotian/gowechat/mp/material"
@@ -60,3 +61,8 @@ func (c *MpMgr) GetMsgHandler(req *http.Request, writer http.ResponseWriter) *br
 	c.Context.Writer = writer
 	c.Context.Writer = writer
 	return bridge.NewMsgHandler(c.Context)
 	return bridge.NewMsgHandler(c.Context)
 }
 }
+
+// GetQrcode 带参数的二维码
+func (c *MpMgr) GetQrcode() *account.Qrcode {
+	return account.NewQrcode(c.Context)
+}

+ 166 - 0
mp/account/qrcode.go

@@ -0,0 +1,166 @@
+package account
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/chanxuehong/wechat/mp"
+	"github.com/yaotian/gowechat/mp/base"
+	"github.com/yaotian/gowechat/wxcontext"
+)
+
+const (
+	qrcodeURL = "https://api.weixin.qq.com/cgi-bin/qrcode/create"
+)
+
+//Qrcode 带参数的二维码
+type Qrcode struct {
+	base.MpBase
+}
+
+//NewQrcode 实例化
+func NewQrcode(context *wxcontext.Context) *Qrcode {
+	qrcode := new(Qrcode)
+	qrcode.Context = context
+	return qrcode
+}
+
+const (
+	//TemporaryQRCodeExpireSecondsLimit 临时二维码 expire_seconds 限制
+	TemporaryQRCodeExpireSecondsLimit = 604800
+	//PermanentQRCodeSceneIDLimit 永久二维码 scene_id 限制
+	PermanentQRCodeSceneIDLimit = 100000
+)
+
+//PermanentQRCode 永久二维码
+type PermanentQRCode struct {
+	// 下面两个字段同时只有一个有效, 非zero值表示有效.
+	SceneID     uint32 `json:"scene_id,omitempty"`  // 场景值ID, 临时二维码时为32位非0整型, 永久二维码时最大值为100000(目前参数只支持1--100000)
+	SceneString string `json:"scene_str,omitempty"` // 场景值ID(字符串形式的ID), 字符串类型, 长度限制为1到64, 仅永久二维码支持此字段
+
+	Ticket string `json:"ticket"` // 获取的二维码ticket, 凭借此ticket可以在有效时间内换取二维码.
+	URL    string `json:"url"`    // 二维码图片解析后的地址, 开发者可根据该地址自行生成需要的二维码图片
+}
+
+//TemporaryQRCode 临时二维码
+type TemporaryQRCode struct {
+	ExpireSeconds int `json:"expire_seconds,omitempty"` // 二维码的有效时间, 以秒为单位. 最大不超过 604800.
+	PermanentQRCode
+}
+
+//CreateTemporaryQRCode  创建临时二维码
+//  SceneId:       场景值ID, 为32位非0整型
+//  ExpireSeconds: 二维码有效时间, 以秒为单位.  最大不超过 604800.
+func (c *Qrcode) CreateTemporaryQRCode(SceneID uint32, ExpireSeconds int) (qrcode *TemporaryQRCode, err error) {
+	if SceneID == 0 {
+		err = errors.New("SceneId should be greater than 0")
+		return
+	}
+	if ExpireSeconds <= 0 {
+		err = errors.New("ExpireSeconds should be greater than 0")
+		return
+	}
+	var request struct {
+		ExpireSeconds int    `json:"expire_seconds"`
+		ActionName    string `json:"action_name"`
+		ActionInfo    struct {
+			Scene struct {
+				SceneID uint32 `json:"scene_id"`
+			} `json:"scene"`
+		} `json:"action_info"`
+	}
+	request.ExpireSeconds = ExpireSeconds
+	request.ActionName = "QR_SCENE"
+	request.ActionInfo.Scene.SceneID = SceneID
+
+	var result struct {
+		mp.Error
+		TemporaryQRCode
+	}
+
+	response, err := c.HTTPPostJSONWithAccessToken(qrcodeURL, &request)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+	result.TemporaryQRCode.SceneID = SceneID
+	qrcode = &result.TemporaryQRCode
+	return
+}
+
+//CreatePermanentQRCode 创建永久二维码
+//  SceneId: 场景值ID, 目前参数只支持1--100000
+func (c *Qrcode) CreatePermanentQRCode(sceneID uint32) (qrcode *PermanentQRCode, err error) {
+	if sceneID == 0 {
+		err = errors.New("SceneId should be greater than 0")
+		return
+	}
+	var request struct {
+		ActionName string `json:"action_name"`
+		ActionInfo struct {
+			Scene struct {
+				SceneID uint32 `json:"scene_id"`
+			} `json:"scene"`
+		} `json:"action_info"`
+	}
+	request.ActionName = "QR_LIMIT_SCENE"
+	request.ActionInfo.Scene.SceneID = sceneID
+
+	var result struct {
+		mp.Error
+		PermanentQRCode
+	}
+
+	response, err := c.HTTPPostJSONWithAccessToken(qrcodeURL, &request)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+
+	result.PermanentQRCode.SceneID = sceneID
+	qrcode = &result.PermanentQRCode
+	return
+}
+
+//CreatePermanentQRCodeWithSceneString 创建永久二维码
+//  SceneString: 场景值ID(字符串形式的ID), 字符串类型, 长度限制为1到64
+func (c *Qrcode) CreatePermanentQRCodeWithSceneString(SceneString string) (qrcode *PermanentQRCode, err error) {
+	if SceneString == "" {
+		err = errors.New("SceneString should not be empty")
+		return
+	}
+	var request struct {
+		ActionName string `json:"action_name"`
+		ActionInfo struct {
+			Scene struct {
+				SceneString string `json:"scene_str"`
+			} `json:"scene"`
+		} `json:"action_info"`
+	}
+	request.ActionName = "QR_LIMIT_STR_SCENE"
+	request.ActionInfo.Scene.SceneString = SceneString
+
+	var result struct {
+		mp.Error
+		PermanentQRCode
+	}
+
+	response, err := c.HTTPPostJSONWithAccessToken(qrcodeURL, &request)
+	if err != nil {
+		return nil, err
+	}
+	err = json.Unmarshal(response, &result)
+	if err != nil {
+		return
+	}
+
+	result.PermanentQRCode.SceneString = SceneString
+	qrcode = &result.PermanentQRCode
+	return
+}

+ 34 - 0
mp/base/base.go

@@ -55,3 +55,37 @@ Do:
 	}
 	}
 	return
 	return
 }
 }
+
+//HTTPPostJSONWithAccessToken post json 自动加上access token, 并retry
+func (c *MpBase) HTTPPostJSONWithAccessToken(url string, obj interface{}) (resp []byte, err error) {
+	retry := 1
+Do:
+	var accessToken string
+	accessToken, err = c.GetAccessToken()
+	if err != nil {
+		return
+	}
+
+	var target = ""
+	if strings.Contains(url, "?") {
+		target = fmt.Sprintf("%s&access_token=%s", url, accessToken)
+	} else {
+		target = fmt.Sprintf("%s?access_token=%s", url, accessToken)
+	}
+
+	resp, err = util.PostJSON(target, obj)
+
+	err = util.CheckCommonError(resp)
+	if err == util.ErrUnmarshall {
+		return
+	}
+	if err != nil {
+		if retry > 0 {
+			retry--
+			c.CleanAccessTokenCache()
+			goto Do
+		}
+		return
+	}
+	return
+}

+ 4 - 20
mp/material/material.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 
 
+	"github.com/yaotian/gowechat/mp/base"
 	"github.com/yaotian/gowechat/util"
 	"github.com/yaotian/gowechat/util"
 	"github.com/yaotian/gowechat/wxcontext"
 	"github.com/yaotian/gowechat/wxcontext"
 )
 )
@@ -17,7 +18,7 @@ const (
 
 
 //Material 素材管理
 //Material 素材管理
 type Material struct {
 type Material struct {
-	*wxcontext.Context
+	base.MpBase
 }
 }
 
 
 //NewMaterial init
 //NewMaterial init
@@ -54,14 +55,11 @@ type resArticles struct {
 func (material *Material) AddNews(articles []*Article) (mediaID string, err error) {
 func (material *Material) AddNews(articles []*Article) (mediaID string, err error) {
 	req := &reqArticles{articles}
 	req := &reqArticles{articles}
 
 
-	var accessToken string
-	accessToken, err = material.GetAccessToken()
+	responseBytes, err := material.HTTPPostJSONWithAccessToken(addNewsURL, req)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
 
 
-	uri := fmt.Sprintf("%s?access_token=%s", addNewsURL, accessToken)
-	responseBytes, err := util.PostJSON(uri, req)
 	var res resArticles
 	var res resArticles
 	err = json.Unmarshal(responseBytes, res)
 	err = json.Unmarshal(responseBytes, res)
 	if err != nil {
 	if err != nil {
@@ -174,23 +172,9 @@ type reqDeleteMaterial struct {
 
 
 //DeleteMaterial 删除永久素材
 //DeleteMaterial 删除永久素材
 func (material *Material) DeleteMaterial(mediaID string) error {
 func (material *Material) DeleteMaterial(mediaID string) error {
-	accessToken, err := material.GetAccessToken()
-	if err != nil {
-		return err
-	}
-
-	uri := fmt.Sprintf("%s?access_token=%s", delMaterialURL, accessToken)
-	response, err := util.PostJSON(uri, reqDeleteMaterial{mediaID})
+	_, err := material.HTTPPostJSONWithAccessToken(delMaterialURL, reqDeleteMaterial{mediaID})
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	var resDeleteMaterial util.CommonError
-	err = json.Unmarshal(response, &resDeleteMaterial)
-	if err != nil {
-		return err
-	}
-	if resDeleteMaterial.ErrCode != 0 {
-		return fmt.Errorf("DeleteMaterial error : errcode=%v , errmsg=%v", resDeleteMaterial.ErrCode, resDeleteMaterial.ErrMsg)
-	}
 	return nil
 	return nil
 }
 }

+ 4 - 60
mp/menu/menu.go

@@ -2,7 +2,6 @@ package menu
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"fmt"
 
 
 	"github.com/yaotian/gowechat/mp/base"
 	"github.com/yaotian/gowechat/mp/base"
 	"github.com/yaotian/gowechat/util"
 	"github.com/yaotian/gowechat/util"
@@ -121,28 +120,13 @@ func NewMenu(context *wxcontext.Context) *Menu {
 
 
 //SetMenu 设置按钮
 //SetMenu 设置按钮
 func (menu *Menu) SetMenu(buttons []*Button) error {
 func (menu *Menu) SetMenu(buttons []*Button) error {
-	accessToken, err := menu.GetAccessToken()
-	if err != nil {
-		return err
-	}
-
-	uri := fmt.Sprintf("%s?access_token=%s", menuCreateURL, accessToken)
 	reqMenu := &reqMenu{
 	reqMenu := &reqMenu{
 		Button: buttons,
 		Button: buttons,
 	}
 	}
-
-	response, err := util.PostJSON(uri, reqMenu)
-	if err != nil {
-		return err
-	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
+	_, err := menu.HTTPPostJSONWithAccessToken(menuCreateURL, reqMenu)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("SetMenu Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
 	return nil
 	return nil
 }
 }
 
 
@@ -165,70 +149,34 @@ func (menu *Menu) DeleteMenu() (err error) {
 
 
 //AddConditional 添加个性化菜单
 //AddConditional 添加个性化菜单
 func (menu *Menu) AddConditional(buttons []*Button, matchRule *MatchRule) error {
 func (menu *Menu) AddConditional(buttons []*Button, matchRule *MatchRule) error {
-	accessToken, err := menu.GetAccessToken()
-	if err != nil {
-		return err
-	}
-
-	uri := fmt.Sprintf("%s?access_token=%s", menuAddConditionalURL, accessToken)
 	reqMenu := &reqMenu{
 	reqMenu := &reqMenu{
 		Button:    buttons,
 		Button:    buttons,
 		MatchRule: matchRule,
 		MatchRule: matchRule,
 	}
 	}
-
-	response, err := util.PostJSON(uri, reqMenu)
-	if err != nil {
-		return err
-	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
+	_, err := menu.HTTPPostJSONWithAccessToken(menuAddConditionalURL, reqMenu)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("AddConditional Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
 	return nil
 	return nil
 }
 }
 
 
 //DeleteConditional 删除个性化菜单
 //DeleteConditional 删除个性化菜单
 func (menu *Menu) DeleteConditional(menuID int64) error {
 func (menu *Menu) DeleteConditional(menuID int64) error {
-	accessToken, err := menu.GetAccessToken()
-	if err != nil {
-		return err
-	}
-
-	uri := fmt.Sprintf("%s?access_token=%s", menuDeleteConditionalURL, accessToken)
 	reqDeleteConditional := &reqDeleteConditional{
 	reqDeleteConditional := &reqDeleteConditional{
 		MenuID: menuID,
 		MenuID: menuID,
 	}
 	}
-
-	response, err := util.PostJSON(uri, reqDeleteConditional)
-	if err != nil {
-		return err
-	}
-	var commError util.CommonError
-	err = json.Unmarshal(response, &commError)
+	_, err := menu.HTTPPostJSONWithAccessToken(menuDeleteConditionalURL, reqDeleteConditional)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if commError.ErrCode != 0 {
-		return fmt.Errorf("DeleteConditional Error , errcode=%d , errmsg=%s", commError.ErrCode, commError.ErrMsg)
-	}
 	return nil
 	return nil
 }
 }
 
 
 //MenuTryMatch 菜单匹配
 //MenuTryMatch 菜单匹配
 func (menu *Menu) MenuTryMatch(userID string) (buttons []Button, err error) {
 func (menu *Menu) MenuTryMatch(userID string) (buttons []Button, err error) {
-	var accessToken string
-	accessToken, err = menu.GetAccessToken()
-	if err != nil {
-		return
-	}
-	uri := fmt.Sprintf("%s?access_token=%s", menuTryMatchURL, accessToken)
 	reqMenuTryMatch := &reqMenuTryMatch{userID}
 	reqMenuTryMatch := &reqMenuTryMatch{userID}
 	var response []byte
 	var response []byte
-	response, err = util.PostJSON(uri, reqMenuTryMatch)
+	response, err = menu.HTTPPostJSONWithAccessToken(menuTryMatchURL, reqMenuTryMatch)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
@@ -237,10 +185,6 @@ func (menu *Menu) MenuTryMatch(userID string) (buttons []Button, err error) {
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
-	if resMenuTryMatch.ErrCode != 0 {
-		err = fmt.Errorf("MenuTryMatch Error , errcode=%d , errmsg=%s", resMenuTryMatch.ErrCode, resMenuTryMatch.ErrMsg)
-		return
-	}
 	buttons = resMenuTryMatch.Button
 	buttons = resMenuTryMatch.Button
 	return
 	return
 }
 }

+ 4 - 11
mp/template/template.go

@@ -2,8 +2,8 @@ package template
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"fmt"
 
 
+	"github.com/yaotian/gowechat/mp/base"
 	"github.com/yaotian/gowechat/util"
 	"github.com/yaotian/gowechat/util"
 	"github.com/yaotian/gowechat/wxcontext"
 	"github.com/yaotian/gowechat/wxcontext"
 )
 )
@@ -14,7 +14,7 @@ const (
 
 
 //Template 模板消息
 //Template 模板消息
 type Template struct {
 type Template struct {
-	*wxcontext.Context
+	base.MpBase
 }
 }
 
 
 //NewTemplate 实例化
 //NewTemplate 实例化
@@ -52,23 +52,16 @@ type resTemplateSend struct {
 
 
 //Send 发送模板消息
 //Send 发送模板消息
 func (tpl *Template) Send(msg *Message) (msgID int64, err error) {
 func (tpl *Template) Send(msg *Message) (msgID int64, err error) {
-	var accessToken string
-	accessToken, err = tpl.GetAccessToken()
+	response, err := tpl.HTTPPostJSONWithAccessToken(templateSendURL, msg)
 	if err != nil {
 	if err != nil {
-		return
+		return 0, err
 	}
 	}
-	uri := fmt.Sprintf("%s?access_token=%s", templateSendURL, accessToken)
-	response, err := util.PostJSON(uri, msg)
 
 
 	var result resTemplateSend
 	var result resTemplateSend
 	err = json.Unmarshal(response, &result)
 	err = json.Unmarshal(response, &result)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
-	if result.ErrCode != 0 {
-		err = fmt.Errorf("template msg send error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
-		return
-	}
 	msgID = result.MsgID
 	msgID = result.MsgID
 	return
 	return
 }
 }

+ 3 - 3
util/http.go

@@ -29,7 +29,7 @@ func HTTPGet(uri string) ([]byte, error) {
 }
 }
 
 
 //PostJSON post json 数据请求
 //PostJSON post json 数据请求
-func PostJSON(uri string, obj interface{}) ([]byte, error) {
+func PostJSON(url string, obj interface{}) ([]byte, error) {
 	jsonData, err := json.Marshal(obj)
 	jsonData, err := json.Marshal(obj)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -40,14 +40,14 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) {
 	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
 	jsonData = bytes.Replace(jsonData, []byte("\\u0026"), []byte("&"), -1)
 
 
 	body := bytes.NewBuffer(jsonData)
 	body := bytes.NewBuffer(jsonData)
-	response, err := http.Post(uri, "application/json;charset=utf-8", body)
+	response, err := http.Post(url, "application/json;charset=utf-8", body)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	defer response.Body.Close()
 	defer response.Body.Close()
 
 
 	if response.StatusCode != http.StatusOK {
 	if response.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
+		return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", url, response.StatusCode)
 	}
 	}
 	return ioutil.ReadAll(response.Body)
 	return ioutil.ReadAll(response.Body)
 }
 }