kefu/ws/visitor.go

470 lines
13 KiB
Go
Raw Normal View History

2024-12-10 02:50:12 +00:00
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
// }
// }()
//}