| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- package service
- import (
- "errors"
- "strings"
- "time"
- "github.com/2930134478/AI-CS/backend/models"
- "github.com/2930134478/AI-CS/backend/repository"
- "gorm.io/gorm"
- )
- // ConversationService 负责会话领域的业务编排。
- type ConversationService struct {
- conversations *repository.ConversationRepository
- messages *repository.MessageRepository
- }
- // NewConversationService 创建 ConversationService 实例。
- func NewConversationService(
- conversations *repository.ConversationRepository,
- messages *repository.MessageRepository,
- ) *ConversationService {
- return &ConversationService{
- conversations: conversations,
- messages: messages,
- }
- }
- // InitConversation 为访客创建或恢复会话。
- func (s *ConversationService) InitConversation(input InitConversationInput) (*InitConversationResult, error) {
- var (
- conv *models.Conversation
- err error
- )
- conv, err = s.conversations.FindOpenByVisitorID(input.VisitorID)
- isNewConversation := false
- if err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- now := time.Now()
- conv = &models.Conversation{
- VisitorID: input.VisitorID,
- Status: "open",
- Website: input.Website,
- Referrer: input.Referrer,
- Browser: input.Browser,
- OS: input.OS,
- Language: input.Language,
- IPAddress: input.IPAddress,
- LastSeenAt: &now,
- }
- if err := s.conversations.Create(conv); err != nil {
- return nil, err
- }
- isNewConversation = true
- } else {
- return nil, err
- }
- } else {
- now := time.Now()
- updates := map[string]interface{}{
- "last_seen_at": &now,
- }
- if input.Website != "" && conv.Website == "" {
- updates["website"] = input.Website
- }
- if input.Referrer != "" && conv.Referrer == "" {
- updates["referrer"] = input.Referrer
- }
- if input.Browser != "" && conv.Browser == "" {
- updates["browser"] = input.Browser
- }
- if input.OS != "" && conv.OS == "" {
- updates["os"] = input.OS
- }
- if input.Language != "" && conv.Language == "" {
- updates["language"] = input.Language
- }
- if input.IPAddress != "" && conv.IPAddress == "" {
- updates["ip_address"] = input.IPAddress
- }
- if err := s.conversations.UpdateFields(conv.ID, updates); err != nil {
- return nil, err
- }
- }
- if isNewConversation {
- now := time.Now()
- message := &models.Message{
- ConversationID: conv.ID,
- SenderID: 0,
- SenderIsAgent: false,
- Content: "Visitor opened the page",
- MessageType: "system_message",
- IsRead: true,
- ReadAt: &now,
- }
- if input.Website != "" {
- message.Content += " [" + input.Website + "]"
- }
- if err := s.messages.Create(message); err != nil {
- return nil, err
- }
- if input.Referrer != "" {
- readTime := time.Now()
- referrerMsg := &models.Message{
- ConversationID: conv.ID,
- SenderID: 0,
- SenderIsAgent: false,
- Content: "Visitor came from [" + input.Referrer + "]",
- MessageType: "system_message",
- IsRead: true,
- ReadAt: &readTime,
- }
- if err := s.messages.Create(referrerMsg); err != nil {
- return nil, err
- }
- }
- }
- return &InitConversationResult{
- ConversationID: conv.ID,
- Status: conv.Status,
- }, nil
- }
- // UpdateConversationContact 更新访客的联系信息(邮箱、电话、备注)。
- func (s *ConversationService) UpdateConversationContact(input UpdateConversationContactInput) (*ConversationDetail, error) {
- if _, err := s.conversations.GetByID(input.ConversationID); err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, ErrConversationNotFound
- }
- return nil, err
- }
- updates := map[string]interface{}{}
- if input.Email != nil {
- updates["email"] = strings.TrimSpace(*input.Email)
- }
- if input.Phone != nil {
- updates["phone"] = strings.TrimSpace(*input.Phone)
- }
- if input.Notes != nil {
- updates["notes"] = strings.TrimSpace(*input.Notes)
- }
- if err := s.conversations.UpdateFields(input.ConversationID, updates); err != nil {
- return nil, err
- }
- return s.GetConversationDetail(input.ConversationID)
- }
- func (s *ConversationService) buildSummary(conv models.Conversation) (ConversationSummary, error) {
- var lastSeen *time.Time
- if conv.LastSeenAt != nil {
- lastSeen = conv.LastSeenAt
- }
- summary := ConversationSummary{
- ID: conv.ID,
- VisitorID: conv.VisitorID,
- AgentID: conv.AgentID,
- Status: conv.Status,
- CreatedAt: conv.CreatedAt,
- UpdatedAt: conv.UpdatedAt,
- LastSeenAt: lastSeen, // 添加 last_seen_at 字段
- }
- if message, err := s.messages.LatestByConversationID(conv.ID); err == nil && message != nil {
- var readAt *time.Time
- if message.ReadAt != nil {
- readAt = message.ReadAt
- }
- summary.LastMessage = &LastMessageSummary{
- ID: message.ID,
- Content: message.Content,
- SenderIsAgent: message.SenderIsAgent,
- MessageType: message.MessageType,
- IsRead: message.IsRead,
- ReadAt: readAt,
- CreatedAt: message.CreatedAt,
- }
- }
- if count, err := s.messages.CountUnreadBySender(conv.ID, false); err == nil {
- summary.UnreadCount = count
- }
- return summary, nil
- }
- // ListConversations 返回当前活跃会话的摘要信息。
- func (s *ConversationService) ListConversations() ([]ConversationSummary, error) {
- conversations, err := s.conversations.ListActive()
- if err != nil {
- return nil, err
- }
- result := make([]ConversationSummary, 0, len(conversations))
- for _, conv := range conversations {
- summary, err := s.buildSummary(conv)
- if err != nil {
- return nil, err
- }
- result = append(result, summary)
- }
- return result, nil
- }
- // GetConversationDetail 获取指定会话的详细信息。
- func (s *ConversationService) GetConversationDetail(id uint) (*ConversationDetail, error) {
- conv, err := s.conversations.GetByID(id)
- if err != nil {
- return nil, err
- }
- summary, err := s.buildSummary(*conv)
- if err != nil {
- return nil, err
- }
- var lastSeen *time.Time
- if conv.LastSeenAt != nil {
- lastSeen = conv.LastSeenAt
- }
- return &ConversationDetail{
- ConversationSummary: summary,
- Website: conv.Website,
- Referrer: conv.Referrer,
- Browser: conv.Browser,
- OS: conv.OS,
- Language: conv.Language,
- IPAddress: conv.IPAddress,
- Location: conv.Location,
- Email: conv.Email,
- Phone: conv.Phone,
- Notes: conv.Notes,
- LastSeen: lastSeen,
- }, nil
- }
- // SearchConversations 根据关键字检索会话摘要。
- func (s *ConversationService) SearchConversations(query string) ([]ConversationSummary, error) {
- pattern := "%" + query + "%"
- idSet := map[uint]struct{}{}
- if ids, err := s.messages.FindConversationIDsByContent(pattern); err == nil {
- for _, id := range ids {
- idSet[id] = struct{}{}
- }
- } else {
- return nil, err
- }
- if convs, err := s.conversations.SearchByIDOrVisitorLike(pattern); err == nil {
- for _, conv := range convs {
- idSet[conv.ID] = struct{}{}
- }
- } else {
- return nil, err
- }
- if len(idSet) == 0 {
- return []ConversationSummary{}, nil
- }
- ids := make([]uint, 0, len(idSet))
- for id := range idSet {
- ids = append(ids, id)
- }
- conversations, err := s.conversations.ListByIDs(ids)
- if err != nil {
- return nil, err
- }
- result := make([]ConversationSummary, 0, len(conversations))
- for _, conv := range conversations {
- summary, err := s.buildSummary(conv)
- if err != nil {
- return nil, err
- }
- result = append(result, summary)
- }
- return result, nil
- }
- // UpdateVisitorOnlineStatus 更新访客在线状态和最后活跃时间。
- // 当 isOnline 为 true 时,更新 last_seen_at 为当前时间,并确保状态为 "open"。
- // 当 isOnline 为 false 时,仅更新 last_seen_at 为当前时间,不改变状态。
- func (s *ConversationService) UpdateVisitorOnlineStatus(conversationID uint, isOnline bool) error {
- now := time.Now()
- updates := map[string]interface{}{
- "last_seen_at": &now,
- }
- // 如果标记为在线,确保状态为 "open"(但不要将已关闭的会话重新打开)
- if isOnline {
- conv, err := s.conversations.GetByID(conversationID)
- if err != nil {
- return err
- }
- // 只有当前状态不是 "closed" 时,才更新为 "open"
- if conv.Status != "closed" {
- updates["status"] = "open"
- }
- }
- return s.conversations.UpdateFields(conversationID, updates)
- }
- // UpdateLastSeenAt 更新访客的最后活跃时间。
- func (s *ConversationService) UpdateLastSeenAt(conversationID uint) error {
- now := time.Now()
- return s.conversations.UpdateFields(conversationID, map[string]interface{}{
- "last_seen_at": &now,
- })
- }
|