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) } }