Skip to content

第5章 项目架构深度解析

作者

谭策 — 独立开发者 | AIOps 领域探索者

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)

关键代码分析

typescript
// 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 路由设计模式

项目中的路由采用模块化设计:每个功能域一个独立的路由文件。

路由文件的标准结构

typescript
// 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;

路由注册到应用

typescript
// app.ts 中
app.use('/api/agents', rateLimiter, agentRoutes);

这里发生了三件事:

  1. 所有以 /api/agents 开头的请求都会经过这个中间件
  2. 先经过 rateLimiter 频率限制
  3. 再交给 agentRoutes 中的具体路由处理

5.2.4 服务层设计

服务层是后端的核心业务逻辑所在。每个服务负责一个独立的业务域。

服务的设计模式

typescript
// 服务通常是一个对象,包含多个方法
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 错误处理机制

项目采用集中式错误处理模式:

typescript
// 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 优雅关闭机制

项目在收到退出信号时,会执行有序的关闭流程

typescript
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 入口代码

typescript
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 管理前端路由:

typescript
// 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 页面
  └─ 否 → 重定向到 /login

5.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 客户端:

typescript
// 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;

使用方式

typescript
// 在页面组件中
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 处理代码结构

typescript
// 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 驱动:

typescript
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全局为每个请求生成唯一追踪 IDapp.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 认证中间件详解

typescript
// 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.tsxapp.ts
路由管理React Router v6Express 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 个工作流开箱即用

本章小结

本章深入解析了项目架构的各个方面:

  1. 整体架构:三层架构(表现层、逻辑层、数据层),层次清晰,职责分明
  2. 后端架构:Express 应用 → 中间件链 → 模块化路由 → 服务层 → SQLite,共 47 个路由组、50+ 个服务
  3. 前端架构:React Router → 路由保护 → 页面组件 → 通用组件,共 45+ 个页面、11 个通用组件
  4. WebSocket 架构:Socket.io 双向通信,支持 SSH 终端、Agent 执行、工作流进度、告警通知
  5. 数据库架构:better-sqlite3 同步驱动,44 张表,17 个迁移版本,WAL 模式优化
  6. 中间件体系:Helmet、CORS、JWT 认证、频率限制、错误处理等
  7. 启动和关闭:有序的初始化流程 + 优雅的关闭机制

核心理念:关注点分离、高内聚低耦合、单一职责。


本章练习

基础练习

  1. 架构绘制:不查看代码,凭记忆画出项目的三层架构图,标注各层使用的技术

  2. 文件定位:回答以下问题,哪个文件负责什么?

    • app.ts 的作用是什么?
    • authRoutes.tsauthMiddleware.ts 的区别是什么?
    • workflowExecutor.tsworkflowRoutes.ts 的关系是什么?
  3. 请求链路追踪:当用户在前端点击"获取 Agent 列表"按钮时,描述从前端到后端再返回的完整链路

进阶练习

  1. 模块依赖图:画出 llmService.ts 的依赖关系图,列出所有依赖它和它依赖的模块

  2. 启动流程分析:在 app.ts 中,如果将 initializeDatabase() 移到服务初始化之后,会发生什么问题?为什么?

  3. 添加新路由:创建一个新的路由文件 healthRoutes.ts,在 /api/health/details 路径上返回系统健康详情,并在 app.ts 中注册

思考题

  1. 为什么项目选择单体架构而非微服务?在什么情况下你会考虑将这个项目改造为微服务?

  2. 项目的中间件注册顺序是否可以随意调整?如果调整了会产生什么影响?

  3. 为什么后端使用同步的 better-sqlite3 而非异步的 node-sqlite3?如果未来并发量增加,应该如何应对?


延伸阅读


本章回顾:你已经理解了项目的整体架构!在下一章中,我们将从零开始编写后端代码,深入实践后端开发的各个环节。

基于 MPL-2.0 许可证发布