Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加了对第三方平台的支持, 获取AccessToken, 并通过AccessToken来调用公众号接口 #225

Open
wants to merge 7 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mp/core/mixed_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type MixedMsg struct {
Longitude float64 `xml:"Longitude" json:"Longitude"` // request
Precision float64 `xml:"Precision" json:"Precision"` // request

CompVeriTicket string `xml:"ComponentVerifyTicket" json:"ComponentVerifyTicket"`

// menu
MenuId int64 `xml:"MenuId" json:"MenuId"`
ScanCodeInfo *struct {
Expand Down
86 changes: 14 additions & 72 deletions mp/core/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strconv"
"sync"
"sync/atomic"
"unicode"
"unsafe"

"github.com/chanxuehong/util/security"
Expand Down Expand Up @@ -337,7 +336,7 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, query url.V
}

var mixedMsg MixedMsg
if err = xml.Unmarshal(msgPlaintext, &mixedMsg); err != nil {
if err = unmarshalXMLMessage(msgPlaintext, &mixedMsg); err != nil {
errorHandler.ServeError(w, r, err)
return
}
Expand Down Expand Up @@ -431,7 +430,7 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, query url.V
callback.DebugPrintPlainRequestMessage(msgPlaintext)

var mixedMsg MixedMsg
if err = xml.Unmarshal(msgPlaintext, &mixedMsg); err != nil {
if err = unmarshalXMLMessage(msgPlaintext, &mixedMsg); err != nil {
errorHandler.ServeError(w, r, err)
return
}
Expand Down Expand Up @@ -526,6 +525,7 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, query url.V

type cipherRequestHttpBody struct {
XMLName struct{} `xml:"xml"`
AppID string `xml:"AppId"`
ToUserName string `xml:"ToUserName"`
Base64EncryptedMsg []byte `xml:"Encrypt"`
}
Expand All @@ -544,78 +544,20 @@ var (
cdataEndLiteral = []byte("]]>")
)

func unmarshalXMLMessage(data []byte, v interface{}) error {
defer func() {
if r := recover(); r != nil {
log.Printf("recovered from xml.unmarshal with xml: %s, type: %T, err: %s\n", data, v, r)
}
}()
return xml.Unmarshal(data, v)
}

//<xml>
// <AppId><![CDATA[wxc792941638093b10]]></AppId>
// <ToUserName><![CDATA[gh_b1eb3f8bd6c6]]></ToUserName>
// <Encrypt><![CDATA[DlCGq+lWQuyjNNK+vDaO0zUltpdUW3u4V00WCzsdNzmZGEhrU7TPxG52viOKCWYPwTMbCzgbCtakZHyNxr5hjoZJ7ORAUYoIAGQy/LDWtAnYgDO+ppKLp0rDq+67Dv3yt+vatMQTh99NII6x9SEGpY3O2h8RpG99+NYevQiOLVKqiQYzan21sX/jE4Y3wZaeudsb4QVjqzRAPaCJ5nS3T31uIR9fjSRgHTDRDOzjQ1cHchge+t6faUhniN5VQVTE+wIYtmnejc55BmHYPfBnTkYah9+cTYnI3diUPJRRiyVocJyHlb+XOZN22dsx9yzKHBAyagaoDIV8Yyb/PahcUbsqGv5wziOgLJQIa6z93/VY7d2Kq2C2oBS+Qb+FI9jLhgc3RvCi+Yno2X3cWoqbsRwoovYdyg6jme/H7nMZn77PSxOGRt/dYiWx2NuBAF7fNFigmbRiive3DyOumNCMvA==]]></Encrypt>
//</xml>
func xmlUnmarshal(data []byte, p *cipherRequestHttpBody) error {
data = bytes.TrimSpace(data)
if !bytes.HasPrefix(data, msgStartElementLiteral) || !bytes.HasSuffix(data, msgEndElementLiteral) {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
data2 := data[len(msgStartElementLiteral) : len(data)-len(msgEndElementLiteral)]

// ToUserName
ToUserNameElementBytes := data2
i := bytes.Index(ToUserNameElementBytes, msgToUserNameStartElementLiteral)
if i == -1 {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
ToUserNameElementBytes = ToUserNameElementBytes[i+len(msgToUserNameStartElementLiteral):]
ToUserNameElementBytes = bytes.TrimLeftFunc(ToUserNameElementBytes, unicode.IsSpace)
if !bytes.HasPrefix(ToUserNameElementBytes, cdataStartLiteral) {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
ToUserNameElementBytes = ToUserNameElementBytes[len(cdataStartLiteral):]
i = bytes.Index(ToUserNameElementBytes, cdataEndLiteral)
if i == -1 {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
ToUserName := ToUserNameElementBytes[:i]
ToUserNameElementBytes = ToUserNameElementBytes[i+len(cdataEndLiteral):]
ToUserNameElementBytes = bytes.TrimLeftFunc(ToUserNameElementBytes, unicode.IsSpace)
if !bytes.HasPrefix(ToUserNameElementBytes, msgToUserNameEndElementLiteral) {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
ToUserNameElementBytes = ToUserNameElementBytes[len(msgToUserNameEndElementLiteral):]

// Encrypt
EncryptElementBytes := ToUserNameElementBytes
i = bytes.Index(EncryptElementBytes, msgEncryptStartElementLiteral)
if i == -1 {
EncryptElementBytes = data2
i = bytes.Index(EncryptElementBytes, msgEncryptStartElementLiteral)
if i == -1 {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
}
EncryptElementBytes = EncryptElementBytes[i+len(msgEncryptStartElementLiteral):]
EncryptElementBytes = bytes.TrimLeftFunc(EncryptElementBytes, unicode.IsSpace)
if !bytes.HasPrefix(EncryptElementBytes, cdataStartLiteral) {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
EncryptElementBytes = EncryptElementBytes[len(cdataStartLiteral):]
i = bytes.Index(EncryptElementBytes, cdataEndLiteral)
if i == -1 {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}
Encrypt := EncryptElementBytes[:i]
EncryptElementBytes = EncryptElementBytes[i+len(cdataEndLiteral):]
EncryptElementBytes = bytes.TrimLeftFunc(EncryptElementBytes, unicode.IsSpace)
if !bytes.HasPrefix(EncryptElementBytes, msgEncryptEndElementLiteral) {
log.Printf("[WARNING] xmlUnmarshal failed, data:\n%s\n", data)
return xml.Unmarshal(data, p)
}

p.ToUserName = string(ToUserName)
p.Base64EncryptedMsg = Encrypt
return nil
return unmarshalXMLMessage(data, p)
}
65 changes: 48 additions & 17 deletions mp/core/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,57 @@ import (
"testing"
)

func TestXmlUnmarshal(t *testing.T) {
data := []byte(`<xml>
<ToUserName><![CDATA[gh_b1eb3f8bd6c6]]></ToUserName>
<Encrypt><![CDATA[DlCGq+lWQuyjNNK+vDaO0zUltpdUW3u4V00WCzsdNzmZGEhrU7TPxG52viOKCWYPwTMbCzgbCtakZHyNxr5hjoZJ7ORAUYoIAGQy/LDWtAnYgDO+ppKLp0rDq+67Dv3yt+vatMQTh99NII6x9SEGpY3O2h8RpG99+NYevQiOLVKqiQYzan21sX/jE4Y3wZaeudsb4QVjqzRAPaCJ5nS3T31uIR9fjSRgHTDRDOzjQ1cHchge+t6faUhniN5VQVTE+wIYtmnejc55BmHYPfBnTkYah9+cTYnI3diUPJRRiyVocJyHlb+XOZN22dsx9yzKHBAyagaoDIV8Yyb/PahcUbsqGv5wziOgLJQIa6z93/VY7d2Kq2C2oBS+Qb+FI9jLhgc3RvCi+Yno2X3cWoqbsRwoovYdyg6jme/H7nMZn77PSxOGRt/dYiWx2NuBAF7fNFigmbRiive3DyOumNCMvA==]]></Encrypt>
</xml>`)
type xmlTestData struct {
Data []byte
WantToUserName string
WantAppID string
WantEncrypt string
}

var x cipherRequestHttpBody
if err := xmlUnmarshal(data, &x); err != nil {
t.Error(err)
return
}
if x.ToUserName != "gh_b1eb3f8bd6c6" {
t.Errorf("ToUserName mismatch,\nhave: %s\nwant: %s\n", x.ToUserName, "gh_b1eb3f8bd6c6")
return
func TestXmlUnmarshal(t *testing.T) {
testTable := []xmlTestData{
xmlTestData{
[]byte(`<xml>
<ToUserName><![CDATA[gh_b1eb3f8bd6c6]]></ToUserName>
<Encrypt><![CDATA[DlCGq+lWQuyjNNK+vDaO0zUltpdUW3u4V00WCzsdNzmZGEhrU7TPxG52viOKCWYPwTMbCzgbCtakZHyNxr5hjoZJ7ORAUYoIAGQy/LDWtAnYgDO+ppKLp0rDq+67Dv3yt+vatMQTh99NII6x9SEGpY3O2h8RpG99+NYevQiOLVKqiQYzan21sX/jE4Y3wZaeudsb4QVjqzRAPaCJ5nS3T31uIR9fjSRgHTDRDOzjQ1cHchge+t6faUhniN5VQVTE+wIYtmnejc55BmHYPfBnTkYah9+cTYnI3diUPJRRiyVocJyHlb+XOZN22dsx9yzKHBAyagaoDIV8Yyb/PahcUbsqGv5wziOgLJQIa6z93/VY7d2Kq2C2oBS+Qb+FI9jLhgc3RvCi+Yno2X3cWoqbsRwoovYdyg6jme/H7nMZn77PSxOGRt/dYiWx2NuBAF7fNFigmbRiive3DyOumNCMvA==]]></Encrypt>
</xml>`),
"gh_b1eb3f8bd6c6",
"",
`DlCGq+lWQuyjNNK+vDaO0zUltpdUW3u4V00WCzsdNzmZGEhrU7TPxG52viOKCWYPwTMbCzgbCtakZHyNxr5hjoZJ7ORAUYoIAGQy/LDWtAnYgDO+ppKLp0rDq+67Dv3yt+vatMQTh99NII6x9SEGpY3O2h8RpG99+NYevQiOLVKqiQYzan21sX/jE4Y3wZaeudsb4QVjqzRAPaCJ5nS3T31uIR9fjSRgHTDRDOzjQ1cHchge+t6faUhniN5VQVTE+wIYtmnejc55BmHYPfBnTkYah9+cTYnI3diUPJRRiyVocJyHlb+XOZN22dsx9yzKHBAyagaoDIV8Yyb/PahcUbsqGv5wziOgLJQIa6z93/VY7d2Kq2C2oBS+Qb+FI9jLhgc3RvCi+Yno2X3cWoqbsRwoovYdyg6jme/H7nMZn77PSxOGRt/dYiWx2NuBAF7fNFigmbRiive3DyOumNCMvA==`,
},
xmlTestData{
[]byte(`<xml>
<AppId><![CDATA[wxc792941638093b10]]></AppId>
<Encrypt><![CDATA[r2sCuDQ4vOp4r4y3TjE/HK+LiethCVOiwMdQAasoLjxqiPuGdJG+hDBhXj06uL5MQyoCMJ34sRl1ilstnmuRrThy43J+M7egX0SYe01S721F3uOjCv2sDEzA04PEu+3GFydJ01VPj4uYzldHrH4F3YLCJBrxHWR/0InA2bnERozZ34IZkZjdDbmAjdlPXgM+6iWpy7R70yPlN+cZFGhwpzSyiINwlOSKQZ8eoXSufrn2RXobq9V5x6WfwJuZfq47m6yaE6Mjvm9g6YWJ0POsw8wfQ0pFL2XwkYj85O8yOfay5UxXR1cTKz3H5ezfxQYSFiIv+qtGWRyZfGBV+qLdCsbrknbo8NsOv4/ECr4DJvrPMbv0vXsua396yS1QAmQoE9iNpB938iXZ7Uk+okU0/vrDA0OgdYQeM35lD3plA7E5zkd1cSkoGyrMOYR2i2b6fM2G0s4Pj3Aw3MqxQw6SVg==]]></Encrypt>
</xml>`),
"",
"wxc792941638093b10",
`r2sCuDQ4vOp4r4y3TjE/HK+LiethCVOiwMdQAasoLjxqiPuGdJG+hDBhXj06uL5MQyoCMJ34sRl1ilstnmuRrThy43J+M7egX0SYe01S721F3uOjCv2sDEzA04PEu+3GFydJ01VPj4uYzldHrH4F3YLCJBrxHWR/0InA2bnERozZ34IZkZjdDbmAjdlPXgM+6iWpy7R70yPlN+cZFGhwpzSyiINwlOSKQZ8eoXSufrn2RXobq9V5x6WfwJuZfq47m6yaE6Mjvm9g6YWJ0POsw8wfQ0pFL2XwkYj85O8yOfay5UxXR1cTKz3H5ezfxQYSFiIv+qtGWRyZfGBV+qLdCsbrknbo8NsOv4/ECr4DJvrPMbv0vXsua396yS1QAmQoE9iNpB938iXZ7Uk+okU0/vrDA0OgdYQeM35lD3plA7E5zkd1cSkoGyrMOYR2i2b6fM2G0s4Pj3Aw3MqxQw6SVg==`,
},
}
wantEncrypt := `DlCGq+lWQuyjNNK+vDaO0zUltpdUW3u4V00WCzsdNzmZGEhrU7TPxG52viOKCWYPwTMbCzgbCtakZHyNxr5hjoZJ7ORAUYoIAGQy/LDWtAnYgDO+ppKLp0rDq+67Dv3yt+vatMQTh99NII6x9SEGpY3O2h8RpG99+NYevQiOLVKqiQYzan21sX/jE4Y3wZaeudsb4QVjqzRAPaCJ5nS3T31uIR9fjSRgHTDRDOzjQ1cHchge+t6faUhniN5VQVTE+wIYtmnejc55BmHYPfBnTkYah9+cTYnI3diUPJRRiyVocJyHlb+XOZN22dsx9yzKHBAyagaoDIV8Yyb/PahcUbsqGv5wziOgLJQIa6z93/VY7d2Kq2C2oBS+Qb+FI9jLhgc3RvCi+Yno2X3cWoqbsRwoovYdyg6jme/H7nMZn77PSxOGRt/dYiWx2NuBAF7fNFigmbRiive3DyOumNCMvA==`
if string(x.Base64EncryptedMsg) != wantEncrypt {
t.Errorf("Encrypt mismatch,\nhave: %s\nwant: %s\n", x.Base64EncryptedMsg, wantEncrypt)
return

for _, d := range testTable {
x := cipherRequestHttpBody{}
if err := xmlUnmarshal(d.Data, &x); err != nil {
t.Error(err)
return
}
if x.ToUserName != d.WantToUserName {
t.Errorf("ToUserName mismatch,\nhave: %s\nwant: %s\n", x.ToUserName, d.WantToUserName)
return
}

if x.AppID != d.WantAppID {
t.Errorf("ToUserName mismatch,\nhave: %s\nwant: %s\n", x.AppID, d.WantAppID)
return
}

if string(x.Base64EncryptedMsg) != d.WantEncrypt {
t.Errorf("Encrypt mismatch,\nhave: %s\nwant: %s\n", x.Base64EncryptedMsg, d.WantEncrypt)
return
}
}

}

func BenchmarkXmlUnmarshal(b *testing.B) {
Expand Down
7 changes: 6 additions & 1 deletion mp/oauth2/userinfo.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package oauth2

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"

Expand Down Expand Up @@ -71,11 +73,14 @@ func GetUserInfo(accessToken, openId, lang string, httpClient *http.Client) (inf
return
}

bytes, _ := ioutil.ReadAll(httpResp.Body)
fmt.Println(string(bytes))

var result struct {
oauth2.Error
UserInfo
}
if err = api.DecodeJSONHttpResponse(httpResp.Body, &result); err != nil {
if err = json.Unmarshal(bytes, &result); err != nil {
return
}
if result.ErrCode != oauth2.ErrCodeOK {
Expand Down
37 changes: 37 additions & 0 deletions mp/user/user.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package user

import (
"encoding/json"
"net/http"
"net/url"

"gopkg.in/chanxuehong/wechat.v2/mp/core"
Expand Down Expand Up @@ -69,6 +71,41 @@ func Get(clt *core.Client, openId string, lang string) (info *UserInfo, err erro
return
}

func CompGetInfo(openId, accessToken, lang string) (info *UserInfo, err error) {
switch lang {
case "":
lang = LanguageZhCN
case LanguageZhCN, LanguageZhTW, LanguageEN:
default:
lang = LanguageZhCN
}

var url = "https://api.weixin.qq.com/cgi-bin/user/info?openid=" + url.QueryEscape(openId) +
"&lang=" + lang + "&access_token=" + url.QueryEscape(accessToken)
var result struct {
core.Error
UserInfo
}
resp, e := http.Get(url)
defer resp.Body.Close()
if e != nil {
err = e
return
}

err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return
}

if result.ErrCode != core.ErrCodeOK {
err = &result.Error
return
}
info = &result.UserInfo
return
}

type batchGetRequestItem struct {
OpenId string `json:"openid"`
Language string `json:"lang,omitempty"`
Expand Down
61 changes: 61 additions & 0 deletions open/accesstoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package open

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"

"gopkg.in/chanxuehong/wechat.v2/util"
)

//GetComponentAccessToken return component access token
func GetComponentAccessToken(componentAppID, componentAppSecret, componentVerifyTicket string, token IDurationToken) (string, error) {
if !token.Expired() {
return token.Value()
}

httpClient := util.DefaultHttpClient
postData := struct {
ComponentAppID string `json:"component_appid"`
ComponentAppSecret string `json:"component_appsecret"`
ComponentVerifyTicket string `json:"component_verify_ticket"`
}{
componentAppID,
componentAppSecret,
componentVerifyTicket,
}

postDataJSON, _ := json.Marshal(postData)

resp, err := httpClient.Post("https://api.weixin.qq.com/cgi-bin/component/api_component_token", "application/json", strings.NewReader(string(postDataJSON)))
if err != nil {
return "", err
}

bytes, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(bytes))

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("http.Status: %s", resp.Status)
return "", err
}

accToken := struct {
ComponentAccessToken string `json:"component_access_token"`
ExpiresIn int `json:"expires_in"`
}{}

if e := json.Unmarshal(bytes, &accToken); e != nil {
return "", e
}
if e := token.Put(accToken.ComponentAccessToken, time.Second*60); e != nil {
return "", e
}

return accToken.ComponentAccessToken, nil
}
22 changes: 22 additions & 0 deletions open/mp/accesstoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mp

import "gopkg.in/chanxuehong/wechat.v2/mp/core"

type defaultOpenAccessTokenServer struct {
token string
}

func (as defaultOpenAccessTokenServer) Token() (token string, err error) {
return as.token, nil
}

func (as defaultOpenAccessTokenServer) RefreshToken(currentToken string) (token string, err error) {
return as.token, nil
}

func (as defaultOpenAccessTokenServer) IID01332E16DF5011E5A9D5A4DB30FED8E1() {}

//GetDefaultAccessTokenServer return the default access token of mp
func GetDefaultAccessTokenServer(mpAccessToken string) core.AccessTokenServer {
return defaultOpenAccessTokenServer{mpAccessToken}
}
12 changes: 12 additions & 0 deletions open/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package open

import (
"time"
)

//IDurationToken 用于一般性存储含有过期时间的token
type IDurationToken interface {
Value() (string, error)
Put(val string, expire time.Duration) error
Expired() bool
}