refactor: update visitor references to guest in WebSocket service and related files

This commit is contained in:
mlogclub
2026-04-27 18:52:33 +08:00
parent c14ade27e4
commit 24105781d9
6 changed files with 27 additions and 27 deletions
+2 -2
View File
@@ -28,9 +28,9 @@ func NewServer() (*iris.Application, error) {
app := iris.New() app := iris.New()
corsHandler := cors.New(). corsHandler := cors.New().
AllowOrigin("*"). AllowOrigin("*").
AllowHeaders("Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-Visitor-Id", "X-Channel-Id", "X-External-Source", "X-External-Id", "X-External-Name"). AllowHeaders("Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-Guest-Id", "X-Channel-Id", "X-External-Source", "X-External-Id", "X-External-Name").
MaxAge(600). MaxAge(600).
ExposeHeaders("Content-Length", "Content-Type", "Authorization", "X-Visitor-Id", "X-Channel-Id", "X-External-Source", "X-External-Id", "X-External-Name"). ExposeHeaders("Content-Length", "Content-Type", "Authorization", "X-Guest-Id", "X-Channel-Id", "X-External-Source", "X-External-Id", "X-External-Name").
Handler() Handler()
app.UseRouter(func(ctx iris.Context) { app.UseRouter(func(ctx iris.Context) {
// WebSocket upgrade is validated by the upgrader's origin policy. // WebSocket upgrade is validated by the upgrader's origin policy.
+1 -1
View File
@@ -38,7 +38,7 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse
AgentLastReadMessageID: readStateMessageID(agentReadState), AgentLastReadMessageID: readStateMessageID(agentReadState),
AgentLastReadSeqNo: readStateSeqNo(agentReadState), AgentLastReadSeqNo: readStateSeqNo(agentReadState),
AgentLastReadAt: readStateAt(agentReadState), AgentLastReadAt: readStateAt(agentReadState),
CustomerOnline: services.WsService.IsVisitorOnline(item.ExternalID), CustomerOnline: services.WsService.IsGuestOnline(item.ExternalID),
ClosedAt: utils.FormatTimePtr(item.ClosedAt), ClosedAt: utils.FormatTimePtr(item.ClosedAt),
ClosedBy: item.ClosedBy, ClosedBy: item.ClosedBy,
CloseReason: item.CloseReason, CloseReason: item.CloseReason,
+2 -2
View File
@@ -28,7 +28,7 @@ const (
const ( const (
realtimeTopicUserPrefix = "user:" realtimeTopicUserPrefix = "user:"
realtimeTopicVisitorPrefix = "visitor:" realtimeTopicGuestPrefix = "guest:"
realtimeTopicAdminPrefix = "admin:" realtimeTopicAdminPrefix = "admin:"
realtimeTopicConversationPrefix = "conversation:" realtimeTopicConversationPrefix = "conversation:"
realtimeTopicAdminAll = "admin:all" realtimeTopicAdminAll = "admin:all"
@@ -54,7 +54,7 @@ type RealtimeEventPayload interface {
type RealtimeConnectedPayload struct { type RealtimeConnectedPayload struct {
ConnID string `json:"connId,omitempty"` ConnID string `json:"connId,omitempty"`
UserID int64 `json:"userId,omitempty"` UserID int64 `json:"userId,omitempty"`
VisitorID string `json:"visitorId,omitempty"` GuestID string `json:"guestId,omitempty"`
Role string `json:"role,omitempty"` Role string `json:"role,omitempty"`
TerminalType string `json:"terminalType,omitempty"` TerminalType string `json:"terminalType,omitempty"`
Topics []string `json:"topics,omitempty"` Topics []string `json:"topics,omitempty"`
+10 -10
View File
@@ -132,7 +132,7 @@ func (s *wsService) upgradeConnection(ctx iris.Context, principal *dto.AuthPrinc
Payload: RealtimeConnectedPayload{ Payload: RealtimeConnectedPayload{
ConnID: session.ID, ConnID: session.ID,
UserID: logUserID, UserID: logUserID,
VisitorID: logExternalID, GuestID: logExternalID,
Role: role, Role: role,
TerminalType: session.TerminalType, TerminalType: session.TerminalType,
Topics: session.topicList(), Topics: session.topicList(),
@@ -500,12 +500,12 @@ func (s *wsService) PublishToTopics(topics []string, event RealtimeEvent) {
} }
} }
func (s *wsService) IsVisitorOnline(visitorID string) bool { func (s *wsService) IsGuestOnline(guestID string) bool {
visitorID = strings.TrimSpace(visitorID) guestID = strings.TrimSpace(guestID)
if visitorID == "" { if guestID == "" {
return false return false
} }
return s.manager.HasTopic(s.visitorTopic(visitorID)) return s.manager.HasTopic(s.guestTopic(guestID))
} }
func (s *wsService) routeConversationTopics(conversation *models.Conversation) []string { func (s *wsService) routeConversationTopics(conversation *models.Conversation) []string {
@@ -515,7 +515,7 @@ func (s *wsService) routeConversationTopics(conversation *models.Conversation) [
topics := []string{s.conversationTopic(conversation.ID)} topics := []string{s.conversationTopic(conversation.ID)}
if strings.TrimSpace(conversation.ExternalID) != "" { if strings.TrimSpace(conversation.ExternalID) != "" {
topics = append(topics, s.visitorTopic(conversation.ExternalID)) topics = append(topics, s.guestTopic(conversation.ExternalID))
} }
if conversation.CurrentAssigneeID > 0 { if conversation.CurrentAssigneeID > 0 {
topics = append(topics, s.adminTopic(conversation.CurrentAssigneeID)) topics = append(topics, s.adminTopic(conversation.CurrentAssigneeID))
@@ -537,9 +537,9 @@ func (s *wsService) defaultTopics(session *ClientSession) []string {
} }
return []string{s.adminTopic(session.Principal.UserID), realtimeTopicAdminAll} return []string{s.adminTopic(session.Principal.UserID), realtimeTopicAdminAll}
default: default:
// 开放 IM:仅 External、无 AuthPrincipal 的访客连接必须仍能订阅 visitor:{externalId},否则收不到推送。 // 开放 IM:仅 External、无 AuthPrincipal 的访客连接必须仍能订阅 guest:{externalId},否则收不到推送。
if session.External != nil && strings.TrimSpace(session.External.ExternalID) != "" { if session.External != nil && strings.TrimSpace(session.External.ExternalID) != "" {
return []string{s.visitorTopic(session.External.ExternalID)} return []string{s.guestTopic(session.External.ExternalID)}
} }
if session.Principal != nil && session.Principal.UserID > 0 { if session.Principal != nil && session.Principal.UserID > 0 {
return []string{s.userTopic(session.Principal.UserID)} return []string{s.userTopic(session.Principal.UserID)}
@@ -637,8 +637,8 @@ func (s *wsService) userTopic(userID int64) string {
return realtimeTopicUserPrefix + strconv.FormatInt(userID, 10) return realtimeTopicUserPrefix + strconv.FormatInt(userID, 10)
} }
func (s *wsService) visitorTopic(visitorID string) string { func (s *wsService) guestTopic(guestID string) string {
return realtimeTopicVisitorPrefix + strings.TrimSpace(visitorID) return realtimeTopicGuestPrefix + strings.TrimSpace(guestID)
} }
func (s *wsService) adminTopic(userID int64) string { func (s *wsService) adminTopic(userID int64) string {
+10 -10
View File
@@ -114,7 +114,7 @@ export type ImWidgetConfig = {
width?: string width?: string
} }
const VISITOR_STORAGE_KEY = "cs_agent_im_visitor_id" const GUEST_STORAGE_KEY = "cs_agent_im_guest_id"
const API_BASE_URL = const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || "" process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || ""
const OPEN_IM_CHANNEL_ID = const OPEN_IM_CHANNEL_ID =
@@ -122,21 +122,21 @@ const OPEN_IM_CHANNEL_ID =
const OPEN_IM_EXTERNAL_SOURCE = const OPEN_IM_EXTERNAL_SOURCE =
process.env.NEXT_PUBLIC_OPEN_IM_EXTERNAL_SOURCE?.trim() || "web_chat" process.env.NEXT_PUBLIC_OPEN_IM_EXTERNAL_SOURCE?.trim() || "web_chat"
function buildVisitorId() { function buildGuestId() {
return `visitor_${generateUUID()}` return `guest_${generateUUID()}`
} }
export function getImVisitorId() { export function getGuestId() {
if (typeof window === "undefined") { if (typeof window === "undefined") {
return "visitor_ssr" return ""
} }
const existing = window.localStorage.getItem(VISITOR_STORAGE_KEY)?.trim() const existing = window.localStorage.getItem(GUEST_STORAGE_KEY)?.trim()
if (existing) { if (existing) {
return existing return existing
} }
const visitorId = buildVisitorId() const guestId = buildGuestId()
window.localStorage.setItem(VISITOR_STORAGE_KEY, visitorId) window.localStorage.setItem(GUEST_STORAGE_KEY, guestId)
return visitorId return guestId
} }
function getRuntimeImConfig() { function getRuntimeImConfig() {
@@ -158,7 +158,7 @@ function createImHeaders() {
const config = getRuntimeImConfig() const config = getRuntimeImConfig()
const headers: Record<string, string> = { const headers: Record<string, string> = {
"X-External-Source": config.externalSource, "X-External-Source": config.externalSource,
"X-External-Id": config.externalId || getImVisitorId(), "X-External-Id": config.externalId || getGuestId(),
"X-Channel-Id": config.channelId, "X-Channel-Id": config.channelId,
} }
if (config.externalName) { if (config.externalName) {
+2 -2
View File
@@ -1,5 +1,5 @@
import { createWebSocketBaseUrl } from "@/lib/api/websocket" import { createWebSocketBaseUrl } from "@/lib/api/websocket"
import { getImVisitorId, type ImMessage } from "@/lib/api/im" import { getGuestId, type ImMessage } from "@/lib/api/im"
import { readKefuWidgetConfig } from "@/lib/kefu-widget-config" import { readKefuWidgetConfig } from "@/lib/kefu-widget-config"
import type { import type {
RealtimeConversationPatch, RealtimeConversationPatch,
@@ -20,7 +20,7 @@ export function createImRealtimeConnection() {
? apiBaseUrl.replace(/^http/, "ws").replace(/\/$/, "") ? apiBaseUrl.replace(/^http/, "ws").replace(/\/$/, "")
: createWebSocketBaseUrl() : createWebSocketBaseUrl()
const resolvedExternalId = encodeURIComponent( const resolvedExternalId = encodeURIComponent(
(config.externalId ?? "").trim() || getImVisitorId() (config.externalId ?? "").trim() || getGuestId()
) )
const externalSource = encodeURIComponent( const externalSource = encodeURIComponent(
(config.externalSource ?? "web_chat").trim() || "web_chat" (config.externalSource ?? "web_chat").trim() || "web_chat"