- 会话创建时按负载自动分配在线客服,无客服则进入离线模式 - 新增 /api/widget/leave-message 沉淀联系方式与留言事件 - 访客端离线留言表单;工作台展示离线留言/自动分配事件 - 提供 public/widget.js + /widget/embed 嵌入方案
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"kefu-sys/server/internal/model"
|
|
"kefu-sys/server/internal/ws"
|
|
)
|
|
|
|
const offlineLeavePrompt = "当前无客服在线,请留言并留下联系方式,我们上线后会尽快回复您。"
|
|
|
|
// pickLeastLoadedAgent 在在线一线客服中选择当前进行中会话最少的一位。
|
|
func pickLeastLoadedAgent(tenantID uint) (*model.User, error) {
|
|
var agents []model.User
|
|
if err := model.DB.Where("tenant_id = ? AND role = ? AND status = ?", tenantID, "agent", "online").
|
|
Order("id asc").Find(&agents).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if len(agents) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
best := &agents[0]
|
|
bestCount := int64(-1)
|
|
for i := range agents {
|
|
var count int64
|
|
if err := model.DB.Model(&model.Session{}).
|
|
Where("tenant_id = ? AND agent_id = ? AND status = ?", tenantID, agents[i].ID, "active").
|
|
Count(&count).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if bestCount < 0 || count < bestCount {
|
|
bestCount = count
|
|
best = &agents[i]
|
|
}
|
|
}
|
|
return best, nil
|
|
}
|
|
|
|
// countOnlineAgents 统计租户当前可接待的在线客服数。
|
|
func countOnlineAgents(tenantID uint) (int64, error) {
|
|
var count int64
|
|
err := model.DB.Model(&model.User{}).
|
|
Where("tenant_id = ? AND role = ? AND status = ?", tenantID, "agent", "online").
|
|
Count(&count).Error
|
|
return count, err
|
|
}
|
|
|
|
// tryAutoAssign 将会话自动分配给负载最低的在线客服;无可分配客服时返回 nil。
|
|
func tryAutoAssign(session *model.Session) (*model.User, error) {
|
|
if session == nil || session.ID == 0 {
|
|
return nil, nil
|
|
}
|
|
if session.Status != "waiting" || session.AgentID != nil {
|
|
return nil, nil
|
|
}
|
|
|
|
agent, err := pickLeastLoadedAgent(session.TenantID)
|
|
if err != nil || agent == nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := model.DB.Model(&model.Session{}).
|
|
Where("id = ? AND tenant_id = ? AND status = ? AND agent_id IS NULL", session.ID, session.TenantID, "waiting").
|
|
Updates(map[string]interface{}{
|
|
"agent_id": agent.ID,
|
|
"status": "active",
|
|
"last_read_seq": 0,
|
|
})
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
// 并发下可能已被他人领取
|
|
if err := model.DB.First(session, session.ID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
session.AgentID = &agent.ID
|
|
session.Status = "active"
|
|
detail := fmt.Sprintf("系统自动分配给 %s", agent.Nickname)
|
|
model.DB.Create(&model.SessionEvent{
|
|
SessionID: session.ID,
|
|
OperatorID: 0,
|
|
Action: "auto_assign",
|
|
Detail: detail,
|
|
})
|
|
|
|
if payload, err := ws.NewEvent("session_updated", session.ID, session); err == nil {
|
|
ws.DefaultHub.BroadcastToTenantStaff(session.TenantID, payload)
|
|
ws.DefaultHub.BroadcastToVisitor(session.TenantID, session.ID, payload)
|
|
}
|
|
return agent, nil
|
|
}
|