新增完整的异步任务管理体系: 1. 新增Redis缓存层实现任务状态实时存储与降级机制 2. 实现并发拉取+批量写入的高性能入库流程 3. 添加任务管理HTTP接口与路由 4. 重构行情控制器使用共享SQLite存储 5. 配套添加Docker Compose Redis启动配置 6. 前端新增任务管理面板支持操作与进度查看
108 lines
3.1 KiB
Go
108 lines
3.1 KiB
Go
package cache
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
// keyPrefix Redis 键前缀,避免与其他应用冲突。
|
||
const keyPrefix = "stock-analysis:"
|
||
|
||
// taskKey 构造任务状态键。
|
||
func taskKey(id string) string { return keyPrefix + "task:" + id }
|
||
|
||
// activeSetKey 构造活跃任务集合键。
|
||
const activeSetKey = keyPrefix + "tasks:active"
|
||
|
||
// RedisCache 基于 go-redis/v9 的 Cache 实现。
|
||
type RedisCache struct {
|
||
client *redis.Client
|
||
}
|
||
|
||
// NewRedisCache 创建 Redis 缓存实例。
|
||
// addr 格式 "host:port",password 为空表示无密码,db 为 Redis 数据库编号。
|
||
// 配置较短的超时和重试次数,Redis 不可用时快速降级到 NoopCache。
|
||
func NewRedisCache(addr, password string, db int) (*RedisCache, error) {
|
||
client := redis.NewClient(&redis.Options{
|
||
Addr: addr,
|
||
Password: password,
|
||
DB: db,
|
||
DialTimeout: 2 * time.Second,
|
||
ReadTimeout: 2 * time.Second,
|
||
WriteTimeout: 2 * time.Second,
|
||
MaxRetries: 1,
|
||
PoolSize: 10,
|
||
})
|
||
return &RedisCache{client: client}, nil
|
||
}
|
||
|
||
// GetTask 从 Redis 读取任务状态(JSON 反序列化)。
|
||
func (c *RedisCache) GetTask(ctx context.Context, id string) (*Task, error) {
|
||
data, err := c.client.Get(ctx, taskKey(id)).Bytes()
|
||
if err != nil {
|
||
if err == redis.Nil {
|
||
return nil, nil // 不存在
|
||
}
|
||
return nil, fmt.Errorf("redis get task %s: %w", id, err)
|
||
}
|
||
var t Task
|
||
if err := json.Unmarshal(data, &t); err != nil {
|
||
return nil, fmt.Errorf("unmarshal task %s: %w", id, err)
|
||
}
|
||
return &t, nil
|
||
}
|
||
|
||
// SetTask 写入任务状态(JSON 序列化),设置 TTL 防止过期数据残留。
|
||
func (c *RedisCache) SetTask(ctx context.Context, t *Task, ttl time.Duration) error {
|
||
data, err := json.Marshal(t)
|
||
if err != nil {
|
||
return fmt.Errorf("marshal task %s: %w", t.ID, err)
|
||
}
|
||
if err := c.client.Set(ctx, taskKey(t.ID), data, ttl).Err(); err != nil {
|
||
return fmt.Errorf("redis set task %s: %w", t.ID, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// DeleteTask 删除任务状态。
|
||
func (c *RedisCache) DeleteTask(ctx context.Context, id string) error {
|
||
if err := c.client.Del(ctx, taskKey(id)).Err(); err != nil {
|
||
return fmt.Errorf("redis del task %s: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ListActiveTaskIDs 返回活跃任务集合中的所有 ID。
|
||
func (c *RedisCache) ListActiveTaskIDs(ctx context.Context) ([]string, error) {
|
||
ids, err := c.client.SMembers(ctx, activeSetKey).Result()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("redis smembers: %w", err)
|
||
}
|
||
return ids, nil
|
||
}
|
||
|
||
// AddActiveTask 将任务 ID 加入活跃集合。
|
||
func (c *RedisCache) AddActiveTask(ctx context.Context, id string) error {
|
||
if err := c.client.SAdd(ctx, activeSetKey, id).Err(); err != nil {
|
||
return fmt.Errorf("redis sadd %s: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// RemoveActiveTask 从活跃集合移除任务 ID。
|
||
func (c *RedisCache) RemoveActiveTask(ctx context.Context, id string) error {
|
||
if err := c.client.SRem(ctx, activeSetKey, id).Err(); err != nil {
|
||
return fmt.Errorf("redis srem %s: %w", id, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Ping 探测 Redis 连通性。
|
||
func (c *RedisCache) Ping(ctx context.Context) error {
|
||
return c.client.Ping(ctx).Err()
|
||
}
|