32 KiB
设计文档:前后端分离 — Java API 后端与前端集成
概述
本设计文档描述将现有 Next.js 16 前端生产管理子系统从硬编码模拟数据迁移到真实 RESTful API 的完整技术方案。涵盖 Java Spring Boot 3.3.6 后端多模块项目架构、PostgreSQL 数据库设计、DDD 分层实现,以及前端 API 集成层的构建。
系统为地球物理仪器(高密度电法仪等)的全生命周期管理平台,包含设备管理、板卡管理、固件库、校准管理、校准文件管理、授权管理、配置文件管理、维修工单、报废回收等核心业务模块。
设计目标
- 搭建符合 DDD 分层架构的 Spring Boot 多模块 Maven 项目
- 设计 PostgreSQL
devschema 下的完整数据库表结构 - 为所有前端页面提供对应的 RESTful API 端点
- 在前端建立统一的 API 调用层,替换所有硬编码模拟数据
- 前后端采用统一的分页、筛选、错误响应规范
架构
整体架构
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
组件与接口
统一响应体
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) { ... }
}
分页响应体
public class PageResult<T> {
private long total; // 总记录数
private int page; // 当前页码
private int pageSize; // 每页条数
private List<T> records; // 数据列表
}
统一分页请求参数
所有列表接口统一使用:
page:页码,从 1 开始,默认 1pageSize:每页条数,默认 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 客户端
// 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;
前端通用类型
// 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 关系图
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 兜底捕获,记录完整堆栈日志 |
自定义异常体系
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;
}
}
全局异常处理器
@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 + TestContainers(PostgreSQL)
- 覆盖范围:
- Controller 层 API 端点的请求/响应格式
- MyBatis-Plus Mapper 的 CRUD 操作
- 审计字段自动填充
- 逻辑删除行为
- 分页查询
属性测试
- 框架:jqwik(Java 属性测试库)
- 最少 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 + MSW(Mock Service Worker)
- 覆盖范围:
- 页面组件与 API 的完整交互流程
- 筛选、分页操作的数据刷新
- 表单提交与 API 调用