From 79f8d5d600ad5f421433f5eec8b340c42dbbde80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=90=E5=A2=A8?= Date: Wed, 11 Feb 2026 09:44:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(channel):=20=E4=B8=BA=20Telegram=20?= =?UTF-8?q?=E5=92=8C=E5=BE=AE=E4=BF=A1=E4=BC=81=E4=B8=9A=E5=8F=B7=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BB=A3=E7=90=86=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 HTTP 客户端工厂函数以支持通过代理发送请求,代理地址可在频道配置中设置。同时修复了微信企业号 TokenStore 更新时代理地址未同步的问题,并确保 Token 存储键值包含代理信息以避免冲突。 --- channel/http-client.go | 63 ++++++++++++++++++++++++++++ channel/telegram.go | 9 +++- channel/token-store.go | 7 +++- channel/wechat-corp-account.go | 30 +++++++++---- web/src/pages/Channel/EditChannel.js | 18 ++++++++ 5 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 channel/http-client.go diff --git a/channel/http-client.go b/channel/http-client.go new file mode 100644 index 0000000..2fb7ca8 --- /dev/null +++ b/channel/http-client.go @@ -0,0 +1,63 @@ +package channel + +import ( + "context" + "errors" + "net" + "net/http" + "net/url" + "strings" + "time" + + "golang.org/x/net/proxy" +) + +func newHTTPClient(proxyAddress string, timeout time.Duration) (*http.Client, error) { + transport, err := newHTTPTransport(proxyAddress) + if err != nil { + return nil, err + } + return &http.Client{ + Timeout: timeout, + Transport: transport, + }, nil +} + +func newHTTPTransport(proxyAddress string) (*http.Transport, error) { + base, ok := http.DefaultTransport.(*http.Transport) + if !ok { + return &http.Transport{}, nil + } + transport := base.Clone() + proxyAddress = strings.TrimSpace(proxyAddress) + if proxyAddress == "" { + transport.Proxy = nil + return transport, nil + } + if !strings.Contains(proxyAddress, "://") { + proxyAddress = "http://" + proxyAddress + } + u, err := url.Parse(proxyAddress) + if err != nil { + return nil, err + } + switch strings.ToLower(u.Scheme) { + case "http", "https": + transport.Proxy = http.ProxyURL(u) + return transport, nil + case "socks5", "socks5h": + normalized := *u + normalized.Scheme = "socks5" + dialer, err := proxy.FromURL(&normalized, proxy.Direct) + if err != nil { + return nil, err + } + transport.Proxy = nil + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.Dial(network, addr) + } + return transport, nil + default: + return nil, errors.New("unsupported proxy scheme: " + strings.TrimSpace(u.Scheme)) + } +} diff --git a/channel/telegram.go b/channel/telegram.go index 9e87355..64bcc6e 100644 --- a/channel/telegram.go +++ b/channel/telegram.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "message-pusher/model" - "net/http" + "time" "unicode/utf8" ) @@ -25,6 +25,10 @@ type telegramMessageResponse struct { func SendTelegramMessage(message *model.Message, user *model.User, channel_ *model.Channel) error { // https://core.telegram.org/bots/api#sendmessage + client, err := newHTTPClient(channel_.URL, 10*time.Second) + if err != nil { + return err + } messageRequest := telegramMessageRequest{ ChatId: channel_.AccountId, } @@ -53,13 +57,14 @@ func SendTelegramMessage(message *model.Message, user *model.User, channel_ *mod if err != nil { return err } - resp, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", channel_.Secret), "application/json", + resp, err := client.Post(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", channel_.Secret), "application/json", bytes.NewBuffer(jsonData)) if err != nil { return err } var res telegramMessageResponse err = json.NewDecoder(resp.Body).Decode(&res) + _ = resp.Body.Close() if err != nil { return err } diff --git a/channel/token-store.go b/channel/token-store.go index 0e65ccd..20ee69c 100644 --- a/channel/token-store.go +++ b/channel/token-store.go @@ -41,6 +41,7 @@ func channel2item(channel_ *model.Channel) TokenStoreItem { CorpId: corpId, AgentSecret: channel_.Secret, AgentId: agentId, + Proxy: channel_.URL, } return item case model.TypeLarkApp: @@ -227,12 +228,13 @@ func TokenStoreUpdateChannel(newChannel *model.Channel, oldChannel *model.Channe CorpId: corpId, AgentSecret: oldChannel.Secret, AgentId: agentId, + Proxy: oldChannel.URL, } // Yeah, it's a deep copy. newItem := oldItem // This means the user updated those fields. if newChannel.AppId != "" { - corpId, agentId, err := parseWechatCorpAccountAppId(oldChannel.AppId) + corpId, agentId, err := parseWechatCorpAccountAppId(newChannel.AppId) if err != nil { common.SysError(err.Error()) return @@ -243,6 +245,9 @@ func TokenStoreUpdateChannel(newChannel *model.Channel, oldChannel *model.Channe if newChannel.Secret != "" { newItem.AgentSecret = newChannel.Secret } + if newChannel.URL != oldChannel.URL { + newItem.Proxy = newChannel.URL + } if !oldItem.IsShared() { TokenStoreRemoveItem(&oldItem) } diff --git a/channel/wechat-corp-account.go b/channel/wechat-corp-account.go index 0bd3313..1c877eb 100644 --- a/channel/wechat-corp-account.go +++ b/channel/wechat-corp-account.go @@ -23,18 +23,19 @@ type WeChatCorpAccountTokenStoreItem struct { CorpId string AgentSecret string AgentId string + Proxy string AccessToken string } func (i *WeChatCorpAccountTokenStoreItem) Key() string { - return i.CorpId + i.AgentId + i.AgentSecret + return i.CorpId + i.AgentId + i.AgentSecret + strings.TrimSpace(i.Proxy) } func (i *WeChatCorpAccountTokenStoreItem) IsShared() bool { appId := fmt.Sprintf("%s|%s", i.CorpId, i.AgentId) var count int64 = 0 - model.DB.Model(&model.Channel{}).Where("secret = ? and app_id = ? and type = ?", - i.AgentSecret, appId, model.TypeWeChatCorpAccount).Count(&count) + model.DB.Model(&model.Channel{}).Where("secret = ? and app_id = ? and type = ? and url = ?", + i.AgentSecret, appId, model.TypeWeChatCorpAccount, strings.TrimSpace(i.Proxy)).Count(&count) return count > 1 } @@ -48,8 +49,10 @@ func (i *WeChatCorpAccountTokenStoreItem) Token() string { func (i *WeChatCorpAccountTokenStoreItem) Refresh() { // https://work.weixin.qq.com/api/doc/90000/90135/91039 - client := http.Client{ - Timeout: 5 * time.Second, + client, err := newHTTPClient(i.Proxy, 5*time.Second) + if err != nil { + common.SysError("failed to create http client: " + err.Error()) + return } req, err := http.NewRequest("GET", fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", i.CorpId, i.AgentSecret), nil) @@ -148,13 +151,24 @@ func SendWeChatCorpMessage(message *model.Message, user *model.User, channel_ *m if err != nil { return err } - key := fmt.Sprintf("%s%s%s", corpId, agentId, agentSecret) - accessToken := TokenStoreGetToken(key) - resp, err := http.Post(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", accessToken), "application/json", + proxyAddress := channel_.URL + tokenItem := WeChatCorpAccountTokenStoreItem{ + CorpId: corpId, + AgentSecret: agentSecret, + AgentId: agentId, + Proxy: proxyAddress, + } + accessToken := TokenStoreGetToken(tokenItem.Key()) + client, err := newHTTPClient(proxyAddress, 10*time.Second) + if err != nil { + return err + } + resp, err := client.Post(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", accessToken), "application/json", bytes.NewBuffer(jsonData)) if err != nil { return err } + defer resp.Body.Close() var res wechatCorpMessageResponse err = json.NewDecoder(resp.Body).Decode(&res) if err != nil { diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index ccaab27..abf7c03 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -337,6 +337,14 @@ const EditChannel = () => { value={inputs.other} onChange={handleInputChange} /> + ); @@ -520,6 +528,16 @@ const EditChannel = () => { placeholder='在此设置 Telegram 会话 ID' /> + + +