feat(generation): migrate article generation and template-assist to RabbitMQ queues

Replace in-process generation with queue-based async execution via
RabbitMQ. Add generation task runtime for lifecycle management, article
generation payload/queue abstractions, and template-assist queue publisher.
Refactor prompt generation service to support batch generation with
per-item error tracking. Update stream hub to bridge queue events to SSE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 14:20:26 +08:00
parent e8f48c6d37
commit ce13331e26
14 changed files with 951 additions and 173 deletions
+27 -18
View File
@@ -120,6 +120,8 @@ type assistJob struct {
OutlineRequest *OutlineTaskRequest
}
type AssistJob = assistJob
func (s *TemplateService) CreateAnalyzeTask(ctx context.Context, templateID int64, req AnalyzeTaskRequest) (*AssistTaskCreateResponse, error) {
actor := auth.MustActor(ctx)
@@ -164,7 +166,7 @@ func (s *TemplateService) CreateAnalyzeTask(ctx context.Context, templateID int6
AnalyzePrompt: extractAnalyzePromptTemplate(templateRecord.CardConfigJSON),
AnalyzeRequest: &req,
}
if err := s.enqueueAssist(job); err != nil {
if err := publishTemplateAssistTask(ctx, s.rabbitMQ, job); err != nil {
_ = repo.FailTask(context.Background(), taskID, templateAssistTaskTypeAnalyze, actor.TenantID, err.Error(), time.Now())
return nil, response.ErrServiceUnavailable(50312, "analyze_queue_unavailable", "analyze queue is busy, please retry")
}
@@ -245,7 +247,7 @@ func (s *TemplateService) CreateTitleTask(ctx context.Context, templateID int64,
TitlePrompt: extractTitlePromptTemplate(templateRecord.CardConfigJSON),
TitleRequest: &req,
}
if err := s.enqueueAssist(job); err != nil {
if err := publishTemplateAssistTask(ctx, s.rabbitMQ, job); err != nil {
_ = repo.FailTask(context.Background(), taskID, templateAssistTaskTypeTitle, actor.TenantID, err.Error(), time.Now())
return nil, response.ErrServiceUnavailable(50313, "title_queue_unavailable", "title queue is busy, please retry")
}
@@ -326,7 +328,7 @@ func (s *TemplateService) CreateOutlineTask(ctx context.Context, templateID int6
OutlinePrompt: extractOutlinePromptTemplate(templateRecord.CardConfigJSON),
OutlineRequest: &req,
}
if err := s.enqueueAssist(job); err != nil {
if err := publishTemplateAssistTask(ctx, s.rabbitMQ, job); err != nil {
_ = repo.FailTask(context.Background(), taskID, templateAssistTaskTypeOutline, actor.TenantID, err.Error(), time.Now())
return nil, response.ErrServiceUnavailable(50314, "outline_queue_unavailable", "outline queue is busy, please retry")
}
@@ -363,21 +365,6 @@ func (s *TemplateService) GetOutlineTask(ctx context.Context, templateID int64,
return resp, nil
}
func (s *TemplateService) enqueueAssist(job assistJob) error {
select {
case s.assistJobs <- job:
return nil
default:
return fmt.Errorf("assist queue is full")
}
}
func (s *TemplateService) runAssistWorker() {
for job := range s.assistJobs {
s.executeAssist(context.Background(), job)
}
}
func (s *TemplateService) executeAssist(ctx context.Context, job assistJob) {
repo := repository.NewTemplateAssistTaskRepository(s.pool)
now := time.Now()
@@ -395,6 +382,28 @@ func (s *TemplateService) executeAssist(ctx context.Context, job assistJob) {
}
}
func (s *TemplateService) ProcessQueuedAssist(ctx context.Context, job assistJob) {
s.executeAssist(ctx, job)
}
func (s *TemplateService) ProcessQueuedAssistJob(ctx context.Context, job AssistJob) {
s.ProcessQueuedAssist(ctx, job)
}
func (s *TemplateService) IsAssistTaskTerminal(ctx context.Context, job AssistJob) (bool, error) {
record, err := repository.NewTemplateAssistTaskRepository(s.pool).GetTask(
ctx,
job.TaskID,
job.TaskType,
job.TenantID,
job.TemplateID,
)
if err != nil {
return false, err
}
return record.Status == "completed" || record.Status == "failed", nil
}
func (s *TemplateService) executeAnalyzeTask(ctx context.Context, repo repository.TemplateAssistTaskRepository, job assistJob) {
if job.AnalyzeRequest == nil {
s.failAssist(ctx, job.TaskID, job.TaskType, job.TenantID, fmt.Errorf("missing analyze request"))