feat(eventbus): implement conversation and ticket assignment event handlers with notifications
This commit is contained in:
@@ -5,9 +5,10 @@ import (
|
|||||||
"cs-agent/internal/pkg/config"
|
"cs-agent/internal/pkg/config"
|
||||||
"cs-agent/internal/pkg/logx"
|
"cs-agent/internal/pkg/logx"
|
||||||
"cs-agent/internal/services/cronx"
|
"cs-agent/internal/services/cronx"
|
||||||
"cs-agent/internal/services/event_handlers"
|
|
||||||
"cs-agent/internal/wxwork"
|
"cs-agent/internal/wxwork"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
_ "cs-agent/internal/services/event_handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(configPath string) error {
|
func Init(configPath string) error {
|
||||||
@@ -37,8 +38,6 @@ func Init(configPath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
event_handlers.Register()
|
|
||||||
|
|
||||||
// 启动任务调度器
|
// 启动任务调度器
|
||||||
cronx.Init()
|
cronx.Init()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package event_handlers
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
var registerOnce sync.Once
|
|
||||||
|
|
||||||
func Register() {
|
|
||||||
registerOnce.Do(func() {
|
|
||||||
registerWxWorkNotifyEventHandlers()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user