# 设计文档：前后端分离 — 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 + 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 调用

