Files
hfb_sys/backend/internal/modules/chat/conversation.go
T

188 lines
5.6 KiB
Go

package chat
import (
"context"
"errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"hfb_sys/backend/internal/model"
"time"
)
func (r *Repository) ListConversations(ctx context.Context, principal Principal, page, pageSize int) (*PaginatedResult, error) {
page, pageSize = normalizePagination(page, pageSize)
db := r.db.WithContext(ctx)
var total int64
countDB := db.Table("chat_conversations AS c").
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
Where("cp.participant_type = ? AND cp.participant_id = ?", principal.Type, principal.ID)
if err := countDB.Count(&total).Error; err != nil {
return nil, err
}
var rows []conversationRow
offset := (page - 1) * pageSize
err := r.conversationQuery(ctx, principal).
Order("COALESCE(c.last_message_at, c.created_at) DESC, c.id DESC").
Offset(offset).
Limit(pageSize).
Scan(&rows).Error
if err != nil {
return nil, err
}
items := make([]ConversationDTO, 0, len(rows))
for _, row := range rows {
items = append(items, row.toDTO(nil))
}
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}
func (r *Repository) FindConversation(ctx context.Context, principal Principal, id uint64) (*ConversationDTO, error) {
db := r.db.WithContext(ctx)
// 管理员可以查看任意会话,无需是 participant
if principal.Type == "admin" {
var conversation model.ChatConversation
if err := db.First(&conversation, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrConversationNotFound
}
return nil, err
}
participants, err := r.participants(ctx, conversation.ID)
if err != nil {
return nil, err
}
dto := ConversationDTO{
ID: conversation.ID,
OrderID: conversation.OrderID,
Type: conversation.Type,
Title: conversation.Title,
Status: conversation.Status,
Role: "admin", // 管理员角色
Participants: participants,
LastMessageID: conversation.LastMessageID,
LastMessagePreview: conversation.LastMessagePreview,
LastMessageAt: conversation.LastMessageAt,
UnreadCount: 0, // 管理员不计未读
CreatedAt: conversation.CreatedAt,
UpdatedAt: conversation.UpdatedAt,
}
return &dto, nil
}
// 普通用户需要是 participant
var row conversationRow
err := r.conversationQuery(ctx, principal).Where("c.id = ?", id).First(&row).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrConversationNotFound
}
return nil, err
}
participants, err := r.participants(ctx, row.ID)
if err != nil {
return nil, err
}
dto := row.toDTO(participants)
return &dto, nil
}
func (r *Repository) FindOrderConversation(ctx context.Context, userID uint64, orderID uint64) (*ConversationDTO, error) {
var row conversationRow
principal := Principal{Type: "user", ID: userID}
err := r.conversationQuery(ctx, principal).Where("c.order_id = ?", orderID).First(&row).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrConversationNotFound
}
return nil, err
}
participants, err := r.participants(ctx, row.ID)
if err != nil {
return nil, err
}
dto := row.toDTO(participants)
return &dto, nil
}
func (r *Repository) EnsureSupportConversation(ctx context.Context, userID uint64) (*ConversationDTO, error) {
var conversationID uint64
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var existing model.ChatConversation
err := tx.Table("chat_conversations AS c").
Select("c.*").
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id").
Where("c.type = ? AND cp.participant_type = ? AND cp.participant_id = ?", "general_support", "user", userID).
Order("c.id ASC").
Limit(1).
Find(&existing).Error
if err != nil {
return err
}
if existing.ID > 0 {
conversationID = existing.ID
return nil
}
now := time.Now()
conversation := model.ChatConversation{
Type: "general_support",
Title: "平台客服",
Status: "active",
}
if err := tx.Create(&conversation).Error; err != nil {
return err
}
participants := []model.ChatParticipant{
{
ConversationID: conversation.ID,
ParticipantType: "user",
ParticipantID: userID,
Role: "customer",
JoinedAt: now,
},
}
if supportID := defaultSupportAdminID(tx); supportID > 0 {
participants = append(participants, model.ChatParticipant{
ConversationID: conversation.ID,
ParticipantType: "admin",
ParticipantID: supportID,
Role: "support",
JoinedAt: now,
})
}
for _, participant := range participants {
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&participant).Error; err != nil {
return err
}
}
message := model.ChatMessage{
ConversationID: conversation.ID,
SenderType: "system",
SenderRole: "system",
ContentType: "system",
Content: "您好,客服会尽快回复,请直接描述您遇到的问题。",
AttachmentURLS: emptyJSONList(),
}
if err := tx.Create(&message).Error; err != nil {
return err
}
conversation.LastMessageID = &message.ID
conversation.LastMessagePreview = truncatePreview(message.Content)
conversation.LastMessageAt = &message.CreatedAt
if err := tx.Save(&conversation).Error; err != nil {
return err
}
conversationID = conversation.ID
return nil
})
if err != nil {
return nil, err
}
item, err := r.FindConversation(ctx, Principal{Type: "user", ID: userID}, conversationID)
if err != nil {
return nil, err
}
r.NotifyNewConversation(conversationID)
return item, nil
}