环境搭建与第一个 AI 应用

学习目标

  • 掌握 Spring AI 开发环境的完整搭建流程
  • 理解不同 AI 模型的配置方法
  • 能够独立创建完整的 AI 应用项目
  • 掌握项目结构设计和代码组织
  • 了解开发、测试、生产环境的配置差异
  • 能够解决环境搭建中的常见问题

知识结构

环境搭建

开发工具

JDK 17+

Maven/Gradle

IDE配置

Git版本控制

AI服务准备

OpenAI账号

API Key获取

本地模型安装

网络配置

项目创建

Spring Initializr

依赖管理

项目结构

配置文件

第一个应用

Hello World

基础对话

参数配置

错误处理

环境配置

开发环境

测试环境

生产环境

Docker部署

一、项目场景引入

1.1 企业 AI 应用开发流程

典型场景:某公司开发智能客服系统

第一阶段:本地开发
- 开发人员在本地搭建环境
- 使用 Ollama 本地模型进行开发
- 快速迭代,无需担心 API 成本

第二阶段:测试验证
- 部署到测试环境
- 使用 OpenAI gpt-3.5-turbo 测试
- 验证功能和性能

第三阶段:生产上线
- 部署到生产环境
- 使用 Azure OpenAI gpt-4
- 保证稳定性和安全性

挑战:
1. 不同环境使用不同的 AI 模型
2. 配置管理复杂
3. 环境切换频繁
4. 需要统一的开发流程

传统方案的问题:

// 问题 1:硬编码环境配置
public class AIService {
    private static final String API_KEY = "sk-xxx";  // 硬编码
    private static final String MODEL = "gpt-3.5-turbo";
    
    public String chat(String message) {
        // 直接调用 API
    }
}

// 问题 2:环境切换需要修改代码
if (environment.equals("dev")) {
    // 使用本地模型
} else if (environment.equals("prod")) {
    // 使用云端模型
}

// 问题 3:配置分散
// API Key 在代码中
// 模型参数在配置文件中
// 环境变量在系统中

1.2 Spring AI 的解决方案

统一的配置管理 + 环境隔离:

# application-dev.yml(开发环境)
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2

# application-test.yml(测试环境)
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-3.5-turbo

# application-prod.yml(生产环境)
spring:
  ai:
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}
        chat:
          options:
            deployment-name: gpt-4

代码无需修改:

@Service
public class ChatService {
    
    private final ChatClient chatClient;  // 统一接口
    
    public ChatService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    public String chat(String message) {
        return chatClient.call(message);  // 简单调用
    }
}

// 启动时指定环境
// java -jar app.jar --spring.profiles.active=dev
// java -jar app.jar --spring.profiles.active=test
// java -jar app.jar --spring.profiles.active=prod

1.3 为什么需要规范的环境搭建

好处:

  1. 开发效率高

    • 统一的开发环境
    • 快速上手
    • 减少环境问题
  2. 配置管理简单

    • 集中管理配置
    • 环境隔离
    • 安全性高
  3. 部署方便

    • 一次构建,多环境部署
    • 配置外部化
    • 容器化支持
  4. 团队协作顺畅

    • 统一的项目结构
    • 标准的开发流程
    • 易于维护

二、开发环境准备

2.1 JDK 安装与配置

要求: JDK 17 或更高版本

Windows 安装:

# 1. 下载 JDK
# 访问 https://www.oracle.com/java/technologies/downloads/
# 或使用 OpenJDK: https://adoptium.net/

# 2. 安装 JDK
# 双击安装包,按提示安装

# 3. 配置环境变量
# 系统属性 → 环境变量 → 新建
JAVA_HOME=C:\Program Files\Java\jdk-17
Path=%JAVA_HOME%\bin

# 4. 验证安装
java -version
javac -version

Linux/Mac 安装:

# 使用 SDKMAN(推荐)
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 17.0.9-tem

# 或使用包管理器
# Ubuntu/Debian
sudo apt update
sudo apt install openjdk-17-jdk

# Mac
brew install openjdk@17

# 验证安装
java -version

配置 JAVA_HOME:

# Linux/Mac
echo 'export JAVA_HOME=/path/to/jdk-17' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

# 验证
echo $JAVA_HOME

2.2 Maven 安装与配置

Windows 安装:

# 1. 下载 Maven
# 访问 https://maven.apache.org/download.cgi

# 2. 解压到目录
# 例如:C:\Program Files\Apache\maven-3.9.5

# 3. 配置环境变量
MAVEN_HOME=C:\Program Files\Apache\maven-3.9.5
Path=%MAVEN_HOME%\bin

# 4. 验证安装
mvn -version

Linux/Mac 安装:

# 使用 SDKMAN
sdk install maven

# 或使用包管理器
# Ubuntu/Debian
sudo apt install maven

# Mac
brew install maven

# 验证安装
mvn -version

配置 Maven 镜像(加速下载):

编辑 ~/.m2/settings.xml(Windows: C:\Users\用户名\.m2\settings.xml):

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
          http://maven.apache.org/xsd/settings-1.0.0.xsd">
    
    <mirrors>
        <!-- 阿里云镜像 -->
        <mirror>
            <id>aliyun</id>
            <mirrorOf>central</mirrorOf>
            <name>Aliyun Maven</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </mirror>
        
        <!-- Spring 仓库 -->
        <mirror>
            <id>spring-milestones</id>
            <mirrorOf>spring-milestones</mirrorOf>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </mirror>
    </mirrors>
    
    <profiles>
        <profile>
            <id>jdk-17</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>17</jdk>
            </activation>
            <properties>
                <maven.compiler.source>17</maven.compiler.source>
                <maven.compiler.target>17</maven.compiler.target>
                <maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
            </properties>
        </profile>
    </profiles>
</settings>

2.3 IDE 安装与配置

IntelliJ IDEA(推荐):

1. 下载安装
   - 访问 https://www.jetbrains.com/idea/download/
   - 选择 Community(免费)或 Ultimate(付费)版本
   - 下载并安装

2. 配置 JDK
   - File → Project Structure → SDKs
   - 添加 JDK 17

3. 配置 Maven
   - File → Settings → Build, Execution, Deployment → Build Tools → Maven
   - Maven home directory: 选择 Maven 安装目录
   - User settings file: 选择 settings.xml

4. 安装插件
   - Lombok Plugin
   - Spring Assistant
   - Rainbow Brackets
   - GitToolBox

VS Code 配置:

1. 安装 VS Code
   - 访问 https://code.visualstudio.com/
   - 下载并安装

2. 安装扩展
   - Extension Pack for Java
   - Spring Boot Extension Pack
   - Lombok Annotations Support

3. 配置 Java
   - Ctrl+Shift+P → Java: Configure Java Runtime
   - 选择 JDK 17

2.4 Git 安装与配置

安装 Git:

# Windows
# 下载 https://git-scm.com/download/win
# 双击安装

# Linux
sudo apt install git

# Mac
brew install git

# 验证
git --version

配置 Git:

# 配置用户信息
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

# 配置编辑器
git config --global core.editor "code --wait"

# 配置别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit

# 查看配置
git config --list

三、AI 服务准备

3.1 OpenAI 账号注册与 API Key 获取

注册流程:

1. 访问 OpenAI 官网
   - https://platform.openai.com/

2. 注册账号
   - 点击 "Sign up"
   - 使用邮箱或 Google 账号注册
   - 验证邮箱

3. 登录账号
   - 输入邮箱和密码
   - 完成二次验证(如果需要)

4. 创建 API Key
   - 进入 API Keys 页面
   - 点击 "Create new secret key"
   - 输入 Key 名称(可选)
   - 复制并保存 Key(只显示一次!)

5. 充值账户(如果需要)
   - 进入 Billing 页面
   - 添加支付方式
   - 充值金额

API Key 管理最佳实践:

# 1. 使用环境变量存储
# Linux/Mac
export OPENAI_API_KEY="sk-your-api-key-here"

# Windows PowerShell
$env:OPENAI_API_KEY="sk-your-api-key-here"

# Windows CMD
set OPENAI_API_KEY=sk-your-api-key-here

# 2. 添加到 ~/.bashrc 或 ~/.zshrc(永久生效)
echo 'export OPENAI_API_KEY="sk-your-api-key-here"' >> ~/.bashrc
source ~/.bashrc

# 3. 使用 .env 文件(开发环境)
# 创建 .env 文件
OPENAI_API_KEY=sk-your-api-key-here

# 添加到 .gitignore
echo ".env" >> .gitignore

安全注意事项:

❌ 不要做的事:
1. 不要将 API Key 提交到 Git 仓库
2. 不要在代码中硬编码 API Key
3. 不要在公开场合分享 API Key
4. 不要使用弱密码保护账号

✅ 应该做的事:
1. 使用环境变量管理 API Key
2. 定期更换 API Key
3. 设置 API Key 的使用限制
4. 监控 API 使用情况
5. 启用账号二次验证

3.2 Azure OpenAI 服务配置

申请 Azure OpenAI 服务:

1. 注册 Azure 账号
   - 访问 https://azure.microsoft.com/
   - 注册账号(需要信用卡验证)

2. 申请 Azure OpenAI 访问权限
   - 访问 https://aka.ms/oai/access
   - 填写申请表单
   - 等待审核(通常 1-2 个工作日)

3. 创建 Azure OpenAI 资源
   - 登录 Azure Portal
   - 创建资源 → AI + Machine Learning → Azure OpenAI
   - 选择订阅和资源组
   - 选择区域(建议选择离你最近的)
   - 创建资源

4. 部署模型
   - 进入 Azure OpenAI Studio
   - 选择 Deployments
   - 创建新部署
   - 选择模型(如 gpt-35-turbo)
   - 设置部署名称

5. 获取配置信息
   - Endpoint: https://your-resource.openai.azure.com/
   - API Key: 在 Keys and Endpoint 页面获取
   - Deployment Name: 你创建的部署名称

配置示例:

spring:
  ai:
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}
        chat:
          options:
            deployment-name: gpt-35-turbo
            temperature: 0.7
            max-tokens: 1000

3.3 Ollama 本地模型安装

安装 Ollama:

# Linux
curl -fsSL https://ollama.com/install.sh | sh

# Mac
brew install ollama

# Windows
# 下载安装包:https://ollama.com/download/windows
# 双击安装

# 验证安装
ollama --version

启动 Ollama 服务:

# 启动服务
ollama serve

# 服务默认运行在 http://localhost:11434

下载模型:

# 下载 Llama 2(7B 参数)
ollama pull llama2

# 下载 Llama 2(13B 参数)
ollama pull llama2:13b

# 下载 Mistral
ollama pull mistral

# 下载 CodeLlama(代码专用)
ollama pull codellama

# 查看已下载的模型
ollama list

# 测试模型
ollama run llama2
>>> Hello!

常用模型对比:

模型 参数量 大小 用途 推荐配置
llama2 7B 3.8GB 通用对话 8GB RAM
llama2:13b 13B 7.3GB 高质量对话 16GB RAM
mistral 7B 4.1GB 通用对话 8GB RAM
codellama 7B 3.8GB 代码生成 8GB RAM
phi 2.7B 1.6GB 轻量级 4GB RAM

配置 Spring AI 使用 Ollama:

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2
          temperature: 0.7
          num-predict: 1000

3.4 网络配置(国内用户)

问题: 国内访问 OpenAI API 可能较慢或无法访问

解决方案:

方案 1:使用代理

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com  # 或使用代理地址
      
# 配置 HTTP 代理
http:
  proxy:
    host: your-proxy-host
    port: your-proxy-port

方案 2:使用国内 AI 服务

# 使用阿里云通义千问
spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-turbo

# 使用百度文心一言
spring:
  ai:
    qianfan:
      api-key: ${QIANFAN_API_KEY}
      secret-key: ${QIANFAN_SECRET_KEY}

方案 3:使用本地模型

# 使用 Ollama 本地模型(推荐)
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2

四、创建第一个 Spring AI 项目

4.1 使用 Spring Initializr 创建项目

在线创建(推荐):

1. 访问 https://start.spring.io/

2. 配置项目信息
   Project: Maven
   Language: Java
   Spring Boot: 3.2.0 或更高
   
   Project Metadata:
   - Group: com.example
   - Artifact: spring-ai-demo
   - Name: spring-ai-demo
   - Description: Spring AI Demo Project
   - Package name: com.example.springai
   - Packaging: Jar
   - Java: 17

3. 添加依赖
   - Spring Web
   - Lombok(可选)
   
4. 点击 "Generate" 下载项目

5. 解压并导入 IDE

使用 IDEA 创建:

1. File → New → Project

2. 选择 Spring Initializr

3. 配置项目信息(同上)

4. 选择依赖

5. 点击 Create

使用命令行创建:

# 使用 Spring CLI
spring init \
  --dependencies=web \
  --build=maven \
  --java-version=17 \
  --boot-version=3.2.0 \
  --group-id=com.example \
  --artifact-id=spring-ai-demo \
  --name=spring-ai-demo \
  --package-name=com.example.springai \
  spring-ai-demo

cd spring-ai-demo

4.2 添加 Spring AI 依赖

编辑 pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>spring-ai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-demo</name>
    <description>Spring AI Demo Project</description>
    
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>0.8.1</spring-ai.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring AI OpenAI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>
        
        <!-- Lombok(可选) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
    <!-- Spring Milestone 仓库 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

刷新依赖:

# 使用 Maven
mvn clean install

# 或在 IDEA 中
# 右键 pom.xml → Maven → Reload Project

4.3 项目结构设计

标准项目结构:

spring-ai-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/springai/
│   │   │       ├── SpringAiDemoApplication.java    # 启动类
│   │   │       ├── controller/                     # 控制器层
│   │   │       │   ├── ChatController.java
│   │   │       │   └── HealthController.java
│   │   │       ├── service/                        # 服务层
│   │   │       │   ├── ChatService.java
│   │   │       │   └── impl/
│   │   │       │       └── ChatServiceImpl.java
│   │   │       ├── dto/                            # 数据传输对象
│   │   │       │   ├── ChatRequest.java
│   │   │       │   └── ChatResponse.java
│   │   │       ├── config/                         # 配置类
│   │   │       │   ├── AIConfig.java
│   │   │       │   └── WebConfig.java
│   │   │       ├── exception/                      # 异常处理
│   │   │       │   ├── AIException.java
│   │   │       │   └── GlobalExceptionHandler.java
│   │   │       └── util/                           # 工具类
│   │   │           └── ResponseUtil.java
│   │   └── resources/
│   │       ├── application.yml                     # 主配置文件
│   │       ├── application-dev.yml                 # 开发环境配置
│   │       ├── application-test.yml                # 测试环境配置
│   │       ├── application-prod.yml                # 生产环境配置
│   │       ├── logback-spring.xml                  # 日志配置
│   │       └── static/                             # 静态资源
│   └── test/
│       └── java/
│           └── com/example/springai/
│               ├── SpringAiDemoApplicationTests.java
│               ├── controller/
│               │   └── ChatControllerTest.java
│               └── service/
│                   └── ChatServiceTest.java
├── .gitignore
├── pom.xml
└── README.md

创建目录结构:

# Linux/Mac
mkdir -p src/main/java/com/example/springai/{controller,service/impl,dto,config,exception,util}
mkdir -p src/main/resources/static
mkdir -p src/test/java/com/example/springai/{controller,service}

# Windows PowerShell
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/controller"
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/service/impl"
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/dto"
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/config"
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/exception"
New-Item -ItemType Directory -Path "src/main/java/com/example/springai/util"

4.4 配置文件编写

主配置文件 application.yml:

spring:
  application:
    name: spring-ai-demo
  
  # 激活的配置文件
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}

# 服务器配置
server:
  port: 8080
  servlet:
    context-path: /api

# 日志配置
logging:
  level:
    root: INFO
    com.example.springai: DEBUG
    org.springframework.ai: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

开发环境配置 application-dev.yml:

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2
          temperature: 0.7
          num-predict: 1000

# 开发环境日志
logging:
  level:
    com.example.springai: DEBUG

测试环境配置 application-test.yml:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-3.5-turbo
          temperature: 0.7
          max-tokens: 1000

# 测试环境日志
logging:
  level:
    com.example.springai: INFO

生产环境配置 application-prod.yml:

spring:
  ai:
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}
        chat:
          options:
            deployment-name: gpt-4
            temperature: 0.5
            max-tokens: 2000

# 生产环境日志
logging:
  level:
    root: WARN
    com.example.springai: INFO
  file:
    name: logs/spring-ai-demo.log
    max-size: 10MB
    max-history: 30

五、编写第一个 AI 应用

5.1 创建 DTO 类

ChatRequest.java:

package com.example.springai.dto;

import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * 聊天请求对象
 */
@Data
public class ChatRequest {
    
    /**
     * 用户消息
     */
    @NotBlank(message = "消息不能为空")
    @Size(max = 2000, message = "消息长度不能超过2000字符")
    private String message;
    
    /**
     * 对话ID(用于多轮对话)
     */
    private String conversationId;
    
    /**
     * 用户ID
     */
    private String userId;
}

ChatResponse.java:

package com.example.springai.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 聊天响应对象
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatResponse {
    
    /**
     * AI 回复
     */
    private String reply;
    
    /**
     * 对话ID
     */
    private String conversationId;
    
    /**
     * 响应时间(毫秒)
     */
    private Long responseTime;
    
    /**
     * Token 使用情况
     */
    private TokenUsage tokenUsage;
    
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class TokenUsage {
        private Long promptTokens;
        private Long completionTokens;
        private Long totalTokens;
    }
}

Result.java(统一响应):

package com.example.springai.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 统一响应对象
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    
    /**
     * 状态码
     */
    private Integer code;
    
    /**
     * 消息
     */
    private String message;
    
    /**
     * 数据
     */
    private T data;
    
    /**
     * 时间戳
     */
    private Long timestamp;
    
    /**
     * 成功响应
     */
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data, System.currentTimeMillis());
    }
    
    /**
     * 成功响应(无数据)
     */
    public static <T> Result<T> success() {
        return success(null);
    }
    
    /**
     * 失败响应
     */
    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null, System.currentTimeMillis());
    }
    
    /**
     * 失败响应(自定义状态码)
     */
    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null, System.currentTimeMillis());
    }
}

5.2 创建服务层

ChatService.java(接口):

package com.example.springai.service;

import com.example.springai.dto.ChatRequest;
import com.example.springai.dto.ChatResponse;

/**
 * 聊天服务接口
 */
public interface ChatService {
    
    /**
     * 简单对话
     * 
     * @param message 用户消息
     * @return AI 回复
     */
    String simpleChat(String message);
    
    /**
     * 标准对话
     * 
     * @param request 聊天请求
     * @return 聊天响应
     */
    ChatResponse chat(ChatRequest request);
    
    /**
     * 流式对话
     * 
     * @param message 用户消息
     * @return 响应流
     */
    reactor.core.publisher.Flux<String> streamChat(String message);
}

ChatServiceImpl.java(实现):

package com.example.springai.service.impl;

import com.example.springai.dto.ChatRequest;
import com.example.springai.dto.ChatResponse;
import com.example.springai.service.ChatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse as AIChatResponse;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.util.UUID;

/**
 * 聊天服务实现
 */
@Slf4j
@Service
public class ChatServiceImpl implements ChatService {
    
    private final ChatClient chatClient;
    
    public ChatServiceImpl(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    @Override
    public String simpleChat(String message) {
        log.info("收到简单对话请求: {}", message);
        
        try {
            String reply = chatClient.call(message);
            log.info("AI 回复: {}", reply);
            return reply;
            
        } catch (Exception e) {
            log.error("对话失败", e);
            throw new RuntimeException("AI 服务暂时不可用", e);
        }
    }
    
    @Override
    public ChatResponse chat(ChatRequest request) {
        log.info("收到对话请求: {}", request);
        
        long startTime = System.currentTimeMillis();
        
        try {
            // 1. 创建 Prompt
            Prompt prompt = new Prompt(new UserMessage(request.getMessage()));
            
            // 2. 调用 AI
            AIChatResponse aiResponse = chatClient.call(prompt);
            
            // 3. 提取响应内容
            String reply = aiResponse.getResult().getOutput().getContent();
            
            // 4. 计算响应时间
            long responseTime = System.currentTimeMillis() - startTime;
            
            // 5. 提取 Token 使用情况
            var usage = aiResponse.getMetadata().getUsage();
            ChatResponse.TokenUsage tokenUsage = ChatResponse.TokenUsage.builder()
                .promptTokens(usage.getPromptTokens())
                .completionTokens(usage.getGenerationTokens())
                .totalTokens(usage.getTotalTokens())
                .build();
            
            // 6. 构建响应
            ChatResponse response = ChatResponse.builder()
                .reply(reply)
                .conversationId(request.getConversationId() != null ? 
                    request.getConversationId() : UUID.randomUUID().toString())
                .responseTime(responseTime)
                .tokenUsage(tokenUsage)
                .build();
            
            log.info("对话成功 - 耗时: {}ms, Token: {}", 
                responseTime, tokenUsage.getTotalTokens());
            
            return response;
            
        } catch (Exception e) {
            log.error("对话失败", e);
            throw new RuntimeException("AI 服务暂时不可用", e);
        }
    }
    
    @Override
    public Flux<String> streamChat(String message) {
        log.info("收到流式对话请求: {}", message);
        
        try {
            Prompt prompt = new Prompt(new UserMessage(message));
            
            return chatClient.stream(prompt)
                .map(response -> response.getResult().getOutput().getContent())
                .doOnNext(content -> log.debug("流式内容: {}", content))
                .doOnComplete(() -> log.info("流式对话完成"))
                .doOnError(error -> log.error("流式对话失败", error));
                
        } catch (Exception e) {
            log.error("流式对话失败", e);
            return Flux.error(new RuntimeException("AI 服务暂时不可用", e));
        }
    }
}

5.3 创建控制器层

ChatController.java:

package com.example.springai.controller;

import com.example.springai.dto.ChatRequest;
import com.example.springai.dto.ChatResponse;
import com.example.springai.dto.Result;
import com.example.springai.service.ChatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

/**
 * 聊天控制器
 */
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
    
    private final ChatService chatService;
    
    public ChatController(ChatService chatService) {
        this.chatService = chatService;
    }
    
    /**
     * 简单对话接口
     * GET /api/chat/simple?message=你好
     * 
     * @param message 用户消息
     * @return AI 回复
     */
    @GetMapping("/simple")
    public Result<String> simpleChat(@RequestParam String message) {
        log.info("简单对话请求: {}", message);
        
        String reply = chatService.simpleChat(message);
        return Result.success(reply);
    }
    
    /**
     * 标准对话接口
     * POST /api/chat
     * Body: {"message": "你好"}
     * 
     * @param request 聊天请求
     * @return 聊天响应
     */
    @PostMapping
    public Result<ChatResponse> chat(@Validated @RequestBody ChatRequest request) {
        log.info("对话请求: {}", request);
        
        ChatResponse response = chatService.chat(request);
        return Result.success(response);
    }
    
    /**
     * 流式对话接口
     * GET /api/chat/stream?message=你好
     * 
     * @param message 用户消息
     * @return 响应流
     */
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(@RequestParam String message) {
        log.info("流式对话请求: {}", message);
        
        return chatService.streamChat(message);
    }
}

HealthController.java:

package com.example.springai.controller;

import com.example.springai.dto.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * 健康检查控制器
 */
@RestController
@RequestMapping("/health")
public class HealthController {
    
    /**
     * 健康检查
     * GET /api/health
     * 
     * @return 健康状态
     */
    @GetMapping
    public Result<Map<String, Object>> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("service", "spring-ai-demo");
        health.put("timestamp", System.currentTimeMillis());
        
        return Result.success(health);
    }
}

5.4 异常处理

AIException.java:

package com.example.springai.exception;

/**
 * AI 服务异常
 */
public class AIException extends RuntimeException {
    
    private Integer code;
    
    public AIException(String message) {
        super(message);
        this.code = 500;
    }
    
    public AIException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    public AIException(String message, Throwable cause) {
        super(message, cause);
        this.code = 500;
    }
    
    public Integer getCode() {
        return code;
    }
}

GlobalExceptionHandler.java:

package com.example.springai.exception;

import com.example.springai.dto.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理 AI 异常
     */
    @ExceptionHandler(AIException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Void> handleAIException(AIException e) {
        log.error("AI 服务异常", e);
        return Result.error(e.getCode(), e.getMessage());
    }
    
    /**
     * 处理参数验证异常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<Void> handleValidationException(Exception e) {
        log.error("参数验证失败", e);
        
        String message = "参数验证失败";
        if (e instanceof MethodArgumentNotValidException) {
            var error = ((MethodArgumentNotValidException) e)
                .getBindingResult().getFieldError();
            if (error != null) {
                message = error.getDefaultMessage();
            }
        }
        
        return Result.error(400, message);
    }
    
    /**
     * 处理通用异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error("系统异常,请稍后重试");
    }
}

5.5 启动类

SpringAiDemoApplication.java:

package com.example.springai;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Spring AI 演示应用
 * 
 * @author Your Name
 * @since 1.0.0
 */
@SpringBootApplication
public class SpringAiDemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SpringAiDemoApplication.class, args);
        System.out.println("""
            
            ========================================
            Spring AI Demo Application Started!
            ========================================
            API Base URL: http://localhost:8080/api
            Health Check: http://localhost:8080/api/health
            Simple Chat:  http://localhost:8080/api/chat/simple?message=你好
            ========================================
            
            """);
    }
}

六、运行和测试

6.1 启动应用

方式 1:使用 Maven

# 开发环境
mvn spring-boot:run

# 指定环境
mvn spring-boot:run -Dspring-boot.run.profiles=dev

# 或
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=dev"

方式 2:使用 IDEA

1. 右键 SpringAiDemoApplication
2. 选择 Run 'SpringAiDemoApplication'

或

1. 点击 Run → Edit Configurations
2. 添加 Environment Variables: SPRING_PROFILES_ACTIVE=dev
3. 点击 Run

方式 3:打包后运行

# 打包
mvn clean package

# 运行
java -jar target/spring-ai-demo-0.0.1-SNAPSHOT.jar

# 指定环境
java -jar target/spring-ai-demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

6.2 测试接口

使用 curl 测试:

# 1. 健康检查
curl http://localhost:8080/api/health

# 2. 简单对话(GET)
curl "http://localhost:8080/api/chat/simple?message=你好"

# 3. 标准对话(POST)
curl -X POST http://localhost:8080/api/chat \
  -H "Content-Type: application/json" \
  -d '{
    "message": "介绍一下 Spring AI",
    "userId": "user123"
  }'

# 4. 流式对话
curl -N http://localhost:8080/api/chat/stream?message=写一首诗

使用 Postman 测试:

1. 创建 Collection: Spring AI Demo

2. 添加请求:健康检查
   - Method: GET
   - URL: http://localhost:8080/api/health

3. 添加请求:简单对话
   - Method: GET
   - URL: http://localhost:8080/api/chat/simple
   - Params: message=你好

4. 添加请求:标准对话
   - Method: POST
   - URL: http://localhost:8080/api/chat
   - Headers: Content-Type: application/json
   - Body (raw JSON):
     {
       "message": "介绍一下 Spring AI",
       "userId": "user123"
     }

5. 添加请求:流式对话
   - Method: GET
   - URL: http://localhost:8080/api/chat/stream
   - Params: message=写一首诗

预期响应:

// 健康检查
{
  "code": 200,
  "message": "success",
  "data": {
    "status": "UP",
    "service": "spring-ai-demo",
    "timestamp": 1704067200000
  },
  "timestamp": 1704067200000
}

// 简单对话
{
  "code": 200,
  "message": "success",
  "data": "你好!我是 AI 助手,有什么可以帮助你的吗?",
  "timestamp": 1704067200000
}

// 标准对话
{
  "code": 200,
  "message": "success",
  "data": {
    "reply": "Spring AI 是 Spring 生态系统中用于构建 AI 应用的框架...",
    "conversationId": "550e8400-e29b-41d4-a716-446655440000",
    "responseTime": 1523,
    "tokenUsage": {
      "promptTokens": 15,
      "completionTokens": 150,
      "totalTokens": 165
    }
  },
  "timestamp": 1704067200000
}

6.3 查看日志

控制台日志:

2024-01-01 10:00:00 [main] INFO  c.e.s.SpringAiDemoApplication - Starting SpringAiDemoApplication
2024-01-01 10:00:01 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080
2024-01-01 10:00:01 [main] INFO  c.e.s.SpringAiDemoApplication - Started SpringAiDemoApplication in 2.5 seconds

2024-01-01 10:00:10 [http-nio-8080-exec-1] INFO  c.e.s.c.ChatController - 对话请求: ChatRequest(message=你好, conversationId=null, userId=user123)
2024-01-01 10:00:10 [http-nio-8080-exec-1] INFO  c.e.s.s.i.ChatServiceImpl - 收到对话请求: ChatRequest(message=你好, conversationId=null, userId=user123)
2024-01-01 10:00:11 [http-nio-8080-exec-1] INFO  c.e.s.s.i.ChatServiceImpl - 对话成功 - 耗时: 1523ms, Token: 165

日志文件(生产环境):

# 查看日志
tail -f logs/spring-ai-demo.log

# 搜索错误
grep "ERROR" logs/spring-ai-demo.log

# 查看最近100行
tail -n 100 logs/spring-ai-demo.log

七、常见问题

Q1: 启动时报错 “Failed to configure a DataSource”

原因: Spring Boot 自动配置了数据源,但没有配置数据库

解决方案:

// 方案 1:排除数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringAiDemoApplication {
    // ...
}

// 方案 2:添加数据库配置(如果需要)
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: password

Q2: 调用 AI 时报错 “401 Unauthorized”

原因: API Key 配置错误或未配置

解决方案:

# 1. 检查环境变量
echo $OPENAI_API_KEY

# 2. 重新设置环境变量
export OPENAI_API_KEY="sk-your-api-key-here"

# 3. 重启应用

Q3: 响应很慢或超时

原因: 网络问题或模型响应慢

解决方案:

# 1. 增加超时时间
spring:
  ai:
    openai:
      chat:
        options:
          timeout: 60s

# 2. 使用本地模型(开发环境)
spring:
  ai:
    ollama:
      base-url: http://localhost:11434

# 3. 使用代理
http:
  proxy:
    host: your-proxy-host
    port: your-proxy-port

Q4: 如何调试 AI 请求和响应?

解决方案:

# 1. 开启 DEBUG 日志
logging:
  level:
    org.springframework.ai: DEBUG

# 2. 添加请求拦截器
@Component
public class AIRequestInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(
            HttpRequest request,
            byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        
        log.debug("AI Request: {} {}", request.getMethod(), request.getURI());
        log.debug("Request Body: {}", new String(body));
        
        ClientHttpResponse response = execution.execute(request, body);
        
        log.debug("AI Response: {}", response.getStatusCode());
        
        return response;
    }
}

Q5: 如何切换不同的 AI 模型?

解决方案:

# 方案 1:修改配置文件
spring:
  ai:
    openai:
      chat:
        options:
          model: gpt-4  # 改为 gpt-4

# 方案 2:运行时指定
java -jar app.jar --spring.ai.openai.chat.options.model=gpt-4

# 方案 3:代码中指定
OpenAiChatOptions options = OpenAiChatOptions.builder()
    .withModel("gpt-4")
    .build();
Prompt prompt = new Prompt(message, options);

八、练习题

基础练习

练习 1: 添加用户认证功能

练习 2: 实现对话历史记录

练习 3: 添加 Token 使用统计

进阶练习

练习 4: 实现多轮对话

练习 5: 添加缓存机制

练习 6: 实现流式响应的前端展示

综合练习

练习 7: 构建完整的智能客服系统

九、学习检查清单

  • 完成开发环境搭建
  • 成功创建 Spring AI 项目
  • 理解项目结构设计
  • 能够编写基础的 AI 应用
  • 掌握配置文件管理
  • 能够测试和调试应用
  • 了解常见问题的解决方法

十、扩展阅读

总结

恭喜你完成了环境搭建和第一个 AI 应用的开发!现在你已经掌握了 Spring AI 开发的基础技能,可以继续学习更高级的功能了。

Logo

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

更多推荐