| 项目 | 内容 |
|---|---|
| 文档版本 | v2.1 |
| 编写日期 | 2024-01-15 |
| 最后更新 | 2026-03-24 |
| 文档状态 | 评审中 |
| 编写人员 | 架构组 |
| 版本 | 日期 | 修改人 | 修改内容 |
|---|---|---|---|
| v1.0 | 2023-12-01 | 架构组 | 初始版本,基础架构设计 |
| v2.0 | 2024-01-15 | 架构组 | 完善API定义,添加性能优化策略 |
| v2.1 | 2026-03-24 | 架构组 | 优化文档结构,补充错误码规范 |
本文档旨在定义NAS(网络附加存储)存储管理模块的技术架构,为开发团队提供清晰的技术指引,确保系统设计的一致性、可维护性和可扩展性。
NAS存储管理模块是企业级存储解决方案的核心组件,提供以下核心能力:
| 术语 | 英文全称 | 说明 |
|---|---|---|
| NAS | Network Attached Storage | 网络附加存储,通过网络提供文件级数据存储服务 |
| RAID | Redundant Array of Independent Disks | 独立磁盘冗余阵列,提供数据冗余和性能提升 |
| S.M.A.R.T | Self-Monitoring Analysis and Reporting Technology | 硬盘自我监测、分析与报告技术 |
| 存储池 | Storage Pool | 由多个物理硬盘组成的逻辑存储单元 |
| LUN | Logical Unit Number | 逻辑单元号,用于标识存储设备 |
graph TD
A[用户浏览器] --> B[React前端应用]
B --> C[Redux状态管理]
B --> D[Ant Design组件库]
B --> E[API服务层 Axios]
E --> F[NAS管理后端 API]
F --> G[存储设备API]
F --> H[系统监控服务]
H --> I[告警通知服务]
subgraph 前端层 Frontend
direction TB
B
C
D
end
subgraph 服务层 Service
direction TB
E
F
end
subgraph 设备层 Device
direction TB
G
H
I
end
style 前端层 fill:#e1f5fe
style 服务层 fill:#f3e5f5
style 设备层 fill:#e8f5e9
| 层级 | 职责 | 技术组件 |
|---|---|---|
| 前端层 | 用户界面渲染、状态管理、交互逻辑 | React 18、Redux Toolkit、Ant Design 5 |
| 服务层 | API网关、业务逻辑处理、数据校验 | RESTful API、JWT认证、中间件 |
| 设备层 | 硬件设备交互、系统监控、告警推送 | 设备驱动、WebSocket、消息队列 |
| 类别 | 技术/工具 | 版本 | 用途说明 |
|---|---|---|---|
| 框架 | React | 18.x | 核心UI框架,支持并发特性 |
| 语言 | TypeScript | 5.x | 静态类型检查,提升代码质量 |
| 状态管理 | Redux Toolkit | 1.9.x | 全局状态管理,简化Redux配置 |
| UI组件 | Ant Design | 5.x | 企业级UI组件库 |
| 构建工具 | Vite | 4.x | 快速构建,支持HMR热更新 |
| 路由 | React Router | 6.x | 单页应用路由管理 |
| HTTP客户端 | Axios | 1.x | HTTP请求库,支持拦截器 |
| 样式方案 | CSS Modules + Less | - | 组件级样式隔离 |
| 类别 | 工具 | 版本 | 配置说明 |
|---|---|---|---|
| 代码规范 | ESLint + Prettier | 8.x / 3.x | Airbnb规则集,统一代码风格 |
| Git钩子 | Husky + lint-staged | 8.x | 提交前自动格式化 |
| 单元测试 | Jest + React Testing Library | 29.x / 14.x | 组件和Hook测试 |
| E2E测试 | Playwright | 1.x | 端到端自动化测试 |
| Mock工具 | MSW | 1.x | Mock Service Worker,API模拟 |
| 国际化 | i18next | 23.x | 多语言支持 |
| API文档 | Swagger/OpenAPI | 3.0 | 接口文档自动生成 |
| 路由路径 | 页面组件 | 权限要求 | 功能描述 |
|---|---|---|---|
/storage | StorageDashboard | user | 存储概览页面,显示总体统计信息 |
/storage/disks | DiskManagement | user | 盘位管理,硬盘列表和状态监控 |
/storage/disks/:id | DiskDetail | user | 硬盘详情页,显示S.M.A.R.T信息 |
/storage/pools | PoolManagement | user | 存储池列表和管理 |
/storage/pools/create | PoolCreateWizard | admin | RAID创建向导(分步表单) |
/storage/pools/:id | PoolDetail | user | 存储池详情页面 |
/storage/pools/:id/edit | PoolEdit | admin | 存储池编辑页面 |
/storage/monitoring | CapacityMonitoring | user | 容量监控和趋势分析 |
/storage/alerts | AlertSettings | admin | 告警规则配置 |
typescript// src/app/routes.tsx
export const storageRoutes: RouteObject[] = [
{
path: '/storage',
element: <StorageLayout />,
children: [
{ index: true, element: <StorageDashboard /> },
{ path: 'disks', element: <DiskManagement /> },
{ path: 'disks/:id', element: <DiskDetail /> },
{
path: 'pools',
element: <PoolManagement />,
children: [
{ path: 'create', element: <PoolCreateWizard /> },
{ path: ':id', element: <PoolDetail /> },
{ path: ':id/edit', element: <PoolEdit /> },
],
},
{ path: 'monitoring', element: <CapacityMonitoring /> },
{ path: 'alerts', element: <AlertSettings /> },
],
},
];
/api/v1application/json2024-01-15T10:30:00Z)typescript// 统一响应结构
interface ApiResponse<T> {
code: number; // 业务状态码,0表示成功
message: string; // 提示信息
data: T; // 响应数据
timestamp: number; // 服务器时间戳
requestId: string; // 请求追踪ID
}
// 分页响应结构
interface PaginatedResponse<T> {
total: number; // 总记录数
page: number; // 当前页码
size: number; // 每页大小
items: T[]; // 数据列表
}
| 错误码 | 说明 | HTTP状态码 | 处理建议 |
|---|---|---|---|
0 | 成功 | 200 | - |
10001 | 参数错误 | 400 | 检查请求参数格式 |
10002 | 认证失败 | 401 | 重新登录获取Token |
10003 | 权限不足 | 403 | 确认用户角色权限 |
10004 | 资源不存在 | 404 | 检查资源ID是否正确 |
20001 | 硬盘不存在 | 404 | 确认硬盘ID有效 |
20002 | 硬盘正在使用 | 409 | 先从存储池中移除 |
20003 | S.M.A.R.T检测失败 | 500 | 检查硬盘连接状态 |
30001 | 存储池不存在 | 404 | 确认存储池ID |
30002 | 磁盘数量不足 | 400 | RAID5至少需要3块盘 |
30003 | RAID创建失败 | 500 | 查看系统日志 |
30004 | 存储池名称已存在 | 409 | 更换存储池名称 |
50000 | 服务器内部错误 | 500 | 联系管理员 |
httpGET /api/v1/storage/disks
请求参数:
| 参数名 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
page | integer | 否 | 1 | 页码,从1开始 |
size | integer | 否 | 20 | 每页数量,最大100 |
status | string | 否 | - | 过滤状态:healthy/warning/error/offline |
pool_id | string | 否 | - | 按存储池过滤 |
sort_by | string | 否 | created_at | 排序字段 |
sort_order | string | 否 | desc | 排序方向:asc/desc |
响应示例(成功):
json{
"code": 0,
"message": "success",
"data": {
"total": 8,
"page": 1,
"size": 20,
"items": [
{
"disk_id": "disk_001",
"model": "ST4000VN008",
"serial_number": "ZGY0K3MZ",
"capacity": 4000787030016,
"capacity_formatted": "3.64 TB",
"health_status": "healthy",
"smart_status": "passed",
"temperature": 42,
"temperature_status": "normal",
"power_on_hours": 8760,
"rotation_rate": 5400,
"interface": "SATA",
"pool_id": "pool_001",
"pool_name": "数据存储池",
"bay_location": "Bay-1",
"filesystem": "ext4",
"created_at": "2024-01-15T08:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
]
},
"timestamp": 1705312200,
"requestId": "req_abc123"
}
httpGET /api/v1/storage/disks/{disk_id}
路径参数:
| 参数名 | 类型 | 必需 | 描述 |
|---|---|---|---|
disk_id | string | 是 | 硬盘唯一标识 |
httpGET /api/v1/storage/disks/{disk_id}/smart
响应示例:
json{
"code": 0,
"message": "success",
"data": {
"disk_id": "disk_001",
"smart_enabled": true,
"overall_health": "PASSED",
"attributes": [
{
"id": 1,
"name": "Raw_Read_Error_Rate",
"value": 100,
"worst": 100,
"threshold": 50,
"raw_value": "0",
"status": "ok"
},
{
"id": 194,
"name": "Temperature_Celsius",
"value": 58,
"worst": 58,
"threshold": 0,
"raw_value": "42",
"status": "ok"
}
],
"power_on_hours": 8760,
"power_cycle_count": 150,
"last_updated": "2024-01-15T10:30:00Z"
}
}
httpPOST /api/v1/storage/pools
请求体:
json{
"pool_name": "数据存储池",
"description": "用于存放用户数据",
"raid_level": "raid5",
"disk_ids": ["disk_001", "disk_002", "disk_003"],
"stripe_size": 64,
"filesystem": "ext4",
"mount_point": "/mnt/data"
}
字段说明:
| 字段名 | 类型 | 必需 | 约束 | 描述 |
|---|---|---|---|---|
pool_name | string | 是 | 1-100字符 | 存储池名称,唯一 |
description | string | 否 | 最大500字符 | 存储池描述 |
raid_level | string | 是 | 枚举值 | RAID级别:raid0/raid1/raid5/raid6/raid10 |
disk_ids | array | 是 | 见下表 | 硬盘ID列表 |
stripe_size | integer | 否 | 4/8/16/32/64/128/256/512/1024 | 条带大小,单位KB,默认64 |
filesystem | string | 否 | 枚举值 | 文件系统:ext4/btrfs/xfs,默认ext4 |
mount_point | string | 否 | 路径格式 | 挂载点路径 |
RAID级别磁盘数量要求:
| RAID级别 | 最小磁盘数 | 最大磁盘数 | 容错能力 |
|---|---|---|---|
| RAID 0 | 2 | 无限制 | 0块 |
| RAID 1 | 2 | 2 | 1块 |
| RAID 5 | 3 | 无限制 | 1块 |
| RAID 6 | 4 | 无限制 | 2块 |
| RAID 10 | 4 | 无限制(偶数) | 每组1块 |
响应示例(成功):
json{
"code": 0,
"message": "存储池创建任务已提交",
"data": {
"pool_id": "pool_001",
"pool_name": "数据存储池",
"raid_level": "raid5",
"status": "creating",
"task_id": "task_abc123",
"estimated_time": 300
}
}
httpPOST /api/v1/storage/pools/{pool_id}/expand
请求体:
json{
"disk_ids": ["disk_004", "disk_005"],
"expand_type": "add_disk"
}
httpDELETE /api/v1/storage/pools/{pool_id}
查询参数:
| 参数名 | 类型 | 必需 | 描述 |
|---|---|---|---|
force | boolean | 否 | 是否强制删除(忽略警告) |
httpGET /api/v1/storage/capacity
响应示例:
json{
"code": 0,
"message": "success",
"data": {
"summary": {
"total_raw_capacity": 16003148752064,
"total_usable_capacity": 12000000000000,
"used_capacity": 8000000000000,
"free_capacity": 4000000000000,
"usage_percent": 66.7
},
"by_pool": [
{
"pool_id": "pool_001",
"pool_name": "数据存储池",
"total_capacity": 8000000000000,
"used_capacity": 6000000000000,
"free_capacity": 2000000000000,
"usage_percent": 75.0,
"status": "warning"
}
],
"trend": {
"period": "7d",
"data_points": [
{ "date": "2024-01-08", "used": 7500000000000 },
{ "date": "2024-01-15", "used": 8000000000000 }
],
"growth_rate": "5.2 GB/天"
}
}
}
httpPOST /api/v1/storage/alerts/rules
请求体:
json{
"rule_name": "容量告警-80%",
"rule_type": "capacity",
"target_type": "pool",
"target_id": "pool_001",
"threshold": 80,
"severity": "warning",
"notification_channels": ["email", "webhook"],
"enabled": true
}
erDiagram
DISK ||--o{ POOL : belongs_to
POOL ||--o{ VOLUME : contains
POOL ||--o{ ALERT_RULE : triggers
POOL ||--o{ TASK : generates
DISK {
string disk_id PK "硬盘唯一标识"
string model "型号"
string serial_number "序列号"
bigint capacity "容量(字节)"
string health_status "健康状态"
string smart_status "S.M.A.R.T状态"
int temperature "温度(°C)"
int power_on_hours "通电时间(小时)"
int rotation_rate "转速(RPM)"
string interface "接口类型"
string pool_id FK "所属存储池"
string bay_location "物理位置"
string filesystem "文件系统"
timestamp created_at "创建时间"
timestamp updated_at "更新时间"
}
POOL {
string pool_id PK "存储池唯一标识"
string pool_name "存储池名称"
string description "描述"
string raid_level "RAID级别"
bigint total_capacity "总容量"
bigint used_capacity "已用容量"
string status "状态"
string filesystem "文件系统"
string mount_point "挂载点"
timestamp created_at "创建时间"
timestamp updated_at "更新时间"
}
VOLUME {
string volume_id PK "卷唯一标识"
string pool_id FK "所属存储池"
string volume_name "卷名称"
bigint capacity "容量"
bigint used_capacity "已用容量"
string filesystem "文件系统"
string mount_point "挂载点"
boolean is_snapshot "是否快照"
string parent_volume_id "父卷ID(快照用)"
}
ALERT_RULE {
string rule_id PK "规则唯一标识"
string rule_name "规则名称"
string rule_type "规则类型"
string target_type "目标类型"
string target_id FK "目标ID"
decimal threshold "阈值"
string severity "严重级别"
json notification_channels "通知渠道"
boolean is_enabled "是否启用"
timestamp created_at "创建时间"
}
TASK {
string task_id PK "任务唯一标识"
string task_type "任务类型"
string target_type "目标类型"
string target_id FK "目标ID"
string status "任务状态"
int progress "进度(%)"
json result "执行结果"
timestamp started_at "开始时间"
timestamp completed_at "完成时间"
}
sqlCREATE TABLE disks (
disk_id VARCHAR(50) PRIMARY KEY COMMENT '硬盘唯一标识',
model VARCHAR(100) NOT NULL COMMENT '硬盘型号',
serial_number VARCHAR(100) UNIQUE COMMENT '序列号',
capacity BIGINT NOT NULL COMMENT '容量(字节)',
health_status VARCHAR(20) DEFAULT 'unknown' COMMENT '健康状态: healthy/warning/error/unknown',
smart_status VARCHAR(20) DEFAULT 'unknown' COMMENT 'S.M.A.R.T状态: passed/failed/unknown',
temperature INTEGER DEFAULT 0 COMMENT '温度(摄氏度)',
power_on_hours INTEGER DEFAULT 0 COMMENT '通电时间(小时)',
rotation_rate INTEGER COMMENT '转速(RPM)',
interface VARCHAR(20) COMMENT '接口类型: SATA/SAS/NVMe',
pool_id VARCHAR(50) COMMENT '所属存储池ID',
bay_location VARCHAR(50) COMMENT '物理盘位位置',
filesystem VARCHAR(20) COMMENT '文件系统类型',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_disks_pool_id (pool_id),
INDEX idx_disks_health (health_status),
INDEX idx_disks_status_temp (health_status, temperature),
FOREIGN KEY (pool_id) REFERENCES storage_pools(pool_id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='硬盘信息表';
sqlCREATE TABLE storage_pools (
pool_id VARCHAR(50) PRIMARY KEY COMMENT '存储池唯一标识',
pool_name VARCHAR(100) NOT NULL UNIQUE COMMENT '存储池名称',
description VARCHAR(500) COMMENT '存储池描述',
raid_level VARCHAR(20) NOT NULL COMMENT 'RAID级别: raid0/raid1/raid5/raid6/raid10',
total_capacity BIGINT NOT NULL COMMENT '总容量(字节)',
used_capacity BIGINT DEFAULT 0 COMMENT '已用容量(字节)',
free_capacity BIGINT GENERATED ALWAYS AS (total_capacity - used_capacity) COMMENT '可用容量',
status VARCHAR(20) DEFAULT 'creating' COMMENT '状态: creating/active/degraded/failed/deleting',
filesystem VARCHAR(20) DEFAULT 'ext4' COMMENT '文件系统: ext4/btrfs/xfs',
mount_point VARCHAR(255) COMMENT '挂载点路径',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_pools_status (status),
INDEX idx_pools_name (pool_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储池信息表';
sqlCREATE TABLE alert_rules (
rule_id VARCHAR(50) PRIMARY KEY COMMENT '规则唯一标识',
rule_name VARCHAR(100) NOT NULL COMMENT '规则名称',
rule_type VARCHAR(50) NOT NULL COMMENT '规则类型: capacity/health/performance',
target_type VARCHAR(50) NOT NULL COMMENT '目标类型: pool/disk/system',
target_id VARCHAR(50) COMMENT '目标ID',
threshold DECIMAL(5,2) COMMENT '阈值',
severity VARCHAR(20) DEFAULT 'warning' COMMENT '严重级别: info/warning/critical',
notification_channels JSON COMMENT '通知渠道配置',
is_enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_rules_type (rule_type),
INDEX idx_rules_enabled (is_enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警规则表';
采用 Feature-Based 目录结构,将相关功能代码组织在一起,提升可维护性。
src/ ├── app/ # 应用层 │ ├── App.tsx # 根组件 │ ├── routes.tsx # 路由配置 │ ├── store.ts # Redux Store配置 │ └── providers.tsx # 全局Provider │ ├── features/ # 特性模块(按功能划分) │ └── nas/ # NAS存储管理特性 │ ├── components/ # 组件 │ │ ├── DiskCard/ # 硬盘卡片组件 │ │ │ ├── index.tsx │ │ │ ├── DiskCard.module.less │ │ │ └── DiskCard.test.tsx │ │ ├── PoolList/ # 存储池列表 │ │ ├── RaidWizard/ # RAID创建向导 │ │ │ ├── steps/ # 向导步骤 │ │ │ ├── hooks/ # 向导专用hooks │ │ │ └── utils/ # 向导工具函数 │ │ ├── CapacityChart/ # 容量图表 │ │ └── AlertConfig/ # 告警配置 │ │ │ ├── hooks/ # 自定义Hooks │ │ ├── useDiskStatus.ts # 硬盘状态订阅 │ │ ├── usePoolActions.ts # 存储池操作 │ │ ├── useCapacityAlert.ts # 容量告警 │ │ └── useWebSocket.ts # WebSocket连接 │ │ │ ├── services/ # API服务层 │ │ ├── diskService.ts # 硬盘API │ │ ├── poolService.ts # 存储池API │ │ ├── monitorService.ts # 监控API │ │ └── client.ts # Axios实例 │ │ │ ├── types/ # TypeScript类型定义 │ │ ├── disk.types.ts │ │ ├── pool.types.ts │ │ ├── api.types.ts │ │ └── enums.ts │ │ │ └── store/ # Redux状态管理 │ ├── diskSlice.ts # 硬盘状态 │ ├── poolSlice.ts # 存储池状态 │ ├── alertSlice.ts # 告警状态 │ └── index.ts # Reducer组合 │ ├── shared/ # 共享资源 │ ├── components/ # 通用组件 │ │ ├── DataTable/ # 数据表格 │ │ ├── StatusBadge/ # 状态标签 │ │ ├── CapacityBar/ # 容量进度条 │ │ └── ConfirmModal/ # 确认弹窗 │ │ │ ├── hooks/ # 通用Hooks │ │ ├── usePagination.ts │ │ ├── useDebounce.ts │ │ └── useLocalStorage.ts │ │ │ ├── utils/ # 工具函数 │ │ ├── formatters.ts # 格式化(容量、日期等) │ │ ├── validators.ts # 表单校验 │ │ └── constants.ts # 常量定义 │ │ │ └── styles/ # 全局样式 │ ├── variables.less # 变量 │ └── mixins.less # 混入 │ └── locales/ # 国际化 ├── zh-CN/ # 中文 └── en-US/ # 英文
graph TD
subgraph 视图层 View
A1[DiskList组件]
A2[PoolDetail组件]
A3[AlertConfig组件]
end
subgraph Redux Store
B[Root Reducer]
B --> C[diskSlice<br/>硬盘状态]
B --> D[poolSlice<br/>存储池状态]
B --> E[alertSlice<br/>告警状态]
B --> F[uiSlice<br/>UI状态]
end
subgraph 服务层 Service
G[Disk API]
H[Pool API]
I[Monitor API]
end
subgraph 后端 Backend
J[NAS后端服务]
end
A1 -->|useSelector| C
A2 -->|useSelector| D
A3 -->|useSelector| E
C -->|thunk| G
D -->|thunk| H
E -->|thunk| I
G --> J
H --> J
I --> J
J -->|WebSocket| F
F -->|通知| A1
F -->|通知| A2
style 视图层 fill:#e3f2fd
style Redux fill:#f3e5f5
style 服务层 fill:#e8f5e9
style 后端 fill:#fff3e0
状态管理原则:
| 优化策略 | 实现方式 | 预期效果 |
|---|---|---|
| 代码分割 | React.lazy() + Suspense | 减少首包体积 |
| 路由懒加载 | 按路由代码分割 | 按需加载页面 |
| 组件懒加载 | 模态框、图表组件懒加载 | 延迟非关键资源 |
| Tree Shaking | Vite自动摇树优化 | 移除无用代码 |
| 图片优化 | WebP格式、懒加载、占位符 | 减少图片体积 |
| 虚拟滚动 | react-window 大数据列表 | 流畅渲染万级数据 |
| Memo优化 | React.memo、useMemo | 减少重渲染 |
| 状态订阅 | 细粒度selector订阅 | 避免不必要更新 |
| 优化策略 | 实现方式 | 适用场景 |
|---|---|---|
| 分页加载 | 后端分页 + 前端虚拟滚动 | 硬盘列表、日志列表 |
| 增量更新 | WebSocket推送状态变化 | 硬盘状态、容量变化 |
| 数据缓存 | RTK Query缓存 + SWR | 配置数据、静态数据 |
| 请求合并 | 防抖/节流处理高频请求 | 搜索输入、滑块调整 |
| 预加载 | 路由预加载、数据预取 | 详情页、下一步向导 |
| 压缩传输 | Gzip/Brotli压缩响应 | 大JSON数据 |
| 安全措施 | 实现方式 | 说明 |
|---|---|---|
| JWT认证 | Access Token + Refresh Token | 无状态认证,支持Token刷新 |
| RBAC权限 | 角色-权限-资源模型 | admin/user/viewer三级角色 |
| 路由守卫 | 高阶组件 + 路由配置 | 前端路由级权限控制 |
| API鉴权 | 请求头携带Token | 401未授权自动跳转登录 |
| Token安全 | HttpOnly Cookie存储 | 防XSS攻击 |
| 安全措施 | 实现方式 | 说明 |
|---|---|---|
| 输入验证 | Formik + Yup校验 | 前端表单输入验证 |
| XSS防护 | React自动转义 + CSP | 内容安全策略 |
| CSRF防护 | SameSite Cookie + Token | 跨站请求伪造防护 |
| 敏感信息 | 前端脱敏显示 | 序列号、密钥部分隐藏 |
| 操作审计 | 关键操作记录日志 | 创建、删除操作留痕 |
/\ / \ / E2E \ <--- 端到端测试 (5%) /--------\ Playwright / \ / Integration \ <--- 集成测试 (15%) /--------------\ React Testing Library / \ / Unit Tests \<--- 单元测试 (80%) /--------------------\ Jest
| 测试类型 | 工具 | 覆盖率目标 | 测试范围 |
|---|---|---|---|
| 单元测试 | Jest + RTL | ≥80% | 工具函数、Hooks、Reducers |
| 组件测试 | React Testing Library | ≥70% | UI组件渲染、交互 |
| 集成测试 | MSW + RTL | 核心流程 | API + 组件联动 |
| E2E测试 | Playwright | 主流程 | 关键用户场景 |
typescript// src/features/nas/components/DiskCard/DiskCard.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DiskCard } from './index';
import type { Disk } from '../../types/disk.types';
describe('DiskCard Component', () => {
const mockDisk: Disk = {
disk_id: 'disk_001',
model: 'ST4000VN008',
serial_number: 'ZGY0K3MZ',
capacity: 4000787030016,
capacity_formatted: '3.64 TB',
health_status: 'healthy',
smart_status: 'passed',
temperature: 42,
temperature_status: 'normal',
power_on_hours: 8760,
};
it('应正确显示硬盘基本信息', () => {
render(<DiskCard disk={mockDisk} />);
expect(screen.getByText('ST4000VN008')).toBeInTheDocument();
expect(screen.getByText('3.64 TB')).toBeInTheDocument();
expect(screen.getByText('健康')).toBeInTheDocument();
expect(screen.getByText('42°C')).toBeInTheDocument();
});
it('点击应触发onSelect回调', async () => {
const onSelect = jest.fn();
render(<DiskCard disk={mockDisk} onSelect={onSelect} />);
await userEvent.click(screen.getByTestId('disk-card'));
expect(onSelect).toHaveBeenCalledWith('disk_001');
});
it('健康状态为error时应显示警告样式', () => {
const errorDisk = { ...mockDisk, health_status: 'error' };
render(<DiskCard disk={errorDisk} />);
const card = screen.getByTestId('disk-card');
expect(card).toHaveClass('disk-card--error');
});
});
yaml# 构建配置
build:
command: npm run build
output_dir: dist/
node_version: 18.x
# 部署配置
deploy:
target: github_pages
branch: gh-pages
cdn:
enabled: true
cache_control: "public, max-age=31536000"
| 环境 | 部署目标 | 触发条件 | 备注 |
|---|---|---|---|
| 开发 | Vercel预览 | PR创建 | 自动部署 |
| 测试 | GitHub Pages (test) | 合并到develop | 功能验证 |
| 生产 | GitHub Pages (prod) | 合并到main | 手动确认 |
| 类别 | 指标 | 告警阈值 | 采集方式 |
|---|---|---|---|
| 性能 | LCP(最大内容绘制) | > 2.5s | Web Vitals API |
| 性能 | FID(首次输入延迟) | > 100ms | Web Vitals API |
| 性能 | CLS(累积布局偏移) | > 0.1 | Web Vitals API |
| 错误 | JS错误率 | > 1% | Sentry |
| 错误 | API错误率 | > 5% | Axios拦截器 |
| 业务 | 页面加载时间 | > 3s | Performance API |
| 业务 | 用户会话数 | - | Google Analytics |
| 文档 | 链接 | 说明 |
|---|---|---|
| React官方文档 | https://react.dev | React 18新特性 |
| Redux Toolkit | https://redux-toolkit.js.org | 状态管理最佳实践 |
| Ant Design | https://ant.design | UI组件库文档 |
| Vite配置指南 | https://vitejs.dev/config/ | 构建工具配置 |
| Web Vitals | https://web.dev/vitals/ | 性能指标标准 |
文档结束
如有疑问,请联系架构组。
本文作者:oyph
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!