一、Java+AI企业级架构项目

项目名称:ai-enterprise-scaffold

项目简介:基于Spring Boot 3 + Spring AI构建的企业级Java+AI通用脚手架,集成OCR识别、多模态图片理解、Redis分布式限流、Prompt模板管理、审计日志、敏感词审核等核心功能,开箱即用,可快速支撑AI类业务项目(智能客服、文档问答、智能审核等)的开发与落地。

核心定位:降低Java+AI项目开发门槛,提供标准化、可扩展的架构方案,兼顾性能、安全与工程化规范,适用于企业级AI应用快速迭代。

✨ 核心功能

  • AI基础能力:基于Spring AI统一对接大模型(通义千问、OpenAI、Qwen-VL等),支持文本对话、多模态交互

  • OCR识别:集成百度OCR,支持图片文字提取(通用文字识别,可扩展发票、身份证等专项识别)

  • 多模态能力:支持图片理解(文生图、图生文交互,适配主流多模态模型)

  • 分布式限流:基于Redis+Redisson实现令牌桶限流,防止AI接口被刷,保障服务稳定性

  • Prompt模板管理:支持Prompt模板配置化(数据库存储),统一管理、灵活复用,降低Prompt维护成本

  • 审计日志:AOP自动记录AI接口调用日志(IP、请求内容、响应结果),支持合规审计

  • 敏感词审核:内置基础敏感词校验,可无缝对接百度/阿里云内容安全接口,拦截违规内容

  • 基础工程化:全局异常处理、统一返回格式、接口限流、文档解析(PDF/Word)等基础能力

📋 技术栈

技术类别

具体技术

版本说明

基础框架

Spring Boot

3.2.5

AI框架

Spring AI

1.0.0-M1

缓存&限流

Redis、Redisson

Redis 6.x+、Redisson 3.26.0

数据库

MySQL、MyBatis

MySQL 8.x+、MyBatis 3.0.3

OCR识别

百度OCR SDK

4.16.1

工具类

Hutool

5.8.25

🚀 快速启动

1. 环境准备

  • JDK:17+(Spring Boot 3要求)

  • MySQL:8.0+(存储审计日志、Prompt模板)

  • Redis:6.0+(分布式限流、缓存)

  • Maven:3.6+(构建项目)

  • AI Key:通义千问/OpenAI API Key(用于AI对话、多模态)

  • OCR Key:百度OCR API Key、Secret Key(用于文字识别)

2. 项目拉取

# 克隆项目
git clone https://github.com/你的用户名/ai-enterprise-scaffold.git

# 进入项目目录
cd ai-enterprise-scaffold

3. 环境配置

修改 src/main/resources/application.yml 文件,配置以下核心参数:

# 服务器配置
server:
  port: 8080

# AI大模型配置(通义千问兼容OpenAI格式,可替换为OpenAI)
spring:
  ai:
    openai:
      api-key: 你的AI API Key
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 # 通义千问兼容地址
      chat:
        options:
          model: qwen-turbo # 模型名称,可替换为qwen-vl-plus(多模态)
          temperature: 0.7

  # Redis配置
  data:
    redis:
      host: 127.0.0.1 # 你的Redis地址
      port: 6379      # Redis端口

  # MySQL配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ai_scaffold?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root    # 你的MySQL用户名
    password: root    # 你的MySQL密码
    driver-class-name: com.mysql.cj.jdbc.Driver

# 百度OCR配置
ocr:
  baidu:
    api-key: 你的百度OCR API Key
    secret-key: 你的百度OCR Secret Key

# 限流配置
rate-limit:
  enabled: true           # 是否开启限流
  permits-per-second: 10  # 每秒允许请求数

# MyBatis配置
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

4. 数据库初始化

执行以下SQL,创建数据库和所需表(MySQL中执行):

-- 创建数据库
CREATE DATABASE IF NOT EXISTS ai_scaffold;
USE ai_scaffold;

-- Prompt模板表(用于模板管理)
CREATE TABLE ai_prompt_template (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(64) UNIQUE NOT NULL COMMENT '模板编码',
    content TEXT NOT NULL COMMENT '模板内容',
    name VARCHAR(128) COMMENT '模板名称'
);

-- 审计日志表(用于记录AI接口调用日志)
CREATE TABLE ai_audit_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    ip VARCHAR(64) COMMENT '请求IP',
    prompt TEXT COMMENT '请求Prompt',
    response TEXT COMMENT 'AI响应结果',
    create_time DATETIME DEFAULT NOW() COMMENT '创建时间'
);

-- 初始化示例Prompt模板(可选)
INSERT INTO ai_prompt_template (code, name, content) VALUES 
('rag.qa', 'RAG问答模板', '基于以下资料回答问题,不要脱离资料内容:\n资料:%s\n问题:%s');

5. 启动项目

  • 方式1:IDEA启动,直接运行 com.ai.scaffold.AiScaffoldApplication 启动类

  • 方式2:Maven命令启动  

  • 2.1: mvn clean package -DskipTests

  • 2.2:java -jar target/ai-enterprise-scaffold-1.0.0.jar

6. 接口测试

项目启动成功后,访问以下接口进行测试(可通过Postman、浏览器直接访问),包含AI对话、OCR识别、多模态图片理解完整示例(含请求头、参数说明、正常/异常响应):

# 1. AI对话接口(GET请求)
请求地址:http://localhost:8080/ai/chat?prompt=你好,介绍下这个Java+AI脚手架
请求头:
  Content-Type: application/json;charset=UTF-8
请求参数:
  prompt:必填,AI对话提示词(字符串)
正常响应示例:
{
  "code": 200,
  "msg": "success",
  "data": "您好!这是一套基于Spring Boot 3 + Spring AI构建的企业级Java+AI通用脚手架,集成了OCR识别、多模态图片理解、Redis分布式限流等核心功能,可快速支撑AI类业务开发。"
}
异常响应示例(敏感词拦截):
{
  "code": 500,
  "msg": "内容包含违规信息,已拦截",
  "data": null
}
异常响应示例(限流拦截):
{
  "code": 500,
  "msg": "请求过于频繁,请稍后再试",
  "data": null
}

# 2. OCR识别接口(POST请求,需新增Controller接口,完整代码见功能说明)
请求地址:http://localhost:8080/ai/ocr
请求方式:POST
请求头:
  Content-Type: multipart/form-data
请求参数(form-data):
  image:必填,图片文件(支持png、jpg、jpeg格式,建议文件大小不超过5MB)
正常响应示例:
{
  "code": 200,
  "msg": "success",
  "data": "Java+AI企业级脚手架\n核心功能:OCR识别、多模态交互、Redis分布式限流、Prompt模板管理"
}
异常响应示例(OCR密钥错误):
{
  "code": 500,
  "msg": "OCR识别失败",
  "data": null
}
异常响应示例(未上传图片):
{
  "code": 500,
  "msg": "Required request part 'image' is not present",
  "data": null
}

# 3. 多模态图片理解接口(POST请求,需新增Controller接口,完整代码见功能说明)
请求地址:http://localhost:8080/ai/multi-modal
请求方式:POST
请求头:
  Content-Type: application/json;charset=UTF-8
请求体(JSON):
{
  "imageUrl": "https://xxx.xxx.com/test.jpg", // 必填,合法可访问的图片URL(http/https协议)
  "question": "这张图片里有什么内容?"       // 必填,对图片的提问(字符串)
}
正常响应示例:
{
  "code": 200,
  "msg": "success",
  "data": "图片中展示了一个Java代码编辑器界面,左侧是项目目录,中间是Spring Boot启动类代码,界面简洁清晰,底部显示编译成功提示。"
}
异常响应示例(图片URL无效/安全拦截):
{
  "code": 500,
  "msg": "link hit security strategy",
  "data": null
}
异常响应示例(缺少参数):
{
  "code": 500,
  "msg": "参数错误:imageUrl不能为空",
  "data": null
}

# 补充说明:
1. 所有接口均支持Postman直接调试,参数按上述要求填写即可
2. 多模态接口若出现「link hit security strategy」报错,参考注意事项第6、7点排查
3. OCR接口若识别失败,优先核对百度OCR密钥配置是否正确

补充:OCR、多模态接口需在AiController中新增对应接口,具体代码见「功能使用说明」模块。

📂 项目结构

ai-enterprise-scaffold/
├── pom.xml                     # 项目依赖配置
├── src/main/java/com/ai/scaffold/
│   ├── AiScaffoldApplication.java        # 项目启动类
│   ├── config/                          # 配置类
│   │   ├── MvcConfig.java                # Spring MVC配置(拦截器注册)
│   │   ├── RedisConfig.java              # Redis配置(可选扩展)
│   │   └── RateLimitProperties.java      # 限流配置类
│   ├── core/                            # 核心基础模块
│   │   ├── Result.java                   # 统一返回格式
│   │   ├── GlobalExceptionHandler.java   # 全局异常处理
│   │   ├── RateLimitException.java       # 限流异常
│   │   └── SensitiveException.java       # 敏感词异常
│   ├── ai/                              # AI核心服务
│   │   ├── AiService.java                # AI统一服务(对话、敏感词校验)
│   │   ├── OcrService.java               # OCR识别服务
│   │   ├── MultiModalService.java        # 多模态服务(图片理解)
│   │   ├── PromptTemplateService.java    # Prompt模板服务
│   │   └── SensitiveCheckService.java    # 敏感词审核服务
│   ├── audit/                           # 审计日志模块
│   │   ├── AiAuditLog.java               # 审计日志实体
│   │   ├── AiAuditLogMapper.java         # MyBatis Mapper
│   │   └── AuditLogAspect.java           # AOP日志记录
│   ├── interceptor/                     # 拦截器
│   │   └── RedisRateLimitInterceptor.java # Redis分布式限流拦截器
│   ├── controller/                      # 对外接口
│   │   └── AiController.java             # AI相关接口(需新增OCR、多模态接口)
│   └── util/                            # 工具类
│       └── DocUtil.java                  # 文档解析工具(PDF/Word)
├── src/main/resources/
│   └── application.yml                  # 全局配置文件
└── src/test/java/com/ai/scaffold/
    └── AiTest.java                       # 单元测试用例

🔧 功能使用说明

1. AI对话功能

通过 AiService 调用大模型,自动进行敏感词校验,示例代码:

// 注入AI服务
@Resource
private AiService aiService;

// 调用AI对话
String prompt = "介绍Java+AI的应用场景";
String response = aiService.chat(prompt);
System.out.println(response);

2. OCR识别功能

通过 OcrService 实现图片文字提取,支持本地图片、字节数组;同时新增Controller接口对外提供服务,接口代码可直接复制使用:

// 1. 注入OCR服务
@Resource
private OcrService ocrService;

// 2. 新增OCR接口(AiController中,完整可复制)
@PostMapping("/ocr")
public Result<String> ocr(@RequestParam("image") MultipartFile image) throws Exception {
    // 校验图片是否为空
    if (image.isEmpty()) {
        throw new IllegalArgumentException("图片不能为空");
    }
    // 校验图片格式
    String contentType = image.getContentType();
    if (contentType == null || !contentType.startsWith("image/")) {
        throw new IllegalArgumentException("请上传合法的图片文件(png、jpg、jpeg)");
    }
    // 校验图片大小(不超过5MB)
    if (image.getSize() > 5 * 1024 * 1024) {
        throw new IllegalArgumentException("图片大小不能超过5MB");
    }
    byte[] imageBytes = image.getBytes();
    String ocrResult = ocrService.recognize(imageBytes);
    return Result.success(ocrResult);
}

// 3. 本地测试示例
byte[] imageBytes = FileUtil.readBytes("test.png");
String ocrResult = ocrService.recognize(imageBytes);
System.out.println("OCR识别结果:" + ocrResult);

3. 多模态图片理解

通过MultiModalService 实现图片理解,支持图片URL或本地图片;同时新增Controller接口对外提供服务,接口代码可直接复制使用,注意:图片URL需为合法可访问地址,避免出现「link hit security strategy」安全拦截报错:

// 1. 注入多模态服务
@Resource
private MultiModalService multiModalService;

// 2. 新增多模态接口(AiController中,完整可复制)
@PostMapping("/multi-modal")
public Result<String> multiModal(@RequestBody MultiModalDTO dto) throws Exception {
    // 校验参数
    if (StringUtils.isBlank(dto.getImageUrl())) {
        throw new IllegalArgumentException("imageUrl不能为空");
    }
    if (StringUtils.isBlank(dto.getQuestion())) {
        throw new IllegalArgumentException("question不能为空");
    }
    // 校验URL格式
    if (!dto.getImageUrl().startsWith("http://") && !dto.getImageUrl().startsWith("https://")) {
        throw new IllegalArgumentException("imageUrl必须是http/https协议的合法URL");
    }
    URL imageUrl = new URL(dto.getImageUrl());
    String result = multiModalService.understand(imageUrl, dto.getQuestion());
    return Result.success(result);
}

// 3. DTO实体(需新增,完整可复制)
@Data
public class MultiModalDTO {
    private String imageUrl; // 合法可访问的图片URL(http/https协议)
    private String question; // 对图片的提问(字符串,非空)
}

// 4. 本地测试示例
URL imageUrl = new URL("https://xxx.xxx.com/test.jpg"); // 替换为自有合法图片URL,避免example.com
String question = "这张图片里有什么?";
String result = multiModalService.understand(imageUrl, question);
System.out.println("图片理解结果:" + result);

4. Prompt模板使用

通过 PromptTemplateService 获取模板,动态填充内容:

// 注入Prompt模板服务
@Resource
private PromptTemplateService promptTemplateService;

// 获取模板并填充内容
String template = promptTemplateService.getTemplate("rag.qa");
String context = "Java+AI脚手架支持OCR、多模态等功能";
String question = "这个脚手架有哪些AI功能?";
String finalPrompt = String.format(template, context, question);

// 调用AI
String response = aiService.chat(finalPrompt);

5. 限流配置调整

修改 application.yml 中的限流参数,调整每秒允许的请求数:

rate-limit:
  enabled: true           # 关闭限流设置为false
  permits-per-second: 15  # 调整为每秒15个请求

🔍 扩展建议

  • 多模态扩展:对接Stable Diffusion,实现文生图、图生图功能

  • 敏感词审核扩展:对接百度/阿里云内容安全API,提升审核精度

  • 向量库集成:添加Milvus/Chroma向量库,实现RAG知识库功能

  • 接口文档:集成SpringDoc,自动生成API文档(访问/swagger-ui.html)

  • 分布式部署:适配K8s,实现容器化部署、弹性伸缩

  • 缓存优化:添加Redis缓存AI高频请求结果,降低模型调用成本

⚠️ 注意事项

  • AI Key、OCR Key需妥善保管,建议通过配置中心管理,避免硬编码

  • Redis需正常启动,否则限流功能无法生效

  • MySQL需提前执行建表SQL,否则审计日志、Prompt模板功能无法使用

  • Spring AI当前使用里程碑版本,生产环境可等待正式版发布后替换

  • 敏感词审核为基础版本,可根据业务需求扩展违规词库或对接第三方接口

  • AI大模型配置注意:通义千问兼容地址(https://dashscope.aliyuncs.com/compatible-mode/v1)若出现「link hit security strategy」报错,需检查API Key有效性、网络环境是否正常,或替换为其他兼容的大模型地址(如OpenAI官方地址)

  • 多模态图片URL注意:避免使用example.com等测试地址、无效地址或违规图片地址,此类地址会触发安全拦截,导致「link hit security strategy」报错,建议使用自有服务器存储的图片地址或公开可访问的合法图片URL

  • OCR识别需确保百度OCR API Key、Secret Key配置正确,否则会出现识别失败报错,可在百度AI控制台核对密钥信息;同时避免上传过大、模糊或非图片格式的文件

  • 接口调试时,若出现「link hit security strategy」报错,优先排查两个场景:1. AI大模型base-url(通义千问兼容地址)配置或API Key异常;2. 多模态图片URL无效、被拦截或格式错误

二、导入 IDEA 运行的 Java+AI 企业级脚手架项目

全部功能:Spring Boot3 + Spring AI + OCR + 多模态 + Redis 分布式限流 + Prompt 模板 + 审计日志 + 敏感词审核 + 全局异常 + 统一返回 + 测试用例

你只需要:

  1. 新建 Maven 项目
  2. 复制对应文件
  3. 配好 Redis / AI Key
  4. 直接启动

(1)、项目结构(完整)

ai-enterprise-scaffold/
├── pom.xml
├── src/main/java/com/ai/scaffold/
│   ├── AiScaffoldApplication.java        # 启动类
│   ├── config/
│   │   ├── MvcConfig.java                # 拦截器注册
│   │   ├── RedisConfig.java
│   │   └── RateLimitProperties.java
│   ├── core/
│   │   ├── Result.java                   # 统一返回
│   │   ├── GlobalExceptionHandler.java   # 全局异常
│   │   ├── RateLimitException.java
│   │   └── SensitiveException.java
│   ├── ai/
│   │   ├── AiService.java                 # AI统一服务
│   │   ├── OcrService.java                # OCR
│   │   ├── MultiModalService.java         # 多模态
│   │   ├── PromptTemplateService.java     # Prompt模板
│   │   └── SensitiveCheckService.java     # 敏感词审核
│   ├── audit/
│   │   ├── AiAuditLog.java                # 审计日志实体
│   │   ├── AiAuditLogMapper.java
│   │   └── AuditLogAspect.java            # AOP自动记录
│   ├── interceptor/
│   │   └── RedisRateLimitInterceptor.java # 分布式限流
│   ├── controller/
│   │   └── AiController.java              # 接口
│   └── util/
│       └── DocUtil.java                   # 文档解析
├── src/main/resources/
│   └── application.yml
└── src/test/java/com/ai/scaffold/AiTest.java # 测试用例

(2)、pom.xml(完整可复制)

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.5</version>
    </parent>

    <groupId>com.ai</groupId>
    <artifactId>ai-enterprise-scaffold</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <spring-ai.version>1.0.0-M1</spring-ai.version>
    </properties>

    <dependencies>
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Spring AI -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>

        <!-- Redis & Redisson 限流 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.26.0</version>
        </dependency>

        <!-- MySQL & MyBatis -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

        <!-- 百度OCR -->
        <dependency>
            <groupId>com.baidu.aip</groupId>
            <artifactId>java-sdk</artifactId>
            <version>4.16.1</version>
        </dependency>

        <!-- Hutool -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.25</version>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
</project>

(3)、application.yml

yaml

server:
  port: 8080

spring:
  application:
    name: ai-enterprise-scaffold

  # AI 大模型(通义千问兼容OpenAI格式)
  ai:
    openai:
      api-key: sk-xxxxxx
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
      chat:
        options:
          model: qwen-turbo
          temperature: 0.7

  # Redis
  data:
    redis:
      host: 127.0.0.1
      port: 6379

  # 数据库(审计日志)
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ai_scaffold?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

# OCR
ocr:
  baidu:
    api-key: xxxx
    secret-key: xxxx

# 限流
rate-limit:
  enabled: true
  permits-per-second: 10

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

(4)、启动类

package com.ai.scaffold;

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

@SpringBootApplication
@EnableAspectJAutoProxy
@MapperScan("com.ai.scaffold.audit")
public class AiScaffoldApplication {
    public static void main(String[] args) {
        SpringApplication.run(AiScaffoldApplication.class, args);
    }
}

(5)、Core 核心模块

Result.java

package com.ai.scaffold.core;

import lombok.Data;

@Data
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMsg("success");
        r.setData(data);
        return r;
    }

    public static <T> Result<T> fail(String msg) {
        Result<T> r = new Result<>();
        r.setCode(500);
        r.setMsg(msg);
        return r;
    }
}

GlobalExceptionHandler.java

package com.ai.scaffold.core;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RateLimitException.class)
    public Result<?> rateLimit() {
        return Result.fail("请求过于频繁,请稍后再试");
    }

    @ExceptionHandler(SensitiveException.class)
    public Result<?> sensitive() {
        return Result.fail("内容包含违规信息,已拦截");
    }

    @ExceptionHandler(Exception.class)
    public Result<?> ex(Exception e) {
        return Result.fail(e.getMessage());
    }
}

RateLimitException.java

package com.ai.scaffold.core;

public class RateLimitException extends RuntimeException {
}

SensitiveException.java

package com.ai.scaffold.core;

public class SensitiveException extends RuntimeException {
}

(6)、限流配置

RateLimitProperties.java

package com.ai.scaffold.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "rate-limit")
public class RateLimitProperties {
    private boolean enabled;
    private int permitsPerSecond;
}

RedisRateLimitInterceptor.java

package com.ai.scaffold.interceptor;

import com.ai.scaffold.config.RateLimitProperties;
import com.ai.scaffold.core.RateLimitException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.redisson.api.RateType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
@RequiredArgsConstructor
public class RedisRateLimitInterceptor implements HandlerInterceptor {

    private final RedissonClient redissonClient;
    private final RateLimitProperties rateLimit;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!rateLimit.isEnabled()) return true;

        String ip = request.getRemoteAddr();
        String key = "rate_limit:" + ip;

        RRateLimiter limiter = redissonClient.getRateLimiter(key);
        limiter.trySetRate(RateType.OVERALL, rateLimit.getPermitsPerSecond(), 1, java.util.concurrent.TimeUnit.SECONDS);

        if (!limiter.tryAcquire(1)) {
            throw new RateLimitException();
        }
        return true;
    }
}

MvcConfig.java

package com.ai.scaffold.config;

import com.ai.scaffold.interceptor.RedisRateLimitInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@RequiredArgsConstructor
public class MvcConfig implements WebMvcConfigurer {
    private final RedisRateLimitInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor)
                .addPathPatterns("/ai/**");
    }
}

(7)、AI 核心服务

OcrService.java

package com.ai.scaffold.ai;

import com.baidu.aip.ocr.AipOcr;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;

@Service
@Slf4j
public class OcrService {

    @Value("${ocr.baidu.api-key}")
    private String apiKey;

    @Value("${ocr.baidu.secret-key}")
    private String secretKey;

    private AipOcr client;

    @PostConstruct
    public void init() {
        client = new AipOcr("", apiKey, secretKey);
    }

    public String recognize(byte[] bytes) {
        JSONObject res = client.basicGeneral(bytes, new HashMap<>());
        StringBuilder sb = new StringBuilder();
        res.getJSONArray("words_result").forEach(w -> {
            JSONObject jo = (JSONObject) w;
            sb.append(jo.getString("words")).append("\n");
        });
        return sb.toString();
    }
}

MultiModalService.java

package com.ai.scaffold.ai;

import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.model.Media;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;

import java.net.URL;

@Service
@RequiredArgsConstructor
public class MultiModalService {

    private final ChatModel chatModel;

    public String understand(URL imageUrl, String question) {
        UserMessage userMessage = new UserMessage(question,
                new Media(MimeTypeUtils.IMAGE_PNG, imageUrl));
        Prompt prompt = new Prompt(userMessage);
        return chatModel.call(prompt).getResult().getOutput().getContent();
    }
}

PromptTemplateService.java

package com.ai.scaffold.ai;

import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PromptTemplateService {

    private final JdbcTemplate jdbcTemplate;

    public String getTemplate(String code) {
        String sql = "select content from ai_prompt_template where code = ?";
        return jdbcTemplate.queryForObject(sql, String.class, code);
    }
}

SensitiveCheckService.java

package com.ai.scaffold.ai;

import com.ai.scaffold.core.SensitiveException;
import org.springframework.stereotype.Service;

@Service
public class SensitiveCheckService {

    public void check(String text) {
        // 可对接百度/阿里云内容安全
        if (text.contains("违规")) {
            throw new SensitiveException();
        }
    }
}

AiService.java

package com.ai.scaffold.ai;

import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AiService {

    private final ChatModel chatModel;
    private final SensitiveCheckService sensitiveCheck;

    public String chat(String prompt) {
        sensitiveCheck.check(prompt);
        return chatModel.call(prompt);
    }
}

(8)、审计日志 AOP

AiAuditLog.java

package com.ai.scaffold.audit;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class AiAuditLog {
    private Long id;
    private String ip;
    private String prompt;
    private String response;
    private LocalDateTime createTime;
}

AiAuditLogMapper.java

package com.ai.scaffold.audit;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AiAuditLogMapper {
    @Insert("insert into ai_audit_log(ip,prompt,response,create_time) values(#{ip},#{prompt},#{response},now())")
    int insert(AiAuditLog log);
}

AuditLogAspect.java

package com.ai.scaffold.audit;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
@RequiredArgsConstructor
public class AuditLogAspect {

    private final AiAuditLogMapper mapper;

    @Around("execution(* com.ai.scaffold.controller.AiController.*(..))")
    public Object log(ProceedingJoinPoint join) throws Throwable {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = req.getRemoteAddr();
        Object[] args = join.getArgs();
        String prompt = args[0] == null ? "" : args[0].toString();

        Object res = join.proceed();
        String response = res.toString();

        AiAuditLog log = new AiAuditLog();
        log.setIp(ip);
        log.setPrompt(prompt);
        log.setResponse(response);
        mapper.insert(log);

        return res;
    }
}

(9)、Controller

package com.ai.scaffold.controller;

import com.ai.scaffold.ai.AiService;
import com.ai.scaffold.core.Result;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/ai")
@RequiredArgsConstructor
public class AiController {

    private final AiService aiService;

    @GetMapping("/chat")
    public Result<String> chat(@RequestParam String prompt) {
        return Result.success(aiService.chat(prompt));
    }
}

(10)、测试用例

package com.ai.scaffold;

import com.ai.scaffold.ai.AiService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class AiTest {

    @Resource
    private AiService aiService;

    @Test
    public void testChat() {
        String rep = aiService.chat("你好,介绍下Java+AI");
        System.out.println(rep);
    }
}

(11)、必须建表 SQL

sql

CREATE DATABASE IF NOT EXISTS ai_scaffold;
USE ai_scaffold;

CREATE TABLE ai_prompt_template (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(64) UNIQUE NOT NULL,
    content TEXT NOT NULL,
    name VARCHAR(128)
);

CREATE TABLE ai_audit_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    ip VARCHAR(64),
    prompt TEXT,
    response TEXT,
    create_time DATETIME DEFAULT NOW()
);

(12)、一键启动说明

  1. 启动 Redis
  2. 启动 MySQL,执行上面 SQL
  3. 配置好 application.yml 中的 AI Key、OCR Key、MySQL 账号
  4. 运行 AiScaffoldApplication
  5. 访问接口:

    plaintext

    http://localhost:8080/ai/chat?prompt=你好
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐