Compare commits

...

19 Commits

14 changed files with 604 additions and 292 deletions

View File

@ -194,8 +194,8 @@ func (o *ChatRoom) Register(c *Client) (sessionId string) {
// message: 消息内容 // message: 消息内容
func (o *ChatRoom) SendSessionMessage(sender *accountFiee.ChatUserData, sessionId string, msgType WsType, message any) (userIdInSession []int64, err error) { func (o *ChatRoom) SendSessionMessage(sender *accountFiee.ChatUserData, sessionId string, msgType WsType, message any) (userIdInSession []int64, err error) {
fmt.Println("ChatRoom.SendSessionMessage ------------------1") fmt.Println("ChatRoom.SendSessionMessage ------------------1")
//o.clientsRwLocker.Lock() o.clientsRwLocker.Lock()
//defer o.clientsRwLocker.Unlock() defer o.clientsRwLocker.Unlock()
var msg = WsSessionInfo{ var msg = WsSessionInfo{
Type: msgType, Type: msgType,
Content: message, Content: message,
@ -209,18 +209,26 @@ func (o *ChatRoom) SendSessionMessage(sender *accountFiee.ChatUserData, sessionI
fmt.Println("ChatRoom.SendSessionMessage ------------------3") fmt.Println("ChatRoom.SendSessionMessage ------------------3")
usableClients := []*Client{} usableClients := []*Client{}
fmt.Printf("sessionId:[%s],客户端数量%d\n", sessionId, len(o.Session[sessionId])) fmt.Printf("sessionId:[%s],客户端数量%d\n", sessionId, len(o.Session[sessionId]))
pushed := false
for i, client := range o.Session[sessionId] { for i, client := range o.Session[sessionId] {
if client != nil { if client != nil {
_, exist := o.clients[client.UserId][client.ClientId] _, exist := o.clients[client.UserId][client.ClientId]
if exist { if exist {
usableClients = append(usableClients, o.Session[sessionId][i]) usableClients = append(usableClients, o.Session[sessionId][i])
go o.pushEvent(EventChatMessage, EventProgressBefore, sender, o.Session[sessionId][i], message) if !pushed {
go o.pushEvent(EventChatMessage, EventProgressBefore, sender, o.Session[sessionId][i], message)
pushed = true
}
} }
} }
fmt.Printf("client:%+v\n", client) fmt.Printf("client:%+v\n", client)
pushed = false
if client != nil && (client.UserId != sender.ID || sender.Role == 3) { if client != nil && (client.UserId != sender.ID || sender.Role == 3) {
client.Send <- msgBytes client.Send <- msgBytes
go o.pushEvent(EventChatMessage, EventProgressAfter, sender, o.Session[sessionId][i], message) if !pushed {
go o.pushEvent(EventChatMessage, EventProgressAfter, sender, o.Session[sessionId][i], message)
pushed = true
}
userIdInSession = append(userIdInSession, client.UserId) userIdInSession = append(userIdInSession, client.UserId)
} }
//client.Send <- msgBytes //client.Send <- msgBytes

View File

@ -40,39 +40,42 @@ func AuthorizationVerify(sourceData []byte) (userInfo *accountFiee.ChatUserData,
var ctx = context.Background() var ctx = context.Background()
var accountInfo accountFiee.ChatUserData var accountInfo accountFiee.ChatUserData
//fiee token校验 //fiee token校验
var fieeJwtInfo *jwt.Claims switch msg.Content.Domain {
fieeJwtInfo, err = jwt.ParseToken(msg.Content.Auth, m.JWTSecret) case "app":
if err != nil { var fieeJwtInfo *jwt.Claims
check = false fieeJwtInfo, err = jwt.ParseToken(msg.Content.Auth, m.JWTSecret)
fmt.Printf("fiee token parse err:%v\n", err)
} else {
fmt.Printf("fieeJwtInfo :%#v\n", fieeJwtInfo)
accountInfo.Origin = config.AppConfig.System.Domain
//accountInfo.OriginId = int64(fieeJwtInfo.ID)
accountInfo.Account = fieeJwtInfo.Account
accountInfo.NickName = fieeJwtInfo.NickName
infoReq := &accountFiee.UserByTelRequest{
Tel: fieeJwtInfo.Phone,
Domain: config.AppConfig.System.Domain,
}
var accInfo *accountFiee.UserInfoResponse
accInfo, err = service.AccountFieeProvider.UserByTel(ctx, infoReq)
if err != nil { if err != nil {
check = false check = false
fmt.Printf("err:%#v\n", err) fmt.Printf("fiee token parse err:%v\n", err)
} else if accInfo != nil { } else {
fmt.Printf("fiee accInfo :%#v\n", accInfo) fmt.Printf("fieeJwtInfo :%#v\n", fieeJwtInfo)
accountInfo.OriginId = int64(accInfo.Id) accountInfo.Origin = config.AppConfig.System.Domain
accountInfo.Account = accInfo.TelNum //accountInfo.OriginId = int64(fieeJwtInfo.ID)
accountInfo.Avatar = accInfo.GroupPhoto accountInfo.Account = fieeJwtInfo.Account
if accInfo.Name != "" { accountInfo.NickName = fieeJwtInfo.NickName
accountInfo.NickName = accInfo.Name infoReq := &accountFiee.UserByTelRequest{
Tel: fieeJwtInfo.Phone,
Domain: config.AppConfig.System.Domain,
}
var accInfo *accountFiee.UserInfoResponse
accInfo, err = service.AccountFieeProvider.UserByTel(ctx, infoReq)
if err != nil {
check = false
fmt.Printf("err:%#v\n", err)
} else if accInfo != nil {
fmt.Printf("fiee accInfo :%#v\n", accInfo)
accountInfo.OriginId = int64(accInfo.Id)
accountInfo.Account = accInfo.TelNum
accountInfo.Avatar = accInfo.GroupPhoto
if accInfo.Name != "" {
accountInfo.NickName = accInfo.Name
}
} }
} }
} case "fontree":
if !check {
msg.Content.Auth, err = secret.GetJwtFromStr(msg.Content.Auth) msg.Content.Auth, err = secret.GetJwtFromStr(msg.Content.Auth)
if err != nil { if err != nil {
fmt.Println("token解析失败", err.Error())
check = false check = false
} else { } else {
var fontreeJwtInfo *account.DecryptJwtResponse var fontreeJwtInfo *account.DecryptJwtResponse

View File

@ -111,7 +111,7 @@ func (a *ChatAutoReplyRulerHandler) GetChatAutoReplyRulerList(c *gin.Context) {
var protoReq = accountFiee.GetChatAutoReplyRulerListRequest{Query: &accountFiee.ChatAutoReplyRulerData{}} var protoReq = accountFiee.GetChatAutoReplyRulerListRequest{Query: &accountFiee.ChatAutoReplyRulerData{}}
utils.RequestDataConvert(&req, &protoReq) utils.RequestDataConvert(&req, &protoReq)
if req.RuleType != "" { if req.RuleType != "" {
protoReq.Where = fmt.Sprintf("ruler LIKE '%%\"%s\":{\"enable\":true}%%'", req.RuleType) protoReq.Where = fmt.Sprintf("ruler LIKE '%%%s\":{\"enable\":true%%'", req.RuleType)
} }
resp, err := service.AccountFieeProvider.GetChatAutoReplyRulerList(c, &protoReq) resp, err := service.AccountFieeProvider.GetChatAutoReplyRulerList(c, &protoReq)
if err != nil { if err != nil {

View File

@ -99,7 +99,7 @@ func (cr ChatCache) GetChatRecord(sessionId string) (data []*accountFiee.ChatRec
//log.Print("获取聊天记录失败", zap.Error(err)) //log.Print("获取聊天记录失败", zap.Error(err))
return return
} }
fmt.Printf("cache data: %+v", string(messages)) //fmt.Printf("cache data: %+v", string(messages))
if len(messages) > 0 { if len(messages) > 0 {
_ = json.Unmarshal(messages, &data) _ = json.Unmarshal(messages, &data)
} }
@ -144,29 +144,36 @@ func (cr ChatCache) IncreaseNewMessageTotal(ownerId int64, sessionId string) (er
// 重置新消息数量 // 重置新消息数量
func (cr ChatCache) ResetNewMessageTotal(ownerId int64, sessionId string, total ...int64) error { func (cr ChatCache) ResetNewMessageTotal(ownerId int64, sessionId string, total ...int64) error {
fmt.Printf("ResetNewMessageTotal: %d ,sessionId:%s ,total:%v\n", ownerId, sessionId, total)
chatCacheLocker.Lock() chatCacheLocker.Lock()
defer chatCacheLocker.Unlock() defer chatCacheLocker.Unlock()
var tl int64 var tl int64
if len(total) > 0 { if len(total) > 0 {
tl = total[0] tl = total[0]
} }
fmt.Println("ResetNewMessageTotal tl:", tl)
ctx := context.Background() ctx := context.Background()
data := cr.GetNewMessageStat(ctx, ownerId) data := cr.GetNewMessageStat(ctx, ownerId)
fmt.Printf("ResetNewMessageTotal data:%+v\n", data)
found := false found := false
for i, v := range data { for i, v := range data {
if v.SessionId == sessionId { if v.SessionId == sessionId {
found = true found = true
data[i].Total = tl data[i].Total = tl
fmt.Println("ResetNewMessageTotal found!")
break break
} }
} }
if !found { if !found {
fmt.Println("ResetNewMessageTotal not found!")
data = append(data, dto.UserMsgStatic{ data = append(data, dto.UserMsgStatic{
SessionId: sessionId, SessionId: sessionId,
Total: tl, Total: tl,
}) })
} }
return cr.coverOwnerNewMessageStat(ctx, ownerId, data) err := cr.coverOwnerNewMessageStat(ctx, ownerId, data)
fmt.Println("ResetNewMessageTotal result", err)
return err
} }
func (cr ChatCache) RecountNewMessageTotal(ownerId int64) { func (cr ChatCache) RecountNewMessageTotal(ownerId int64) {
@ -194,7 +201,8 @@ func (cr ChatCache) RecountNewMessageTotal(ownerId int64) {
if len(messages) > 0 { if len(messages) > 0 {
_ = json.Unmarshal(messages, &data) _ = json.Unmarshal(messages, &data)
} }
var sessionId = strings.Split(key, ":")[1] lastIndex := strings.Count(key, ":")
var sessionId = strings.Split(key, ":")[lastIndex]
countMap[sessionId] = 0 countMap[sessionId] = 0
for _, v := range data { for _, v := range data {
if v.WaiterRead == 2 { //统计未读消息数量 if v.WaiterRead == 2 { //统计未读消息数量

View File

@ -31,8 +31,10 @@ type MessageMedia struct {
// 客户端发送消息请求使用api发送消息 // 客户端发送消息请求使用api发送消息
type NewMessageRequest struct { type NewMessageRequest struct {
Waiter bool `json:"waiter"` //是否是客服发送,客服没有userId Waiter bool `json:"waiter"` //是否是客服发送,客服没有userId
Robot bool `json:"-"` //是否机器人发送
SessionId string `json:"sessionId"` SessionId string `json:"sessionId"`
Message Message
AtUserId int64 `json:"atUserId"` //指定发送给sessionId中的某一个用户
} }
// 服务端接收到消息后使用websocket发送给userId关联的客户端通知客户端有新消息然后调用接口获取消息 // 服务端接收到消息后使用websocket发送给userId关联的客户端通知客户端有新消息然后调用接口获取消息

View File

@ -230,9 +230,9 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
service.Error(c, err) service.Error(c, err)
return return
} }
domain := c.GetHeader("domain") //domain := c.GetHeader("domain")
fmt.Println("MessageList domain:", domain) //fmt.Println("MessageList domain:", domain)
if (request.Direction == 0 && request.Recent == false) || (request.Direction > 0 && request.Recent == true) { if (request.Direction == 0 && !request.Recent) || (request.Direction > 0 && request.Recent) {
service.Error(c, errors.New("组合条件校验失败")) service.Error(c, errors.New("组合条件校验失败"))
return return
} }
@ -249,7 +249,7 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
service.Success(c, resp) service.Success(c, resp)
return return
} }
chatUser, code := jwt.ParseToChatUser(c) accessUser, code := jwt.ParseToChatUser(c)
if code != 0 { if code != 0 {
service.ErrWithCode(c, code) service.ErrWithCode(c, code)
return return
@ -261,16 +261,16 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
// return // return
// } // }
//} //}
//messages := cr.cache.GetChatRecord(request.SessionId) messages := cr.cache.GetChatRecord(request.SessionId)
messages := []*accountFiee.ChatRecordData{} //messages := []*accountFiee.ChatRecordData{}
var returnDataIdList = make([]int64, 0) var returnDataIdList = make([]int64, 0)
defer func() { defer func() {
//获取最新数据时,重置新消息数量统计 //获取最新数据时,重置新消息数量统计
if request.Direction == 2 || request.Recent { if request.Direction == 2 || request.Recent {
cr.cache.ResetNewMessageTotal(chatUser.ID, request.SessionId) cr.cache.ResetNewMessageTotal(accessUser.ID, request.SessionId)
} }
//设置消息已被客服阅读,当客服重新通过通过websocket连接时这些消息将不被纳入新消息数量统计 //设置消息已被客服阅读,当客服重新通过通过websocket连接时这些消息将不被纳入新消息数量统计
if len(returnDataIdList) > 0 && domain == "fontree" { if len(returnDataIdList) > 0 && accessUser.Role == 2 {
for _, hasReadId := range returnDataIdList { for _, hasReadId := range returnDataIdList {
for i, message := range messages { for i, message := range messages {
if message.ID == hasReadId { if message.ID == hasReadId {
@ -368,6 +368,8 @@ func (cr ChatHandler) MessageList(c *gin.Context) {
resp[i].Message.Media = []dto.MessageMedia{} resp[i].Message.Media = []dto.MessageMedia{}
} }
} }
if accessUser.Role == 1 {
}
service.Success(c, resp) service.Success(c, resp)
} }
@ -501,10 +503,21 @@ func (cr ChatHandler) UserMessageStat(c *gin.Context) {
} }
} }
if result[i].Name == "" { if result[i].Name == "" {
result[i].Name = beautifulZeroName(result[i].Name, result[i].UserId) result[i].Name = beautifulZeroNameWithPhone(result[i].Name, result[i].UserId)
} }
} }
reverse(result) reverse(result)
if chatUser.Role == 1 {
userSessionId := fmt.Sprintf("%d", chatUser.ID)
newResp := []dto.UserMsgStatic{}
for _, v := range result {
if v.SessionId == userSessionId {
newResp = append(newResp, v)
service.Success(c, newResp)
return
}
}
}
service.Success(c, result) service.Success(c, result)
} }
func reverse(slice []dto.UserMsgStatic) { func reverse(slice []dto.UserMsgStatic) {
@ -563,6 +576,10 @@ func (cr ChatHandler) UserDetail(c *gin.Context) {
return return
} }
} }
//fmt.Printf("chatUser:%#v\n", chatUser)
//if chatUser.Origin == "fiee" {
// chatUser.Origin = "app"
//}
resp, err := service.AccountFieeProvider.Info(c, &accountFiee.InfoRequest{ID: uint64(chatUser.OriginId), Domain: chatUser.Origin}) resp, err := service.AccountFieeProvider.Info(c, &accountFiee.InfoRequest{ID: uint64(chatUser.OriginId), Domain: chatUser.Origin})
if err != nil { if err != nil {
service.Error(c, err) service.Error(c, err)
@ -588,3 +605,31 @@ func (cr ChatHandler) UserDetail(c *gin.Context) {
func beautifulZeroName(name string, userId int64) string { func beautifulZeroName(name string, userId int64) string {
return utils.IfGec(name == "", fmt.Sprintf("未实名用户:%d", userId), name) return utils.IfGec(name == "", fmt.Sprintf("未实名用户:%d", userId), name)
} }
var userIdMapPhone = make(map[int64]string)
func beautifulZeroNameWithPhone(name string, userId int64) string {
var ctx = context.Background()
if name == "" {
telNum, ok := userIdMapPhone[userId]
if ok {
return telNum
}
chatUserRes, err := service.AccountFieeProvider.GetChatUserDetail(ctx, &accountFiee.GetChatUserByIdRequest{Id: userId})
if err != nil {
return fmt.Sprintf("未实名用户:%d", userId)
} else {
if userRes, errs := service.AccountFieeProvider.Info(ctx, &accountFiee.InfoRequest{
Domain: chatUserRes.Origin,
ID: uint64(chatUserRes.OriginId),
Scene: "",
}); errs != nil {
return fmt.Sprintf("未实名用户:%d", userId)
} else {
userIdMapPhone[userId] = userRes.TelNum
return userRes.TelNum
}
}
}
return name
}

View File

@ -24,8 +24,8 @@ import (
//} //}
type AutoReply struct { type AutoReply struct {
Response string `json:"response"` Response string `json:"response"`
Rules map[string]IRule `json:"rules"` Rules map[string]IRobotTask `json:"rules"`
} }
type AutoReplyRule struct { type AutoReplyRule struct {
Enable bool `json:"enable"` Enable bool `json:"enable"`

View File

@ -26,3 +26,6 @@ web端和后端交互式时增删改查的规则配置是存放在rules对象
- keywords :关键字回复 - keywords :关键字回复
- joinSession用户打开聊天窗口后 - joinSession用户打开聊天窗口后
- noReplyAfter客服指定时间没有回复后 - noReplyAfter客服指定时间没有回复后
## 注意
- 目前不支持用户多端登录,会导致用户收到重复消息

View File

@ -7,26 +7,20 @@
package robot package robot
import ( import (
"context"
"fonchain-fiee/api/accountFiee" "fonchain-fiee/api/accountFiee"
"fonchain-fiee/pkg/common/ws" "fonchain-fiee/pkg/common/ws"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/asChat/chatCache"
"fonchain-fiee/pkg/service/asChat/dto"
"fonchain-fiee/pkg/service/asChat/logic"
"strings"
"time"
) )
// 回复规则 // 回复规则
type Reply struct { type Reply struct {
Title string
Response string Response string
Rules []IRule Rules []IRobotTask
} }
func (r *Reply) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) { func (r *Reply) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, rule IRobotTask) {
for _, rule := range r.Rules { for _, rule = range r.Rules {
hit, task = rule.Hit(event, robotInfo) hit = rule.Hit(event, robotInfo)
if hit { if hit {
return return
} }
@ -34,226 +28,219 @@ func (r *Reply) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserDat
return return
} }
// 规则接口 //
type IRule interface { //// 规则接口
Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) //type IRule interface {
} // Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask)
//}
// KeywordsRuleChecker 关键字回复 //
type ReplyWhenHitKeywords struct { //// KeywordsRuleChecker 关键字回复
Keywords []string `json:"keywords"` //type ReplyWhenHitKeywords struct {
} // Keywords []string `json:"keywords"`
//}
func NewReplyWhenHitKeywords(keywords []string) IRule { //
return &ReplyWhenHitKeywords{Keywords: keywords} //func NewReplyWhenHitKeywords(keywords []string) IRule {
} // return &ReplyWhenHitKeywords{Keywords: keywords}
func (k ReplyWhenHitKeywords) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) { //}
if event.EventType != ws.EventChatMessage || event.Msg == "" || event.Client == nil || event.ChatUser == nil { //func (k ReplyWhenHitKeywords) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) {
return // if event.EventType != ws.EventChatMessage || event.Msg == "" || event.Client == nil || event.ChatUser == nil {
} // return
//客服的消息不需要处理 // }
if event.ChatUser.Role == 2 { // if event.ChatUser.Role != 1 {
return // return
} // }
for _, v := range k.Keywords { // for _, v := range k.Keywords {
if strings.Contains(event.Msg, v) { // if strings.Contains(event.Msg, v) {
hit = true // hit = true
break // break
} // }
} // }
task = RobotTask{ // atUserId := event.Client.UserId
ChatUser: event.ChatUser, // task = RobotTask{
Run: func(msg string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error { // ChatUser: event.ChatUser,
return logic.NewMessage(context.Background(), cache, Sender, dto.NewMessageRequest{ // Run: func(msg string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error {
Waiter: true, // return logic.NewMessage(context.Background(), cache, Sender, dto.NewMessageRequest{
SessionId: event.Client.SessionId, // Waiter: true,
Message: dto.Message{ // Robot: true,
MsgType: 1, // AtUserId: atUserId,
Text: msg, // SessionId: event.Client.SessionId,
LocalStamp: time.Now().Unix(), // Message: dto.Message{
}, // MsgType: 1,
}) // Text: msg,
}, // LocalStamp: time.Now().Unix(),
} // },
//logicFunc = func(content string, cache *chatCache.ChatCache, chatUser *accountFiee.ChatUserData) error { // })
// //var notice = dto.MessageListType{} // },
// //newRecord := &accountFiee.ChatRecordData{ // }
// // SessionId: wsClient.SessionId, // return
// // UserId: wsClient.UserId, //}
// // Name: chatUser.NickName, //
// // Avatar: robotInfo.Avatar, //// 用户打开聊天会话直接发送
// // MsgType: 1, //type ReplyWhenUserJoinSession struct {
// // Content: content, //}
// //} //
// //notice.BuildMessage(newRecord) //func NewReplyWhenUserJoinSession() IRule {
// //_, err := consts.ChatRoom.SendSessionMessage(robotInfo, wsClient.SessionId, ws.NewChatMsgType, notice) // return &ReplyWhenUserJoinSession{}
// //return err //}
// err := logic.NewMessage(context.Background(), cache, chatUser, dto.NewMessageRequest{ //
// Waiter: true, //func (k ReplyWhenUserJoinSession) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) {
// SessionId: wsClient.SessionId, // if event.EventType != ws.EventUserJoin {
// Message: dto.Message{ // return
// MsgType: 1, // }
// Text: msg, // if event.Client == nil {
// LocalStamp: time.Now().Unix(), // return
// }, // }
// }) // clientSessionId := event.Client.SessionId
// return err // atUserId := event.Client.UserId
//} // ctx := context.Background()
return // queryRes, err := service.AccountFieeProvider.GetChatRecordList(ctx, &accountFiee.GetChatRecordListRequest{
} // Query: &accountFiee.ChatRecordData{
// SessionId: event.Client.SessionId,
// 用户打开聊天会话直接发送 // },
type ReplyWhenUserJoinSession struct { // Page: 1,
} // PageSize: 1,
// Order: "created_at desc",
func NewReplyWhenUserJoinSession() IRule { // })
return &ReplyWhenUserJoinSession{} // if err != nil {
} // return
// }
func (k ReplyWhenUserJoinSession) Hit(event ws.ListenEventData, robotInfo *accountFiee.ChatUserData) (hit bool, task RobotTask) { // //如果最近一次的消息也是机器人发送的,就不再发送了
if event.EventType != ws.EventUserJoin { // for i, v := range queryRes.List {
return // if i == 0 {
} // if v.UserId == robotInfo.ID {
if event.Client == nil { // return
return // } else {
} // break
ctx := context.Background() // }
queryRes, err := service.AccountFieeProvider.GetChatRecordList(ctx, &accountFiee.GetChatRecordListRequest{ // }
Query: &accountFiee.ChatRecordData{ // }
SessionId: event.Client.SessionId, // hit = true
}, // if event.ChatUser == nil {
Page: 1, // event.ChatUser, err = service.AccountFieeProvider.GetChatUserDetail(context.Background(), &accountFiee.GetChatUserByIdRequest{Id: event.Client.UserId})
PageSize: 1, // if err != nil {
Order: "created_at desc", // return
}) // }
if err != nil { // }
return // task = RobotTask{
} // ChatUser: event.ChatUser,
//如果最近一次的消息也是机器人发送的,就不再发送了 // Run: func(msg string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error {
for i, v := range queryRes.List { // return logic.NewMessage(ctx, cache, Sender, dto.NewMessageRequest{
if i == 0 { // Waiter: true,
if v.UserId == robotInfo.ID { // Robot: true,
return // AtUserId: atUserId,
} else { // SessionId: clientSessionId,
break // Message: dto.Message{
} // MsgType: 1,
} // Text: msg,
} // LocalStamp: time.Now().Unix(),
hit = true // },
if event.ChatUser == nil { // })
event.ChatUser, err = service.AccountFieeProvider.GetChatUserDetail(context.Background(), &accountFiee.GetChatUserByIdRequest{Id: event.Client.UserId}) // },
if err != nil { // }
return // //logicFunc = func(msg string, cache *chatCache.ChatCache, chatUser *accountFiee.ChatUserData) error {
} // // //var notice = dto.MessageListType{}
} // // //newRecord := &accountFiee.ChatRecordData{
task = RobotTask{ // // // SessionId: wsClient.SessionId,
ChatUser: event.ChatUser, // // // UserId: wsClient.UserId,
Run: func(msg string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error { // // // Name: wsClient.SessionId,
return logic.NewMessage(ctx, cache, Sender, dto.NewMessageRequest{ // // // Avatar: robotInfo.Avatar,
Waiter: true, // // // MsgType: 1,
SessionId: event.Client.SessionId, // // // Content: msg,
Message: dto.Message{ // // //}
MsgType: 1, // // //notice.BuildMessage(newRecord)
Text: msg, // // //_, err = consts.ChatRoom.SendSessionMessage(robotInfo, wsClient.SessionId, ws.NewChatMsgType, notice)
LocalStamp: time.Now().Unix(), // // err = logic.NewMessage(ctx, cache, chatUser, dto.NewMessageRequest{
}, // // Waiter: true,
}) // // SessionId: wsClient.SessionId,
}, // // Message: dto.Message{
} // // MsgType: 1,
//logicFunc = func(msg string, cache *chatCache.ChatCache, chatUser *accountFiee.ChatUserData) error { // // Text: msg,
// //var notice = dto.MessageListType{} // // LocalStamp: time.Now().Unix(),
// //newRecord := &accountFiee.ChatRecordData{ // // },
// // SessionId: wsClient.SessionId, // // })
// // UserId: wsClient.UserId, // // return err
// // Name: wsClient.SessionId, // //}
// // Avatar: robotInfo.Avatar, // return
// // MsgType: 1, //}
// // Content: msg, //
// //} //// 客服指定时间不回复则自动回复
// //notice.BuildMessage(newRecord) //
// //_, err = consts.ChatRoom.SendSessionMessage(robotInfo, wsClient.SessionId, ws.NewChatMsgType, notice) //type ReplyWhenWaiterNoAction struct {
// err = logic.NewMessage(ctx, cache, chatUser, dto.NewMessageRequest{ // DelaySecond time.Duration
// Waiter: true, //}
// SessionId: wsClient.SessionId, //
// Message: dto.Message{ ////func NewReplyWhenWaiterNoAction(delaySecond time.Duration) *ReplyWhenWaiterNoAction {
// MsgType: 1, //// return &ReplyWhenWaiterNoAction{
// Text: msg, //// DelaySecond: delaySecond,
// LocalStamp: time.Now().Unix(), //// }
// }, ////}
// }) //
// return err //func (k *ReplyWhenWaiterNoAction) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool, task RobotTask) {
//} // if event.Client == nil || event.EventType != ws.EventChatMessage {
return // return
} // }
// //客服和机器人的的消息不需要处理
// 客服指定时间不回复则自动回复 // if event.ChatUser.Role != 1 {
// return
type ReplyWhenWaiterNoAction struct { // }
DelaySecond time.Duration // hit = true // 立即保存SessionId的值
} //
// clientSessionId := event.Client.SessionId
func NewReplyWhenWaiterNoAction(delaySecond time.Duration) *ReplyWhenWaiterNoAction { // atUserId := event.Client.UserId
return &ReplyWhenWaiterNoAction{ // fmt.Printf("闭包前: clientSessionId=%s\n", clientSessionId)
DelaySecond: delaySecond, // task = RobotTask{
} // RunTime: time.Now().Add(k.DelaySecond * time.Second),
} // Run: func(content string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error {
// // 记录闭包执行时的Client状态
func (k *ReplyWhenWaiterNoAction) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool, task RobotTask) { // fmt.Printf("闭包执行: clientSessionId=%s\n", clientSessionId)
if event.Client == nil || event.EventType != ws.EventChatMessage { //
return // //如果客服已经回复则不发送消息
} // chatRecordListRes, err := service.AccountFieeProvider.GetChatRecordList(context.Background(), &accountFiee.GetChatRecordListRequest{
//客服的消息不需要处理 // Query: &accountFiee.ChatRecordData{
if event.ChatUser.Role == 2 { // SessionId: event.Client.SessionId,
return // },
} // Page: 1,
hit = true // PageSize: 1,
task = RobotTask{ // Order: "created_at desc",
RunTime: time.Now().Add(k.DelaySecond * time.Second), // })
Run: func(content string, cache *chatCache.ChatCache, Sender *accountFiee.ChatUserData) error { // if err != nil || chatRecordListRes.Total == 0 {
//如果客服已经回复则不发送消息 // return err
chatRecordListRes, err := service.AccountFieeProvider.GetChatRecordList(context.Background(), &accountFiee.GetChatRecordListRequest{ // }
Query: &accountFiee.ChatRecordData{ // checkUserId := chatRecordListRes.List[0].UserId
SessionId: event.Client.SessionId, // checkChatUser, err := service.AccountFieeProvider.GetChatUserDetail(context.Background(), &accountFiee.GetChatUserByIdRequest{Id: checkUserId})
}, // if err != nil || checkChatUser.Role != 1 {
Page: 1, // return err
PageSize: 1, // }
Order: "created_at desc", //
}) // //var notice = dto.MessageListType{}
if err != nil || chatRecordListRes.Total == 0 { // //newRecord := &accountFiee.ChatRecordData{
return err // // SessionId: wsClient.SessionId,
} // // UserId: wsClient.UserId,
checkUserId := chatRecordListRes.List[0].UserId // // Name: chatUser.NickName,
checkChatUser, err := service.AccountFieeProvider.GetChatUserDetail(context.Background(), &accountFiee.GetChatUserByIdRequest{Id: checkUserId}) // // Avatar: robotInfo.Avatar,
if err != nil || checkChatUser.Role != 1 { // // MsgType: 1,
return err // // Content: content,
} // //}
// //notice.BuildMessage(newRecord)
//var notice = dto.MessageListType{} // //_, err = consts.ChatRoom.SendSessionMessage(robotInfo, wsClient.SessionId, ws.NewChatMsgType, notice)
//newRecord := &accountFiee.ChatRecordData{ // //return err
// SessionId: wsClient.SessionId, // fmt.Println("延时回复 sessionID:", clientSessionId)
// UserId: wsClient.UserId, // err = logic.NewMessage(context.Background(), cache, sender, dto.NewMessageRequest{
// Name: chatUser.NickName, // Waiter: true,
// Avatar: robotInfo.Avatar, // Robot: true,
// MsgType: 1, // AtUserId: atUserId,
// Content: content, // SessionId: clientSessionId,
//} // Message: dto.Message{
//notice.BuildMessage(newRecord) // MsgType: 1,
//_, err = consts.ChatRoom.SendSessionMessage(robotInfo, wsClient.SessionId, ws.NewChatMsgType, notice) // Text: content,
//return err // LocalStamp: time.Now().Unix(),
err = logic.NewMessage(context.Background(), cache, sender, dto.NewMessageRequest{ // },
Waiter: true, // })
SessionId: event.Client.SessionId, // return err
Message: dto.Message{ // },
MsgType: 1, // Response: "",
Text: content, // ChatUser: event.ChatUser,
LocalStamp: time.Now().Unix(), // }
}, // return
}) //
return err //}
},
Response: "",
ChatUser: event.ChatUser,
}
return
}

View File

@ -57,7 +57,7 @@ func NewRobot(cache *chatCache.ChatCache) *Robot {
cache: cache, cache: cache,
} }
err = r.ReloadRules(ctx) err = r.ReloadRules(ctx)
fmt.Println("机器人规则加载失败") fmt.Println("机器人规则加载完成,结果:", err)
consts.ChatRoom.RegisterEventListener(r.EventListener) consts.ChatRoom.RegisterEventListener(r.EventListener)
go r.Run() go r.Run()
return r return r
@ -66,7 +66,7 @@ func NewRobot(cache *chatCache.ChatCache) *Robot {
type Robot struct { type Robot struct {
Info *accountFiee.ChatUserData //机器人信息 Info *accountFiee.ChatUserData //机器人信息
Rules []Reply //回复规则 Rules []Reply //回复规则
DelayTask []RobotTask //演示任务 DelayTask []IRobotTask //演示任务
ticker *time.Ticker //定时器 ticker *time.Ticker //定时器
stopChan chan struct{} //停止管道 stopChan chan struct{} //停止管道
isRunning bool //运行状态 isRunning bool //运行状态
@ -140,12 +140,12 @@ func (r *Robot) Run() {
//return // 没有任务时退出 //return // 没有任务时退出
} }
now := time.Now() now := time.Now()
var remainingTasks []RobotTask var remainingTasks []IRobotTask
for _, task := range r.DelayTask { for _, task := range r.DelayTask {
if now.After(task.RunTime) { if now.After(task.RunTime()) {
// 执行任务 // 执行任务
go func() { go func() {
err := task.Run(task.Response, r.cache, task.ChatUser) err := task.Run(r.cache)
if err != nil { if err != nil {
log.Printf("聊天机器人[%d]延时回复消息失败:%v", r.Info.ID, err) log.Printf("聊天机器人[%d]延时回复消息失败:%v", r.Info.ID, err)
} else { } else {
@ -163,23 +163,26 @@ func (r *Robot) Run() {
return return
case event := <-r.EventListener.Chan: case event := <-r.EventListener.Chan:
fmt.Printf("robot listen event:%#v\n", event) fmt.Printf("robot listen event:%#v\n", event)
r.mu.Lock()
for _, ruleResponse := range r.Rules { for _, ruleResponse := range r.Rules {
hit, task := ruleResponse.Hit(event, r.Info) hit, task := ruleResponse.Hit(event, r.Info)
if hit { if hit {
if task.RunTime.IsZero() { fmt.Println("命中规则:", ruleResponse.Title)
err := task.Run(ruleResponse.Response, r.cache, r.Info) if task.RunTime().IsZero() {
task.SetResponse(ruleResponse.Response)
err := task.Run(r.cache)
if err != nil { if err != nil {
log.Printf("robot 执行任务失败:%v\n", err) log.Printf("robot 执行任务失败:%v\n", err)
} }
} else { } else {
ruleResponse := ruleResponse ruleResponse := ruleResponse
task.Response = ruleResponse.Response task.SetResponse(ruleResponse.Response)
r.RegisterDelayTask(task) r.RegisterDelayTask(task)
} }
break break
} }
} }
r.mu.Unlock()
} }
} }
} }
@ -192,9 +195,7 @@ func (r *Robot) Stop() {
} }
r.mu.Unlock() r.mu.Unlock()
} }
func (r *Robot) RegisterDelayTask(task RobotTask) { func (r *Robot) RegisterDelayTask(task IRobotTask) {
r.mu.Lock()
defer r.mu.Unlock()
if task.Run == nil { if task.Run == nil {
return return
} }

View File

@ -7,6 +7,7 @@
package robot package robot
import ( import (
"fmt"
"fonchain-fiee/pkg/service/asChat/dto" "fonchain-fiee/pkg/service/asChat/dto"
"strings" "strings"
) )
@ -14,6 +15,7 @@ import (
// 自动回复规则结构转换 // 自动回复规则结构转换
func ParseReplyRule(data *dto.ChatAutoReplyData) (r Reply) { func ParseReplyRule(data *dto.ChatAutoReplyData) (r Reply) {
r.Response = data.Response r.Response = data.Response
r.Title = data.Title
for ruleName, v := range data.Rules { for ruleName, v := range data.Rules {
if !v.Enable { if !v.Enable {
continue continue
@ -26,6 +28,7 @@ func ParseReplyRule(data *dto.ChatAutoReplyData) (r Reply) {
} else { } else {
keywords = strings.Split(v.Content, ",") keywords = strings.Split(v.Content, ",")
} }
fmt.Println("ParseReplyRule 解析keywords:", keywords)
r.Rules = append(r.Rules, NewReplyWhenHitKeywords(keywords)) r.Rules = append(r.Rules, NewReplyWhenHitKeywords(keywords))
case "joinSession": //加入聊天后回复 case "joinSession": //加入聊天后回复
r.Rules = append(r.Rules, NewReplyWhenUserJoinSession()) r.Rules = append(r.Rules, NewReplyWhenUserJoinSession())

View File

@ -0,0 +1,98 @@
// Package robot -----------------------------
// @file : ruler_ReplyWhenWaiterNoAction.go
// @author : JJXu
// @contact : wavingbear@163.com
// @time : 2025/6/13 18:02
// -------------------------------------------
package robot
import (
"context"
"fmt"
"fonchain-fiee/api/accountFiee"
"fonchain-fiee/pkg/common/ws"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/asChat/chatCache"
"fonchain-fiee/pkg/service/asChat/dto"
"fonchain-fiee/pkg/service/asChat/logic"
"time"
)
type IRobotTask interface {
Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool)
Run(cache *chatCache.ChatCache) error
RunTime() time.Time
SetResponse(response string)
}
// 客服指定时间不回复则自动回复
func NewReplyWhenWaiterNoAction(delaySecond time.Duration) IRobotTask {
return &RobotTaskReplyWhenWaiterNoAction{
delaySecond: delaySecond,
}
}
type RobotTaskReplyWhenWaiterNoAction struct {
runTime time.Time
Response string
Receiver *accountFiee.ChatUserData
Sender *accountFiee.ChatUserData
Msg string
Resp string
delaySecond time.Duration
}
func (r *RobotTaskReplyWhenWaiterNoAction) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool) {
if event.Client == nil || event.EventType != ws.EventChatMessage {
return
}
//客服和机器人的的消息不需要处理
if event.ChatUser.Role != 1 {
return
}
hit = true // 立即保存SessionId的值
r.Sender = sender
r.Receiver = event.ChatUser
r.runTime = time.Now().Add(r.delaySecond * time.Second)
return
}
func (r *RobotTaskReplyWhenWaiterNoAction) Run(cache *chatCache.ChatCache) error {
clientSessionId := fmt.Sprintf("%d", r.Receiver.ID)
fmt.Println("延时回复 sessionID:", clientSessionId)
//如果客服已经回复则不发送消息
chatRecordListRes, err := service.AccountFieeProvider.GetChatRecordList(context.Background(), &accountFiee.GetChatRecordListRequest{
Query: &accountFiee.ChatRecordData{
SessionId: clientSessionId,
},
Page: 1,
PageSize: 1,
Order: "created_at desc",
})
if err != nil || chatRecordListRes.Total == 0 {
return err
}
checkUserId := chatRecordListRes.List[0].UserId
checkChatUser, err := service.AccountFieeProvider.GetChatUserDetail(context.Background(), &accountFiee.GetChatUserByIdRequest{Id: checkUserId})
if err != nil || checkChatUser.Role != 1 {
return err
}
err = logic.NewMessage(context.Background(), cache, r.Sender, dto.NewMessageRequest{
Waiter: true,
Robot: true,
AtUserId: r.Receiver.ID,
SessionId: clientSessionId,
Message: dto.Message{
MsgType: 1,
Text: r.Resp,
LocalStamp: time.Now().Unix(),
},
})
return err
}
func (r *RobotTaskReplyWhenWaiterNoAction) RunTime() time.Time {
return r.runTime
}
func (r *RobotTaskReplyWhenWaiterNoAction) SetResponse(response string) {
r.Resp = response
}

View File

@ -0,0 +1,69 @@
package robot
import (
"context"
"fmt"
"fonchain-fiee/api/accountFiee"
"fonchain-fiee/pkg/common/ws"
"fonchain-fiee/pkg/service/asChat/chatCache"
"fonchain-fiee/pkg/service/asChat/dto"
"fonchain-fiee/pkg/service/asChat/logic"
"strings"
"time"
)
type RobotTaskWithKeyworkds struct {
runTime time.Time
Response string
Receiver *accountFiee.ChatUserData
Sender *accountFiee.ChatUserData
Msg string
Resp string
keywords []string
}
func NewReplyWhenHitKeywords(keywords []string) IRobotTask {
return &RobotTaskWithKeyworkds{keywords: keywords}
}
func (r *RobotTaskWithKeyworkds) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool) {
if event.EventType != ws.EventChatMessage || event.Msg == "" || event.Client == nil || event.ChatUser == nil {
return
}
if event.ChatUser.Role != 1 {
return
}
r.Sender = sender
r.Receiver = event.ChatUser
for _, v := range r.keywords {
if strings.Contains(event.Msg, v) {
fmt.Printf("关键词比对:%s ----- %s : true", event.Msg, v)
hit = true
break
}
fmt.Printf("关键词比对:%s ----- %s: false", event.Msg, v)
}
return
}
func (r *RobotTaskWithKeyworkds) Run(cache *chatCache.ChatCache) (err error) {
err = logic.NewMessage(context.Background(), cache, r.Sender, dto.NewMessageRequest{
Waiter: true,
Robot: true,
AtUserId: r.Receiver.ID,
SessionId: fmt.Sprintf("%d", r.Receiver.ID),
Message: dto.Message{
MsgType: 1,
Text: r.Resp,
LocalStamp: time.Now().Unix(),
},
})
return
}
func (r *RobotTaskWithKeyworkds) RunTime() time.Time {
return time.Time{}
}
func (r *RobotTaskWithKeyworkds) SetResponse(response string) {
r.Resp = response
}

View File

@ -0,0 +1,85 @@
package robot
import (
"context"
"fonchain-fiee/api/accountFiee"
"fonchain-fiee/pkg/common/ws"
"fonchain-fiee/pkg/service"
"fonchain-fiee/pkg/service/asChat/chatCache"
"fonchain-fiee/pkg/service/asChat/dto"
"fonchain-fiee/pkg/service/asChat/logic"
"strconv"
"time"
)
func NewReplyWhenUserJoinSession() IRobotTask {
return &ReplyWhenUserJoinSession{}
}
type ReplyWhenUserJoinSession struct {
Response string
Sender *accountFiee.ChatUserData
Msg string
Resp string
sessionId string
atUserId int
}
func (r *ReplyWhenUserJoinSession) Hit(event ws.ListenEventData, sender *accountFiee.ChatUserData) (hit bool) {
if event.EventType != ws.EventUserJoin {
return
}
if event.Client == nil {
return
}
ctx := context.Background()
queryRes, err := service.AccountFieeProvider.GetChatRecordList(ctx, &accountFiee.GetChatRecordListRequest{
Query: &accountFiee.ChatRecordData{
SessionId: event.Client.SessionId,
},
Page: 1,
PageSize: 1,
Order: "created_at desc",
})
if err != nil {
return
}
//如果最近一次的消息也是机器人发送的,就不再发送了
for i, v := range queryRes.List {
if i == 0 {
if v.UserId == sender.ID {
return
} else {
break
}
}
}
hit = true
r.Sender = sender
r.sessionId = event.Client.SessionId
r.atUserId, _ = strconv.Atoi(event.Client.SessionId)
return
}
func (r *ReplyWhenUserJoinSession) Run(cache *chatCache.ChatCache) (err error) {
err = logic.NewMessage(context.Background(), cache, r.Sender, dto.NewMessageRequest{
Waiter: true,
Robot: true,
AtUserId: int64(r.atUserId),
SessionId: r.sessionId,
Message: dto.Message{
MsgType: 1,
Text: r.Resp,
LocalStamp: time.Now().Unix(),
},
})
return
}
func (r *ReplyWhenUserJoinSession) RunTime() time.Time {
return time.Time{}
}
func (r *ReplyWhenUserJoinSession) SetResponse(response string) {
r.Resp = response
}