浏览代码

问答社区-推送回复模板消息

hsun 2 年之前
父节点
当前提交
35fd3615a1

+ 2 - 1
controller/community/question.go

@@ -66,7 +66,8 @@ func QuestionDetail(c *gin.Context) {
 		response.Fail("参数有误", c)
 		return
 	}
-	item, err := community.GetQuestionDetail(req.QuestionId)
+	userinfo := user.GetInfoByClaims(c)
+	item, err := community.GetQuestionDetail(req.QuestionId, userinfo)
 	if err != nil {
 		response.FailMsg("获取失败", "QuestionDetail ErrMsg:"+err.Error(), c)
 		return

+ 2 - 1
models/response/community.go

@@ -2,7 +2,7 @@ package response
 
 type CommunityQuestionList struct {
 	QuestionList   []*CommunityQuestionItem
-	PermissionInfo PermissionCheckInfo
+	//PermissionInfo PermissionCheckInfo
 }
 
 type CommunityQuestionItem struct {
@@ -19,6 +19,7 @@ type CommunityQuestionItem struct {
 	ReplyTime           string                        `json:"reply_time"`
 	IsTop               int                           `json:"is_top"`
 	AuthOk              bool                          `json:"auth_ok" description:"是否有权限"`
+	PermissionInfo      PermissionCheckInfo           `json:"permission_info"`
 	AudioList           []*CommunityQuestionAudioItem `json:"audio_list"`
 }
 

+ 45 - 0
models/tables/user_template_record/entity.go

@@ -0,0 +1,45 @@
+package user_template_record
+
+// UserTemplateRecord [...]
+type UserTemplateRecord struct {
+	ID         int    `gorm:"primaryKey;column:id;type:int(11);not null" json:"-"`
+	UserID     int    `gorm:"column:user_id;type:int(11);default:0" json:"userId"`
+	OpenID     string `gorm:"column:open_id;type:varchar(100);default:''" json:"openId"`
+	Resource   string `gorm:"column:resource;type:varchar(20)" json:"resource"` // 资源:报告id/手机号/活动id
+	SendData   string `gorm:"column:send_data;type:text" json:"sendData"`
+	Result     string `gorm:"column:result;type:text" json:"result"`
+	CreateDate string `gorm:"column:create_date;type:date" json:"createDate"`
+	CreateTime string `gorm:"column:create_time;type:datetime" json:"createTime"`
+	SendStatus int    `gorm:"column:send_status;type:tinyint(4);default:1" json:"sendStatus"` // 1:发送成功,0:发送失败
+	SendType   int    `gorm:"column:send_type;type:tinyint(4);default:1" json:"sendType"`     // 1:报告,2:指标更新提醒,3:审批通知,4:销售领取客户通知,5:活动取消通知,6活动更改时间通知,7:关注的作者发布报告通知,8:发送日报(周报、双周报、月报)模板消息,9:活动预约/报名时间通知
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *UserTemplateRecord) TableName() string {
+	return "user_template_record"
+}
+
+// UserTemplateRecordColumns get sql column name.获取数据库列名
+var UserTemplateRecordColumns = struct {
+	ID         string
+	UserID     string
+	OpenID     string
+	Resource   string
+	SendData   string
+	Result     string
+	CreateDate string
+	CreateTime string
+	SendStatus string
+	SendType   string
+}{
+	ID:         "id",
+	UserID:     "user_id",
+	OpenID:     "open_id",
+	Resource:   "resource",
+	SendData:   "send_data",
+	Result:     "result",
+	CreateDate: "create_date",
+	CreateTime: "create_time",
+	SendStatus: "send_status",
+	SendType:   "send_type",
+}

+ 8 - 0
models/tables/user_template_record/model.go

@@ -0,0 +1,8 @@
+package user_template_record
+
+import "hongze/hongze_yb/global"
+
+func (item *UserTemplateRecord) Create() (err error) {
+	err = global.DEFAULT_MYSQL.Create(item).Error
+	return
+}

+ 18 - 19
services/community/question.go

@@ -9,6 +9,7 @@ import (
 	"hongze/hongze_yb/models/tables/yb_community_question_audio"
 	"hongze/hongze_yb/services/company"
 	"hongze/hongze_yb/services/user"
+	"hongze/hongze_yb/services/wechat"
 	"hongze/hongze_yb/utils"
 	"strconv"
 	"strings"
@@ -40,8 +41,8 @@ func GetQuestionList(pageIndex, pageSize, onlyMine, chartPermissionId, replyStat
 				condition["user_id ="] = userInfo.UserID
 			}
 		} else {
-			// 默认只查看已回复的
-			condition["reply_status ="] = 2
+			// 默认只展示已回复的
+			condition["reply_status ="] = 3
 		}
 	}
 	if chartPermissionId > 0 {
@@ -69,15 +70,11 @@ func GetQuestionList(pageIndex, pageSize, onlyMine, chartPermissionId, replyStat
 		return
 	}
 	// 用户权限
-	_, permissionInfo, permissionIdArr, e := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+	authOk, permissionInfo, _, e := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
 	if e != nil {
 		err = errors.New("获取用户权限失败 Err:" + e.Error())
 		return
 	}
-	itemAuthMap := make(map[int]bool)
-	for _, v := range permissionIdArr {
-		itemAuthMap[v] = true
-	}
 
 	userId := int(userInfo.UserID)
 	resp = new(response.CommunityQuestionList)
@@ -108,7 +105,8 @@ func GetQuestionList(pageIndex, pageSize, onlyMine, chartPermissionId, replyStat
 			IsRead:              v.IsRead,
 			CreateTime:          v.CreateTime.Format(utils.FormatDateTime),
 			ReplyTime:           v.ReplyTime.Format(utils.FormatDateTime),
-			AuthOk:              itemAuthMap[v.ChartPermissionID],
+			AuthOk:              authOk,
+			PermissionInfo:      permissionInfo,
 			AudioList:           audios,
 		}
 		if !isAdmin && item.IsRead == 0 && item.UserId == userId {
@@ -117,12 +115,12 @@ func GetQuestionList(pageIndex, pageSize, onlyMine, chartPermissionId, replyStat
 		respList = append(respList, item)
 	}
 	resp.QuestionList = respList
-	resp.PermissionInfo = permissionInfo
+	//resp.PermissionInfo = permissionInfo
 	return
 }
 
 // GetQuestionDetail 获取问答详情
-func GetQuestionDetail(questionId int) (item *response.CommunityQuestionItem, err error) {
+func GetQuestionDetail(questionId int, userInfo user.UserInfo) (item *response.CommunityQuestionItem, err error) {
 	detail, e := yb_community_question.GetItemById(questionId)
 	if e != nil {
 		err = errors.New("获取问题详情失败 Err:" + e.Error())
@@ -144,6 +142,12 @@ func GetQuestionDetail(questionId int) (item *response.CommunityQuestionItem, er
 		})
 	}
 	replierRank := fmt.Sprintf("弘则%s研究员", detail.ResearchGroupFirstName)
+	// 用户权限
+	authOk, permissionInfo, _, e := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+	if e != nil {
+		err = errors.New("获取用户权限失败 Err:" + e.Error())
+		return
+	}
 	item = &response.CommunityQuestionItem{
 		CommunityQuestionID: detail.CommunityQuestionID,
 		UserId:              detail.UserID,
@@ -151,11 +155,13 @@ func GetQuestionDetail(questionId int) (item *response.CommunityQuestionItem, er
 		ReplierRealName:     detail.ReplierRealName,
 		ReplierRank:         replierRank,
 		ReplierAvatar:       detail.ReplierAvatar,
-		ChartPermissionID:   detail.CommunityQuestionID,
+		ChartPermissionID:   detail.ChartPermissionID,
 		ChartPermissionName: detail.ChartPermissionName,
 		IsRead:              detail.IsRead,
 		CreateTime:          detail.CreateTime.Format(utils.FormatDateTime),
 		ReplyTime:           detail.ReplyTime.Format(utils.FormatDateTime),
+		AuthOk:              authOk,
+		PermissionInfo:      permissionInfo,
 		AudioList:           audios,
 	}
 	return
@@ -216,9 +222,7 @@ func ReplyUserQuestion(replierId, questionId int, audios []*request.ReplyReqAudi
 		return
 	}
 	// 推送回复消息给用户
-	go func() {
-		_ = sendReplyMsg2User()
-	}()
+	go wechat.SendQuestionReplyWxMsg(item.CommunityQuestionID, item.UserID, item.UserOpenid, item.QuestionContent)
 	return
 }
 
@@ -264,8 +268,3 @@ func GetReplyListTotal(replierUserId int) (resp *response.CommunityReplyTotal, e
 	resp.Total = resp.Wait + resp.Replied
 	return
 }
-
-// sendReply2User 推送模板消息给用户
-func sendReplyMsg2User() (err error) {
-	return
-}

+ 216 - 0
services/wechat/template_msg.go

@@ -0,0 +1,216 @@
+package wechat
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/user_template_record"
+	"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, openid, questionTitle string) (err error) {
+	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)
+		}
+	}()
+	initConf()
+
+	openIdList := make([]*OpenIdList, 0)
+	openIdList = append(openIdList, &OpenIdList{
+		OpenId: 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
+
+	// TODO:小程序pagepath待定
+	wxAppPath := ""
+	if wxAppPath != "" {
+		sendMap["miniprogram"] = map[string]interface{}{"appid": WxAppId, "pagepath": wxAppPath}
+	}
+	err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
+	return
+}

+ 6 - 37
services/wechat/wechat.go

@@ -15,24 +15,10 @@ import (
 )
 
 var (
-	WxId                string //微信原始ID
-	WxAppId             string
-	WxAppSecret         string
-	TemplateIdByProduct string //产品运行报告通知-模板ID
-
-	TemplateRedirectUrl                   string //模板消息跳转地址
-	DayReportTemplateRedirectUrl          string //模板消息跳转地址(晨报、周报)
-	TemplateIdByCompanyApply              string //客户申请单审批通知-模板ID
-	TemplateCompanyApplyRedirectUrl       string //审批单模板消息跳转地址
-	TemplateIdByCompanyReceive            string //销售跨部门领取客户通知-模板ID
-	WxMsgTemplateIdActivityChangeApply    string //查研观向活动变更通知-模板ID
-	WxMsgTemplateIdActivityChangeApplyXzs string //查研观向活动变更通知-模板ID(小助手)
-	TemplateIdByProductXzs                string //产品运行报告通知-模板ID(小助手)
-	WxCygxAppId                           string //查研观向小程序APPID
-	WxMsgTemplateIdAskByUser              string //查研观向用户提问新消息推送ID
-	WxCrmAppId                            string //随手办公小程序APPID
-	WxPublicIdXzs                         string //查研观向小助手公众号
-	WxPublicSecretXzs                     string //查研观向小助手公众号
+	WxId                            string //微信原始ID
+	WxAppId                         string
+	WxAppSecret                     string
+	TemplateIdWithCommunityQuestion string // 问答社区回复模板消息ID
 )
 
 func initConf() {
@@ -40,29 +26,12 @@ func initConf() {
 		WxAppId = "wx9b5d7291e581233a"
 		WxAppSecret = "f4d52e34021eee262dce9682b31f8861"
 		WxId = "gh_5dc508325c6f"
-		TemplateIdByProduct = "-YjuPOB7Fqd-S3ilabYa6wvjDY9aXmeEfPN6DCiy-EY"
-		TemplateRedirectUrl = "http://rddpweb.brilliantstart.cn/reportdtl?id="
-		DayReportTemplateRedirectUrl = "http://report.brilliantstart.cn/#/allindex/" //晨报模板跳转url
-		TemplateIdByCompanyApply = "eTalI1LbiT_B0mTaBeDTlfSMITenK8dQIgEB5yqjjvA"
-		TemplateCompanyApplyRedirectUrl = "http://advisoryadmin.brilliantstart.cn/approval/approval/list"
-		//销售跨部门领取客户通知
-		TemplateIdByCompanyReceive = "KAxFvX79f-DHmtC_Jw98elvqxiDPK0xP_b48LUT-Y14"
-		WxMsgTemplateIdActivityChangeApply = "CB7bOl7f3viMG4s1uhRo7WM0Jbx3WvodKuIZ8A_z8fM"
-
-		WxMsgTemplateIdAskByUser = `qfNuops-sKrfIkbA7U97A7gSrX03mUpoEpJksRUdloo`
+		TemplateIdWithCommunityQuestion = "CB7bOl7f3viMG4s1uhRo7WM0Jbx3WvodKuIZ8A_z8fM"
 	} else {
 		WxAppId = "wx4a844c734d8c8e56"
 		WxAppSecret = "26c586e7ccb3c575433f0f37797b3eeb"
 		WxId = "gh_b67e0049fb8c"
-		TemplateIdByProduct = "Cp2wF8gvBtxyWV4DeYuI172oqwyYXVRSm3AyJO42d84"
-		TemplateRedirectUrl = "https://ficc.hzinsights.com/reportdtl?id="
-		DayReportTemplateRedirectUrl = "https://report.hzinsights.com/#/allindex/" //晨报模板跳转url
-		TemplateIdByCompanyApply = "ZKcOfNIWBpwHJxpptufHIK1mp2nIwkT3cxub-35cFqI"
-		TemplateCompanyApplyRedirectUrl = "https://ficc.hzinsights.com/approval/approval/list"
-		WxMsgTemplateIdActivityChangeApply = "dYg6iHooRq74PyCXmw_Ns7qdJZmbtLoKS2p2FKeaXl0"
-		//销售跨部门领取客户通知
-		TemplateIdByCompanyReceive = "A5fV-XWBcu-LIj_W-tBiOJ-D39a9WDd9GOB0WGbpoBg"
-		TemplateCompanyApplyRedirectUrl = "https://ficc.hzinsights.com/approval/approval/list"
+		TemplateIdWithCommunityQuestion = "dYg6iHooRq74PyCXmw_Ns7qdJZmbtLoKS2p2FKeaXl0"
 	}
 }
 

+ 21 - 7
utils/constants.go

@@ -23,7 +23,7 @@ const (
 )
 
 const (
-	APPNAME = "弘则研报"
+	APPNAME          = "弘则研报"
 	EmailSendToUsers = "317699326@qq.com;984198890@qq.com;hsun@hzinsights.com;xyxie@hzinsights.com"
 )
 
@@ -73,7 +73,7 @@ const (
 
 const (
 	HZ_CHART_LIB_DETAIL = "HZ_CHART_LIB_DETAIL_"
-	HONGZEYB_ = "hongze_yb:"
+	HONGZEYB_           = "hongze_yb:"
 )
 
 //var (
@@ -87,9 +87,9 @@ const (
 
 //数据刷新频率
 const (
-	DATA_REFRESH        = 7 //7个单位,日/周/月/季度/年
+	DATA_REFRESH             = 7 //7个单位,日/周/月/季度/年
 	DATA_START_REFRESH_LIMIT = 7 //7个单位,日/周/月/季度/年
-	DATA_END_DATE_LIMIT = 4 //数据结束日期为,当前日期,加上4年时间
+	DATA_END_DATE_LIMIT      = 4 //数据结束日期为,当前日期,加上4年时间
 )
 
 const (
@@ -99,7 +99,6 @@ const (
 	EDB_DATA_LIMIT = 10
 )
 
-
 var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "黑色产业", "有色产业", "市场策略"}
 
 //缓存key
@@ -109,7 +108,7 @@ const (
 
 // es相关
 const (
-	ES_INDEX_RDDP_REPORT = "research_report_v1"                //报告
+	ES_INDEX_RDDP_REPORT = "research_report_v1" //报告
 )
 
 //EDB_LIB
@@ -121,4 +120,19 @@ var (
 const ALIYUN_YBIMG_HOST = "https://hzstatic.hzinsights.com/static/yb_wx/"
 const HZ_DEFAULT_AVATAR = "https://hzstatic.hzinsights.com/static/yb_wx/hz_default_avatar.png"
 
-const HZPHONE = "057187186319"    //弘则电话
+const HZPHONE = "057187186319" //弘则电话
+
+//模板消息推送类型
+const (
+	TEMPLATE_MSG_REPORT               = iota + 1 //日度点评报告推送
+	TEMPLATE_MSG_INDEX                           //指标更新
+	TEMPLATE_MSG_APPLY                           //审批通知
+	TEMPLATE_MSG_RECEIVE                         //销售领取客户通知
+	TEMPLATE_MSG_CYGX_ACTIVITY_CACLE             //查研观向活动取消通知
+	TEMPLATE_MSG_CYGX_ACTIVITY_UPDATE            //查研观向活动更改时间通知
+	TEMPLATE_MSG_CYGX_ARTICLE                    //关注的作者发布报告通知
+	TEMPLATE_MSG_CYGX_DAY_REPORT                 //发送日报(周报、双周报、月报)
+	TEMPLATE_MSG_ACTIVITY_APPOINTMENT            //活动预约/报名时间通知
+	_
+	TEMPLATE_MSG_YB_COMMUNITY_QUESTION // 研报小程序-问答社区通知
+)