kefu/ws/visitor.go

470 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// }
// }()
//}