第一部分:[交互设计] 用户画像系统与各模块的协同工作
用户画像模块不是一个孤岛,它是一个数据枢纽,与其他模块进行着双向的数据交换。其交互的核心原则是:"单向依赖,面向接口"。即,具体功能模块(如 Onboarding)可以依赖 user_profile 模块的 application 或 domain 层,但反之不行。
1. 与 Onboarding 模块的交互
- 目的: 收集用户初始的、显式的偏好 (
UserPreferences)。这是用户画像的冷启动数据。 - 数据流向:
Onboarding(UI) ->UpdateUserPreferencesUseCase(user_profile/application) ->UserProfileRepository(user_profile/domain)。 - 架构实现:
Onboarding屏幕是一个ConsumerWidget,包含一系列让用户选择兴趣标签的 UI。- 当用户点击“完成”按钮时,
Onboarding屏幕的Provider(或直接在onPressed回调中) 会执行以下操作:// In Onboarding screen's logic void onComplete(WidgetRef ref) { // 1. 从 UI 状态中收集用户的选择 final selectedCategories = ref.read(selectedCategoriesProvider); final userPreferences = UserPreferences(likedCategories: selectedCategories, ...); // 2. 调用 user_profile 模块的 UseCase // 注意:Onboarding 模块只知道 UseCase 的存在,不知道其内部实现 ref.read(updateUserPreferencesUseCaseProvider).execute(userPreferences); // 3. 导航到主页 context.go('/home'); } updateUserPreferencesUseCaseProvider是在user_profile模块中定义的 Provider。Onboarding模块通过pubspec.yaml依赖user_profile模块来访问它。
2. 与 Auth 模块的交互
- 目的: 关联用户身份。
UserProfile必须与一个唯一的userId绑定。 - 数据流向:
Auth模块在登录/注册成功后,会产生一个userId。其他模块(包括user_profile)需要能够获取到这个userId,以便在进行数据操作时进行关联。 - 架构实现:
Auth模块的核心产出是一个AuthProvider,它管理着用户的认证状态(未登录、已登录、加载中)和认证信息(如AuthToken,其中包含userId)。// In auth/providers final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) => ...); // AuthState could be a sealed class // sealed class AuthState {} // class Authenticated extends AuthState { final AuthToken token; } // class Unauthenticated extends AuthState {}UserProfileRepositoryImpl在执行任何操作前,都需要获取当前的userId。它会通过ref来读取authStateProvider。// In user_profile/infrastructure/repositories/user_profile_repository_impl.dart class UserProfileRepositoryImpl implements UserProfileRepository { final Ref _ref; // ...其他依赖... UserProfileRepositoryImpl(this._ref, ...); String _getCurrentUserId() { final authState = _ref.read(authStateProvider); if (authState is Authenticated) { return authState.token.userId; } throw AuthException('User not authenticated'); } @override Future<UserProfile> getUserProfile() async { final userId = _getCurrentUserId(); // ...后续所有本地或远程操作都带上 userId return _localDataSource.getProfile(userId); } // ...其他方法同样处理 }- 关键点:
user_profile模块依赖auth模块的状态输出 (authStateProvider),而不是其内部实现。这是一种松耦合的依赖关系。
3. 与 个人中心 (Profile Display) 模块的交互
- 目的: 展示用户画像数据,并提供修改入口。
- 数据流向:
GetUserProfileUseCase(user_profile/application) ->个人中心(UI)。同时,用户在个人中心修改偏好时,流向与Onboarding类似。 - 架构实现:
个人中心屏幕的ViewModel(或StateNotifierProvider) 会调用GetUserProfileUseCase来获取UserProfile对象。// In profile_display/providers @riverpod class ProfileViewModel extends _$ProfileViewModel { @override Future<UserProfile> build() { // 调用 UseCase 获取数据 return ref.watch(getUserProfileUseCaseProvider).execute(); } // 提供修改入口,例如修改昵称或偏好 Future<void> updatePreferences(UserPreferences newPrefs) async { // ... 更新状态为 loading await ref.read(updateUserPreferencesUseCaseProvider).execute(newPrefs); // ... 刷新页面数据 ref.invalidateSelf(); } }- UI (
ConsumerWidget)watch这个ProfileViewModelProvider,并根据其AsyncValue(loading, data, error) 来构建界面。
第二部分:[分级存储] 基于数据敏感性的安全设计
这是一个至关重要的部分,直接关系到用户隐私和应用安全。我们不能将所有数据都以相同的方式存储在同一个地方。必须设计一个分级的本地存储策略。
核心思想: 将 Infrastructure 层的 DataSource 拆分为多个,每个 DataSource 负责一个安全级别的数据,并使用不同的存储技术。Repository 层作为外观(Façade),负责整合这些 DataSource,对上层(Application 层)屏蔽这些复杂性。
数据敏感性分级
| 级别 | 级别名称 | 数据例子 | 存储技术建议 | 特点 |
|---|---|---|---|---|
| L3 (最高) | 高度敏感数据 (Highly Sensitive) | accessToken, refreshToken, 第三方平台 token | flutter_secure_storage (使用 Keychain/Keystore) | 系统级加密,App卸载后数据清除。仅在需要时读入内存。 |
| L2 (中等) | 个人身份信息 (PII - Personally Identifiable Info) | 用户名, 邮箱, 手机号, 用户自己填写的地址 | Hive / Isar 加密盒子 (Encrypted Box) | App级别加密,性能较高,适合结构化数据。密钥存储在 L3。 |
| L1 (较低) | 推断与行为数据 (Inferred & Behavioral) | inferredTopCategories, engagementScore, 点击/浏览记录 | Hive / Isar 普通盒子 (Regular Box) | 无需加密或轻量级加密。追求读写性能,数据量可能很大。 |
| L0 (无敏感) | 应用配置 (App Config) | isDarkMode, language | shared_preferences | 简单键值对,性能高,无安全要求。 |
架构实现
-
拆分
DataSource接口和实现:// core/storage/ // L3 abstract class SecureAuthDataSource { Future<void> saveToken(AuthToken token); ... } class SecureAuthDataSourceImpl implements SecureAuthDataSource { ... } // 使用 flutter_secure_storage // L2 abstract class EncryptedPiiDataSource { Future<void> saveUserInfo(UserInfo info); ... } class EncryptedPiiDataSourceImpl implements EncryptedPiiDataSource { ... } // 使用加密的 Hive Box // L1 abstract class BehavioralDataSource { Future<void> logBehavior(UserBehaviorEvent event); ... } class BehavioralDataSourceImpl implements BehavioralDataSource { ... } // 使用普通的 Hive Box注意:这些
DataSource可以分散在各自的feature模块中,例如SecureAuthDataSource在auth模块的infrastructure里,而另外两个在user_profile模块里。 -
重构
UserProfileRepositoryImpl作为数据整合者:// In user_profile/infrastructure/repositories/user_profile_repository_impl.dart class UserProfileRepositoryImpl implements UserProfileRepository { final Ref _ref; // 注入多个不同级别的 DataSource final EncryptedPiiDataSource _piiDataSource; final BehavioralDataSource _behavioralDataSource; final ProfileGeneratorService _profileGenerator; UserProfileRepositoryImpl(this._ref, this._piiDataSource, this._behavioralDataSource, this._profileGenerator); String _getCurrentUserId() { /* ... */ } @override Future<UserProfile> getUserProfile() async { final userId = _getCurrentUserId(); // 1. 并行从不同安全级别的数据源获取数据 final results = await Future.wait([ _piiDataSource.getUserInfo(userId), _behavioralDataSource.getRecentBehaviors(userId), _piiDataSource.getPreferences(userId) // 假设偏好也存在 L2 ]); final userInfo = results[0] as UserInfo; final behaviors = results[1] as List<UserBehaviorEvent>; final preferences = results[2] as UserPreferences; // 2. 将原始数据喂给画像生成服务 (ML 或规则引擎) final profile = await _profileGenerator.generateProfile( preferences: preferences, recentBehaviors: behaviors, // ...可能还需要 userInfo ); // 3. 组合最终返回给 UI 的对象 (可能需要合并 userInfo 和 profile) // 对上层屏蔽了数据来自多个源头的复杂性 return profile.copyWith( // 如果 UserProfile 实体也包含 PII 信息 userName: userInfo.name, email: userInfo.email ); } @override Future<void> updateUserPreferences(UserPreferences preferences) async { final userId = _getCurrentUserId(); // 存储到 L2 加密数据源 await _piiDataSource.savePreferences(userId, preferences); } @override Future<void> logBehavior(UserBehaviorEvent event) async { final userId = _getCurrentUserId(); // 存储到 L1 普通数据源 await _behavioralDataSource.logBehavior(userId, event); } } -
在 Riverpod 中组装依赖:
// providers.dart // L3 final secureAuthDataSourceProvider = Provider((ref) => SecureAuthDataSourceImpl()); // L2 final encryptedPiiDataSourceProvider = Provider((ref) => EncryptedPiiDataSourceImpl(/* hive encryption key */)); // L1 final behavioralDataSourceProvider = Provider((ref) => BehavioralDataSourceImpl()); final userProfileRepositoryProvider = Provider<UserProfileRepository>((ref) { return UserProfileRepositoryImpl( ref, ref.watch(encryptedPiiDataSourceProvider), ref.watch(behavioralDataSourceProvider), ref.watch(profileGeneratorProvider) ); });
总结
通过这样的设计,我们构建了一个既健壮又安全的系统:
- 交互清晰: 各模块职责单一,通过 UseCase 和 Provider 进行松耦合交互,数据流向明确。
- 安全分级: 敏感数据(Token、PII)和非敏感数据(行为、推断)被物理隔离在不同的存储介质和加密等级中,极大地增强了应用的安全性,并为满足 GDPR、数据安全法等隐私法规打下了坚实基础。
- 封装良好:
UserProfileRepository完美地扮演了外观模式的角色,它将底层复杂的多源、分级存储细节完全封装起来,对Application层和Presentation层只暴露一个统一、干净的UserProfileRepository接口。这使得上层业务逻辑可以完全不关心数据到底是怎么存的,从而保持了 Clean Architecture 的核心优势。
下一篇文章会给出 Flutter 开发中常见模块划分范例。