package wechat import ( "bytes" "context" "encoding/json" "errors" "fmt" "hongze/hongze_yb/global" "hongze/hongze_yb/models/tables/user_record" "hongze/hongze_yb/models/tables/user_template_record" "hongze/hongze_yb/models/tables/wx_user" "hongze/hongze_yb/services/alarm_msg" "hongze/hongze_yb/utils" "io/ioutil" "net/http" "time" ) var ( TemplateMsgSendUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s" TemplateMsgClearQuotaUrl = "https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s" ) type TemplateMsgSendClient struct { AccessToken string Data []byte } type SendTemplateResponse struct { Errcode int `json:"errcode"` Errmsg string `json:"errmsg"` MsgID int `json:"msgid"` } type ClearQuotaResponse struct { Errcode int `json:"errcode"` Errmsg string `json:"errmsg"` } type OpenIdList struct { OpenId string UserId int } // TemplateMsgSendClient.SendMsg 推送消息 func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err error) { // 请求接口 sendUrl := fmt.Sprintf(TemplateMsgSendUrl, c.AccessToken) client := http.Client{} resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(c.Data)) if err != nil { return } defer func() { _ = resp.Body.Close() }() body, _ := ioutil.ReadAll(resp.Body) if err = json.Unmarshal(body, &sendRes); err != nil { return } // 模板消息发送超过当日10万次限制错误处理 if sendRes.Errcode == 45009 { // 发送提示邮件 go alarm_msg.SendAlarmMsg("模板消息发送超过当日10万次限制, SendTemplateResponse: "+string(body), 3) // 清理限制 clearRes, e := c.ClearQuota() if e != nil { err = e return } if clearRes.Errcode != 0 { clearJson, _ := json.Marshal(clearRes) go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3) return } // 发送成功邮件 go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1) // 重新推送 go func() { reSend, e := c.SendMsg() if e != nil { reSendJson, _ := json.Marshal(reSend) alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3) } }() } if sendRes.Errcode != 0 { err = errors.New("推送模板消息失败, SendTemplateResponse: " + string(body)) } return } // TemplateMsgSendClient.ClearQuota 清除发送超过当日10万次限制 func (c *TemplateMsgSendClient) ClearQuota() (result *ClearQuotaResponse, err error) { key := "CACHE_SendTemplateMsg_ERR" exists, _ := global.Redis.Exists(context.TODO(), key).Result() if exists == 1 { return } _ = global.Redis.SetEX(context.TODO(), key, 1, 6*time.Minute) sendUrl := fmt.Sprintf(TemplateMsgClearQuotaUrl, c.AccessToken) client := http.Client{} clearData := make(map[string]interface{}) clearData["appid"] = WxAppId clearJson, _ := json.Marshal(clearData) resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(clearJson)) if err != nil { return } defer func() { _ = resp.Body.Close() }() clearBody, err := ioutil.ReadAll(resp.Body) err = json.Unmarshal(clearBody, &result) return } // AddUserTemplateRecord 新增模板消息推送记录 func AddUserTemplateRecord(userId, sendStatus, sendType int, openid, resource, sendData, result string) (err error) { item := &user_template_record.UserTemplateRecord{ UserID: userId, OpenID: openid, Resource: resource, SendData: sendData, Result: result, CreateDate: time.Now().Format(utils.FormatDate), CreateTime: time.Now().Format(utils.FormatDateTime), SendStatus: sendStatus, SendType: sendType, } err = item.Create() return } // SendMultiTemplateMsg 推送模板消息至多个用户 func SendMultiTemplateMsg(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) { ws := GetWxChat() accessToken, err := ws.GetAccessToken() if err != nil { return } for _, item := range items { sendMap["touser"] = item.OpenId data, e := json.Marshal(sendMap) if e != nil { err = e return } ts := &TemplateMsgSendClient{ AccessToken: accessToken, Data: data, } result, e := ts.SendMsg() if result == nil { return } // 推送消息记录 { go func(v *OpenIdList) { sendStatus := 1 if e != nil { sendStatus = 0 } resultJson, _ := json.Marshal(result) _ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson)) }(item) } if e != nil { err = e return } } return } // SendQuestionReplyWxMsg 推送研报小程序模板消息-问答社区回复 func SendQuestionReplyWxMsg(questionId, userId int, questionTitle string) (err error) { if userId == 0 { return } var errMsg string defer func() { if err != nil { alarmMsg := fmt.Sprintf("SendQuestionReplyWxMsg-推送问答社区回复模版消息失败; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg) go alarm_msg.SendAlarmMsg(alarmMsg, 3) } }() // 校验用户是否取消关注公众号 userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP) if e != nil { // 用户无公众号信息 if e == utils.ErrNoRow { return } err = errors.New("获取用户Record信息失败, Err:" + e.Error()) return } // 取消关注则不推送 if userRecord.Subscribe == 0 { return } openIdList := make([]*OpenIdList, 0) openIdList = append(openIdList, &OpenIdList{ OpenId: userRecord.OpenID, UserId: userId, }) sendMap := make(map[string]interface{}) sendData := make(map[string]interface{}) first := "您好,您的提问已被回复" keyword1 := questionTitle keyword2 := "待查看" remark := "请点击详情查看回复" sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"} sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"} sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"} sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"} sendMap["template_id"] = TemplateIdWithCommunityQuestion sendMap["data"] = sendData wxAppPath := fmt.Sprintf("pages-question/answerDetail?id=%d", questionId) if global.CONFIG.Serve.RunMode == "debug" { // 仅测试环境测试用 wxAppPath = "pages-report/reportDetail?reportId=3800" } if wxAppPath != "" { sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath} } err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION) return } // SendVoiceBroadcastWxMsg 推送研报小程序模板消息-语音播报 func SendVoiceBroadcastWxMsg(broadcastId int, sectionName, broadcastName string) (err error) { var errMsg string defer func() { if err != nil { alarmMsg := fmt.Sprintf("SendVoiceBroadcastWxMsg-推送语音播报模版消息失败; broadcastId: %d; Err: %s; Msg: %s", broadcastId, err.Error(), errMsg) go alarm_msg.SendAlarmMsg(alarmMsg, 3) } }() List, err := wx_user.GetOpenIdList() if err != nil { return } openIdList := make([]*OpenIdList, 0) for _, item := range List { openIdList = append(openIdList, &OpenIdList{ OpenId: item.OpenID, UserId: item.UserID, }) } //openIdList = append(openIdList, &OpenIdList{ // OpenId: "oN0jD1cuiBLxV1IRpu74_oHnoOjk", // UserId: 52709, // }) sendMap := make(map[string]interface{}) sendData := make(map[string]interface{}) first := "您好,有新的语音播报待查看" keyword1 := sectionName + ":" + broadcastName keyword2 := "待查看" remark := "请点击详情查看" sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"} sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"} sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"} sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"} sendMap["template_id"] = TemplateIdWithCommunityQuestion sendMap["data"] = sendData wxAppPath := fmt.Sprintf("pages-voice/voiceDetail?voiceId=%d", broadcastId) if global.CONFIG.Serve.RunMode == "debug" { // 仅测试环境测试用 wxAppPath = "pages-report/reportDetail?reportId=3800" } if wxAppPath != "" { sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath} } err = SendMultiTemplateMsgNoReturn(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_VOICE_BROADCAST) return } // SendMultiTemplateMsg 推送模板消息至多个用户中间出错不返回 func SendMultiTemplateMsgNoReturn(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) { ws := GetWxChat() accessToken, err := ws.GetAccessToken() if err != nil { return } for _, item := range items { sendMap["touser"] = item.OpenId data, e := json.Marshal(sendMap) if e != nil { err = e return } ts := &TemplateMsgSendClient{ AccessToken: accessToken, Data: data, } result, e := ts.SendMsg() if result == nil { return } // 推送消息记录 { go func(v *OpenIdList) { sendStatus := 1 if e != nil { sendStatus = 0 } resultJson, _ := json.Marshal(result) _ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson)) }(item) } if e != nil { err = e } } return }