本文档基于当前实现代码(2026-01-18)描述 GraphNote 应用的完整架构设计。
GraphNote 是一个 AI 驱动的知识管理工具,通过关系图可视化帮助用户管理笔记和知识。
- 关系图可视化:使用 G6 库实现节点和边的交互式编辑
- AI 文本分析:通过 OpenAI 兼容接口(可配置自定义 URL)自动从文本提取实体和关系
- 笔记管理:富文本编辑、笔记 CRUD、版本控制
- 用户认证:JWT 令牌认证,支持注册/登录
- 数据导出:支持 JSON 和 CSV 格式的数据导出
- 共享功能:每个笔记生成唯一 8 字符 ID 用于共享
┌─────────────────────────────────────────────────────────────┐
│ 客户端浏览器 │
│ (Vue 3 + TypeScript) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ GraphNote Web 前端应用 │ │
│ │ ┌────────────┐ ┌───────────┐ ┌──────────────┐ │ │
│ │ │ Views │ │ Components│ │ Stores │ │ │
│ │ │ (Pages) │ │(UI/Graph) │ │ (Pinia) │ │ │
│ │ └────────────┘ └───────────┘ └──────────────┘ │ │
│ │ ↓ ↓ ↓ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Services Layer (Axios) │ │ │
│ │ │ - auth.ts │ │ │
│ │ │ - notebook.ts │ │ │
│ │ │ - api.ts (拦截器、token 管理) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ HTTP/JSON (REST API)
│
┌─────────────────────────────────────────────────────────────┐
│ 后端 API 服务器 │
│ (Express + TypeScript) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Middleware Stack │ │
│ │ - helmet (安全头) │ │
│ │ - cors (跨域) │ │
│ │ - rate-limit (速率限制) │ │
│ │ - auth (JWT 验证) │ │
│ │ - validation (Zod 数据验证) │ │
│ │ - errorHandler (统一错误处理) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Routes + Controllers │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ │
│ │ │ auth.ts │ │ notebook.ts │ │ graph.ts │ │ │
│ │ │ (注册/登录) │ │ (CRUD/导出) │ │(AI分析) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ │ - Controllers: 请求处理 │ │
│ │ - Services: JWT、密码处理 │ │
│ │ - Validation: 请求数据验证 │ │
│ │ - LLM Integration: OpenAI 兼容 API │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Database Layer (Prisma ORM) │ │
│ │ - Schema: User, Notebook, Version │ │
│ │ - Relationships: 用户→笔记→版本 │ │
│ │ - Indexes: userId, shareId for performance │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL 数据库 │
│ - User 表:用户账户信息 │
│ - Notebook 表:笔记内容、关系图数据 │
│ - Version 表:版本历史和变更追踪 │
└─────────────────────────────────────────────────────────────┘
外部服务:
├── LLM 服务(OpenAI 兼容格式,LLM_BASE_URL 可配置)
└── 数据库服务
└── PostgreSQL 服务
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.5+ | 核心框架 |
| TypeScript | 5.x | 类型系统 |
| Vite | 7.x | 构建工具 |
| TailwindCSS | 3.x | 样式框架 |
| Pinia | 2.x | 状态管理 |
| Vue Router | 4.x | 路由管理 |
| Axios | 1.x | HTTP 客户端 |
| G6 | 5.x | 图表可视化 |
| Tiptap | 2.x | 富文本编辑 |
| 技术 | 版本 | 用途 |
|---|---|---|
| Express.js | 4.x | Web 框架 |
| TypeScript | 5.x | 类型系统 |
| Prisma | 5.x | ORM |
| PostgreSQL | 14.x | 数据库 |
| JWT | - | 认证 |
| Bcrypt | - | 密码加密 |
| Zod | - | 数据验证 |
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer (展示层) │
│ - Vue 组件(Views、Components) │
│ - Pinia Store(状态管理) │
│ - 路由管理(Vue Router) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Business Logic Layer (业务逻辑层) │
│ - Services(API 调用、业务操作) │
│ - Controllers(请求处理) │
│ - Utils(JWT、密码等工具) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Data Access Layer (数据访问层) │
│ - Prisma ORM(数据库交互) │
│ - PostgreSQL(数据存储) │
└─────────────────────────────────────────────────────────┘
前端 Axios Service
↓
HTTP Request + JWT Token (Authorization header)
↓
后端 Middleware Stack
├─ CORS 检查
├─ 身份验证 (auth.ts)
├─ 请求验证 (validation.ts)
└─ 速率限制
↓
Route Handler → Controller → Business Logic
↓
Prisma ORM → PostgreSQL
↓
Response JSON
↓
前端 Axios 拦截器处理
├─ 成功:数据保存到 Store
├─ 401 Unauthorized:清除 token,重定向到登录
└─ 其他错误:错误处理
User {
id: String (CUID) // 唯一标识符
email: String @unique // 邮箱(唯一)
username: String @unique // 用户名(唯一)
password: String // 加密密码 (bcrypt)
avatar: String? // 头像 URL(可选)
createdAt: DateTime // 创建时间
updatedAt: DateTime // 更新时间
// 关系
notebooks: Notebook[] // 用户拥有的笔记
versions: Version[] // 用户创建的版本
}Notebook {
id: String (CUID) // 笔记唯一 ID
shareId: String @unique // 共享 ID(8 字符)
title: String // 笔记标题
description: String? // 笔记描述
content: String // 笔记内容(Markdown/HTML)
userId: String (FK) // 所有者 ID
graph: Json { // 关系图数据
nodes: [{
id: String // 节点唯一 ID
label: String // 节点显示文本
type: String // 节点类型 (concept/person/place/event/other)
properties: Object // 其他属性
}]
edges: [{
id: String // 边唯一 ID
source: String // 源节点 ID
target: String // 目标节点 ID
label: String? // 边标签
type: String // 边类型
}]
layout: Object // 布局配置
}
isPublic: Boolean @default(false) // 是否公开
tags: String[] // 标签数组
createdAt: DateTime // 创建时间
updatedAt: DateTime // 更新时间
// 关系
user: User // 所有者
versions: Version[] // 版本历史
// 索引
@@index([userId])
@@index([shareId])
}Version {
id: String (CUID) // 版本唯一 ID
notebookId: String (FK) // 关联笔记 ID
content: String // 该版本的内容
graphSnapshot: Json // 该版本的图表数据快照
action: String // 操作类型 (create/edit/delete)
userId: String (FK) // 操作用户 ID
createdAt: DateTime // 创建时间
// 关系
notebook: Notebook
user: User
// 索引
@@index([notebookId])
@@index([userId])
}User (1) ──→ (Many) Notebook
├─ id
├─ title
├─ content
├─ graph (nodes + edges)
└─ shareId (用于分享)
User (1) ──→ (Many) Version
Notebook (1) ──→ (Many) Version
graphnote-web/src/
├── main.ts # 应用入口(Vue + Pinia + Router)
├── App.vue # 根组件
├── assets/ # 静态资源
├── router/
│ └── index.ts # 路由配置和守卫
├── stores/ # Pinia 状态管理
│ ├── user.ts # 用户认证状态(token + refreshToken)
│ └── notebook.ts # 笔记列表和编辑状态
├── services/ # API 服务层
│ ├── api.ts # Axios 配置、401 自动刷新 token 并重试
│ ├── auth.ts # 认证 API
│ └── notebook.ts # 笔记 API
├── composables/ # Vue 组合式函数
│ └── useNotification.ts # 全局通知系统(模块级共享状态)
├── views/ # 页面组件
│ ├── HomePage.vue # 首页
│ ├── LoginPage.vue # 登录页
│ ├── RegisterPage.vue # 注册页
│ ├── NotebookPage.vue # 笔记编辑页(主要功能页)
│ ├── SharePage.vue # 公开分享页(只读,无需登录)
│ └── SettingsPage.vue # 设置页
├── components/ # 可复用组件
│ ├── NotificationCenter.vue # 通知中心
│ ├── editor/
│ │ └── RichTextEditor.vue # Tiptap 富文本编辑器
│ ├── graph/
│ │ ├── GraphContainer.vue # G6 图表容器
│ │ ├── NodeEditModal.vue # 节点编辑弹窗
│ │ ├── EdgeEditModal.vue # 边编辑弹窗
│ │ ├── NodeTypeStyleEditor.vue# 节点类型样式编辑
│ │ ├── EdgeTypeStyleEditor.vue# 边类型样式编辑
│ │ └── ContextMenu.vue # 右键菜单
│ └── [其他通用组件]
├── utils/ # 工具函数
├── types/ # TypeScript 类型定义
└── style.css # 全局样式
LoginPage
↓
authService.login(email, password)
↓
API POST /api/auth/login
↓
Backend: hash 验证 → 生成 JWT token
↓
Response: { token, user }
↓
localStorage.setItem('token', token)
apiClient 拦截器自动添加到所有请求头
↓
userStore.setUser(user)
↓
Router 重定向到 NotebookPage
NotebookPage mounted
↓
notebookStore.loadNotebooks()
↓
API GET /api/notebooks
↓
Backend: Prisma 查询 User 的所有 Notebooks
↓
Response: Notebook[]
↓
notebookStore.setNotebooks(data)
↓
模板渲染笔记列表或显示当前笔记
NotebookPage - 用户输入文本
↓
点击 "解析关系图" 按钮
↓
notebookService.analyzeText(text)
↓
API POST /api/graphs/analyze { text }
↓
后端路由到 graphController.analyzeText()
↓
analyzeWithLLM(text)
↓
调用 OpenAI 兼容接口(LLM_BASE_URL 可指向官方或第三方服务)
↓
系统提示词:提取 3-5 个实体和 3-5 条关系
↓
LLM 返回 JSON: { nodes: [], edges: [] }
↓
enrichGraphDataWithIds(graphData)
├─ 剥离可能存在的 markdown 代码围栏
├─ 为每个 node 生成 id (node_1, node_2...)
├─ 建立 label → id 映射
├─ 为每条边生成 id
└─ 验证 edge 的 source/target 存在
↓
Response: graphData { nodes, edges }
↓
前端接收数据
↓
graphStore.updateGraph(graphData)
↓
G6 容器刷新显示新的图表
用户编辑笔记或图表
↓
更改监听器触发
↓
notebookService.updateNotebook(shareId, { content, graph })
↓
API PUT /api/notebooks/:shareId
↓
Backend: authMiddleware 验证用户
↓
Backend: 权限检查 (userId == notebook.userId)
↓
Prisma 更新 notebook 记录
↓
创建 Version 记录(用于版本控制)
↓
Response: 更新后的 Notebook 对象
↓
前端 notebookStore 更新本地状态
↓
显示保存成功通知
interface UserState {
user: User | null
token: string | null // access token(1 小时)
refreshToken: string | null // refresh token(7 天)
isAuthenticated: boolean
}
// 401 时 axios 拦截器自动调用 /auth/refresh 换新 token 并重试原请求
setUser(user)
setToken(token)
setRefreshToken(token)
logout()interface NotebookState {
notebooks: Notebook[]
currentNotebook: Notebook | null
isLoading: boolean
error: string | null
}
// saveNotebook 响应返回时校验 shareId 是否仍是当前笔记,
// 防止慢响应覆盖切换后的新笔记(竞态保护)
createNotebook()
loadNotebooks()
setCurrentNotebook(notebook)
updateCurrentNotebook(updates)
deleteNotebook(id)已移除(原为空壳死代码)。图表状态由 GraphContainer 组件内部管理, UI 通知由 useNotification 的模块级共享状态管理。
backend/src/
├── main.ts # Express 应用入口
├── routes/ # 路由定义
│ ├── auth.ts # 认证路由 (public + protected)
│ ├── notebooks.ts # 笔记 CRUD 路由
│ ├── graphs.ts # 图表分析路由
│ └── shared.ts # 公开分享路由(无需认证)
├── controllers/ # 请求处理器
│ ├── auth.ts # authController
│ ├── notebook.ts # notebookController
│ └── graph.ts # graphController (LLM 集成)
├── middlewares/ # 中间件
│ ├── auth.ts # JWT 验证中间件
│ ├── errorHandler.ts # 全局错误处理
│ ├── validation.ts # 请求验证(Zod)
├── utils/ # 工具函数
│ ├── jwt.ts # JWT 生成/验证(access + refresh)
│ ├── password.ts # 密码加密/比较
│ └── graph.ts # LLM 图数据校验/补全
├── schemas/ # Zod 验证 schemas
│ └── validation.ts # 所有验证规则
├── lib/
│ └── prisma.ts # Prisma 单例
└── prisma/
├── schema.prisma # 数据库 Schema
└── migrations/ # 数据库迁移文件
backend/tests/ # vitest 单元测试(npm test)
├── graph.test.ts # 图数据校验/补全
├── jwt.test.ts # access/refresh token
└── validation.test.ts # 输入校验边界
1. helmet()
└─ 设置安全 HTTP 头
2. cors()
└─ 处理跨域请求
3. rateLimit()
└─ 全局限速(15min window, 600 requests max)
└─ LLM 分析接口单独限速(15min window, 30 requests max)
4. express.json()
└─ 解析 JSON body
5. express.urlencoded()
└─ 解析 URL 编码数据
6. authMiddleware (selective)
└─ 验证 JWT token(仅在受保护路由上)
7. validationMiddleware (selective)
└─ 验证请求数据(Zod schemas)
8. routeHandlers
└─ 实际的路由处理器
9. errorHandler
└─ 全局错误处理
HTTP Request
↓
Middleware Stack (helmet, cors, rate-limit)
↓
Body Parser (JSON, urlencoded)
↓
Route Matching
↓
authMiddleware (if protected)
├─ 提取 Authorization header 中的 token
├─ 使用 JWT 验证和解析
├─ 将 user 对象注入到 req
└─ 如果验证失败,返回 401
↓
validationMiddleware (if required)
├─ 使用 Zod schema 验证 req.body
├─ 如果验证失败,返回 400
└─ 继续处理
↓
Controller Handler
├─ 接收验证后的数据
├─ 处理业务逻辑
├─ 调用 Prisma 或外部 API
└─ 返回响应
↓
Response JSON
↓
errorHandler (if error)
├─ 捕获未处理的错误
├─ 标准化错误响应
└─ 记录日志
↓
HTTP Response
POST /api/auth/register
Body: { email, username, password }
↓
validationMiddleware 验证格式
↓
authController.register()
├─ 检查 email 和 username 是否已存在
├─ bcrypt 加密密码
├─ Prisma 创建 User 记录
├─ 生成 JWT token (payload: {userId, email})
└─ 返回 { user, token }
POST /api/auth/login
Body: { email, password }
↓
authController.login()
├─ Prisma 查询 User by email
├─ 如果不存在,返回 401
├─ bcrypt 比较密码
├─ 如果不匹配,返回 401
├─ 生成新 JWT token
└─ 返回 { user, token }
GET /api/notebooks
Header: { Authorization: "Bearer {token}" }
↓
authMiddleware
├─ 提取 token 从 header
├─ jwt.verify(token)
├─ 解析 payload 得到 userId
├─ 将 { userId, email } 注入到 req.user
└─ 继续处理
↓
notebookController.listNotebooks()
├─ 使用 req.user.userId 查询笔记
└─ 返回该用户的所有笔记
| 函数 | 功能 | 认证 |
|---|---|---|
| register | 用户注册 | ✗ |
| login | 用户登录 | ✗ |
| getCurrentUser | 获取当前用户信息 | ✓ |
| logout | 用户登出 | ✓ |
| 函数 | 功能 | 认证 |
|---|---|---|
| createNotebook | 创建新笔记 | ✓ |
| listNotebooks | 列出用户的所有笔记 | ✓ |
| getNotebook | 获取单个笔记详情 | ✓ |
| updateNotebook | 更新笔记内容或图表 | ✓ |
| deleteNotebook | 删除笔记 | ✓ |
| getLatestNotebook | 获取最新笔记 | ✓ |
| exportNotebook | 导出笔记(JSON/CSV) | ✓ |
| 函数 | 功能 | 认证 |
|---|---|---|
| analyzeText | AI 文本分析,生成图表数据 | ✓ |
后端通过 OpenAI 兼容格式(POST {LLM_BASE_URL}/chat/completions)调用 LLM,
可指向官方 API 或任何兼容该格式的第三方服务,通过三个环境变量配置:
LLM_API_KEY:API 密钥(必填)LLM_BASE_URL:接口地址(默认https://api.openai.com/v1)LLM_MODEL:模型名称(默认gpt-4.1-nano)
analyzeText(text)
↓
analyzeWithLLM(text)
├─ 构建 systemPrompt
│ └─ "你是一个知识图谱构建专家..."
│ "提取 3-5 个主要实体和 3-5 条关系"
├─ API 调用: {LLM_BASE_URL}/chat/completions
├─ Model: LLM_MODEL 环境变量(默认 gpt-4.1-nano)
├─ Timeout: 30s
├─ Temperature: 0.7
├─ Max tokens: 1000
└─ Headers: Authorization: Bearer {LLM_API_KEY}
↓
LLM 返回 JSON response
├─ stripCodeFences:剥离可能携带的 markdown 代码围栏
├─ 解析 JSON 字符串
└─ catch 错误 → 返回 "Invalid response format"
↓
enrichGraphDataWithIds(graphData)(src/utils/graph.ts)
├─ 节点缺 id 时生成 node_N
├─ 边 source/target 先按 id 匹配、再按 label 匹配
├─ 过滤悬空边、重名 label 保留第一个并告警
└─ 边缺 id 时生成 edge_N
↓
Response: graphData with ids
1. 用户打开应用
↓
2. 检查 localStorage 中的 token
├─ 如果存在:自动登录(获取用户信息)
└─ 如果不存在:重定向到 LoginPage
3. 登录成功
↓
4. 进入 NotebookPage
↓
5. 加载笔记列表
├─ API GET /api/notebooks
└─ 显示笔记列表或创建新笔记
6. 选择或创建笔记
├─ 如果创建:API POST /api/notebooks
└─ 如果选择:加载笔记内容和图表
7. 编辑笔记文本
├─ RichTextEditor 组件处理编辑
└─ 保存到 notebookStore
8. 点击 "解析关系图"
├─ 收集文本内容
├─ API POST /api/graphs/analyze
├─ 后端调用 LLM API
└─ 返回 { nodes, edges }
9. 更新图表显示
├─ G6 容器接收新数据
└─ 渲染可交互的图表
10. 编辑图表(可选)
├─ 拖拽节点
├─ 修改节点/边属性
└─ 删除节点/边
11. 保存笔记
├─ 收集最新的 content 和 graph
├─ API PUT /api/notebooks/:shareId
├─ 后端创建 Version 记录
└─ 显示保存成功
12. 导出笔记(可选)
├─ API GET /api/notebooks/:shareId/export?format=json
├─ 后端返回导出数据
└─ 前端下载文件
13. 登出
├─ 清除 localStorage token
├─ 重置所有 stores
└─ 重定向到 LoginPage
POST /api/auth/register
请求: { email, username, password }
响应: { user, token }
认证: ✗
POST /api/auth/login
请求: { email, password }
响应: { user, token }
认证: ✗
GET /api/auth/me
响应: { user }
认证: ✓
POST /api/auth/logout
响应: { message }
认证: ✓
POST /api/auth/refresh
请求: { refreshToken }
响应: { token, refreshToken }(签发新的 access/refresh token;JWT 无状态,旧 refresh token 在过期前仍有效)
认证: ✗
POST /api/notebooks
请求: { title?, content?, description? }
响应: { Notebook }
认证: ✓
GET /api/notebooks
查询参数: page?, limit?
响应: { notebooks: [Notebook] }
认证: ✓
GET /api/notebooks/:shareId
响应: { Notebook }
认证: ✓
PUT /api/notebooks/:shareId
请求: { title?, content?, graph?, description?, isPublic?, tags? }
响应: { Notebook }
认证: ✓
DELETE /api/notebooks/:shareId
响应: { message }
认证: ✓
GET /api/notebooks/:shareId/export/json
响应: 笔记完整数据(带下载头)
认证: ✓
GET /api/notebooks/:shareId/export/csv/nodes
GET /api/notebooks/:shareId/export/csv/edges
响应: CSV 文件(带 UTF-8 BOM)
认证: ✓
GET /api/notebooks/:shareId/versions
响应: { versions: [{id, action, createdAt, userId}] }
认证: ✓
GET /api/notebooks/latest/fetch
响应: { Notebook } 或 null(无笔记时)
认证: ✓
GET /api/shared/:shareId
响应: { Notebook }(仅当 isPublic=true,否则 404)
认证: ✗(公开分享端点)
POST /api/graphs/analyze
请求: { text }
响应: { nodes, edges }
认证: ✓
说明: 使用 LLM 分析文本生成知识图谱
- 密码加密:使用 bcryptjs,盐轮数为 10
- JWT 令牌:
- Access Token 有效期:1 小时
- Refresh Token 有效期:7 天
- 密钥存储在 .env 文件中
- Token 中包含 userId 和 email
-
Zod Schema 验证:
- 注册/登录:验证邮箱格式、密码长度
- 笔记操作:验证标题、内容类型
- 图表分析:验证文本长度和格式
-
中间件验证链:
HTTP Request → Helmet (安全头) → CORS (跨域检查) → Rate Limit (速率限制) → Body Parse → Auth (JWT验证) → Validation (Zod schema) → Handler
-
用户隔离:
- 笔记只能由所有者访问和修改
- 每个笔记 CRUD 操作都检查 userId
- 版本记录关联到用户
-
SQL 注入防护:
- Prisma ORM 使用参数化查询
- 无原生 SQL 查询
-
Vue 3 自动转义:
- 所有模板插值 {{ }} 自动转义
- 仅在必要时使用 v-html
-
Tiptap 内容清理:
- 富文本编辑器清理 HTML 内容
- 防止恶意脚本注入
cors({
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
credentials: true // 允许发送 cookie
})- 全局:15 分钟窗口内限制 600 个请求(适配前端自动保存频率)
- LLM 分析接口:
POST /api/graphs/analyze单独限制 30 个请求/15 分钟,防止 API 费用被刷
-
数据库优化
- 添加更多索引(如 email, username)
- 实现查询缓存
-
API 性能
- 实现分页(笔记列表)
- 添加响应压缩
-
错误处理
- 更细粒度的错误代码
- 详细的错误日志
-
多用户协作
- WebSocket 实时同步
- CRDT 冲突解决
- 权限管理(OWNER/EDITOR/VIEWER)
-
高级功能
- 笔记搜索和全文索引
- AI 自动标签
- 关系强度计算
-
数据导入导出
- Markdown 导入
- Obsidian 同步
- Neo4j 导出
-
离线支持
- IndexedDB 本地存储
- Service Worker 缓存
-
移动应用
- React Native 客户端
- 移动特定的 UI 优化
-
企业功能
- SAML/OAuth 认证
- 审计日志
- 数据备份和恢复
GraphNote 采用前后端分离、三层架构、微服务就绪的设计模式,具有:
- 清晰的职责划分:表示层、业务逻辑层、数据访问层
- 强大的安全机制:JWT 认证、请求验证、权限控制
- 灵活的 LLM 集成:OpenAI 兼容格式,接口地址和模型均可配置
- 完整的错误处理:全局中间件、异常捕获
- 可扩展的架构:预留多用户协作、实时同步、高级搜索等扩展点
通过 Pinia Store 和 Composition API,前端实现了响应式的状态管理和复用逻辑。后端通过 Express 中间件栈和 Prisma ORM,提供了安全、高效的 API 服务。整个系统具有良好的可维护性和扩展性。