作者完整实现了一个AI Prompt Lab全栈应用,整合认证授权、PostgreSQL持久化、OpenRouter多模型路由;Java 21虚拟线程处理高并发,React 19实现TypeScript类型安全的提示词管理界面。
AI Prompt Lab 是一个全栈应用,核心理念很简单:管理可复用的 AI Prompt 应该像操作其他结构化应用资产一样自然,而不是把它们散落在笔记、文本文件或聊天记录里。
这个项目整合了 Java 21、Spring Boot、React 19、TypeScript、PostgreSQL 和 OpenRouter,用一套紧凑的架构涵盖了现代 Web 应用的主要关注点:认证、授权、持久化、外部 API 集成以及敏感配置管理。
我刻意让系统保持相对简单。目标不是为了引入架构模式而引入,而是构建一个每个技术都有明确职责的清晰应用。
AI Prompt Lab 提供一个认证工作空间,用户可以在其中创建、更新、整理和复用生成式 AI 模型的 Prompt。
每个用户有独立的 Prompt 集合,并可以配置 OpenRouter 账户,通过应用与不同的语言模型交互。
整体来看,应用提供以下功能:
从架构角度看,这使得项目比传统 CRUD 应用更有意思,同时又足够小,整体设计易于理解和推演。
仓库分为两个独立应用:
backend/
frontend/
技术栈有意选择了常规方案。
没有微服务拆分,前端没有外部状态管理库,也没有应用当前不需要的额外基础设施。
在这个范围内,将系统保持为模块化单体在可维护性、部署复杂度和开发速度之间提供了更好的平衡。
Spring Boot 应用遵循传统的分层结构:
controller
service
repository
model
config
分层逻辑很直接。
Controller 定义 HTTP API,Service 包含应用逻辑,Repository 封装持久化操作,配置组件处理横切关注点(如安全和加密)。
对于这个规模的应用,我更偏好这种方式,因为控制流是显式的。请求通过 Controller 进入,穿过 Service 层到达持久化层,不会引入不必要的间接层。
Prompt 管理是应用的核心领域。
已认证用户可以创建、编辑、删除和检索与其账户关联的 Prompt。
后端也在 Repository 层直接支持排序,将数据导向的操作保持在持久化层附近,而不是在客户端重新实现。
最终得到一个小型 REST API,具有可预测的面向资源的操作,客户端展示层和服务器端数据访问之间有清晰的分离。
认证完全由 Spring Security 处理。
密码使用 BCrypt 哈希存储,认证会话由服务器签发的令牌表示,保存在 HTTP-only cookie 中。
浏览器会在后续请求中自动发送该 cookie,自定义的 Spring Security 过滤器在请求到达应用层之前恢复已认证用户。
流程本质上是:
Login request
|
v
Credential verification
|
v
Session token creation
|
v
HTTP-only cookie
|
v
Spring Security filter
|
v
Authenticated request
授权在后端强制执行,而不是委托给用户界面。
应用区分普通用户和管理员,管理员端点通过 Spring Security 保护。
因此前端可以用角色来调整导航和展示,而服务器仍是访问控制的权威边界。
PostgreSQL 提供持久化层,Spring Data JPA 处理 Repository 访问和实体映射。
Schema 演进通过 Flyway 迁移管理。
我更偏好将数据库变更与应用程序一起显式版本化管理,而不是依赖运行时自动变更 schema。
这让数据库获得了与代码库其他部分同等的可追溯性:
V1__init_database.sql
V2__future_change.sql
V3__another_change.sql
这也让应用在不同环境之间迁移更容易,因为 schema 的创建和演进是可控过程的一部分,而不是手动部署步骤。
AI 请求由后端处理,而不是直接从 React 发送到外部提供商。
前端发送对话到 Spring Boot API,后端加载当前用户的 AI 配置,然后向 OpenRouter 发起请求。
React
|
v
Spring Boot API
|
v
User AI configuration
|
v
OpenRouter
|
v
Language model
|
v
Response returned to React
这个边界将提供商特定的行为隔离在前端之外,让后端控制认证头、请求构建和外部 API 通信。
这也为未来扩展留出了空间。
OpenRouter 最终可以成为通用 AI 提供商接口背后的一个实现,UI 不需要大的改动。
允许用户配置 OpenRouter 引入了一个基本 CRUD 流程中没有的安全需求:第三方凭证必须被持久化,但不能被视为普通应用数据。
项目使用 AES GCM 模式加密 API 密钥后再存储到 PostgreSQL。
后端仅在需要向外部提供商发起请求时才解密值。
OpenRouter API key
|
v
AES-GCM encryption
|
v
Encrypted database value
|
v
Backend decryption
|
v
External API request
将这个职责放在服务器端也防止提供商凭证成为 React 应用配置的一部分。
前端使用 React 19 和 TypeScript 实现,通过 Vite 构建。
源码树围绕一组小而明确的职责组织:
components/
pages/
context/
services/
types/
React Router 处理导航,React Context 用于认证用户状态。
对于应用当前的规模,Context 已经够用。引入更大的状态管理方案会增加一层抽象,而不会解决具体问题。
大多数应用状态保持在拥有它的页面或组件本地,只有真正需要共享的状态才提升到认证上下文中。
HTTP 通信集中在一个独立的前端服务中,而不是分散在各个组件里。
该层对外暴露以下操作:
这样 React 组件可以专注于渲染和交互,请求构建、凭证和通用响应处理都集中在一处。
如果后端 API 后续有变动,这也创造了一个有用的边界:大多数 HTTP 级别的变更可以隔离在服务层内部,而不必传播到整个 UI。
聊天页面是系统大部分组件汇聚的地方。
用户可以使用已保存的 Prompt,提供额外上下文,然后将对话发送到配置好的语言模型。
React 管理交互和对话状态,Spring Boot 执行已认证的服务器端操作,PostgreSQL 提供用户特定配置,OpenRouter 处理模型请求。
最终流程是:
Prompt
+
User message
|
v
React chat interface
|
v
Spring Boot
|
v
OpenRouter
|
v
AI model response
|
v
React chat interface
这可能是项目中最能体现整体架构的部分,因为它跨越了应用的每一个主要边界,同时没有让这些层相互耦合。
AI Prompt Lab 背后的主要设计决策之一是避免解决应用本身没有的问题。
后端是一个单一的 Spring Boot 应用,因为目前没有领域或运营需求需要将系统分布到多个服务。
前端不使用 Redux,因为认证是唯一重要的全局状态,而 React Context 已经覆盖了这个需求。
数据模型保持关系型,因为应用的实体和所有权关系很自然地映射到 PostgreSQL。
这是一个我倾向于无论技术栈如何都遵循的原则:引入抽象是为了消除有意义的复杂性,而不是仅仅因为抽象存在就引入它。
一个相对小但边界清晰的系统,往往比一个基础设施比领域更复杂的过度工程化系统更容易演进。
当前架构为未来开发留出了几个自然方向。
Prompt 可以按领域、目的或工作流分组,使更大的集合更易于管理。
不再每次更新时替换 Prompt 内容,而是保留历史版本并支持比较。
聊天 API 可以从请求-响应通信迁移到 Server-Sent Events 或其他流式机制,让生成的内容在 UI 中逐步显示。
内部提供商抽象可以支持 OpenRouter、OpenAI、Anthropic 或本地部署的模型,置于统一的应用接口之后。
聊天会话可以存储和重新打开,而不是仅存在于当前前端会话期间。
后端可以收集令牌使用量、请求延迟和每个模型的预估成本,将应用转变为一个更完整的 Prompt 实验环境。
AI Prompt Lab 是一个紧凑的全栈应用,将真实系统中通常会出现的几个关注点整合在一起:持久化、认证、授权、密钥处理、外部 HTTP 集成和类型化前端。
后端使用 Java 21 和 Spring Boot 提供应用和安全层,PostgreSQL 管理持久化状态,React 19 和 TypeScript 提供用户界面,OpenRouter 将应用连接到不同的语言模型。
这个项目有意不以架构新颖性为核心。
它的设计基于保持职责清晰、只在需求合理的地方才引入复杂性。
对我来说,这就是这个项目有意思的地方:前端、后端、持久化、安全和 AI 集成被视为同一个系统的组成部分,同时每一层都保持对明确定义的职责负责。
完整源代码可在 GitHub 获取:
Java21_React19_AIPromptLab on GitHub
Originally published on CertosinoLab.