spark-server/internal/module/deliver/model/redis_queue.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

123 lines
2.6 KiB
Go

// Package model is deliver queue persistence (Redis lists and SETNX done keys).
package model
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
"spark-server/internal/module/deliver/entity"
"spark-server/internal/pkg/errs"
)
type RedisQueue struct {
rdb *redis.Client
queueKey string
processKey string
}
func NewRedisQueue(rdb *redis.Client, queueKey, processKey string) *RedisQueue {
if queueKey == "" {
queueKey = "deliver:queue"
}
if processKey == "" {
processKey = "deliver:processing"
}
return &RedisQueue{rdb: rdb, queueKey: queueKey, processKey: processKey}
}
func (q *RedisQueue) Push(ctx context.Context, job *entity.Job) error {
if q.rdb == nil {
return errs.ErrInternal
}
raw, err := json.Marshal(job)
if err != nil {
return err
}
return q.rdb.RPush(ctx, q.queueKey, raw).Err()
}
func (q *RedisQueue) Take(ctx context.Context, timeout time.Duration) (*entity.Job, error) {
if q.rdb == nil {
return nil, errs.ErrInternal
}
if timeout <= 0 {
timeout = 5 * time.Second
}
res, err := q.rdb.BRPopLPush(ctx, q.queueKey, q.processKey, timeout).Result()
if err == redis.Nil {
return nil, nil
}
if err != nil {
return nil, err
}
raw := []byte(res)
job := &entity.Job{}
if err := json.Unmarshal(raw, job); err != nil {
_ = q.rdb.LRem(ctx, q.processKey, 1, res).Err()
return nil, fmt.Errorf("deliver job: %w", err)
}
job.SetRaw(raw)
return job, nil
}
func (q *RedisQueue) Ack(ctx context.Context, job *entity.Job) error {
if q.rdb == nil || job == nil {
return nil
}
raw := job.Raw()
if len(raw) == 0 {
var err error
raw, err = json.Marshal(job)
if err != nil {
return err
}
}
return q.rdb.LRem(ctx, q.processKey, 1, raw).Err()
}
func (q *RedisQueue) RequeueStale(ctx context.Context) error {
if q.rdb == nil {
return nil
}
for {
_, err := q.rdb.RPopLPush(ctx, q.processKey, q.queueKey).Result()
if err == redis.Nil {
return nil
}
if err != nil {
return err
}
}
}
type RedisDone struct {
rdb *redis.Client
prefix string
ttl time.Duration
}
func NewRedisDone(rdb *redis.Client, prefix string, ttl time.Duration) *RedisDone {
if prefix == "" {
prefix = "deliver:done:"
}
if ttl <= 0 {
ttl = 10 * time.Minute
}
return &RedisDone{rdb: rdb, prefix: prefix, ttl: ttl}
}
func (d *RedisDone) Mark(ctx context.Context, msgID int64) (bool, error) {
if d == nil || d.rdb == nil || msgID <= 0 {
return true, nil
}
ok, err := d.rdb.SetNX(ctx, fmt.Sprintf("%s%d", d.prefix, msgID), "1", d.ttl).Result()
if err != nil {
return false, err
}
return ok, nil
}