package chat import ( "context" "errors" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "hfb_sys/backend/internal/model" ) // QrCodeStatus 二维码状态常量 const ( QrCodeStatusUnused = "unused" QrCodeStatusUsed = "used" QrCodeStatusDisabled = "disabled" ) var ( ErrQrCodeNotFound = errors.New("二维码不存在") ErrQrCodeCannotDelete = errors.New("已使用的二维码不能删除") ) // CreateQrCodeRequest 创建二维码请求 type CreateQrCodeRequest struct { ImageURL string `json:"image_url" binding:"required"` Note string `json:"note"` ExpiresAt *time.Time `json:"expires_at"` } // UpdateQrCodeRequest 更新二维码请求 type UpdateQrCodeRequest struct { Note *string `json:"note"` Status *string `json:"status"` ExpiresAt *time.Time `json:"expires_at"` } // QrCodeListRequest 列表查询请求 type QrCodeListRequest struct { Status string `form:"status"` Page int `form:"page"` Limit int `form:"limit"` } // QrCodeStats 二维码统计 type QrCodeStats struct { UnusedCount int64 `json:"unused_count"` UsedCount int64 `json:"used_count"` DisabledCount int64 `json:"disabled_count"` TotalCount int64 `json:"total_count"` } // CreateQrCode 创建二维码 func (r *Repository) CreateQrCode(ctx context.Context, adminID uint64, req CreateQrCodeRequest) (*model.ChatQrCode, error) { qrcode := model.ChatQrCode{ ImageURL: req.ImageURL, Status: QrCodeStatusUnused, CreatedBy: adminID, Note: req.Note, ExpiresAt: req.ExpiresAt, } // 如果未指定过期时间,默认7天后过期 if qrcode.ExpiresAt == nil { expires := time.Now().Add(7 * 24 * time.Hour) qrcode.ExpiresAt = &expires } if err := r.db.WithContext(ctx).Create(&qrcode).Error; err != nil { return nil, err } return &qrcode, nil } // ListQrCodes 列表查询二维码 func (r *Repository) ListQrCodes(ctx context.Context, req QrCodeListRequest) ([]model.ChatQrCode, int64, error) { if req.Page < 1 { req.Page = 1 } if req.Limit < 1 || req.Limit > 100 { req.Limit = 20 } query := r.db.WithContext(ctx).Model(&model.ChatQrCode{}) // 状态过滤 if req.Status != "" { query = query.Where("status = ?", req.Status) } // 统计总数 var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } // 查询列表 var qrcodes []model.ChatQrCode offset := (req.Page - 1) * req.Limit if err := query.Order("id DESC").Offset(offset).Limit(req.Limit).Find(&qrcodes).Error; err != nil { return nil, 0, err } return qrcodes, total, nil } // GetQrCodeStats 获取二维码统计信息 func (r *Repository) GetQrCodeStats(ctx context.Context) (*QrCodeStats, error) { stats := &QrCodeStats{} // 统计各状态数量 type CountResult struct { Status string Count int64 } var results []CountResult if err := r.db.WithContext(ctx). Model(&model.ChatQrCode{}). Select("status, COUNT(*) as count"). Group("status"). Find(&results).Error; err != nil { return nil, err } for _, r := range results { stats.TotalCount += r.Count switch r.Status { case QrCodeStatusUnused: stats.UnusedCount = r.Count case QrCodeStatusUsed: stats.UsedCount = r.Count case QrCodeStatusDisabled: stats.DisabledCount = r.Count } } return stats, nil } // UpdateQrCode 更新二维码 func (r *Repository) UpdateQrCode(ctx context.Context, id uint64, req UpdateQrCodeRequest) error { updates := make(map[string]interface{}) if req.Note != nil { updates["note"] = *req.Note } if req.Status != nil { // 校验状态值 if *req.Status != QrCodeStatusUnused && *req.Status != QrCodeStatusUsed && *req.Status != QrCodeStatusDisabled { return errors.New("无效的状态值") } updates["status"] = *req.Status } if req.ExpiresAt != nil { updates["expires_at"] = req.ExpiresAt } if len(updates) == 0 { return nil } result := r.db.WithContext(ctx).Model(&model.ChatQrCode{}).Where("id = ?", id).Updates(updates) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return ErrQrCodeNotFound } return nil } // DeleteQrCode 删除二维码(仅未使用的可删除) func (r *Repository) DeleteQrCode(ctx context.Context, id uint64) error { var qrcode model.ChatQrCode if err := r.db.WithContext(ctx).First(&qrcode, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrQrCodeNotFound } return err } // 已使用的不能删除 if qrcode.Status == QrCodeStatusUsed { return ErrQrCodeCannotDelete } return r.db.WithContext(ctx).Delete(&qrcode).Error } // fetchUnusedQrCode 获取一个未使用且未过期的二维码(带行锁) func (r *Repository) fetchUnusedQrCode(tx *gorm.DB) (*model.ChatQrCode, error) { var qrcode model.ChatQrCode now := time.Now() err := tx.Where("status = ?", QrCodeStatusUnused). Where("expires_at IS NULL OR expires_at > ?", now). Order("id ASC"). Limit(1). Clauses(clause.Locking{Strength: "UPDATE"}). First(&qrcode).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil // 无可用二维码,返回 nil 而非错误 } return nil, err } return &qrcode, nil } // markQrCodeAsUsed 标记二维码为已使用 func (r *Repository) markQrCodeAsUsed(tx *gorm.DB, qrcodeID uint64, conversationID uint64) error { now := time.Now() return tx.Model(&model.ChatQrCode{}). Where("id = ?", qrcodeID). Updates(map[string]interface{}{ "status": QrCodeStatusUsed, "conversation_id": conversationID, "used_at": now, }).Error }