微信公众号获取关注列表
This commit is contained in:
commit
cf845c9cd3
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
9
.idea/development-kit-sdk.iml
Normal file
9
.idea/development-kit-sdk.iml
Normal 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
8
.idea/modules.xml
Normal 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
6
.idea/vcs.xml
Normal 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
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module git.valavala.com/vala/development-kit-sdk
|
||||||
|
|
||||||
|
go 1.24.9
|
||||||
1
pre_commit.sh
Normal file
1
pre_commit.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
go build ./... && go mod tidy
|
||||||
42
utils/http.go
Normal file
42
utils/http.go
Normal 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
|
||||||
|
}
|
||||||
68
wechat-sdk/access-token/access_token.go
Normal file
68
wechat-sdk/access-token/access_token.go
Normal 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
|
||||||
|
}
|
||||||
43
wechat-sdk/public-platform/fans.go
Normal file
43
wechat-sdk/public-platform/fans.go
Normal 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
|
||||||
|
}
|
||||||
21
wechat-sdk/structs/common.go
Normal file
21
wechat-sdk/structs/common.go
Normal 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"`
|
||||||
|
}
|
||||||
20
wechat-sdk/structs/public_platform_fans.go
Normal file
20
wechat-sdk/structs/public_platform_fans.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user