SeatFlow.Infrastructure 层的数据提供者、导出器、仓库、布局构建器和文件迁移系统
本页目录
全部文档
Infrastructure 层详解
SeatFlow.Infrastructure 是架构中负责数据访问和外部 I/O 的基础设施层。它实现了 Core 层定义的数据提供者接口,提供多种数据格式支持、导出能力和文件版本迁移。
数据提供者
学生数据可以从多种文件格式加载,统一的接口 IStudentProvider 定义在 SeatFlow.Core 中。
提供者层次
IStudentProvider (Core 接口)
├── CsvStudentProvider ← CSV 文件读取
├── XlsxStudentProvider ← XLSX 文件读取 (EPPlus 8)
├── JsonStudentProvider ← JSON 文件读取
├── CompositeStudentProvider ← 组合模式:按优先级链式尝试各提供者
└── InMemoryStudentProvider ← 内存数据(用于引导系统注入)
CompositeStudentProvider
CompositeStudentProvider 是默认注册的主 IStudentProvider。它内部维护一个提供者列表,按注册顺序依次尝试——每个提供者检查文件扩展名是否匹配,匹配则尝试解析。这使得系统可以自动识别任意受支持的格式。
模糊字段匹配(FuzzyColumnMatcher)
FuzzyColumnMatcher (SeatFlow.Infrastructure.Providers) 是一个内部静态类,为 CSV/XLSX 导入提供智能字段识别能力。它不依赖固定模板格式,而是通过扫描单元格内容自动推测数据结构。
工作流程:
- 扫描二维单元格网格,使用
StudentDataMapping.ResolveProperty()匹配已知字段名 - 按命中位置推断布局方向(列式 Columnar / 行式 RowBased)
- 标准模板快速路径:字段全在第 0 行且无重复 → 直接走原有解析逻辑
- 非标准布局:定位数据起始位置 → 按列/行读取,含双列聚合、合并格扩展、2-连续空终止
支持的场景:
- 字段不在第 1 行(任意行位置均可)
- 行式布局(字段在首列,数据向右延伸)
- 双列/多列名单(同一字段名出现多次 → 自动聚合)
- 合并单元格(XLSX)→ 字段值扩展到整个合并范围
- 数据间隔的空行 → 连续 ≥2 个空单元格视为当前字段数据结束
- 不对称双列名单(如
|姓名|性别|姓名|需要前排|)→ 按字段列的空间位置自动分组
双列名单空间分组算法:
以重复次数最多的字段列作为锚点划定组边界。例如 |姓名|性别|姓名|需要前排|:
锚点: Name 位于列 [0, 2]
组边界: Group 0 = [0, 2) 即列 0-1, Group 1 = [2, ∞) 即列 2+
分配: Gender(列1) ∈ [0,2) → Group 0; NeedsFrontRow(列3) ∈ [2,∞) → Group 1
结果: G0=(Name[0],Gender[1]) G1=(Name[2],NeedsFrontRow[3])
同样算法适用于行式布局——以重复行号作为锚点。FindGroupIndex(pos, anchors) 方法为列/行通用。
学生数据集导入流程
用户选择文件 → IFileService.OpenFileAsync()
→ ApplicationFacade.ImportStudentsAsync(filePath)
→ CompositeStudentProvider 尝试解析
→ CsvStudentProvider / XlsxStudentProvider:
→ 构建二维网格 → FuzzyColumnMatcher.TryParse()
→ 标准模板 → 快速路径;非标准 → 模糊解析
→ JsonStudentDatasetRepository 创建数据集
→ 数据集记录在 {AppData}/Rosters/{id}.roster.json
数据写入器
支持将学生数据写回文件:
| 写入器 | 格式 | 用途 |
|---|---|---|
JsonStudentWriter |
JSON | 数据集保存 |
CsvStudentWriter |
CSV | 导出模板 |
XlsxStudentWriter |
XLSX | 导出模板(EPPlus 8) |
导出器
排座结果可导出为多种格式,统一实现 ISeatingPlanExporter 接口:
| 导出器 | 格式 | 技术 |
|---|---|---|
ExcelSeatingExporter |
XLSX | EPPlus 8 |
CsvSeatingExporter |
CSV | 标准 .NET I/O |
PdfSeatingExporter |
QuestPDF | |
ImageSeatingExporter |
图片 | Avalonia 渲染 |
布局构建器
将布局定义转换为座位实体集合:
| 构建器 | 布局类型 | 说明 |
|---|---|---|
GridLayoutBuilder |
Grid | 网格布局(行列),按行主序创建座位 |
PolarLayoutBuilder |
Polar | 极坐标布局(环形、扇形) |
FreeformLayoutBuilder |
Freeform | 自由点布局 |
Grid 座位排序
GridLayoutBuilder.BuildGrid 以行主序创建座位(外层循环:行,内层循环:列)。这确保 RandomFillStrategy 逐行(从左到右,从上到下)填充座位。对于不规则的 ColumnRowCounts 网格,maxRows = ColumnRowCounts.Max(),每列检查 r <= rowsForCol。
仓库
仓库列表
| 仓库 | 存储位置 | 用途 |
|---|---|---|
JsonVenueRepository |
{AppData}/Venues/*.venue.json |
会场定义 CRUD |
JsonAppSettingsRepository |
{AppData}/AppSettings.json |
应用设置读写 |
StrategyConfigFileRepository |
{AppData}/StrategyConfig/ |
策略配置持久化 |
SeatingSnapshotRepository |
{AppData}/Assignments/{venueId}/{date}/ |
排座快照管理 |
JsonStudentDatasetRepository |
{AppData}/Rosters/*.roster.json |
学生数据集管理 |
持久化文件结构和版本
| 文件类型 | 版本 | 位置 | 包装类 |
|---|---|---|---|
| Venue | 1.1 | {data}/Venues/*.venue.json |
VenueFile |
| Roster | 1.1 | {data}/Rosters/*.roster.json |
RosterFile |
| Snapshot | 1.0 | {data}/Assignments/{venueId}/{date}/*.json |
SeatingSnapshot |
| VenueInfo | 1.0 | {data}/Assignments/{venueId}/_venue.json |
VenueSnapshotInfo |
| AppSettings | 1.0 | {data}/AppSettings.json |
AppSettings |
| StrategyConfig | 1.0 | {data}/StrategyConfig/{strategyId}.config.json |
StrategyConfig |
| StrategyDatasetConfig | 1.0 | {data}/StrategyConfig/{strategyId}/*.config.json |
StrategyDatasetConfig |
| Seatsets | 1.0 | {data}/Seatsets/*.seatsets |
SeatsetsFile |
内容哈希
VenueFile.ContentHash 和 RosterFile.ContentHash 使用 SHA256 哈希,在保存时计算:
- 序列化(跳过
ContentHash字段) - 计算哈希 → 设置
ContentHash - 重新序列化(包含
ContentHash)
学生数据集哈希排除 importedAt/originalFileName(不稳定的时间戳)。
文件迁移系统
所有持久化 JSON 文件在加载时通过自动迁移确保向前兼容。
迁移管道
加载文件 → 作为 JsonNode 读取
→ 提取 version 字段
→ FileMigrationService.Migrate(fileType, node, fileVersion, targetVersion)
→ 反序列化为目标类型
迁移是仅向前的——不支持版本回滚。
添加新迁移
- 在
Migration/Migrators/{FileType}Migrators.cs中添加嵌套类:
public static class VenueMigrators
{
public sealed class Step_1_0_to_1_1 : IFileMigrator
{
public string FileType => "venue";
public string FromVersion => "1.0";
public string ToVersion => "1.1";
public JsonNode Migrate(JsonNode root)
{
// 迁移逻辑:操作 JsonNode 后返回
return root;
}
}
}
- 在
ServiceCollectionExtensions.cs中注册:services.AddSingleton<IFileMigrator, VenueMigrators.Step_1_0_to_1_1>() - 在
file_versions.json中提升对应版本号 - 在
Core/Models/对应模型类中更新默认Version属性 - 添加测试
现有迁移
VenueMigrators.Step_1_0_to_1_1— 将 Grid 布局座位从列主序重排为行主序(先按Row排序,再按Column排序)
版本管理文件
SeatFlow.Infrastructure/Migration/file_versions.json 是嵌入资源,编译到程序集中。FileVersionInfo.GetCurrentVersion(fileType) 在运行时读取最新版本。scripts/version.py 脚本可统一管理各文件版本。
SeatSets 服务
SeatSets 是 SeatFlow 的数据包格式,用于打包和传输完整排座场景:
- 打包:将会场、数据集和策略配置打包为
.seatsets文件 - 解包:从
.seatsets文件还原场景 - 校验:验证数据包的完整性和格式正确性
- 自动导入:扫描指定目录自动导入数据包
序列化
JSON 字段约定
- 使用
JsonNamingPolicy.CamelCase— JSON 中所有字段为小写起始 ClassroomLayoutDefinition.LayoutType序列化为 同时 包含数字(layoutType: 0=Grid, 1=Polar, 2=Freeform)和字符串(layoutTypeString: "Grid"/"Polar"/"Freeform")- 迁移器应优先读取
layoutTypeString以保持清晰
Seat 多态序列化
SeatJsonConverter 实现座位对象的多态序列化/反序列化:
{
"Type": "Grid", // 鉴别器(大写 T,字符串)
"id": "S001",
"row": 1,
"column": 1,
"type": 0, // SeatType 枚举值(小写,数字)
"isAvailable": true,
"isFixed": false,
"occupantId": null
}
Type(大写 T)是字符串鉴别器:"Grid"/"Polar"/"Freeform"type(小写)是SeatType枚举的 JSON 整数值- 反序列化时根据
Type字段创建对应的派生类实例
快照会场布局嵌入
快照在创建时将完整的 ClassroomLayoutDefinition 序列化到 Metadata["venueLayout"]。预览时优先读取嵌入布局;旧快照回退到加载会场文件。这使得快照自包含——编辑或删除会场文件不会破坏现有快照预览。