group.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. "net/rpc"
  8. "regexp"
  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(appId string) (*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, appId)
  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, appId, 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(appId string) 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) GetWxAccessToken(ctx context.Context, code string) string {
  98. var repl string
  99. client, err := rpc.DialHTTP("tcp", g.Config().MustGet(ctx, "wxTokenRpc").String())
  100. if err != nil {
  101. g.Log().Errorf(ctx, code, err)
  102. return repl
  103. }
  104. defer client.Close()
  105. err = client.Call("WxTokenRpc.GetAccessToken", code, &repl)
  106. if err != nil {
  107. g.Log().Errorf(ctx, code, err)
  108. return repl
  109. }
  110. if repl == "" {
  111. g.Log().Errorf(ctx, "未获取到accessToken")
  112. } else {
  113. g.Log().Errorf(ctx, "获取到accessToken %s", repl)
  114. }
  115. return repl
  116. }
  117. func (wsg *WxSignGroup) AddNewUsers(dayNum int, openids ...string) error {
  118. group, ok := wsg.data[dayNum]
  119. if !ok {
  120. return fmt.Errorf("未知标签")
  121. }
  122. group.New = append(group.New, openids...)
  123. return nil
  124. }
  125. // LoadGroupUser 加载标签下的用户
  126. func (wsg *WxSignGroup) LoadGroupUser(ctx context.Context, appId string) {
  127. //加载标签对应用户
  128. for _, group := range wsg.data {
  129. var nextOpenid string
  130. group.Old = []string{}
  131. for {
  132. userIds, nextId, err := wsg.getGroupUsers(ctx, appId, group.Sign.Id, nextOpenid)
  133. if err != nil {
  134. g.Log().Errorf(ctx, "加载%s用户异常", group.Sign.Name)
  135. break
  136. }
  137. group.Old = append(group.Old, userIds...)
  138. if nextId == "" {
  139. break
  140. }
  141. nextOpenid = nextId
  142. }
  143. g.Log().Infof(ctx, "加载 %s 标签下 %d用户,实际加载%d个", group.Sign.Name, group.Sign.Count, len(group.Old))
  144. }
  145. g.Dump(wsg.data)
  146. }
  147. // ClearGroupUser 清除标签下的用户
  148. func (wsg *WxSignGroup) ClearGroupUser(ctx context.Context, appId string) {
  149. for _, group := range wsg.data {
  150. for _, openids := range chunkArray(group.Old, 50) {
  151. if err := wsg.groupDelUser(ctx, appId, group.Sign.Id, openids...); err != nil {
  152. g.Log().Errorf(ctx, "%s 清除 %s标签下的用户异常 %v", appId, group.Sign.Name, err)
  153. }
  154. }
  155. }
  156. }
  157. // UpdateNewGroupUser 更新最新的标签用户
  158. func (wsg *WxSignGroup) UpdateNewGroupUser(ctx context.Context, appId string) {
  159. for _, group := range wsg.data {
  160. for _, openids := range chunkArray(group.New, 50) {
  161. if err := wsg.groupAddUser(ctx, appId, group.Sign.Id, openids...); err != nil {
  162. g.Log().Errorf(ctx, "%s %s标签添加用户异常 %v", appId, group.Sign.Name, err)
  163. }
  164. }
  165. }
  166. }
  167. func (wsg *WxSignGroup) DelGroup(ctx context.Context, appId string, ids ...int) {
  168. for _, groupId := range ids {
  169. if err := wsg.delGroup(ctx, appId, gconv.Int64(groupId)); err != nil {
  170. g.Log().Errorf(ctx, "%s 删除群组异常 %v", appId, err)
  171. }
  172. }
  173. }
  174. // greatGroup 创建分组
  175. func (wsg *WxSignGroup) greatGroup(ctx context.Context, appId, name string) (*SignGroup, error) {
  176. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  177. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/create?access_token=%s", wsg.GetWxAccessToken(ctx, appId)), g.Map{
  178. "tag": g.Map{"name": name},
  179. })
  180. if err != nil {
  181. return nil, err
  182. }
  183. defer res.Close()
  184. var rData SignGroup
  185. cMap := gconv.Map(res.ReadAll())
  186. if errMsg := gconv.String(cMap["errmsg"]); errMsg != "" {
  187. return nil, fmt.Errorf(errMsg)
  188. }
  189. if err := gconv.Struct(cMap["tag"], &rData); err != nil {
  190. return nil, err
  191. }
  192. return &rData, nil
  193. }
  194. // delGroup 删除分组
  195. func (wsg *WxSignGroup) delGroup(ctx context.Context, appId string, groupId int64) error {
  196. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  197. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/delete?access_token=%s", wsg.GetWxAccessToken(ctx, appId)), g.Map{
  198. "tag": g.Map{"id": groupId},
  199. })
  200. if err != nil {
  201. return err
  202. }
  203. defer res.Close()
  204. rMap := gconv.Map(res.ReadAll())
  205. if gconv.String(rMap["errmsg"]) != "ok" {
  206. return fmt.Errorf("%s 删除分组异常:%s", appId, gconv.String(rMap["errmsg"]))
  207. }
  208. return nil
  209. }
  210. // groupAddUser 分组添加用户
  211. func (wsg *WxSignGroup) groupAddUser(ctx context.Context, appId string, groupId int64, openids ...string) error {
  212. if len(openids) == 0 {
  213. return nil
  214. }
  215. if len(openids) > 50 {
  216. return fmt.Errorf("超出最大限制")
  217. }
  218. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  219. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchtagging?access_token=%s", wsg.GetWxAccessToken(ctx, appId)), g.Map{
  220. "tagid": groupId,
  221. "openid_list": openids,
  222. })
  223. if err != nil {
  224. return err
  225. }
  226. defer res.Close()
  227. rMap := gconv.Map(res.ReadAll())
  228. if gconv.String(rMap["errmsg"]) != "ok" {
  229. return fmt.Errorf("%d 添加用户异常:%s", appId, gconv.String(rMap["errmsg"]))
  230. }
  231. return nil
  232. }
  233. // groupDelUser 分组删除用户
  234. func (wsg *WxSignGroup) groupDelUser(ctx context.Context, appId string, groupId int64, openids ...string) error {
  235. if len(openids) == 0 {
  236. return nil
  237. }
  238. if len(openids) > 50 {
  239. return fmt.Errorf("超出最大限制")
  240. }
  241. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  242. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/members/batchuntagging?access_token=%s", wsg.GetWxAccessToken(ctx, appId)), g.Map{
  243. "tagid": groupId,
  244. "openid_list": openids,
  245. })
  246. if err != nil {
  247. return err
  248. }
  249. defer res.Close()
  250. rMap := gconv.Map(res.ReadAll())
  251. if gconv.String(rMap["errmsg"]) != "ok" {
  252. return fmt.Errorf("%s 标签删除用户异常:%s", appId, gconv.String(rMap["errmsg"]))
  253. }
  254. return nil
  255. }
  256. // allGroup 获取所有分组
  257. func (wsg *WxSignGroup) allGroup(ctx context.Context, appId string) ([]*SignGroup, error) {
  258. res, err := g.Client().Get(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s", wsg.GetWxAccessToken(ctx, appId)))
  259. if err != nil {
  260. return nil, err
  261. }
  262. defer res.Close()
  263. var rData []*SignGroup
  264. m := gconv.Map(res.ReadAll())
  265. if err := gconv.Struct(m["tags"], &rData); err != nil {
  266. return nil, err
  267. }
  268. return rData, nil
  269. }
  270. // getGroupUsers 获取所有分组
  271. func (wsg *WxSignGroup) getGroupUsers(ctx context.Context, appId string, groupId int64, next_openid string) ([]string, string, error) {
  272. res, err := g.Client().Header(map[string]string{"Content-Type": "application/json"}).
  273. Post(ctx, fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s", wsg.GetWxAccessToken(ctx, appId)), g.Map{
  274. "tagid": groupId,
  275. "next_openid": next_openid,
  276. })
  277. if err != nil {
  278. return nil, "", err
  279. }
  280. defer res.Close()
  281. var rData = struct {
  282. Count int `json:"count"`
  283. Data struct {
  284. Openid []string `json:"openid"`
  285. } `json:"data"`
  286. NextOpenid string `json:"next_openid"`
  287. }{}
  288. if err := gconv.Struct(res.ReadAll(), &rData); err != nil {
  289. return nil, "", err
  290. }
  291. if rData.Data.Openid != nil && len(rData.Data.Openid) > 0 {
  292. return rData.Data.Openid, rData.NextOpenid, nil
  293. }
  294. return nil, "", nil
  295. }
  296. func chunkArray(arr []string, size int) [][]string {
  297. var result [][]string
  298. // 遍历一维数组,每次取 size 个元素
  299. for i := 0; i < len(arr); i += size {
  300. // 计算切片的结束位置,确保不超出数组的长度
  301. end := i + size
  302. if end > len(arr) {
  303. end = len(arr)
  304. }
  305. // 将切片添加到结果数组中
  306. result = append(result, arr[i:end])
  307. }
  308. return result
  309. }