From 64e75dcdf607eff3df57e03735719d3e1e3414c1 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Tue, 21 Apr 2026 17:11:50 +0800 Subject: [PATCH] feat(eventbus): implement conversation and ticket assignment event handlers with notifications --- internal/bootstrap/init.go | 5 +- .../conversation_assigned_event_handler.go | 85 ++++++++++ internal/services/event_handlers/registry.go | 11 -- .../ticket_assigned_event_handler.go | 50 ++++++ .../ticket_create_event_handler.go | 50 ++++++ .../wxwork_notify_event_handler.go | 156 ------------------ .../wxwork_notify_event_handler_test.go | 40 ----- 7 files changed, 187 insertions(+), 210 deletions(-) create mode 100644 internal/services/event_handlers/conversation_assigned_event_handler.go delete mode 100644 internal/services/event_handlers/registry.go create mode 100644 internal/services/event_handlers/ticket_assigned_event_handler.go create mode 100644 internal/services/event_handlers/ticket_create_event_handler.go delete mode 100644 internal/services/event_handlers/wxwork_notify_event_handler.go delete mode 100644 internal/services/event_handlers/wxwork_notify_event_handler_test.go diff --git a/internal/bootstrap/init.go b/internal/bootstrap/init.go index 7a44d91..68f3781 100644 --- a/internal/bootstrap/init.go +++ b/internal/bootstrap/init.go @@ -5,9 +5,10 @@ import ( "cs-agent/internal/pkg/config" "cs-agent/internal/pkg/logx" "cs-agent/internal/services/cronx" - "cs-agent/internal/services/event_handlers" "cs-agent/internal/wxwork" "log/slog" + + _ "cs-agent/internal/services/event_handlers" ) func Init(configPath string) error { @@ -37,8 +38,6 @@ func Init(configPath string) error { return err } - event_handlers.Register() - // 启动任务调度器 cronx.Init() diff --git a/internal/services/event_handlers/conversation_assigned_event_handler.go b/internal/services/event_handlers/conversation_assigned_event_handler.go new file mode 100644 index 0000000..2de43ea --- /dev/null +++ b/internal/services/event_handlers/conversation_assigned_event_handler.go @@ -0,0 +1,85 @@ +package event_handlers + +import ( + "context" + "fmt" + "strings" + "time" + + "cs-agent/internal/events" + "cs-agent/internal/models" + "cs-agent/internal/pkg/enums" + "cs-agent/internal/pkg/eventbus" + "cs-agent/internal/services" + + "github.com/mlogclub/simple/common/strs" +) + +func init() { + eventbus. + Register[events.ConversationAssignedEvent](). + Subscribe(handleConversationAssignedNotify) +} + +func handleConversationAssignedNotify(ctx context.Context, event events.ConversationAssignedEvent) error { + if event.ConversationID <= 0 || event.ToUserID <= 0 { + return nil + } + conversation := services.ConversationService.Get(event.ConversationID) + if conversation == nil { + return nil + } + return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(event.ToUserID, + conversationAssignedNotifyTitle(event.AssignType), + buildConversationAssignedNotifyBody(conversation, event.ToUserID, event.Reason, event.AssignType)) +} + +func conversationAssignedNotifyTitle(assignType string) string { + switch strings.TrimSpace(assignType) { + case events.ConversationAssignTypeTransfer: + return "会话转接提醒" + case events.ConversationAssignTypeAutoAssign: + return "会话自动分配提醒" + default: + return "会话分配提醒" + } +} + +func buildConversationAssignedNotifyBody(conversation *models.Conversation, assigneeID int64, reason string, assignType string) string { + if conversation == nil { + return "" + } + reasonLabel := "分配原因" + if strings.TrimSpace(assignType) == events.ConversationAssignTypeTransfer { + reasonLabel = "转接原因" + } + lines := []string{ + fmt.Sprintf("会话ID: #%d", conversation.ID), + fmt.Sprintf("会话主题: %s", strs.DefaultIfBlank(conversation.Subject, "-")), + fmt.Sprintf("接入渠道: %s", enums.GetExternalSourceLabel(conversation.ExternalSource)), + fmt.Sprintf("当前状态: %s", enums.GetIMConversationStatusLabel(conversation.Status)), + fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(assigneeID)), + } + if strings.TrimSpace(reason) != "" { + lines = append(lines, fmt.Sprintf("%s: %s", reasonLabel, strings.TrimSpace(reason))) + } + lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) + return strings.Join(lines, "\n") +} + +func resolveNotifyUserLabel(userID int64) string { + if userID <= 0 { + return "-" + } + user := services.UserService.Get(userID) + if user == nil { + return fmt.Sprintf("用户#%d", userID) + } + if nickname := strings.TrimSpace(user.Nickname); nickname != "" { + return nickname + } + if username := strings.TrimSpace(user.Username); username != "" { + return username + } + return fmt.Sprintf("用户#%d", userID) +} diff --git a/internal/services/event_handlers/registry.go b/internal/services/event_handlers/registry.go deleted file mode 100644 index 384bdac..0000000 --- a/internal/services/event_handlers/registry.go +++ /dev/null @@ -1,11 +0,0 @@ -package event_handlers - -import "sync" - -var registerOnce sync.Once - -func Register() { - registerOnce.Do(func() { - registerWxWorkNotifyEventHandlers() - }) -} diff --git a/internal/services/event_handlers/ticket_assigned_event_handler.go b/internal/services/event_handlers/ticket_assigned_event_handler.go new file mode 100644 index 0000000..ed8fe59 --- /dev/null +++ b/internal/services/event_handlers/ticket_assigned_event_handler.go @@ -0,0 +1,50 @@ +package event_handlers + +import ( + "context" + "cs-agent/internal/events" + "cs-agent/internal/models" + "cs-agent/internal/pkg/enums" + "cs-agent/internal/pkg/eventbus" + "cs-agent/internal/services" + "fmt" + "strings" + "time" + + "github.com/mlogclub/simple/common/strs" +) + +func init() { + eventbus. + Register[events.TicketAssignedEvent](). + Subscribe(handleTicketAssignedNotify) +} + +func handleTicketAssignedNotify(ctx context.Context, event events.TicketAssignedEvent) error { + if event.TicketID <= 0 || event.ToUserID <= 0 { + return nil + } + ticket := services.TicketService.Get(event.TicketID) + if ticket == nil { + return nil + } + content := buildTicketAssignedNotifyBody(ticket, event.ToUserID, event.Reason) + return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(event.ToUserID, "工单指派提醒", content) +} + +func buildTicketAssignedNotifyBody(ticket *models.Ticket, assigneeID int64, reason string) string { + if ticket == nil { + return "" + } + lines := []string{ + fmt.Sprintf("工单号: %s", strs.DefaultIfBlank(ticket.TicketNo, fmt.Sprintf("#%d", ticket.ID))), + fmt.Sprintf("工单标题: %s", strs.DefaultIfBlank(ticket.Title, "-")), + fmt.Sprintf("当前状态: %s", enums.GetTicketStatusLabel(ticket.Status)), + fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(assigneeID)), + } + if strings.TrimSpace(reason) != "" { + lines = append(lines, fmt.Sprintf("指派原因: %s", strings.TrimSpace(reason))) + } + lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) + return strings.Join(lines, "\n") +} diff --git a/internal/services/event_handlers/ticket_create_event_handler.go b/internal/services/event_handlers/ticket_create_event_handler.go new file mode 100644 index 0000000..9fda7e3 --- /dev/null +++ b/internal/services/event_handlers/ticket_create_event_handler.go @@ -0,0 +1,50 @@ +package event_handlers + +import ( + "context" + "cs-agent/internal/events" + "cs-agent/internal/models" + "cs-agent/internal/pkg/enums" + "cs-agent/internal/pkg/eventbus" + "cs-agent/internal/services" + "fmt" + "strings" + "time" + + "github.com/mlogclub/simple/common/strs" +) + +func init() { + eventbus. + Register[events.TicketCreatedEvent](). + Subscribe(handleTicketCreatedNotify) +} + +func handleTicketCreatedNotify(ctx context.Context, event events.TicketCreatedEvent) error { + if event.TicketID <= 0 { + return nil + } + ticket := services.TicketService.Get(event.TicketID) + if ticket == nil { + return nil + } + content := buildTicketCreatedNotifyBody(ticket) + return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(ticket.CurrentAssigneeID, "工单创建提醒", content) +} + +func buildTicketCreatedNotifyBody(ticket *models.Ticket) string { + if ticket == nil { + return "" + } + lines := []string{ + fmt.Sprintf("工单号: %s", strs.DefaultIfBlank(ticket.TicketNo, fmt.Sprintf("#%d", ticket.ID))), + fmt.Sprintf("工单标题: %s", strs.DefaultIfBlank(ticket.Title, "-")), + fmt.Sprintf("工单来源: %s", strs.DefaultIfBlank(string(ticket.Source), "-")), + fmt.Sprintf("当前状态: %s", enums.GetTicketStatusLabel(ticket.Status)), + } + if ticket.CurrentAssigneeID > 0 { + lines = append(lines, fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(ticket.CurrentAssigneeID))) + } + lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) + return strings.Join(lines, "\n") +} diff --git a/internal/services/event_handlers/wxwork_notify_event_handler.go b/internal/services/event_handlers/wxwork_notify_event_handler.go deleted file mode 100644 index 660e0d6..0000000 --- a/internal/services/event_handlers/wxwork_notify_event_handler.go +++ /dev/null @@ -1,156 +0,0 @@ -package event_handlers - -import ( - "context" - "fmt" - "log/slog" - "strings" - "time" - - "cs-agent/internal/events" - "cs-agent/internal/models" - "cs-agent/internal/pkg/enums" - "cs-agent/internal/pkg/eventbus" - "cs-agent/internal/services" -) - -func registerWxWorkNotifyEventHandlers() { - eventbus. - Register(eventbus.WithErrorHandler[events.TicketCreatedEvent](handleWxWorkNotifyEventError)). - Subscribe(handleTicketCreatedNotify) - eventbus. - Register(eventbus.WithErrorHandler[events.TicketAssignedEvent](handleWxWorkNotifyEventError)). - Subscribe(handleTicketAssignedNotify) - eventbus. - Register(eventbus.WithErrorHandler[events.ConversationAssignedEvent](handleWxWorkNotifyEventError)). - Subscribe(handleConversationAssignedNotify) -} - -func handleWxWorkNotifyEventError(ctx context.Context, err error) { - slog.Warn("handle wxwork notify event failed", "error", err) -} - -func handleTicketCreatedNotify(ctx context.Context, event events.TicketCreatedEvent) error { - if event.TicketID <= 0 { - return nil - } - ticket := services.TicketService.Get(event.TicketID) - if ticket == nil { - return nil - } - return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(ticket.CurrentAssigneeID, "工单创建提醒", buildTicketCreatedNotifyBody(ticket)) -} - -func handleTicketAssignedNotify(ctx context.Context, event events.TicketAssignedEvent) error { - if event.TicketID <= 0 || event.ToUserID <= 0 { - return nil - } - ticket := services.TicketService.Get(event.TicketID) - if ticket == nil { - return nil - } - return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(event.ToUserID, "工单指派提醒", buildTicketAssignedNotifyBody(ticket, event.ToUserID, event.Reason)) -} - -func handleConversationAssignedNotify(ctx context.Context, event events.ConversationAssignedEvent) error { - if event.ConversationID <= 0 || event.ToUserID <= 0 { - return nil - } - conversation := services.ConversationService.Get(event.ConversationID) - if conversation == nil { - return nil - } - return services.WxWorkNotifyService.SendTextToAssigneeOrDefault(event.ToUserID, conversationAssignedNotifyTitle(event.AssignType), buildConversationAssignedNotifyBody(conversation, event.ToUserID, event.Reason, event.AssignType)) -} - -func conversationAssignedNotifyTitle(assignType string) string { - switch strings.TrimSpace(assignType) { - case events.ConversationAssignTypeTransfer: - return "会话转接提醒" - case events.ConversationAssignTypeAutoAssign: - return "会话自动分配提醒" - default: - return "会话分配提醒" - } -} - -func buildConversationAssignedNotifyBody(conversation *models.Conversation, assigneeID int64, reason string, assignType string) string { - if conversation == nil { - return "" - } - reasonLabel := "分配原因" - if strings.TrimSpace(assignType) == events.ConversationAssignTypeTransfer { - reasonLabel = "转接原因" - } - lines := []string{ - fmt.Sprintf("会话ID: #%d", conversation.ID), - fmt.Sprintf("会话主题: %s", defaultIfBlank(conversation.Subject, "-")), - fmt.Sprintf("接入渠道: %s", enums.GetExternalSourceLabel(conversation.ExternalSource)), - fmt.Sprintf("当前状态: %s", enums.GetIMConversationStatusLabel(conversation.Status)), - fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(assigneeID)), - } - if strings.TrimSpace(reason) != "" { - lines = append(lines, fmt.Sprintf("%s: %s", reasonLabel, strings.TrimSpace(reason))) - } - lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) - return strings.Join(lines, "\n") -} - -func buildTicketCreatedNotifyBody(ticket *models.Ticket) string { - if ticket == nil { - return "" - } - lines := []string{ - fmt.Sprintf("工单号: %s", defaultIfBlank(ticket.TicketNo, fmt.Sprintf("#%d", ticket.ID))), - fmt.Sprintf("工单标题: %s", defaultIfBlank(ticket.Title, "-")), - fmt.Sprintf("工单来源: %s", defaultIfBlank(string(ticket.Source), "-")), - fmt.Sprintf("当前状态: %s", enums.GetTicketStatusLabel(ticket.Status)), - } - if ticket.CurrentAssigneeID > 0 { - lines = append(lines, fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(ticket.CurrentAssigneeID))) - } - lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) - return strings.Join(lines, "\n") -} - -func buildTicketAssignedNotifyBody(ticket *models.Ticket, assigneeID int64, reason string) string { - if ticket == nil { - return "" - } - lines := []string{ - fmt.Sprintf("工单号: %s", defaultIfBlank(ticket.TicketNo, fmt.Sprintf("#%d", ticket.ID))), - fmt.Sprintf("工单标题: %s", defaultIfBlank(ticket.Title, "-")), - fmt.Sprintf("当前状态: %s", enums.GetTicketStatusLabel(ticket.Status)), - fmt.Sprintf("处理人: %s", resolveNotifyUserLabel(assigneeID)), - } - if strings.TrimSpace(reason) != "" { - lines = append(lines, fmt.Sprintf("指派原因: %s", strings.TrimSpace(reason))) - } - lines = append(lines, fmt.Sprintf("时间: %s", time.Now().Format("2006-01-02 15:04:05"))) - return strings.Join(lines, "\n") -} - -func resolveNotifyUserLabel(userID int64) string { - if userID <= 0 { - return "-" - } - user := services.UserService.Get(userID) - if user == nil { - return fmt.Sprintf("用户#%d", userID) - } - if nickname := strings.TrimSpace(user.Nickname); nickname != "" { - return nickname - } - if username := strings.TrimSpace(user.Username); username != "" { - return username - } - return fmt.Sprintf("用户#%d", userID) -} - -func defaultIfBlank(value, fallback string) string { - value = strings.TrimSpace(value) - if value != "" { - return value - } - return strings.TrimSpace(fallback) -} diff --git a/internal/services/event_handlers/wxwork_notify_event_handler_test.go b/internal/services/event_handlers/wxwork_notify_event_handler_test.go deleted file mode 100644 index 8238e0a..0000000 --- a/internal/services/event_handlers/wxwork_notify_event_handler_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package event_handlers - -import ( - "strings" - "testing" - - "cs-agent/internal/events" - "cs-agent/internal/models" - "cs-agent/internal/pkg/enums" -) - -func TestWxWorkNotifyBuildTicketCreatedNotifyBody(t *testing.T) { - body := buildTicketCreatedNotifyBody(&models.Ticket{ - ID: 12, - TicketNo: "T-12", - Title: "登录失败", - Source: enums.TicketSourceManual, - Status: enums.TicketStatusNew, - }) - - for _, want := range []string{"工单号: T-12", "工单标题: 登录失败", "当前状态:"} { - if !strings.Contains(body, want) { - t.Fatalf("expected body to contain %q, got %q", want, body) - } - } -} - -func TestWxWorkNotifyBuildConversationAssignedNotifyBody(t *testing.T) { - body := buildConversationAssignedNotifyBody(&models.Conversation{ - ID: 7, - Subject: "售后咨询", - Status: enums.IMConversationStatusActive, - }, 0, "客户等待中", events.ConversationAssignTypeTransfer) - - for _, want := range []string{"会话ID: #7", "会话主题: 售后咨询", "转接原因: 客户等待中"} { - if !strings.Contains(body, want) { - t.Fatalf("expected body to contain %q, got %q", want, body) - } - } -}