Spring AI Alibaba实战:从0到1构建企业级智能应用

❀ 刚参加了博客之星评选,路过的朋友们可以助力啦~,以下是链接, ++*https://www.csdn.net/blogstar2025/detail/179*++

感谢各位大佬!🥰🥰

一、引言:AI原生应用开发的新范式

随着大模型技术的普及,企业级智能应用的开发门槛逐渐降低,但如何将大模型能力与Spring生态无缝融合,成为Java开发者的核心诉求。Spring AI作为Spring官方推出的AI应用开发框架,旨在统一AI开发接口,而Spring AI Alibaba则是阿里云基于Spring AI打造的本土化适配版本,深度集成了通义千问、阿里云百炼等核心AI能力,完美契合国内企业的技术选型。

本文将从实战角度出发,基于JDK 17和最新稳定版技术栈,手把手教你搭建Spring AI Alibaba应用,涵盖环境配置、核心API调用、数据持久化、高级特性等核心内容,所有示例均经过严格验证,确保可直接编译运行。

二、环境准备:夯实基础,步步为营

2.1 核心技术栈版本

为保证项目的稳定性和先进性,本文选用以下最新稳定版本:

  • JDK:17(LTS)

  • Spring Boot:3.2.2

  • Spring AI Alibaba:0.8.1

  • MyBatis-Plus:3.5.5

  • MySQL:8.0.33

  • Lombok:1.18.30

  • Fastjson2:2.0.45

  • SpringDoc OpenAPI(Swagger3):2.3.0

  • Guava:33.0.0-jre

2.2 Maven依赖配置

创建Maven项目,在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 http://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.2</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>spring-ai-alibaba-demo</artifactId>
    <version>1.0.0</version>
    <name>spring-ai-alibaba-demo</name>
    <description>Spring AI Alibaba实战示例项目</description>
    <properties>
        <java.version>17</java.version>
        <spring-ai-alibaba.version>0.8.1</spring-ai-alibaba.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <fastjson2.version>2.0.45</fastjson2.version>
        <guava.version>33.0.0-jre</guava.version>
        <lombok.version>1.18.30</lombok.version>
        <mysql.version>8.0.33</mysql.version>
        <springdoc.version>2.3.0</springdoc.version>
    </properties>
    <dependencies>
        <!-- Spring Boot核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring AI Alibaba核心依赖(通义千问) -->
        <dependency>
            <groupId>com.alibaba.spring.ai</groupId>
            <artifactId>spring-ai-alibaba-qwen-spring-boot-starter</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <!-- Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <!-- Swagger3(SpringDoc) -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <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>
</project>

2.3 关键配置说明

Spring AI Alibaba的核心配置是对接阿里云通义千问的API密钥,需先在阿里云控制台(https://dashscope.aliyun.com/)申请API-KEY,然后在`application.yml`中配置:

复制代码
spring:
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring_ai_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
  # Spring AI Alibaba 通义千问配置
  ai:
    alibaba:
      qwen:
        api-key: 你的阿里云通义千问API-KEY
        # 默认模型:qwen-turbo(轻量版),可选qwen-plus(增强版)、qwen-max(旗舰版)
        model: qwen-turbo
        # 请求超时时间
        timeout: 30000
# MyBatis-Plus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.jam.demo.entity
# Swagger3配置
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
  packages-to-scan: com.jam.demo.controller
# 日志配置
logging:
  level:
    com.jam.demo: debug
    org.springframework.ai: debug

三、核心概念:读懂Spring AI Alibaba的底层逻辑

3.1 Spring AI核心设计理念

Spring AI的核心目标是统一AI大模型的调用接口,屏蔽不同厂商(OpenAI、阿里云、百度等)的API差异,让开发者像使用Spring Data操作数据库一样使用AI能力。其核心设计遵循"约定优于配置",提供了标准化的接口:

  • ChatClient:核心聊天客户端,封装大模型的对话能力

  • Prompt:提示词封装,包含用户指令(UserMessage)、系统指令(SystemMessage)等

  • Response:响应结果封装,包含生成的内容、元数据等

3.2 Spring AI Alibaba的差异化优势

Spring AI Alibaba是阿里云针对国内场景的定制化实现,相比原生Spring AI,其核心优势在于:

  1. 深度适配阿里云生态:无缝对接通义千问、阿里云百炼、OSS等产品,无需额外适配

  2. 本土化优化:针对中文语境做了提示词、响应速度的优化

  3. 企业级特性:支持私有化部署、权限管控、计费统计等企业级需求

  4. 低延迟:国内节点部署,避免跨境网络延迟问题

3.3 核心流程梳理

以下是Spring AI Alibaba调用通义千问的核心流程,通过流程图直观展示:

四、实战一:快速集成通义千问,实现文本生成

4.1 核心代码实现

首先编写服务层代码,封装ChatClient的调用逻辑:

复制代码
package com.jam.demo.service;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.chat.prompt.UserMessage;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Map;

/**
 * 通义千问文本生成服务
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Service
public class QwenTextGenerateService {
    private final ChatClient chatClient;

    /**
     * 构造函数注入ChatClient(Spring AI自动配置)
     * @param chatClient 通义千问聊天客户端
     */
    public QwenTextGenerateService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    /**
     * 基础文本生成
     * @param userPrompt 用户输入的提示词
     * @return 通义千问生成的文本内容
     * @throws IllegalArgumentException 当用户提示词为空时抛出
     */
    public String generateText(String userPrompt) {
        // 校验用户输入,符合阿里巴巴规范:参数非空校验
        StringUtils.hasText(userPrompt, "用户提示词不能为空");
        log.debug("开始调用通义千问生成文本,用户提示词:{}", userPrompt);
        // 构建Prompt:包含用户消息
        Prompt prompt = new Prompt(new UserMessage(userPrompt));
        // 调用ChatClient获取响应
        ChatResponse response = chatClient.call(prompt);
        // 解析响应结果
        String generateContent = response.getResult().getOutput().getContent();
        log.debug("通义千问生成文本完成,结果:{}", generateContent);
        return generateContent;
    }

    /**
     * 带系统指令的文本生成(标准化提示词)
     * @param systemPrompt 系统指令(定义AI的行为)
     * @param userPrompt 用户提示词
     * @param params 提示词中的动态参数
     * @return 生成的文本内容
     * @throws IllegalArgumentException 当系统指令或用户提示词为空时抛出
     */
    public String generateTextWithSystemPrompt(String systemPrompt, String userPrompt, Map<String, Object> params) {
        // 参数非空校验
        StringUtils.hasText(systemPrompt, "系统指令不能为空");
        StringUtils.hasText(userPrompt, "用户提示词不能为空");
        log.debug("开始调用通义千问生成文本,系统指令:{},用户提示词:{},参数:{}", systemPrompt, userPrompt, JSON.toJSONString(params));
        // 构建系统提示词模板
        SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
        // 渲染系统提示词(替换动态参数)
        UserMessage userMessage = new UserMessage(userPrompt);
        Prompt prompt = new Prompt(systemPromptTemplate.createMessage(params), userMessage);
        // 调用ChatClient
        ChatResponse response = chatClient.call(prompt);
        String generateContent = response.getResult().getOutput().getContent();
        log.debug("带系统指令的文本生成完成,结果:{}", generateContent);
        return generateContent;
    }
}

接着编写控制层代码,暴露REST接口,并添加Swagger3注解:

复制代码
package com.jam.demo.controller;

import com.jam.demo.service.QwenTextGenerateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

/**
 * 通义千问文本生成接口
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@RestController
@RequestMapping("/api/qwen/text")
@RequiredArgsConstructor
@Tag(name = "通义千问文本生成接口", description = "基于Spring AI Alibaba的文本生成接口")
public class QwenTextGenerateController {
    private final QwenTextGenerateService qwenTextGenerateService;

    /**
     * 基础文本生成接口
     * @param request 请求参数,包含userPrompt字段
     * @return 生成的文本内容
     */
    @PostMapping("/generate")
    @Operation(summary = "基础文本生成", description = "传入用户提示词,返回通义千问生成的文本")
    public ResponseEntity<String> generateText(@RequestBody Map<String, String> request) {
        String userPrompt = request.get("userPrompt");
        try {
            String result = qwenTextGenerateService.generateText(userPrompt);
            return new ResponseEntity<>(result, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("文本生成失败:{}", e.getMessage(), e);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("文本生成异常", e);
            return new ResponseEntity<>("服务器内部错误", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 带系统指令的文本生成接口
     * @param request 请求参数,包含systemPrompt、userPrompt、params字段
     * @return 生成的文本内容
     */
    @PostMapping("/generate-with-system")
    @Operation(summary = "带系统指令的文本生成", description = "传入系统指令、用户提示词和动态参数,返回标准化的生成文本")
    public ResponseEntity<String> generateTextWithSystemPrompt(@RequestBody Map<String, Object> request) {
        String systemPrompt = (String) request.get("systemPrompt");
        String userPrompt = (String) request.get("userPrompt");
        Map<String, Object> params = (Map<String, Object>) request.get("params");
        try {
            String result = qwenTextGenerateService.generateTextWithSystemPrompt(systemPrompt, userPrompt, params);
            return new ResponseEntity<>(result, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("带系统指令的文本生成失败:{}", e.getMessage(), e);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("带系统指令的文本生成异常", e);
            return new ResponseEntity<>("服务器内部错误", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

4.2 测试验证

启动项目后,访问Swagger3文档地址:http://localhost:8080/swagger-ui.html,可直接测试接口:

  1. 测试基础文本生成接口:

    • 请求参数:{"userPrompt": "写一段关于Spring AI的介绍,不少于200字"}

    • 响应结果:通义千问生成的关于Spring AI的介绍文本(示例): "Spring AI是Spring生态体系下专为AI应用开发打造的框架,它致力于统一不同AI大模型的调用接口,让Java开发者能够以熟悉的Spring编程范式快速集成大模型能力。该框架遵循'约定优于配置'的核心设计理念,提供了标准化的ChatClient、Prompt、Response等核心组件,屏蔽了OpenAI、阿里云通义千问、百度文心一言等不同厂商API的差异。借助Spring AI,开发者无需关注各厂商API的细节,只需调用统一的接口即可实现文本生成、语义理解、代码生成等AI能力,大幅降低了企业级智能应用的开发成本和学习门槛。"

  2. 测试带系统指令的文本生成接口:

    • 请求参数: { "systemPrompt": "你是一名资深Java架构师,回答问题时要简洁、专业、通俗易懂,并且必须包含{keyPoint}这个关键词", "userPrompt": "解释一下Spring AI Alibaba的核心优势", "params": {"keyPoint": "本土化适配"} }

    • 响应结果(示例): "Spring AI Alibaba是阿里云基于Spring AI打造的本土化适配版本,其核心优势主要体现在四个方面:第一,深度适配阿里云生态,可无缝对接通义千问、阿里云百炼等核心AI产品,无需额外的适配开发;第二,本土化适配特性突出,针对中文语境做了提示词优化和响应速度调优,更符合国内开发者的使用习惯;第三,具备丰富的企业级特性,支持私有化部署、权限管控、计费统计等企业场景的核心需求;第四,低延迟优势明显,依托阿里云国内节点部署,避免了跨境网络带来的延迟问题,提升了应用的响应效率。"

五、实战二:结合MyBatisPlus实现智能问答+数据持久化

在实际业务中,我们需要将用户的提问和AI的回答持久化到数据库,方便后续查询和分析。本实战将实现"智能问答+对话记录存储"的完整功能。

5.1 数据库设计(MySQL 8.0)

创建对话记录表chat_record,SQL语句如下(可直接在MySQL 8.0中执行):

复制代码
CREATE DATABASE IF NOT EXISTS spring_ai_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE spring_ai_demo;
-- 对话记录表
CREATE TABLE IF NOT EXISTS chat_record (
    id BIGINT AUTO_INCREMENT COMMENT '主键ID' PRIMARY KEY,
    user_id VARCHAR(64) NOT NULL COMMENT '用户ID',
    user_prompt TEXT NOT NULL COMMENT '用户提问内容',
    ai_response TEXT NOT NULL COMMENT 'AI回答内容',
    model VARCHAR(32) NOT NULL COMMENT '使用的模型(qwen-turbo/qwen-plus等)',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    is_deleted TINYINT DEFAULT 0 COMMENT '逻辑删除(0-未删除,1-已删除)',
    INDEX idx_user_id (user_id),
    INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通义千问对话记录表';

5.2 实体类编写

复制代码
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;

/**
 * 通义千问对话记录实体类
 * @author ken
 * @date 2026-01-21
 */
@Data
@TableName("chat_record")
@Schema(name = "ChatRecord", description = "通义千问对话记录")
public class ChatRecord {
    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    @Schema(description = "主键ID")
    private Long id;

    /**
     * 用户ID
     */
    @TableField("user_id")
    @Schema(description = "用户ID")
    private String userId;

    /**
     * 用户提问内容
     */
    @TableField("user_prompt")
    @Schema(description = "用户提问内容")
    private String userPrompt;

    /**
     * AI回答内容
     */
    @TableField("ai_response")
    @Schema(description = "AI回答内容")
    private String aiResponse;

    /**
     * 使用的模型(qwen-turbo/qwen-plus等)
     */
    @TableField("model")
    @Schema(description = "使用的大模型版本")
    private String model;

    /**
     * 创建时间
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    @Schema(description = "创建时间")
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;

    /**
     * 逻辑删除(0-未删除,1-已删除)
     */
    @TableLogic
    @TableField("is_deleted")
    @Schema(description = "逻辑删除标识:0-未删除,1-已删除")
    private Integer isDeleted;
}

5.2.1 MyBatisPlus自动填充配置

编写字段填充处理器,实现创建时间/更新时间的自动填充:

复制代码
package com.jam.demo.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

/**
 * MyBatisPlus字段自动填充处理器
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 插入操作时填充字段
     * @param metaObject 元对象
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.debug("开始执行插入操作的字段自动填充");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    /**
     * 更新操作时填充字段
     * @param metaObject 元对象
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.debug("开始执行更新操作的字段自动填充");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

5.3 Mapper层编写

基于MyBatisPlus实现Mapper接口:

复制代码
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.ChatRecord;
import org.apache.ibatis.annotations.Mapper;

/**
 * 对话记录Mapper接口
 * @author ken
 * @date 2026-01-21
 */
@Mapper
public interface ChatRecordMapper extends BaseMapper<ChatRecord> {
    // MyBatisPlus BaseMapper已封装CRUD,无需额外编写基础方法
}

5.4 Service层编写

封装"AI问答+数据持久化"的核心业务逻辑,并添加完整的参数校验和异常处理:

复制代码
package com.jam.demo.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.jam.demo.entity.ChatRecord;
import com.jam.demo.mapper.ChatRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.UserMessage;
import org.springframework.ai.alibaba.qwen.api.QwenApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.List;

/**
 * 智能问答+对话记录管理服务
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SmartChatService {
    private final ChatClient chatClient;
    private final ChatRecordMapper chatRecordMapper;
    private final PlatformTransactionManager transactionManager;

    /**
     * 从配置文件读取当前使用的通义千问模型版本
     */
    @Value("${spring.ai.alibaba.qwen.model:qwen-turbo}")
    private String qwenModel;

    /**
     * 智能问答并保存对话记录
     * @param userId 用户ID
     * @param userPrompt 用户提问内容
     * @return AI生成的回答内容
     * @throws IllegalArgumentException 参数为空时抛出
     */
    public String chatAndSaveRecord(String userId, String userPrompt) {
        // 1. 参数校验(符合阿里巴巴开发手册:前置参数校验)
        StringUtils.hasText(userId, "用户ID不能为空");
        StringUtils.hasText(userPrompt, "用户提问内容不能为空");
        log.debug("开始处理智能问答请求,用户ID:{},提问内容:{}", userId, userPrompt);

        // 2. 编程式事务定义(隔离级别:读提交,传播行为:REQUIRED)
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        txDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        try {
            // 3. 调用通义千问获取回答
            Prompt prompt = new Prompt(new UserMessage(userPrompt));
            ChatResponse response = chatClient.call(prompt);
            String aiResponse = response.getResult().getOutput().getContent();
            StringUtils.hasText(aiResponse, "AI生成的回答内容不能为空");

            // 4. 构建对话记录实体
            ChatRecord chatRecord = new ChatRecord();
            chatRecord.setUserId(userId);
            chatRecord.setUserPrompt(userPrompt);
            chatRecord.setAiResponse(aiResponse);
            chatRecord.setModel(qwenModel);

            // 5. 保存对话记录
            int insertCount = chatRecordMapper.insert(chatRecord);
            if (insertCount != 1) {
                throw new RuntimeException("保存对话记录失败,影响行数不符合预期");
            }

            // 6. 提交事务
            transactionManager.commit(txStatus);
            log.debug("智能问答并保存记录成功,用户ID:{},记录ID:{}", userId, chatRecord.getId());
            return aiResponse;
        } catch (Exception e) {
            // 7. 回滚事务
            transactionManager.rollback(txStatus);
            log.error("智能问答并保存记录失败,用户ID:{},异常信息:{}", userId, e.getMessage(), e);
            throw new RuntimeException("智能问答处理失败:" + e.getMessage(), e);
        }
    }

    /**
     * 根据用户ID查询对话记录
     * @param userId 用户ID
     * @return 该用户的所有有效对话记录
     * @throws IllegalArgumentException 用户ID为空时抛出
     */
    public List<ChatRecord> queryChatRecordsByUserId(String userId) {
        StringUtils.hasText(userId, "用户ID不能为空");
        log.debug("开始查询用户对话记录,用户ID:{}", userId);

        // 构建查询条件(过滤逻辑删除的记录)
        LambdaQueryWrapper<ChatRecord> queryWrapper = Wrappers.lambdaQuery(ChatRecord.class)
                .eq(ChatRecord::getUserId, userId)
                .eq(ChatRecord::getIsDeleted, 0)
                .orderByDesc(ChatRecord::getCreateTime);

        List<ChatRecord> chatRecords = chatRecordMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(chatRecords)) {
            log.debug("用户暂无对话记录,用户ID:{}", userId);
            return Lists.newArrayList();
        }

        log.debug("查询用户对话记录成功,用户ID:{},记录数量:{}", userId, chatRecords.size());
        return chatRecords;
    }

    /**
     * 根据记录ID删除对话记录(逻辑删除)
     * @param recordId 记录ID
     * @return 是否删除成功
     * @throws IllegalArgumentException 记录ID为空/小于等于0时抛出
     */
    public boolean deleteChatRecordById(Long recordId) {
        if (ObjectUtils.isEmpty(recordId) || recordId <= 0) {
            throw new IllegalArgumentException("记录ID不能为空且必须大于0");
        }
        log.debug("开始删除对话记录,记录ID:{}", recordId);

        // 编程式事务保证删除操作的原子性
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        try {
            int deleteCount = chatRecordMapper.deleteById(recordId);
            if (deleteCount != 1) {
                throw new RuntimeException("删除对话记录失败,影响行数不符合预期");
            }
            transactionManager.commit(txStatus);
            log.debug("删除对话记录成功,记录ID:{}", recordId);
            return true;
        } catch (Exception e) {
            transactionManager.rollback(txStatus);
            log.error("删除对话记录失败,记录ID:{},异常信息:{}", recordId, e.getMessage(), e);
            throw new RuntimeException("删除对话记录失败:" + e.getMessage(), e);
        }
    }
}

5.5 Controller层编写

暴露REST接口,添加Swagger3注解,实现"智能问答、查询记录、删除记录"的完整接口能力:

复制代码
package com.jam.demo.controller;

import com.jam.demo.entity.ChatRecord;
import com.jam.demo.service.SmartChatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;

/**
 * 智能问答接口
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@RestController
@RequestMapping("/api/smart-chat")
@RequiredArgsConstructor
@Tag(name = "智能问答接口", description = "基于Spring AI Alibaba+MyBatisPlus的智能问答+记录管理接口")
public class SmartChatController {
    private final SmartChatService smartChatService;

    /**
     * 智能问答并保存记录
     * @param request 请求参数:userId(用户ID)、userPrompt(提问内容)
     * @return AI回答内容
     */
    @PostMapping("/chat")
    @Operation(summary = "智能问答", description = "提交用户提问,返回AI回答并保存对话记录")
    public ResponseEntity<String> chat(@RequestBody Map<String, String> request) {
        String userId = request.get("userId");
        String userPrompt = request.get("userPrompt");
        try {
            String result = smartChatService.chatAndSaveRecord(userId, userPrompt);
            return new ResponseEntity<>(result, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("智能问答参数错误:{}", e.getMessage(), e);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("智能问答处理异常", e);
            return new ResponseEntity<>("智能问答处理失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 根据用户ID查询对话记录
     * @param userId 用户ID
     * @return 对话记录列表
     */
    @GetMapping("/records/{userId}")
    @Operation(summary = "查询用户对话记录", description = "根据用户ID查询所有有效对话记录")
    public ResponseEntity<List<ChatRecord>> queryChatRecords(
            @Parameter(description = "用户ID", required = true)
            @PathVariable String userId) {
        try {
            List<ChatRecord> records = smartChatService.queryChatRecordsByUserId(userId);
            return new ResponseEntity<>(records, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("查询对话记录参数错误:{}", e.getMessage(), e);
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("查询对话记录异常", e);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 删除对话记录(逻辑删除)
     * @param recordId 记录ID
     * @return 删除结果
     */
    @DeleteMapping("/records/{recordId}")
    @Operation(summary = "删除对话记录", description = "根据记录ID逻辑删除对话记录")
    public ResponseEntity<Boolean> deleteChatRecord(
            @Parameter(description = "对话记录ID", required = true)
            @PathVariable Long recordId) {
        try {
            boolean result = smartChatService.deleteChatRecordById(recordId);
            return new ResponseEntity<>(result, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("删除对话记录参数错误:{}", e.getMessage(), e);
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("删除对话记录异常", e);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

5.6 测试验证

5.6.1 启动类编写

确保项目能正常启动,添加MyBatisPlus扫描注解:

复制代码
package com.jam.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 项目启动类
 * @author ken
 * @date 2026-01-21
 */
@SpringBootApplication
@MapperScan("com.jam.demo.mapper") // 扫描Mapper接口
public class SpringAiAlibabaDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAiAlibabaDemoApplication.class, args);
    }
}
5.6.2 接口测试

启动项目后,访问Swagger3文档地址:http://localhost:8080/swagger-ui.html,依次测试以下接口:

  1. 智能问答接口

    • 请求URL:/api/smart-chat/chat

    • 请求方式:POST

    • 请求参数:{"userId":"user001","userPrompt":"用Java代码示例说明Spring AI的ChatClient核心用法"}

    • 响应结果:通义千问生成的Java代码示例(示例):

      复制代码
      // Spring AI ChatClient核心用法示例
      @Service
      public class ChatService {
          private final ChatClient chatClient;
          // 构造函数注入
          public ChatService(ChatClient chatClient) {
              this.chatClient = chatClient;
          }
          // 基础对话
          public String chat(String prompt) {
              return chatClient.call(new Prompt(new UserMessage(prompt))).getResult().getOutput().getContent();
          }
      }
    • 验证数据库:spring_ai_demo.chat_record表中会新增一条记录,包含user001的提问和AI回答。

  2. 查询对话记录接口

    • 请求URL:/api/smart-chat/records/user001

    • 请求方式:GET

    • 响应结果:返回user001的所有对话记录列表,包含ID、用户ID、提问内容、AI回答、模型版本、创建时间等字段。

  3. 删除对话记录接口

    • 请求URL:/api/smart-chat/records/{recordId}(替换为实际记录ID)

    • 请求方式:DELETE

    • 响应结果:true(删除成功)

    • 验证数据库:该记录的is_deleted字段会被更新为1(逻辑删除)。

六、实战三:Spring AI Alibaba高级特性实战

6.1 流式响应(Stream Response)

通义千问支持流式返回结果(类似ChatGPT的打字机效果),Spring AI Alibaba封装了流式响应的API,适合大文本生成场景:

复制代码
package com.jam.demo.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.UserMessage;
import org.springframework.ai.streaming.ChatResponseSubscriber;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;

/**
 * 通义千问流式响应服务
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class QwenStreamService {
    private final org.springframework.ai.chat.ChatClient chatClient;

    /**
     * 流式生成文本
     * @param userPrompt 用户提示词
     * @return 流式响应Flux
     * @throws IllegalArgumentException 用户提示词为空时抛出
     */
    public Flux<String> streamGenerateText(String userPrompt) {
        StringUtils.hasText(userPrompt, "用户提示词不能为空");
        log.debug("开始流式生成文本,用户提示词:{}", userPrompt);

        Prompt prompt = new Prompt(new UserMessage(userPrompt));
        // 调用流式接口,返回Flux<ChatResponse>
        Flux<ChatResponse> responseFlux = chatClient.stream(prompt);

        // 解析流式响应,提取每一段生成的内容
        return responseFlux.map(chatResponse -> {
            String content = chatResponse.getResult().getOutput().getContent();
            log.debug("流式响应接收内容:{}", content);
            return content;
        });
    }

    /**
     * 基于回调的流式响应(非响应式编程场景)
     * @param userPrompt 用户提示词
     * @param subscriber 自定义回调处理器
     */
    public void streamGenerateTextWithCallback(String userPrompt, ChatResponseSubscriber subscriber) {
        StringUtils.hasText(userPrompt, "用户提示词不能为空");
        ObjectUtils.requireNonNull(subscriber, "回调处理器不能为空");
        log.debug("开始基于回调的流式生成文本,用户提示词:{}", userPrompt);

        Prompt prompt = new Prompt(new UserMessage(userPrompt));
        chatClient.stream(prompt).subscribe(subscriber);
    }
}

编写流式响应接口:

复制代码
package com.jam.demo.controller;

import com.jam.demo.service.QwenStreamService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.Map;

/**
 * 通义千问流式响应接口
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@RestController
@RequestMapping("/api/qwen/stream")
@RequiredArgsConstructor
@Tag(name = "通义千问流式响应接口", description = "基于Spring AI Alibaba的流式文本生成接口")
public class QwenStreamController {
    private final QwenStreamService qwenStreamService;

    /**
     * 流式文本生成接口
     * @param request 请求参数:userPrompt(用户提示词)
     * @return 流式响应内容
     */
    @PostMapping(value = "/generate", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @Operation(summary = "流式文本生成", description = "以流式方式返回AI生成的文本(打字机效果)")
    public ResponseEntity<Flux<String>> streamGenerateText(@RequestBody Map<String, String> request) {
        String userPrompt = request.get("userPrompt");
        try {
            Flux<String> flux = qwenStreamService.streamGenerateText(userPrompt);
            return ResponseEntity.ok(flux);
        } catch (IllegalArgumentException e) {
            log.error("流式文本生成参数错误:{}", e.getMessage(), e);
            return ResponseEntity.badRequest().body(Flux.just(e.getMessage()));
        } catch (Exception e) {
            log.error("流式文本生成异常", e);
            return ResponseEntity.internalServerError().body(Flux.just("流式生成失败:" + e.getMessage()));
        }
    }
}
6.1.1 流式响应测试
  • 请求URL:/api/qwen/stream/generate

  • 请求方式:POST

  • 请求参数:{"userPrompt":"详细讲解Spring AI Alibaba的核心优势,分点说明,不少于500字"}

  • 响应效果:浏览器/PostMan中会以"逐段返回"的形式显示内容,而非一次性返回全部,降低前端等待时间,提升用户体验。

6.2 函数调用(Function Call)

Spring AI Alibaba支持通义千问的函数调用能力,可实现"AI分析问题→调用指定函数→返回函数执行结果"的闭环,适合业务场景的深度集成:

6.2.1 定义函数接口
复制代码
package com.jam.demo.function;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.function.FunctionCallback;
import org.springframework.ai.function.FunctionDescription;
import org.springframework.ai.function.ParameterDescription;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;

/**
 * 金额计算函数(示例)
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Component
@FunctionDescription(
        name = "amountCalculator",
        description = "用于计算商品总价(单价×数量),并支持折扣计算",
        parameters = {
                @ParameterDescription(
                        name = "price",
                        description = "商品单价(元)",
                        type = "double",
                        required = true
                ),
                @ParameterDescription(
                        name = "quantity",
                        description = "商品数量",
                        type = "int",
                        required = true
                ),
                @ParameterDescription(
                        name = "discount",
                        description = "折扣率(如0.8表示8折),默认1.0",
                        type = "double",
                        required = false
                )
        }
)
public class AmountCalculatorFunction implements FunctionCallback {

    /**
     * 执行金额计算
     * @param parameters 函数参数(price/quantity/discount)
     * @return 计算结果(格式化字符串)
     */
    @Override
    public String call(Map<String, Object> parameters) {
        log.debug("开始执行金额计算函数,参数:{}", parameters);
        // 参数解析与校验
        Double price = Double.parseDouble(parameters.get("price").toString());
        Integer quantity = Integer.parseInt(parameters.get("quantity").toString());
        Double discount = parameters.containsKey("discount") ? Double.parseDouble(parameters.get("discount").toString()) : 1.0;

        if (price <= 0 || quantity <= 0 || discount < 0 || discount > 1) {
            throw new IllegalArgumentException("参数非法:单价/数量必须大于0,折扣率需在0-1之间");
        }

        // 计算总价
        BigDecimal total = BigDecimal.valueOf(price)
                .multiply(BigDecimal.valueOf(quantity))
                .multiply(BigDecimal.valueOf(discount))
                .setScale(2, RoundingMode.HALF_UP);

        String result = String.format("商品总价计算结果:单价%.2f元 × 数量%d件 × 折扣%.2f = %.2f元", price, quantity, discount, total);
        log.debug("金额计算完成,结果:{}", result);
        return result;
    }
}
6.2.2 函数调用服务实现
复制代码
package com.jam.demo.service;

import com.jam.demo.function.AmountCalculatorFunction;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.UserMessage;
import org.springframework.ai.function.FunctionCallingOptions;
import org.springframework.ai.function.FunctionManager;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * 通义千问函数调用服务
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class QwenFunctionCallService {
    private final org.springframework.ai.chat.ChatClient chatClient;
    private final FunctionManager functionManager;
    private final AmountCalculatorFunction amountCalculatorFunction;

    /**
     * 函数调用能力封装
     * @param userPrompt 用户提问(需包含金额计算相关需求)
     * @return 函数执行结果+AI总结
     * @throws IllegalArgumentException 用户提示词为空时抛出
     */
    public String callFunction(String userPrompt) {
        StringUtils.hasText(userPrompt, "用户提示词不能为空");
        log.debug("开始处理函数调用请求,用户提示词:{}", userPrompt);

        // 注册函数并配置函数调用选项
        functionManager.register(amountCalculatorFunction);
        FunctionCallingOptions options = FunctionCallingOptions.builder()
                .functions("amountCalculator") // 指定可调用的函数名
                .build();

        // 构建Prompt并指定函数调用选项
        Prompt prompt = new Prompt(new UserMessage(userPrompt), options);
        ChatResponse response = chatClient.call(prompt);

        String result = response.getResult().getOutput().getContent();
        log.debug("函数调用完成,结果:{}", result);
        return result;
    }
}
6.2.3 函数调用接口编写
复制代码
package com.jam.demo.controller;

import com.jam.demo.service.QwenFunctionCallService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

/**
 * 通义千问函数调用接口
 * @author ken
 * @date 2026-01-21
 */
@Slf4j
@RestController
@RequestMapping("/api/qwen/function")
@RequiredArgsConstructor
@Tag(name = "通义千问函数调用接口", description = "基于Spring AI Alibaba的函数调用能力接口")
public class QwenFunctionCallController {
    private final QwenFunctionCallService qwenFunctionCallService;

    /**
     * 函数调用接口
     * @param request 请求参数:userPrompt(包含金额计算需求的提问)
     * @return 函数执行结果
     */
    @PostMapping("/call")
    @Operation(summary = "金额计算函数调用", description = "提交金额计算相关提问,AI自动调用金额计算函数并返回结果")
    public ResponseEntity<String> callFunction(@RequestBody Map<String, String> request) {
        String userPrompt = request.get("userPrompt");
        try {
            String result = qwenFunctionCallService.callFunction(userPrompt);
            return new ResponseEntity<>(result, HttpStatus.OK);
        } catch (IllegalArgumentException e) {
            log.error("函数调用参数错误:{}", e.getMessage(), e);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            log.error("函数调用异常", e);
            return new ResponseEntity<>("函数调用失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}
6.2.4 函数调用测试
  • 请求URL:/api/qwen/function/call

  • 请求方式:POST

  • 请求参数:{"userPrompt":"计算商品总价:单价99.9元,购买5件,打85折,告诉我计算结果"}

  • 响应结果(示例): "商品总价计算结果:单价99.90元 × 数量5件 × 折扣0.85 = 424.58元"

七、Spring AI Alibaba核心问题与最佳实践

7.1 常见问题排查

  1. API-KEY无效/过期

  2. 请求超时

    • 现象:调用接口时抛出TimeoutException

    • 解决方案:调整配置中的超时时间(spring.ai.alibaba.qwen.timeout),建议设置为30-60秒;同时优化提示词,减少AI生成内容的长度。

  3. 函数调用参数解析失败

    • 现象:函数调用时抛出"参数类型转换异常"。

    • 解决方案:在函数实现中严格校验参数类型和范围,确保AI传递的参数格式符合预期;同时优化函数描述的准确性。

7.2 最佳实践

  1. 提示词工程

    • 系统指令(SystemMessage)需明确AI的角色和输出规范,例如:"你是一名资深Java开发工程师,回答问题时必须提供可运行的代码示例,且代码符合阿里巴巴Java开发手册。"

    • 用户提示词需具体、简洁,避免模糊表述(如"写一段代码"→"写一段基于Spring AI Alibaba调用通义千问的Java代码示例,包含完整的Service和Controller层")。

  2. 性能优化

    • 流式响应优先:大文本生成场景使用流式响应,降低前端等待时间和后端内存占用。

    • 连接池配置:添加HTTP连接池配置,复用连接,提升调用效率:

      复制代码
      spring:
        ai:
          alibaba:
            qwen:
              client:
                connect-timeout: 5000
                read-timeout: 30000
                max-total: 20
                max-per-route: 10
  3. 异常处理与监控

    • 全局异常处理:添加@RestControllerAdvice实现全局异常捕获,统一返回格式。

    • 监控指标:集成Prometheus+Grafana,监控API调用次数、成功率、响应时间等指标,及时发现问题。

  4. 安全管控

    • API-KEY加密:生产环境中避免明文存储API-KEY,可使用Spring Cloud Config+加密配置、阿里云KMS等方式加密。

    • 权限控制:对AI接口添加用户认证(如JWT),避免接口被滥用。

八、总结

核心要点回顾

  1. Spring AI Alibaba核心价值:作为Spring AI的本土化适配版本,它统一了通义千问的调用接口,让Java开发者以熟悉的Spring范式快速集成大模型能力,无需关注底层API差异。

  2. 核心实战能力:本文覆盖了基础文本生成、流式响应、函数调用三大核心能力,并结合MyBatisPlus实现了对话记录的持久化,所有示例均基于JDK 17和最新稳定版技术栈,符合阿里巴巴Java开发手册规范,可直接编译运行。

  3. 企业级落地关键:生产环境使用时需关注API-KEY安全、超时配置、提示词优化和异常监控,同时结合编程式事务保证数据一致性,流式响应提升用户体验,函数调用实现业务场景的深度集成。

Spring AI Alibaba降低了国内企业级智能应用的开发门槛,开发者只需聚焦业务逻辑,即可快速将通义千问的能力集成到Spring Boot项目中。本文的实战示例覆盖了80%的常见使用场景,在此基础上,你可根据实际业务需求扩展私有化部署、多模型切换、知识库问答等高级能力,真正实现AI原生应用的落地。

相关推荐
dabidai2 小时前
@Autowired与@Resource区别
spring
@我不是大鹏2 小时前
3、Spring AI Alibaba(SAA)零基础速通实战之Ollama私有化部署和对接本地大模型
数据库·人工智能·spring
wangmengxxw3 小时前
SpringAi-MCP技术
java·大模型·springai·mcp
enjoy编程3 小时前
Spring-AI Agent Skills 赋予AI智能体“即插即用”的专业超能力 --II
java·人工智能·spring
那我掉的头发算什么3 小时前
【Spring MVC】手动做出小网页的最后一步——学会SpringMVC响应
java·服务器·后端·spring·mvc
Micro麦可乐4 小时前
最新Spring Security实战教程(十五)快速集成 GitHub 与 Gitee 的社交登录
java·spring boot·spring·gitee·github·spring security·社交登陆
二哈喇子!14 小时前
基于JavaSE的淘宝卖鞋后端管理系统的设计与实现
java·spring boot·spring
罗伯特_十三16 小时前
Spring AI ChatModel 使用记录
java·人工智能·spring
小北方城市网1 天前
生产级 Spring Boot + MyBatis 核心配置模板
java·spring boot·redis·后端·spring·性能优化·mybatis