Files
hfb_sys/backend/internal/modules/chat/qrcode.go
T
yml 12f589f0df 二维码池批量上传与认证图片预览修复
- 后端新增批量创建二维码接口 POST /admin/chats/qrcodes/batch
- 前端二维码管理页支持多图拖拽批量上传
- 注册 qrcode 图片压缩场景(maxSide=2560, quality=0.95)
- 后端 normalizeScene 注册 qrcode scene
- 增强 AuthImage 组件支持 el-image 大图预览(previewSrcList)
- 修复 WithdrawalDetailDialog 提现凭证图片 401 问题
- 删除重复的 components/AuthImage.vue,统一使用共享组件
- 提交3剩余: 会话列表群类型图标、发布成功跳群、管理端路由等
2026-06-17 17:57:05 +08:00

258 lines
6.3 KiB
Go

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"`
}
// BatchCreateQrCodeRequest 批量创建二维码请求
type BatchCreateQrCodeRequest struct {
Items []CreateQrCodeRequest `json:"items" binding:"required,min=1,max=20"`
}
// 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
}
// BatchCreateQrCode 批量创建二维码
func (r *Repository) BatchCreateQrCode(ctx context.Context, adminID uint64, req BatchCreateQrCodeRequest) ([]model.ChatQrCode, error) {
var qrcodes []model.ChatQrCode
defaultExpires := time.Now().Add(7 * 24 * time.Hour)
for _, item := range req.Items {
qr := model.ChatQrCode{
ImageURL: item.ImageURL,
Status: QrCodeStatusUnused,
CreatedBy: adminID,
Note: item.Note,
ExpiresAt: item.ExpiresAt,
}
if qr.ExpiresAt == nil {
qr.ExpiresAt = &defaultExpires
}
qrcodes = append(qrcodes, qr)
}
if err := r.db.WithContext(ctx).Create(&qrcodes).Error; err != nil {
return nil, err
}
return qrcodes, 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
}