# 设计文档:前后端分离 — 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["页面组件
devices / boards / firmware / ..."] ApiLayer["API 集成层
src/lib/api/"] Pages --> ApiLayer end subgraph Backend["后端 Spring Boot 3.3.6"] subgraph ApiAdmin["api-admin 模块"] Controllers["REST Controllers
/api/admin/*"] end subgraph ApiPortal["api-portal 模块"] PortalControllers["REST Controllers
/api/portal/*
(无需鉴权)"] end subgraph Business["business 模块"] subgraph Device["device 子模块"] Interfaces["interfaces 层
Controller / VO / Assembler"] Application["application 层
AppService / Command / Query"] Domain["domain 层
Entity / Repository接口"] Infrastructure["infrastructure 层
Mapper / DO / Repository实现"] end end subgraph Common["common 模块"] Response["统一响应体 R"] Exception["全局异常处理"] Audit["审计字段自动填充"] end end subgraph DB["PostgreSQL 12.14"] DevSchema["dev schema
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 { private int code; // 业务状态码,0=成功 private String message; // 提示信息 private T data; // 响应数据 public static R ok(T data) { ... } public static R fail(int code, String message) { ... } } ``` ### 分页响应体 ```java public class PageResult { private long total; // 总记录数 private int page; // 当前页码 private int pageSize; // 每页条数 private List 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 { code: number; message: string; data: T; } export interface PageResult { 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 handleBizException(BizException e) { return R.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public R 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 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 调用