package ws import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "kefu/lib" "kefu/models" "kefu/tools" "log" "net/http" "time" ) type VisitorConnection struct { Conn *websocket.Conn Name string Id string Avatar string ToId string EntId string ClientIp string UpdateTime time.Time } func NewVisitorServer(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { http.NotFound(c.Writer, c.Request) log.Println("upgrade error:", err) return } //获取GET参数,创建WS toId := c.Query("to_id") visitorId := c.Query("visitor_id") if toId == "" || visitorId == "" { errStr := "访客websocket的GET参数为空" log.Println(errStr) conn.WriteMessage(websocket.TextMessage, []byte(errStr)) conn.Close() return } vistorInfo := models.FindVisitorByCondition("visitor_id = ? and to_id = ?", visitorId, toId) if vistorInfo.VisitorId == "" { errStr := "该客服下访客不存在:" + visitorId + "," + toId log.Println(errStr) conn.WriteMessage(websocket.TextMessage, []byte(errStr)) conn.Close() return } //验证访客黑名单 black := models.FindVisitorBlack("visitor_id = ?", visitorId) if black.Id != 0 { errStr := "访客加入黑名单:" + visitorId + "," + toId log.Println(errStr) conn.WriteMessage(websocket.TextMessage, []byte(errStr)) conn.Close() return } oldUser, ok := ClientList[visitorId] if ok && oldUser.Conn == conn { return } connection := &VisitorConnection{ Conn: conn, Name: vistorInfo.Name, Avatar: vistorInfo.Avator, Id: visitorId, ToId: toId, EntId: vistorInfo.EntId, UpdateTime: time.Now(), ClientIp: c.ClientIP(), } AddVisitorToList(connection) VisitorOnline(toId, vistorInfo) for { //接受消息 var receive []byte messageType, receive, err := conn.ReadMessage() if err != nil { log.Println("ws/visitor.go conn.ReadMessage:", visitorId, err, receive, messageType) conn.Close() visitor, ok := ClientList[visitorId] if ok && visitor.Conn == conn { delete(ClientList, visitor.Id) VisitorOffline(visitor.ToId, visitor.Id, visitor.Name) } return } //消息体 //message := &Message{ // conn: conn, // content: receive, // context: c, // messageType: messageType, //} var typeMsg TypeMessage err = json.Unmarshal(receive, &typeMsg) if err != nil || typeMsg.Type == nil || typeMsg.Data == nil { log.Println("ws/visitor.go message json unmarshal error:", err, receive) continue } msgType := typeMsg.Type.(string) switch msgType { //心跳 case "ping": msg := TypeMessage{ Type: "pong", } str, _ := json.Marshal(msg) //message.Mux.Lock() conn.WriteMessage(websocket.TextMessage, str) //message.Mux.Unlock() case "inputing": data := typeMsg.Data.(map[string]interface{}) from := data["from"].(string) to := data["to"].(string) //限流 if tools.LimitFreqSingle("inputing:"+from, 1, 2) { OneKefuMessage(to, receive) } } } } func AddVisitorToList(connection *VisitorConnection) { //用户id对应的连接 oldUser, ok := ClientList[connection.Id] if ok { VisitorOffline(oldUser.ToId, oldUser.Id, oldUser.Name) closemsg := TypeMessage{ Type: "close", Data: connection.Id, } closeStr, _ := json.Marshal(closemsg) oldUser.Conn.WriteMessage(websocket.TextMessage, closeStr) oldUser.Conn.Close() delete(ClientList, connection.Id) } ClientList[connection.Id] = connection } // func AddVisitorToRoom(roomId, visitorId string) { // //用户id对应的连接 // user, ok := ClientList[visitorId] // if user != nil && ok { // members, _ := Room.GetMembers(roomId) // members = append(members, user) // Room.SetMembers(roomId, members) // } // } func VisitorOnline(kefuId string, visitor models.Visitor) { go models.UpdateUserRecNum(kefuId, 1) lastMessage := models.FindLastMessageByVisitorId(visitor.VisitorId) unreadMap := models.FindUnreadMessageNumByVisitorIds([]string{visitor.VisitorId}, "visitor") var unreadNum uint32 if num, ok := unreadMap[visitor.VisitorId]; ok { unreadNum = num } userInfo := make(map[string]string) userInfo["uid"] = visitor.VisitorId userInfo["visitor_id"] = visitor.VisitorId userInfo["username"] = fmt.Sprintf("#%d%s", visitor.ID, visitor.Name) userInfo["avator"] = visitor.Avator userInfo["last_message"] = lastMessage.Content userInfo["unread_num"] = fmt.Sprintf("%d", unreadNum) userInfo["last_time"] = lastMessage.CreatedAt.Format("2006-01-02 15:04:05") //if userInfo["last_message"] == "" { // userInfo["last_message"] = "新访客" //} if userInfo["last_time"] == "0001-01-01 00:00:00" { userInfo["last_time"] = visitor.UpdatedAt.Format("2006-01-02 15:04:05") } msg := TypeMessage{ Type: "userOnline", Data: userInfo, } str, _ := json.Marshal(msg) OneKefuMessage(kefuId, str) } func VisitorOffline(kefuId string, visitorId string, visitorName string) { go models.UpdateUserRecNum(kefuId, -1) userInfo := make(map[string]string) userInfo["uid"] = visitorId userInfo["visitor_id"] = visitorId userInfo["name"] = visitorName msg := TypeMessage{ Type: "userOffline", Data: userInfo, } str, _ := json.Marshal(msg) //新版 OneKefuMessage(kefuId, str) } func VisitorNotice(visitorId string, notice string) { msg := TypeMessage{ Type: "notice", Data: notice, } str, _ := json.Marshal(msg) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func VisitorCustomMessage(visitorId string, notice TypeMessage) { str, _ := json.Marshal(notice) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func VisitorTransfer(visitorId string, kefuId string) { msg := TypeMessage{ Type: "transfer", Data: kefuId, } str, _ := json.Marshal(msg) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func VisitorChangeId(visitorId, newId string) { msg := TypeMessage{ Type: "change_id", Data: newId, } str, _ := json.Marshal(msg) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func UpdateVisitorName(visitorId string, visitorName string) { visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Name = visitorName } func VisitorMessage(visitorId, content string, kefuInfo models.User) { msgId := tools.Now() msg := TypeMessage{ Type: "message", Data: ClientMessage{ MsgId: uint(msgId), Name: kefuInfo.Nickname, Avator: kefuInfo.Avator, Id: kefuInfo.Name, Time: time.Now().Format("2006-01-02 15:04:05"), ToId: visitorId, Content: content, IsKefu: "no", }, } str, _ := json.Marshal(msg) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func VisitorMessageSameMsgId(msgId uint, visitorId, content string, kefuInfo models.User) { msg := TypeMessage{ Type: "message", Data: ClientMessage{ MsgId: msgId, Name: kefuInfo.Nickname, Avator: kefuInfo.Avator, Id: kefuInfo.Name, Time: time.Now().Format("2006-01-02 15:04:05"), ToId: visitorId, Content: content, IsKefu: "no", }, } str, _ := json.Marshal(msg) visitor, ok := ClientList[visitorId] if !ok || visitor == nil || visitor.Conn == nil { return } visitor.Conn.WriteMessage(websocket.TextMessage, str) } func VisitorToKefuMessage(visitorInfo models.Visitor, kefuName, content string) { msg := TypeMessage{ Type: "message", Data: ClientMessage{ Name: visitorInfo.Name, Avator: visitorInfo.Avator, Id: visitorInfo.VisitorId, Time: time.Now().Format("2006-01-02 15:04:05"), ToId: kefuName, Content: content, IsKefu: "no", }, } str, _ := json.Marshal(msg) OneKefuMessage(kefuName, str) } func VisitorAutoReply(vistorInfo models.Visitor, kefuInfo models.User, content string) (string, bool) { //var entInfo models.User //if fmt.Sprintf("%v", kefuInfo.Pid) == fmt.Sprintf("%d", 1) { // entInfo = kefuInfo //} else { // entInfo = models.FindUserByUid(kefuInfo.Pid) //} entId := vistorInfo.EntId //查询机器人信息 configs := models.GetEntConfigsMap(entId, "RobotName", "RobotAvator", "RobotStatus", "KeywordFinalReply") robotName := configs["RobotName"] robotAvator := configs["RobotAvator"] if configs["RobotStatus"] == "3" { return "", false } systemHotQaBussinesId := models.FindConfig("SystemHotQaBussinesId") if systemHotQaBussinesId != "" { entId = systemHotQaBussinesId } title := content replyContent := "" //if strings.HasPrefix(content, "@") { // apiKeys := strings.Split(strings.Replace(content, ":", ":", 1), ":") // title = apiKeys[0] //} //匹配方式 replys := models.FindArticleRows("id,title,search_type", "ent_id = ? ", entId) //按分值计算 var exactReply []lib.MatchData var matchReply []lib.MatchData for _, row := range replys { //精准 if row.SearchType == 2 { exactReply = append(exactReply, lib.MatchData{ ID: row.Id, Text: row.Title, }) } //包含 if row.SearchType == 1 { matchReply = append(matchReply, lib.MatchData{ ID: row.Id, Text: row.Title, }) } //通配符 if row.Title == "*" { exactReply = append(exactReply, lib.MatchData{ ID: row.Id, Text: row.Title, }) } } matchRes, ok := lib.NewMatcher(exactReply).Match(title, true) if ok { r := models.FindArticleRow("id = ?", matchRes.ID) replyContent = r.Content article := models.Article{ Score: r.Score + 1, } go article.SaveArticle("ent_id = ? and id = ?", entId, r.Id) } if replyContent == "" { matchRes, ok = lib.NewMatcher(matchReply).Match(title, false) if ok { r := models.FindArticleRow("id = ?", matchRes.ID) replyContent = r.Content article := models.Article{ Score: r.Score + 1, } go article.SaveArticle("ent_id = ? and id = ?", entId, r.Id) } } if replyContent == "" { replyContent = configs["KeywordFinalReply"] } //reply := models.FindArticleRow("ent_id = ? and title like ?", entId, "%"+title+"%") //reply = models.FindReplyItemByUserIdTitle(entInfo.Name, content) //调用第三方接口 //if reply.ApiUrl != "" { // params := strings.TrimSpace(strings.Replace(content, reply.Title, "", 1)) // url := fmt.Sprintf("%s?ent_id=%s&kefu_id=%s&visitor_id=%s&keyword=%s&content=%s", reply.ApiUrl, entId, kefuInfo.Name, vistorInfo.VisitorId, reply.Title, params) // log.Println(vistorInfo.VisitorId, content, url) // res := tools.Get(url) // if res != "" { // reply.Content = res // } //} //gpt流式回复 isGpt := false if replyContent == "" && models.CheckVisitorRobotReply(vistorInfo.State) { replyContent = Gpt3Knowledge(entId, vistorInfo.VisitorId, kefuInfo, content) if replyContent != "" { isGpt = true } } //调用chatGPT //if replyContent == "" { // replyContent = Gpt3dot5Message(entId, vistorInfo.VisitorId, kefuInfo, content) // if replyContent != "" { // isGpt = true // } //} if replyContent != "" && !isGpt { kefuInfo.Nickname = tools.Ifelse(robotName != "", robotName, kefuInfo.Nickname).(string) kefuInfo.Avator = tools.Ifelse(robotAvator != "", robotAvator, kefuInfo.Avator).(string) VisitorMessage(vistorInfo.VisitorId, replyContent, kefuInfo) KefuMessage(vistorInfo.VisitorId, replyContent, kefuInfo) models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, replyContent, "kefu", vistorInfo.EntId, "read") } return replyContent, isGpt } func VisitorCount(kefuName string) uint { num := 0 for _, visitor := range ClientList { if visitor.ToId == kefuName { num = num + 1 } } return uint(num) } //func CleanVisitorExpire() { // go func() { // log.Println("cleanVisitorExpire start...") // for { // for _, user := range ClientList { // diff := time.Now().Sub(user.UpdateTime).Seconds() // if diff >= common.VisitorExpire { // entConfig := models.FindEntConfig(user.Ent_id, "CloseVisitorMessage") // if entConfig.ConfValue != "" { // kefu := models.FindUserByUid(user.Ent_id) // VisitorMessage(user.Id, entConfig.ConfValue, kefu) // } // msg := TypeMessage{ // Type: "auto_close", // Data: user.Id, // } // str, _ := json.Marshal(msg) // if err := user.Conn.WriteMessage(websocket.TextMessage, str); err != nil { // user.Conn.Close() // delete(ClientList, user.Id) // VisitorOffline(user.To_id, user.Id, user.Name) // } // log.Println(user.Name + ":cleanVisitorExpire finshed") // } // } // t := time.NewTimer(time.Second * 5) // <-t.C // } // }() //}