// Package research 提供深度研究任务的业务编排(service 层)。 // // 该层与具体存储/队列解耦:依赖 Repository(任务持久化)、Queue(任务下发)、 // Cache(快速状态)三个接口,便于单测与替换实现。HTTP handler 仅做参数解析与 // 响应,业务规则集中在这里。 package research import ( "context" "errors" "strings" "time" "github.com/google/uuid" ) // Task 是研究任务的领域模型(用于查询/列表返回)。 type Task struct { ID string Topic string Status string Progress int StatusMessage *string ErrorMessage *string Report *string Sources []byte // 原始 JSON([{title,url,snippet}]) TokensUsed int CreatedAt time.Time } // CreateInput 创建研究任务的入参。 type CreateInput struct { UserID string AppID *string Topic string Config map[string]any } // CachedStatus 来自 Redis 的快速状态(worker 实时写入)。 type CachedStatus struct { Status string Progress int Message string } // Repository 任务持久化接口。 type Repository interface { Insert(ctx context.Context, id, userID string, appID *string, topic string, config map[string]any) error Get(ctx context.Context, userID, taskID string) (*Task, error) List(ctx context.Context, userID string, limit int) ([]Task, error) // Cancel 仅取消进行中的任务;found 表示是否有可取消的任务被更新。 Cancel(ctx context.Context, userID, taskID string) (found bool, err error) } // Queue 任务下发接口(worker 消费)。 type Queue interface { Enqueue(ctx context.Context, taskID string) error } // Cache 快速状态读取接口(可选)。 type Cache interface { GetStatus(ctx context.Context, taskID string) (*CachedStatus, bool) } var ( ErrEmptyTopic = errors.New("研究题目不能为空") ErrNotFound = errors.New("任务不存在") ) type Service struct { repo Repository queue Queue cache Cache // 可为 nil } func NewService(repo Repository, queue Queue, cache Cache) *Service { return &Service{repo: repo, queue: queue, cache: cache} } // Create 校验入参、落库并下发到队列,返回任务 ID。 func (s *Service) Create(ctx context.Context, in CreateInput) (string, error) { topic := strings.TrimSpace(in.Topic) if topic == "" { return "", ErrEmptyTopic } id := uuid.New().String() if err := s.repo.Insert(ctx, id, in.UserID, in.AppID, topic, in.Config); err != nil { return "", err } if err := s.queue.Enqueue(ctx, id); err != nil { return "", err } return id, nil } // Status 返回任务(先按所有权从库中取,再用 Redis 快速状态覆盖以保证新鲜度)。 func (s *Service) Status(ctx context.Context, userID, taskID string) (*Task, error) { t, err := s.repo.Get(ctx, userID, taskID) if err != nil { return nil, err } if s.cache != nil { if cs, ok := s.cache.GetStatus(ctx, taskID); ok { t.Status = cs.Status t.Progress = cs.Progress if cs.Message != "" { msg := cs.Message t.StatusMessage = &msg } } } return t, nil } // List 返回用户最近的研究任务。 func (s *Service) List(ctx context.Context, userID string) ([]Task, error) { return s.repo.List(ctx, userID, 50) } // Cancel 取消进行中的任务;任务不存在/不可取消时返回 ErrNotFound。 func (s *Service) Cancel(ctx context.Context, userID, taskID string) error { found, err := s.repo.Cancel(ctx, userID, taskID) if err != nil { return err } if !found { return ErrNotFound } return nil }