✨ 我是 Muzi 的「文章捕手」,擅长在文字的星海中打捞精华。每当新的篇章诞生,我就会像整理贝壳一样,将思想的闪光点串成珍珠项链~

本次开发日记记录了Thesis Project Generator项目的两大核心更新:一是将文件存储方案由MinIO替换为腾讯COS,实现存储方案统一,包含配置文件更新、配置类新增及存储接口和实现的重构;二是重构AI客户端架构,新增支持自定义中转站API(如OneAPI、NewAPI、Groq等),通过OpenAiCompatibleLlmClient实现灵活调用多种AI模型。后端核心功能模块均已完成,支持多智能体协作,整体项目进度达约92%。文中还详述了环境变量配置、遇到的问题及解决方案,并规划了后续集成测试、数据库迁移和安全审计等工作,提升系统的稳定性和扩展性。

# Thesis Project Generator - 开发日记 #4

# 前言

本次开发完成了两个重要任务:1) 将 MinIO 替换为腾讯 COS,统一文件存储方案;2) 重构 AI 客户端架构,支持自定义中转站 API。同时对后端整体完成度进行了分析确认。


# 一、技术栈统一分析

# 1.1 问题发现

在审查项目时发现以下不一致:

  • CLAUDE.md 文档写 PostgreSQL,实际使用 MySQL
  • MinIO 配置存在但代码中只有 TODO 注释,从未真正实现
  • 用户建议使用腾讯 COS 替代 MinIO

# 1.2 决定方案

组件 原计划 实际方案
数据库 PostgreSQL ❌ MySQL 8.0 ✅
文件存储 MinIO ❌ 腾讯 COS ✅

# 二、MinIO 替换为腾讯 COS

# 2.1 配置文件更新

application.yml

# COS 配置
cos:
  region: ${COS_REGION:ap-guangzhou}
  secret-id: ${COS_SECRET_ID:}
  secret-key: ${COS_SECRET_KEY:}
  bucket: ${COS_BUCKET:thesis-generator}
  timeout: ${COS_TIMEOUT:5000}

.env.example

# COS 配置
COS_REGION=ap-guangzhou
COS_SECRET_ID=your_cos_secret_id
COS_SECRET_KEY=your_cos_secret_key
COS_BUCKET=thesis-generator

# 2.2 新增配置类

CosConfig.java

@Data
@Component
@ConfigurationProperties(prefix = "cos")
public class CosConfig {
    private String region = "ap-guangzhou";
    private String secretId;
    private String secretKey;
    private String bucket = "thesis-generator";
    private int timeout = 5000;
}

# 2.3 领域层存储接口

IFileStoragePort.java

public interface IFileStoragePort {
    String uploadFile(String fileName, InputStream inputStream, long size, String contentType);
    InputStream downloadFile(String filePath);
    void deleteFile(String filePath);
    String getFileUrl(String filePath);
}

# 2.4 COS 存储实现

CosStoragePortImpl.java

@Slf4j
@Component
@RequiredArgsConstructor
public class CosStoragePortImpl implements IFileStoragePort {
    private final CosConfig cosConfig;

    @Override
    public String uploadFile(String fileName, InputStream inputStream, long size, String contentType) {
        COSClient cosClient = createCOSClient();
        try {
            String key = "papers/" + UUID.randomUUID().toString() + "_" + fileName;

            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(size);
            metadata.setContentType(contentType);

            PutObjectRequest putObjectRequest = new PutObjectRequest(
                cosConfig.getBucket(), key, inputStream, metadata
            );

            PutObjectResult result = cosClient.putObject(putObjectRequest);
            log.info("文件上传成功: {}, ETag: {}", key, result.getETag());

            return key;
        } finally {
            cosClient.shutdown();
        }
    }
}

# 2.5 PaperController 集成

原代码:

// TODO: 保存文件到MinIO
String filePath = "/uploads/" + file.getOriginalFilename();

更新后:

private final IFileStoragePort fileStoragePort;

// uploadPaper 方法中
String filePath = fileStoragePort.uploadFile(
    file.getOriginalFilename(),
    file.getInputStream(),
    file.getSize(),
    file.getContentType()
);

# 2.6 docker-compose.yml 更新

移除 MinIO 服务,添加 COS 环境变量:

```bashironment:
  - COS_REGION=${COS_REGION:-ap-guangzhou}
  - COS_SECRET_ID=${COS_SECRET_ID:-}
  - COS_SECRET_KEY=${COS_SECRET_KEY:-}
  - COS_BUCKET=${COS_BUCKET:-thesis-generator}

# 三、AI 中转站支持

# 3.1 需求背景

用户需要支持自定义中转站(如 OneAPI、NewAPI、Groq 等)来调用 AI 模型,而非仅限于官方 API。

# 3.2 新增 OpenAiCompatibleLlmClient

@Slf4j
@Component
@RequiredArgsConstructor
public class OpenAiCompatibleLlmClient {
    private final AiConfig aiConfig;

    public String chat(IAiModelPort.InvokeRequest request) {
        String baseUrl = resolveBaseUrl();
        String endpoint = resolveEndpoint();

        WebClient webClient = WebClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader("Authorization", "Bearer " + aiConfig.getApiKey())
            .defaultHeader("Content-Type", "application/json")
            .build();

        Map<String, Object> requestBody = buildRequestBody(request);

        String response = webClient.post()
            .uri(endpoint)
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(requestBody)
            .retrieve()
            .bodyToMono(String.class)
            .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
                .filter(this::isRetryableException))
            .block(Duration.ofSeconds(60));

        return parseResponse(response);
    }

    private String resolveBaseUrl() {
        // 优先使用配置的 baseUrl
        if (hasCustomBaseUrl()) {
            return aiConfig.getBaseUrl().trim();
        }
        // 否则根据 provider 使用默认地址
        return switch (aiConfig.getProvider().toLowerCase()) {
            case "claude" -> "https://api.anthropic.com";
            case "qwen" -> "https://dashscope.aliyuncs.com/compatible-mode/v1";
            default -> "https://api.openai.com/v1";
        };
    }
}

# 3.3 LlmGateway 路由优化

public String chat(IAiModelPort.InvokeRequest request) {
    // 如果配置了 AI_BASE_URL,优先使用 OpenAI 兼容客户端
    if (hasCustomBaseUrl()) {
        log.info("使用自定义 baseUrl: {}", aiConfig.getBaseUrl());
        return customClient.chat(request);
    }

    // 否则根据 provider 调用对应的官方客户端
    String provider = aiConfig.getProvider().toLowerCase();
    return switch (provider) {
        case "claude" -> claudeClient.chat(request);
        case "qwen" -> qwenClient.chat(request);
        default -> customClient.chat(request);
    };
}

# 3.4 配置方式

# 方式一:官方 API

AI_PROVIDER=claude
AI_API_KEY=sk-ant-xxx
AI_MODEL=claude-sonnet-4-20250514
# 不设置 AI_BASE_URL

# 方式二:自定义中转站

AI_BASE_URL=https://your-proxy.com/v1
AI_MODEL=gpt-4-turbo
AI_API_KEY=sk-xxx

# 3.5 支持的中转站

  • OneAPI
  • NewAPI
  • Groq
  • LocalAI
  • 任意 OpenAI 兼容 API

# 四、后端完成度分析

# 4.1 核心功能确认 ✅

模块 Controller 状态
用户认证 AuthController ✅ 完成
用户管理 UserController ✅ 完成
项目管理 ProjectController ✅ 完成
模板管理 TemplateController ✅ 完成
论文解析 PaperController ✅ 完成
AI生成 GenerationController ✅ 完成
管理后台 AdminController ✅ 完成
模板管理 AdminTemplateController ✅ 完成

# 4.2 AI 多智能体确认 ✅

CoordinatorAgent      ✅
ArchitectAgent       ✅
FrontendDevAgent     ✅
BackendDevAgent      ✅
ReviewerAgent        ✅
LlmGateway          ✅ (Claude/Qwen/中转站)

# 4.3 非核心 TODO (不影响 MVP)

位置 问题 影响
TemplateController:58 generateFromTemplate 可后续对接
GenerationController:94 cancelJob 可后续添加
ProjectController:216 getVersionArtifacts 已返回空列表
PaperController:117 updateAnalysis 非核心功能
PaperController:126 generateFromPaper 可后续实现

# 4.4 结论

后端核心功能已完全开发完成,MVP 阶段可以使用。


# 五、项目进度总览

阶段 名称 状态 进度
M1 基础架构搭建 ✅ 已完成 100%
M2 核心功能开发 ✅ 已完成 ~96%
M3 AI 功能集成 ✅ 已完成 ~92%
M4 模板与后台 ✅ 已完成 ~85%
M5 测试与优化 🔄 进行中 ~60%

总体进度: ~92%


# 六、环境变量配置总结

# .env.example AI 配置

# AI 配置
# 方式一:官方 API(不配置 BASE_URL 时使用)
AI_PROVIDER=openai  # openai / claude / qwen
AI_API_KEY=your_api_key
AI_MODEL=gpt-4-turbo-preview

# 方式二:自定义中转站(配置 BASE_URL 后优先使用)
# AI_BASE_URL=https://your-proxy.com/v1
# AI_MODEL=your-model-name

AI_MAX_TOKENS=8192
AI_TEMPERATURE=0.7

# COS 配置
COS_REGION=ap-guangzhou
COS_SECRET_ID=your_cos_secret_id
COS_SECRET_KEY=your_cos_secret_key
COS_BUCKET=thesis-generator

# 遇到的问题

# 1. docker-compose.yml 编辑出错

编辑时出现重复的 MinIO 服务定义,导致 YAML 格式错误。

解决:重写整个文件。

# 2. CosStoragePortImpl 中流关闭问题

下载文件后不应关闭 cosClient,让调用方处理流。

解决:在 finally 中不调用 shutdown()。


# 后续计划

# P0 优先级

# P1 优先级


项目地址:Thesis Project Generator