diff --git a/internal/bootstrap/server.go b/internal/bootstrap/server.go index 74eafa8..d87a0d5 100644 --- a/internal/bootstrap/server.go +++ b/internal/bootstrap/server.go @@ -28,9 +28,9 @@ func NewServer() (*iris.Application, error) { app := iris.New() corsHandler := cors.New(). 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). - 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() app.UseRouter(func(ctx iris.Context) { // WebSocket upgrade is validated by the upgrader's origin policy. diff --git a/internal/builders/conversation_builder.go b/internal/builders/conversation_builder.go index 1d3bddd..637b33a 100644 --- a/internal/builders/conversation_builder.go +++ b/internal/builders/conversation_builder.go @@ -38,7 +38,7 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse AgentLastReadMessageID: readStateMessageID(agentReadState), AgentLastReadSeqNo: readStateSeqNo(agentReadState), AgentLastReadAt: readStateAt(agentReadState), - CustomerOnline: services.WsService.IsVisitorOnline(item.ExternalID), + CustomerOnline: services.WsService.IsGuestOnline(item.ExternalID), ClosedAt: utils.FormatTimePtr(item.ClosedAt), ClosedBy: item.ClosedBy, CloseReason: item.CloseReason, diff --git a/internal/services/ws_realtime_types.go b/internal/services/ws_realtime_types.go index 4e032d3..9a74d5f 100644 --- a/internal/services/ws_realtime_types.go +++ b/internal/services/ws_realtime_types.go @@ -28,7 +28,7 @@ const ( const ( realtimeTopicUserPrefix = "user:" - realtimeTopicVisitorPrefix = "visitor:" + realtimeTopicGuestPrefix = "guest:" realtimeTopicAdminPrefix = "admin:" realtimeTopicConversationPrefix = "conversation:" realtimeTopicAdminAll = "admin:all" @@ -54,7 +54,7 @@ type RealtimeEventPayload interface { type RealtimeConnectedPayload struct { ConnID string `json:"connId,omitempty"` UserID int64 `json:"userId,omitempty"` - VisitorID string `json:"visitorId,omitempty"` + GuestID string `json:"guestId,omitempty"` Role string `json:"role,omitempty"` TerminalType string `json:"terminalType,omitempty"` Topics []string `json:"topics,omitempty"` diff --git a/internal/services/ws_service.go b/internal/services/ws_service.go index 63c7ceb..c22d8a6 100644 --- a/internal/services/ws_service.go +++ b/internal/services/ws_service.go @@ -132,7 +132,7 @@ func (s *wsService) upgradeConnection(ctx iris.Context, principal *dto.AuthPrinc Payload: RealtimeConnectedPayload{ ConnID: session.ID, UserID: logUserID, - VisitorID: logExternalID, + GuestID: logExternalID, Role: role, TerminalType: session.TerminalType, Topics: session.topicList(), @@ -500,12 +500,12 @@ func (s *wsService) PublishToTopics(topics []string, event RealtimeEvent) { } } -func (s *wsService) IsVisitorOnline(visitorID string) bool { - visitorID = strings.TrimSpace(visitorID) - if visitorID == "" { +func (s *wsService) IsGuestOnline(guestID string) bool { + guestID = strings.TrimSpace(guestID) + if guestID == "" { return false } - return s.manager.HasTopic(s.visitorTopic(visitorID)) + return s.manager.HasTopic(s.guestTopic(guestID)) } 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)} if strings.TrimSpace(conversation.ExternalID) != "" { - topics = append(topics, s.visitorTopic(conversation.ExternalID)) + topics = append(topics, s.guestTopic(conversation.ExternalID)) } if conversation.CurrentAssigneeID > 0 { 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} default: - // 开放 IM:仅 External、无 AuthPrincipal 的访客连接必须仍能订阅 visitor:{externalId},否则收不到推送。 + // 开放 IM:仅 External、无 AuthPrincipal 的访客连接必须仍能订阅 guest:{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 { return []string{s.userTopic(session.Principal.UserID)} @@ -637,8 +637,8 @@ func (s *wsService) userTopic(userID int64) string { return realtimeTopicUserPrefix + strconv.FormatInt(userID, 10) } -func (s *wsService) visitorTopic(visitorID string) string { - return realtimeTopicVisitorPrefix + strings.TrimSpace(visitorID) +func (s *wsService) guestTopic(guestID string) string { + return realtimeTopicGuestPrefix + strings.TrimSpace(guestID) } func (s *wsService) adminTopic(userID int64) string { diff --git a/web/lib/api/im.ts b/web/lib/api/im.ts index 064620d..c0f08b5 100644 --- a/web/lib/api/im.ts +++ b/web/lib/api/im.ts @@ -114,7 +114,7 @@ export type ImWidgetConfig = { width?: string } -const VISITOR_STORAGE_KEY = "cs_agent_im_visitor_id" +const GUEST_STORAGE_KEY = "cs_agent_im_guest_id" const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL?.trim() || "" const OPEN_IM_CHANNEL_ID = @@ -122,21 +122,21 @@ const OPEN_IM_CHANNEL_ID = const OPEN_IM_EXTERNAL_SOURCE = process.env.NEXT_PUBLIC_OPEN_IM_EXTERNAL_SOURCE?.trim() || "web_chat" -function buildVisitorId() { - return `visitor_${generateUUID()}` +function buildGuestId() { + return `guest_${generateUUID()}` } -export function getImVisitorId() { +export function getGuestId() { 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) { return existing } - const visitorId = buildVisitorId() - window.localStorage.setItem(VISITOR_STORAGE_KEY, visitorId) - return visitorId + const guestId = buildGuestId() + window.localStorage.setItem(GUEST_STORAGE_KEY, guestId) + return guestId } function getRuntimeImConfig() { @@ -158,7 +158,7 @@ function createImHeaders() { const config = getRuntimeImConfig() const headers: Record = { "X-External-Source": config.externalSource, - "X-External-Id": config.externalId || getImVisitorId(), + "X-External-Id": config.externalId || getGuestId(), "X-Channel-Id": config.channelId, } if (config.externalName) { diff --git a/web/lib/im-realtime.ts b/web/lib/im-realtime.ts index 83828ec..2e50f00 100644 --- a/web/lib/im-realtime.ts +++ b/web/lib/im-realtime.ts @@ -1,5 +1,5 @@ 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 type { RealtimeConversationPatch, @@ -20,7 +20,7 @@ export function createImRealtimeConnection() { ? apiBaseUrl.replace(/^http/, "ws").replace(/\/$/, "") : createWebSocketBaseUrl() const resolvedExternalId = encodeURIComponent( - (config.externalId ?? "").trim() || getImVisitorId() + (config.externalId ?? "").trim() || getGuestId() ) const externalSource = encodeURIComponent( (config.externalSource ?? "web_chat").trim() || "web_chat"