package controller import ( "encoding/json" "errors" "fmt" "github.com/gin-gonic/gin" "github.com/silenceper/wechat/v2" offConfigMini "github.com/silenceper/wechat/v2/miniprogram/config" miniMsg "github.com/silenceper/wechat/v2/miniprogram/message" offConfig "github.com/silenceper/wechat/v2/officialaccount/config" "github.com/silenceper/wechat/v2/officialaccount/message" "github.com/silenceper/wechat/v2/util" workConfig "github.com/silenceper/wechat/v2/work/config" "github.com/tidwall/gjson" "kefu/common" "kefu/lib" "kefu/lib/wechat_kf_sdk" "kefu/models" "kefu/tools" "kefu/types" "kefu/ws" "log" "regexp" "strconv" "strings" "time" ) var IP_SERVER_URL = "https://gofly.sopans.com/uc/v2/ipAuth" var memory = tools.NewMemory() // 处理错误 func HandleError(c *gin.Context, code uint, msg string, err error) { c.JSON(200, gin.H{ "code": code, "msg": msg, "result": err.Error(), }) } /* * 处理分页页码 */ func HandlePagePageSize(c *gin.Context) (uint, uint) { var page uint pagesize := common.VisitorPageSize myPage, _ := strconv.Atoi(c.Query("page")) if myPage != 0 { page = uint(myPage) } myPagesize, _ := strconv.Atoi(c.Query("pagesize")) if myPagesize != 0 { pagesize = uint(myPagesize) } return page, pagesize } // 发送访客微信消息 func SendWechatVisitorMessage(visitorId, content, entId string) bool { visitorIdArr := strings.Split(visitorId, "|") if len(visitorIdArr) < 3 || visitorIdArr[0] != "wx" { return false } content = tools.TrimHtml(content) return SendWechatMesage(visitorIdArr[2], content, entId) } // 客服发送给小程序访客消息 func SendMiniVisitorMessage(visitorId, content, entId string) bool { content = tools.TrimHtml(content) visitorIdArr := strings.Split(visitorId, "|") if len(visitorIdArr) < 3 || visitorIdArr[0] != "mini" { return false } toUser := visitorIdArr[2] configs := models.GetEntConfigsMap(entId, "WechatMiniToken", "WechatMiniAppId", "WechatMiniAppSecret") cfg := &offConfigMini.Config{ AppID: configs["WechatMiniAppId"], AppSecret: configs["WechatMiniAppSecret"], //EncodingAESKey: "xxxx", Cache: memory, } wc := wechat.NewWechat() mini := wc.GetMiniProgram(cfg).GetCustomerMessage() if imgUrl := ParseImgMessage(content); imgUrl != "" { accessToken, _ := mini.GetAccessToken() uri := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", accessToken, "image") response, err := util.PostFile("media", common.UploadDirPath+imgUrl, uri) mediaId := gjson.Get(string(response), "media_id").String() if err != nil || mediaId == "" { log.Println("发送小程序微信客服图片消息-上传临时素材错误:", toUser, imgUrl, err) return false } msg := miniMsg.NewCustomerImgMessage(toUser, mediaId) mini.Send(msg) return true } msg := miniMsg.NewCustomerTextMessage(toUser, content) mini.Send(msg) return true } // 客服发送给微信客服用户的消息 func SendWeworkKfVisitorMessage(visitor models.Visitor, content string) bool { visitorIdArr := strings.Split(visitor.VisitorId, "|") if len(visitorIdArr) < 3 || visitorIdArr[0] != "wxkf" { return false } content = tools.TrimHtml(content) configs := models.GetEntConfigsMap(visitor.EntId, "QdrantAIStatus", "kefuWeworkCorpid", "kefuWeworkSecret", "kefuWeworkToken", "kefuWeworkEncodingAESKey") kefuWework := wechat_kf_sdk.NewKefuWework(configs["kefuWeworkCorpid"], configs["kefuWeworkSecret"], configs["kefuWeworkToken"], configs["kefuWeworkEncodingAESKey"]) var err error if imgUrl := ParseImgMessage(content); imgUrl != "" { err = kefuWework.SendImagesMsg(visitor.ToId, visitorIdArr[2], common.UploadDirPath+imgUrl) } else if voiceUrl := ParseVoiceMessage(content); voiceUrl != "" { err = kefuWework.SendVoiceMsg(visitor.ToId, visitorIdArr[2], common.UploadDirPath+voiceUrl) } else { log.Println("企业微信客服发送消息:", visitor.ToId, visitorIdArr[2], content) err = kefuWework.SendTextMsg(visitor.ToId, visitorIdArr[2], content) } if err != nil { log.Println("企业微信客服发送消息失败:", err) } return true } // 发送客服微信消息 func SendWechatKefuNotice(kefuName, content, entId string) bool { oauth := models.FindOauthById(kefuName) if oauth.OauthId == "" { return false } return SendWechatMesage(oauth.OauthId, content, entId) } // 发送新访客提醒模板消息 //func SendWechatVisitorTemplate(kefuName, visitorName, content, entId string) bool { // oauths := models.FindOauthsById(kefuName) // if len(oauths) == 0 { // return false // } // wechatConfig, _ := lib.NewWechatLib(entId) // if wechatConfig.WechatMessageTemplateId == "" { // systemBussinesId := models.FindConfig("SystemBussinesId") // if systemBussinesId == "" { // return false // } // wechatConfig, _ = lib.NewWechatLib(systemBussinesId) // if wechatConfig.WechatMessageTemplateId == "" { // return false // } // } // msgData := make(map[string]*message.TemplateDataItem) // msgData["keyword1"] = &message.TemplateDataItem{ // Value: visitorName, // Color: "", // } // msgData["keyword2"] = &message.TemplateDataItem{ // Value: time.Now().Format("2006-01-02 15:04:05"), // Color: "", // } // msgData["keyword3"] = &message.TemplateDataItem{ // Value: content, // Color: "", // } // for _, oauth := range oauths { // msg := &message.TemplateMessage{ // ToUser: oauth.OauthId, // Data: msgData, // TemplateID: wechatConfig.WechatVisitorTemplateId, // URL: wechatConfig.WechatHost + "/wechatKefuTransfer?ent_id=" + entId + "&kefu_name=" + kefuName, // } // SendWechatTemplate(wechatConfig, msg) // } // return true //} // 发送访客新消息提醒模板消息 func SendWechatVisitorMessageTemplate(kefuName, visitorName, visitorId, content, entId string) bool { oauths := models.FindOauthsById(kefuName) if len(oauths) == 0 { return false } openids := make([]string, 0) for _, oauth := range oauths { openids = append(openids, oauth.OauthId) } systemBussinesId := models.FindConfig("SystemBussinesId") configs := models.GetEntConfigsMap(entId, "WechatAppId", "WechatHost", "WechatAppSecret", "WechatAppToken", "WechatMessageTemplateId", "WechatMessageTemplateColumn") for _, v := range configs { if v == "" { configs = models.GetEntConfigsMap(systemBussinesId, "WechatAppId", "WechatHost", "WechatAppSecret", "WechatAppToken", "WechatMessageTemplateId", "WechatMessageTemplateColumn") break } } for _, v := range configs { if v == "" { return false } } templateColums := strings.Split(configs["WechatMessageTemplateColumn"], "|") if len(templateColums) < 3 { return false } offical := lib.NewWechatOffical(configs["WechatAppId"], configs["WechatAppSecret"], configs["WechatAppToken"], memory) messages := []map[string]string{ {"key": templateColums[0], "value": visitorName}, {"key": templateColums[1], "value": time.Now().Format("2006-01-02 15:04:05")}, {"key": templateColums[2], "value": content}, } offical.SendTemplateMessage( openids, configs["WechatMessageTemplateId"], configs["WechatHost"]+"/wechatKefuTransfer?ent_id="+entId+"&kefu_name="+kefuName, messages, ) //wechatConfig, _ := lib.NewWechatLib(entId) //if wechatConfig.WechatMessageTemplateId == "" { // systemBussinesId := models.FindConfig("SystemBussinesId") // if systemBussinesId == "" { // return false // } // wechatConfig, _ = lib.NewWechatLib(systemBussinesId) // if wechatConfig.WechatMessageTemplateId == "" { // return false // } //} //msgData := make(map[string]*message.TemplateDataItem) //msgData["keyword1"] = &message.TemplateDataItem{ // Value: visitorName, // Color: "", //} //msgData["keyword2"] = &message.TemplateDataItem{ // Value: time.Now().Format("2006-01-02 15:04:05"), // Color: "", //} //msgData["keyword3"] = &message.TemplateDataItem{ // Value: content, // Color: "", //} //msgData["remark"] = &message.TemplateDataItem{ // Value: models.FindConfig("WechatTemplateRemark"), // Color: "", //} //for _, oauth := range oauths { // msg := &message.TemplateMessage{ // ToUser: oauth.OauthId, // Data: msgData, // TemplateID: wechatConfig.WechatMessageTemplateId, // URL: wechatConfig.WechatHost + "/wechatKefuTransfer?ent_id=" + entId + "&kefu_name=" + kefuName, // } // SendWechatTemplate(wechatConfig, msg) //} msg := ws.TypeMessage{ Type: "wechat_notice", Data: 1, } go ws.VisitorCustomMessage(visitorId, msg) return true } // 发送客服回复模板消息 //func SendWechatKefuTemplate(visitorId, kefuName, kefuNickname, content, entId string) bool { // visitorIdArr := strings.Split(visitorId, "|") // if len(visitorIdArr) < 3 || visitorIdArr[0] != "wx" { // return false // } // wechatConfig, _ := lib.NewWechatLib(entId) // if wechatConfig.WechatKefuTemplateId == "" { // return false // } // msgData := make(map[string]*message.TemplateDataItem) // msgData["keyword1"] = &message.TemplateDataItem{ // Value: kefuNickname, // Color: "", // } // msgData["keyword2"] = &message.TemplateDataItem{ // Value: time.Now().Format("2006-01-02 15:04:05"), // Color: "", // } // msgData["keyword3"] = &message.TemplateDataItem{ // Value: tools.TrimHtml(content), // Color: "", // } // msgData["remark"] = &message.TemplateDataItem{ // Value: models.FindConfig("WechatTemplateRemark"), // Color: "", // } // msg := &message.TemplateMessage{ // ToUser: visitorIdArr[2], // Data: msgData, // TemplateID: wechatConfig.WechatKefuTemplateId, // URL: wechatConfig.WechatHost + "/wechatIndex" + // "?ent_id=" + entId + // "&kefu_id=" + kefuName + // "&visitor_id=" + visitorId, // } // res, _ := SendWechatTemplate(wechatConfig, msg) // return res //} // 发送微信模板消息 func SendWechatTemplate(wechatConfig *lib.Wechat, msg *message.TemplateMessage) (bool, error) { if wechatConfig == nil { return false, errors.New("该企业未配置appid等公众号资料") } if msg.TemplateID == "" || msg.ToUser == "" { return false, errors.New("openid或templateId不存在") } wc := wechat.NewWechat() cfg := &offConfig.Config{ AppID: wechatConfig.AppId, AppSecret: wechatConfig.AppSecret, Token: wechatConfig.Token, //EncodingAESKey: "xxxx", Cache: memory, } officialAccount := wc.GetOfficialAccount(cfg) template := officialAccount.GetTemplate() _, err := template.Send(msg) if err != nil { log.Println("发送模板消息失败:", err.Error()) return false, err } m, _ := json.Marshal(msg) log.Println("发送模板消息成功!:" + string(m)) return true, nil } // 发送微信客服消息 func SendWechatMesage(openId, content, entId string) bool { wechatConfig, _ := lib.NewWechatLib(entId) if wechatConfig == nil || wechatConfig.WechatKefu == "" || wechatConfig.WechatKefu == "off" { return false } wc := wechat.NewWechat() cfg := &offConfig.Config{ AppID: wechatConfig.AppId, AppSecret: wechatConfig.AppSecret, Token: wechatConfig.Token, //EncodingAESKey: "xxxx", Cache: memory, } officialAccount := wc.GetOfficialAccount(cfg) messager := officialAccount.GetCustomerMessageManager() if imgUrl := ParseImgMessage(content); imgUrl != "" { media, err := officialAccount.GetMaterial().MediaUpload("image", common.UploadDirPath+imgUrl) if err != nil { log.Println("发送公众号微信客服图片消息-上传临时素材错误:", openId, imgUrl, err) return true } err = messager.Send(message.NewCustomerImgMessage(openId, media.MediaID)) log.Println("发送公众号微信客服图片消息:", openId, content, err) return true } if voiceUrl := ParseVoiceMessage(content); voiceUrl != "" { media, err := officialAccount.GetMaterial().MediaUpload("voice", common.UploadDirPath+voiceUrl) if err != nil { log.Println("发送公众号微信客服图片消息-上传临时素材错误:", openId, voiceUrl, err) return true } err = messager.Send(message.NewCustomerVoiceMessage(openId, media.MediaID)) log.Println("发送公众号微信客服语音消息:", openId, content, err) return true } err := messager.Send(message.NewCustomerTextMessage(openId, content)) log.Println("发送公众号微信客服消息:", openId, content, err) return true } // 验证访客黑名单 func CheckVisitorBlack(visitorId string) bool { black := models.FindVisitorBlack("visitor_id = ?", visitorId) if black.Id != 0 { return false } return true } // 过滤敏感词 func ReplaceBlackWords(content string) (string, bool) { blackWords := models.FindConfig("BlackWords") strings.ReplaceAll(blackWords, "\r\n", "\n") blackList := strings.Split(blackWords, "\n") exist := false for _, word := range blackList { word = strings.Trim(word, " ") if word == "" { continue } if strings.Contains(content, word) { exist = true content = strings.ReplaceAll(content, word, "*") } } return content, exist } // 发送企业微信应用通知 func SendWorkWechatMesage(entId string, visitor models.Visitor, kefu models.User, content string, c *gin.Context) bool { currentHost := tools.GetHost(c.Request) token := GenUserToken(kefu) pcUrl := fmt.Sprintf("%s/sso?token=%s&redirect=%s/main", currentHost, token, currentHost) //企业微信群机器人webhook webhookUrl := models.FindEntConfig(entId, "WorkWechatWebHookUrl").ConfValue if webhookUrl != "" { data := map[string]interface{}{ "msgtype": "markdown", "markdown": map[string]interface{}{ "content": fmt.Sprintf(`### %s > 访客名:%s > 客服名:%s > 访问次数:%d > 时间:%s > 回复:请点击[H5后台](%s/h5/)或[PC后台](%s) `, content, visitor.Name, kefu.Nickname, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl), }, } str, _ := tools.JsonEncode(data) url := webhookUrl res, err := tools.PostJson(url, []byte(str)) log.Println(url, str, err, res) } wechatConfig, _ := lib.NewWechatLib(entId) if wechatConfig.WorkWechatCorpid == "" || wechatConfig.WorkWechatAppAgentId == "" || wechatConfig.WorkWechatAppSecret == "" { return false } wc := wechat.NewWechat() cfg := &workConfig.Config{ CorpID: wechatConfig.WorkWechatCorpid, CorpSecret: wechatConfig.WorkWechatAppSecret, Cache: memory, } work := wc.GetWork(cfg) accessToken, _ := work.GetContext().GetAccessToken() data := map[string]interface{}{ "touser": "@all", "toparty": "", "totag": "", "msgtype": "markdown", "agentid": wechatConfig.WorkWechatAppAgentId, "markdown": map[string]interface{}{ "content": fmt.Sprintf(`### %s > 访客名:%s > 客服名:%s > 访客编号:@%d > 访问次数:%d > 时间:%s > 回复:请点击[H5后台](%s/h5/)或[PC后台](%s) `, content, visitor.Name, kefu.Nickname, visitor.ID, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl), }, "safe": 0, "enable_id_trans": 0, "enable_duplicate_check": 0, "duplicate_check_interval": 1800, } str, _ := tools.JsonEncode(data) url := "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken res, err := tools.PostJson(url, []byte(str)) log.Println(url, str, err, res) return true } // 获取企业配置信息 func GetEntConfigsMap(entId string, keys ...string) map[string]string { configs := models.FindEntConfigByEntid(entId) result := make(map[string]string) for _, key := range keys { result[key] = "" } for _, config := range configs { mapKey := config.ConfKey if _, ok := result[mapKey]; ok { result[mapKey] = config.ConfValue } } return result } func ParseImgMessage(str string) string { var url string url, _ = tools.GetOneStringByRegex(str, "img\\[.*?/static/upload/(.*?)\\]") return url } func ParseVoiceMessage(str string) string { var url string url, _ = tools.GetOneStringByRegex(str, "audio\\[.*?/static/upload/(.*?)\\]") return url } func ParseImgMessage2(str string) string { var url string url, _ = tools.GetOneStringByRegex(str, "img\\[.*?/static/(.*?)\\]") return url } func ParseVoiceMessage2(str string) string { var url string url, _ = tools.GetOneStringByRegex(str, "audio\\[(.*?)\\]") return url } func CheckSystemAuthCode(c *gin.Context) bool { authCode := models.FindConfig("SystemAuthCode") privateKey := common.RsaPrivateKey if authCode == "" || privateKey == "" { return false } rsa := tools.NewRsa("", privateKey) decContent, _ := tools.Base64Decode2(authCode) jsonByte, err := rsa.Decrypt(decContent) if err != nil { jsonByte = []byte("") } allowHost := gjson.Get(string(jsonByte), "host").String() if allowHost == "" { return false } if !strings.Contains(allowHost, c.Request.Host) { return false } return true } // 发送通知邮件 func SendSystemNoticeEmail(email, title, content string) (bool, error) { //验证邮箱 matched, _ := regexp.MatchString("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", email) if !matched { return false, errors.New(types.ApiCode.GetMessage(types.ApiCode.INVALID)) } smtp := models.FindConfig("NoticeEmailSmtp") sender := models.FindConfig("NoticeEmailAddress") password := models.FindConfig("NoticeEmailPassword") if smtp == "" || sender == "" || password == "" { return false, errors.New("系统没有配置发送邮箱") } htmlContent := strings.Replace(common.NoticeTemplate, "[:content]", content, -1) err := tools.SendSmtp(smtp, sender, password, []string{email}, title, htmlContent) log.Println("发送邮件:" + smtp + "," + sender + "," + password + "," + email + "," + content) if err != nil { log.Println("发送邮件验证码失败:", err) return false, err } return true, nil } // 发送企业微信WEBHOOK通知 func SendWorkWechatWebHook(entId string, visitor models.Visitor, kefu models.User, content string, c *gin.Context) bool { currentHost := tools.GetHost(c.Request) token := GenUserToken(kefu) pcUrl := fmt.Sprintf("%s/sso?token=%s&redirect=%s/main", currentHost, token, currentHost) //企业微信群机器人webhook webhookUrl := models.FindEntConfig(entId, "WorkWechatWebHookUrl").ConfValue if webhookUrl != "" { data := map[string]interface{}{ "msgtype": "markdown", "markdown": map[string]interface{}{ "content": fmt.Sprintf(`### %s > 访客名:%s > 客服名:%s > 访问次数:%d > 时间:%s > 回复:请点击[H5后台](%s/h5/)或[PC后台](%s) `, content, visitor.Name, kefu.Nickname, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl), }, } str, _ := tools.JsonEncode(data) url := webhookUrl res, err := tools.PostJson(url, []byte(str)) log.Println(url, str, err, res) } return true }