template_msg.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package wechat
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "hongze/hongze_yb/global"
  9. "hongze/hongze_yb/models/tables/user_template_record"
  10. "hongze/hongze_yb/services/alarm_msg"
  11. "hongze/hongze_yb/utils"
  12. "io/ioutil"
  13. "net/http"
  14. "time"
  15. )
  16. var (
  17. TemplateMsgSendUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s"
  18. TemplateMsgClearQuotaUrl = "https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s"
  19. )
  20. type TemplateMsgSendClient struct {
  21. AccessToken string
  22. Data []byte
  23. }
  24. type SendTemplateResponse struct {
  25. Errcode int `json:"errcode"`
  26. Errmsg string `json:"errmsg"`
  27. MsgID int `json:"msgid"`
  28. }
  29. type ClearQuotaResponse struct {
  30. Errcode int `json:"errcode"`
  31. Errmsg string `json:"errmsg"`
  32. }
  33. type OpenIdList struct {
  34. OpenId string
  35. UserId int
  36. }
  37. // TemplateMsgSendClient.SendMsg 推送消息
  38. func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err error) {
  39. // 请求接口
  40. sendUrl := fmt.Sprintf(TemplateMsgSendUrl, c.AccessToken)
  41. client := http.Client{}
  42. resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(c.Data))
  43. if err != nil {
  44. return
  45. }
  46. defer func() {
  47. _ = resp.Body.Close()
  48. }()
  49. body, _ := ioutil.ReadAll(resp.Body)
  50. if err = json.Unmarshal(body, &sendRes); err != nil {
  51. return
  52. }
  53. // 模板消息发送超过当日10万次限制错误处理
  54. if sendRes.Errcode == 45009 {
  55. // 发送提示邮件
  56. go alarm_msg.SendAlarmMsg("模板消息发送超过当日10万次限制, SendTemplateResponse: "+string(body), 3)
  57. // 清理限制
  58. clearRes, e := c.ClearQuota()
  59. if e != nil {
  60. err = e
  61. return
  62. }
  63. if clearRes.Errcode != 0 {
  64. clearJson, _ := json.Marshal(clearRes)
  65. go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3)
  66. return
  67. }
  68. // 发送成功邮件
  69. go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1)
  70. // 重新推送
  71. go func() {
  72. reSend, e := c.SendMsg()
  73. if e != nil {
  74. reSendJson, _ := json.Marshal(reSend)
  75. alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3)
  76. }
  77. }()
  78. }
  79. if sendRes.Errcode != 0 {
  80. err = errors.New("推送模板消息失败, SendTemplateResponse: " + string(body))
  81. }
  82. return
  83. }
  84. // TemplateMsgSendClient.ClearQuota 清除发送超过当日10万次限制
  85. func (c *TemplateMsgSendClient) ClearQuota() (result *ClearQuotaResponse, err error) {
  86. key := "CACHE_SendTemplateMsg_ERR"
  87. exists, _ := global.Redis.Exists(context.TODO(), key).Result()
  88. if exists == 1 {
  89. return
  90. }
  91. _ = global.Redis.SetEX(context.TODO(), key, 1, 6*time.Minute)
  92. sendUrl := fmt.Sprintf(TemplateMsgClearQuotaUrl, c.AccessToken)
  93. client := http.Client{}
  94. clearData := make(map[string]interface{})
  95. clearData["appid"] = WxAppId
  96. clearJson, _ := json.Marshal(clearData)
  97. resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(clearJson))
  98. if err != nil {
  99. return
  100. }
  101. defer func() {
  102. _ = resp.Body.Close()
  103. }()
  104. clearBody, err := ioutil.ReadAll(resp.Body)
  105. err = json.Unmarshal(clearBody, &result)
  106. return
  107. }
  108. // AddUserTemplateRecord 新增模板消息推送记录
  109. func AddUserTemplateRecord(userId, sendStatus, sendType int, openid, resource, sendData, result string) (err error) {
  110. item := &user_template_record.UserTemplateRecord{
  111. UserID: userId,
  112. OpenID: openid,
  113. Resource: resource,
  114. SendData: sendData,
  115. Result: result,
  116. CreateDate: time.Now().Format(utils.FormatDate),
  117. CreateTime: time.Now().Format(utils.FormatDateTime),
  118. SendStatus: sendStatus,
  119. SendType: sendType,
  120. }
  121. err = item.Create()
  122. return
  123. }
  124. // SendMultiTemplateMsg 推送模板消息至多个用户
  125. func SendMultiTemplateMsg(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) {
  126. ws := GetWxChat()
  127. accessToken, err := ws.GetAccessToken()
  128. if err != nil {
  129. return
  130. }
  131. for _, item := range items {
  132. sendMap["touser"] = item.OpenId
  133. data, e := json.Marshal(sendMap)
  134. if e != nil {
  135. err = e
  136. return
  137. }
  138. ts := &TemplateMsgSendClient{
  139. AccessToken: accessToken,
  140. Data: data,
  141. }
  142. result, e := ts.SendMsg()
  143. if result == nil {
  144. return
  145. }
  146. // 推送消息记录
  147. {
  148. go func(v *OpenIdList) {
  149. sendStatus := 1
  150. if e != nil {
  151. sendStatus = 0
  152. }
  153. resultJson, _ := json.Marshal(result)
  154. _ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson))
  155. }(item)
  156. }
  157. if e != nil {
  158. err = e
  159. return
  160. }
  161. }
  162. return
  163. }
  164. // SendQuestionReplyWxMsg 推送研报小程序模板消息-问答社区回复
  165. func SendQuestionReplyWxMsg(questionId, userId int, openid, questionTitle string) (err error) {
  166. if userId == 0 || openid == "" {
  167. return
  168. }
  169. var errMsg string
  170. defer func() {
  171. if err != nil {
  172. alarmMsg := fmt.Sprintf("SendQuestionReplyWxMsg-推送问答社区回复模版消息失败; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg)
  173. go alarm_msg.SendAlarmMsg(alarmMsg, 3)
  174. }
  175. }()
  176. openIdList := make([]*OpenIdList, 0)
  177. openIdList = append(openIdList, &OpenIdList{
  178. OpenId: openid,
  179. UserId: userId,
  180. })
  181. sendMap := make(map[string]interface{})
  182. sendData := make(map[string]interface{})
  183. first := "您好,您的提问已被回复"
  184. keyword1 := questionTitle
  185. keyword2 := "待查看"
  186. remark := "请点击详情查看回复"
  187. sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
  188. sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
  189. sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
  190. sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
  191. sendMap["template_id"] = TemplateIdWithCommunityQuestion
  192. sendMap["data"] = sendData
  193. wxAppPath := fmt.Sprintf("page-question/answerDetail?id=%d", questionId)
  194. if global.CONFIG.Serve.RunMode == "debug" {
  195. // 仅测试环境测试用
  196. wxAppPath = "pages-report/reportDetail?reportId=3800"
  197. }
  198. if wxAppPath != "" {
  199. sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath}
  200. }
  201. err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
  202. return
  203. }