123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- package wxSignGroup
- import (
- "context"
- "fmt"
- "github.com/gogf/gf/v2/frame/g"
- "github.com/gogf/gf/v2/util/gconv"
- "regexp"
- "time"
- )
- type (
- SignGroup struct {
- Id int64 `json:"id"`
- Name string `json:"name"`
- Count int64 `json:"count"`
- }
- AccessToken struct {
- AccessToken string `json:"access_token"`
- ExpiresIn int64 `json:"expires_in"`
- LastTime int64 `json:"last_time"`
- }
- GroupFull struct {
- Sign *SignGroup
- Old, New []string
- }
- )
- type WxSignGroup struct {
- data map[int]*GroupFull
- }
- func NewWxSignManager() (*WxSignGroup, error) {
- //加载所有分组
- var (
- wsg = &WxSignGroup{}
- data = map[int]*GroupFull{}
- ctx = context.Background()
- re = regexp.MustCompile(`^关注第(\d+)天$`)
- )
- //加载分组信息
- list, err := wsg.allGroup(ctx)
- if err != nil {
- return nil, err
- }
- for _, day := range g.Cfg().MustGet(ctx, "subDaySign").Ints() {
- var isExists = false
- for _, group := range list {
- match := re.FindStringSubmatch(group.Name)
- if len(match) == 0 {
- continue
- }
- s := gconv.Int(match[1])
- if s == day {
- data[day] = &GroupFull{
- Sign: group,
- Old: []string{},
- New: []string{},
- }
- isExists = true
- break
- }
- }
- if !isExists {
- newGroupName := fmt.Sprintf("关注第%d天", day)
- newGroup, err := wsg.greatGroup(ctx, newGroupName)
- if err != nil {
- g.Log().Errorf(ctx, "创建%s异常 %v", newGroupName, err)
- continue
- }
- data[day] = &GroupFull{
- Sign: newGroup,
- Old: []string{},
- New: []string{},
- }
- }
- }
- wsg.data = data
- return wsg, nil
- }
- func (wsg *WxSignGroup) getToken() string {
- for {
- ctx := context.Background()
- gv, err := g.Redis().Get(ctx, fmt.Sprintf("WxToken_%s", g.Cfg().MustGet(ctx, "wxAppId").String()))
- if err != nil {
- g.Log().Error(ctx, "获取accessToken异常", err)
- continue
- }
- var accessToken AccessToken
- if err := gv.Struct(&accessToken); err != nil {
- g.Log().Error(ctx, "获取反序列化异常", err)
- continue
- }
- if time.Now().Unix() > accessToken.ExpiresIn {
- g.Log().Error(ctx, "token已过期", err)
- continue
- }
- return accessToken.AccessToken
- }
- }
- func (wsg *WxSignGroup) AddNewUsers(dayNum int, openids ...string) error {
- group, ok := wsg.data[dayNum]
- if !ok {
- return fmt.Errorf("未知标签")
- }
- group.New = append(group.New, openids...)
- return nil
- }
- // LoadGroupUser 加载标签下的用户
- func (wsg *WxSignGroup) LoadGroupUser(ctx context.Context) {
- //a, b, c := wsg.getGroupUsers(ctx, 0, "")
- //fmt.Println(a, b, c)
- //return
- //加载标签对应用户
- for _, group := range wsg.data {
- var nextOpenid string
- group.Old = []string{}
- for {
- userIds, nextId, err := wsg.getGroupUsers(ctx, group.Sign.Id, nextOpenid)
- if err != nil {
- g.Log().Errorf(ctx, "加载%s用户异常", group.Sign.Name)
- break
- }
- group.Old = append(group.Old, userIds...)
- if nextId == "" {
- break
- }
- nextOpenid = nextId
- }
- g.Log().Infof(ctx, "加载 %s 标签下 %d用户,实际加载%d个", group.Sign.Name, group.Sign.Count, len(group.Old))
- }
- g.Dump(wsg.data)
- }
- // ClearGroupUser 清除标签下的用户
- func (wsg *WxSignGroup) ClearGroupUser(ctx context.Context) {
- for _, group := range wsg.data {
- for _, openids := range chunkArray(group.Old, 50) {
- if err := wsg.groupDelUser(ctx, group.Sign.Id, openids...); err != nil {
- g.Log().Errorf(ctx, "清除 %s标签下的用户异常 %v", group.Sign.Name, err)
- }
- }
- }
- }
- // UpdateNewGroupUser 更新最新的标签用户
- func (wsg *WxSignGroup) UpdateNewGroupUser(ctx context.Context) {
- for _, group := range wsg.data {
- for _, openids := range chunkArray(group.New, 50) {
- if err := wsg.groupAddUser(ctx, group.Sign.Id, openids...); err != nil {
- g.Log().Errorf(ctx, "%s标签添加用户异常 %v", group.Sign.Name, err)
- }
- }
- }
- }
- func (wsg *WxSignGroup) DelGroup(ctx context.Context, ids ...int) {
- for _, groupId := range ids {
- if err := wsg.delGroup(ctx, gconv.Int64(groupId)); err != nil {
- g.Log().Errorf(ctx, "删除群组异常 %v", err)
- }
- }
- }
- // greatGroup 创建分组
- func (wsg *WxSignGroup) greatGroup(ctx context.Context, name string) (*SignGroup, error) {
- res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
- Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/create?access_token=%s", wsg.getToken()), g.Map{
- "tag": g.Map{"name": name},
- })
- if err != nil {
- return nil, err
- }
- defer res.Close()
- var rData SignGroup
- cMap := gconv.Map(res.ReadAll())
- if errMsg := gconv.String(cMap["errmsg"]); errMsg != "" {
- return nil, fmt.Errorf(errMsg)
- }
- if err := gconv.Struct(cMap["tag"], &rData); err != nil {
- return nil, err
- }
- return &rData, nil
- }
- // delGroup 删除分组
- func (wsg *WxSignGroup) delGroup(ctx context.Context, groupId int64) error {
- res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
- Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=%s", wsg.getToken()), g.Map{
- "tag": g.Map{"id": groupId},
- })
- if err != nil {
- return err
- }
- defer res.Close()
- rMap := gconv.Map(res.ReadAll())
- if gconv.String(rMap["errmsg"]) != "ok" {
- return fmt.Errorf("删除分组异常:%s", gconv.String(rMap["errmsg"]))
- }
- return nil
- }
- // groupAddUser 分组添加用户
- func (wsg *WxSignGroup) groupAddUser(ctx context.Context, groupId int64, openids ...string) error {
- if len(openids) == 0 {
- return nil
- }
- if len(openids) > 50 {
- return fmt.Errorf("超出最大限制")
- }
- res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
- Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=%s", wsg.getToken()), g.Map{
- "tagid": groupId,
- "openid_list": openids,
- })
- if err != nil {
- return err
- }
- defer res.Close()
- rMap := gconv.Map(res.ReadAll())
- if gconv.String(rMap["errmsg"]) != "ok" {
- return fmt.Errorf("添加用户异常:%s", gconv.String(rMap["errmsg"]))
- }
- return nil
- }
- // groupDelUser 分组删除用户
- func (wsg *WxSignGroup) groupDelUser(ctx context.Context, groupId int64, openids ...string) error {
- if len(openids) == 0 {
- return nil
- }
- if len(openids) > 50 {
- return fmt.Errorf("超出最大限制")
- }
- res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
- Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=%s", wsg.getToken()), g.Map{
- "tagid": groupId,
- "openid_list": openids,
- })
- if err != nil {
- return err
- }
- defer res.Close()
- rMap := gconv.Map(res.ReadAll())
- if gconv.String(rMap["errmsg"]) != "ok" {
- return fmt.Errorf("标签删除用户异常:%s", gconv.String(rMap["errmsg"]))
- }
- return nil
- }
- // allGroup 获取所有分组
- func (wsg *WxSignGroup) allGroup(ctx context.Context) ([]*SignGroup, error) {
- res, err := g.Client().Get(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s", wsg.getToken()))
- if err != nil {
- return nil, err
- }
- defer res.Close()
- var rData []*SignGroup
- m := gconv.Map(res.ReadAll())
- if err := gconv.Struct(m["tags"], &rData); err != nil {
- return nil, err
- }
- return rData, nil
- }
- // getGroupUsers 获取所有分组
- func (wsg *WxSignGroup) getGroupUsers(ctx context.Context, groupId int64, next_openid string) ([]string, string, error) {
- res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
- Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s", wsg.getToken()), g.Map{
- "tagid": groupId,
- "next_openid": next_openid,
- })
- if err != nil {
- return nil, "", err
- }
- defer res.Close()
- var rData = struct {
- Count int `json:"count"`
- Data struct {
- Openid []string `json:"openid"`
- } `json:"data"`
- NextOpenid string `json:"next_openid"`
- }{}
- if err := gconv.Struct(res.ReadAll(), &rData); err != nil {
- return nil, "", err
- }
- if rData.Data.Openid != nil && len(rData.Data.Openid) > 0 {
- return rData.Data.Openid, rData.NextOpenid, nil
- }
- return nil, "", nil
- }
- func chunkArray(arr []string, size int) [][]string {
- var result [][]string
- // 遍历一维数组,每次取 size 个元素
- for i := 0; i < len(arr); i += size {
- // 计算切片的结束位置,确保不超出数组的长度
- end := i + size
- if end > len(arr) {
- end = len(arr)
- }
- // 将切片添加到结果数组中
- result = append(result, arr[i:end])
- }
- return result
- }
|