Files
kefu_cloud/server/internal/service/presence.go
T
yml2213 38d6fc1684
CI / Frontend (typecheck + lint + build) (push) Successful in 2m5s
CI / Backend (go vet + test) (push) Failing after 5m20s
feat(widget): 优化访客在线轨迹、GeoIP与多站点渠道
2026-11-06 18:21:30 +08:00

57 lines
1.7 KiB
Go

package service
import (
"time"
"kefu-cloud/server/internal/model"
"kefu-cloud/server/internal/ws"
)
const VisitorOnlineTimeout = 45 * time.Second
// VisitorIsOnline 根据最近心跳和显式离开时间动态判断访客是否在线。
func VisitorIsOnline(session *model.Session, now time.Time) bool {
if session == nil || session.Status == "ended" || session.Status == "archived" {
return false
}
if session.LastSeenAt == nil {
return false
}
if session.VisitorLeftAt != nil && !session.LastSeenAt.After(*session.VisitorLeftAt) {
return false
}
return now.Sub(*session.LastSeenAt) <= VisitorOnlineTimeout
}
// UpdateVisitorPresence 处理 visible/hidden/leave 状态,并返回切换前后的在线状态。
func UpdateVisitorPresence(session *model.Session, state string, now time.Time) (bool, bool, error) {
wasOnline := VisitorIsOnline(session, now)
updates := map[string]interface{}{}
switch state {
case "hidden", "leave":
leftAt := now
updates["visitor_left_at"] = leftAt
session.VisitorLeftAt = &leftAt
default:
updates["last_seen_at"] = now
updates["visitor_left_at"] = nil
session.LastSeenAt = &now
session.VisitorLeftAt = nil
}
if err := model.DB.Model(session).Updates(updates).Error; err != nil {
return wasOnline, false, err
}
return wasOnline, VisitorIsOnline(session, now), nil
}
func broadcastVisitorPresence(session *model.Session, online bool) {
payload, err := ws.NewEvent("visitor_presence", session.ID, map[string]interface{}{
"visitor_online": online,
"last_seen_at": session.LastSeenAt,
"visitor_left_at": session.VisitorLeftAt,
})
if err == nil {
ws.DefaultHub.BroadcastToTenantStaff(session.TenantID, payload)
}
}