|
@@ -0,0 +1,318 @@
|
|
|
+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
|
|
|
+}
|