CSV 导入导出实现
约 975 字大约 3 分钟
2026-03-20
1. 背景与目标
CSV 功能是 SleepIn 的“批量数据入口/出口”。
- 导入:把外部文件转换为课程表或作息表数据。
- 导出:把当前课程表或作息表转为可分享的 CSV 文本。
- 核心挑战是“扁平文本行”到领域模型(课程-课时、作息-课节)的映射。
2. 相关模块与文件位置(先看树)
app/src/main/java/com/kurosu/sleepin/
├─ data/csv/
│ ├─ CsvCodec.kt # CSV 行编解码
│ ├─ CsvImporter.kt # 课程表解析 + 行级校验
│ ├─ CsvExporter.kt # 课程表导出文本生成
│ ├─ ScheduleCsvImporter.kt # 作息表解析 + 行级校验
│ └─ ScheduleCsvExporter.kt # 作息表导出文本生成
├─ domain/usecase/csv/
│ ├─ ImportCsvUseCase.kt # 导入主流程
│ ├─ ExportCsvUseCase.kt # 导出主流程
│ └─ CsvImportModels.kt # CsvCourseRow/CsvImportReport 等
├─ domain/usecase/schedule/
│ ├─ ImportScheduleCsvUseCase.kt # 作息表 CSV 导入主流程
│ ├─ ExportScheduleCsvUseCase.kt # 作息表 CSV 导出主流程
│ └─ ScheduleCsvModels.kt # ScheduleCsvImportReport 等
├─ ui/screen/timetable/TimetableEditorViewModel.kt # 课程表 CSV 触发入口
└─ ui/screen/schedule/ScheduleEditorViewModel.kt # 作息表 CSV 触发入口3. 导入主流程(总览)
sequenceDiagram
participant UI as TimetableEditorScreen
participant VM as TimetableEditorViewModel
participant UC as ImportCsvUseCase
participant Importer as CsvImporter
participant Repo as CourseRepository
UI->>VM: 选择 CSV 并确认导入
VM->>UC: invoke(timetableId, totalWeeks, maxPeriod, rawCsv)
UC->>Importer: parse(rawCsv,...)
Importer-->>UC: rows + errors
UC->>UC: groupBy(courseName) 组装 Course + Sessions
UC->>Repo: saveCourseWithSessions(...)
UC-->>VM: CsvImportReport
VM-->>UI: 显示导入统计与错误详情4. 关键实现细节
4.1 withContext(Dispatchers.IO)
ImportCsvUseCase 在 IO 线程执行解析与持久化,避免阻塞主线程。
文件:app/src/main/java/com/kurosu/sleepin/domain/usecase/csv/ImportCsvUseCase.kt
4.2 扁平行到一对多模型
- CSV 每行通常表示一次上课时段。
- 导入时按课程名分组:同名行聚合为 1 个
Course+ N 个CourseSession。
这一步由 ImportCsvUseCase 通过 groupBy { it.name.trim().lowercase() } 完成。
4.3 周次规则解析
CsvImporter.kt 支持新旧两类周次表达:
- 新格式:
周次/Weeks(如1-16、1;3;5、1-16(odd))。 - 旧格式:
WeekType + StartWeek + EndWeek + CustomWeeks兼容解析。
目标是兼容历史文件并减少用户迁移成本。
4.4 Domain 保持无 Android 依赖
- UseCase 输入是纯文本
rawCsv,不是Uri。 - 读取
Uri与ContentResolver的逻辑放在 UI 层。
这保证 Domain 可以被独立测试。
5. 导出主流程(总览)
sequenceDiagram
participant VM as TimetableEditorViewModel
participant UC as ExportCsvUseCase
participant Repo as CourseRepository + TimetableRepository
participant Exporter as CsvExporter
VM->>UC: exportCsvUseCase(timetableId)
UC->>Repo: 读取课表与课程数据
UC->>Exporter: 组装 CSV 文本
UC-->>VM: csvString
VM-->>UI: ExportCsvReady(fileName, content)6. 常见问题与排错
6.1 导入完成但条目为 0
- 先检查
CsvImportReport.errors。 - 重点看周次字段、节次范围是否超出
maxPeriod。
6.2 导入后课程重复
- 当前策略是“追加插入”,不是“覆盖导入”。
- 若业务需要幂等导入,需要额外设计去重策略。
6.3 文件解析乱码
- UI 层当前采用“UTF-8 优先 + 中文编码兜底(GB18030 / GBK / GB2312 / Big5 + BOM UTF-16)”解码策略。
- 若仍出现乱码,优先核对 CSV 首行表头是否完整,以及文件是否被编辑器二次破坏编码。
7. 作息表 CSV 导入导出链路
7.1 导入链路(仅新建页)
sequenceDiagram
participant UI as ScheduleEditorScreen
participant VM as ScheduleEditorViewModel
participant UC as ImportScheduleCsvUseCase
participant Importer as ScheduleCsvImporter
participant SaveUC as SaveScheduleUseCase
UI->>VM: 选择 CSV 并点击导入
VM->>UC: invoke(rawCsv)
UC->>Importer: parse(rawCsv)
Importer-->>UC: rows + errors
UC->>SaveUC: saveScheduleUseCase(null, name, null, periodDrafts)
UC-->>VM: ScheduleCsvImportReport
VM-->>UI: Snackbar + CSV 错误详情弹窗关键文件:
app/src/main/java/com/kurosu/sleepin/domain/usecase/schedule/ImportScheduleCsvUseCase.ktapp/src/main/java/com/kurosu/sleepin/data/csv/ScheduleCsvImporter.ktapp/src/main/java/com/kurosu/sleepin/ui/screen/schedule/ScheduleEditorViewModel.kt
7.2 导出链路(编辑页)
ScheduleEditorViewModel.exportCsvForEditingSchedule() -> ExportScheduleCsvUseCase -> GetScheduleDetailUseCase -> ScheduleCsvExporter。
关键文件:
app/src/main/java/com/kurosu/sleepin/domain/usecase/schedule/ExportScheduleCsvUseCase.ktapp/src/main/java/com/kurosu/sleepin/data/csv/ScheduleCsvExporter.kt
7.3 行为约束(必须同步理解)
- 只允许在新建作息表页面导入(
scheduleId == null)。 - 单文件只允许一个作息表名称(
ScheduleCsvImporter会校验)。 - 导入策略为“始终新建”,不会覆盖已存在作息表。
7.4 作息表 CSV 常见错误
- 多个作息表名称混在一个文件:返回
CSV must contain exactly one schedule name。 - 时间格式不是
HH:mm:返回Start/End time format must be HH:mm。 - 课节时间重叠或节次重复:由
SaveScheduleUseCase返回校验错误。
8. 延伸阅读与下一步
- Widget 刷新与导入后的联动:
docs/SleepIn-Docs/docs/dev/2.architecture/5.widget-workmanager.md - 全局排错:
docs/SleepIn-Docs/docs/dev/4.others/1.troubleshooting.md
更新日志
2026/5/18 12:59
查看所有更新日志
2a526-docs(readme): update screenshot layout于
