Files
backend/internal/cache/seq_buffer.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

106 lines
3.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cache
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
redisPkg "with_you/internal/pkg/redis"
)
const seqKeyTTL = 24 * time.Hour
// SeqBufferConfig seq 预分配配置(保留结构以兼容配置文件,实际不再使用缓冲)
type SeqBufferConfig struct {
Enabled bool `mapstructure:"enabled"`
PrivateBufferSize int `mapstructure:"private_buffer_size"`
GroupBufferSize int `mapstructure:"group_buffer_size"`
LockTimeoutMs int `mapstructure:"lock_timeout_ms"`
MaxRetries int `mapstructure:"max_retries"`
}
// SeqBufferManager seq 分配管理器(简化版:每条消息一个 Redis INCR无本地缓冲
type SeqBufferManager struct {
rdb *redis.Client
config SeqBufferConfig
repo ConversationRepository
msgRepo MessageRepository
}
// NewSeqBufferManager 创建 seq 分配管理器
func NewSeqBufferManager(rdb *redis.Client, cfg SeqBufferConfig, repo ConversationRepository, msgRepo MessageRepository) *SeqBufferManager {
return &SeqBufferManager{
rdb: rdb,
config: cfg,
repo: repo,
msgRepo: msgRepo,
}
}
// GetNextSeq 获取下一个 seq单次 Redis INCR原子且无竞态
func (m *SeqBufferManager) GetNextSeq(ctx context.Context, convID string, _ bool) (int64, error) {
seqKey := MessageSeqKey(convID)
// 1. 尝试 INCRkey 存在时直接递增,原子操作)
result, err := nextSeqLua.Run(ctx, m.rdb, []string{seqKey}).Int64()
if err == nil {
if result > 0 {
m.rdb.Expire(ctx, seqKey, seqKeyTTL)
return result, nil
}
// result == 0: 冷启动,需从 DB 初始化
return m.initAndIncr(ctx, convID, seqKey)
}
// Redis 不可用,降级到 DB
zap.L().Warn("seq INCR failed, falling back to DB", zap.String("convID", convID), zap.Error(err))
if m.msgRepo != nil {
if dbSeq, dbErr := m.msgRepo.GetNextSeq(convID); dbErr == nil {
return dbSeq, nil
}
}
return 0, fmt.Errorf("seq allocation failed for %s: %w", convID, err)
}
// initAndIncr 冷启动:从 DB 加载 last_seq然后用 SETNX+INCR 原子初始化
func (m *SeqBufferManager) initAndIncr(ctx context.Context, convID, seqKey string) (int64, error) {
if m.repo == nil {
return 0, fmt.Errorf("conversation repository not configured for seq init")
}
conv, err := m.repo.GetConversationByID(convID)
if err != nil {
return 0, fmt.Errorf("load conversation for seq init: %w", err)
}
initVal := conv.LastSeq
result, err := initSeqLua.Run(ctx, m.rdb, []string{seqKey}, initVal).Int64()
if err != nil {
return 0, fmt.Errorf("initSeqLua failed: %w", err)
}
m.rdb.Expire(ctx, seqKey, seqKeyTTL)
return result, nil
}
// InvalidateBuffer no-op保留接口兼容
func (m *SeqBufferManager) InvalidateBuffer(convID string) {}
// IsEnabled 是否启用
func (m *SeqBufferManager) IsEnabled() bool {
return m.config.Enabled && m.rdb != nil
}
// NewSeqBufferManagerFromRedisPkg 从 pkg/redis.Client 创建 SeqBufferManager
func NewSeqBufferManagerFromRedisPkg(redisPkgClient *redisPkg.Client, cfg SeqBufferConfig, repo ConversationRepository, msgRepo MessageRepository) *SeqBufferManager {
if redisPkgClient == nil {
zap.L().Warn("SeqBuffer: redis client is nil, seq pre-allocation disabled")
return &SeqBufferManager{config: cfg, repo: repo, msgRepo: msgRepo}
}
rdb := redisPkgClient.GetClient()
return NewSeqBufferManager(rdb, cfg, repo, msgRepo)
}