template_msg.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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_record"
  10. "hongze/hongze_yb/models/tables/user_template_record"
  11. "hongze/hongze_yb/models/tables/wx_user"
  12. "hongze/hongze_yb/services/alarm_msg"
  13. "hongze/hongze_yb/utils"
  14. "io/ioutil"
  15. "net/http"
  16. "time"
  17. )
  18. var (
  19. TemplateMsgSendUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s"
  20. TemplateMsgClearQuotaUrl = "https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s"
  21. )
  22. type TemplateMsgSendClient struct {
  23. AccessToken string
  24. Data []byte
  25. }
  26. type SendTemplateResponse struct {
  27. Errcode int `json:"errcode"`
  28. Errmsg string `json:"errmsg"`
  29. MsgID int `json:"msgid"`
  30. }
  31. type ClearQuotaResponse struct {
  32. Errcode int `json:"errcode"`
  33. Errmsg string `json:"errmsg"`
  34. }
  35. type OpenIdList struct {
  36. OpenId string
  37. UserId int
  38. }
  39. // TemplateMsgSendClient.SendMsg 推送消息
  40. func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err error) {
  41. // 请求接口
  42. sendUrl := fmt.Sprintf(TemplateMsgSendUrl, c.AccessToken)
  43. client := http.Client{}
  44. resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(c.Data))
  45. if err != nil {
  46. return
  47. }
  48. defer func() {
  49. _ = resp.Body.Close()
  50. }()
  51. body, _ := ioutil.ReadAll(resp.Body)
  52. if err = json.Unmarshal(body, &sendRes); err != nil {
  53. return
  54. }
  55. // 模板消息发送超过当日10万次限制错误处理
  56. if sendRes.Errcode == 45009 {
  57. // 发送提示邮件
  58. go alarm_msg.SendAlarmMsg("模板消息发送超过当日10万次限制, SendTemplateResponse: "+string(body), 3)
  59. // 清理限制
  60. clearRes, e := c.ClearQuota()
  61. if e != nil {
  62. err = e
  63. return
  64. }
  65. if clearRes.Errcode != 0 {
  66. clearJson, _ := json.Marshal(clearRes)
  67. go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3)
  68. return
  69. }
  70. // 发送成功邮件
  71. go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1)
  72. // 重新推送
  73. go func() {
  74. reSend, e := c.SendMsg()
  75. if e != nil {
  76. reSendJson, _ := json.Marshal(reSend)
  77. alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3)
  78. }
  79. }()
  80. }
  81. if sendRes.Errcode != 0 {
  82. err = errors.New("推送模板消息失败, SendTemplateResponse: " + string(body))
  83. }
  84. return
  85. }
  86. // TemplateMsgSendClient.ClearQuota 清除发送超过当日10万次限制
  87. func (c *TemplateMsgSendClient) ClearQuota() (result *ClearQuotaResponse, err error) {
  88. key := "CACHE_SendTemplateMsg_ERR"
  89. exists, _ := global.Redis.Exists(context.TODO(), key).Result()
  90. if exists == 1 {
  91. return
  92. }
  93. _ = global.Redis.SetEX(context.TODO(), key, 1, 6*time.Minute)
  94. sendUrl := fmt.Sprintf(TemplateMsgClearQuotaUrl, c.AccessToken)
  95. client := http.Client{}
  96. clearData := make(map[string]interface{})
  97. clearData["appid"] = WxAppId
  98. clearJson, _ := json.Marshal(clearData)
  99. resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(clearJson))
  100. if err != nil {
  101. return
  102. }
  103. defer func() {
  104. _ = resp.Body.Close()
  105. }()
  106. clearBody, err := ioutil.ReadAll(resp.Body)
  107. err = json.Unmarshal(clearBody, &result)
  108. return
  109. }
  110. // AddUserTemplateRecord 新增模板消息推送记录
  111. func AddUserTemplateRecord(userId, sendStatus, sendType int, openid, resource, sendData, result string) (err error) {
  112. item := &user_template_record.UserTemplateRecord{
  113. UserID: userId,
  114. OpenID: openid,
  115. Resource: resource,
  116. SendData: sendData,
  117. Result: result,
  118. CreateDate: time.Now().Format(utils.FormatDate),
  119. CreateTime: time.Now().Format(utils.FormatDateTime),
  120. SendStatus: sendStatus,
  121. SendType: sendType,
  122. }
  123. err = item.Create()
  124. return
  125. }
  126. // SendMultiTemplateMsg 推送模板消息至多个用户
  127. func SendMultiTemplateMsg(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) {
  128. ws := GetWxChat()
  129. accessToken, err := ws.GetAccessToken()
  130. if err != nil {
  131. return
  132. }
  133. for _, item := range items {
  134. sendMap["touser"] = item.OpenId
  135. data, e := json.Marshal(sendMap)
  136. if e != nil {
  137. err = e
  138. return
  139. }
  140. ts := &TemplateMsgSendClient{
  141. AccessToken: accessToken,
  142. Data: data,
  143. }
  144. result, e := ts.SendMsg()
  145. if result == nil {
  146. return
  147. }
  148. // 推送消息记录
  149. {
  150. go func(v *OpenIdList) {
  151. sendStatus := 1
  152. if e != nil {
  153. sendStatus = 0
  154. }
  155. resultJson, _ := json.Marshal(result)
  156. _ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson))
  157. }(item)
  158. }
  159. if e != nil {
  160. err = e
  161. return
  162. }
  163. }
  164. return
  165. }
  166. // SendQuestionReplyWxMsg 推送研报小程序模板消息-问答社区回复
  167. func SendQuestionReplyWxMsg(questionId, userId int, questionTitle string) (err error) {
  168. if userId == 0 {
  169. return
  170. }
  171. var errMsg string
  172. defer func() {
  173. if err != nil {
  174. alarmMsg := fmt.Sprintf("SendQuestionReplyWxMsg-推送问答社区回复模版消息失败; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg)
  175. go alarm_msg.SendAlarmMsg(alarmMsg, 3)
  176. }
  177. }()
  178. // 校验用户是否取消关注公众号
  179. userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP)
  180. if e != nil {
  181. // 用户无公众号信息
  182. if e == utils.ErrNoRow {
  183. return
  184. }
  185. err = errors.New("获取用户Record信息失败, Err:" + e.Error())
  186. return
  187. }
  188. // 取消关注则不推送
  189. if userRecord.Subscribe == 0 {
  190. return
  191. }
  192. openIdList := make([]*OpenIdList, 0)
  193. openIdList = append(openIdList, &OpenIdList{
  194. OpenId: userRecord.OpenID,
  195. UserId: userId,
  196. })
  197. sendMap := make(map[string]interface{})
  198. sendData := make(map[string]interface{})
  199. first := "您好,您的提问已被回复"
  200. keyword1 := questionTitle
  201. keyword2 := "待查看"
  202. remark := "请点击详情查看回复"
  203. sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
  204. sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
  205. sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
  206. sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
  207. sendMap["template_id"] = TemplateIdWithCommunityQuestion
  208. sendMap["data"] = sendData
  209. wxAppPath := fmt.Sprintf("pages-question/answerDetail?id=%d", questionId)
  210. if global.CONFIG.Serve.RunMode == "debug" {
  211. // 仅测试环境测试用
  212. wxAppPath = "pages-report/reportDetail?reportId=3800"
  213. }
  214. if wxAppPath != "" {
  215. sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath}
  216. }
  217. err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
  218. return
  219. }
  220. // SendVoiceBroadcastWxMsg 推送研报小程序模板消息-语音播报
  221. func SendVoiceBroadcastWxMsg(broadcastId int, sectionName, broadcastName string) (err error) {
  222. var errMsg string
  223. defer func() {
  224. if err != nil {
  225. alarmMsg := fmt.Sprintf("SendVoiceBroadcastWxMsg-推送语音播报模版消息失败; broadcastId: %d; Err: %s; Msg: %s", broadcastId, err.Error(), errMsg)
  226. go alarm_msg.SendAlarmMsg(alarmMsg, 3)
  227. }
  228. }()
  229. List, err := wx_user.GetOpenIdList()
  230. if err != nil {
  231. return
  232. }
  233. openIdList := make([]*OpenIdList, 0)
  234. for _, item := range List {
  235. openIdList = append(openIdList, &OpenIdList{
  236. OpenId: item.OpenID,
  237. UserId: item.UserID,
  238. })
  239. }
  240. //openIdList = append(openIdList, &OpenIdList{
  241. // OpenId: "oN0jD1cuiBLxV1IRpu74_oHnoOjk",
  242. // UserId: 52709,
  243. // })
  244. sendMap := make(map[string]interface{})
  245. sendData := make(map[string]interface{})
  246. first := "您好,有新的语音播报待查看"
  247. keyword1 := sectionName + ":" + broadcastName
  248. keyword2 := "待查看"
  249. remark := "请点击详情查看"
  250. sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
  251. sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
  252. sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
  253. sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
  254. sendMap["template_id"] = TemplateIdWithCommunityQuestion
  255. sendMap["data"] = sendData
  256. wxAppPath := fmt.Sprintf("pages/voice/voice?voiceId=%d", broadcastId)
  257. if global.CONFIG.Serve.RunMode == "debug" {
  258. // 仅测试环境测试用
  259. wxAppPath = "pages-report/reportDetail?reportId=3800"
  260. }
  261. if wxAppPath != "" {
  262. sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath}
  263. }
  264. err = SendMultiTemplateMsgNoReturn(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_VOICE_BROADCAST)
  265. return
  266. }
  267. // SendMultiTemplateMsg 推送模板消息至多个用户中间出错不返回
  268. func SendMultiTemplateMsgNoReturn(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) {
  269. ws := GetWxChat()
  270. accessToken, err := ws.GetAccessToken()
  271. if err != nil {
  272. return
  273. }
  274. for _, item := range items {
  275. sendMap["touser"] = item.OpenId
  276. data, e := json.Marshal(sendMap)
  277. if e != nil {
  278. err = e
  279. return
  280. }
  281. ts := &TemplateMsgSendClient{
  282. AccessToken: accessToken,
  283. Data: data,
  284. }
  285. result, e := ts.SendMsg()
  286. if result == nil {
  287. return
  288. }
  289. // 推送消息记录
  290. {
  291. go func(v *OpenIdList) {
  292. sendStatus := 1
  293. if e != nil {
  294. sendStatus = 0
  295. }
  296. resultJson, _ := json.Marshal(result)
  297. _ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson))
  298. }(item)
  299. }
  300. if e != nil {
  301. err = e
  302. }
  303. }
  304. return
  305. }
  306. // SendQuestionToAdmin 推送研报小程序模板消息-用户提问时,通知到管理员
  307. func SendQuestionToAdmin(questionId, userId int, questionTitle string) (err error) {
  308. if userId == 0 {
  309. return
  310. }
  311. var errMsg string
  312. defer func() {
  313. if err != nil {
  314. alarmMsg := fmt.Sprintf("SendQuestionToAdmin-推送研报小程序模板消息-用户提问时,通知到管理员; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg)
  315. go alarm_msg.SendAlarmMsg(alarmMsg, 3)
  316. }
  317. }()
  318. // 校验用户是否取消关注公众号
  319. userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP)
  320. if e != nil {
  321. // 用户无公众号信息
  322. if e == utils.ErrNoRow {
  323. return
  324. }
  325. err = errors.New("获取用户Record信息失败, Err:" + e.Error())
  326. return
  327. }
  328. // 取消关注则不推送
  329. if userRecord.Subscribe == 0 {
  330. return
  331. }
  332. openIdList := make([]*OpenIdList, 0)
  333. openIdList = append(openIdList, &OpenIdList{
  334. OpenId: userRecord.OpenID,
  335. UserId: userId,
  336. })
  337. sendMap := make(map[string]interface{})
  338. sendData := make(map[string]interface{})
  339. first := "您好,有新的提问待分配"
  340. keyword1 := questionTitle
  341. keyword2 := "待查看"
  342. remark := "请点击详情查看回复"
  343. sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
  344. sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
  345. sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
  346. sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
  347. sendMap["template_id"] = TemplateIdWithCommunityQuestion
  348. sendMap["data"] = sendData
  349. wxAppPath := fmt.Sprintf("/pages-question/detail/index?type=question&id=%d", questionId)
  350. if global.CONFIG.Serve.RunMode == "debug" {
  351. // 仅测试环境测试用
  352. wxAppPath = "pages-approve/seal/list"
  353. }
  354. if wxAppPath != "" {
  355. sendMap["miniprogram"] = map[string]interface{}{"appid": WxMobileCrmAppId, "pagepath": wxAppPath}
  356. }
  357. err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
  358. return
  359. }
  360. // SendQuestionCommentToAdmin 推送研报小程序模板消息-用户评论问答时,通知到管理员
  361. func SendQuestionCommentToAdmin(commentId, userId int, commentContent string) (err error) {
  362. if userId == 0 {
  363. return
  364. }
  365. var errMsg string
  366. defer func() {
  367. if err != nil {
  368. alarmMsg := fmt.Sprintf("SendQuestionCommentToAdmin-推送研报小程序模板消息-用户评论问答时,通知到管理员; CommentId: %d; Err: %s; Msg: %s", commentId, err.Error(), errMsg)
  369. go alarm_msg.SendAlarmMsg(alarmMsg, 3)
  370. }
  371. }()
  372. // 校验用户是否取消关注公众号
  373. userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP)
  374. if e != nil {
  375. // 用户无公众号信息
  376. if e == utils.ErrNoRow {
  377. return
  378. }
  379. err = errors.New("获取用户Record信息失败, Err:" + e.Error())
  380. return
  381. }
  382. // 取消关注则不推送
  383. if userRecord.Subscribe == 0 {
  384. return
  385. }
  386. openIdList := make([]*OpenIdList, 0)
  387. openIdList = append(openIdList, &OpenIdList{
  388. OpenId: userRecord.OpenID,
  389. UserId: userId,
  390. })
  391. sendMap := make(map[string]interface{})
  392. sendData := make(map[string]interface{})
  393. first := "您好,有新的评论待查看"
  394. keyword1 := commentContent
  395. keyword2 := "待查看"
  396. remark := "请点击详情查看回复"
  397. sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
  398. sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
  399. sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
  400. sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
  401. sendMap["template_id"] = TemplateIdWithCommunityQuestion
  402. sendMap["data"] = sendData
  403. wxAppPath := fmt.Sprintf("/pages-question/detail/index?type=comment&id=%d", commentId)
  404. if global.CONFIG.Serve.RunMode == "debug" {
  405. // 仅测试环境测试用
  406. wxAppPath = "pages-approve/seal/list"
  407. }
  408. if wxAppPath != "" {
  409. sendMap["miniprogram"] = map[string]interface{}{"appid": WxMobileCrmAppId, "pagepath": wxAppPath}
  410. }
  411. err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
  412. return
  413. }