Java+AI 企业级架构项目
一、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 模板 + 审计日志 + 敏感词审核 + 全局异常 + 统一返回 + 测试用例
你只需要:
- 新建 Maven 项目
- 复制对应文件
- 配好 Redis / AI Key
- 直接启动
(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)、一键启动说明
- 启动 Redis
- 启动 MySQL,执行上面 SQL
- 配置好
application.yml中的 AI Key、OCR Key、MySQL 账号 - 运行
AiScaffoldApplication - 访问接口:
plaintext
http://localhost:8080/ai/chat?prompt=你好
更多推荐

所有评论(0)