startup-plan/开发计划/ios-projects/[进行中]-缺失项与待补全方向.md
2026-05-15 17:29:57 +08:00

36 KiB
Raw Blame History

缺失项与待补全方向

基于 v0.1 创业计划文档与当前 iOS 代码对比分析 整理时间2026-05-10

本文档系统性列出知习 iOS App 当前在架构、页面、功能、设计等方面的缺失项,并给出优先级建议。


一、架构层缺失

1.1 MVVM 分层

现状:全部代码写在 SwiftUI View 中,无任何 ViewModel/ObservableObject/@Published。grep 搜索 ViewModel、ObservableObject、@Published 均为零结果。

缺失

  • 无 ViewModel 层,业务逻辑、状态管理、数据转换全部堆在 View 里
  • 无 Model 层,数据结构通过 View 内的局部 struct 或硬编码数据隐式定义
  • 代码不可测试,无法单独验证业务逻辑

计划要求官网与技术基础.md 第 5.3 节):

AIStudyApp/
├── Features/
│   ├── Onboarding/
│   │   ├── Views/       ← 当前有,但无 ViewModel/Model 子目录
│   │   ├── ViewModels/  ← 缺失
│   │   └── Models/      ← 缺失

1.2 Service 层

现状:无任何 Service 类AI 分析、学习记录、用户管理等概念没有对应的服务抽象。

缺失

Service 职责 设计文档
AuthService Apple 登录、Token 管理、会话维护 docs/AI对话.md(详细设计)
LearningService 学习记录 CRUD、进度追踪
AIService AI 分析请求代理、结果解析
ReviewService 复习任务生成、调度
KnowledgeService 知识库/路径/课程查询
FeedbackService 用户反馈提交

Auth 模块已有详细文件清单(来自 docs/AI对话.md

Features/Auth/Views/LoginView.swift
Features/Auth/ViewModels/LoginViewModel.swift

Core/Services/AuthService.swift
Core/Services/AuthServiceProtocol.swift

Core/Storage/KeychainStore.swift
Core/Storage/TokenStore.swift

App/AppSession.swift

Core/Models/AuthModels.swift
Core/Models/User.swift

1.3 Repository 层

现状:零数据持久化,所有"数据"均为 View 中硬编码的 mock。

缺失

  • 无数据访问抽象(未来可能切换 CoreData → API需要 Repository 隔离)
  • 无本地缓存层
  • 无网络数据源层

1.4 网络层

现状:无任何网络请求代码,无 APIClient无 URLSession 调用。

缺失

  • APIClient封装 URLSession注入 baseURL、header、token
  • APIEndpoint枚举化 API 路径,统一请求构建)
  • APIError统一错误模型和处理
  • 请求/响应拦截器日志、token 刷新)
  • Mock 层(本地开发和 UI 预览用)

1.5 依赖注入

现状:无任何 DI 模式Service 和 ViewModel 尚未创建,暂时不存在注入问题。但需要在架构搭建时建立模式。

建议:初期使用构造函数注入 + @EnvironmentObject,避免引入第三方 DI 框架。


二、核心能力缺失

2.1 Sign in with Apple已有详细设计文档

现状LoginPage 有 UI手机号/邮箱/微信/Apple 入口),但 AIStudyAppApp 仅用 @AppStorage("hasCompletedOnboarding") 控制是否进入主界面,无实际认证。

计划要求:第一版登录方式仅为 Sign in with AppleDemo与MVP.md 第 5.2 节)。

详细设计方案docs/AI对话.md,核心结论:

登录页只保留一个入口

Sign in with Apple

删除的入口:手机号登录、邮箱登录、微信登录、验证码登录

"跳过"按钮处理

#if DEBUG
Button("跳过,进入演示模式") { ... }
#endif

正式环境不展示跳过入口,避免后续匿名用户迁移问题。

登录流程设计

App 启动
    ↓
AppSession 检查 Keychain 是否有 refreshToken
    ↓
有 token → 调用 /auth/refresh 或 /users/me
    ↓
成功 → 进入主界面
失败 → 清空 token进入登录页
    ↓
无 token → 进入登录页
    ↓
用户点击 Sign in with Apple
    ↓
获取 identityToken / authorizationCode / userIdentifier
    ↓
POST /api/auth/apple
    ↓
后端返回 accessToken / refreshToken / user
    ↓
token 存入 Keychain不要 UserDefaults
    ↓
判断 user.onboardingCompleted
    ↓
未完成 → 引导/目标设置
已完成 → 主界面

登录相关新增文件

Features/Auth/Views/LoginView.swift          # 仅 Apple 登录按钮
Features/Auth/ViewModels/LoginViewModel.swift # @MainActor, @Published isLoading/errorMessage

Core/Services/AuthService.swift
Core/Services/AuthServiceProtocol.swift

Core/Storage/KeychainStore.swift
Core/Storage/TokenStore.swift

App/AppSession.swift                         # 全局登录状态管理

Core/Models/AuthModels.swift                 # AppleLoginRequest, AuthResponse
Core/Models/User.swift

API ContractAuth

// 请求
struct AppleLoginRequest: Encodable {
    let identityToken: String
    let authorizationCode: String?
    let userIdentifier: String
    let fullName: AppleFullName?
    let email: String?
}

// 响应
struct AuthResponse: Decodable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
    let user: User
}

关键约束

  • 不再用 @AppStorage("hasCompletedOnboarding") 单独决定是否进入主界面
  • 登录状态必须由 AppSession + Keychain token 决定
  • Token 不存 UserDefaults
  • View 里不写网络请求,不直接处理 Apple 登录细节

2.2 后端 API 对接

现状:所有页面为静态 UI无任何网络请求。

计划定义的 P0 API

  • POST /ai/analyze-learning-input — AI 分析用户学习输入
  • POST /ai/chat — AI 对话
  • 用户/知识库/学习记录/反馈 CRUD

2.3 真实 AI 集成

现状AI 相关页面全为静态文本。

需对接

  • 后端 AI Provider 抽象层MiniMax/DeepSeek/OpenAI 等)
  • 结构化 JSON 输出解析
  • AI 分析结果展示(掌握度评分、优缺点、建议)
  • AI 对话流式响应

2.4 本地数据持久化

现状:零持久化实现。

需实现

  • UserDefaults / @AppStorage简单偏好
  • KeychainToken、敏感信息
  • 后续可考虑 CoreData 或 SwiftData学习记录离线缓存

2.5 多语言本地化

现状:所有文案硬编码在 View 中,无 Localizable.xcstrings 文件。

计划要求Demo与MVP.md 第 6 节):

  • 默认简体中文
  • 预留英文
  • App UI 文案使用本地化资源

需实现

  • 创建 Localizable.xcstrings
  • 将所有硬编码文案迁移为 LocalizedStringKey
  • 支持语言切换

2.6 错误/加载/空状态处理

现状:无任何错误处理、加载态、空状态 UI。

至少需要

  • 网络请求 loading 指示器
  • 网络错误提示和重试按钮
  • AI 分析中的等待状态
  • 列表空状态(如知识库为空时的引导)
  • 登录失败错误提示

三、页面层面差距

3.1 与计划页面对比

计划页面 计划优先级 当前状态 说明
启动页/欢迎页 P1 已实现 SplashPage + WelcomePage
登录页 P0 已实现 新 LoginView 替换旧 LoginPage仅 Apple 登录
语言与偏好页 P1 未实现 无页面
学习方向选择页 P0 ⚠️ 部分实现 GoalSetupPage 有目标选择,但非学习方向选择
学习路径页 P0 已实现 LibraryDetailPage
今日学习任务页 P0 已实现 StudyHomeView
内容阅读页 P0 已实现 KnowledgeDetailPage
主动回忆/笔记输入页 P0 已实现 DailyThinkingPage + RecallTestPage
AI 分析结果页 P0 已实现 AIFeedbackPage
AI 对话页 P0 已实现 AIChatPage
复习计划页 P0 已实现 ReviewPlanView含今天/明天/本周分组 + 复习类型标签
学习进度页 P1 已实现 AnalysisHomeView
设置页 P1 ⚠️ 部分实现 ProfileView 有设置菜单,但功能入口为空
反馈页 P1 已实现 FeedbackView + FeedbackViewModel分类选择 + 提交确认

3.2 复习计划页P0

计划描述:系统生成复习任务,用户查看待复习内容,按推荐时间安排学习。

当前:已实现 Features/Review/ReviewPlanView.swift,含今天/明天/本周三组、复习类型标签(间隔重复/费曼/回忆/薄弱)、完成勾选、播放按钮。数据层使用 ReviewTask Model + mock 数据。

待对接:接入 ReviewService 后端数据。

3.3 反馈页P1

计划描述App 内反馈入口,让内测用户提交问题和建议。

当前:已实现 Features/Feedback/FeedbackView.swift + FeedbackViewModel.swift,含 4 类反馈分类Bug/功能建议/内容问题/其他)的图标选择器、文本描述输入、提交通知。入口位于 ProfileView 菜单末项。

待对接:接入后端 /feedback APIFeedbackService

3.4 等待名单入口

计划:官网 /waitlist 页面收集用户App 内也需要引导用户加入等待名单/申请内测。

需考虑:是否在 App 内嵌等待名单入口(如 Welcome 页或设置页)。


四、设计与交互差距

4.1 Tab 结构调整

计划设计4 个 Tab — 学习 | 知识库 | AI助手 | 我的

当前实现5 个 Tab — AI | 知识库 | 学习 | 分析 | 我的

差异分析

  • 当前把"学习"和"分析"拆成了两个独立 Tab
  • 计划把"AI助手"独立为一个 Tab当前 AI 已是独立 Tab
  • "分析"在计划中属于"学习"Tab 下的子页面,不需要顶层 Tab

建议(两种方案):

  • 方案 A:完全对齐计划 → 合并学习和分析为一个 Tab保持 4 Tab
  • 方案 B:保留 5 Tab 结构 → 更新计划文档,论证"分析"独立为 Tab 的合理性(学习数据可视化、学习进度监控是独立价值)

4.2 登录流程简化(已有详细设计)

计划要求:仅 Sign in with Apple不做手机号/邮箱/微信登录。

当前 UI:包含 4 种登录方式入口。

最终方案(详见 docs/AI对话.md

  • 登录页只保留 Sign in with Apple 一个按钮
  • 删除手机号、邮箱、微信、验证码登录
  • 跳过按钮仅限 #if DEBUGRelease 不展示
  • 登录页文案极简:品牌 + 一句话价值主张 + Apple 登录按钮 + 协议入口

4.3 浅色/深色模式双主题P1 新增) 已完成

实现方式

改造项 状态
Color(light:dark:) 自适应 helper DesignTokens.swift 新增基于 UITraitCollection.userInterfaceStyle 的动态颜色
28 个颜色 token 双主题化 背景(4) + 文字(9) + 边框(5) + 填充(6) + 品牌色/彩色半透不变
渐变自适应 page/splash 渐变改用自适应 Color token
硬编码色值替换 13 处内联 Color(hex:) 替换为自适应 token
ColorSchemeManager Core/Appearance/ColorSchemeManager.swift@AppStorage 持久化,支持系统/浅色/深色
移除强制深色 移除 4 处 .preferredColorScheme(.dark)
设置页切换入口 ProfileView 外观行改为可点击confirmationDialog 三选一

文件变更

  • 新增:Core/Appearance/ColorSchemeManager.swift
  • 修改:DesignTokens.swift(颜色全量自适应 + 渐变)
  • 修改:AIStudyAppApp.swift(根视图用 manager 控制 scheme
  • 修改:ContentView.swiftLoginView.swift(移除强制暗黑)
  • 修改:ProfileView.swift(外观切换入口)
  • 修改:SplashPage.swiftZXTabBar.swiftZXAIInteractionRow.swiftZXChartView.swiftAIFeedbackPage.swiftWelcomePage.swiftOnboardingPage.swiftGoalSetupPage.swiftZXSTaskRow.swiftZXIconBtn.swift内联色值→token

4.4 语言系统P1 新增)

现状:所有文案硬编码中文在 SwiftUI View 中,无 Localizable.strings。

要求:先只支持中文,但搭建好本地化基础设施,后续加语言时只需加翻译文件。

需实现

步骤 说明
创建 Localizable.strings (Base) 中文作为 Base 语言,不设 zh-Hans
封装 ZXLocalized 辅助 String(localized:) + Text("key") 的 SwiftUI 原生方式
迁移硬编码文案 逐文件将文案替换为 LocalizedStringKey
设置页语言入口 预留语言切换 UI当前仅显示"中文"

4.5 无障碍

现状:未考虑 VoiceOver、Dynamic Type、高对比度等无障碍需求。

至少需做

  • 关键按钮添加 .accessibilityLabel
  • 确保 Dynamic Type 下布局不破碎
  • 重点页面 VoiceOver 测试

4.6 样式规范

已于 docs/样式规范.md 中梳理完整的样式规范文档,涵盖:

  • 色彩系统(背景/文字/品牌语义色/边框/填充)
  • 渐变体系页面、品牌、卡片、进度条、CTA 等 11 组渐变)
  • 圆角、间距、尺寸 token
  • 字体层级12 级定义)
  • 共享组件目录20+ 组件,含导航、按钮、卡片行、数据展示、输入、标签等)
  • 页面布局模式(主 Tab 页、子页面、卡片、输入框、状态标签的标准写法)
  • 设计决策与约束

后续新页面必须遵循 docs/样式规范.md,复用已有组件和 token禁止随意使用内联颜色/间距/字体。

4.7 动效

计划要求官网与技术基础.md 第 6.3 节):

  • P0页面过渡、按钮反馈、加载状态、AI 分析中状态、学习完成反馈
  • P1今日任务卡片动效、进度条更新、AI 结果分块出现

当前:仅有基础 SwiftUI 隐式动画withAnimation未实现任何计划中的动效。


五、数据层缺失

5.1 Model 定义

现状:所有数据通过 View 内局部变量或硬编码定义,无独立 Model 文件。

计划中定义的核心实体Demo与MVP.md

User
├── id, appleUserId, displayName, email
├── preferredLanguage, createdAt, lastLoginAt, status

KnowledgeBase
├── id, title, description, language, targetUser
├── createdAt, updatedAt

LearningPath
├── id, knowledgeBaseId, title, description
├── estimatedDays, order

Lesson
├── id, pathId, title, content, objectives
├── keyPoints, recallQuestions, practicePrompt
├── order, estimatedMinutes

LearningSession
├── id, userId, lessonId
├── startedAt, endedAt, userInput
├── aiAnalysis, masteryScore, weakPoints
├── nextSuggestion, reviewAt

AIAnalysis
├── id, userId, sessionId
├── inputText, outputJson, masteryScore
├── weakPoints, suggestions
├── modelName, createdAt, costEstimate

ReviewTask
├── id, userId, lessonId, sourceSessionId
├── reviewType, scheduledAt, completedAt, status

Feedback
UserLearningProfile

需实现:在 Features/*/Models/ 下创建对应的 Swift struct需 Codable、Identifiable

5.2 API Contract

现状:无 API 类型定义。

建议:参考计划中定义的 JSON 结构,先创建 Swift Model再定义 API 请求/响应类型Request/Response struct实现前后端类型同构。

5.3 数据流规范

现状View 直接持有 @State无数据流管理。

建议

  • ViewModel 持有 @Published 状态
  • ViewModel 通过 Service 获取数据
  • Service 通过 Repository 访问数据源
  • View 通过 @StateObject / @ObservedObject 绑定 ViewModel

六、工程化缺失

6.1 大文件拆分

已完成

原文件 拆分结果
AIStudyAppApp.swift (~190行) 保留 App/Root/OnboardingFlowView页面移入 Features/Onboarding/
DailyThinkingPage.swift (~200行) 拆为 5 个文件DailyThinking / RecallTest / WeakPoints / AIFeedback / AIChat
LibrarySubpages.swift (~112行) 已删除,拆为 6 个独立页面文件

6.2 共享组件管理

已完成20 个共享组件集中到 Shared/Components/,原文件中的定义已移除。

Shared/Components/
├── ZXTabBar.swift          ├── ZXBackHeader.swift
├── ZXIconBtn.swift         ├── ZXScoreBox.swift
├── ZXAIInputBar.swift      ├── ZXOutlineBtn.swift
├── ZXQuickAction.swift     ├── ZXAIInteractionRow.swift
├── ZXCardRow.swift         ├── ZXChip.swift
├── ZXImportOption.swift    ├── ZXWeakRow.swift
├── ZXStatBadge.swift       ├── ZXProfileStat.swift
├── ZXProfileMenuRow.swift  ├── ZXAchievementBadge.swift
├── ZXChartView.swift       ├── ZXSTaskRow.swift
├── FeatureRow.swift        ├── ReviewTaskRow.swift
├── ZXLoadingView.swift     ├── ZXErrorView.swift
└── ZXEmptyView.swift

6.3 测试

现状:无任何测试代码。

至少需要

  • ViewModel 单元测试(当 ViewModel 创建后)
  • Service 层单元测试Mock Repository
  • 关键 UI 流程的 Snapshot 测试

6.4 CI/CD

现状:无。

建议(后续):

  • GitHub Actions / Xcode Cloud 自动构建
  • TestFlight 自动分发

6.5 崩溃监控与埋点

现状:无。

建议:接入 Firebase Crashlytics 或类似服务,至少在 TestFlight 阶段要有崩溃收集能力。


七、优先级建议

P0 — 必须在接后端前完成

步骤 项目 理由 设计文档
创建 Auth ModelAuthModels + User 所有后续步骤的数据基础 第十章 步骤 1
实现 Keychain 存储层 Token 安全存储是登录的前提 第十章 步骤 2
搭建网络层最小实现APIClient 所有后端交互的唯一通道 第十章 步骤 3
实现 AuthServiceApple 登录 + 后端调用) 用户身份是学习记录的前提 docs/AI对话.md、第十章 步骤 4
实现 AppSession全局登录状态 统一的登录态管理 第十章 步骤 5
实现 LoginView + LoginViewModel 替换当前过度实现的登录页 docs/AI对话.md、第十章 步骤 6
改造 App 入口启动逻辑 Token 分流替换 @AppStorage 第十章 步骤 7
创建 Model 层(其余数据实体) 是 Service/ViewModel/API 的基础 本文档 5.1 节
实现复习计划独立页 计划标记 P0
拆分大文件 + 集中共享组件 降低后续修改的认知负担 本文档 6.1/6.2 节
添加加载/错误/空状态处理 真机使用的基本体验保障 Shared/Components/ (ZXLoadingView, ZXErrorView, ZXEmptyView)

P1 — 与后端对接同步推进

优先级 项目 理由
P1 浅色/深色模式双主题 覆盖所有页面和 DesignTokens工作量较大
P1 语言系统搭建(中文 Base 先建基础设施,后续加语言不返工
P1 搭建 ViewModel 层(逐步迁移) 架构分层,但不阻塞功能开发
P1 搭建 Service 层 随 API 对接自然建立
P1 实现反馈页 TestFlight 内测必须
P1 实现设置页完整功能 外观切换、语言入口、复习提醒等

P2 — App Store 前完成

优先级 项目 理由
P2 Repository 层 当需要本地缓存 + 网络切换时再做
P2 动效补充 体验优化,不阻塞功能
P2 无障碍适配 App Store 审核加分项
P2 测试 用户量增长后需要
P2 Tab 结构调整决策 需要更多用户反馈来决策

Tab 结构分析

当前 5-Tab 结构

Tab 页面 核心功能
AI AIHomeView AI 对话入口 + 每日思考 + 薄弱点
知识库 LibraryHomeView 知识库浏览 + 导入 + 搜索
学习 StudyHomeView 今日任务 + 进度 + 每周活跃
分析 AnalysisHomeView 学习时长 + 掌握度 + 雷达图
我的 ProfileView 个人信息 + 设置 + 成就

问题诊断

  1. AI 与学习边界模糊 — AI 对话产生学习记录,但学习任务在独立 Tab用户需要在两个 Tab 间切换
  2. 分析 Tab 内容单薄 — 纯展示仪表盘,无交互深度,与"学习"Tab 的进度卡片有重叠
  3. 知识库入口过重 — 知识库本质是学习的前置步骤,独立 Tab 使其脱离学习流程

可选方案

方案 结构 优点 缺点
A: 保持现状 5 Tab 不变 无改动成本 上述问题持续
B: 合并 AI+学习 4 Tab学习/AI、知识库、分析、我的 AI 与学习一体化 需重设计学习首页
C: 合并分析入学习 4 TabAI、知识库、学习+分析、我的) 分析数据有上下文 学习页信息密度增加
D: 精简 3 Tab 3 Tab学习、知识库、我的 最简洁AI 内嵌学习 分析页降级为次级入口

建议MVP 阶段保持方案 A收集用户反馈后优先尝试方案 C分析并入学习。触发条件分析 Tab 的周活跃用户 < 20%。


八、总结

当前 iOS 项目完成了 UI 层的全量搭建21 页),但缺少"能让产品真正运转"的架构底座和数据能力。核心矛盾是:

UI 超前,架构滞后。页面能点,但无数据、无认证、无 AI、无服务。

当前进度2026-05-10

P0 — 全部完成

  1. Apple 登录 + Auth 体系9 个文件)
  2. Model 层10 个数据实体)
  3. 网络层最小实现APIClient + Endpoint + Error
  4. App 入口重构AppSession 驱动路由,替代 @AppStorage
  5. 复习计划独立页ReviewPlanView
  6. 大文件拆分3 个大文件拆为 15+ 个独立文件)
  7. 共享组件集中管理(Shared/Components/ 下 20+ 个组件)
  8. 加载/错误/空状态处理ZXLoadingView / ZXErrorView / ZXEmptyView

P1 — 部分完成

  • 浅色/深色模式双主题DesignTokens 自适应 + ColorSchemeManager + 移除强制暗黑)
  • 语言系统搭建(中文 BaseLocalizable.strings + ZXStrings + LanguageManager
  • 搭建 ViewModel 层ReviewPlanViewModel + AIChatViewModel + StudyHomeViewModel
  • 搭建 Service 层5 个 Service 协议 + 实现 + 20 个 APIEndpoint
  • 实现反馈页TestFlight 内测必须)
  • 设置页外观切换ColorSchemeManager + ProfileView confirmationDialog
  • 设置页完整功能5 个子页面 + 外观/语言切换)

接下来推荐顺序

1. 语言系统搭建(基础设施优先,避免后续返工)
   ├── 创建 Localizable.strings (Base = 中文)
   ├── 迁移硬编码文案到 LocalizedStringKey
   └── 设置页预留语言入口(当前仅显示中文)
       ↓
2. 浅色/深色模式双主题(体验升级,需全量回归)
   ├── DesignTokens 定义 light/dark 双套色值
   ├── 替换所有内联 Color(hex:) 为 token 引用
   ├── 设置页新增外观切换(跟随系统 / 浅色 / 深色)
   └── 全页面浅色模式验证
       ↓
3. 反馈页 + 设置页补全(独立页面,不依赖其他改造)
       ↓
4. ViewModel 层迁移(逐步,不阻塞功能)
       ↓
5. Service 层搭建(随 API 对接自然建立)

九、AI对话.md 登录方案摘要

docs/AI对话.md 是登录模块的详细实现规范。以下为关键决策的结构化提取,便于对照实施。

9.1 登录入口决策

第一版只保留

Sign in with Apple

删除这些入口

  • 手机号登录 / 邮箱登录 / 微信登录 / 验证码登录

"跳过"按钮

  • #if DEBUG 保留Release 不展示
  • 理由避免匿名用户后续迁移学习记录、AI 分析绑定用户身份)

9.2 登录页内容

知习

更懂你,更会学。

用 AI 把知识库、主动回忆和间隔复习连接起来,
从"看过"走向"真正学会"。

[ Sign in with Apple ]

登录即代表你同意《用户服务协议》和《隐私政策》

9.3 完整登录流程

App 启动
    ↓
AppSession 检查 Keychain 是否有 refreshToken
    ↓
有 token → 调用 /auth/refresh 或 /users/me
    ├─ 成功 → 进入主界面
    └─ 失败 → 清空 token进入登录页
    ↓
无 token → 进入登录页
    ↓
用户点击 Sign in with Apple
    ↓
ASAuthorizationController 获取:
  · identityToken
  · authorizationCode
  · userIdentifier
  · email / fullNameApple 可能不返回)
    ↓
POST /api/auth/apple
    ↓
后端返回 { accessToken, refreshToken, expiresIn, user }
    ↓
accessToken / refreshToken 存入 Keychain
    ↓
判断 user.onboardingCompleted
    ├─ false → 引导页 / 学习目标设置
    └─ true  → 主界面ContentView

9.4 需要新增的 9 个文件

文件 职责
Model Core/Models/AuthModels.swift AppleLoginRequest、AuthResponse 等 Codable struct
Model Core/Models/User.swift 用户实体
Storage Core/Storage/KeychainStore.swift 通用 Keychain 读写封装
Storage Core/Storage/TokenStore.swift Token 专用存取save/load/clear
Service Core/Services/AuthServiceProtocol.swift AuthService 协议定义
Service Core/Services/AuthService.swift ASAuthorizationController 集成 + 后端调用
App App/AppSession.swift @MainActor 全局登录状态
View Features/Auth/Views/LoginView.swift 纯 Apple 登录按钮 UI
ViewModel Features/Auth/ViewModels/LoginViewModel.swift @Published isLoading/errorMessage

9.5 API Contract

// 请求 → POST /api/auth/apple
struct AppleLoginRequest: Encodable {
    let identityToken: String
    let authorizationCode: String?
    let userIdentifier: String
    let fullName: AppleFullName?
    let email: String?
}

struct AppleFullName: Encodable {
    let givenName: String?
    let familyName: String?
}

// 响应
struct AuthResponse: Decodable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
    let user: User
}

9.6 关键约束

约束 说明
Token 存 Keychain 不存 UserDefaults
不用 @AppStorage 控制登录 登录状态由 AppSession + Keychain token 决定
View 不写网络请求 网络调用在 Service 层
View 不处理 Apple 登录细节 ASAuthorizationController 逻辑在 AuthService
不改变现有主页面 UI 只替换入口路由逻辑

十、登录模块可行实施计划

以下是按依赖关系排列的 7 个实施步骤。每步独立可验证,后一步依赖前一步完成。

整体依赖图

步骤1: Model ─────────────────────────────────────────┐
       ↓                                               │
步骤2: KeychainStore / TokenStore ────────────────────┤
       ↓                                               │
步骤4: APIClient / APIEndpoint ─┐                      │
       ↓                        │                      │
步骤3: AuthService ←────────────┘                      │
       ↓                                               │
步骤5: AppSession ─────────────────────────────────────┤
       ↓                                               │
步骤6: LoginView + LoginViewModel ────────────────────┘
       ↓
步骤7: 改造 App 入口启动逻辑AIStudyAppApp.swift

步骤 1创建 Auth Model 已完成2026-05-10

产出文件

  • Core/Models/AuthModels.swift
  • Core/Models/User.swift

内容

// AuthModels.swift
struct AppleLoginRequest: Encodable { ... }
struct AppleFullName: Encodable { ... }
struct AuthResponse: Decodable { ... }

// User.swift
struct User: Codable, Identifiable {
    let id: String
    let appleUserId: String
    let displayName: String?
    let email: String?
    let preferredLanguage: String
    let onboardingCompleted: Bool
    let createdAt: String
    let lastLoginAt: String?
    let status: String
}

依赖:无

验证Xcode 编译通过


步骤 2实现 Keychain 存储层 已完成2026-05-10

产出文件

  • Core/Storage/KeychainStore.swift
  • Core/Storage/TokenStore.swift

KeychainStore 职责:通用 Keychain 读写,封装 SecItemAdd/SecItemCopyMatching/SecItemDelete,支持 save/load/delete 操作。

TokenStore 职责

protocol TokenStoreProtocol {
    func saveAccessToken(_ token: String) throws
    func getAccessToken() throws -> String?
    func saveRefreshToken(_ token: String) throws
    func getRefreshToken() throws -> String?
    func clearAll() throws
}

依赖:步骤 1Model 定义,具体来说不需要 Model 依赖TokenStore 操作的是原始 String

验证:可写简单单元测试验证存取清除


步骤 3搭建网络层最小实现 已完成2026-05-10

注意:这一步和 AuthService 互相依赖——AuthService 需要 APIClient 发请求,但可以先建网络层骨架。做的时候步骤 3 和 4 可以部分并行:先建 APIClient 基础,再写 AuthService 时补充 Auth 相关 endpoint。

产出文件

  • Core/Network/APIClient.swift
  • Core/Network/APIEndpoint.swift
  • Core/Network/APIError.swift

最小接口

// APIClient
class APIClient {
    init(baseURL: URL, tokenStore: TokenStoreProtocol?)
    func request<T: Decodable>(_ endpoint: APIEndpoint) async throws -> T
    func requestVoid(_ endpoint: APIEndpoint) async throws
}

// APIEndpoint
enum APIEndpoint {
    case appleLogin(AppleLoginRequest)
    case refreshToken(String)
    case me
    // 后续扩展其他 endpoint
}

// APIError
enum APIError: Error {
    case network(Error)
    case httpError(Int)
    case decoding(Error)
    case unauthorized
}

依赖:步骤 1Model、步骤 2TokenStore

验证Xcode 编译通过,可以先 mock 一个请求验证 pipeline 跑通


步骤 4实现 AuthService 已完成2026-05-10

产出文件

  • Core/Services/AuthServiceProtocol.swift
  • Core/Services/AuthService.swift

AuthServiceProtocol

protocol AuthServiceProtocol {
    func loginWithApple() async throws -> AuthResponse
    func refreshSession() async throws -> AuthResponse
    func logout() async throws
    func fetchCurrentUser() async throws -> User
}

AuthService 实现要点

  1. 集成 ASAuthorizationController(需 import AuthenticationServices
  2. 获取 identityToken、authorizationCode、userIdentifier
  3. 调用 APIClient.request(.appleLogin(request))
  4. 将返回的 token 写入 TokenStore
  5. refreshSession用 refreshToken 换新 token

依赖:步骤 1AuthModels、步骤 2TokenStore、步骤 3APIClient

验证Xcode 编译通过,可在模拟器点击 Apple 登录(后端未就绪时用 mock


步骤 5实现 AppSession 已完成2026-05-10

产出文件

  • App/AppSession.swift

关键代码骨架

@MainActor
final class AppSession: ObservableObject {
    @Published var currentUser: User?
    @Published var isAuthenticated = false
    @Published var isLoading = true
    @Published var authError: String?

    private let authService: AuthServiceProtocol
    private let tokenStore: TokenStoreProtocol

    func bootstrap() async {
        // 1. 检查 Keychain 是否有 refreshToken
        // 2. 有 → 调用 refreshSession()
        // 3. 成功 → isAuthenticated=true, currentUser=user
        // 4. 失败 → 清空 token, isAuthenticated=false
        // 5. 无 → isAuthenticated=false
        // 6. isLoading = false
    }

    func loginWithApple() async { ... }
    func logout() { ... }
}

依赖:步骤 4AuthService、步骤 2TokenStore

验证:模拟器启动时可根据 Keychain 状态正确分流


步骤 6实现 LoginView + LoginViewModel 已完成2026-05-10

产出文件

  • Features/Auth/Views/LoginView.swift
  • Features/Auth/ViewModels/LoginViewModel.swift

LoginView

  • 品牌标题"知习" + 副标题
  • Sign in with Apple 按钮ASAuthorizationAppleIDButton
  • Loading 状态ProgressView
  • Error 提示
  • #if DEBUG 跳过按钮
  • 协议入口链接

LoginViewModel

@MainActor
final class LoginViewModel: ObservableObject {
    @Published var isLoading = false
    @Published var errorMessage: String?

    func loginWithApple() async {
        isLoading = true
        errorMessage = nil
        do {
            try await appSession.loginWithApple()
        } catch {
            errorMessage = "登录失败:\(error.localizedDescription)"
            isLoading = false
        }
    }
}

依赖:步骤 5AppSession

验证:模拟器显示登录页,点击 Apple 登录按钮触发流程loading 状态可展示


步骤 7改造 App 入口启动逻辑 已完成2026-05-10

修改文件AIStudyAppApp.swift

改动要点

  1. 注入 AppSession@StateObject
  2. 启动时调用 appSession.bootstrap()
  3. appSession.isLoading / isAuthenticated / currentUser?.onboardingCompleted 替换原来的 @AppStorage("hasCompletedOnboarding")
  4. 路由逻辑:
    isLoading          → Splash启动加载中
    !isAuthenticated   → LoginView
    onboardingCompleted == false → OnboardingFlowView
    onboardingCompleted == true  → ContentView
    

依赖:步骤 1-6 全部完成

验证:完整启动流程测试——

  • 首次启动 → 登录页 → Apple 登录mock → 引导页 → 目标设置 → 主界面
  • 二次启动(有有效 token→ 直接进主界面
  • Token 过期 → 登录页

实施俯视图

步骤 1 ──→ 步骤 2 ──→ 步骤 3 ──→ 步骤 4 ──→ 步骤 5 ──→ 步骤 6 ──→ 步骤 7
Model     Keychain   网络层    AuthSvc   AppSession  LoginUI   入口改造
(0依赖)   (无依赖)   (依赖1,2) (依赖1-3)  (依赖2,4)  (依赖5)   (依赖1-6)

每一步均可独立编译验证。后端 API 未就绪时,步骤 4-7 可以用 mock 数据先行开发,后端就绪后仅替换 APIClient 的真实 endpoint。

文件目录结构(完成后)

AIStudyApp/
├── App/
│   └── AppSession.swift              ← 新增
├── Core/
│   ├── Models/
│   │   ├── AuthModels.swift          ← 新增
│   │   └── User.swift                ← 新增
│   ├── Network/
│   │   ├── APIClient.swift           ← 新增
│   │   ├── APIEndpoint.swift         ← 新增
│   │   └── APIError.swift            ← 新增
│   ├── Services/
│   │   ├── AuthServiceProtocol.swift ← 新增
│   │   └── AuthService.swift         ← 新增
│   ├── Storage/
│   │   ├── KeychainStore.swift       ← 新增
│   │   └── TokenStore.swift          ← 新增
│   └── DesignSystem/
│       └── DesignTokens.swift        (已有,不变)
├── Features/
│   ├── Auth/
│   │   ├── Views/
│   │   │   └── LoginView.swift       ← 新增
│   │   └── ViewModels/
│   │       └── LoginViewModel.swift   ← 新增
│   ├── AI/          (已有)
│   ├── Library/     (已有)
│   ├── Study/       (已有)
│   ├── Analysis/    (已有)
│   └── Profile/     (已有)
└── AIStudyAppApp.swift               ← 修改