577 lines
17 KiB
Markdown
577 lines
17 KiB
Markdown
# 固态功率控制器动态参数测试系统 — 开发文档
|
||
|
||
> 版本 v1.1 | 技术栈:**C# + WPF + .NET 8** | 口径:**以当前仓库代码为准**
|
||
>
|
||
> 本文档描述当前仓库的真实结构、配置方式、核心流程和维护约定。
|
||
> 若文档与代码冲突,以代码实现为准,并应同步回写本文档。
|
||
|
||
---
|
||
|
||
## 0. 开发约束
|
||
|
||
对本项目进行新增、修改、排障时,默认遵守以下原则:
|
||
|
||
1. **分层隔离优先**:UI 只依赖 ViewModel / Service / Logic,不直接依赖具体设备驱动。
|
||
2. **接口先行**:SSPC、电源、负载、DAQ 一律经 `SSPCTester.Devices.Interfaces` 暴露,真假驱动由配置决定。
|
||
3. **按设备粒度切换 Mock/Real**:当前不再是“一个全局 `MockMode` 管全部设备”,而是每类设备都有自己的 `UseMock`。
|
||
4. **配置分层加载**:程序先读内置默认值,再读 `appsettings.json`,最后读 `%LOCALAPPDATA%\SSPCTester\settings.json`。
|
||
5. **测试态独占**:进入测试后只展示当前项目,禁止切换其他项目,设备连接由测试态独占。
|
||
6. **文档随代码更新**:涉及接口、项目结构、运行方式、持久化格式变化时,要同步更新本文档。
|
||
|
||
---
|
||
|
||
## 1. 项目背景
|
||
|
||
### 1.1 被测对象
|
||
|
||
固态功率控制器(SSPC)用于智能配电场景。本项目按 **24 路** 输出通道建模,通过软件控制每路导通/关断,并测量:
|
||
|
||
- 基础通断状态
|
||
- 开启/关断曲线与时间参数
|
||
- 功率损耗与效率
|
||
- 保护功能演示结果
|
||
|
||
### 1.2 当前测试台硬件
|
||
|
||
| 部件 | 型号 | 作用 | 当前代码状态 |
|
||
|---|---|---|---|
|
||
| 工控机 | Windows x64 | 运行 WPF 主程序 | 已支持 |
|
||
| 被测件 SSPC | 24 路 | 控制 24 路导通/关断 | 已有 `SspcModbus` |
|
||
| 程控电源 | UDP5080-100 | 输入端电压/电流读取 | 已有真实驱动,控制命令保留接口 |
|
||
| 可编程负载 | IT8733P / IT8702P | 输出端电压/电流/功率读取 | 已有真实驱动 |
|
||
| 高速采集卡 | PCIe8586 | 曲线波形采集 | 已有 `Pcie8586Daq` |
|
||
| 示波器 | Tektronix MDO3024 | 线下校准基准 | 不纳入软件控制 |
|
||
|
||
### 1.3 当前实现事实
|
||
|
||
- SSPC 24 路通过 **3 个 Modbus 模块 × 每模块 8 路** 映射。
|
||
- 采集卡实现已经统一为 **PCIe8586**,不再使用旧文档中的 `PCIe9318` 命名。
|
||
- 真假设备切换已经演进为 **每类设备独立 `UseMock`**。
|
||
- 主程序启动后会按配置自动连接 SSPC、电源、负载、DAQ。
|
||
- 根目录脚本 `run.ps1` / `publish.ps1` 已是当前标准运行入口。
|
||
|
||
---
|
||
|
||
## 2. 当前功能范围
|
||
|
||
主流程如下:
|
||
|
||
```text
|
||
新建/打开项目
|
||
-> 填写项目信息 / 勾选测试项
|
||
-> 进入测试态
|
||
-> 执行基础测试 / 曲线测试 / 功率损耗 / 保护功能
|
||
-> 生成 PDF / Word 报告
|
||
```
|
||
|
||
### 2.1 四大模块
|
||
|
||
| 模块 | 当前实现 | 状态 |
|
||
|---|---|---|
|
||
| 基础测试 | 24 路逐路开/关、读电压电流、判定稳态关断 | 已实现 |
|
||
| 开启与关断曲线 | 支持开通/关断波形采集、指标计算、独立曲线视窗、波形落盘 | 已实现 |
|
||
| 功率损耗 | 使用 UDP5080 读输入端,IT87xx 读输出端功率/电压/电流 | 已实现 |
|
||
| 保护功能 | UI、数据结构和演示结果生成已完成,真实联动未接 | 框架完成 |
|
||
|
||
### 2.2 指标口径
|
||
|
||
当前计算与判定主要围绕以下指标:
|
||
|
||
- `1.1` 开启时间:到达输入电压 90% 的时间
|
||
- `1.2` 关闭时间:下降到输入电压 10% 的时间
|
||
- `1.5` 上升/下降时间:10%~90% 或 90%~10% 时间
|
||
- `1.9` 电压降:`Vin - Vout`
|
||
- `1.10 / 1.13` 功率损耗、效率
|
||
- `1.14+` 保护功能相关故障时间与演示状态
|
||
|
||
阈值定义和最终判断以 `Project.CurveThresholds` 与各页结果模型为准。
|
||
|
||
---
|
||
|
||
## 3. 技术栈
|
||
|
||
| 用途 | 当前选型 | 说明 |
|
||
|---|---|---|
|
||
| UI | WPF + .NET 8 | `net8.0-windows`,x64 |
|
||
| MVVM | `CommunityToolkit.Mvvm` | `ObservableObject` / `RelayCommand` |
|
||
| 配置 | `Microsoft.Extensions.Configuration` | JSON + 用户设置覆盖 |
|
||
| DI / Host | `Microsoft.Extensions.Hosting` | 启动时装配所有服务 |
|
||
| Modbus | `NModbus` | SSPC 串口控制 |
|
||
| 串口 | `System.IO.Ports` | SSPC/部分设备底层通信 |
|
||
| 波形绘制 | `ScottPlot` / `ScottPlot.WPF` | 曲线页与报告插图 |
|
||
| 报告 | `QuestPDF` + `DocumentFormat.OpenXml` | PDF 与 Word 双实现 |
|
||
| Web 预览 | `Microsoft.Web.WebView2` | 报告页 PDF 预览 |
|
||
| 日志 | Serilog | Debug + 文件输出 |
|
||
|
||
---
|
||
|
||
## 4. 解决方案结构
|
||
|
||
### 4.1 分层
|
||
|
||
```text
|
||
UI (WPF / XAML / ViewModel)
|
||
-> Logic (测试流程 / 计算 / 报告 / 存储)
|
||
-> Devices.Interfaces (统一设备接口)
|
||
-> Devices.Drivers.Mock / Real (具体驱动)
|
||
```
|
||
|
||
依赖方向始终是 **上层只依赖下层抽象**。
|
||
例如 UI 只拿到 `ISspc`,不直接感知背后是 `MockSspc` 还是 `SspcModbus`。
|
||
|
||
### 4.2 当前解决方案
|
||
|
||
```text
|
||
SSPCTester.sln
|
||
├── SSPCTester.UI/ # WPF 主程序(启动项)
|
||
│ ├── Views/ # 页面与窗口
|
||
│ ├── ViewModels/ # MVVM 视图模型
|
||
│ ├── Controls/ # 自定义控件(含曲线控件)
|
||
│ ├── Resources/ # Tokens / Controls / Icons
|
||
│ ├── Converters/ # 状态与颜色转换
|
||
│ └── Services/ # 用户设置、应用配置等
|
||
├── SSPCTester.Logic/ # 业务层
|
||
│ ├── Testing/ # 测试模块与运行器
|
||
│ ├── Calculation/ # 曲线/功率计算纯函数
|
||
│ ├── Report/ # PDF/Word 报告实现
|
||
│ ├── Storage/ # 项目与波形持久化
|
||
│ └── Models/ # 项目、结果、阈值、枚举
|
||
├── SSPCTester.Devices/ # 设备抽象与驱动
|
||
│ ├── Interfaces/ # ISspc / IPowerSupply / ILoad / IDaq
|
||
│ ├── Drivers/Mock/ # Mock 驱动
|
||
│ ├── Drivers/Real/ # 真设备驱动
|
||
│ ├── Drivers/ConfigurableDevices.cs # 按设备切换 Mock/Real
|
||
│ ├── Protocol/ # 协议辅助
|
||
│ └── Config/ # 设备配置模型
|
||
├── SSPCTester.Tests/ # xUnit 测试
|
||
├── SSPCTester.Seed/ # 示例项目/种子数据生成
|
||
└── Docs/PCIe8586驱动接入资料/src/Pcie8586.Driver/
|
||
# 采集卡独立驱动类库,被 Devices/Tests 引用
|
||
```
|
||
|
||
### 4.3 关键脚本
|
||
|
||
- `run.ps1`:发布 UI、可选执行单测、可选灌入种子数据、最后启动发布产物
|
||
- `publish.ps1`:单独发布 WPF 程序
|
||
- `SSPCTester.Seed/WriteQualifiedProjects.ps1`:生成演示项目
|
||
|
||
---
|
||
|
||
## 5. 配置与驱动切换
|
||
|
||
### 5.1 配置加载顺序
|
||
|
||
程序启动时配置来源按优先级叠加:
|
||
|
||
1. 内置默认配置
|
||
2. `SSPCTester.UI/appsettings.json`
|
||
3. `%LOCALAPPDATA%\SSPCTester\settings.json`
|
||
|
||
用户在设置页修改的路径、报告格式等,最终写到用户目录,不回写仓库里的 `appsettings.json`。
|
||
|
||
### 5.2 当前切换策略
|
||
|
||
当前采用 **每类设备独立切换**:
|
||
|
||
- `Devices:Sspc:UseMock`
|
||
- `Devices:PowerSupply:UseMock`
|
||
- `Devices:Load:UseMock`
|
||
- `Devices:Daq:UseMock`
|
||
|
||
`Devices:MockMode` 仅保留兼容旧配置用途,不应再作为新逻辑入口。
|
||
|
||
### 5.3 当前 `appsettings.json` 示例
|
||
|
||
```json
|
||
{
|
||
"Devices": {
|
||
"MockMode": false,
|
||
"Sspc": {
|
||
"UseMock": false,
|
||
"AutoConnect": true,
|
||
"PortName": "COM1",
|
||
"BaudRate": 9600,
|
||
"ModuleAddresses": [1, 2, 3]
|
||
},
|
||
"PowerSupply": {
|
||
"UseMock": false,
|
||
"AutoConnect": true,
|
||
"Host": "192.168.6.5",
|
||
"Port": 5025
|
||
},
|
||
"Load": {
|
||
"UseMock": false,
|
||
"AutoConnect": true,
|
||
"Host": "192.168.200.100",
|
||
"Port": 30000
|
||
},
|
||
"Daq": {
|
||
"UseMock": false,
|
||
"AutoConnect": true,
|
||
"LogicalDeviceId": 0,
|
||
"ChannelCount": 4,
|
||
"PreTriggerMs": 10,
|
||
"PostTriggerMs": 40
|
||
}
|
||
},
|
||
"Storage": {
|
||
"ProjectsRoot": "%LOCALAPPDATA%\\SSPCTester\\Projects"
|
||
},
|
||
"Report": {
|
||
"SavePath": "%LOCALAPPDATA%\\SSPCTester\\Reports",
|
||
"Format": "Pdf"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 设备接口
|
||
|
||
所有设备接口均为异步接口,默认支持 `CancellationToken`。
|
||
|
||
### 6.1 公共连接接口
|
||
|
||
```csharp
|
||
public interface IDeviceConnection
|
||
{
|
||
bool IsConnected { get; }
|
||
string DisplayName { get; }
|
||
Task<bool> ConnectAsync(CancellationToken ct = default);
|
||
Task DisconnectAsync();
|
||
event EventHandler<bool>? ConnectionChanged;
|
||
}
|
||
```
|
||
|
||
### 6.2 SSPC
|
||
|
||
```csharp
|
||
public interface ISspc : IDeviceConnection
|
||
{
|
||
int ChannelCount { get; }
|
||
Task TurnOnAsync(int physicalSlot, CancellationToken ct = default);
|
||
Task TurnOffAsync(int physicalSlot, CancellationToken ct = default);
|
||
Task FlashAsync(int physicalSlot, int milliseconds, CancellationToken ct = default);
|
||
Task<bool> GetChannelStateAsync(int physicalSlot, CancellationToken ct = default);
|
||
Task<ChannelMeasurement> ReadMeasurementAsync(int physicalSlot, CancellationToken ct = default);
|
||
Task<IReadOnlyList<ChannelMeasurement>> ReadMeasurementsAsync(CancellationToken ct = default);
|
||
}
|
||
```
|
||
|
||
### 6.3 电源
|
||
|
||
```csharp
|
||
public interface IPowerSupply : IDeviceConnection
|
||
{
|
||
Task SetVoltageAsync(double volts, CancellationToken ct = default);
|
||
Task SetCurrentLimitAsync(double amps, CancellationToken ct = default);
|
||
Task OutputAsync(bool on, CancellationToken ct = default);
|
||
Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default);
|
||
Task<string> QueryIdentityAsync(CancellationToken ct = default);
|
||
}
|
||
```
|
||
|
||
### 6.4 负载
|
||
|
||
```csharp
|
||
public readonly record struct PowerReading(double Volts, double Amps, double Watts);
|
||
|
||
public interface ILoad : IDeviceConnection
|
||
{
|
||
Task SetModeAsync(LoadMode mode, CancellationToken ct = default);
|
||
Task SetValueAsync(double value, CancellationToken ct = default);
|
||
Task InputAsync(bool on, CancellationToken ct = default);
|
||
Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default);
|
||
Task<PowerReading> ReadPowerAsync(CancellationToken ct = default);
|
||
Task<string> QueryIdentityAsync(CancellationToken ct = default);
|
||
}
|
||
```
|
||
|
||
### 6.5 DAQ
|
||
|
||
```csharp
|
||
public interface IDaq : IDeviceConnection
|
||
{
|
||
Task<IReadOnlyList<DaqDeviceDescriptor>> EnumerateDevicesAsync(CancellationToken ct = default);
|
||
Task<CurveData> CaptureAsync(int[] channels, int sampleRateHz, double durationSec, CancellationToken ct = default);
|
||
Task<double[]> ReadInstantAsync(int[] channels, CancellationToken ct = default);
|
||
Task<CurveData> CaptureAroundActionAsync(
|
||
DaqActionCaptureRequest request,
|
||
Func<CancellationToken, Task> action,
|
||
CancellationToken ct = default);
|
||
event EventHandler<CurveSample>? SampleReceived;
|
||
}
|
||
```
|
||
|
||
`CaptureAroundActionAsync()` 是当前曲线测试的核心接口:
|
||
先进入预采集,再在触发点附近执行开关动作,以得到统一对齐的波形。
|
||
|
||
---
|
||
|
||
## 7. 核心业务逻辑
|
||
|
||
### 7.1 测试模块统一入口
|
||
|
||
`Logic/Testing` 中已经落地:
|
||
|
||
```csharp
|
||
public interface ITestModule
|
||
{
|
||
string Name { get; }
|
||
Task RunAsync(TestContext ctx, IProgress<TestProgress> progress, CancellationToken ct);
|
||
}
|
||
```
|
||
|
||
`TestContext` 当前持有:
|
||
|
||
- `Project`
|
||
- `ISspc`
|
||
- `IPowerSupply`
|
||
- `ILoad`
|
||
- `IDaq`
|
||
|
||
`TestRunner` 依据 `Project.Selection` 串行执行模块。
|
||
|
||
### 7.2 浏览态 / 测试态
|
||
|
||
当前程序已实现两种应用状态:
|
||
|
||
- **浏览态**:左侧树显示全部项目,可自由切换
|
||
- **测试态**:
|
||
- 只显示当前项目
|
||
- 顶部展示“测试进行中”状态条
|
||
- 可中止测试
|
||
- 设备连接视为当前项目独占
|
||
|
||
这一逻辑由 `MainViewModel.AppState` 控制。
|
||
|
||
### 7.3 基础测试
|
||
|
||
当前基础测试实现采用 `ISspc.ReadMeasurementAsync()` 直接读取每路电压/电流:
|
||
|
||
```text
|
||
TurnOnAsync(ch)
|
||
-> Delay
|
||
-> ReadMeasurementAsync(ch)
|
||
-> 记录 VoltageOn / CurrentOn
|
||
-> TurnOffAsync(ch)
|
||
-> WaitForStableOffAsync(ch)
|
||
-> 根据导通和关断稳态结果判定 Pass / Fail
|
||
```
|
||
|
||
说明:
|
||
|
||
- 不是旧文档中的 `Daq.ReadInstant()` 方案
|
||
- “关断是否稳定”通过 `BasicTestCriteria.WaitForStableOffAsync()` 判定
|
||
|
||
### 7.4 曲线测试
|
||
|
||
当前代码存在两套相关逻辑:
|
||
|
||
1. **Logic 模块批处理**:`CurveTest.cs`
|
||
- 一次完成开通与关断两段采集
|
||
- 适用于测试模块整体执行
|
||
2. **UI 曲线页交互**:`CurveTestViewModel`
|
||
- 已拆成两个独立按钮:
|
||
- `开启曲线测试`
|
||
- `关断曲线测试`
|
||
- 开/关曲线各自保存、各自绘图、各自维护视窗范围
|
||
|
||
当前页与弹窗的“起点 / 终点 / 默认”已经拆成两套独立状态:
|
||
|
||
- `RisingViewStartMs` / `RisingViewEndMs`
|
||
- `FallingViewStartMs` / `FallingViewEndMs`
|
||
|
||
这些值会写回 `CurveResultRow` 并持久化。
|
||
|
||
状态规则:
|
||
|
||
- 两半都没做:`Untested`
|
||
- 只完成一半:`Measured`
|
||
- 两半都完成后:才根据 4 个指标判 `Pass / Fail`
|
||
|
||
### 7.5 功率损耗
|
||
|
||
当前正式实现采用:
|
||
|
||
- `UDP5080` 读取输入端 `Vin / Iin`
|
||
- `IT87xx` 读取输出端 `Vout / Iout / Pout`
|
||
|
||
这是当前在用方案。
|
||
仓库中仍保留旧的 `PowerTestLegacy`(DAQ 思路),但未作为正式模块注册。
|
||
|
||
### 7.6 保护功能
|
||
|
||
当前保护功能页和逻辑层是**演示框架**:
|
||
|
||
- UI 已完成
|
||
- 结果模型已完成
|
||
- `ProtectTest` 目前生成演示数据
|
||
- 未接真实故障触发与真实曲线采集链路
|
||
|
||
### 7.7 报告
|
||
|
||
当前报告支持:
|
||
|
||
- PDF
|
||
- Word
|
||
- PDF + Word
|
||
|
||
报告特性:
|
||
|
||
- 基于项目与勾选项动态生成
|
||
- 曲线插图由 `CurveImageRenderer` 生成
|
||
- 报告文件名不是固定 `report.docx`,而是动态解析后输出
|
||
- 报告页通过 `WebView2` 预览 PDF
|
||
|
||
---
|
||
|
||
## 8. 数据存储
|
||
|
||
### 8.1 项目目录
|
||
|
||
每个项目一个目录,目录根路径由 `Storage:ProjectsRoot` 决定。
|
||
典型内容:
|
||
|
||
- `project.json`
|
||
- `curves/`
|
||
- 生成出的 PDF / Word 报告
|
||
|
||
### 8.2 项目对象
|
||
|
||
`Project` 当前持久化内容包括:
|
||
|
||
- DUT 信息
|
||
- 测试站点信息
|
||
- 测试项勾选
|
||
- 报告配置
|
||
- 基础测试、曲线测试、功率损耗、保护功能结果
|
||
- 曲线阈值
|
||
- 四页测试说明文本
|
||
|
||
### 8.3 波形存储格式
|
||
|
||
波形不是简单的 JSON,而是:
|
||
|
||
- `curves/*.bin`:float32 二进制采样数据
|
||
- `curves/*.json`:元数据
|
||
|
||
元数据包含:
|
||
|
||
- 采样率
|
||
- 触发点位置
|
||
- 通道角色/名称/单位
|
||
- 标定参数
|
||
|
||
### 8.4 旧数据兼容
|
||
|
||
`CurveResultRow` 中保留了旧字段:
|
||
|
||
- `ViewStartMs`
|
||
- `ViewEndMs`
|
||
|
||
新逻辑优先使用:
|
||
|
||
- `RisingViewStartMs / RisingViewEndMs`
|
||
- `FallingViewStartMs / FallingViewEndMs`
|
||
|
||
缺失时会回退到旧字段,兼容老项目。
|
||
|
||
---
|
||
|
||
## 9. 运行与发布
|
||
|
||
### 9.1 本地运行
|
||
|
||
推荐命令:
|
||
|
||
```powershell
|
||
PowerShell -ExecutionPolicy Bypass -File .\run.ps1
|
||
```
|
||
|
||
该脚本默认行为:
|
||
|
||
1. `dotnet publish` UI 到 `publish\win-x64`
|
||
2. 可选执行测试
|
||
3. 可选执行种子数据生成
|
||
4. 直接启动 `publish\win-x64\SSPCTester.UI.exe`
|
||
|
||
### 9.2 常用参数
|
||
|
||
- `-Test`:执行 `SSPCTester.Tests`
|
||
- `-Seed`:执行 `SSPCTester.Seed`
|
||
- `-CleanSeed`:清空项目目录后重新灌入种子数据
|
||
- `-NoLaunch`:只发布不启动
|
||
|
||
### 9.3 发布
|
||
|
||
推荐命令:
|
||
|
||
```powershell
|
||
PowerShell -ExecutionPolicy Bypass -File .\publish.ps1
|
||
```
|
||
|
||
当前默认发布参数是:
|
||
|
||
- `Release`
|
||
- `win-x64`
|
||
- `SelfContained = true`
|
||
- `SingleFile = false`
|
||
|
||
说明:
|
||
|
||
- 当前脚本明确没有走 WPF 单文件发布默认方案
|
||
- 发布时会检查 `ACTS1000_64.dll` 是否存在
|
||
|
||
### 9.4 运行环境
|
||
|
||
源码构建至少需要:
|
||
|
||
- Windows x64
|
||
- .NET 8 SDK x64
|
||
- WebView2 Runtime
|
||
|
||
若只运行现成发布版,则至少需要满足发布产物依赖和本机驱动/DLL 条件。
|
||
|
||
---
|
||
|
||
## 10. 当前状态与后续工作
|
||
|
||
### 10.1 已完成
|
||
|
||
- WPF 主程序骨架、主题资源、页面导航
|
||
- 浏览态 / 测试态切换
|
||
- 项目新建、加载、保存
|
||
- 基础测试、曲线测试、功率损耗、保护功能页
|
||
- PDF / Word 报告生成
|
||
- 真/假设备可按设备粒度切换
|
||
- 实际 SSPC / 电源 / 负载 / PCIe8586 驱动接入代码
|
||
|
||
### 10.2 待统一 / 待完善
|
||
|
||
- 保护功能仍为演示逻辑,未接真实测试链路
|
||
- 功率损耗旧版 `PowerTestLegacy` 仍在仓库,后续应明确去留
|
||
- 曲线弹窗窗口已实现,但当前主页面尚未提供入口按钮
|
||
- 报告生成对新旧曲线视窗字段的使用口径后续建议统一
|
||
|
||
---
|
||
|
||
## 11. 风险与注意事项
|
||
|
||
1. **DAQ 仍是最高风险点**:厂商 DLL、设备 ID、采样参数、输入量程和触发时序都依赖现场。
|
||
2. **Modbus 地址和端口仍需现场核实**:虽然当前实现已支持 3 模块映射,但设备地址/串口号应视现场配置为准。
|
||
3. **电源与负载真实驱动已接入,但控制命令支持度需按实物验证**。
|
||
4. **所有长流程应贯穿 `CancellationToken`**,中止后优先保证设备回到安全状态。
|
||
5. **示波器不纳入软件控制**,仅作为线下校准基准。
|
||
6. **任何涉及数据模型持久化格式的修改,都要考虑旧项目兼容**。
|
||
|
||
---
|
||
|
||
## 12. 维护建议
|
||
|
||
1. 改接口前,先同步修改 `Devices.Interfaces`、实现类、DI 装配和本文档。
|
||
2. 改项目结构前,先检查 `run.ps1`、`publish.ps1`、`Seed`、测试项目是否受影响。
|
||
3. 改曲线页前,注意当前已经有“主页面 + 弹窗 + 报告图”的三处视窗相关逻辑。
|
||
4. 改报告前,确认 `ReportViewModel`、`Logic/Report`、`WebView2` 预览链路同时保持可用。
|
||
5. 若发现文档内容再次与代码偏离,应优先更新本文档,而不是继续在过时约束上开发。
|