外观
Android 功能说明 — CodeNote Android
1. 概述
CodeNote Android 是 CodeNote 二维码管理系统的移动端应用。
1.1 核心定位
- 离线优先设计:所有核心功能(扫码/生成/编辑/搜索)完全离线可用,联网后自动同步
- 组织协作工具:支持多组织切换、资源共享,满足协作需求
- 智能扫码引擎:CameraX + ZXing 纯本地扫码,自动识别类型并路由到对应业务模块
1.2 技术栈
- 语言: Kotlin (100%)
- UI 框架: Jetpack Compose + Material 3
- 架构模式: MVVM + Clean Architecture
- 依赖注入: Dagger Hilt
- 本地数据库: Room + FTS4 全文索引
- 扫码引擎: CameraX + ZXing(纯本地)
- 二维码生成: ZXing(本地生成)
- 网络请求: Retrofit + OkHttp
- 图片加载: Coil(支持 AsyncImage 加载网络头像和图片)
- 后台任务: WorkManager(同步触发)
- 最低版本: Android 8.0 (API 26)
- 核心能力: 14个功能模块、完全离线可用(扫码/生成/编辑/搜索)、离线队列与增量同步、冲突仲裁(last-write-wins)
- 完整CRUD:所有主要实体(二维码/活动/资源/组织)均支持完整的创建/读取/更新/删除操作
1.3 角色与使用场景
主要角色场景:
| 角色场景 | 说明 | 可用功能 |
|---|---|---|
| 设备身份登录 | 登录页「免注册」tab 主动选择设备登录时自动注册账号(DEVICE identity),与手机/邮箱登录平级 | 可绑定手机/邮箱/用户名密码,可管理绑定设备 |
| 组织 OWNER | 创建或接管组织的用户 | 可管理组织成员、共享资源、转移所有权、解散组织 |
| 组织 ADMIN | 被提升为管理员的用户 | 可管理组织成员和资源,但不可解散组织 |
权限特点:
- 支持多组织切换:用户可在头像下拉菜单中切换当前活跃组织
- 离线优先:所有核心功能完全离线可用,联网后自动同步
- 扫码智能路由:根据二维码类型自动跳转到对应业务模块
- 数据隔离:只显示当前用户有权限访问的个人数据和组织共享数据
1.3.1 权限控制说明(实施)
组织内页面操作按钮按 ORG 域权限点显隐,无权限不显示(不再出现“按钮可见、提交才报无权限”):
- 统一权限入口:
OrgRepository.getOrgPermissions(orgId)(复用 GET /orgs/{id} 的 permissions 字段),页面 ViewModel 在加载数据时并行拉取,存入 UiState - 权限常量:
feature/org/.../OrgPerms.kt(与后端 ORG 域权限点一一对应,resource:action 格式) - 个人场景(无 orgId / orgId=-1,如个人资产、个人二维码、个人活动)不限制;组织场景(组织资产、组织活动、组织成员/设置/分享等)按权限显隐
- 组织资产/活动详情页的 orgId 通过导航链传递(FormStateManager),子页(编辑/盘点/通行证)沿用
- 后端
@RequirePermission仍为最终校验,客户端显隐仅是第一道防线
已覆盖的页面与权限点(详见/architecture/overview.md §3.5 映射表):组织成员(member:update_role/remove)、邀请记录(invitation:cancel)、角色权限(rbac_role:)、组织资产/详情/编辑/盘点(asset:)、资产分类(asset_category:)、位置(location:)、组织模板(meta_template:*)、组织活动(activity:create/delete)、通行证(pass:issue/verify)、组织分享(share:delete)。
2. 项目结构
codenote-android/
├── app/ # 应用主模块
│ ├── src/main/
│ │ ├── kotlin/com/codenote/
│ │ │ ├── CodenoteApp.kt # Application 入口
│ │ │ ├── MainActivity.kt # 主 Activity
│ │ │ ├── ui/
│ │ │ │ ├── theme/ # Material 3 主题配置
│ │ │ │ │ ├── Color.kt # 颜色定义
│ │ │ │ │ ├── Theme.kt # 主题配置
│ │ │ │ │ └── Type.kt # 字体配置
│ │ │ │ └── navigation/ # 导航配置
│ │ │ └── di/ # Hilt 依赖注入模块
│ │ └── res/ # 资源文件
│ └── build.gradle.kts
├── core/ # 核心模块
│ ├── common/ # 通用工具
│ │ └── EventBus.kt # 全局事件总线
│ ├── data/ # 数据层
│ │ ├── local/ # 本地数据源(Room + DataStore)
│ │ │ ├── DeviceUserManager.kt # 设备用户管理器
│ │ │ ├── TokenStore.kt # Token 持久化
│ │ │ ├── SessionStore.kt # 会话状态持久化
│ │ │ ├── AccountStore.kt # 账号信息存储
│ │ │ └── UserPreferences.kt # 用户偏好设置
│ │ ├── remote/ # 远程数据源(Retrofit)
│ │ ├── repository/ # 数据仓库
│ │ └── manager/ # 管理器
│ │ ├── IdentityManager.kt # 统一身份管理层(接口)
│ │ └── IdentityManagerImpl.kt # 统一身份管理层(实现)
│ ├── domain/ # 领域层
│ │ ├── model/ # 领域模型
│ │ ├── repository/ # 仓库接口
│ │ └── usecase/ # 用例
│ ├── ui/ # UI 基础设施
│ │ └── ToastHost.kt # 统一信息提示
│ └── network/ # 网络层
│ ├── RetrofitClient.kt # Retrofit 客户端
│ ├── ApiService.kt # API 服务接口
│ ├── AuthInterceptor.kt # 认证拦截器
│ └── TokenRefresher.kt # Token 自动刷新
├── feature/ # 功能模块(14 个)
│ ├── auth/ # 用户认证(登录/注册)
│ ├── camera/ # 相机中心(扫码/水印拍照/文档扫描)
│ ├── qrcode/ # 二维码管理(列表/详情/创建/编辑/批量/分类管理)
│ ├── asset/ # 固定资源管理(含资源分类管理页面)
│ ├── activity/ # 活动管理(含签到/通行子功能入口)
│ ├── checkin/ # 签到系统(活动的子功能)
│ ├── pass/ # 通行证系统(活动的子功能)
│ ├── album/ # 水印相册
│ ├── org/ # 组织管理
│ ├── notification/ # 通知中心
│ ├── profile/ # 个人中心/设置
│ ├── home/ # 主页
│ └── sync/ # 云同步
├── build.gradle.kts # 根构建配置
├── settings.gradle.kts # 项目设置
└── README.md # 项目说明3. 功能清单
3.1 核心基础模块
| # | 模块 | Feature 模块 | 主要功能 |
|---|---|---|---|
| M1 | 用户认证 | auth | 注册/登录/JWT 刷新/个人信息/BCrypt 密码加密/会话管理/多账号切换 |
| M2 | 相机中心 | camera | CameraX + ZXing 扫码/水印拍照/文档扫描/结果操作/最近扫码记录 |
| M3 | 二维码管理 | qrcode | 本地生成(ZXing)/批量生成/分类管理/搜索筛选(FTS4)/收藏置顶/公开分享/回收站 |
| M4 | 分类管理 | qrcode | 树形分类、自定义颜色/排序/软删除/置顶/批量置顶/分类管理页面(创建/编辑/删除/颜色选择/拖曳排序/移动到顶级) |
| M5 | 会话管理 | profile | 多会话管理(创建/切换/注销)、账号关联(添加/切换/移除) |
| M6 | 通知中心 | notification | 系统通知/已读未读/未读角标/分页展示/列表 bodyPlain 摘要 + 详情 MarkdownView 渲染 |
| M7 | 云同步 | sync | 首次全量拉取/增量同步/离线补传/LWW 冲突仲裁/指数退避重试 |
| M8 | 协议展示 | settings | 用户协议/隐私政策(MarkdownView 展示后端渲染 HTML,入口:设置页) |
3.2 组织模块
| # | 模块 | Feature 模块 | 主要功能 |
|---|---|---|---|
| B1 | 组织管理 | org | 创建/加入/退出/解散/组织码/二维码/三级角色(OWNER/ADMIN/MEMBER)/转移所有权/切换当前组织 |
3.3 业务模块
| # | 模块 | Feature 模块 | 主要功能 |
|---|---|---|---|
| B4 | 固定资源管理 | asset | 资源登记/树形分类(组织分类)/分类置顶/批量置顶/资源二维码(TYPE=ASSET)/流转(+审批)/盘点/报废(直接标记SCRAPPED,无审批流程)/批量导入导出/资源地图(GPS 标记) |
| B6 | 活动管理 | activity | 活动创建/编辑/删除/状态流转(DRAFT→ACTIVE→ENDED/CANCELLED)/子功能开关(has_check_in/has_pass)/容器式管理 |
| B7 | 签到系统 | checkin | 活动的子功能/扫码签到/GPS 校验(可选)/次数限制/离线签到/手动签到码/check_in_records 表 |
| B8 | 通行证系统 | pass | 活动的子功能/签发通行证/通行核验(扫码)/使用记录/撤销通行证/pass_templates/passes/pass_usage_records 表 |
| B9 | 水印相册 | album | 水印拍照/二维码关联/OSS 备份/按二维码筛选/网格浏览/大图查看/图片编辑 |
3.4 详细功能说明
3.4.1 相机中心(首页默认)
架构:统一 CameraX 底座(CameraContainer)+ 业务 Tab 委派(扫码/水印相机)
全局特性(所有 Tab 共享):
- CameraX 底座(CameraContainer)统一管理预览、缩放、闪光灯、触控对焦、生命同期
- 触控对焦:点击预览画面触发 FocusMeteringAction,显示对焦圈指示
- 长按锁定 AE/AF:长按画面锁定曝光/对焦
- CameraState 监听:相机异常时通过 Snackbar 提示用户(如"相机被占用"、"相机出现致命错误")
- 闪光灯三端同步:顶部操作栏闪光灯按钮、预览界面左上角 FLSH 面板、相机设置页闪光灯选项,三处修改均即时持久化到记忆文件,切换页面后自动恢复。默认值=关闭
- 画面调整浮层:预览界面左上角提供 5 个半透明图标按钮(EV/WB/CLR/FLSH/重置),点击后隐藏其他按钮及重置按钮并向右弹出调整面板,所见即所得:
- 曝光补偿:横形滑块 -3EV ~ +3EV,实时同步到 CameraX
- 白平衡:5 选项按钮(自动/阴天/日光/白炽灯/荧光灯),横向排列
- 色彩滤镜:4 选项按钮(正常/黑白/负片/怀旧),横向排列
- 闪光灯:2 档按钮(关闭/开启),横向排列。三处可调(顶部栏/预览FLSH面板/设置页),记忆同步
- 重置:RestartAlt 图标按钮,点击恢复全部画面控制到默认值,即时生效
- 缩放持久化:扫码模式缩放值跨会话记忆
Tab 1:扫码
- CameraX + ZXing 条形码扫描,纯本地,不依赖网络
- 防抖策略:内容一致性检测(连续 3 帧识别结果相同才导航),替代原 2s 时间门
- 扫描线动画:Canvas 绘制四角扫描线 + 水平扫描动画(2s 周期往返)
- 扫码反馈:识别成功后 MediaPlayer 播放系统通知音(无震动)
- 多码识别:单码识别,每帧返回第一个识别结果
- 批量扫码模式:识别后不自动跳转,底部累积扫码记录列表,点击任一结果再跳转
- 缩放滑块:底部半透明浮层,显示缩放倍数,支持 1×~8×
- 智能路由:扫码内容自动识别 URL/文本/业务码类型,跳转对应模块
- 底部操作栏:相册导入 / 新建 / 列表 / 扫描历史
Tab 2:水印相机
- 实时预览:定位 + 天气 + 地址 + 自定义文字叠加到相机预览
- 拍照:一次拍照→降采样→水印叠加→JPEG压缩→本地保存→Room写入→同步OSS→写入系统相册
- 连拍模式:按住拍照按钮连续拍照(10 帧/300ms 间隔),仅保存最后一帧
- 定时拍照:3/5/10s 倒计时 overlay,倒计时结束自动拍照
- 水印预设:3 个预设模板一键切换(精简/详细/简约)
- 水印设置弹窗:可配置字段(日期/时间/GPS/地址/海拔/天气/备注),备注支持预置标签(施工现场/巡检记录/验收/会议)
- 右侧缩放竖条 + 微距模式切换(绿色=微距激活,红色=微距可用,灰色=不可用)
- 底部栏:最新缩略图(点击→相册)/ 连拍/定时按钮 / 拍照按钮 / 设置齿轮
Tab 3:文档扫描(未来扩展,预留占位)
额外页面:
- 相机设置页:通用设置(分辨率)、画面控制(曝光补偿/白平衡/色彩滤镜/闪光灯 2 档—按钮全列出)、扫码设置(声音/震动/批量模式)、关于—支持"默认"按钮一键恢复顶部标题栏右侧
- 扫码专用设置页:分辨率/声音/震动/批量模式(从相机设置页独立拆分)
- 扫描历史记录页:分页展示(20 条/页)、按天分组(今天/昨天/更早)、搜索、长按删除、清空
- 锁屏相机快捷入口:锁屏状态下直接打开扫码页
- 桌面小部件:1×1 扫码快捷图标,点击直接进入扫码页
扩展方式(未来):新增 Tab 只需在 feature/camera/ 下建子目录 → CameraScreen 加 1 项 Tab
3.4.2 用户认证
功能概述:
Android 端采用“设备优先 + 多方式登录”的用户认证策略:
- 首次安装:自动生成基于设备 ID 的临时用户,无需注册即可使用
- 手机号登录:支持手机号+验证码(PNVS)或手机号+密码,首次登录自动注册
- 离线模式:所有核心功能完全离线可用,数据保存在本地
- 账户绑定:用户可在个人中心设置用户名/密码/手机号,实现跨设备登录
- 数据同步:绑定账户后,本地数据自动同步到服务器
- 多设备支持:使用相同账户在不同设备登录,数据自动合并
技术实现:
- 设备 ID 生成:
device_{android_id}(ANDROID_ID 持久,卸载重装不变) - 本地存储:DataStore 保存设备 ID 与本地用户缓存
- 登录机制:首次进入显示登录页(不静默注册);「一键登录」tab 需点击按钮才调 SDK 弹运营商授权页;「免注册」tab 调
POST /api/auth/device-login(查 DEVICE identity,无则自动建用户+登记设备);登出后重启回登录页 - 设备不绑定账号:DEVICE identity 仅存在于「无其他登录方式」窗口期——① 登出 = 解绑设备(登出时服务端删除该设备 DEVICE identity,之后免注册登录创建新账号);② 设备注册用户绑定手机/邮箱/设置密码成功后,自动删除其全部 DEVICE identity,此后免注册登录创建全新账号;③ 凭据登录(手机/邮箱/密码)不再自动登记设备身份
- 设备管理 = 登录设备管理:展示登录过当前账号的设备(设备名/最后活跃时间/当前设备标记);移除 = 吊销该设备上本账号的全部会话(token 立即失效),该设备需重新登录;当前设备不可移除(请用退出登录)
- 解绑保护:解绑手机/邮箱时若将无任何登录方式,后端拒绝(提示先设置用户名密码或绑定其他方式);解绑点击有确认弹窗(文案三档:无任何方式/仅剩设备/正常),解绑失败 Toast 提示
- 本机号码绑定:点「使用本机号码绑定」→ 弹运营商授权页 → 服务端取号自动绑定(不要求手动输手机号)
- 注销账户:账户设置底部「注销账户」→ 确认弹窗(红字警告)→ 删除账号全部登录方式 → 清本地跳登录页
- 一账号多设备:手机/邮箱/密码登录成功自动登记当前设备为 DEVICE identity;「设备管理」页可查看/移除绑定设备(设备丢失处置)
3.4.2.1 会话管理(AccountSwitchScreen)
功能概述: 会话管理是用户认证的延伸,允许用户管理多个登录会话。
核心页面:
AccountSwitchScreen:展示当前设备的所有会话列表,支持切换/注销- 入口:个人中心 → 会话管理
会话操作:
- 创建会话:登录后自动创建,记录 deviceId/deviceName
- 切换会话:调用
/api/auth/sessions/{sessionId}/switch获取新 Token - 注销会话:调用
DELETE /api/auth/sessions/{sessionId}使会话失效
会话去重机制:
- 同一设备+用户组合只保留一条有效会话
- 服务端
user_sessions表管理会话生命周期 - Token 携带
sessionId用于验证
3.4.2.2 多账号切换
功能概述: 用户可以关联多个账号,在个人中心一键切换。
核心组件:
IdentityManager/IdentityManagerImpl:统一身份管理层AccountRepositoryImpl:账号相关的仓库实现AccountSwitchViewModel:账号切换 ViewModel
操作流程:
- 添加关联账号:添加关联账号页支持三种验证方式(Tab 切换):
- 账号密码:输入目标账号 ID + 密码
- 手机验证码:输入目标账号手机号 → 获取验证码(60s 倒计时)→ 输入验证码;验证码发送到该手机号(需已绑定 CodeNote 账号)
- 邮箱验证码:输入目标账号邮箱 → 获取验证码(60s 倒计时)→ 输入验证码;验证码发送到该邮箱(需已绑定 CodeNote 账号)
- 支持跳转注册页创建新账号
- 切换账号:调用
/api/auth/accounts/{targetUserId}/switch获取新 Token - 移除关联:调用
/api/auth/link/{linkId}解除关联
技术细节:
- 关联关系记录在服务端
account_links表 - 验证码关联调用
/api/auth/sms/link-code、/api/auth/email/link-code发码,/api/auth/link传verifyType(PHONE_CODE/EMAIL_CODE)+ credential + code 完成关联 - 关联成功后被关联账号收到站内通知(账号关联提醒)
- 切换时重新初始化 Token 和用户信息
SessionHelper:提取会话管理公共逻辑
3.4.3 二维码管理
- 本地生成:文字/URL/WiFi/联系人/电话/短信/邮件/定位,ZXing Kotlin API,零网络依赖
- 批量生成:Excel 导入内容,批量生成+导出
- 分类管理:分类管理页面(创建/编辑/删除/颜色选择),树形自定义颜色/顺序
- 拖曳排序:长按拖曳调整分类顺序,支持同级重排和跨父级移动
- 置顶功能:单个分类置顶/取消置顶,批量置顶/取消置顶
- 移动到顶级:拖曳到列表顶部可将子分类移动到顶级
- 统一排序规则:置顶 > sortOrder > 创建时间(正序)
- 搜索/筛选:Room FTS4 全文索引:title + content
- 收藏/置顶:置顶+收藏
- 公开分享:生成分享链接
- 二维码样式:前景色/背景色/Logo/尺寸/格式
- 回收站:查看恢复/清空
- 二维码编辑(QrCodeEditScreen):
- 架构:复用
QrCodeFormScreen+QrCodeFormViewModel - id 传递:AppNavHost 在导航前通过
FormStateManager写入种子 id(crossPageSeedId("qr_code_edit")),composable内readFieldOrNull读取后作为参数qrCodeId传给QrCodeEditScreen - 加载数据:
QrCodeFormViewModel.loadQrCode(qrCodeId)从服务端加载详情并填充表单 - 修改信息:可修改二维码内容、类型、分类、前景色、背景色、Logo、尺寸、格式等所有字段
- 保存更改:提交时同时发送样式字段(foregroundColor/backgroundColor/logoUrl/size),更新后同步到服务器
- 独立页面:遵循编码规范,不使用弹窗
- 架构:复用
3.4.10 固定资源管理
后台表结构、API 参见
/reference/backend.md;meta_* 模型见/architecture/data-model.md,接口见/reference/api.mdA21 节。
资源入口:
- 组织入口:组织管理 → 组织资源
通用动态数据模型(实施,替代 动态字段系统):
- 服务端 3 张动态扩展表(field_definitions/asset_category_field_bindings/asset_field_values)已删除,改为 meta_* 四层模型(meta_objects/meta_fields/meta_templates/meta_template_fields/meta_object_bindings/meta_values);
- 8 个高频字段升为 assets 固定列(brand/model/serial_no/purchase_date/purchase_price/supplier/warranty_expiry/remark):Android
AssetDto新增 8 字段仅读返回;写统一走updateAssetFields()→PUT /api/meta/objects/asset/entities/{id}/values(服务端按 entity_column 转写固定列,杜绝双写);createAsset/updateAsset僵尸参数已删除; - 分类创建改为选模板:
AssetCategoryEditScreen删除硬编码 8 个 templateFieldList,改为拉取可见模板列表(PERSONAL→GLOBAL+USER;ORG→GLOBAL+本组织 ORG)多选绑定(默认模板+附加模板),create()/update()直接传templateIds,删除 presetDefIds/customDefIds/updateCategoryFieldBindings 三步流程; - 字段渲染大写类型:
DynamicFieldsSection统一fieldType.uppercase()分发(TEXT/TEXTAREA/NUMBER/DATE/DATETIME/BOOLEAN/SELECT...),config 驱动校验; - Room 版本 2→3(开发阶段无用户,重装即全新建库,无迁移脚本):
AssetEntity新增 supplier/warrantyExpiry/remark 3 列,amount ↔ purchase_price 用 @ColumnInfo 映射保留列名; - RepositoryImpls:getFieldDefinitions/getCategoryFieldBindings/updateCategoryFieldBindings 删除,换 getMetaFields/getMetaTemplates/getEntityTemplates/bindEntityTemplates;getAssetFields/updateAssetFields 改调 meta 端点。
资源列表(AssetScreen):
- 资源展示:显示资源名称、编号、分类、状态
- 树形分类:组织分类
- 搜索筛选:按名称/编号/分类/使用人/位置/状态筛选
- 创建入口:悬浮按钮快速创建资源
- 点击进入详情:点击资源卡片进入详情页
资源创建(AssetCreateScreen):
- 表单输入:
- 资源名称(必填)
- 资源编号(自动生成或手动输入)
- 资源分类(树形选择器,组织分类)
- 品牌/型号/序列号(选填)
- 采购日期/金额(选填)
- 使用人/位置(选填)
- 资源照片(多选上传,最多 5 张,缩略图网格预览)
- 临时保存:使用 FormStateManager 管理表单临时状态
- 独立页面:遵循编码规范,不使用弹窗
- 提交验证:检查必填项
资源编辑(AssetEditScreen):
- 加载数据:从服务器加载资源详情并填充表单
- 修改信息:可修改所有资源字段
- 删除资源:提供删除按钮(需二次确认)
- 保存更改:提交后同步到服务器
- 资源流转:提供流转入口(责任人变更+位置变更)
资源详情(AssetDetailScreen):
- 基本信息:显示资源所有字段信息
- 二维码展示:自动生成唯一二维码,扫码即看详情
- 编辑操作:OWNER 可编辑资源信息
- 流转操作:发起资源流转申请
- 盘点操作:参与资源盘点任务
- 地图定位:GPS 标记资源位置(资源地图功能)
资源分类管理(AssetCategoryManageScreen):
- 树形分类:IT设备→笔记本→MacBook / 消防设施→灭火器
- 分类置顶:单个资源分类置顶/取消置顶,批量置顶/取消置顶
- 拖曳排序:长按拖曳调整分类顺序,支持同级重排和跨父级移动
- 统一排序规则:置顶 > sortOrder > 创建时间(正序)
- 分类操作:创建/编辑/删除分类,自定义颜色
- 分类模板字段(新增):创建资源分类时可勾选预置字段模板(品牌/制造商、型号/规格、序列号、资源编号、购买日期、购买价格、使用部门、使用人、供应商、保修到期日、资源状态、备注),保存后自动绑定到分类,该分类下的资源编辑页直接显示这些字段
资源检索与统计:
多维度筛选:分类/使用人/位置/状态/搜索
资源盘点:创建盘点任务→扫码核对→盘盈盘亏报告
离线操作:资源登记/编辑/查看均支持离线
扫码执行:扫码→填写结果(正常/异常/待修)→拍照留证→提交
3.4.6 活动管理
活动完整业务流程(创建流程、子功能配置管理、状态流转、配额校验)参见
/designs/activity.md。以下仅列出 Android 端特定内容。
活动入口(多入口模式,§可见性架构):
- 首页入口:首页快捷操作卡片 → 活动(个人活动,PERSONAL 容器)
- 组织入口:组织首页 → 功能入口 → 组织活动(组织活动,ORGANIZATION 容器;MEMBER 无「发起活动」tab)
- 发现页:底部导航新增「发现」tab → 公开活动 / 公开组织(PUBLIC 资源,点击进详情/组织主页)
- 组织预览页(OrgPreviewScreen):非成员点公开组织 → 信息 + 按 joinPolicy 申请加入(
applyOrg);成员 → 进入组织主页(permissions 非空判断)
活动列表(ActivityListScreen):
- 活动展示:显示活动名称、描述、地点、时间范围
- 状态标识:显示活动状态(草稿/进行中/已结束/已取消)
- 子功能标签:显示是否启用签到/通行证功能
- 搜索筛选:按名称搜索、按状态筛选
- 创建入口:悬浮按钮快速创建活动(带入口对应的 ownerId)
- 点击进入详情:点击活动卡片进入详情页;草稿(DRAFT)点击进入编辑页(第一步:继续编辑/保存/发布)
- 归属过滤:个人场景传 ownerType=PERSONAL;组织场景传 ownerType=ORGANIZATION + ownerId
活动创建(ActivityCreateScreen):
- 三步引导:基本信息 → 时间地点 → 子功能(第 2 步改版)
- 表单输入:
- 活动名称(必填)/ 活动描述(选填)/ 开始时间(必填)/ 结束时间(必填)/ 封面图片(选填)
- 可见性选择(第 1 步,§可见性架构):按容器动态渲染——个人:仅邀请(默认)/公开;组织:公开/组织内部(默认)/仅邀请
- 第 2 步地址区(地图定位 + 表单录入):
- 地图区:AmapWebMap(高德 JS API),固定高 240dp 贴屏幕左右边;点击/拖动选点取中心回填;顶部提示条「拖动地图,精准定位地址」(可关闭);右上黑色 X 收起地图(可展开恢复);右下靶心按钮定位到当前位置
- 搜索定位栏:POI 真实候选下拉(高德 Web API),选中回填「所在地区(region,省市区)」+「详细地址(location)」+ 图钉定位
- 智能粘贴:行右侧扫描框图标,读剪贴板解析回填 手机号/联系人/地址(parseClipboardText 占位实现)
- 表单行(细线分隔):所在地区(下拉选择,占位列表)/ 详细地址(门牌号)/ 联系人(红星必填)/ 手机号
- 表单字段由 AddressViewModel(屏幕级)管理,实时同步 ActivityFormViewModel
- 子功能开关:启用签到(has_check_in)/ 启用通行证(has_pass)
- 组织归属:根据入口自动携带 ownerId(首页个人活动 PERSONAL,组织入口 ORGANIZATION+组织ID)
- 临时保存:使用 FormStateManager 管理表单临时状态
- 提交验证:检查必填项(活动名称、联系人)和时间逻辑
活动编辑(ActivityEditScreen):
- 三步引导:与创建流程一致(基本信息→时间地点→子功能),加载草稿数据后从第 1 步继续编辑
- 可见性编辑:可切换可见性;INVITE_ONLY 展示邀请码 + 重置按钮(重置后旧码作废,需重新分享二维码)
- 加载数据:从服务器加载活动详情并填充表单
- 修改信息:可修改所有活动字段和子功能开关
- 删除活动:提供删除按钮(需二次确认,§6.6 无 activity:delete 权限不显示)
- 保存草稿/发布:第 3 步保存草稿(不发布)或发布(DRAFT→ACTIVE)
活动详情(ActivityDetailScreen):
- 基本信息:显示活动名称、描述、地点、时间、可见性标签;INVITE_ONLY 显示邀请码
- 状态展示:显示当前活动状态
- 子功能入口:根据 has_check_in / has_pass 动态显示签到/通行按钮
- 编辑操作:OWNER 可编辑活动信息
- 扫码即看:扫活动码直接进入详情页;INVITE_ONLY 二维码
activity:{id}:{code}扫码自动带凭证,无凭证访问 403 → 输入邀请码 - 权限校验:无权限(403)时显示邀请码输入框,输入正确邀请码后加载详情
3.4.7 签到系统(活动的子功能)
- 扫码签到:扫活动码 → 活动详情 → 点击签到;或扫独立签到码 → 直接签到
- GPS 定位校验:扫码时校验 GPS 位置是否符合活动半径
- 签到统计:活动内查看签到人数/次数
- 离线签到:离线记录,联网后自动补传
- 手动签到码:输入短码签到
3.4.8 通行证系统(活动的子功能)
- 签发通行证:活动内填写身份信息,签发通行证实例
- 通行核验:扫活动码 → 通行按钮 → 进入核验页
- 通行查验:管理人员查看持证人通行信息 + 使用记录
- 通行记录:查看该活动的所有通行使用记录
3.4.9 水印相机
水印相机全流程(预加载→拍照→降采样→水印叠加→压缩→保存→写入 Room→写入相册)参见
/designs/camera/watermark-flow.md。定位/天气获取规范参见/standards/location.md。
- 水印拍照:拍照时自动叠加GPS定位+时间+天气+海拔+自定义备注水印,调用 WatermarkViewModel 全流程管理
- 模式切换:相机中心底部 Tab 切换「扫码」/「水印相机」
- 水印设置:水印模式下点击齿轮图标打开 WatermarkSettingsSheet,支持备注输入(含预置标签)、显示海拔/天气开关
- 水印数据:拍照时并发获取定位(LocationService)、天气(Open-Meteo)、海拔,叠加到照片右下角半透明底条
- 保存流程:JPEG → 水印叠加 → 压缩(ImageCompressConfig.ALBUM_PHOTO) → 保存至 files/watermark/ → 写入 Room
- 相册浏览:网格+大图查看,优先加载 localPath(本地),回退至签名 URL
- 服务端同步:syncPhoto 自动上传本地水印照片至 OSS
- 数据库字段:新增 locationDesc / altitude / weather / remark(本地独占,不传服务端)
3.4.10 文件上传架构(直传OSS方案)
完整规范(压缩规则、上传流程、ViewModel 使用模式)参见
/standards/android-coding.md第 6 章"图片上传规范(OSS + STS 直传)"。以下为概述。
- 客户端直传阿里云OSS(不经过服务器)
- 智能图片压缩(95%压缩率)
- STS临时凭证(15分钟有效期,自动刷新)
- 自动重试机制(最多3次,指数退避)
- Fallback保底(失败时走服务端中转)
上传流程:
- 用户选择图片
- ImageCompressor压缩图片(根据不同场景配置不同参数)
- 调用服务端获取STS临时凭证(POST /api/files/sts-token)
- 使用临时凭证直传阿里云OSS
- 上传成功后回调服务端记录元数据(POST /api/files/callback)
- 返回OSS URL并更新UI
图片压缩规则:
| 场景 | 最大尺寸 | 质量 | 最大文件大小 | 典型压缩率 |
|---|---|---|---|---|
| 头像 | 800x800 | 85% | 200KB | 95% (2-5MB → 100-200KB) |
| 相册照片 | 1920x1920 | 80% | 500KB | 95% (5-10MB → 300-500KB) |
| 资源照片 | 1280x1280 | 75% | 300KB | 95% (3-8MB → 200-300KB) |
相关文件:
core/common/ImageCompressor.kt- 图片压缩工具core/data/repository/FileManager.kt- 文件管理器(核心)core/common/OssConfiguration.kt- OSS配置常量feature/profile/ProfileEditViewModel.kt- 头像上传示例
3.4.11 组织
组织入口:
- 首页入口:首页快捷操作卡片 → 组织
- 我的入口:个人中心菜单 → 组织
组织首页(OrgHomeScreen):
当前组织信息:显示组织名称、成员数量,支持切换组织
功能入口网格:
- 组织成员:管理成员列表和邀请新成员
- 组织设置:编辑组织信息、转让所有权、退出/解散组织
组织码展示:显示组织码用于邀请成员加入
成员预览:显示前3个成员,点击可查看全部
共享资源区块:展示成员分享到组织的二维码/相册副本(前3条 + 查看全部入口,跳 OrgSharesScreen)
组织成员管理(OrgMembersScreen):
- 成员列表:显示所有成员及其角色(OWNER/ADMIN/MEMBER);当前用户角色由 ViewModel 拉取(getOrg),决定审批/邀请/邀请记录按钮显隐
- 邀请成员:点击右上角
+跳转到 OrgInviteScreen 独立页面搜索并多选用户 - 审批申请:ADMIN/OWNER 可点击右上角审批按钮进入 OrgApprovalScreen 审批待加入申请
- 邀请记录:ADMIN/OWNER 点击右上角历史图标进入 OrgInvitationsManageScreen 查看/撤回邀请
- 角色管理:提升/降级成员角色(仅OWNER可用)
- 移除成员:从组织中移除成员(仅OWNER/ADMIN可用)
组织邀请(OrgInviteScreen)【新增】:
- 搜索用户:输入用户名/邮箱实时搜索(300ms 防抖)
- 多选用户:勾选后批量发送邀请
- 发送通知:被邀请用户收到 ORG_INVITE 类型通知,点击通知跳转到 OrgInvitationRespondScreen
组织邀请回应(OrgInvitationRespondScreen)【新增】:
- 入口:通知列表点击组织邀请通知,自动导航;「我的邀请」列表点击待处理邀请
- 展示:组织名称、编码、描述、成员数、邀请人
- 操作:接受/拒绝邀请
我的邀请列表(OrgMyInvitationsScreen)【新增】:
- 入口:组织首页抽屉 → 「组织邀请」
- 列表:展示全部邀请(组织名/邀请人/成员数/状态徽章),仅 PENDING 可点击进回应页
邀请记录管理(OrgInvitationsManageScreen)【新增】:
- 入口:组织成员页右上角历史图标(仅 ADMIN/OWNER)
- 列表:组织全部邀请(被邀请人/邮箱/邀请人/时间/状态)
- 撤回:PENDING 邀请可撤回(确认弹窗),撤回后对方无法接受
组织审批(OrgApprovalScreen)【新增】:
- 待审批列表:显示 PENDING 状态的成员申请(申请来源:用户使用加入策略 APPROVAL 的组织码加入)
- 操作:通过/拒绝(通知申请人结果)
组织资源(OrgResourcesScreen): 共享资源管理(OrgSharesScreen)【新增】:
- 入口:组织首页共享资源区块「查看全部」/ 组织设置 → 共享资源
- 列表:展示成员分享到组织的资源(二维码/相册照片,含副本名称)
- 取消共享:删除组织副本 + 映射(仅 OWNER/ADMIN,share:delete 权限,二次确认)
组织设置(OrgSettingsScreen):
- 组织信息:查看/编辑组织详情(名称/描述/加入策略/成员上限/状态)
- 位置管理:管理组织内位置层级
- 共享资源:查看/取消共享(share:read/share:delete)
- 组织码(OrgCodeScreen,点击进入新页面):组织码大字展示/复制;申请加入二维码(内容
org-join:{组织码},其他用户扫码 → 加入页 → 申请加入);复制申请链接;分享申请链接给指定用户(搜索选择 → 发 ORG_JOIN_LINK 通知,对方点通知查看组织信息并申请加入);重置组织码(org:regenerate_code 权限控制) - 扫码加入:扫描
org-join:{code}二维码 → 扫描结果页「申请加入组织」按钮 → 加入页(自动预填组织码) - 通知跳转:ORG_JOIN_LINK 通知点击 → 加入页(按组织 ID 拉取公开信息)
- 角色权限:自定义角色管理(仅 RBAC_ROLE_CREATE)
- 转让所有权:仅 ORG_TRANSFER
- 退出/解散组织:按 ORG_LEAVE / ORG_DISSOLVE 权限显隐
组织编辑(OrgEditScreen):
- 加载数据:从服务器加载组织详情并填充表单
- 修改信息:可修改组织名称和描述
- 保存更改:提交后同步到服务器
- 独立页面:遵循编码规范,不使用弹窗
组织转让(OrgTransferScreen):
- 成员列表:显示所有可转让的成员(排除当前用户)
- 选择新OWNER:点击成员卡片选择新的所有者
- 二次确认:转让前需要二次确认,防止误操作
- 权限要求:仅 OWNER 角色可用
- 转让流程:选择成员 → 确认转让 → 同步到服务器
功能定位:
- 线上功能:所有组织功能需要联网使用,不考虑离线场景
- 独立页面:每个功能模块都有独立的页面,减少弹窗使用
- 角色权限:不同角色(OWNER/ADMIN/MEMBER)看到不同的操作选项
3.4.12 主题切换(新增)
功能入口:
- 首页标题栏:扫码图标左侧的 ☀ 空心图标按钮
- 抽屉菜单:抽屉面板左侧标题行头像前的同款按钮
切换逻辑:
- 点击在 LIGHT ↔ DARK 两态互切
- 未手动设置时(SYSTEM)跟随系统深色模式
- 切换后立即持久化到 DataStore(
AppPreferences.themeMode),应用内所有页面即时同步
技术实现:
core/theme/Theme.kt:新增ThemeMode枚举(SYSTEM/LIGHT/DARK),CodeNoteTheme接收themeMode参数替代原darkThemecore/data/local/AppPreferences:已有themeMode字段(0=SYSTEM, 1=LIGHT, 2=DARK),setThemeMode()写入 DataStoreHomeViewModel:注入AppPreferences,提供toggleTheme()方法MainActivity:收集appPreferences.themeModeFlow 传入CodeNoteTheme
3.4.13 首页抽屉菜单(重构)
功能入口:首页标题栏左侧三横菜单图标
布局结构:
┌─ 顶部入口 ──────────────────────┐
│ 二维码扫描 / 创建二维码 / 水印相机 │
├──────────────────────────────────┤
│ 📷 相机功能 │
│ 扫描二维码 / 水印相机 / 相册 │
├──────────────────────────────────┤
│ 📱 二维码功能 │
│ 生成 / 分类 / 批量导入 / 二维码列表│
├──────────────────────────────────┤
│ 🏢 组织功能 │
│ 成员 / 资产 / 资产分类 / 存放位置 │
│ 活动 / 签到→活动列表 / 通行证→活动│
├──────────────────────────────────┤
│ 📌 其他 │
│ 通知 / 设置 │
└──────────────────────────────────┘分组规则:
- 功能按「相机」「二维码」「组织」「其他」分组显示
- 未选中当前活跃组织时,成员/资产分类/存放位置三项跳转到组织首页
- 选中组织后通过
PUT /api/users/me/current-org同步到服务端 - App 重启后从服务端
GET /api/auth/userinfo回填当前组织
3.5 离线能力说明
完全离线可用(无网也能操作):
- 创建/编辑/删除二维码(含本地生成二维码图片)
- 创建/编辑/删除分类
- 扫码(ZXing 纯本地)
- 签到(含 GPS 校验,离线记录补传)
- 拍照+水印(照片存本地,有网后同步到 OSS)
- 通行证验证(本地缓存白名单)
- 本地相册浏览
- 资源登记/编辑
- 搜索/筛选(Room 本地数据库 + FTS4 全文索引)
仅限联网:
- 组织管理(邀请成员)
- 组织共享资源
- 查看他人公开分享
- 资源流转审批
- 查看组织统计数据
3.6 同步原则
所有个人数据操作(创建/编辑/删除)都走本地 Room,有网时同步到服务端。
- 首次全量拉取:登录后全量同步到本地
- 增量同步:前台活跃时自动拉取变更,无固定轮询
- 离线补传:离线创建/编辑/删除标记 unsynced,联网后自动推送
- 冲突解决:LWW(Last-Write-Wins)策略,服务端仲裁;
SyncManager.pullChanges()实现 LWW 策略拉取服务端增量数据,SyncService.resolve()支持客户端选择接受或拒绝服务端版本
3.7 会话管理与登录记录
详细规范(数据表设计、API 接口、Session 状态机、安全规范)参见
/designs/auth/user-state.md。以下为 Android 端实现摘要。
会话管理(user_sessions 表)
- 核心职责:管理用户登录会话的生命周期,维护 Token 有效性
- 去重机制:同一设备+用户组合只保留一条有效会话,避免重复创建
- 状态字段:
is_current:标记是否为当前会话(Android 端本地判断,服务端依据 last_active_time)expired_at:会话过期时间(服务端下发)last_active_time:最后活跃时间status:ACTIVE / EXPIRED / REVOKED(服务端管理)
登录记录(login_records 表)
- 核心职责:记录每次登录事件,用于安全审计
- 记录字段:
device_id/device_name:设备信息ip_address:登录IP地址login_type:登录类型(password/device/qrcode/oauth)status:登录状态(success/failed)failure_reason:失败原因
技术实现
- 会话创建:
DeviceUserManager.initializeDeviceUser()检查本地是否已有有效会话;无有效会话时调用POST /api/auth/sessions创建新会话 - 登录记录:登录/注册成功后调用
POST /api/login-records记录登录事件 - 数据持久化:
EncryptedTokenStore:Token 加密持久化SessionStore/DataStoreSessionStore:会话状态 DataStore 持久化AccountStore:账号信息 DataStore 持久化
- 身份管理层:
IdentityManagerImpl统一管理用户身份、会话和 Token - 会话切换:
AccountSwitchScreen+AccountSwitchViewModel负责多会话 UI 交互- 切换会话 →
POST /api/auth/sessions/{sessionId}/switch - 注销会话 →
DELETE /api/auth/sessions/{sessionId}
- 切换会话 →
EOF
9. 网络层配置
9.1 AuthInterceptor 认证拦截器
安全架构(公开路径、权限校验、后端过滤器)参见
/reference/api.md第 6 节 和/designs/auth/user-state.md。以下为 Android 端客户端拦截器配置。
Android 端使用 AuthInterceptor.kt 拦截所有 HTTP 请求,自动处理认证逻辑。
公开路径(无需认证):
以下路径在 AuthInterceptor.kt 的 publicPaths 集合中配置,不会添加认证头:
| 路径 | 说明 |
|---|---|
/api/auth/login | 用户登录 |
/api/auth/register | 用户注册 |
/api/auth/refresh | Token 刷新(新增) |
/api/auth/device-login | 设备登录(自动注册,新增) |
认证逻辑:
- 请求路径在
publicPaths中 → 直接放行,不添加认证头 - 请求路径不在
publicPaths中:- 有 JWT Token → 添加
Authorization: Bearer {token}头 - 始终携带
X-Device-Id: {deviceId}(服务端校验与活跃会话一致;已无匿名设备认证)
- 有 JWT Token → 添加
配置文件:core/data/src/main/java/com/codenote/core/data/remote/AuthInterceptor.kt
Token 自动刷新时机(详见《用户状态管理规范》§7.6.1):
- 冷启动(Splash + autoRegisterLogin)→ force 刷新
- token 变化时 / 每 60s 定时检查 → 距过期 <5 分钟静默刷新
- 获取用户信息前(
getUserInfo())→ 距过期 <5 分钟刷新 - 任意请求 401(非公开路径)→ force 刷新 + 重放请求;刷新失败跳登录
- WebSocket 通知连接前 → force 刷新
- 401 兜底:统一 refresh;refresh 失败清登录态跳登录(不再区分设备用户/普通用户)
9.2 后端权限校验
所有需要认证的接口在后端通过以下机制校验:
- Spring Security:
SecurityConfig.kt配置permitAll()路径,其他路径必须认证 - JWT 过滤器:
JwtAuthFilter.kt解析 Token 或 Device ID,注入认证对象 - 权限注解:管理员接口使用
@PreAuthorize("hasRole('ADMIN')")强制校验角色
详细后端安全架构:参见 /reference/backend.md 第 1.4 节"安全架构"。
5. 二维码列表查询与详情相邻导航
5.1 设计说明(重构)
列表筛选/排序条件属于列表页 ViewModel 状态,不应作为全局单例暂存跨页传递(编码规范第 4.8 节)。 本节为重构后的行为说明。
5.2 列表页筛选/排序状态
- 列表筛选/排序条件为
QrCodeListViewModel内部状态,随列表页生命周期存在 - 分类、关键字、排序模式变化时:更新 ViewModel 状态 → 调用
loadData()重新加载 - 返回列表页时状态由 ViewModel 保留(ViewModel 绑定 NavBackStackEntry,返回栈内不销毁)
kotlin
// 选择分类
fun selectCategory(categoryId: Long?) {
_uiState.value = _uiState.value.copy(categoryId = categoryId)
loadData()
}
// 设置搜索关键字
fun setSearchQuery(query: String) {
_uiState.value = _uiState.value.copy(keyword = query.takeIf { it.isNotEmpty() })
loadData()
}
// 切换排序模式
fun toggleSortMode() {
val newMode = if (_uiState.value.sortMode == SortMode.DEFAULT)
SortMode.SORTORDER_AT_DESC else SortMode.DEFAULT
_uiState.value = _uiState.value.copy(sortMode = newMode)
loadData()
}5.3 详情页相邻导航(上一张/下一张)
- 详情页"上一张/下一张"不依赖列表页筛选条件(避免跨页状态污染)
QrCodeDetailViewModel.getAdjacentQrCodeId()按服务端"全部列表"默认排序查找相邻 ID- 到达边界时 Toast 提示"已经是第一张/最后一张"
kotlin
suspend fun getAdjacentQrCodeId(currentId: Long, direction: Int): Long? {
return qrCodeRepository.getAdjacentQrCode(
currentId = currentId,
categoryId = null, // 不传列表筛选条件
keyword = null,
sort = emptyList(),
direction = direction
)
}5.4 排序模式说明
| 模式 | 排序参数 | 说明 |
|---|---|---|
SortMode.DEFAULT | sortOrder,asc + createdAt,asc | 默认排序(旧的在前) |
SortMode.SORTORDER_AT_DESC | sortOrder,desc + createdAt,asc | 最新排序(新创建的在前) |
5.5 前后端参数映射
| Android 参数 | Server 参数 | 说明 |
|---|---|---|
categoryId | categoryId | 分类筛选 |
keyword | keyword | 搜索关键字 |
sortMode.toSortList() | sort | 排序参数列表 |
版本更新:App 内更新功能参见 /reference/app-update.md,支持大版本强制弹窗和小版本静默下载两种模式。
附录:账户设置页面与数据层
页面结构
- 账户设置 hub 页(
account_settings):个人中心 → 点击用户名进入;固定 4 入口(设置用户名 / 设置密码 / 绑定邮箱 / 绑定手机号码),任何用户类型都显示,内页判断可用性;顶部状态卡(昵称/用户名/邮箱/脱敏手机/设备标识) - 页面:
email_bind(绑定邮箱+解绑)、change_password(password=null 时引导去设置密码,原密码/手机码/邮箱码三选一验证)、forgot_password(未登录,手机/邮箱自动识别) - SetUsernameScreen / SetPasswordScreen:用户名/密码拆分为两个独立入口;用户先设置用户名(下一步)→ 设置密码两步完成;有密码用户进设置密码页引导去修改密码
- PhoneBindingScreen 迁入账户设置:加已绑定显示(脱敏)+ 解绑
- LoginScreen:账号密码 tab 加「忘记密码」链接
- ProfileScreen:显示昵称(默认),点击用户名 → 账户设置 hub;昵称下方展示个性签名(未设置不显示,最多 2 行省略)
- ProfileEditScreen:头像卡下方新增「个性签名」编辑区(≤100 字计数 + 保存签名按钮,保存走 updateProfile)
数据层
- UserInfoResponse:username/email 可空;含 nickname/phone/identities/hasPassword/hasRecoverableCredential
- ApiService:device-login 返回 LoginResponse;devices 列表/移除;phone/email/oneclick-login 返回 LoginResponse
- DeviceUserManager:保留 deviceId 生成与本地缓存
- DeviceManagementScreen/ViewModel(设备列表/移除)
- IdentityManager:registerDeviceUser → deviceLogin(静默自动注册);登录成功且 hasRecoverableCredential=false 时 EventBus 弹一次性提醒(建议绑定手机/邮箱)
- AuthInterceptor:token 失效统一 refresh
RBAC 权限接入
数据层:
OrgModel:删role,增permissions: List<String>+roleName: String?OrgMemberModel:删role,增roleName: String+isOwner: BooleanOrgShareModel:entityId→sourceId(原件)+copyId(副本)LoginResponse/MyPermissionsResponse:权限点集合(APP+PLATFORM / APP+当前组织 ORG);GET /api/rbac/my-permissions全局拉取(ApiService)- Room 迁移 v2:
org_members.role→roleName + isOwner(重建表保留数据,MIGRATION_1_2)
UI 判断(统一收口 OrgPerms 常量对象,§6.6):
- 解散组织
org:dissolve/ 退出组织org:leave(OrgHomeScreen / OrgSettingsScreen) - 编辑组织信息
org:update;我的角色展示roleName(OrgProfileScreen) - 成员管理:审批
member:approve、邀请member:invite、邀请记录invitation:read、升降级member:update_role、移除member:remove;成员标签roleName、转让过滤isOwner(OrgMembersScreen / OrgTransferScreen) - OrgSettingsScreen 转让/解散/退出三入口按权限显隐(§1.2.17 存量漏洞修复)
403 处理:ApiService 拦截器提取后端 message(「无权限执行该操作」)→ Toast 提示;敏感操作前端按 permissions 预检。
Markdown 渲染与协议展示
统一渲染出口:富文本内容(协议/通知/更新日志)由后端 commonmark 渲染为安全 HTML,App 端零解析、零第三方解析库。接口约定:xxx 原文 + xxxHtml 渲染(摘要场景 xxxPlain)。实现细节见 /reference/backend.md「Markdown 统一渲染」。
MarkdownView 通用组件(core:ui 的 component/MarkdownView.kt):
- WebView 展示后端 HTML,
javaScriptEnabled=false(双保险) update回调保证异步数据到达后重新加载(避免首帧空白)- 链接默认交系统浏览器(仅 http/https);可传
onLinkClick拦截 - 可传
baseUrl支持相对路径图片
协议页(feature/settings/AgreementScreen.kt):
- 入口:设置页「用户协议」(SERVICE)/「隐私政策」(PRIVACY)两个独立入口(图标区分:Description/Lock)→ 路由
AGREEMENT(带type参数) AgreementViewModel从 SavedStateHandle 读 type,调GET /api/agreement/current?type=(公开接口)MarkdownView(html = contentHtml)渲染,标题取title
数据链路:AgreementResponse(remote model)→ AgreementInfo(domain model)→ AgreementRepository(AgreementRepositoryImpl + Hilt Binds)。
通知 Markdown 化:
- 列表接口返回
bodyPlain(纯文本摘要,列表项展示);新增详情接口GET /api/notifications/{id}返回bodyHtml - 通知列表项:
bodyPlain(正文带 Markdown 语法时不暴露#/**) - 详情页:点开先展示列表数据,异步拉详情后
MarkdownView(bodyHtml)渲染;拉取失败兑底纯文本 - 系统通知(组织邀请等)为纯文本 body,渲染为普通段落,无需特判
登录页协议同意:
- 登录页底部「已阅读并同意《用户服务协议》和《隐私政策》」勾选(Checkbox),两个协议可点击跳转协议页
- 所有登录入口(一键登录/验证码/免注册/账号密码/邮箱)未勾选时点击 → Toast「请先阅读并同意」拦截,勾选后放行
联系人聊天模块(新增,feature/chat)
完整设计见专用文档
/designs/messaging/contacts-chat.md(§10 Android 端设计 / §19 代码对照)。此处仅索引。
模块:feature/chat(多模块架构第 16 个 feature 模块,依赖 core:data/ui/common/domain/theme)
Room(core/data/local/chat,DB version 3→4,无存量用户不写迁移):chat_messages(clientMsgId 本地主键 + 服务端 id 游标 + FTS4 全文索引)、chat_conversations(置顶/免打扰/未读/草稿/删除标记)、chat_contacts(备注/标签/拉黑/双向标记)
数据层:ChatApiService(Retrofit 独立接口,不膨胀 ApiService)、ChatRepository(发送管道:先本地 SENDING → API → 回填 SENT,clientMsgId 幂等重发;增量 sync 游标补拉)、ChatWsManager(/ws/chat 多设备长连接 + 指数退避重连)、ChatSyncWorker(WorkManager 15min 后台兜底)、ChatWsBootstrap(App 启动挂 token 流监听:登录自动连接、登出清本地缓存防串号)
页面(ui/):ConversationListScreen(会话列表:置顶/未读角标/长按菜单)、ChatScreen(气泡页 + 输入栏,语音/图片/视频/文件扩展点)、ContactsScreen(通讯录 + 新的朋友红点 + 我的二维码入口)、ContactRequestListScreen(申请通过/拒绝)、ContactProfileScreen(资料页:备注/拉黑/删除)、UserFromQrCodeScreen(扫码名片码 → 添加好友)、MyQrCodeScreen(名片码展示/重置)、ChatSettingsScreen(本地设置:通知/流量节省/存储管理占位)
导航:ChatNavigation(嵌套导航图,AppNavHost 注册 NavRoutes.CHAT);扫码 user: 前缀 → onNavigateToChatUser 回调进入聊天模块
媒体上传:FileManager.uploadChatImage()(CHAT_IMAGE 压缩规则 1920px/85/800KB,新增);语音/视频/文件上传复用底层管道(feature 内扩展)