Skip to content

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相机中心cameraCameraX + 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 端采用“设备优先 + 多方式登录”的用户认证策略:

  1. 首次安装:自动生成基于设备 ID 的临时用户,无需注册即可使用
  2. 手机号登录:支持手机号+验证码(PNVS)或手机号+密码,首次登录自动注册
  3. 离线模式:所有核心功能完全离线可用,数据保存在本地
  4. 账户绑定:用户可在个人中心设置用户名/密码/手机号,实现跨设备登录
  5. 数据同步:绑定账户后,本地数据自动同步到服务器
  6. 多设备支持:使用相同账户在不同设备登录,数据自动合并

技术实现

  • 设备 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

操作流程

  1. 添加关联账号:添加关联账号页支持三种验证方式(Tab 切换):
    • 账号密码:输入目标账号 ID + 密码
    • 手机验证码:输入目标账号手机号 → 获取验证码(60s 倒计时)→ 输入验证码;验证码发送到该手机号(需已绑定 CodeNote 账号)
    • 邮箱验证码:输入目标账号邮箱 → 获取验证码(60s 倒计时)→ 输入验证码;验证码发送到该邮箱(需已绑定 CodeNote 账号)
    • 支持跳转注册页创建新账号
  2. 切换账号:调用 /api/auth/accounts/{targetUserId}/switch 获取新 Token
  3. 移除关联:调用 /api/auth/link/{linkId} 解除关联

技术细节

  • 关联关系记录在服务端 account_links
  • 验证码关联调用 /api/auth/sms/link-code/api/auth/email/link-code 发码,/api/auth/linkverifyType(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")),composablereadFieldOrNull 读取后作为参数 qrCodeId 传给 QrCodeEditScreen
    • 加载数据QrCodeFormViewModel.loadQrCode(qrCodeId) 从服务端加载详情并填充表单
    • 修改信息:可修改二维码内容、类型、分类、前景色、背景色、Logo、尺寸、格式等所有字段
    • 保存更改:提交时同时发送样式字段(foregroundColor/backgroundColor/logoUrl/size),更新后同步到服务器
    • 独立页面:遵循编码规范,不使用弹窗

3.4.10 固定资源管理

后台表结构、API 参见 /reference/backend.md;meta_* 模型见 /architecture/data-model.md,接口见 /reference/api.md A21 节。

资源入口

  • 组织入口:组织管理 → 组织资源

通用动态数据模型(实施,替代 动态字段系统)

  • 服务端 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保底(失败时走服务端中转)

上传流程

  1. 用户选择图片
  2. ImageCompressor压缩图片(根据不同场景配置不同参数)
  3. 调用服务端获取STS临时凭证(POST /api/files/sts-token)
  4. 使用临时凭证直传阿里云OSS
  5. 上传成功后回调服务端记录元数据(POST /api/files/callback)
  6. 返回OSS URL并更新UI

图片压缩规则

场景最大尺寸质量最大文件大小典型压缩率
头像800x80085%200KB95% (2-5MB → 100-200KB)
相册照片1920x192080%500KB95% (5-10MB → 300-500KB)
资源照片1280x128075%300KB95% (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 参数替代原 darkTheme
  • core/data/local/AppPreferences:已有 themeMode 字段(0=SYSTEM, 1=LIGHT, 2=DARK),setThemeMode() 写入 DataStore
  • HomeViewModel:注入 AppPreferences,提供 toggleTheme() 方法
  • MainActivity:收集 appPreferences.themeMode Flow 传入 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.ktpublicPaths 集合中配置,不会添加认证头:

路径说明
/api/auth/login用户登录
/api/auth/register用户注册
/api/auth/refreshToken 刷新(新增)
/api/auth/device-login设备登录(自动注册,新增)

认证逻辑

  1. 请求路径在 publicPaths 中 → 直接放行,不添加认证头
  2. 请求路径不在 publicPaths 中:
    • 有 JWT Token → 添加 Authorization: Bearer {token}
    • 始终携带 X-Device-Id: {deviceId}(服务端校验与活跃会话一致;已无匿名设备认证)

配置文件core/data/src/main/java/com/codenote/core/data/remote/AuthInterceptor.kt

Token 自动刷新时机(详见《用户状态管理规范》§7.6.1):

  1. 冷启动(Splash + autoRegisterLogin)→ force 刷新
  2. token 变化时 / 每 60s 定时检查 → 距过期 <5 分钟静默刷新
  3. 获取用户信息前(getUserInfo())→ 距过期 <5 分钟刷新
  4. 任意请求 401(非公开路径)→ force 刷新 + 重放请求;刷新失败跳登录
  5. WebSocket 通知连接前 → force 刷新
  6. 401 兜底:统一 refresh;refresh 失败清登录态跳登录(不再区分设备用户/普通用户)

9.2 后端权限校验

所有需要认证的接口在后端通过以下机制校验:

  1. Spring SecuritySecurityConfig.kt 配置 permitAll() 路径,其他路径必须认证
  2. JWT 过滤器JwtAuthFilter.kt 解析 Token 或 Device ID,注入认证对象
  3. 权限注解:管理员接口使用 @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.DEFAULTsortOrder,asc + createdAt,asc默认排序(旧的在前)
SortMode.SORTORDER_AT_DESCsortOrder,desc + createdAt,asc最新排序(新创建的在前)

5.5 前后端参数映射

Android 参数Server 参数说明
categoryIdcategoryId分类筛选
keywordkeyword搜索关键字
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: Boolean
  • OrgShareModelentityIdsourceId(原件)+ copyId(副本)
  • LoginResponse / MyPermissionsResponse:权限点集合(APP+PLATFORM / APP+当前组织 ORG);GET /api/rbac/my-permissions 全局拉取(ApiService)
  • Room 迁移 v2:org_members.roleroleName + 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:uicomponent/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)→ AgreementRepositoryAgreementRepositoryImpl + 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 内扩展)