enterprise-saa-s-dashboard-.../.kiro/specs/frontend-backend-separation/design.md

886 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 设计文档:前后端分离 — Java API 后端与前端集成
## 概述
本设计文档描述将现有 Next.js 16 前端生产管理子系统从硬编码模拟数据迁移到真实 RESTful API 的完整技术方案。涵盖 Java Spring Boot 3.3.6 后端多模块项目架构、PostgreSQL 数据库设计、DDD 分层实现,以及前端 API 集成层的构建。
系统为地球物理仪器(高密度电法仪等)的全生命周期管理平台,包含设备管理、板卡管理、固件库、校准管理、校准文件管理、授权管理、配置文件管理、维修工单、报废回收等核心业务模块。
### 设计目标
- 搭建符合 DDD 分层架构的 Spring Boot 多模块 Maven 项目
- 设计 PostgreSQL `dev` schema 下的完整数据库表结构
- 为所有前端页面提供对应的 RESTful API 端点
- 在前端建立统一的 API 调用层,替换所有硬编码模拟数据
- 前后端采用统一的分页、筛选、错误响应规范
## 架构
### 整体架构
```mermaid
graph TB
subgraph Frontend["前端 Next.js 16"]
Pages["页面组件<br/>devices / boards / firmware / ..."]
ApiLayer["API 集成层<br/>src/lib/api/"]
Pages --> ApiLayer
end
subgraph Backend["后端 Spring Boot 3.3.6"]
subgraph ApiAdmin["api-admin 模块"]
Controllers["REST Controllers<br/>/api/admin/*"]
end
subgraph ApiPortal["api-portal 模块"]
PortalControllers["REST Controllers<br/>/api/portal/*<br/>(无需鉴权)"]
end
subgraph Business["business 模块"]
subgraph Device["device 子模块"]
Interfaces["interfaces 层<br/>Controller / VO / Assembler"]
Application["application 层<br/>AppService / Command / Query"]
Domain["domain 层<br/>Entity / Repository接口"]
Infrastructure["infrastructure 层<br/>Mapper / DO / Repository实现"]
end
end
subgraph Common["common 模块"]
Response["统一响应体 R<T>"]
Exception["全局异常处理"]
Audit["审计字段自动填充"]
end
end
subgraph DB["PostgreSQL 12.14"]
DevSchema["dev schema<br/>devices / device_models / board_types / board_cards / calibration_files / ..."]
end
ApiLayer -->|HTTP REST| Controllers
ApiLayer -->|HTTP REST| PortalControllers
Controllers --> Interfaces
PortalControllers --> Interfaces
Interfaces --> Application
Application --> Domain
Domain --> Infrastructure
Infrastructure -->|MyBatis-Plus| DevSchema
```
### 后端项目结构
```
apps/geo-bps-api/
├── pom.xml # 父 POM
├── api-admin/
│ ├── pom.xml
│ └── src/main/java/com/geomative/bps/admin/
│ ├── ApiAdminApplication.java
│ └── config/
│ ├── CorsConfig.java # CORS 配置
│ └── WebMvcConfig.java
├── api-portal/
│ ├── pom.xml
│ └── src/main/java/com/geomative/bps/portal/
│ ├── ApiPortalApplication.java
│ └── config/
│ ├── CorsConfig.java # CORS 配置
│ └── WebMvcConfig.java # 文件上传大小限制等
├── business/
│ ├── pom.xml # pom packaging
│ └── device/
│ ├── pom.xml
│ └── src/main/java/com/geomative/bps/device/
│ ├── interfaces/ # Controller + VO
│ ├── application/ # AppService + Command + Query
│ ├── domain/ # Entity + Repository接口
│ └── infrastructure/ # Mapper + DO + Repository实现
└── common/
├── pom.xml
└── src/main/java/com/geomative/bps/common/
├── response/
│ └── R.java # 统一响应体
├── exception/
│ ├── BizException.java
│ └── GlobalExceptionHandler.java
├── mybatis/
│ └── AuditMetaObjectHandler.java
└── page/
└── PageResult.java # 分页响应
```
### 前端 API 集成层结构
```
src/
├── lib/
│ └── api/
│ ├── client.ts # Axios 实例,统一配置
│ ├── types.ts # 通用响应/分页类型
│ ├── deviceApi.ts # 设备管理 API
│ ├── modelApi.ts # 设备型号 API
│ ├── boardApi.ts # 板卡管理 API
│ ├── firmwareApi.ts # 固件库 API
│ ├── calibrationApi.ts # 校准管理 API
│ ├── calibrationFileApi.ts # 校准文件 API
│ ├── configFileApi.ts # 配置文件 API
│ ├── licenseApi.ts # 授权管理 API
│ ├── repairApi.ts # 维修工单 API
│ ├── scrapApi.ts # 报废管理 API
│ └── dashboardApi.ts # 首页统计 API
└── hooks/
└── useApi.ts # 通用数据获取 Hook
```
## 组件与接口
### 统一响应体
```java
public class R<T> {
private int code; // 业务状态码0=成功
private String message; // 提示信息
private T data; // 响应数据
public static <T> R<T> ok(T data) { ... }
public static <T> R<T> fail(int code, String message) { ... }
}
```
### 分页响应体
```java
public class PageResult<T> {
private long total; // 总记录数
private int page; // 当前页码
private int pageSize; // 每页条数
private List<T> records; // 数据列表
}
```
### 统一分页请求参数
所有列表接口统一使用:
- `page`:页码,从 1 开始,默认 1
- `pageSize`:每页条数,默认 10
### API 端点清单
| 模块 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 设备 | GET | `/api/admin/devices` | 分页设备列表(筛选:型号/状态/日期/SN/批次) |
| 设备 | GET | `/api/admin/devices/{sn}` | 设备详情(含授权、子设备、固件) |
| 设备 | POST | `/api/admin/devices` | 创建设备(设备登记) |
| 设备 | GET | `/api/admin/devices/batches` | 生产批次列表 |
| 型号 | GET | `/api/admin/device-models` | 设备型号列表 |
| 型号 | POST | `/api/admin/device-models` | 创建设备型号 |
| 型号 | GET | `/api/admin/checklist-templates` | Checklist 模板列表 |
| 型号 | POST | `/api/admin/checklist-templates` | 创建 Checklist 模板 |
| 板卡 | GET | `/api/admin/board-types` | 板卡型号列表(筛选:类型) |
| 板卡 | GET | `/api/admin/board-types/{id}` | 板卡详情 |
| 板卡 | POST | `/api/admin/board-types` | 创建板卡型号 |
| 固件 | GET | `/api/admin/firmware` | 固件版本列表(筛选:类型/板卡型号) |
| 固件 | POST | `/api/admin/firmware` | 创建固件版本 |
| 固件 | GET | `/api/admin/firmware/{id}/download` | 下载固件文件 |
| 校准 | GET | `/api/admin/calibrations` | 校准记录列表筛选SN/状态/人员) |
| 校准 | GET | `/api/admin/calibrations/{id}` | 校准详情 |
| 校准 | POST | `/api/admin/calibrations/import` | 批量导入校准记录 |
| 校准文件 | POST | `/api/portal/calibration-files/upload` | 上传校准文件(无需鉴权,上位机调用) |
| 校准文件 | GET | `/api/admin/board-cards/{id}/calibration-files` | 指定采集板的校准文件列表(按上传时间倒序) |
| 校准文件 | GET | `/api/admin/calibration-files/{id}/download` | 下载校准文件 |
| 配置 | GET | `/api/admin/config-files` | 配置文件列表(筛选:型号/版本/关键字) |
| 配置 | GET | `/api/admin/config-files/{id}` | 配置文件详情 |
| 配置 | POST | `/api/admin/config-files` | 创建配置文件 |
| 配置 | PUT | `/api/admin/config-files/{id}` | 更新配置文件 |
| 配置 | DELETE | `/api/admin/config-files/{id}` | 逻辑删除配置文件 |
| 授权 | GET | `/api/admin/licenses` | 授权列表(筛选:型号/状态) |
| 授权 | POST | `/api/admin/licenses` | 创建授权记录 |
| 授权 | PUT | `/api/admin/licenses/{id}` | 更新授权记录 |
| 授权 | PUT | `/api/admin/licenses/{id}/disable` | 停用授权 |
| 维修 | GET | `/api/admin/repair-orders` | 工单列表(筛选:状态/优先级/人员/日期/SN |
| 维修 | GET | `/api/admin/repair-orders/{id}` | 工单详情 |
| 维修 | POST | `/api/admin/repair-orders` | 创建工单 |
| 维修 | PUT | `/api/admin/repair-orders/{id}/process` | 处理工单 |
| 维修 | PUT | `/api/admin/repair-orders/{id}/close` | 关闭工单 |
| 报废 | GET | `/api/admin/scrap-records` | 报废记录列表筛选SN/状态/日期) |
| 报废 | GET | `/api/admin/scrap-records/{id}` | 报废详情 |
| 报废 | PUT | `/api/admin/scrap-records/{id}/approve` | 审批通过 |
| 报废 | PUT | `/api/admin/scrap-records/{id}/reject` | 驳回 |
| 报废 | PUT | `/api/admin/scrap-records/{id}/recover` | 物料回收入库 |
| 报废 | GET | `/api/admin/scrap-records/stats` | 报废统计 |
| 首页 | GET | `/api/admin/dashboard/metrics` | 统计指标 |
| 首页 | GET | `/api/admin/dashboard/device-status` | 设备状态分布 |
| 首页 | GET | `/api/admin/dashboard/tasks` | 待处理任务 |
### 前端 API 客户端
```typescript
// src/lib/api/client.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
timeout: 5000,
headers: { 'Content-Type': 'application/json' },
});
// 响应拦截器:统一错误处理
apiClient.interceptors.response.use(
(res) => res.data,
(error) => {
const message = error.response?.data?.message || '请求失败,请稍后重试';
return Promise.reject(new Error(message));
}
);
export default apiClient;
```
### 前端通用类型
```typescript
// src/lib/api/types.ts
export interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
export interface PageResult<T> {
total: number;
page: number;
pageSize: number;
records: T[];
}
export interface PageParams {
page?: number;
pageSize?: number;
}
```
## 数据模型
### ER 关系图
```mermaid
erDiagram
device_models ||--o{ devices : "型号关联"
device_models ||--o{ checklist_templates : "模板关联"
device_models ||--o{ config_files : "配置关联"
device_models ||--o{ licenses : "授权关联"
checklist_templates ||--o{ checklist_items : "包含检查项"
devices ||--o{ device_boards : "装配板卡"
devices ||--o{ repair_orders : "维修工单"
devices ||--o{ scrap_records : "报废记录"
board_types ||--o{ device_boards : "板卡实例"
board_types ||--o{ firmware_versions : "固件版本"
board_types ||--o{ calibration_records : "校准记录"
board_cards ||--o{ calibration_files : "校准文件"
device_models {
varchar(64) id PK
varchar(100) name
varchar(50) code UK
varchar(20) status
varchar(500) description
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
devices {
varchar(64) id PK
varchar(100) sn UK
varchar(64) model_id FK
varchar(100) model_name
varchar(20) status
varchar(20) firmware_version
timestamp production_date
varchar(200) customer
varchar(20) batch
varchar(64) operator
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
board_types {
varchar(64) id PK
varchar(50) type
varchar(50) version UK
varchar(20) latest_firmware
date production_date
varchar(20) status
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
device_boards {
varchar(64) id PK
varchar(64) device_id FK
varchar(100) device_sn
varchar(64) board_type_id FK
varchar(100) board_sn
varchar(50) board_name
varchar(50) board_model
varchar(20) calibration_status
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
firmware_versions {
varchar(64) id PK
varchar(20) version
varchar(64) board_type_id FK
varchar(50) board_version
varchar(50) firmware_type
date release_date
varchar(20) status
varchar(20) file_size
int download_count
varchar(200) hw_range
varchar(20) upgrade_type
boolean signed
varchar(64) md5
varchar(128) sha256
text release_notes
varchar(500) file_path
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
calibration_records {
varchar(64) id PK
varchar(100) board_sn
varchar(64) board_type_id FK
varchar(50) board_version
date calibration_date
date expiry_date
varchar(100) calibrator
varchar(20) status
int channel_count
decimal overall_deviation
jsonb channel_results
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
config_files {
varchar(64) id PK
varchar(100) name
varchar(64) model_id FK
varchar(100) model_name
varchar(20) version
varchar(20) status
jsonb transmission_params
jsonb acquisition_params
jsonb protection_params
jsonb network_params
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
licenses {
varchar(64) id PK
varchar(64) model_id FK
varchar(50) model_code
jsonb modules
varchar(20) validity_type
date expiry_date
varchar(20) status
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
repair_orders {
varchar(64) id PK
varchar(50) order_no UK
varchar(100) device_sn
varchar(50) fault_type
varchar(20) status
varchar(10) priority
varchar(100) assignee
varchar(500) description
varchar(500) phenomenon
date expected_fix_date
text note
jsonb process_records
jsonb board_replacements
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
scrap_records {
varchar(64) id PK
varchar(100) device_sn
varchar(100) model_name
varchar(500) reason
varchar(100) applicant
varchar(20) status
varchar(50) order_id
decimal residual_value
jsonb materials
jsonb approval_timeline
text approval_note
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
checklist_templates {
varchar(64) id PK
varchar(64) model_id FK
varchar(50) model_code
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
checklist_items {
varchar(64) id PK
varchar(64) template_id FK
varchar(200) name
boolean required
int sort_order
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
board_cards {
varchar(64) id PK
varchar(100) sn UK
varchar(50) type
varchar(50) version
varchar(20) firmware_version
varchar(20) status
varchar(100) device_sn
date production_date
varchar(20) calib_status
date calib_date
varchar(500) remark
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
calibration_files {
varchar(64) id PK
varchar(100) board_sn
varchar(200) original_name
varchar(500) file_path
bigint file_size
varchar(64) md5
timestamp upload_time
timestamp created_at
varchar(64) created_by
timestamp updated_at
varchar(64) updated_by
smallint deleted
}
```
### 表结构详细说明
#### 公共审计字段(所有表必须包含)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | VARCHAR(64) | PK | 应用层生成(雪花算法/UUID |
| created_at | TIMESTAMP | NOT NULL | 创建时间INSERT 自动填充 |
| created_by | VARCHAR(64) | NULL | 创建人 |
| updated_at | TIMESTAMP | NOT NULL | 修改时间INSERT/UPDATE 自动填充 |
| updated_by | VARCHAR(64) | NULL | 修改人 |
| deleted | SMALLINT | NOT NULL DEFAULT 0 | 逻辑删除标记 |
#### dev.devices — 设备表
| 字段 | 类型 | 说明 |
|------|------|------|
| sn | VARCHAR(100) | 设备SN号唯一索引 |
| model_id | VARCHAR(64) | 关联设备型号 |
| model_name | VARCHAR(100) | 型号名称(冗余) |
| status | VARCHAR(20) | 装配中/已出厂/已激活/报废 |
| firmware_version | VARCHAR(20) | 当前固件版本 |
| production_date | TIMESTAMP | 生产日期 |
| customer | VARCHAR(200) | 客户名称 |
| batch | VARCHAR(20) | 生产批次YYYY-WXX |
| operator | VARCHAR(64) | 登记人 |
#### dev.device_models — 设备型号表
| 字段 | 类型 | 说明 |
|------|------|------|
| name | VARCHAR(100) | 型号名称(如 GD-30 Supreme |
| code | VARCHAR(50) | 型号代码(如 GD30唯一索引 |
| status | VARCHAR(20) | 在产/停产 |
| description | VARCHAR(500) | 描述 |
#### dev.board_types — 板卡型号表
| 字段 | 类型 | 说明 |
|------|------|------|
| type | VARCHAR(50) | 板卡类型(主协板/采集板/发射板/升压板) |
| version | VARCHAR(50) | 版本号(如 MB-V1.8),唯一索引 |
| latest_firmware | VARCHAR(20) | 最新固件版本 |
| production_date | DATE | 生产日期 |
| status | VARCHAR(20) | 在产/停产 |
#### dev.device_boards — 设备板卡关联表
| 字段 | 类型 | 说明 |
|------|------|------|
| device_id | VARCHAR(64) | 关联设备 |
| device_sn | VARCHAR(100) | 设备SN冗余 |
| board_type_id | VARCHAR(64) | 关联板卡型号 |
| board_sn | VARCHAR(100) | 板卡SN号 |
| board_name | VARCHAR(50) | 板卡名称(主协板/采集板等) |
| board_model | VARCHAR(50) | 板卡型号 |
| calibration_status | VARCHAR(20) | 校准状态 |
#### dev.firmware_versions — 固件版本表
| 字段 | 类型 | 说明 |
|------|------|------|
| version | VARCHAR(20) | 版本号 |
| board_type_id | VARCHAR(64) | 关联板卡型号 |
| board_version | VARCHAR(50) | 板卡版本(冗余) |
| firmware_type | VARCHAR(50) | 固件类型(主协板/采集板/发射板/升压板/主机固件/计算单元固件) |
| release_date | DATE | 发布日期 |
| status | VARCHAR(20) | 已发布/草稿 |
| file_size | VARCHAR(20) | 文件大小 |
| download_count | INT | 下载次数,默认 0 |
| hw_range | VARCHAR(200) | 硬件版本范围 |
| upgrade_type | VARCHAR(20) | 可选/强制 |
| signed | BOOLEAN | 是否数字签名 |
| md5 | VARCHAR(64) | MD5 校验值 |
| sha256 | VARCHAR(128) | SHA256 校验值 |
| release_notes | TEXT | 发布说明JSON 数组) |
| file_path | VARCHAR(500) | 文件存储路径 |
#### dev.calibration_records — 校准记录表
| 字段 | 类型 | 说明 |
|------|------|------|
| board_sn | VARCHAR(100) | 采集板SN号 |
| board_type_id | VARCHAR(64) | 关联板卡型号 |
| board_version | VARCHAR(50) | 板卡版本(冗余) |
| calibration_date | DATE | 校准日期 |
| expiry_date | DATE | 到期日期 |
| calibrator | VARCHAR(100) | 校准人员 |
| status | VARCHAR(20) | 合格/不合格/待校准 |
| channel_count | INT | 通道数 |
| overall_deviation | DECIMAL(10,4) | 综合偏差 |
| channel_results | JSONB | 各通道校准结果 |
#### dev.config_files — 配置文件表
| 字段 | 类型 | 说明 |
|------|------|------|
| name | VARCHAR(100) | 配置文件名(如 CFG-GD30-v1.3.0 |
| model_id | VARCHAR(64) | 关联设备型号 |
| model_name | VARCHAR(100) | 型号名称(冗余) |
| version | VARCHAR(20) | 配置版本 |
| status | VARCHAR(20) | 生效/已停用 |
| transmission_params | JSONB | 发射参数 |
| acquisition_params | JSONB | 采集参数 |
| protection_params | JSONB | 保护参数 |
| network_params | JSONB | 网络参数 |
#### dev.licenses — 授权表
| 字段 | 类型 | 说明 |
|------|------|------|
| model_id | VARCHAR(64) | 关联设备型号 |
| model_code | VARCHAR(50) | 型号代码(冗余) |
| modules | JSONB | 授权模块列表 |
| validity_type | VARCHAR(20) | 永久/1年/2年/自定义 |
| expiry_date | DATE | 到期日期 |
| status | VARCHAR(20) | 生效/草稿/已停用 |
#### dev.repair_orders — 维修工单表
| 字段 | 类型 | 说明 |
|------|------|------|
| order_no | VARCHAR(50) | 工单号(如 WO-2024-0001唯一索引 |
| device_sn | VARCHAR(100) | 设备SN号 |
| fault_type | VARCHAR(50) | 故障类型 |
| status | VARCHAR(20) | 待处理/处理中/已处理 |
| priority | VARCHAR(10) | 高/中/低 |
| assignee | VARCHAR(100) | 负责人 |
| description | VARCHAR(500) | 故障描述 |
| phenomenon | VARCHAR(500) | 故障现象 |
| expected_fix_date | DATE | 预计修复日期 |
| note | TEXT | 备注 |
| process_records | JSONB | 处理记录时间线 |
| board_replacements | JSONB | 板卡更换记录 |
#### dev.scrap_records — 报废记录表
| 字段 | 类型 | 说明 |
|------|------|------|
| device_sn | VARCHAR(100) | 设备SN号 |
| model_name | VARCHAR(100) | 设备型号 |
| reason | VARCHAR(500) | 报废原因 |
| applicant | VARCHAR(100) | 申请人 |
| status | VARCHAR(20) | 待审批/审批中/已审批/已驳回/回收中/已回收 |
| order_id | VARCHAR(50) | 来源工单号 |
| residual_value | DECIMAL(12,2) | 残值评估 |
| materials | JSONB | 可回收物料列表 |
| approval_timeline | JSONB | 审批记录时间线 |
| approval_note | TEXT | 审批意见 |
#### dev.checklist_templates — Checklist 模板表
| 字段 | 类型 | 说明 |
|------|------|------|
| model_id | VARCHAR(64) | 关联设备型号 |
| model_code | VARCHAR(50) | 型号代码(冗余) |
#### dev.checklist_items — Checklist 检查项表
| 字段 | 类型 | 说明 |
|------|------|------|
| template_id | VARCHAR(64) | 关联模板 |
| name | VARCHAR(200) | 检查项名称 |
| required | BOOLEAN | 是否必填 |
| sort_order | INT | 排序序号 |
#### dev.calibration_files — 校准文件表
| 字段 | 类型 | 说明 |
|------|------|------|
| board_sn | VARCHAR(100) | 采集板SN号关联 board_cards.sn |
| original_name | VARCHAR(200) | 原始文件名 |
| file_path | VARCHAR(500) | 服务器存储路径 |
| file_size | BIGINT | 文件大小(字节) |
| md5 | VARCHAR(64) | 文件MD5校验值 |
| upload_time | TIMESTAMP | 上传时间 |
## 正确性属性
*正确性属性是在系统所有有效执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规范与机器可验证正确性保证之间的桥梁。*
### Property 1: 统一响应格式一致性
*For any* API 请求(无论成功或失败),响应 JSON 都应包含且仅包含 `code`(整数)、`message`(字符串)、`data`(泛型)三个顶层字段。成功时 code=0业务异常和参数校验异常均应返回 code≠0 的统一格式响应。
**Validates: Requirements 1.3, 1.4**
### Property 2: 审计字段自动填充
*For any* 通过 MyBatis-Plus 插入的实体记录,`created_at` 和 `updated_at` 字段应非空且为合理的时间戳;*For any* 更新操作,`updated_at` 应大于等于更新前的值。*For any* `dev` schema 下的业务表,都应包含 `id`、`created_at`、`created_by`、`updated_at`、`updated_by`、`deleted` 六个审计字段。
**Validates: Requirements 1.5, 2.12**
### Property 3: CRUD 数据往返一致性
*For any* 有效的创建命令设备、型号、板卡、固件、配置文件、授权、工单、Checklist 模板),通过 POST 创建后再通过 GET 查询,返回的数据应与创建时提交的数据一致(在服务端生成的字段如 id、审计字段除外
**Validates: Requirements 3.2, 3.3, 4.2, 4.4, 5.3, 6.2, 8.3, 8.4, 9.2, 9.3, 10.3**
### Property 4: 唯一性约束与冲突检测
*For any* 已存在的唯一标识设备SN号、型号代码、同一板卡型号下的固件版本号尝试创建具有相同唯一标识的记录时API 应返回 HTTP 409 冲突错误码和描述性错误消息,且数据库中不应产生重复记录。
**Validates: Requirements 3.5, 4.5, 6.4**
### Property 5: 筛选条件正确性
*For any* 列表查询接口和任意有效的筛选参数组合,返回的所有记录都应满足指定的筛选条件。例如:按型号筛选时,所有返回的设备型号应与筛选值匹配;按状态筛选时,所有返回记录的状态应与筛选值匹配。
**Validates: Requirements 3.1, 5.1, 6.1, 7.1, 8.1, 9.1, 10.1, 11.1**
### Property 6: 状态转换正确性
*For any* 支持状态转换的实体(授权停用、工单关闭、报废审批/驳回/回收),执行状态转换操作后,实体的状态应更新为目标状态,且相关附加信息(如驳回意见、回收物料清单)应被正确记录。
**Validates: Requirements 9.4, 10.5, 11.3, 11.4, 11.5**
### Property 7: 逻辑删除排除性
*For any* 被逻辑删除的记录deleted=1该记录不应出现在任何列表查询的结果中。
**Validates: Requirements 8.5**
### Property 8: 分页一致性
*For any* 列表查询接口,响应应包含 `total`、`page`、`pageSize`、`records` 四个字段;`records` 的长度应不超过 `pageSize`;当不传筛选条件或筛选条件为空时,应返回不带过滤的完整数据集(受分页限制)。
**Validates: Requirements 14.1, 14.2, 14.4**
### Property 9: 聚合统计一致性
*For any* 统计接口返回的数据,各分类计数之和应等于总数。例如:设备状态分布中各状态数量之和应等于设备总数;报废统计中各状态数量之和应等于报废总数。
**Validates: Requirements 11.6, 12.2**
### Property 10: 参数校验防御性
*For any* 无效的筛选参数如非法的状态值、格式错误的日期API 应返回 HTTP 400 错误码和描述性错误消息,而非返回错误数据或抛出未处理异常。
**Validates: Requirements 14.3**
### Property 11: 校准文件上传-下载往返一致性
*For any* 有效的校准文件和已存在的采集板SN号通过 POST `/api/portal/calibration-files/upload` 上传后,再通过 GET `/api/admin/calibration-files/{id}/download` 下载,下载的文件内容应与上传的原始文件完全一致,且 `dev.calibration_files` 表中的记录应正确关联该采集板SN号文件大小和MD5校验值应与原始文件匹配。
**Validates: Requirements 17.2, 17.3, 17.7**
### Property 12: 校准文件列表完整性与排序
*For any* 采集板,上传 N 个校准文件后,通过 GET `/api/admin/board-cards/{id}/calibration-files` 查询应返回恰好 N 条记录,每条记录包含 id、fileName、fileSize、md5、uploadTime 字段,且结果按 uploadTime 倒序排列。
**Validates: Requirements 17.6, 17.10**
## 错误处理
### 后端错误处理策略
| 错误类型 | HTTP 状态码 | 业务码 | 处理方式 |
|----------|------------|--------|----------|
| 参数校验失败 | 400 | 40001 | `GlobalExceptionHandler` 捕获 `MethodArgumentNotValidException`,返回字段级错误信息 |
| 资源不存在 | 404 | 40401 | Service 层抛出 `BizException`Handler 统一处理 |
| 文件不存在 | 404 | 40402 | 校准文件在存储目录中不存在时,返回描述性错误消息 |
| 唯一性冲突 | 409 | 40901 | Service 层检测重复后抛出 `BizException` |
| 文件上传失败 | 500 | 50002 | 文件写入磁盘失败时,记录日志并返回错误消息 |
| 服务器内部错误 | 500 | 50001 | `GlobalExceptionHandler` 兜底捕获,记录完整堆栈日志 |
### 自定义异常体系
```java
public class BizException extends RuntimeException {
private final int code;
private final String message;
public BizException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
}
```
### 全局异常处理器
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public R<Void> handleBizException(BizException e) {
return R.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return R.fail(40001, msg);
}
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e) {
log.error("Unhandled exception", e);
return R.fail(50001, "服务器内部错误");
}
}
```
### 前端错误处理策略
| 场景 | 处理方式 |
|------|----------|
| 网络超时5秒 | 显示"网络请求超时,请检查网络连接" |
| HTTP 400 | 显示后端返回的具体校验错误信息 |
| HTTP 404 | 显示"请求的资源不存在" |
| HTTP 409 | 显示后端返回的冲突描述(如"SN号已存在" |
| HTTP 500 | 显示"服务器异常,请稍后重试" |
| 请求进行中 | 页面显示 loading 骨架屏或 spinner |
## 测试策略
### 后端测试
#### 单元测试
- 框架JUnit 5 + Mockito
- 覆盖范围:
- Application Service 层的业务逻辑
- Domain Entity 的状态转换逻辑
- 参数校验逻辑
- 异常处理逻辑
#### 集成测试
- 框架Spring Boot Test + TestContainersPostgreSQL
- 覆盖范围:
- Controller 层 API 端点的请求/响应格式
- MyBatis-Plus Mapper 的 CRUD 操作
- 审计字段自动填充
- 逻辑删除行为
- 分页查询
#### 属性测试
- 框架jqwikJava 属性测试库)
- 最少 100 次迭代
- 每个属性测试需引用设计文档中的属性编号
- 标签格式:`Feature: frontend-backend-separation, Property {number}: {property_text}`
- 覆盖范围:
- Property 1: 统一响应格式(生成随机数据类型,验证序列化格式)
- Property 3: CRUD 往返一致性(生成随机实体数据,验证创建-查询一致性)
- Property 4: 唯一性约束(生成随机唯一标识,验证重复创建返回 409
- Property 5: 筛选正确性(生成随机筛选参数,验证返回结果匹配)
- Property 8: 分页一致性(生成随机 page/pageSize验证响应格式
- Property 10: 参数校验(生成随机无效参数,验证返回 400
- Property 11: 校准文件上传-下载往返生成随机文件内容和SN号验证上传后下载内容一致、元数据正确
- Property 12: 校准文件列表完整性(生成随机数量的校准文件上传,验证列表返回数量和排序正确)
### 前端测试
#### 单元测试
- 框架Vitest + React Testing Library
- 覆盖范围:
- API 客户端的请求拦截和错误处理
- 各 API 服务文件的请求参数构造
- 页面组件的加载状态和错误状态渲染
- 板卡详情抽屉中校准文件列表的渲染和下载按钮交互
#### 集成测试
- 框架Vitest + MSWMock Service Worker
- 覆盖范围:
- 页面组件与 API 的完整交互流程
- 筛选、分页操作的数据刷新
- 表单提交与 API 调用