15 KiB
Design Document
Overview
随工水印相机 2.0.1 客户端是一个 Windows 桌面应用程序,使用 WinForms + .NET 8 开发。主要功能包括:
- 主页统计信息展示
- 工作记录管理(查询、新增、编辑、删除、导出)
- 月报表查看与导出
- 照片 ZIP 下载
- 通用功能(登录管理、网络处理、设置、日志)
核心设计原则:所有导出功能在客户端本地执行,减轻服务器压力。
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ CS Client (WinForms) │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ MainForm │ │ WorkRecord │ │ MonthReport │ │
│ │ (主页) │ │ Form │ │ Form │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ┌──────┴────────────────┴────────────────┴──────┐ │
│ │ Services Layer │ │
│ ├───────────────────────────────────────────────┤ │
│ │ ApiService │ ExportService │ ConfigService │ │
│ │ ImageService│ LogService │ AuthService │ │
│ └───────────────────────────────────────────────┘ │
│ │ │ │ │
│ ┌──────┴────────────────┴────────────────┴──────┐ │
│ │ Models Layer │ │
│ ├───────────────────────────────────────────────┤ │
│ │ WorkRecord │ MonthlyReport │ Statistics │ │
│ │ ExportTask │ AppConfig │ ApiResponse │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Backend Server │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ /business/CamWorkrecord/* │ /business/CamWorkers/* ││
│ │ /api/workrecord/statistics │ /api/workrecord/monthImages ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
Components and Interfaces
1. Forms (UI Layer)
MainForm - 主页窗体
public class MainForm : Form
{
// 统计信息显示
void LoadStatistics();
void DisplayStatistics(StatisticsDto data);
// 导航
void NavigateToWorkRecord();
void NavigateToMonthReport();
void NavigateToMigration();
}
WorkRecordForm - 工作记录管理窗体
public class WorkRecordForm : Form
{
// 查询
void Search(WorkRecordQueryDto query);
void ResetQuery();
void LoadPage(int pageNum);
// CRUD
void ShowAddDialog();
void ShowEditDialog(int recordId);
void ShowDetailDialog(int recordId);
void DeleteRecord(int recordId);
// 导出
void ExportAll();
void ExportSelected(List<int> ids);
// 多选
void SelectAll();
void ClearSelection();
List<int> GetSelectedIds();
}
MonthReportForm - 月报表窗体
public class MonthReportForm : Form
{
// 查询
void Search(MonthReportQueryDto query);
void ResetQuery();
// 导出
void ExportExcel();
void DownloadPhotosZip();
}
ImageViewerForm - 图片查看器窗体
public class ImageViewerForm : Form
{
// 图片列表
List<string> ImageUrls { get; set; }
int CurrentIndex { get; set; }
// 操作
void ShowImage(int index);
void NextImage();
void PreviousImage();
void ZoomIn();
void ZoomOut();
void ResetZoom();
}
2. Services Layer
ApiService - API 通信服务
public interface IApiService
{
// 认证
Task<LoginResult> LoginAsync(string username, string password);
Task<bool> RefreshTokenAsync();
// 统计
Task<StatisticsDto> GetStatisticsAsync();
// 工作记录
Task<PagedResult<WorkRecordDto>> GetWorkRecordsAsync(WorkRecordQueryDto query);
Task<WorkRecordDto> GetWorkRecordAsync(int id);
Task<int> AddWorkRecordAsync(WorkRecordDto record);
Task UpdateWorkRecordAsync(WorkRecordDto record);
Task DeleteWorkRecordAsync(int id);
// 月报表
Task<List<MonthlyReportDto>> GetMonthlyReportAsync(MonthReportQueryDto query);
Task<List<MonthImageDto>> GetMonthImagesAsync(string yearMonth);
}
ExportService - 导出服务
public interface IExportService
{
// 工作记录导出
Task ExportWorkRecordsAsync(
WorkRecordQueryDto query,
string outputPath,
IProgress<ExportProgress> progress,
CancellationToken cancellationToken);
Task ExportWorkRecordsByIdsAsync(
List<int> ids,
string outputPath,
IProgress<ExportProgress> progress,
CancellationToken cancellationToken);
// 月报表导出
Task ExportMonthlyReportAsync(
List<MonthlyReportDto> data,
string outputPath);
// 照片ZIP下载
Task DownloadPhotosZipAsync(
string yearMonth,
string outputPath,
IProgress<DownloadProgress> progress,
CancellationToken cancellationToken);
}
ImageService - 图片服务
public interface IImageService
{
// 下载
Task<byte[]> DownloadImageAsync(string url);
Task DownloadImagesAsync(
List<string> urls,
string outputDir,
int concurrency,
IProgress<int> progress,
CancellationToken cancellationToken);
// 处理
byte[] CompressImage(byte[] imageData, int quality);
byte[] ResizeImage(byte[] imageData, int width, int height);
}
ConfigService - 配置服务
public interface IConfigService
{
AppConfig LoadConfig();
void SaveConfig(AppConfig config);
// 登录凭证
void SaveCredentials(string serverUrl, string token);
(string serverUrl, string token)? LoadCredentials();
void ClearCredentials();
}
LogService - 日志服务
public interface ILogService
{
void Info(string message);
void Error(string message, Exception ex = null);
void Warn(string message);
Task ExportLogsAsync(string outputPath);
}
3. Models Layer
// 工作记录
public class WorkRecordDto
{
public int Id { get; set; }
public string DeptName { get; set; }
public DateTime RecordTime { get; set; }
public string Longitude { get; set; }
public string Latitude { get; set; }
public string Address { get; set; }
public string Content { get; set; }
public string StatusName { get; set; }
public List<string> Workers { get; set; }
public List<ImageDto> Images { get; set; }
public string Remarks { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
// 月报表
public class MonthlyReportDto
{
public string YearMonth { get; set; }
public string DeptName { get; set; }
public string WorkerName { get; set; }
public int WorkDays { get; set; }
}
// 统计信息
public class StatisticsDto
{
public int MonthRecordCount { get; set; }
public int MonthImageCount { get; set; }
public int MonthWorkerCount { get; set; }
public int TodayRecordCount { get; set; }
public int TotalRecordCount { get; set; }
public int PendingMigrationCount { get; set; }
}
// 导出进度
public class ExportProgress
{
public int TotalRecords { get; set; }
public int ProcessedRecords { get; set; }
public int TotalImages { get; set; }
public int DownloadedImages { get; set; }
public string Status { get; set; }
}
// 应用配置
public class AppConfig
{
public string DefaultSavePath { get; set; }
public int ImageDownloadConcurrency { get; set; } = 5;
public int ImageCompressQuality { get; set; } = 50;
public bool AutoCleanTempFiles { get; set; } = true;
}
Data Models
数据库表结构(后端已有)
cam_workrecord (工作记录表)
├── Id (int, PK)
├── DeptName (varchar)
├── RecordTime (datetime)
├── Longitude (varchar)
├── Latitude (varchar)
├── Address (varchar)
├── Content (varchar)
├── StatusName (varchar)
├── ImageUrl (varchar) - 封面图
├── Remarks (text)
├── CreateTime (datetime)
└── UpdateTime (datetime)
cam_workrecord_image (工作记录图片表)
├── Id (int, PK)
├── WorkrecordId (int, FK)
├── ImageUrl (varchar)
├── SortOrder (int)
├── CreateTime (datetime)
└── UpdateTime (datetime)
cam_worker (施工人员表)
├── Id (int, PK)
├── WorkrecordId (int, FK)
├── WorkerName (varchar)
├── CreateTime (datetime)
└── UpdateTime (datetime)
客户端本地存储
%AppData%/WorkCameraExport/
├── config.json # 应用配置
├── credentials.dat # 加密的登录凭证
├── logs/
│ ├── app_2025-01-05.log
│ └── error_2025-01-05.log
└── temp/ # 临时文件目录
└── export_xxx/ # 导出临时目录
Correctness Properties
A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
Property 1: 图片显示数量限制
For any 工作记录,如果图片数量大于 3,则列表中显示的缩略图数量应等于 3,且徽章显示的数字应等于总图片数减 3。 Validates: Requirements 3.1, 3.2
Property 2: 图片轮播索引边界
For any 图片列表,当前索引应始终在 [0, 图片数量-1] 范围内。点击下一张时,如果当前是最后一张则循环到第一张;点击上一张时,如果当前是第一张则循环到最后一张。 Validates: Requirements 3.6
Property 3: 图片上传数量限制
For any 上传操作,图片列表长度应始终不超过 15。当尝试添加图片使总数超过 15 时,应拒绝添加并提示用户。 Validates: Requirements 4.5
Property 4: 图片删除后列表更新
For any 图片删除操作,删除后图片列表长度应减少 1,且被删除的图片不应存在于列表中。 Validates: Requirements 4.6
Property 5: 导出数据完整性
For any 导出操作,导出的 Excel 文件中的记录数应等于查询结果的记录数(导出全部)或选中的记录数(导出所选)。 Validates: Requirements 6.1, 6.2
Property 6: 分页数据获取
For any 分页查询,每页返回的数据量应不超过 pageSize(50),且所有页的数据合并后应等于总记录数。 Validates: Requirements 6.3
Property 7: 并发下载控制
For any 图片下载任务,同时进行的下载数应不超过配置的并发数(默认 5)。 Validates: Requirements 6.4
Property 8: Excel 图片布局
For any 导出的 Excel 文件,每个单元格中的图片应按水平排列,图片尺寸应为 100x60 像素。 Validates: Requirements 6.6, 6.7
Property 9: ZIP 目录结构
For any 下载的照片 ZIP 文件,解压后的目录结构应包含:当日照片/、参与人员/{人员姓名}/、工作内容/{工作内容}/、部门/{部门名称}/ 四个分类目录。 Validates: Requirements 9.2, 9.3
Property 10: Token 自动刷新
For any API 请求,如果 Token 即将过期(剩余时间小于阈值),应在请求前自动刷新 Token。 Validates: Requirements 10.3
Property 11: 请求重试机制
For any 失败的 API 请求,应自动重试,重试次数不超过 3 次。 Validates: Requirements 11.2
Property 12: 配置持久化
For any 配置修改操作,保存后重新加载配置应得到相同的值。 Validates: Requirements 12.2
Property 13: 日志记录
For any 用户操作,应在日志文件中记录操作信息,包含时间戳和操作类型。 Validates: Requirements 12.3
Error Handling
网络错误
- 连接超时:显示"网络连接超时,请检查网络设置",自动重试 3 次
- 服务器错误(5xx):显示"服务器繁忙,请稍后重试"
- 认证失败(401):跳转到登录界面
业务错误
- 数据不存在(404):显示"数据不存在或已被删除"
- 参数错误(400):显示具体的错误信息
- 权限不足(403):显示"您没有权限执行此操作"
导出错误
- 磁盘空间不足:显示"磁盘空间不足,请清理后重试"
- 文件被占用:显示"文件被其他程序占用,请关闭后重试"
- 图片下载失败:记录失败的图片,继续导出其他内容,最后提示用户
Testing Strategy
单元测试
- 使用 xUnit 测试框架
- 测试 Services 层的业务逻辑
- 测试 Models 的数据验证
- Mock 外部依赖(API、文件系统)
属性测试
- 使用 FsCheck 进行属性测试
- 每个属性测试运行 100 次迭代
- 测试边界条件和随机输入
集成测试
- 测试 API 通信
- 测试文件导出功能
- 测试配置持久化
UI 测试
- 手动测试 UI 交互
- 测试窗体导航
- 测试表单验证