微信公众号获取关注列表

This commit is contained in:
liuxingran 2026-01-13 19:33:04 +08:00
commit cf845c9cd3
11 changed files with 229 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/development-kit-sdk.iml" filepath="$PROJECT_DIR$/.idea/development-kit-sdk.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.valavala.com/vala/development-kit-sdk
go 1.24.9

1
pre_commit.sh Normal file
View File

@ -0,0 +1 @@
go build ./... && go mod tidy

42
utils/http.go Normal file
View File

@ -0,0 +1,42 @@
package utils
import (
"bytes"
"fmt"
"io"
"net/http"
"time"
)
// HttpRequest 发送 HTTP 请求
func HttpRequest(method, url string, headers map[string]string, body []byte) ([]byte, error) {
client := &http.Client{
Timeout: 30 * time.Second, // 设置超时时间
}
// 创建请求
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
// 设置请求头
for key, value := range headers {
req.Header.Set(key, value)
}
// 发送请求
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// 检查响应状态码
rBody, err := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(rBody))
}
// 读取响应
return rBody, err
}

View File

@ -0,0 +1,68 @@
package access_token
import (
"encoding/json"
"errors"
"net/http"
"time"
"git.valavala.com/vala/development-kit-sdk/utils"
"git.valavala.com/vala/development-kit-sdk/wechat-sdk/structs"
)
var wechatStableAccessToken []*structs.WechatStableAccessToken
type wechatStableAccessTokenMgr struct {
}
func NewWechatStableAccessTokenMgr() *wechatStableAccessTokenMgr {
return &wechatStableAccessTokenMgr{}
}
func (m *wechatStableAccessTokenMgr) GetStableAccessToken(appConf *structs.WechatConf) (string, error) {
if appConf == nil {
return "", errors.New("conf is empty")
}
for i := range wechatStableAccessToken {
if wechatStableAccessToken[i].Appid == appConf.AppId {
if wechatStableAccessToken[i].AccessTokenExpiresAt > time.Now().Unix() && wechatStableAccessToken[i].AccessToken != "" {
return wechatStableAccessToken[i].AccessToken, nil
}
break
}
}
url := "https://api.weixin.qq.com/cgi-bin/stable_token"
params := map[string]string{
"grant_type": "client_credential",
"appid": appConf.AppId,
"secret": appConf.AppSecret,
}
body, _ := json.Marshal(params)
header := map[string]string{
"Content-Type": "application/json",
}
resp, err := utils.HttpRequest(http.MethodPost, url, header, body)
if err != nil {
return "", err
}
thisToken := &structs.WechatStableAccessToken{}
_ = json.Unmarshal(resp, thisToken)
thisToken.AccessTokenExpiresAt = time.Now().Unix() + int64(thisToken.ExpiresIn)
thisToken.Appid = appConf.AppId
if thisToken.AccessToken == "" {
return "", errors.New(appConf.AppId + " access_token is empty")
}
var newTokens []*structs.WechatStableAccessToken
for i := range wechatStableAccessToken {
if wechatStableAccessToken[i].Appid == appConf.AppId {
newTokens = append(newTokens, thisToken)
continue
}
newTokens = append(newTokens, wechatStableAccessToken[i])
}
wechatStableAccessToken = newTokens
return thisToken.AccessToken, nil
}

View File

@ -0,0 +1,43 @@
package public_platform
import (
"encoding/json"
"errors"
"net/http"
"git.valavala.com/vala/development-kit-sdk/utils"
access_token "git.valavala.com/vala/development-kit-sdk/wechat-sdk/access-token"
"git.valavala.com/vala/development-kit-sdk/wechat-sdk/structs"
)
type PublicPlatform struct {
}
func NewWechatPublicPlatform() *PublicPlatform {
return &PublicPlatform{}
}
// GetFans 获取公众号的关注列表
// https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=OPENID
func (p *PublicPlatform) GetFans(wechatConf *structs.WechatConf, nextOpenid string) (*structs.WechatPublicPlatformFansResp, error) {
mgr := access_token.NewWechatStableAccessTokenMgr()
accessToken, err := mgr.GetStableAccessToken(wechatConf)
if err != nil {
return nil, err
}
url := "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + accessToken + "&next_openid=" + nextOpenid
header := map[string]string{
"Content-Type": "application/json",
}
resp, err := utils.HttpRequest(http.MethodGet, url, header, nil)
if err != nil {
return nil, err
}
wechatPublicPlatformFansResp := &structs.WechatPublicPlatformFansResp{}
_ = json.Unmarshal(resp, wechatPublicPlatformFansResp)
if wechatPublicPlatformFansResp.Errcode != 0 {
return nil, errors.New(wechatPublicPlatformFansResp.Errmsg)
}
return wechatPublicPlatformFansResp, nil
}

View File

@ -0,0 +1,21 @@
package structs
type WechatConf struct {
AppId string `json:"appId"`
AppName string `json:"appName"`
AppSecret string `json:"appSecret"`
MsgToken string `json:"msgToken"`
MsgAesKey string `json:"msgAesKey"`
}
type WechatStableAccessToken struct {
Appid string `json:"appid"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
AccessTokenExpiresAt int64 `json:"access_token_expires_at"`
}
type WechatCommonResp struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}

View File

@ -0,0 +1,20 @@
package structs
/*
WechatPublicPlatformFansResp
一次拉取调用最多拉取10000个关注者的OpenID可以通过多次拉取的方式来满足需求
最后一次返回时next_openid可能为空表示列表结束
接口信息https://developers.weixin.qq.com/doc/subscription/api/usermanage/userinfo/api_getfans.html
*/
type WechatPublicPlatformFansResp struct {
WechatCommonResp
Total int `json:"total"` //关注该公众账号的总用户数
Count int `json:"count"` //拉取的OPENID个数最大值为10000
Data struct { //列表数据OPENID的列表
Openid []string `json:"openid"` //用户唯一ID
}
NextOpenid string `json:"next_openid"` //拉取列表后一个用户的OPENID
}