张延森 3 年之前
父節點
當前提交
ff07972d1e

+ 0
- 21
api/authorization/bind_component.go 查看文件

20
 	"gitee.com/yansen_zh/wxcomponent/config"
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
 const (
23
 const (
35
 	componentloginpage = "https://mp.weixin.qq.com/cgi-bin/componentloginpage"
24
 	componentloginpage = "https://mp.weixin.qq.com/cgi-bin/componentloginpage"
36
 	bindcomponent      = "https://mp.weixin.qq.com/safe/bindcomponent"
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
 // GetAuthLink 构建授权链接
28
 // GetAuthLink 构建授权链接
50
 // redirectURI 回调地址必须是未经过处理的原始URL字符串
29
 // redirectURI 回调地址必须是未经过处理的原始URL字符串
51
 func GetAuthLink(client, preAuthCode, redirectURI, bizAppID string, authType int) (string, error) {
30
 func GetAuthLink(client, preAuthCode, redirectURI, bizAppID string, authType int) (string, error) {

+ 2
- 2
api/authorization/types.go 查看文件

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

+ 0
- 37
api/authorization/verify_ticket.go 查看文件

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

+ 59
- 1
authorization.go 查看文件

20
 
20
 
21
 // 授权与Token
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
 func RefreshToken() error {
31
 func RefreshToken() error {
26
 	result, err := authorization.GetComponentToken()
32
 	result, err := authorization.GetComponentToken()
37
 
43
 
38
 	return nil
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
 
12
 
13
 package config
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
 type Config interface {
21
 type Config interface {
18
 	// GetAppID 获取平台 APPID
22
 	// GetAppID 获取平台 APPID
35
 
39
 
36
 	// RefreshTicket 刷新票据
40
 	// RefreshTicket 刷新票据
37
 	RefreshVerifyTicket(ticket string, expire time.Time) error
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
 var conf Config
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
 func RefreshVerifyTicket(ticket string, expire time.Time) error {
74
 func RefreshVerifyTicket(ticket string, expire time.Time) error {
75
 	return conf.RefreshVerifyTicket(ticket, expire)
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 查看文件

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
 package wxcomponent
13
 package wxcomponent
14
 
14
 
15
 import (
15
 import (
16
+	"encoding/base64"
16
 	"errors"
17
 	"errors"
17
 
18
 
18
 	"gitee.com/yansen_zh/wxcomponent/api/authorization"
19
 	"gitee.com/yansen_zh/wxcomponent/api/authorization"
20
 	"gitee.com/yansen_zh/wxcomponent/utils/log"
21
 	"gitee.com/yansen_zh/wxcomponent/utils/log"
21
 )
22
 )
22
 
23
 
24
+var aesKey []byte
25
+
23
 // InitLogger 初始化 Logger
26
 // InitLogger 初始化 Logger
24
 func InitLogger(logger log.Logger) {
27
 func InitLogger(logger log.Logger) {
25
 	log.SetLogger(logger)
28
 	log.SetLogger(logger)
27
 
30
 
28
 // InitConfig 初始化配置
31
 // InitConfig 初始化配置
29
 func InitConfig(conf config.Config) error {
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
 // Start 启动服务
45
 // Start 启动服务
40
 		errors.New("请先进行日志初始化")
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
 // pkcs7 填充
79
 // pkcs7 填充
80
 func pkcs7(data []byte, blockSize int) []byte {
80
 func pkcs7(data []byte, blockSize int) []byte {
81
 	paddingBlock := blockSize - len(data)%blockSize
81
 	paddingBlock := blockSize - len(data)%blockSize
82
-	if paddingBlock == 0 {
83
-		paddingBlock = blockSize
84
-	}
85
-
86
 	padding := bytes.Repeat([]byte{byte(paddingBlock)}, paddingBlock)
82
 	padding := bytes.Repeat([]byte{byte(paddingBlock)}, paddingBlock)
87
 	return append(data, padding...)
83
 	return append(data, padding...)
88
 }
84
 }
90
 // unpkcs7 取消 pkcs7 填充
86
 // unpkcs7 取消 pkcs7 填充
91
 func unpkcs7(data []byte) []byte {
87
 func unpkcs7(data []byte) []byte {
92
 	length := len(data)
88
 	length := len(data)
93
-
94
 	unpadding := int(data[length-1])
89
 	unpadding := int(data[length-1])
95
 	return data[:(length - unpadding)]
90
 	return data[:(length - unpadding)]
96
 }
91
 }

+ 107
- 0
utils/encrypt/xml.go 查看文件

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
 package utils
13
 package utils
14
 
14
 
15
 import (
15
 import (
16
+	"encoding/xml"
16
 	"math/rand"
17
 	"math/rand"
17
 	"strings"
18
 	"strings"
18
 	"time"
19
 	"time"
20
+
21
+	"gitee.com/yansen_zh/wxcomponent/utils/encrypt"
19
 )
22
 )
20
 
23
 
21
 // RandStr 获取指定 n 长度的随机字符串
24
 // RandStr 获取指定 n 长度的随机字符串
39
 func GetExpireTime(sec int) time.Time {
42
 func GetExpireTime(sec int) time.Time {
40
 	return time.Now().Add(time.Duration(sec) * time.Second)
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
+}