1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-13 17:22:26 +08:00

Compare commits

...

10 Commits

Author SHA1 Message Date
is-Xiaoen
30c8e77246 fix: improve type safety in httpWithTLS for custom RoundTripper (#861)
* fix: improve type safety in httpWithTLS for custom RoundTripper

Add type assertion check to handle cases where DefaultHTTPClient.Transport
is a custom http.RoundTripper implementation (not *http.Transport).

This improves upon the fix in PR #844 which only handled nil Transport.
The previous code would still panic if users set a custom RoundTripper:

  trans := baseTransport.(*http.Transport).Clone()  // panic if not *http.Transport

Now safely handles three scenarios:
1. Transport is nil -> use http.DefaultTransport
2. Transport is *http.Transport -> clone it
3. Transport is custom RoundTripper -> use http.DefaultTransport

Added comprehensive test cases:
- TestHttpWithTLS_NilTransport
- TestHttpWithTLS_CustomTransport
- TestHttpWithTLS_CustomRoundTripper

Related to #803

* refactor: reduce code duplication and complexity in httpWithTLS

- Eliminate duplicate http.DefaultTransport.Clone() calls
- Reduce cyclomatic complexity by simplifying conditional logic
- Use nil check pattern instead of nested else branches
- Maintain same functionality with cleaner code structure

This addresses golangci-lint warnings for dupl and gocyclo.

* fix: add newline at end of http_test.go

Fix gofmt -s compliance issue:
- File must end with newline character
- Addresses golangci-lint gofmt error on line 81

This fixes CI check failure.
2025-10-27 14:24:24 +08:00
mahongran
6f6e95cfdb SubscribeMsgSentList结构中的ErrorCode类型从int更新为string (#859) 2025-10-24 20:49:24 +08:00
silenceper
c806a0c172 Add star badge to README 2025-10-24 17:37:01 +08:00
zhangjiani
c136b878ce 调整企微回调URL参数tag,兼容kratos框架 (#855)
* fix: handle JSON parse error when API returns binary file instead of error JSON

* fix: add JSON tags to SignatureOptions struct fields for proper serialization

* fix: mod module

* fix: rollback

---------

Co-authored-by: tax <jia_deng@intsig.net>
2025-09-19 11:16:52 +08:00
zhangjiani
d4a81916d5 fix: handle JSON parse error when API returns binary file instead of error JSON (#852)
Co-authored-by: tax <jia_deng@intsig.net>
2025-09-14 19:47:00 +08:00
Outyua
ef1372b98a fix BatchGetExternalUserDetails to return NextCursor in response (#849) 2025-08-18 15:51:30 +08:00
silenceper
0d666b60ba update readme (#848)
* update readme

* update readme

* update readme
2025-07-29 23:31:01 +08:00
silenceper
e1122d42b0 Update FUNDING.yml 2025-07-29 23:13:42 +08:00
silenceper
be3f0d8bd5 Update FUNDING.yml 2025-07-29 22:52:50 +08:00
silenceper
66f9794d2f Update FUNDING.yml (#847) 2025-07-29 22:41:02 +08:00
9 changed files with 134 additions and 30 deletions

4
.github/FUNDING.yml vendored
View File

@@ -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

View File

@@ -1,12 +1,13 @@
# WeChat SDK for Go # WeChat SDK for Go
![Go](https://github.com/silenceper/wechat/workflows/Go/badge.svg?branch=release-2.0) ![Go](https://github.com/silenceper/wechat/actions/workflows/go.yml/badge.svg?branch=v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat)](https://goreportcard.com/report/github.com/silenceper/wechat) [![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat/v2)](https://goreportcard.com/report/github.com/silenceper/wechat/v2)
[![pkg](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc) [![pkg](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
![version](https://img.shields.io/badge/version-v2-green) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/silenceper/wechat?sort=semver)
![star](https://gitcode.com/silenceper/wechat/star/badge.svg)
使用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>
## 作者公众号
![img](https://silenceper.oss-cn-beijing.aliyuncs.com/qrcode/search_study_program.png) ![img](https://silenceper.oss-cn-beijing.aliyuncs.com/qrcode/search_study_program.png)

View File

@@ -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"`
} }

View File

@@ -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
}

View File

@@ -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
View 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")
}

View File

@@ -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 修改客户备注信息请求体

View File

@@ -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 验证请求参数是否合法并返回解密后的消息内容

View File

@@ -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
} }