group.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. package wxSignGroup
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/util/gconv"
  7. "regexp"
  8. "time"
  9. )
  10. type (
  11. SignGroup struct {
  12. Id int64 `json:"id"`
  13. Name string `json:"name"`
  14. Count int64 `json:"count"`
  15. }
  16. AccessToken struct {
  17. AccessToken string `json:"access_token"`
  18. ExpiresIn int64 `json:"expires_in"`
  19. LastTime int64 `json:"last_time"`
  20. }
  21. GroupFull struct {
  22. Sign *SignGroup
  23. Old, New []string
  24. }
  25. )
  26. type WxSignGroup struct {
  27. data map[int]*GroupFull
  28. }
  29. func NewWxSignManager() (*WxSignGroup, error) {
  30. //加载所有分组
  31. var (
  32. wsg = &WxSignGroup{}
  33. data = map[int]*GroupFull{}
  34. ctx = context.Background()
  35. re = regexp.MustCompile(`^关注第(\d+)天$`)
  36. )
  37. //加载分组信息
  38. list, err := wsg.allGroup(ctx)
  39. if err != nil {
  40. return nil, err
  41. }
  42. for _, day := range g.Cfg().MustGet(ctx, "subDaySign").Ints() {
  43. var isExists = false
  44. for _, group := range list {
  45. match := re.FindStringSubmatch(group.Name)
  46. if len(match) == 0 {
  47. continue
  48. }
  49. s := gconv.Int(match[1])
  50. if s == day {
  51. data[day] = &GroupFull{
  52. Sign: group,
  53. Old: []string{},
  54. New: []string{},
  55. }
  56. isExists = true
  57. break
  58. }
  59. }
  60. if !isExists {
  61. newGroupName := fmt.Sprintf("关注第%d天", day)
  62. newGroup, err := wsg.greatGroup(ctx, newGroupName)
  63. if err != nil {
  64. g.Log().Errorf(ctx, "创建%s异常 %v", newGroupName, err)
  65. continue
  66. }
  67. data[day] = &GroupFull{
  68. Sign: newGroup,
  69. Old: []string{},
  70. New: []string{},
  71. }
  72. }
  73. }
  74. wsg.data = data
  75. return wsg, nil
  76. }
  77. func (wsg *WxSignGroup) getToken() string {
  78. for {
  79. ctx := context.Background()
  80. gv, err := g.Redis().Get(ctx, fmt.Sprintf("WxToken_%s", g.Cfg().MustGet(ctx, "wxAppId").String()))
  81. if err != nil {
  82. g.Log().Error(ctx, "获取accessToken异常", err)
  83. continue
  84. }
  85. var accessToken AccessToken
  86. if err := gv.Struct(&accessToken); err != nil {
  87. g.Log().Error(ctx, "获取反序列化异常", err)
  88. continue
  89. }
  90. if time.Now().Unix() > accessToken.ExpiresIn {
  91. g.Log().Error(ctx, "token已过期", err)
  92. continue
  93. }
  94. return accessToken.AccessToken
  95. }
  96. }
  97. func (wsg *WxSignGroup) AddNewUsers(dayNum int, openids ...string) error {
  98. group, ok := wsg.data[dayNum]
  99. if !ok {
  100. return fmt.Errorf("未知标签")
  101. }
  102. group.New = append(group.New, openids...)
  103. return nil
  104. }
  105. // LoadGroupUser 加载标签下的用户
  106. func (wsg *WxSignGroup) LoadGroupUser(ctx context.Context) {
  107. //a, b, c := wsg.getGroupUsers(ctx, 0, "")
  108. //fmt.Println(a, b, c)
  109. //return
  110. //加载标签对应用户
  111. for _, group := range wsg.data {
  112. var nextOpenid string
  113. group.Old = []string{}
  114. for {
  115. userIds, nextId, err := wsg.getGroupUsers(ctx, group.Sign.Id, nextOpenid)
  116. if err != nil {
  117. g.Log().Errorf(ctx, "加载%s用户异常", group.Sign.Name)
  118. break
  119. }
  120. group.Old = append(group.Old, userIds...)
  121. if nextId == "" {
  122. break
  123. }
  124. nextOpenid = nextId
  125. }
  126. g.Log().Infof(ctx, "加载 %s 标签下 %d用户,实际加载%d个", group.Sign.Name, group.Sign.Count, len(group.Old))
  127. }
  128. g.Dump(wsg.data)
  129. }
  130. // ClearGroupUser 清除标签下的用户
  131. func (wsg *WxSignGroup) ClearGroupUser(ctx context.Context) {
  132. for _, group := range wsg.data {
  133. for _, openids := range chunkArray(group.Old, 50) {
  134. if err := wsg.groupDelUser(ctx, group.Sign.Id, openids...); err != nil {
  135. g.Log().Errorf(ctx, "清除 %s标签下的用户异常 %v", group.Sign.Name, err)
  136. }
  137. }
  138. }
  139. }
  140. // UpdateNewGroupUser 更新最新的标签用户
  141. func (wsg *WxSignGroup) UpdateNewGroupUser(ctx context.Context) {
  142. for _, group := range wsg.data {
  143. for _, openids := range chunkArray(group.New, 50) {
  144. if err := wsg.groupAddUser(ctx, group.Sign.Id, openids...); err != nil {
  145. g.Log().Errorf(ctx, "%s标签添加用户异常 %v", group.Sign.Name, err)
  146. }
  147. }
  148. }
  149. }
  150. func (wsg *WxSignGroup) DelGroup(ctx context.Context, ids ...int) {
  151. for _, groupId := range ids {
  152. if err := wsg.delGroup(ctx, gconv.Int64(groupId)); err != nil {
  153. g.Log().Errorf(ctx, "删除群组异常 %v", err)
  154. }
  155. }
  156. }
  157. // greatGroup 创建分组
  158. func (wsg *WxSignGroup) greatGroup(ctx context.Context, name string) (*SignGroup, error) {
  159. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  160. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/create?access_token=%s", wsg.getToken()), g.Map{
  161. "tag": g.Map{"name": name},
  162. })
  163. if err != nil {
  164. return nil, err
  165. }
  166. defer res.Close()
  167. var rData SignGroup
  168. cMap := gconv.Map(res.ReadAll())
  169. if errMsg := gconv.String(cMap["errmsg"]); errMsg != "" {
  170. return nil, fmt.Errorf(errMsg)
  171. }
  172. if err := gconv.Struct(cMap["tag"], &rData); err != nil {
  173. return nil, err
  174. }
  175. return &rData, nil
  176. }
  177. // delGroup 删除分组
  178. func (wsg *WxSignGroup) delGroup(ctx context.Context, groupId int64) error {
  179. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  180. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=%s", wsg.getToken()), g.Map{
  181. "tag": g.Map{"id": groupId},
  182. })
  183. if err != nil {
  184. return err
  185. }
  186. defer res.Close()
  187. rMap := gconv.Map(res.ReadAll())
  188. if gconv.String(rMap["errmsg"]) != "ok" {
  189. return fmt.Errorf("删除分组异常:%s", gconv.String(rMap["errmsg"]))
  190. }
  191. return nil
  192. }
  193. // groupAddUser 分组添加用户
  194. func (wsg *WxSignGroup) groupAddUser(ctx context.Context, groupId int64, openids ...string) error {
  195. if len(openids) == 0 {
  196. return nil
  197. }
  198. if len(openids) > 50 {
  199. return fmt.Errorf("超出最大限制")
  200. }
  201. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  202. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=%s", wsg.getToken()), g.Map{
  203. "tagid": groupId,
  204. "openid_list": openids,
  205. })
  206. if err != nil {
  207. return err
  208. }
  209. defer res.Close()
  210. rMap := gconv.Map(res.ReadAll())
  211. if gconv.String(rMap["errmsg"]) != "ok" {
  212. return fmt.Errorf("添加用户异常:%s", gconv.String(rMap["errmsg"]))
  213. }
  214. return nil
  215. }
  216. // groupDelUser 分组删除用户
  217. func (wsg *WxSignGroup) groupDelUser(ctx context.Context, groupId int64, openids ...string) error {
  218. if len(openids) == 0 {
  219. return nil
  220. }
  221. if len(openids) > 50 {
  222. return fmt.Errorf("超出最大限制")
  223. }
  224. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  225. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=%s", wsg.getToken()), g.Map{
  226. "tagid": groupId,
  227. "openid_list": openids,
  228. })
  229. if err != nil {
  230. return err
  231. }
  232. defer res.Close()
  233. rMap := gconv.Map(res.ReadAll())
  234. if gconv.String(rMap["errmsg"]) != "ok" {
  235. return fmt.Errorf("标签删除用户异常:%s", gconv.String(rMap["errmsg"]))
  236. }
  237. return nil
  238. }
  239. // allGroup 获取所有分组
  240. func (wsg *WxSignGroup) allGroup(ctx context.Context) ([]*SignGroup, error) {
  241. res, err := g.Client().Get(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s", wsg.getToken()))
  242. if err != nil {
  243. return nil, err
  244. }
  245. defer res.Close()
  246. var rData []*SignGroup
  247. m := gconv.Map(res.ReadAll())
  248. if err := gconv.Struct(m["tags"], &rData); err != nil {
  249. return nil, err
  250. }
  251. return rData, nil
  252. }
  253. // getGroupUsers 获取所有分组
  254. func (wsg *WxSignGroup) getGroupUsers(ctx context.Context, groupId int64, next_openid string) ([]string, string, error) {
  255. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  256. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s", wsg.getToken()), g.Map{
  257. "tagid": groupId,
  258. "next_openid": next_openid,
  259. })
  260. if err != nil {
  261. return nil, "", err
  262. }
  263. defer res.Close()
  264. var rData = struct {
  265. Count int `json:"count"`
  266. Data struct {
  267. Openid []string `json:"openid"`
  268. } `json:"data"`
  269. NextOpenid string `json:"next_openid"`
  270. }{}
  271. if err := gconv.Struct(res.ReadAll(), &rData); err != nil {
  272. return nil, "", err
  273. }
  274. if rData.Data.Openid != nil && len(rData.Data.Openid) > 0 {
  275. return rData.Data.Openid, rData.NextOpenid, nil
  276. }
  277. return nil, "", nil
  278. }
  279. func chunkArray(arr []string, size int) [][]string {
  280. var result [][]string
  281. // 遍历一维数组,每次取 size 个元素
  282. for i := 0; i < len(arr); i += size {
  283. // 计算切片的结束位置,确保不超出数组的长度
  284. end := i + size
  285. if end > len(arr) {
  286. end = len(arr)
  287. }
  288. // 将切片添加到结果数组中
  289. result = append(result, arr[i:end])
  290. }
  291. return result
  292. }