张延森 пре 3 година
родитељ
комит
ff07972d1e

+ 0
- 21
api/authorization/bind_component.go Прегледај датотеку

@@ -20,32 +20,11 @@ import (
20 20
 	"gitee.com/yansen_zh/wxcomponent/config"
21 21
 )
22 22
 
23
-// AuthLinkParam 构建授权链接入参
24
-type AuthLinkParam struct {
25
-	ComponentAppId string
26
-	PreAuthCode    string
27
-
28
-	// RedirectUri 不需要转码
29
-	RedirectUri string
30
-	AuthType    int
31
-	BizAppId    string
32
-}
33
-
34 23
 const (
35 24
 	componentloginpage = "https://mp.weixin.qq.com/cgi-bin/componentloginpage"
36 25
 	bindcomponent      = "https://mp.weixin.qq.com/safe/bindcomponent"
37 26
 )
38 27
 
39
-// GetPCAuthLink 构建PC端授权链接
40
-func GetPCAuthLink(preAuthCode, redirectURI, bizAppID string, authType int) (string, error) {
41
-	return GetAuthLink("PC", preAuthCode, redirectURI, bizAppID, authType)
42
-}
43
-
44
-// GetH5AuthLink 构建H5端授权链接
45
-func GetH5AuthLink(preAuthCode, redirectURI, bizAppID string, authType int) (string, error) {
46
-	return GetAuthLink("H5", preAuthCode, redirectURI, bizAppID, authType)
47
-}
48
-
49 28
 // GetAuthLink 构建授权链接
50 29
 // redirectURI 回调地址必须是未经过处理的原始URL字符串
51 30
 func GetAuthLink(client, preAuthCode, redirectURI, bizAppID string, authType int) (string, error) {

+ 2
- 2
api/authorization/types.go Прегледај датотеку

@@ -70,7 +70,7 @@ type AuthorizerInfo struct {
70 70
 
71 71
 // AuthorizationInfo 授权信息
72 72
 type AuthorizationInfo struct {
73
-	AuthorizerAppId        string     `json:"authorizer_appid"`
73
+	AuthorizerAppID        string     `json:"authorizer_appid"`
74 74
 	AuthorizerAccessToken  string     `json:"authorizer_access_token"`
75 75
 	ExpiresIn              int        `json:"expires_in"`
76 76
 	AuthorizerRefreshToken string     `json:"authorizer_refresh_token"`
@@ -79,7 +79,7 @@ type AuthorizationInfo struct {
79 79
 
80 80
 // AuthorizationChangeNotice 授权变更通知
81 81
 type AuthorizationChangeNotice struct {
82
-	AppId                        string `json:"AppId"`
82
+	AppID                        string `json:"AppId"`
83 83
 	CreateTime                   int64  `json:"CreateTime"`
84 84
 	InfoType                     string `json:"InfoType"`
85 85
 	AuthorizerAppid              string `json:"AuthorizerAppid"`

+ 0
- 37
api/authorization/verify_ticket.go Прегледај датотеку

@@ -1,37 +0,0 @@
1
-/**
2
- * Copyright (c) 2022 Yansen Zhang
3
- * wxcomponent is licensed under Mulan PSL v2.
4
- * You can use this software according to the terms and conditions of the Mulan PSL v2.
5
- * You may obtain a copy of Mulan PSL v2 at:
6
- *          http://license.coscl.org.cn/MulanPSL2
7
- * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
8
- * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
9
- * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10
- * See the Mulan PSL v2 for more details.
11
-**/
12
-
13
-package authorization
14
-
15
-import (
16
-	"encoding/xml"
17
-)
18
-
19
-// VerifyTicketResult 获取验证票据结果
20
-type VerifyTicketResult struct {
21
-	AppId                 string `xml:"AppId"`
22
-	CreateTime            int64  `xml:"CreateTime"`
23
-	InfoType              string `xml:"InfoType"`
24
-	ComponentVerifyTicket string `xml:"ComponentVerifyTicket"`
25
-}
26
-
27
-// ParseVerifyTicket 获取验证票据
28
-// data 是解密之后的数据
29
-func ParseVerifyTicket(data []byte) (*VerifyTicketResult, error) {
30
-	res := VerifyTicketResult{}
31
-
32
-	if e := xml.Unmarshal(data, &res); e != nil {
33
-		return nil, e
34
-	}
35
-
36
-	return &res, nil
37
-}

+ 1
- 1
api/authorizer/get_authorizer_option.go Прегледај датотеку

@@ -25,7 +25,7 @@ import (
25 25
 // GetAuthorizerOptionResult 获取授权方选项信息结果
26 26
 type GetAuthorizerOptionResult struct {
27 27
 	wxerr.Error
28
-	AuthorizerAppId string `json:"authorizer_appid"`
28
+	AuthorizerAppID string `json:"authorizer_appid"`
29 29
 	OptionName      string `json:"option_name"`
30 30
 	OptionValue     string `json:"option_value"`
31 31
 }

+ 59
- 1
authorization.go Прегледај датотеку

@@ -20,7 +20,13 @@ import (
20 20
 
21 21
 // 授权与Token
22 22
 
23
-// RefreshToken 刷新Token
23
+// VerifyTicket 验证票据
24
+func VerifyTicket(ticket string) error {
25
+	expire := utils.GetExpireTime(12 * 3600) // component_verify_ticket 的有效时间为12小时
26
+	return config.RefreshVerifyTicket(ticket, expire)
27
+}
28
+
29
+// RefreshToken 获取令牌
24 30
 // 主要是定时任务调用
25 31
 func RefreshToken() error {
26 32
 	result, err := authorization.GetComponentToken()
@@ -37,3 +43,55 @@ func RefreshToken() error {
37 43
 
38 44
 	return nil
39 45
 }
46
+
47
+// CreateAuthLink 自建授权链接, client 默认是 PC 端, 如果是移动端, 需要传入 H5。
48
+// url 为需要跳转的结果页, 必须是原始的值, 不能经过 encode 处理.
49
+// appID 为小程序或者公众号ID, 可以不传
50
+func CreateAuthLink(client, url, appID string) (string, error) {
51
+	preAuth, err := authorization.CreatePreAuthCode()
52
+	if err != nil {
53
+		return "", err
54
+	}
55
+
56
+	return authorization.GetAuthLink(client, preAuth.PreAuthCode, url, appID, 3)
57
+}
58
+
59
+// RefreshAuthorizerInfo 刷新授权对象信息
60
+func RefreshAuthorizerInfo(authCode string, authorizer config.AuthorizerConfig) error {
61
+	res, err := authorization.GetQueryAuth(authCode)
62
+	if err != nil {
63
+		return err
64
+	}
65
+
66
+	authInfo := res.AuthorizationInfo
67
+	appID := authInfo.AuthorizerAppID
68
+	expire := utils.GetExpireTime(authInfo.ExpiresIn)
69
+
70
+	authorizer.RefreshFuncInfo(appID, authInfo.FuncInfo)
71
+	if err := authorizer.RefreshToken(appID, authInfo.AuthorizerAccessToken, authInfo.AuthorizerRefreshToken, expire); err != nil {
72
+		return err
73
+	}
74
+
75
+	return nil
76
+}
77
+
78
+// RefreshAuthorizerToken 刷新接口调用令牌
79
+func RefreshAuthorizerToken(appID string, authorizer config.AuthorizerConfig) error {
80
+	res, err := authorization.RefreshAuthorizerToken(appID, authorizer.GetRefreshToken(appID))
81
+	if err != nil {
82
+		return err
83
+	}
84
+
85
+	expire := utils.GetExpireTime(res.ExpiresIn)
86
+
87
+	if err := authorizer.RefreshToken(appID, res.AuthorizerAccessToken, res.AuthorizerRefreshToken, expire); err != nil {
88
+		return err
89
+	}
90
+
91
+	return nil
92
+}
93
+
94
+// GetAuthorizerInfo 获取授权帐号信息
95
+func GetAuthorizerInfo(appID string) (*authorization.AuthorizerInfoResult, error) {
96
+	return authorization.GetAuthorizerInfo(appID)
97
+}

+ 26
- 1
config/config.go Прегледај датотеку

@@ -12,7 +12,11 @@
12 12
 
13 13
 package config
14 14
 
15
-import "time"
15
+import (
16
+	"time"
17
+
18
+	"gitee.com/yansen_zh/wxcomponent/api/authorization"
19
+)
16 20
 
17 21
 type Config interface {
18 22
 	// GetAppID 获取平台 APPID
@@ -35,6 +39,27 @@ type Config interface {
35 39
 
36 40
 	// RefreshTicket 刷新票据
37 41
 	RefreshVerifyTicket(ticket string, expire time.Time) error
42
+
43
+	// GetPushTicketState 是否开启票据推送服务
44
+	GetPushTicketState() bool
45
+
46
+	// RefreshPushTicketState 刷新票据推送服务状态
47
+	RefreshPushTicketState(state bool)
38 48
 }
39 49
 
40 50
 var conf Config
51
+
52
+// AuthorizerConfig 公众号或小程序配置
53
+type AuthorizerConfig interface {
54
+	// GetToken 获取 Token
55
+	GetAccessToken(appID string) string
56
+
57
+	// GetRefreshToken 获取 RefreshToken
58
+	GetRefreshToken(appID string) string
59
+
60
+	// RefreshToken 刷新 Token
61
+	RefreshToken(appID, token, refreshToken string, expire time.Time) error
62
+
63
+	// RefreshFuncInfo 刷新权限集列表
64
+	RefreshFuncInfo(appID string, lst []authorization.FuncInfo)
65
+}

+ 10
- 0
config/index.go Прегледај датотеку

@@ -74,3 +74,13 @@ func GetVerifyTicket() string {
74 74
 func RefreshVerifyTicket(ticket string, expire time.Time) error {
75 75
 	return conf.RefreshVerifyTicket(ticket, expire)
76 76
 }
77
+
78
+// GetPushTicketState 是否开启票据推送服务
79
+func GetPushTicketState() bool {
80
+	return conf.GetPushTicketState()
81
+}
82
+
83
+// RefreshPushTicketState 刷新票据推送服务状态
84
+func RefreshPushTicketState(state bool) {
85
+	conf.RefreshPushTicketState(state)
86
+}

+ 62
- 0
decode.go Прегледај датотеку

@@ -0,0 +1,62 @@
1
+/**
2
+ * Copyright (c) 2022 Yansen Zhang
3
+ * wxcomponent is licensed under Mulan PSL v2.
4
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
5
+ * You may obtain a copy of Mulan PSL v2 at:
6
+ *          http://license.coscl.org.cn/MulanPSL2
7
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
8
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
9
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10
+ * See the Mulan PSL v2 for more details.
11
+**/
12
+
13
+package wxcomponent
14
+
15
+import (
16
+	"encoding/base64"
17
+	"encoding/xml"
18
+
19
+	"gitee.com/yansen_zh/wxcomponent/utils/encrypt"
20
+	"gitee.com/yansen_zh/wxcomponent/utils/log"
21
+)
22
+
23
+// EncryptMessage 待解密数据
24
+type EncryptMessage struct {
25
+	XMLName    xml.Name `xml:"xml"`
26
+	ToUserName string   `xml:"ToUserName"`
27
+	Encrypt    string   `xml:"Encrypt"`
28
+}
29
+
30
+// DecodeMessage 解密消息
31
+func DecodeMessage(src []byte) (*encrypt.XMLMap, error) {
32
+	log.Info("解码 xml 数据: ", string(src))
33
+
34
+	msg := EncryptMessage{}
35
+	if err := xml.Unmarshal(src, &msg); err != nil {
36
+		log.Error("解码 xml 数据失败: ", err.Error())
37
+		return nil, err
38
+	}
39
+
40
+	bt1, e1 := base64.StdEncoding.DecodeString(msg.Encrypt)
41
+	if e1 != nil {
42
+		log.Error("解码 base 数据失败: ", e1.Error())
43
+		log.Error("原始 base64 数据: ", msg.Encrypt)
44
+		return nil, e1
45
+	}
46
+
47
+	bt2, e2 := encrypt.MsgDecode(bt1, aesKey)
48
+	if e2 != nil {
49
+		log.Error("解码加密数据失败: ", e2.Error())
50
+		log.Error("待解密数据: ", string(bt1))
51
+		return nil, e2
52
+	}
53
+
54
+	res := encrypt.XMLMap{}
55
+	if err := xml.Unmarshal(bt2, &res); err != nil {
56
+		log.Error("解码 xml 数据失败: ", err.Error())
57
+		log.Info("待解码 xml 数据: ", string(bt2))
58
+		return nil, err
59
+	}
60
+
61
+	return &res, nil
62
+}

+ 25
- 4
init.go Прегледај датотеку

@@ -13,6 +13,7 @@
13 13
 package wxcomponent
14 14
 
15 15
 import (
16
+	"encoding/base64"
16 17
 	"errors"
17 18
 
18 19
 	"gitee.com/yansen_zh/wxcomponent/api/authorization"
@@ -20,6 +21,8 @@ import (
20 21
 	"gitee.com/yansen_zh/wxcomponent/utils/log"
21 22
 )
22 23
 
24
+var aesKey []byte
25
+
23 26
 // InitLogger 初始化 Logger
24 27
 func InitLogger(logger log.Logger) {
25 28
 	log.SetLogger(logger)
@@ -27,7 +30,16 @@ func InitLogger(logger log.Logger) {
27 30
 
28 31
 // InitConfig 初始化配置
29 32
 func InitConfig(conf config.Config) error {
30
-	return config.Init(conf)
33
+	err := config.Init(conf)
34
+	if err != nil {
35
+		var e error
36
+		aesKey, e = base64.StdEncoding.DecodeString(conf.GetAppSecret() + "=")
37
+		if e != nil {
38
+			return e
39
+		}
40
+	}
41
+
42
+	return err
31 43
 }
32 44
 
33 45
 // Start 启动服务
@@ -40,7 +52,16 @@ func Start() error {
40 52
 		errors.New("请先进行日志初始化")
41 53
 	}
42 54
 
43
-	// 启动ticket推送服务, 之后微信服务器会每隔 10 分钟推送 component_verify_ticket
44
-	err := authorization.StartPushTicket()
45
-	return err
55
+	if !config.GetPushTicketState() {
56
+		// 启动ticket推送服务, 之后微信服务器会每隔 10 分钟推送 component_verify_ticket
57
+		err := authorization.StartPushTicket()
58
+
59
+		if err != nil {
60
+			config.RefreshPushTicketState(true)
61
+		}
62
+
63
+		return err
64
+	}
65
+
66
+	return nil
46 67
 }

+ 0
- 5
utils/encrypt/msg_encrypt.go Прегледај датотеку

@@ -79,10 +79,6 @@ func MsgDecode(data, key []byte) ([]byte, error) {
79 79
 // pkcs7 填充
80 80
 func pkcs7(data []byte, blockSize int) []byte {
81 81
 	paddingBlock := blockSize - len(data)%blockSize
82
-	if paddingBlock == 0 {
83
-		paddingBlock = blockSize
84
-	}
85
-
86 82
 	padding := bytes.Repeat([]byte{byte(paddingBlock)}, paddingBlock)
87 83
 	return append(data, padding...)
88 84
 }
@@ -90,7 +86,6 @@ func pkcs7(data []byte, blockSize int) []byte {
90 86
 // unpkcs7 取消 pkcs7 填充
91 87
 func unpkcs7(data []byte) []byte {
92 88
 	length := len(data)
93
-
94 89
 	unpadding := int(data[length-1])
95 90
 	return data[:(length - unpadding)]
96 91
 }

+ 107
- 0
utils/encrypt/xml.go Прегледај датотеку

@@ -0,0 +1,107 @@
1
+/**
2
+ * Copyright (c) 2022 Yansen Zhang
3
+ * wxcomponent is licensed under Mulan PSL v2.
4
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
5
+ * You may obtain a copy of Mulan PSL v2 at:
6
+ *          http://license.coscl.org.cn/MulanPSL2
7
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
8
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
9
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10
+ * See the Mulan PSL v2 for more details.
11
+**/
12
+
13
+package encrypt
14
+
15
+import (
16
+	"encoding/xml"
17
+	"io"
18
+	"regexp"
19
+)
20
+
21
+// 简易版本的 xml 解析, 实现将 单层 xml 解析到 map[string]string
22
+
23
+// XMLMap  xml 转换 map
24
+type XMLMap map[string]string
25
+
26
+// XMLElement xml 转换 element
27
+type XMLElement struct {
28
+	XMLName xml.Name
29
+	Value   string `xml:",chardata"`
30
+}
31
+
32
+// XMLCDATA xml 转换 cdata
33
+type XMLCDATA struct {
34
+	XMLName xml.Name
35
+	Value   string `xml:",cdata"`
36
+}
37
+
38
+// UnmarshalXML xml.Unmarshaler 接口实现
39
+// 参考 https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go
40
+func (m *XMLMap) UnmarshalXML(d *xml.Decoder, _ xml.StartElement) error {
41
+	for {
42
+		ele := XMLElement{}
43
+		err := d.Decode(&ele)
44
+		if err == io.EOF {
45
+			break
46
+		} else if err != nil {
47
+			return err
48
+		}
49
+
50
+		// 忽略 Space
51
+		(*m)[ele.XMLName.Local] = ele.Value
52
+	}
53
+
54
+	return nil
55
+}
56
+
57
+// MarshalXML xml.Marshaler 接口实现
58
+// 参考 https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go
59
+func (m *XMLMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
60
+	if m == nil || len(*m) == 0 {
61
+		return nil
62
+	}
63
+
64
+	if start.Name.Local == "XMLMap" {
65
+		start.Name.Local = "xml"
66
+	}
67
+
68
+	if err := e.EncodeToken(start); err != nil {
69
+		return err
70
+	}
71
+
72
+	regx, err := regexp.Compile(`<!\[CDATA\[(.*)\]\]>`)
73
+	if err != nil {
74
+		return err
75
+	}
76
+
77
+	for k, v := range *m {
78
+		res := regx.FindAllStringSubmatch(v, -1)
79
+		if len(res) > 0 {
80
+			t := XMLCDATA{
81
+				XMLName: xml.Name{
82
+					Space: "",
83
+					Local: k,
84
+				},
85
+				Value: res[0][1],
86
+			}
87
+
88
+			if err := e.Encode(&t); err != nil {
89
+				return err
90
+			}
91
+		} else {
92
+			t := XMLElement{
93
+				XMLName: xml.Name{
94
+					Space: "",
95
+					Local: k,
96
+				},
97
+				Value: v,
98
+			}
99
+
100
+			if err := e.Encode(&t); err != nil {
101
+				return err
102
+			}
103
+		}
104
+	}
105
+
106
+	return e.EncodeToken(start.End())
107
+}

+ 12
- 0
utils/utils.go Прегледај датотеку

@@ -13,9 +13,12 @@
13 13
 package utils
14 14
 
15 15
 import (
16
+	"encoding/xml"
16 17
 	"math/rand"
17 18
 	"strings"
18 19
 	"time"
20
+
21
+	"gitee.com/yansen_zh/wxcomponent/utils/encrypt"
19 22
 )
20 23
 
21 24
 // RandStr 获取指定 n 长度的随机字符串
@@ -39,3 +42,12 @@ func RandStr(n int) string {
39 42
 func GetExpireTime(sec int) time.Time {
40 43
 	return time.Now().Add(time.Duration(sec) * time.Second)
41 44
 }
45
+
46
+// DeCodeXML 解析XML到MAP
47
+func DeCodeXML(src []byte) (*encrypt.XMLMap, error) {
48
+	dst := new(encrypt.XMLMap)
49
+
50
+	err := xml.Unmarshal(src, dst)
51
+
52
+	return dst, err
53
+}