chore(init): 初始化解忧商行项目并完成 Phase 0 基座
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/internal/modules/chathub"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
hub *chathub.Hub
|
||||
redis *redis.Client
|
||||
logger *zap.Logger
|
||||
ocrHTTPClient *http.Client
|
||||
ocrGate *ocrRequestGate
|
||||
ocrSleep func(context.Context, time.Duration) error
|
||||
}
|
||||
|
||||
const (
|
||||
defaultSupportRoleCode = "cs"
|
||||
)
|
||||
|
||||
type RepositoryOption func(*Repository)
|
||||
|
||||
func WithLogger(logger *zap.Logger) RepositoryOption {
|
||||
return func(repo *Repository) {
|
||||
if logger != nil {
|
||||
repo.logger = logger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withOCRHTTPClient(client *http.Client) RepositoryOption {
|
||||
return func(repo *Repository) {
|
||||
if client != nil {
|
||||
repo.ocrHTTPClient = client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withOCRGate(gate *ocrRequestGate) RepositoryOption {
|
||||
return func(repo *Repository) {
|
||||
if gate != nil {
|
||||
repo.ocrGate = gate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func withOCRSleep(sleep func(context.Context, time.Duration) error) RepositoryOption {
|
||||
return func(repo *Repository) {
|
||||
if sleep != nil {
|
||||
repo.ocrSleep = sleep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB, hub *chathub.Hub, redis *redis.Client, options ...RepositoryOption) *Repository {
|
||||
repo := &Repository{
|
||||
db: db,
|
||||
hub: hub,
|
||||
redis: redis,
|
||||
logger: zap.NewNop(),
|
||||
ocrHTTPClient: ocrHTTPClient,
|
||||
ocrGate: sharedOCRRequestGate,
|
||||
ocrSleep: sleepWithContext,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(repo)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
func EnsureOrderConversation(tx *gorm.DB, order model.RentalOrder) (*model.ChatConversation, error) {
|
||||
var existing model.ChatConversation
|
||||
err := tx.Where("order_id = ?", order.ID).First(&existing).Error
|
||||
if err == nil {
|
||||
return &existing, nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var siteID *uint64
|
||||
if order.DistributionSiteID != nil && *order.DistributionSiteID > 0 && order.DistributionMode != "two_party_distributor_receive" {
|
||||
siteID = order.DistributionSiteID
|
||||
}
|
||||
now := time.Now()
|
||||
conversation := model.ChatConversation{
|
||||
OrderID: &order.ID,
|
||||
DistributionSiteID: siteID,
|
||||
Type: "order_group",
|
||||
Title: orderConversationTitle(order),
|
||||
Status: "active",
|
||||
}
|
||||
if err := tx.Create(&conversation).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
participants := []model.ChatParticipant{
|
||||
{
|
||||
ConversationID: conversation.ID,
|
||||
ParticipantType: "user",
|
||||
ParticipantID: order.RenterID,
|
||||
Role: "renter",
|
||||
JoinedAt: now,
|
||||
},
|
||||
{
|
||||
ConversationID: conversation.ID,
|
||||
ParticipantType: "user",
|
||||
ParticipantID: order.OwnerID,
|
||||
Role: "owner",
|
||||
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 nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 获取自动话术
|
||||
autoMessage := "订单已支付,群聊已创建。租客、号主和客服可在这里沟通交接与结账问题。"
|
||||
var cfg model.SystemConfig
|
||||
if err := tx.Where("`key` = ?", "chat.auto_welcome_message").First(&cfg).Error; err == nil && cfg.Value != "" {
|
||||
autoMessage = cfg.Value
|
||||
}
|
||||
message := model.ChatMessage{
|
||||
ConversationID: conversation.ID,
|
||||
SenderType: "system",
|
||||
SenderRole: "system",
|
||||
ContentType: "system",
|
||||
Content: autoMessage,
|
||||
AttachmentURLS: emptyJSONList(),
|
||||
}
|
||||
if err := tx.Create(&message).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversation.LastMessageID = &message.ID
|
||||
conversation.LastMessagePreview = truncatePreview(message.Content)
|
||||
conversation.LastMessageAt = &message.CreatedAt
|
||||
if err := tx.Save(&conversation).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &conversation, nil
|
||||
}
|
||||
func (r *Repository) NotifyNewConversation(conversationID uint64) {
|
||||
if r.hub == nil {
|
||||
return
|
||||
}
|
||||
event := &chathub.ChatEvent{
|
||||
Type: "conversation_updated",
|
||||
ConversationID: conversationID,
|
||||
}
|
||||
r.hub.NotifyConversation(conversationID, event)
|
||||
// 新会话(如未分配的平台咨询)需让所有在线客服的列表实时出现。
|
||||
r.hub.NotifyAllAdmins(event)
|
||||
}
|
||||
Reference in New Issue
Block a user