package wechat import ( "bytes" "context" "encoding/json" "errors" "fmt" "hongze/hongze_yb/controller/response" "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/services/wx_app" "hongze/hongze_yb/utils" "io/ioutil" "net/http" "strings" "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 } type SendWxTemplate struct { WxAppId string `description:"公众号appId"` First string `description:"模板消息first字段"` Keyword1 string `description:"模板消息keyword1字段"` Keyword2 string `description:"模板消息keyword2字段"` Keyword3 string `description:"模板消息keyword3字段"` Keyword4 string `description:"模板消息keyword4字段"` Remark string `description:"模板消息remark字段"` TemplateId string `description:"模板id"` RedirectUrl string `description:"跳转地址"` RedirectTarget int `description:"小程序跳转目标:1:弘则研报小程序,2:随手办公小程序"` Resource string `description:"资源唯一标识"` SendType int `description:"发送的消息类型:1:报告,2:指标更新提醒,3:审批通知,4:销售领取客户通知,5:活动取消通知,6活动更改时间通知,7:关注的作者发布报告通知,8:发送日报(周报、双周报、月报)模板消息,9:活动预约/报名时间通知"` OpenIdArr []string `description:"消息接收者openid"` } // 推送模板消息 func SendTemplateMsg(sendInfo *SendWxTemplate) (err error) { postData, err := json.Marshal(sendInfo) if err != nil { alarm_msg.SendAlarmMsg("SendTemplateMsg json.Marshal Err:"+err.Error(), 1) return err } body := ioutil.NopCloser(strings.NewReader(string(postData))) client := &http.Client{} req, err := http.NewRequest("POST", wx_app.SendWxTemplateMsgUrl, body) if err != nil { alarm_msg.SendAlarmMsg("SendTemplateMsg http.NewRequest Err:"+err.Error(), 1) return err } contentType := "application/json;charset=utf-8" req.Header.Set("Content-Type", contentType) req.Header.Set("Authorization", utils.SendTemplateMsgAuthorization) resp, err := client.Do(req) if err != nil { fmt.Println("http client.Do Err:" + err.Error()) return err } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return err } result := new(response.BaseResponse) err = json.Unmarshal(b, &result) if err != nil { return err } if result.Ret != 200 { err = errors.New(string(b)) return err } return } // 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) openIdArr := make([]string, len(openIdList)) for i, v := range openIdList { openIdArr[i] = v.OpenId } // 获取小程序配置 appConf, err := wx_app.GetWxAppConf() if err != nil { return } sendInfo := new(SendWxTemplate) sendInfo.WxAppId = WxAppId sendInfo.First = first sendInfo.Keyword1 = keyword1 sendInfo.Keyword2 = keyword2 sendInfo.Remark = remark sendInfo.RedirectUrl = wxAppPath sendInfo.TemplateId = TemplateIdWithCommunityQuestion sendInfo.RedirectTarget = appConf.MsgRedirectTarget sendInfo.Resource = wxAppPath sendInfo.SendType = utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION sendInfo.OpenIdArr = openIdArr err = SendTemplateMsg(sendInfo) return } // SendVoiceBroadcastWxMsg 推送研报小程序模板消息-语音播报 func SendVoiceBroadcastWxMsg(broadcastId, varietyId 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) } }() openIdArr, err := wx_user.GetOpenIdArrByVarietyTag(varietyId) //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 := "待查看" keyword1 := broadcastName keyword2 := fmt.Sprintf("【%s】 待查看", sectionName) 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) //openIdArr := make([]string, len(openIdList)) //for i, v := range openIdList { // openIdArr[i] = v.OpenId //} // 获取小程序配置 appConf, err := wx_app.GetWxAppConf() if err != nil { return } //openIdArr := make([]string, len(openIdList)) //for i, v := range openIdList { // openIdArr[i] = v.OpenId //} sendInfo := new(SendWxTemplate) sendInfo.WxAppId = WxAppId sendInfo.First = first sendInfo.Keyword1 = keyword1 sendInfo.Keyword2 = keyword2 sendInfo.Remark = remark sendInfo.RedirectUrl = wxAppPath sendInfo.TemplateId = TemplateIdWithCommunityQuestion sendInfo.RedirectTarget = appConf.MsgRedirectTarget sendInfo.Resource = wxAppPath sendInfo.SendType = utils.TEMPLATE_MSG_YB_VOICE_BROADCAST sendInfo.OpenIdArr = openIdArr err = SendTemplateMsg(sendInfo) 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 } // SendQuestionToAdmin 推送研报小程序模板消息-用户提问时,通知到管理员 func SendQuestionToAdmin(questionId, adminId int, adminOpenId string, questionTitle string) (err error) { if adminId == 0 || adminOpenId == "" { return } var errMsg string defer func() { if err != nil { alarmMsg := fmt.Sprintf("SendQuestionToAdmin-推送研报小程序模板消息-用户提问时,通知到管理员; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg) go alarm_msg.SendAlarmMsg(alarmMsg, 3) } }() openIdList := make([]*OpenIdList, 0) openIdList = append(openIdList, &OpenIdList{ OpenId: adminOpenId, UserId: 0, }) //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/detail/index?type=question&id=%d", questionId) if global.CONFIG.Serve.RunMode == "debug" { // 仅测试环境测试用 wxAppPath = "pages-approve/seal/list" } //if wxAppPath != "" { // sendMap["miniprogram"] = map[string]interface{}{"appid": WxMobileCrmAppId, "pagepath": wxAppPath} //} //err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION) openIdArr := make([]string, len(openIdList)) for i, v := range openIdList { openIdArr[i] = v.OpenId } sendInfo := new(SendWxTemplate) sendInfo.WxAppId = AdminWxAppId sendInfo.First = first sendInfo.Keyword1 = keyword1 sendInfo.Keyword2 = keyword2 sendInfo.Remark = remark sendInfo.RedirectUrl = wxAppPath sendInfo.TemplateId = TemplateIdWithCommunityQuestionNotifyAdmin sendInfo.RedirectTarget = 2 sendInfo.Resource = wxAppPath sendInfo.SendType = utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION sendInfo.OpenIdArr = openIdArr err = SendTemplateMsg(sendInfo) return } // SendQuestionCommentToAdmin 推送研报小程序模板消息-用户评论问答时,通知到管理员 func SendQuestionCommentToAdmin(commentId, adminId int, adminOpenId string, commentContent string) (err error) { if adminId == 0 || adminOpenId == "" { return } var errMsg string defer func() { if err != nil { alarmMsg := fmt.Sprintf("SendQuestionCommentToAdmin-推送研报小程序模板消息-用户评论问答时,通知到管理员; CommentId: %d; Err: %s; Msg: %s", commentId, err.Error(), errMsg) go alarm_msg.SendAlarmMsg(alarmMsg, 3) } }() openIdList := make([]*OpenIdList, 0) openIdList = append(openIdList, &OpenIdList{ OpenId: adminOpenId, UserId: 0, }) //sendMap := make(map[string]interface{}) //sendData := make(map[string]interface{}) first := "您好,有新的评论待查看" keyword1 := commentContent 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/detail/index?type=comment&id=%d", commentId) if global.CONFIG.Serve.RunMode == "debug" { // 仅测试环境测试用 wxAppPath = "pages-approve/seal/list" } //if wxAppPath != "" { // sendMap["miniprogram"] = map[string]interface{}{"appid": WxMobileCrmAppId, "pagepath": wxAppPath} //} //err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION) openIdArr := make([]string, len(openIdList)) for i, v := range openIdList { openIdArr[i] = v.OpenId } sendInfo := new(SendWxTemplate) sendInfo.WxAppId = AdminWxAppId sendInfo.First = first sendInfo.Keyword1 = keyword1 sendInfo.Keyword2 = keyword2 sendInfo.Remark = remark sendInfo.RedirectUrl = wxAppPath sendInfo.TemplateId = TemplateIdWithCommunityQuestionNotifyAdmin sendInfo.RedirectTarget = 2 sendInfo.Resource = wxAppPath sendInfo.SendType = utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION sendInfo.OpenIdArr = openIdArr err = SendTemplateMsg(sendInfo) return } // SendQuestionToResearcher 推送研报小程序模板消息-用户提问时,通知到研究员 func SendQuestionToResearcher(questionId int, adminOpenId, questionTitle, remark string) (err error) { var errMsg string defer func() { if err != nil { alarmMsg := fmt.Sprintf("SendQuestionToAdmin-推送研报小程序模板消息-用户提问时,通知到研究员; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg) go alarm_msg.SendAlarmMsg(alarmMsg, 3) } }() if adminOpenId == "" { err = errors.New("研究员openid为空") errMsg = "研究员openid为空" return } openIdList := make([]*OpenIdList, 0) openIdList = append(openIdList, &OpenIdList{ OpenId: adminOpenId, UserId: 0, }) first := "您好,有新的提问待回复" keyword1 := questionTitle keyword2 := "待回复" if remark == "" { remark = "请点击详情尽快处理" keyword2 = remark } wxAppPath := fmt.Sprintf("pages-question/answerDetail?id=%d", questionId) if global.CONFIG.Serve.RunMode == "debug" { // 仅测试环境测试用 wxAppPath = "pages-report/reportDetail?reportId=3800" } openIdArr := make([]string, len(openIdList)) for i, v := range openIdList { openIdArr[i] = v.OpenId } sendInfo := new(SendWxTemplate) sendInfo.WxAppId = AdminWxAppId sendInfo.First = first sendInfo.Keyword1 = keyword1 sendInfo.Keyword2 = keyword2 sendInfo.Remark = remark sendInfo.RedirectUrl = wxAppPath sendInfo.TemplateId = TemplateIdWithCommunityQuestionNotifyAdmin sendInfo.RedirectTarget = 1 sendInfo.Resource = wxAppPath sendInfo.SendType = utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION sendInfo.OpenIdArr = openIdArr err = SendTemplateMsg(sendInfo) return }