ChapterSplitAsyncListener.java

package com.yumu.noveltranslator.domain.service;

import com.yumu.noveltranslator.domain.model.CollabChapterTask;
import com.yumu.noveltranslator.domain.model.CollabProject;
import com.yumu.noveltranslator.domain.model.Document;
import com.yumu.noveltranslator.enums.ChapterTaskStatus;
import com.yumu.noveltranslator.enums.CollabProjectStatus;
import com.yumu.noveltranslator.enums.TranslationStatus;
import com.yumu.noveltranslator.domain.event.ChapterSplitEvent;
import com.yumu.noveltranslator.domain.service.CollabStateMachine;
import com.yumu.noveltranslator.port.out.CollaborationRepositoryPort;
import com.yumu.noveltranslator.port.out.DocumentRepositoryPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 章节拆分异步监听器
 * 接收 ChapterSplitEvent 事件,按批次异步插入章节,完成后激活项目。
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class ChapterSplitAsyncListener {

    private static final int BATCH_SIZE = 50;

    private final CollaborationRepositoryPort collaborationRepository;
    private final DocumentRepositoryPort documentRepository;
    private final CollabStateMachine collabStateMachine;

    @Async("chapterSplitExecutor")
    @EventListener
    public void onChapterSplitEvent(ChapterSplitEvent event) {
        Long projectId = event.getProjectId();
        List<String> chapters = event.getChapters();
        log.info("开始异步插入章节: projectId={}, totalChapters={}", projectId, chapters.size());

        try {
            int totalBatches = (int) Math.ceil((double) chapters.size() / BATCH_SIZE);
            for (int i = 0; i < chapters.size(); i += BATCH_SIZE) {
                int end = Math.min(i + BATCH_SIZE, chapters.size());
                List<String> batch = chapters.subList(i, end);
                int batchIndex = (i / BATCH_SIZE) + 1;
                log.debug("插入章节批次: projectId={}, batch={}/{}", projectId, batchIndex, totalBatches);
                insertChapterBatch(event, batch, i);
            }

            // 所有章节批次插入完成,激活项目
            activateProject(projectId, event.getDocumentId());
            log.info("章节异步插入完成: projectId={}, totalChapters={}", projectId, chapters.size());
        } catch (Exception e) {
            log.error("章节异步插入失败: projectId={}, error={}", projectId, e.getMessage(), e);
        }
    }

    @Transactional
    protected void insertChapterBatch(ChapterSplitEvent event, List<String> batch, int startIndex) {
        Long projectId = event.getProjectId();
        for (int i = 0; i < batch.size(); i++) {
            String chapterText = batch.get(i);
            CollabChapterTask chapter = new CollabChapterTask();
            chapter.setProjectId(projectId);
            chapter.setChapterNumber(startIndex + i + 1);
            chapter.setTitle("第 " + (startIndex + i + 1) + " 章");
            chapter.setSourceText(chapterText);
            chapter.setTargetText(null);
            chapter.setStatus(ChapterTaskStatus.UNASSIGNED.getValue());
            chapter.setProgress(0);
            chapter.setSourceWordCount(chapterText.length());
            collaborationRepository.saveChapterTask(chapter);
        }
        log.debug("批次插入成功: projectId={}, batchStart={}, count={}", projectId, startIndex + 1, batch.size());
    }

    @Transactional
    protected void activateProject(Long projectId, Long documentId) {
        CollabProject project = collaborationRepository.findProjectById(projectId).orElse(null);
        if (project == null) {
            log.error("激活项目失败: 项目不存在, projectId={}", projectId);
            return;
        }

        // 通过状态机将项目从 DRAFT 转换到 ACTIVE
        collabStateMachine.transitionProject(project, CollabProjectStatus.ACTIVE);
        project.setProgress(0);
        collaborationRepository.updateProject(project);

        // 更新文档状态为已完成
        Document doc = documentRepository.findById(documentId).orElse(null);
        if (doc != null) {
            doc.setStatus(TranslationStatus.COMPLETED.getValue());
            doc.setUpdateTime(LocalDateTime.now());
            documentRepository.update(doc);
        }

        log.info("项目已激活: projectId={}", projectId);
    }
}