470 lines
13 KiB
Go
470 lines
13 KiB
Go
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
|
||
// }
|
||
// }()
|
||
//}
|