【第35篇】文本摘要微服务
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 的继承体系确保了所有示例模块共享同一个版本号。层次如下:
这里实际上形成了两层依赖:
- 父 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 自动配置过程中: -
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 调用链路
整个过程像一个精心封装的黑箱,开发者只需要两行 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.2,max-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) {}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)