1. 概述

1.1 text-summarizer

一个基于 Spring AI Alibaba 框架的文本摘要微服务,核心能力:

  • 接收一段文本,自动提炼摘要
  • 接收一个文件(PDF/Word/TXT等),自动提取文本后生成摘要
  • 背后调用阿里云 DashScope 平台上的 通义千问‑turbo 模型

代码总量仅 4 个文件,完美演绎“最小可行 AI 服务”的构建范式。

1.2 项目结构一览

spring-ai-alibaba-text-summarizer-example/
├── pom.xml                          # Maven 构建配置(依赖父POM)
└── src/main/
    ├── java/com/alibaba/example/summarizer/
    │   ├── SummarizerApplication.java     # 启动入口
    │   └── controller/
    │       └── SummaryController.java     # REST API(核心逻辑)
    └── resources/
        └── application.yml                # 应用配置

1.3 技术栈速览

组件 作用
Spring Boot 3.x 应用框架
Spring AI Alibaba (DashScope Starter) 适配阿里云通义千问,自动创建 ChatClient
Spring AI Tika Document Reader 文档内容提取(依赖中声明,代码中未实际使用)
Apache Tika 底层解析引擎(支持 1000+ 文件格式)
Maven 构建管理

📌 核心亮点:通过 ChatClient 和一句 Prompt,就能把大模型能力嵌入到 Spring Boot 应用中。整个调用链被高度抽象,开发者几乎只需关注业务逻辑。


2. 逐文件深度解析

2.1 pom.xml – 构建基石

<parent>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-examples</artifactId>
    <version>${revision}</version>
    <relativePath>../../pom.xml</relativePath>
</parent>
继承关系与版本管理

Maven 的继承体系确保了所有示例模块共享同一个版本号。层次如下:

项目结构

依赖项

子模块

继承

依赖

依赖

依赖

🔗 spring-ai-alibaba-starter-dashscope:1.0.0.2

📦 spring-ai-alibaba-examples
父 POM
(Revision: 1.0.0)

⚙️ text-summarizer-example

🔗 spring-boot-starter-web

🔗 spring-ai-tika-document-reader

这里实际上形成了两层依赖:

  • 父 POM 控制全局版本(如 spring-ai-alibaba.version=1.0.0.2
  • 子模块 只需声明 artifactId,版本自动继承

⚠️ 问题 1:示例声称依赖 spring-ai-tika-document-reader,但控制器中并未注入 TikaDocumentReader。这是一个死依赖,仅代表初衷是支持文件上传解析,但未完成实现(文件摘要方法返回硬编码文本)。

替代方案:实际部署时可改用原生 tika-core,更轻量、更可控。详细修正见第 3 节。

2.2 application.yml – 配置与密钥安全

server:
  port: 18080

spring:
  ai:
    dash-scope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-turbo
关键配置原理解析
  • api-key: ${AI_DASHSCOPE_API_KEY}
    使用 Spring 的属性占位符从环境变量中注入密钥,保证敏感信息不落入代码仓库。
    在 Spring AI Alibaba 自动配置过程中:

    ApplicationContext DashScopeAutoConfiguration 环境变量 ApplicationContext DashScopeAutoConfiguration 环境变量 读取 AI_DASHSCOPE_API_KEY 创建 DashScopeApi(apiKey) 注册 DashScopeChatModel Bean 注册 ChatClient.Builder Bean
  • model: qwen-turbo
    Turbo 版本响应极快、成本最低,是摘要场景(信息压缩)的性价比之选。

缺少的关键参数
参数 摘要场景建议值 作用
temperature 0.2 ~ 0.3 越低输出越稳定,减少随机性
max-tokens 1024 ~ 2048 防止模型无限生成,控制成本
top-p 0.8 ~ 0.9 采样概率阈值,微调创造力
connect-timeout / read-timeout 30s / 300s 避免长时间挂起

⚠️ 问题 2:原配置缺失这些参数,可能导致输出不稳定或超时无响应。修正方案见第 3 节。

2.3 SummarizerApplication.java – 启动入口

@SpringBootApplication
public class SummarizerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SummarizerApplication.class, args);
    }
}

标准 Spring Boot 3 启动类,通过 @SpringBootApplication 触发自动配置和组件扫描。没有问题。

2.4 SummaryController.java – 核心 API 实现

这是整个项目的核心,也是问题最多的地方。

2.4.1 原始代码全貌
@RestController
public class SummaryController {

    private final ChatClient chatClient;

    public SummaryController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @PostMapping("/api/v1/summarize/text")
    public String textSummarize(@RequestParam("text") String text) {
        return chatClient.prompt()
                .user(user -> user.text("请帮我总结以下内容: %s".formatted(text)))
                .call()
                .content();
    }

    @PostMapping("/api/v1/summarize/file")
    public String fileSummarize(@RequestParam("file") MultipartFile file) {
        String text = extractTextFromFile(file);
        return chatClient.prompt()
                .user(user -> user.text("请帮我总结以下内容: %s".formatted(text)))
                .call()
                .content();
    }

    private String extractTextFromFile(MultipartFile file) {
        // TODO To be implemented
        return "Text content to be extracted from file.";
    }
}
2.4.2 ChatClient 调用链路
DashScope REST API DashScopeChatModel ChatClient Controller DashScope REST API DashScopeChatModel ChatClient Controller prompt().user(promptText).call() call(Prompt) POST /text-generation/generation (model=qwen-turbo, messages...) ChatResponse { output: {text: "摘要..."}, usage:... } ChatResponse content() 返回文本

整个过程像一个精心封装的黑箱,开发者只需要两行 Java 代码就能调用大模型。

2.4.3 两大 API 端点
  • POST /api/v1/summarize/text:接受 text 参数(application/x-www-form-urlencoded 或 query string)。
  • POST /api/v1/summarize/file:接受 file 参数(multipart/form-data),但文本提取是假实现。

3. 原始代码的问题分析与修正方案

🔴 严重问题

问题 1:文本接口参数绑定不符合 REST 惯例

@RequestParam("text") 只能处理 application/x-www-form-urlencoded 或 URL 查询参数。
多数前端和 Postman 默认发送 application/json,此时会报 400 错。

修正(推荐用 DTO + @RequestBody)

public record SummarizeRequest(String text) {}

@PostMapping("/text")
public ResponseEntity<ApiResponse> textSummarize(@RequestBody SummarizeRequest request) {
    String text = request.text();
    // ... 校验与调用
}
问题 2:文件摘要是假功能

extractTextFromFile() 返回硬编码字符串,无论上传什么文件,AI 收到的永远是“Text content to be extracted from file.”。

修正(二选一)

  • 方案 A:使用 Tika 原生解析(推荐)
private final Tika tika = new Tika();   // 需添加 org.apache.tika:tika-core 依赖

private String extractTextFromFile(MultipartFile file) throws IOException {
    try (InputStream is = file.getInputStream()) {
        return tika.parseToString(is);
    }
}
  • 方案 B:使用 Spring AI 的 TikaDocumentReader
private String extractTextFromFile(MultipartFile file) throws IOException {
    Resource resource = new InputStreamResource(file.getInputStream());
    TikaDocumentReader reader = new TikaDocumentReader(resource);
    return reader.get().stream()
                 .map(Document::getText)
                 .collect(Collectors.joining("\n"));
}
问题 3:缺少健壮性设计
  • 无输入校验(空文本、超长文本)
  • AI 调用异常直接抛出 500,返回堆栈给客户端
  • 响应结构单一,无法区分业务成功和系统失败

修正——结构化错误处理

public record ApiResponse(String summary, String error) {}

// 在控制器方法中捕获异常并返回
try {
    String summary = chatClient.prompt()...content();
    return ResponseEntity.ok(new ApiResponse(summary, null));
} catch (Exception e) {
    log.error("AI 调用失败", e);
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ApiResponse(null, "摘要生成失败: " + e.getMessage()));
}

🟡 中等问题

问题 4:Prompt 过于简陋

原始 Prompt:"请帮我总结以下内容: %s",没有角色、格式、长度限制。

优化后的 Prompt

private String buildSummaryPrompt(String text) {
    return """
        你是专业的文本摘要助手。请按以下要求生成摘要:
        1. 使用简洁中文
        2. 长度 150‑300 字
        3. 保留核心观点和数据,不添加主观评价
        4. 保持原文逻辑层次
        ---
        %s
        ---
        """.formatted(text);
}
问题 5:文件上传缺少限制

未限制文件大小和类型,可能导致 OOM 或解析异常。

全局配置(application.yml

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

代码层二次校验(防御性编程):

if (file.getSize() > 10 * 1024 * 1024) {
    throw new IllegalArgumentException("文件过大,最大支持 10MB");
}

🟢 小改进

  • 记录 AI 调用耗时和 token 用量
  • 支持异步非阻塞调用(配合 Tomcat NIO)
  • 提供健康检查端点 /health

4. 替代方案对比与选择指南

方案 技术栈 特点 适合场景
A(当前) Spring AI Alibaba + 通义千问 国内合规、中文最佳、Spring 无缝集成 国内生产环境,中文业务
B LangChain4j + 任意模型 模型无关,链式编排灵活 复杂 RAG、Agent 编排
C Spring AI + Ollama + 本地模型 免费、数据不出域 内部工具、数据安全场景
D Spring AI + DeepSeek API 兼容 OpenAI 格式,成本极低 追求极致性价比

推荐

  • 国内生产环境 → 方案 A
  • 希望更灵活、未来要扩展 RAG/Agent → 方案 B
  • 数据不允许出内网 → 方案 C

5. 从零到一:部署实操步骤

这里提供脱离父 POM 依赖的独立部署方法,避免因父模块依赖问题导致编译失败。

5.1 环境准备

  • JDK 17+
  • Maven 3.6+
  • 阿里云 DashScope API Key(获取地址

5.2 创建独立项目骨架

项目结构不变,但 pom.xml 自包含所有版本,不再继承任何父 POM。可直接拷贝下方关键文件。

独立 pom.xml(节选)

<properties>
    <spring-boot.version>3.5.7</spring-boot.version>
    <spring-ai-alibaba.version>1.1.2.1</spring-ai-alibaba.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        <version>${spring-ai-alibaba.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>3.1.0</version>
    </dependency>
</dependencies>

修正后的 Controller、application.yml(含温度、超时等参数)请参考本指南第 3 节与下文附录。

5.3 编译与启动

# 设置 API Key
export AI_DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 进入项目目录
cd /path/to/standalone-summarizer

# 编译
mvn clean package -DskipTests

# 启动
java -jar target/standalone-summarizer-1.0.0.jar

5.4 测试 API

文本摘要

curl -s -X POST "http://localhost:18080/api/v1/summarize/text" \
  -H "Content-Type: application/json" \
  -d '{"text":"通义千问是阿里云自研的大语言模型系列..."}' | python3 -m json.tool

文件摘要

echo "测试文本内容" > /tmp/test.txt
curl -s -X POST "http://localhost:18080/api/v1/summarize/file" \
  -F "file=@/tmp/test.txt" | python3 -m json.tool

6. Docker 容器化与生产环境 Checklist

6.1 Dockerfile(多阶段构建)

FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests -U

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 18080
HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:18080/api/v1/summarize/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]

6.2 docker-compose.yml

version: '3.8'
services:
  summarizer:
    build: .
    ports:
      - "18080:18080"
    environment:
      - AI_DASHSCOPE_API_KEY=${AI_DASHSCOPE_API_KEY}
    restart: unless-stopped

6.3 生产上线前检查项

  • ✅ 健康检查:/api/v1/summarize/health
  • ✅ 日志级别与存留(application-prod.yml
  • ✅ 鉴权:添加 Spring Security + JWT
  • ✅ 限流:如 Guava RateLimiter 或网关层限流
  • ✅ 超时与重试:connect-timeout: 30s, read-timeout: 300s
  • ✅ Token 用量监控:在代码中记录 Usage 字段,接入成本分析

7. FAQ 排障手册

Q:启动后请求返回“AI 服务调用失败”?
A:检查 AI_DASHSCOPE_API_KEY 是否正确,账户是否开通 qwen-turbo,以及网络能否访问 dashscope.aliyuncs.com

Q:Maven 编译提示找不到 spring-ai-alibaba-starter-dashscope
A:检查是否配置了 Spring Milestones 仓库,或将版本改为本地已缓存的版本(如 1.1.2.1)。

Q:如何限制输入长度?
A:在 Controller 中检查 text.length() > 50000,返回 400。

Q:如何使摘要更稳定?
A:将 temperature 设为 0.2max-tokens 限制为 1500 以内。


8. 总结与延伸

这个示例虽小,却展示了 Spring AI Alibaba 的生产力

  • 自动装配 → 省去繁琐的 HTTP 客户端封装
  • ChatClient → 像调用本地方法一样调用大模型
  • 结合 Tika → 轻松打通文档→文本→AI 的流水线

下一步可做的增强

  • 异步流式响应(SSE),实现“打字机”效果
  • 加入向量数据库,实现长文档的“检索增强生成”(RAG)
  • 集成 Spring AI 的 Advisor,实现敏感词过滤、内容审核
  • 与工作流引擎结合,构建企业级文档审核/摘要流水线

理解原理、补齐缺失、拓展替代方案,是让一个“能跑”的 Demo 蜕变为生产级服务的必经之路。希望这份整合指南能让你少走弯路,快速落地自己的 AI 摘要服务。


附件:完整修订版 Controller 核心逻辑(精简)

@RestController
@RequestMapping("/api/v1/summarize")
public class SummaryController {

    private static final Logger log = LoggerFactory.getLogger(SummaryController.class);
    private final ChatClient chatClient;
    private final Tika tika = new Tika();

    public SummaryController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @PostMapping("/text")
    public ResponseEntity<ApiResponse> summarizeText(@RequestBody SummarizeRequest req) {
        if (req.text() == null || req.text().isBlank())
            return ResponseEntity.badRequest().body(new ApiResponse(null, "文本为空"));
        String summary = callAI(buildPrompt(req.text()));
        return ResponseEntity.ok(new ApiResponse(summary, null));
    }

    @PostMapping("/file")
    public ResponseEntity<ApiResponse> summarizeFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty() || file.getSize() > 10_000_000)
            return ResponseEntity.badRequest().body(new ApiResponse(null, "文件无效或过大"));
        String text = tika.parseToString(file.getInputStream());
        String summary = callAI(buildPrompt(text));
        return ResponseEntity.ok(new ApiResponse(summary, null));
    }

    private String callAI(String prompt) {
        return chatClient.prompt().user(u -> u.text(prompt)).call().content();
    }

    private String buildPrompt(String text) { /* 见上文优化 Prompt */ }

    public record SummarizeRequest(String text) {}
    public record ApiResponse(String summary, String error) {}
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐