mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-14 01:32:27 +08:00
Compare commits
10 Commits
ee5f045b89
...
v2.1.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30c8e77246 | ||
|
|
6f6e95cfdb | ||
|
|
c806a0c172 | ||
|
|
c136b878ce | ||
|
|
d4a81916d5 | ||
|
|
ef1372b98a | ||
|
|
0d666b60ba | ||
|
|
e1122d42b0 | ||
|
|
be3f0d8bd5 | ||
|
|
66f9794d2f |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,8 +1,8 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: # silenceper
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: gowechat
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -1,12 +1,13 @@
|
|||||||
# WeChat SDK for Go
|
# WeChat SDK for Go
|
||||||
|
|
||||||

|

|
||||||
[](https://goreportcard.com/report/github.com/silenceper/wechat)
|
[](https://goreportcard.com/report/github.com/silenceper/wechat/v2)
|
||||||
[](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
|
[](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
使用Golang开发的微信SDK,简单、易用。
|
使用Golang开发的微信SDK,简单、易用。
|
||||||
> 注意:当前版本为v2版本,v1版本已废弃
|
|
||||||
|
|
||||||
## 文档 && 例子
|
## 文档 && 例子
|
||||||
|
|
||||||
@@ -75,7 +76,13 @@ server.Send()
|
|||||||
- 提交issue,描述需要贡献的内容
|
- 提交issue,描述需要贡献的内容
|
||||||
- 完成更改后,提交PR
|
- 完成更改后,提交PR
|
||||||
|
|
||||||
## 公众号
|
|
||||||
|
## 感谢以下贡献者
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/gowechat"><img src="https://opencollective.com/gowechat/contributors.svg?width=890" /></a>
|
||||||
|
|
||||||
|
|
||||||
|
## 作者公众号
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ type SubscribeMsgSentEvent struct {
|
|||||||
type SubscribeMsgSentList struct {
|
type SubscribeMsgSentList struct {
|
||||||
TemplateID string `xml:"TemplateId" json:"TemplateId"`
|
TemplateID string `xml:"TemplateId" json:"TemplateId"`
|
||||||
MsgID string `xml:"MsgID" json:"MsgID"`
|
MsgID string `xml:"MsgID" json:"MsgID"`
|
||||||
ErrorCode int `xml:"ErrorCode" json:"ErrorCode"`
|
ErrorCode string `xml:"ErrorCode" json:"ErrorCode"`
|
||||||
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
|
ErrorStatus string `xml:"ErrorStatus" json:"ErrorStatus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,3 +68,18 @@ func DecodeWithError(response []byte, obj interface{}, apiName string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleFileResponse 通用处理微信等接口返回:有时 JSON 错误,有时文件内容
|
||||||
|
func HandleFileResponse(response []byte, apiName string) ([]byte, error) {
|
||||||
|
var commErr CommonError
|
||||||
|
if err := json.Unmarshal(response, &commErr); err == nil {
|
||||||
|
// 能解析成 JSON,判断是否为错误
|
||||||
|
if commErr.ErrCode != 0 {
|
||||||
|
commErr.apiName = apiName
|
||||||
|
return nil, &commErr
|
||||||
|
}
|
||||||
|
// 能解析成 JSON 且没错误码,极少情况(比如微信返回的业务数据是 JSON 但无 errcode 字段),可根据需要调整
|
||||||
|
}
|
||||||
|
// 不能解析成 JSON,或没错误码,直接返回原始内容
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|||||||
16
util/http.go
16
util/http.go
@@ -292,13 +292,19 @@ func httpWithTLS(rootCa, key string) (*http.Client, error) {
|
|||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseTransport http.RoundTripper
|
// 安全地获取 *http.Transport
|
||||||
|
var trans *http.Transport
|
||||||
|
// 尝试从 DefaultHTTPClient 获取 Transport,如果失败则使用默认值
|
||||||
if DefaultHTTPClient.Transport != nil {
|
if DefaultHTTPClient.Transport != nil {
|
||||||
baseTransport = DefaultHTTPClient.Transport
|
if t, ok := DefaultHTTPClient.Transport.(*http.Transport); ok {
|
||||||
} else {
|
trans = t.Clone()
|
||||||
baseTransport = http.DefaultTransport
|
|
||||||
}
|
}
|
||||||
trans := baseTransport.(*http.Transport).Clone()
|
}
|
||||||
|
// 如果无法获取有效的 Transport,使用默认值
|
||||||
|
if trans == nil {
|
||||||
|
trans = http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
}
|
||||||
|
|
||||||
trans.TLSClientConfig = config
|
trans.TLSClientConfig = config
|
||||||
trans.DisableCompression = true
|
trans.DisableCompression = true
|
||||||
client = &http.Client{Transport: trans}
|
client = &http.Client{Transport: trans}
|
||||||
|
|||||||
81
util/http_test.go
Normal file
81
util/http_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHttpWithTLS_NilTransport tests the scenario where DefaultHTTPClient.Transport is nil
|
||||||
|
func TestHttpWithTLS_NilTransport(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set Transport to nil to simulate the bug scenario
|
||||||
|
DefaultHTTPClient.Transport = nil
|
||||||
|
|
||||||
|
// This should not panic after the fix
|
||||||
|
// Note: This will fail due to invalid cert path, but shouldn't panic on type assertion
|
||||||
|
_, err := httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
|
||||||
|
// We expect an error (cert file not found), but NOT a panic
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error due to invalid cert path, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHttpWithTLS_CustomTransport tests the scenario where DefaultHTTPClient has a custom Transport
|
||||||
|
func TestHttpWithTLS_CustomTransport(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set a custom http.Transport
|
||||||
|
customTransport := &http.Transport{
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
}
|
||||||
|
DefaultHTTPClient.Transport = customTransport
|
||||||
|
|
||||||
|
// This should not panic
|
||||||
|
_, err := httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
|
||||||
|
// We expect an error (cert file not found), but NOT a panic
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error due to invalid cert path, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRoundTripper is a custom implementation of http.RoundTripper
|
||||||
|
type CustomRoundTripper struct{}
|
||||||
|
|
||||||
|
func (c *CustomRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return http.DefaultTransport.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHttpWithTLS_CustomRoundTripper tests the edge case where DefaultHTTPClient has a custom RoundTripper
|
||||||
|
// that is NOT *http.Transport
|
||||||
|
func TestHttpWithTLS_CustomRoundTripper(t *testing.T) {
|
||||||
|
// Save original transport
|
||||||
|
originalTransport := DefaultHTTPClient.Transport
|
||||||
|
defer func() {
|
||||||
|
DefaultHTTPClient.Transport = originalTransport
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set a custom RoundTripper that is NOT *http.Transport
|
||||||
|
customRoundTripper := &CustomRoundTripper{}
|
||||||
|
DefaultHTTPClient.Transport = customRoundTripper
|
||||||
|
|
||||||
|
// Create a recovery handler to catch potential panic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("httpWithTLS panicked with custom RoundTripper: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// This might panic if the code doesn't handle non-*http.Transport RoundTripper properly
|
||||||
|
_, _ = httpWithTLS("./testdata/invalid_cert.p12", "password")
|
||||||
|
}
|
||||||
@@ -176,6 +176,7 @@ type BatchGetExternalUserDetailsRequest struct {
|
|||||||
type ExternalUserDetailListResponse struct {
|
type ExternalUserDetailListResponse struct {
|
||||||
util.CommonError
|
util.CommonError
|
||||||
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
|
ExternalContactList []ExternalUserForBatch `json:"external_contact_list"`
|
||||||
|
NextCursor string `json:"next_cursor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalUserForBatch 批量获取外部联系人客户列表
|
// ExternalUserForBatch 批量获取外部联系人客户列表
|
||||||
@@ -214,23 +215,23 @@ type FollowInfo struct {
|
|||||||
|
|
||||||
// BatchGetExternalUserDetails 批量获取外部联系人详情
|
// BatchGetExternalUserDetails 批量获取外部联系人详情
|
||||||
// @see https://developer.work.weixin.qq.com/document/path/92994
|
// @see https://developer.work.weixin.qq.com/document/path/92994
|
||||||
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, error) {
|
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUserForBatch, string, error) {
|
||||||
accessToken, err := r.GetAccessToken()
|
accessToken, err := r.GetAccessToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
var response []byte
|
var response []byte
|
||||||
jsonData, err := json.Marshal(request)
|
jsonData, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
|
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
var result ExternalUserDetailListResponse
|
var result ExternalUserDetailListResponse
|
||||||
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
|
err = util.DecodeWithError(response, &result, "BatchGetExternalUserDetails")
|
||||||
return result.ExternalContactList, err
|
return result.ExternalContactList, result.NextCursor, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserRemarkRequest 修改客户备注信息请求体
|
// UpdateUserRemarkRequest 修改客户备注信息请求体
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
|
|
||||||
// SignatureOptions 微信服务器验证参数
|
// SignatureOptions 微信服务器验证参数
|
||||||
type SignatureOptions struct {
|
type SignatureOptions struct {
|
||||||
Signature string `form:"msg_signature"`
|
Signature string `form:"msg_signature" json:"msg_signature"`
|
||||||
TimeStamp string `form:"timestamp"`
|
TimeStamp string `form:"timestamp" json:"timestamp"`
|
||||||
Nonce string `form:"nonce"`
|
Nonce string `form:"nonce" json:"nonce"`
|
||||||
EchoStr string `form:"echostr"`
|
EchoStr string `form:"echostr" json:"echostr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容
|
// VerifyURL 验证请求参数是否合法并返回解密后的消息内容
|
||||||
|
|||||||
@@ -191,12 +191,6 @@ func (r *Client) GetTempFile(mediaID string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查响应是否为错误信息
|
// 检查响应是否为错误信息,如果不是错误响应,则返回原始数据
|
||||||
err = util.DecodeWithCommonError(response, "GetTempFile")
|
return util.HandleFileResponse(response, "GetTempFile")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果不是错误响应,则返回原始数据
|
|
||||||
return response, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user