191 lines
7.5 KiB
Markdown
191 lines
7.5 KiB
Markdown
|
|
# PRD: Do Not Sell My Personal Information 开关
|
|||
|
|
|
|||
|
|
## 1. 功能需求概述
|
|||
|
|
|
|||
|
|
实现 CCPA/CPRA 合规的 "Do Not Sell My Personal Information" 功能:
|
|||
|
|
- 隐私政策页添加 Do Not Sell 开关(胶囊状态栏切换)
|
|||
|
|
- 设置 → 隐私页添加相同开关
|
|||
|
|
- 后端存储用户隐私偏好
|
|||
|
|
- 开关默认开启(用户选择不被销售)
|
|||
|
|
|
|||
|
|
## 2. 背景与合规要求
|
|||
|
|
|
|||
|
|
### CCPA/CPRA 要求
|
|||
|
|
- 如果企业"销售"或"共享"个人信息用于广告,必须提供退出选项
|
|||
|
|
- 即使声明不卖,仍需提供退出机制作为合规保险
|
|||
|
|
- App Store 审核会检查此功能的存在
|
|||
|
|
|
|||
|
|
### 业务现状
|
|||
|
|
- 隐私政策已声明 `WE DO NOT SELL YOUR PERSONAL INFORMATION`
|
|||
|
|
- 但缺少用户端开关实现
|
|||
|
|
|
|||
|
|
## 3. 技术方案
|
|||
|
|
|
|||
|
|
### 3.1 后端实现
|
|||
|
|
|
|||
|
|
**存储位置**: `profiles.settings.privacy` (JSONB)
|
|||
|
|
|
|||
|
|
**设计决策**: 将 `privacy` 字段升级为结构化的 `PrivacySettings` 对象
|
|||
|
|
|
|||
|
|
**新增字段**:
|
|||
|
|
```python
|
|||
|
|
# backend/src/schemas/shared/user.py
|
|||
|
|
class PrivacySettings(BaseModel):
|
|||
|
|
"""隐私相关设置统一管理"""
|
|||
|
|
do_not_sell: bool = True # 本次需求:Do Not Sell(默认 True)
|
|||
|
|
profile_visibility: str = "public" # 未来扩展:个人资料可见性
|
|||
|
|
|
|||
|
|
# ProfileSettingsV1 中替换
|
|||
|
|
class ProfileSettingsV1(BaseModel):
|
|||
|
|
privacy: PrivacySettings = Field(default_factory=PrivacySettings) # 替换 dict
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**API 变更**: 复用现有 `PATCH /users/me/settings` 接口,无需新增 endpoint
|
|||
|
|
|
|||
|
|
**数据库迁移**: 无需 Schema 变更(privacy 是 JSONB,可自由扩展)
|
|||
|
|
|
|||
|
|
**向后兼容**:
|
|||
|
|
- 现有 `privacy: dict` 数据会自动转换为 `PrivacySettings`
|
|||
|
|
- 新字段使用默认值,不影响现有用户
|
|||
|
|
|
|||
|
|
### 3.2 Flutter 前端实现
|
|||
|
|
|
|||
|
|
**数据层**:
|
|||
|
|
- 新增 `PrivacySettings` class(对应后端)
|
|||
|
|
- `ProfileSettingsV1.privacy` 类型从 `Map<String, Object?>` 改为 `PrivacySettings`
|
|||
|
|
- 默认值:`doNotSell = true`(默认开启)
|
|||
|
|
|
|||
|
|
**UI 实现**:
|
|||
|
|
|
|||
|
|
1. **主开关:隐私通知设置页** (`PrivacyNotificationSettingsScreen`)
|
|||
|
|
- 在"隐私" section 下(最前面)添加 "Do Not Sell" 开关
|
|||
|
|
- 复用 `SettingsSwitchTile` 组件(已有)
|
|||
|
|
- 文案: "Limit Use of My Personal Information"
|
|||
|
|
- 默认值: True(开启)
|
|||
|
|
- 开关状态实时保存
|
|||
|
|
- "Profile Visibility" 保持现状(占位符,显示 Coming Soon)
|
|||
|
|
|
|||
|
|
2. **快捷入口:法律中心列表页** (`LegalCenterScreen`)
|
|||
|
|
- 在"隐私" section 下(法律文档列表之后)添加独立按钮
|
|||
|
|
- 不是开关,而是快捷入口(SettingsMenuTile)
|
|||
|
|
- 显示当前状态:已开启 / 已关闭
|
|||
|
|
- 点击跳转到 `PrivacyNotificationSettingsScreen`
|
|||
|
|
- 满足 CCPA/CPRA 隐私政策中提供"Do Not Sell"入口的要求
|
|||
|
|
|
|||
|
|
3. **隐私政策详情页** (`LegalDocumentScreen`)
|
|||
|
|
- 保持现状:只显示静态 Markdown 内容
|
|||
|
|
- **不添加开关**(避免重复功能,保持文档阅读纯粹性)
|
|||
|
|
|
|||
|
|
**代码结构**:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// apps/lib/features/settings/data/models/profile_settings.dart
|
|||
|
|
class PrivacySettings {
|
|||
|
|
const PrivacySettings({
|
|||
|
|
this.doNotSell = true,
|
|||
|
|
this.profileVisibility = 'public',
|
|||
|
|
this.personalization = false,
|
|||
|
|
this.historyVisibility = 'private',
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
final bool doNotSell;
|
|||
|
|
final String profileVisibility;
|
|||
|
|
final bool personalization;
|
|||
|
|
final String historyVisibility;
|
|||
|
|
|
|||
|
|
PrivacySettings copyWith({...});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class ProfileSettingsV1 {
|
|||
|
|
final PrivacySettings privacy; // 替换 Map<String, Object?>
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 国际化
|
|||
|
|
|
|||
|
|
需要新增以下 key(示例):
|
|||
|
|
- `settingsDoNotSellTitle`: "Do Not Sell My Personal Information"
|
|||
|
|
- `settingsDoNotSellDescription`: "Limit the use of your personal information"
|
|||
|
|
- `settingsDoNotSellHint`: "When enabled, we will not sell or share your data"
|
|||
|
|
|
|||
|
|
## 4. 实现步骤
|
|||
|
|
|
|||
|
|
### Phase 1: 后端数据模型
|
|||
|
|
|
|||
|
|
- [ ] **Backend**: `schemas/shared/user.py` - 新增 `PrivacySettings` class
|
|||
|
|
- 添加 `do_not_sell: bool = True`(本次需求)
|
|||
|
|
- 添加未来扩展字段:`profile_visibility`, `personalization`, `history_visibility`
|
|||
|
|
- [ ] **Backend**: `schemas/shared/user.py` - 更新 `ProfileSettingsV1`
|
|||
|
|
- 将 `privacy: dict[str, object]` 改为 `privacy: PrivacySettings`
|
|||
|
|
- 添加 `parse_profile_settings` 兼容性逻辑(dict → PrivacySettings)
|
|||
|
|
|
|||
|
|
### Phase 2: 前端数据模型
|
|||
|
|
|
|||
|
|
- [ ] **Flutter**: `settings/data/models/profile_settings.dart` - 新增 `PrivacySettings` class
|
|||
|
|
- 包含 `doNotSell`, `profileVisibility`, `personalization`, `historyVisibility`
|
|||
|
|
- 实现 `copyWith` 方法
|
|||
|
|
- [ ] **Flutter**: `settings/data/models/profile_settings.dart` - 更新 `ProfileSettingsV1`
|
|||
|
|
- 将 `privacy` 类型从 `Map<String, Object?>` 改为 `PrivacySettings`
|
|||
|
|
|
|||
|
|
### Phase 3: UI 实现
|
|||
|
|
|
|||
|
|
- [ ] **Flutter**: 扩展 `PrivacyNotificationSettingsScreen`
|
|||
|
|
- 在"隐私" section 最前面添加 Do Not Sell 开关(SettingsSwitchTile)
|
|||
|
|
- 保持 Profile Visibility 占位符不变
|
|||
|
|
- [ ] **Flutter**: 扩展 `LegalCenterScreen`
|
|||
|
|
- 添加快捷入口按钮(SettingsMenuTile)
|
|||
|
|
- 显示当前状态:已开启 / 已关闭
|
|||
|
|
- 实现导航到 `PrivacyNotificationSettingsScreen`
|
|||
|
|
|
|||
|
|
### Phase 4: 业务逻辑
|
|||
|
|
|
|||
|
|
- [ ] 开关状态持久化(通过 `PATCH /users/me/settings`)
|
|||
|
|
- [ ] 法律中心快捷按钮显示当前状态(从 settings.privacy.doNotSell 读取)
|
|||
|
|
|
|||
|
|
### Phase 5: 国际化
|
|||
|
|
|
|||
|
|
- [ ] 添加中/英/繁三语 ARB keys
|
|||
|
|
- `settingsDoNotSellTitle`: Do Not Sell My Personal Information
|
|||
|
|
- `settingsDoNotSellDescription`: Limit use of your personal information
|
|||
|
|
- `settingsDoNotSellEnabled`: Enabled
|
|||
|
|
- `settingsDoNotSellDisabled`: Disabled
|
|||
|
|
- [ ] 执行 `flutter gen-l10n`
|
|||
|
|
|
|||
|
|
## 5. 相关代码文件
|
|||
|
|
|
|||
|
|
### Backend
|
|||
|
|
| 文件 | 说明 | 变更类型 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| `backend/src/schemas/shared/user.py` | 新增 PrivacySettings class,ProfileSettingsV1.privacy 改为 PrivacySettings 类型 | 修改 |
|
|||
|
|
| `backend/src/v1/users/router.py` | 用户设置 API(复用 PATCH /me/settings) | 无变更 |
|
|||
|
|
| `backend/src/v1/users/service.py` | 设置更新逻辑 | 无变更 |
|
|||
|
|
|
|||
|
|
### Frontend
|
|||
|
|
| 文件 | 说明 | 优先级 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| `apps/lib/features/settings/data/models/profile_settings.dart` | 新增 PrivacySettings class,ProfileSettingsV1.privacy 类型修改 | P0 |
|
|||
|
|
| `apps/lib/features/settings/presentation/screens/privacy_notification_settings_screen.dart` | 添加 Do Not Sell 开关(主开关),保持 Profile Visibility 占位符 | P0 |
|
|||
|
|
| `apps/lib/features/settings/presentation/screens/legal_center_screen.dart` | 添加快捷入口按钮 | P1 |
|
|||
|
|
| `apps/lib/features/settings/presentation/widgets/settings_section_widgets.dart` | 复用 SettingsSwitchTile(已有) | - |
|
|||
|
|
|
|||
|
|
## 6. 测试计划
|
|||
|
|
|
|||
|
|
- [ ] 开关默认状态为开启
|
|||
|
|
- [ ] 开关切换后值正确保存到后端
|
|||
|
|
- [ ] App 重启后设置正确恢复
|
|||
|
|
- [ ] 中/英/繁语言正确显示
|
|||
|
|
|
|||
|
|
## 7. 注意事项
|
|||
|
|
|
|||
|
|
1. **数据模型升级**: `privacy` 从 `Map` 升级为结构化 `PrivacySettings` 对象
|
|||
|
|
2. **向后兼容**: 后端需要兼容现有 `privacy: dict` 数据的读取
|
|||
|
|
3. **合规文案**: 使用 "Limit Use" 而非 "We Don't Sell",避免直接承诺
|
|||
|
|
4. **默认值**: 默认开启(opt-out),符合隐私保护趋势
|
|||
|
|
5. **开关位置**: 主开关在隐私设置页,法律中心只提供快捷入口(避免重复)
|
|||
|
|
6. **开关形式**: 使用 `SettingsSwitchTile` 组件(已有)
|
|||
|
|
7. **法律中心实现**: 添加快捷按钮(SettingsMenuTile),显示当前状态,点击跳转
|
|||
|
|
8. **后端兼容性**: 由于使用 JSONB 扩展,无需数据库迁移
|
|||
|
|
9. **状态同步**: 确保法律中心快捷按钮显示的状态与实际设置一致
|
|||
|
|
10. **Profile Visibility**: 保持现状(占位符 + Coming Soon),不影响本次需求
|