第5章 项目架构深度解析
作者
谭策 — 独立开发者 | AIOps 领域探索者
- 🌐 项目官网:ITOpsAgentinfo
- 📝 博客:zjzwfw.cloud
- 📧 邮箱:huawei_network@foxmail.com
- 💬 微信公众号:IT Online

许可证
MPL-2.0 © 谭策
本章导读
在前面的章节中,我们了解了项目的全貌和各项技术栈。本章将深入项目的内部架构,理解各个模块是如何组织、通信和协作的。这是从"使用者"到"开发者"的关键一步。
类比理解:如果前三章让你了解了这栋楼的外观、周边环境和入住流程,本章就是带你参观建筑的施工图纸、管道线路、电路布局和承重结构。只有理解了这些,你才能放心地装修(二次开发)而不影响整体安全。
学习目标
阅读完本章后,你将能够:
- 画出项目的完整架构图,说明各层职责
- 理解后端的路由 → 中间件 → 服务 → 数据库的请求链路
- 理解前端的路由 → 页面 → 组件 → API 调用的数据流
- 说明前后端之间的通信方式(HTTP + WebSocket)
- 理解项目的服务初始化和优雅关闭机制
- 独立定位项目中的任何一个功能模块
5.1 整体架构总览
5.1.1 三层架构模型
项目采用经典的三层架构(Three-Tier Architecture),将系统划分为三个独立的层级:
┌─────────────────────────────────────────────────────────────────────┐
│ 表现层 (Presentation Tier) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ React 前端应用 │ │
│ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │
│ │ │ 页面组件 │ │ 通用组件 │ │ 状态管理 │ │ HTTP客户端 │ │ │
│ │ └─────────┘ └──────────┘ └──────────┘ └────────────┘ │ │
│ │ │ │
│ │ 技术:React + TypeScript + Vite + Tailwind CSS │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
HTTP REST API + WebSocket
│
┌─────────────────────────────────────────────────────────────────────┐
│ 逻辑层 (Logic Tier) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Express 后端应用 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 路由层 │→│ 中间件层 │→│ 服务层 │→│ 数据模型 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ │ 技术:Node.js + Express + TypeScript + SQLite │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
文件/进程
│
┌─────────────────────────────────────────────────────────────────────┐
│ 数据层 (Data Tier) │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ SQLite 数据库 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 用户表 │ │ 服务器表 │ │ 任务表 │ │ 告警表 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ │ 技术:better-sqlite3 (同步 SQLite 驱动) │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘5.1.2 架构设计原则
| 原则 | 说明 | 项目中的体现 |
|---|---|---|
| 关注点分离 | 每层只关心自己的职责 | 前端管 UI,后端管业务逻辑,数据库管存储 |
| 高内聚低耦合 | 模块内部紧密相关,模块之间松耦合 | 服务层之间通过接口调用,不直接依赖实现 |
| 依赖倒置 | 高层模块不依赖低层模块 | 路由层依赖服务接口,不依赖具体数据库实现 |
| 单一职责 | 每个模块只做一件事 | 每个 service 文件只负责一个业务域 |
5.1.3 模块通信图
浏览器
│
├─ HTTP 请求 ──────────────────────────────→ Express 路由
│ │
│ 中间件链
│ │
│ 业务服务
│ │
│ SQLite 数据库
│ │
│ 响应数据
│ │
│ ←────────────────────────────────────────── JSON 响应
│
├─ WebSocket 连接 ─────────────────────────→ Socket.io 服务
│ │
│ SSH 连接 → 目标服务器
│ │
│ ←─────────────────────────────────────── 实时数据推送5.2 后端架构详解
5.2.1 后端目录结构
backend/src/
├── app.ts # 应用入口:Express 初始化 + 中间件 + 路由注册
├── routes/ # 路由层(47 个路由文件)
│ ├── authRoutes.ts # 认证路由
│ ├── agentRoutes.ts # Agent 管理
│ ├── workflowRoutes.ts # 工作流管理
│ ├── serverRoutes.ts # 服务器管理
│ ├── alertRoutes.ts # 告警管理
│ ├── ...
│ └── webhookRoutes.ts # 外部系统 Webhook
├── middleware/ # 中间件层
│ ├── auth.ts # JWT 认证中间件
│ ├── rateLimiter.ts # 请求频率限制
│ ├── trace.ts # 请求追踪
│ ├── commandFilter.ts # SSH 命令过滤
│ ├── validation.ts # 请求验证
│ └── errorHandler.ts # 全局错误处理
├── services/ # 业务逻辑层(50+ 个服务)
│ ├── llmService.ts # LLM 调用服务
│ ├── sshService.ts # SSH 连接服务
│ ├── workflowExecutor.ts # 工作流执行引擎
│ ├── alertService.ts # 告警处理服务
│ ├── agentExecutor.ts # Agent 执行服务
│ ├── copilotService.ts # AI Copilot 服务
│ ├── schedulerService.ts # 定时任务调度
│ └── ...
├── models/ # 数据模型层
│ ├── database.ts # 数据库初始化 + 表定义
│ ├── migrations.ts # 数据库迁移
│ └── presets/ # 预设数据初始化
│ ├── initAgents.ts
│ ├── initWorkflows.ts
│ └── ...
├── websocket/ # WebSocket 处理
│ └── handler.ts # WebSocket 事件处理
├── schemas/ # 数据验证 Schema
│ └── apiValidation.ts # API 参数验证
├── types/ # TypeScript 类型定义
│ ├── index.ts
│ └── errors.ts
└── utils/ # 工具函数
├── logger.ts # 日志工具
├── env.ts # 环境变量读取
├── apiConfig.ts # API 配置
├── retry.ts # 重试工具
└── sensitiveMask.ts # 敏感信息脱敏5.2.2 应用启动流程
后端启动是一个有序的初始化过程,在 app.ts 中按以下步骤执行:
1. 创建 Express 应用
↓
2. 创建 HTTP 服务器 + Socket.io 实例
↓
3. 注册全局中间件(Helmet、CORS、BodyParser、Morgan)
↓
4. 初始化数据库(创建表 + 预设数据)
↓
5. 初始化各业务服务
├── initAlertService() - 告警服务初始化
├── reportService.init() - 报告服务
├── copilotService.init() - Copilot 服务
├── schedulerService.init() - 定时任务调度
├── notificationService.init() - 通知服务
├── remediationService.init() - 修复服务
├── rootCauseAnalysisService.init() - 根因分析服务
├── backupService.init() - 备份服务
└── initTokenBlacklist() - Token 黑名单
↓
6. 设置 WebSocket 连接处理
↓
7. 注册路由(公开路由 → 受保护路由)
↓
8. 注册错误处理中间件
↓
9. 启动 HTTP 服务器(监听端口)
↓
10. 注册进程信号处理(SIGTERM、SIGINT)关键代码分析:
// 1. 创建 Express 应用和 HTTP 服务器
const app = express();
const httpServer = createServer(app);
// 2. 创建 Socket.io 实例,绑定到 HTTP 服务器
const io = new SocketIOServer(httpServer, {
cors: {
origin: env.ALLOWED_ORIGINS, // 允许的跨域来源
methods: ['GET', 'POST']
},
maxHttpBufferSize: 1e6, // 最大消息 1MB
pingTimeout: 60000, // 心跳超时 60 秒
pingInterval: 25000 // 心跳间隔 25 秒
});
// 3. 中间件注册顺序很重要!
app.use(helmet()); // 安全头(最先注册)
app.use(traceMiddleware); // 请求追踪
app.use(morgan('combined')); // 访问日志
app.use(cors({ ... })); // 跨域配置
app.use(bodyParser.json({ ... })); // 请求体解析为什么中间件的注册顺序很重要?
中间件按注册顺序依次执行,就像流水线上的工人:
请求进入
↓
[第1个] Helmet → 添加安全头
↓
[第2个] Trace → 记录请求 ID
↓
[第3个] Morgan → 记录访问日志
↓
[第4个] CORS → 处理跨域
↓
[第5个] BodyParser → 解析 JSON/表单数据
↓
[第6个] 路由 → 执行具体业务逻辑如果 CORS 在 BodyParser 之后注册,跨域预检请求(OPTIONS)可能无法正确返回。
5.2.3 路由设计模式
项目中的路由采用模块化设计:每个功能域一个独立的路由文件。
路由文件的标准结构:
// backend/src/routes/agentRoutes.ts 示例
import { Router } from 'express';
const router = Router();
// GET /api/agents - 获取所有 Agent
router.get('/', async (req, res, next) => {
try {
const agents = db.prepare('SELECT * FROM agents').all();
res.json({ success: true, data: agents });
} catch (error) {
next(error); // 交给错误处理中间件
}
});
// POST /api/agents - 创建 Agent
router.post('/', async (req, res, next) => {
try {
const { name, description, systemPrompt } = req.body;
const result = db.prepare(
'INSERT INTO agents (name, description, system_prompt) VALUES (?, ?, ?)'
).run(name, description, systemPrompt);
res.status(201).json({ success: true, data: { id: result.lastInsertRowid } });
} catch (error) {
next(error);
}
});
// PUT /api/agents/:id - 更新 Agent
router.put('/:id', async (req, res, next) => {
// ...
});
// DELETE /api/agents/:id - 删除 Agent
router.delete('/:id', async (req, res, next) => {
// ...
});
export default router;路由注册到应用:
// app.ts 中
app.use('/api/agents', rateLimiter, agentRoutes);这里发生了三件事:
- 所有以
/api/agents开头的请求都会经过这个中间件 - 先经过
rateLimiter频率限制 - 再交给
agentRoutes中的具体路由处理
5.2.4 服务层设计
服务层是后端的核心业务逻辑所在。每个服务负责一个独立的业务域。
服务的设计模式:
// 服务通常是一个对象,包含多个方法
export const agentService = {
// 初始化方法
init(): void {
// 加载配置、建立连接等
},
// 查询方法
getAll(): Agent[] {
return db.prepare('SELECT * FROM agents').all();
},
getById(id: number): Agent | undefined {
return db.prepare('SELECT * FROM agents WHERE id = ?').get(id);
},
// 创建方法
create(data: CreateAgentInput): Agent {
const result = db.prepare(
'INSERT INTO agents (name, system_prompt) VALUES (?, ?)'
).run(data.name, data.systemPrompt);
return this.getById(result.lastInsertRowid as number);
},
// 执行方法(核心业务逻辑)
async execute(agentId: number, input: string): Promise<string> {
const agent = this.getById(agentId);
if (!agent) throw new Error('Agent not found');
// 调用 LLM API
const response = await llmService.callLLM(agent.systemPrompt, input);
return response;
},
};服务间的依赖关系:
llmService.ts ← 被多个服务依赖(调用 AI 模型)
↑
├── agentExecutor.ts ← Agent 执行器
├── copilotService.ts ← AI Copilot
├── workflowExecutor.ts ← 工作流执行器
└── alertService.ts ← 告警处理
sshService.ts ← SSH 连接管理
↑
├── terminalService.ts ← 终端服务
└── serverInfoCollector.ts ← 服务器信息收集
schedulerService.ts ← 定时任务调度
↑
└── 定时触发各种服务5.2.5 错误处理机制
项目采用集中式错误处理模式:
// 404 处理
export const notFoundHandler = (req, res, next) => {
res.status(404).json({
success: false,
error: `Route ${req.method} ${req.originalUrl} not found`
});
};
// 全局错误处理
export const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
// 记录错误日志
logger.error(`${req.method} ${req.originalUrl}`, {
error: message,
stack: err.stack,
traceId: req.traceId
});
// 生产环境不暴露堆栈信息
res.status(statusCode).json({
success: false,
error: env.NODE_ENV === 'production' ? message : err.stack
});
};错误处理流程:
路由处理函数抛出错误
↓
throw new Error('Agent not found')
↓
Express 捕获错误
↓
errorHandler 中间件
↓
记录日志 + 返回统一格式的错误响应
↓
{ "success": false, "error": "Agent not found" }5.2.6 优雅关闭机制
项目在收到退出信号时,会执行有序的关闭流程:
const gracefulShutdown = async (signal: string) => {
logger.info(`${signal} received, starting graceful shutdown...`);
// 设置超时保护(30 秒)
const shutdownTimeout = setTimeout(() => {
logger.error('Graceful shutdown timed out, forcing exit');
process.exit(1);
}, 30000);
try {
// 1. 关闭 HTTP 服务器(不再接受新请求)
await new Promise<void>((resolve) => httpServer.close(() => resolve()));
// 2. 关闭 WebSocket 服务器
await new Promise<void>((resolve) => io.close(() => resolve()));
// 3. 停止定时任务调度
schedulerService.shutdown();
// 4. 停止自动备份
backupService.stopAutoBackup();
// 5. 关闭数据库连接
db.close();
// 6. 关闭日志
logger.shutdown();
clearTimeout(shutdownTimeout);
process.exit(0);
} catch (error) {
logger.error('Error during shutdown', error as Error);
clearTimeout(shutdownTimeout);
process.exit(1);
}
};
// 注册信号处理
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));为什么需要优雅关闭?
| 场景 | 不优雅关闭 | 优雅关闭 |
|---|---|---|
| 正在处理请求 | 请求中断,数据丢失 | 等待请求完成后再关闭 |
| 数据库写入 | 事务中断,数据损坏 | 等待事务提交 |
| SSH 连接 | 连接异常断开 | 正常关闭 SSH 连接 |
| 定时任务 | 任务执行到一半被杀掉 | 等待当前任务完成 |
5.3 前端架构详解
5.3.1 前端目录结构
frontend/src/
├── main.tsx # 应用入口:React DOM 渲染
├── App.tsx # 路由配置 + 应用结构
├── index.css # 全局样式 + Tailwind 指令
├── vite-env.d.ts # Vite 类型声明
├── contexts/ # React Context
│ └── AuthContext.tsx # 认证上下文(登录状态、用户信息)
├── hooks/ # 自定义 Hooks
│ └── useTheme.ts # 主题切换 Hook
├── lib/ # 工具库
│ ├── api.ts # API 客户端(Axios 封装)
│ └── xss.ts # XSS 防护工具
├── components/ # 通用组件
│ ├── layout/
│ │ └── Layout.tsx # 主布局(侧边栏 + 顶栏 + 内容区)
│ ├── ProtectedRoute.tsx # 路由守卫组件
│ ├── WebTerminal.tsx # Web SSH 终端组件
│ ├── ChatWidget.tsx # AI Copilot 聊天组件
│ ├── MarkdownOutput.tsx # Markdown 渲染组件
│ ├── ErrorBoundary.tsx # 错误边界组件
│ ├── AnimatedBarChart.tsx # 动画柱状图
│ ├── AnimatedLineChart.tsx# 动画折线图
│ ├── CircularProgress.tsx # 环形进度条
│ ├── ParticleBackground.tsx # 粒子背景
│ └── ImportExport.tsx # 导入导出组件
└── pages/ # 页面组件(45+ 个页面)
├── Dashboard.tsx # 仪表盘
├── Login.tsx # 登录页
├── Servers.tsx # 服务器管理
├── Agents.tsx # Agent 管理
├── WorkflowEditor.tsx # 工作流编辑器
├── Workflows.tsx # 工作流列表
├── Alerts.tsx # 告警中心
├── Tasks.tsx # 任务列表
├── Reports.tsx # 报告中心
├── Knowledge.tsx # 知识库
├── AuditLogs.tsx # 审计日志
├── Settings.tsx # 系统设置
├── Users.tsx # 用户管理
├── Scripts.tsx # 脚本管理
├── Notifications.tsx # 通知管理
├── ScheduledTasks.tsx # 定时任务
├── TerminalPage.tsx # SSH 终端页
├── NotFound.tsx # 404 页面
└── ... # 其他页面5.3.2 前端启动流程
main.tsx 启动
↓
创建 QueryClient(React Query 配置)
↓
创建 React 应用根节点
↓
<QueryClientProvider> 包裹
↓
<AuthProvider> 包裹
↓
<App /> 渲染
↓
<BrowserRouter> 初始化路由
↓
根据 URL 匹配对应页面组件main.tsx 入口代码:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from './contexts/AuthContext';
import App from './App';
import './index.css';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 分钟缓存
retry: 2, // 失败重试 2 次
},
},
});
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<App />
</AuthProvider>
</QueryClientProvider>
</React.StrictMode>
);5.3.3 路由设计
项目使用 React Router v6 管理前端路由:
// App.tsx 路由配置
function App() {
return (
<BrowserRouter>
<Routes>
{/* 公开路由 */}
<Route path="/login" element={<Login />} />
{/* 受保护路由 */}
<Route path="/" element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}>
<Route index element={<Dashboard />} />
<Route path="servers" element={<Servers />} />
<Route path="agents" element={<Agents />} />
<Route path="workflows" element={<Workflows />} />
<Route path="workflow-editor" element={<WorkflowEditor />} />
<Route path="alerts" element={<Alerts />} />
<Route path="tasks" element={<Tasks />} />
<Route path="reports" element={<Reports />} />
<Route path="knowledge" element={<Knowledge />} />
<Route path="terminal" element={<TerminalPage />} />
<Route path="audit" element={<AuditLogs />} />
<Route path="settings" element={<Settings />} />
{/* ... 更多路由 */}
</Route>
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}路由保护机制:
访问 /servers
↓
<ProtectedRoute> 检查
↓
localStorage 中有 Token?
├─ 是 → 渲染 <Layout /> → 匹配 <Route path="servers"> → 显示 Servers 页面
└─ 否 → 重定向到 /login5.3.4 组件架构
项目中的组件分为三类:
| 类型 | 位置 | 职责 | 示例 |
|---|---|---|---|
| 页面组件 | src/pages/ | 对应一个路由,包含完整业务逻辑 | Dashboard.tsx |
| 通用组件 | src/components/ | 可复用的 UI 组件,无路由依赖 | WebTerminal.tsx |
| 布局组件 | src/components/layout/ | 包裹页面的框架结构 | Layout.tsx |
组件层次结构:
App
└── BrowserRouter
└── Routes
└── Route (protected)
└── ProtectedRoute
└── Layout ← 布局组件
├── Sidebar ← 侧边导航
├── TopBar ← 顶部栏
└── Outlet ← 页面内容
└── Dashboard ← 页面组件
├── StatCard ← 通用组件
├── AnimatedBarChart ← 通用组件
└── ChatWidget ← 通用组件5.3.5 数据流模式
前端采用多源数据管理策略:
┌────────────────────────────────────────────────────┐
│ 前端数据流架构 │
│ │
│ 服务器数据 (Servers, Agents, Tasks, ...) │
│ ↑ │
│ │ useQuery / useMutation │
│ │ │
│ ├────→ React Query 缓存层 │
│ │ │
│ │ api.get() / api.post() │
│ │ │
│ └────→ Axios (带拦截器) │
│ │ │
│ ↓ │
│ 后端 API │
│ │
│ 全局 UI 状态 (侧边栏展开、主题) │
│ ↑ │
│ │ useStore() │
│ │ │
│ └────→ Zustand Store │
│ │
│ 认证状态 (Token, 用户信息) │
│ ↑ │
│ │ useAuth() │
│ │ │
│ └────→ AuthContext (React Context) │
│ │
│ 表单状态 │
│ ↑ │
│ │ useState │
│ │ │
│ └────→ 组件内部状态 │
└────────────────────────────────────────────────────┘5.3.6 API 客户端封装
项目使用 Axios 封装了统一的 API 客户端:
// frontend/src/lib/api.ts
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
timeout: 30000,
});
// 请求拦截器
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
api.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
// Token 过期或无效,清除本地存储并跳转登录
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.href = '/login';
}
return Promise.reject(error.response?.data || error);
}
);
export default api;使用方式:
// 在页面组件中
import api from '../lib/api';
function Servers() {
const { data: servers } = useQuery({
queryKey: ['servers'],
queryFn: () => api.get('/servers'),
});
const deleteServer = (id: string) => {
api.delete(`/servers/${id}`);
queryClient.invalidateQueries({ queryKey: ['servers'] });
};
}5.4 WebSocket 架构
5.4.1 WebSocket 连接流程
前端连接
↓
socket.io-client 发起 WebSocket 连接
↓
后端 Socket.io 接收连接
↓
setupWebSocket(io) 注册事件监听
↓
连接建立,开始双向通信5.4.2 WebSocket 事件设计
项目中的 WebSocket 事件按功能域分组:
WebSocket 事件树:
├── connection ← 客户端连接
├── disconnect ← 客户端断开
│
├── SSH 终端
│ ├── terminal:input ← 前端 → 后端:终端输入
│ ├── terminal:output ← 后端 → 前端:终端输出
│ ├── terminal:resize ← 前端 → 后端:终端大小变化
│ └── terminal:disconnect ← 前端 → 后端:断开 SSH 连接
│
├── Agent 执行
│ └── agent:progress ← 后端 → 前端:执行进度
│
├── 工作流执行
│ └── workflow:progress ← 后端 → 前端:节点执行进度
│
└── 告警通知
└── alert:new ← 后端 → 前端:新告警5.4.3 WebSocket 处理代码结构
// backend/src/websocket/handler.ts 模式
export function setupWebSocket(io: Server) {
io.on('connection', (socket) => {
logger.info(`WebSocket connected: ${socket.id}`);
// SSH 终端事件
socket.on('terminal:input', async (data) => {
const { serverId, command } = data;
// 获取 SSH 连接
const connection = getSSHConnection(serverId);
// 执行命令
connection.write(command + '\n');
});
// 客户端断开
socket.on('disconnect', () => {
logger.info(`WebSocket disconnected: ${socket.id}`);
// 清理 SSH 连接
closeSSHConnections(socket.id);
});
});
}5.5 数据库架构
5.5.1 数据库初始化流程
应用启动
↓
initializeDatabase()
↓
1. 打开/创建 SQLite 数据库文件
↓
2. 启用 WAL 模式(预写式日志)
↓
3. 创建所有数据表(如果不存在)
├── users 表
├── servers 表
├── agents 表
├── workflows 表
└── ... (40 张初始创建,17 次迁移追加,共 44 张)
↓
4. 执行数据库迁移(16 个增量迁移版本,追加网络设备、AI模型、审批等表)
↓
5. 插入预设数据(如果是首次初始化)
├── 9 个预设 Agent
├── 6 个预设工作流
├── 知识库条目
└── 告警映射规则
↓
数据库就绪5.5.2 数据库操作模式
项目使用 better-sqlite3,这是一个同步的 SQLite 驱动:
import Database from 'better-sqlite3';
const db = new Database('./data/itops.db', {
verbose: console.log // 可选:打印 SQL 日志
});
// 启用 WAL 模式
db.pragma('journal_mode = WAL');
// 查询 - 单行
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
// 查询 - 多行
const servers = db.prepare('SELECT * FROM servers').all();
// 插入
const result = db.prepare(
'INSERT INTO servers (name, host, port) VALUES (?, ?, ?)'
).run('prod-server', '192.168.1.1', 22);
// 更新
db.prepare('UPDATE servers SET status = ? WHERE id = ?').run('online', 1);
// 删除
db.prepare('DELETE FROM servers WHERE id = ?').run(1);
// 事务(多条操作要么全成功,要么全失败)
const transaction = db.transaction((data) => {
db.prepare('INSERT INTO servers ...').run(data.server);
db.prepare('INSERT INTO audit_logs ...').run(data.log);
});
transaction.run({ server: {...}, log: {...} });为什么选择同步驱动?
| 特性 | better-sqlite3(同步) | sqlite3(异步) |
|---|---|---|
| API 简洁度 | 同步调用,代码更简洁 | 回调/Promise,代码嵌套 |
| 性能 | 单线程场景下性能更好 | 异步开销 |
| 事务支持 | 原生同步事务 | 需要手动管理 |
| 适用场景 | 中小型应用(本项目) | 高并发场景 |
5.6 中间件体系
5.6.1 中间件清单
| 中间件 | 位置 | 用途 | 注册方式 |
|---|---|---|---|
helmet() | 全局 | 设置安全相关的 HTTP 头 | app.use() |
traceMiddleware | 全局 | 为每个请求生成唯一追踪 ID | app.use() |
morgan('combined') | 全局 | 记录 HTTP 请求日志 | app.use() |
cors() | 全局 | 处理跨域资源共享 | app.use() |
bodyParser.json() | 全局 | 解析 JSON 请求体 | app.use() |
rateLimiter | 路由组 | 限制请求频率 | app.use('/api/*', rateLimiter) |
authenticateToken | 路由组 | JWT 身份验证 | app.use(authenticateToken) |
5.6.2 JWT 认证中间件详解
// backend/src/middleware/auth.ts
export const authenticateToken = (req, res, next) => {
// 1. 从请求头获取 Token
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1]; // "Bearer <token>"
if (!token) {
return res.status(401).json({
success: false,
error: '认证失败:未提供访问令牌'
});
}
// 2. 验证 Token
try {
const decoded = jwt.verify(token, env.JWT_SECRET);
req.user = decoded; // 将用户信息挂载到请求对象
next(); // 继续下一步
} catch (error) {
return res.status(403).json({
success: false,
error: '认证失败:令牌无效或已过期'
});
}
};认证流程可视化:
请求: GET /api/servers
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
↓
authenticateToken 中间件
↓
提取 Token → 验证签名 → 检查有效期
↓
├─ 有效 → req.user = { id: 1, role: 'admin' } → next() → 路由处理
└─ 无效 → 返回 401/403 → 前端收到错误 → 跳转登录页5.7 项目结构对比
5.7.1 前后端结构对照
| 维度 | 前端 | 后端 |
|---|---|---|
| 入口文件 | main.tsx | app.ts |
| 路由管理 | React Router v6 | Express Router |
| 页面/路由 | src/pages/ (45+ 个) | routes/ (47 个) |
| 通用组件 | src/components/ | middleware/ |
| 状态管理 | Zustand + React Query + Context | 内存 + SQLite |
| API 调用 | lib/api.ts (Axios) | 直接处理请求 |
| 工具函数 | lib/, hooks/ | utils/ |
| 类型定义 | 内联定义 | types/ 目录 |
5.7.2 模块数量统计
| 层级 | 数量 | 说明 |
|---|---|---|
| 前端页面 | 45+ 个 | 对应 47 个路由 |
| 前端通用组件 | 11 个 | 跨页面复用 |
| 后端路由文件 | 47 个 | 对应 47 个路由组 |
| 后端服务文件 | 50+ 个 | 核心业务逻辑 |
| 后端中间件 | 6 个 | 认证、限流、错误处理等 |
| 数据库表 | 44 张 | 17 个迁移版本管理 |
| 预设数据 | 9 个 Agent + 6 个工作流 | 开箱即用 |
本章小结
本章深入解析了项目架构的各个方面:
- 整体架构:三层架构(表现层、逻辑层、数据层),层次清晰,职责分明
- 后端架构:Express 应用 → 中间件链 → 模块化路由 → 服务层 → SQLite,共 47 个路由组、50+ 个服务
- 前端架构:React Router → 路由保护 → 页面组件 → 通用组件,共 45+ 个页面、11 个通用组件
- WebSocket 架构:Socket.io 双向通信,支持 SSH 终端、Agent 执行、工作流进度、告警通知
- 数据库架构:better-sqlite3 同步驱动,44 张表,17 个迁移版本,WAL 模式优化
- 中间件体系:Helmet、CORS、JWT 认证、频率限制、错误处理等
- 启动和关闭:有序的初始化流程 + 优雅的关闭机制
核心理念:关注点分离、高内聚低耦合、单一职责。
本章练习
基础练习
架构绘制:不查看代码,凭记忆画出项目的三层架构图,标注各层使用的技术
文件定位:回答以下问题,哪个文件负责什么?
app.ts的作用是什么?authRoutes.ts和authMiddleware.ts的区别是什么?workflowExecutor.ts和workflowRoutes.ts的关系是什么?
请求链路追踪:当用户在前端点击"获取 Agent 列表"按钮时,描述从前端到后端再返回的完整链路
进阶练习
模块依赖图:画出
llmService.ts的依赖关系图,列出所有依赖它和它依赖的模块启动流程分析:在
app.ts中,如果将initializeDatabase()移到服务初始化之后,会发生什么问题?为什么?添加新路由:创建一个新的路由文件
healthRoutes.ts,在/api/health/details路径上返回系统健康详情,并在app.ts中注册
思考题
为什么项目选择单体架构而非微服务?在什么情况下你会考虑将这个项目改造为微服务?
项目的中间件注册顺序是否可以随意调整?如果调整了会产生什么影响?
为什么后端使用同步的 better-sqlite3 而非异步的 node-sqlite3?如果未来并发量增加,应该如何应对?
延伸阅读
本章回顾:你已经理解了项目的整体架构!在下一章中,我们将从零开始编写后端代码,深入实践后端开发的各个环节。
