first commit
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
# KIOSK 工程代码审查报告
|
||||
|
||||
审查日期:2026-08-04
|
||||
工程包名:`com.yzx.kiosk`
|
||||
审查范围:工程配置、应用入口、网络、存储、WebSocket、打印、上传下载、版本更新、权限、测试与构建质量。
|
||||
|
||||
## 总体结论
|
||||
|
||||
工程当前能够完成 Debug 和 Release 编译,也能安装到真机运行,但暂不建议直接作为正式生产包发布。主要风险集中在:
|
||||
|
||||
1. 正式包签名与密钥管理。
|
||||
2. 打印结果和服务端通知的准确性。
|
||||
3. 隐私合规及本地敏感数据保护。
|
||||
4. WebSocket、上传下载等异步任务的并发可靠性。
|
||||
5. 自动化测试和静态检查缺失。
|
||||
|
||||
## 严重问题
|
||||
|
||||
### 1. Release 包实际使用 Debug 签名
|
||||
|
||||
位置:`app/build.gradle.kts:112-151`
|
||||
|
||||
- Debug 和 Release keystore 及其密码直接写在工程配置中。
|
||||
- Release 构建在 `signingConfig` 中选择了 `debug` 签名,而非已声明的 `release` 签名。
|
||||
- 实际构建并验证后,Debug 和 Release APK 的签名证书 SHA-256 完全相同。
|
||||
- 获取仓库内容的人可以使用现有密钥签出能够覆盖正式应用的 APK。
|
||||
|
||||
建议:
|
||||
|
||||
- 立即停止在仓库中保存 keystore 和密码。
|
||||
- 将签名信息改为从安全的 CI Secret、本机环境变量或未纳入版本控制的属性文件读取。
|
||||
- 评估现有正式设备是否已经使用该证书,再制定密钥轮换和升级迁移方案。
|
||||
|
||||
### 2. 设置页密码硬编码在客户端
|
||||
|
||||
位置:
|
||||
|
||||
- `app/src/main/java/com/yzx/kiosk/ui/home/viewmodel/HomeViewModel.kt:162-164`
|
||||
- `app/src/main/java/com/yzx/kiosk/ui/home/viewmodel/HomeViewModel.kt:322-324`
|
||||
|
||||
设置入口使用固定常量密码,并在客户端直接进行字符串比较。APK 被反编译后即可得到密码,进而进入设置页并修改设备密钥等敏感配置。
|
||||
|
||||
建议:
|
||||
|
||||
- 不在 APK 中保存通用管理密码。
|
||||
- 使用服务端校验、每台设备独立凭证或短期动态口令。
|
||||
- 增加失败次数限制、审计和必要的锁定机制。
|
||||
|
||||
### 3. 高德 SDK 被无条件标记为“用户已同意隐私政策”
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/App.kt:50-54`
|
||||
|
||||
Application 启动时直接调用:
|
||||
|
||||
```kotlin
|
||||
AMapLocationClient.updatePrivacyShow(this, true, true)
|
||||
AMapLocationClient.updatePrivacyAgree(this, true)
|
||||
```
|
||||
|
||||
工程虽然包含 `PrivacyPolicyDialogFragment`,但当前没有业务代码展示它,也没有持久化和校验用户真实同意状态。
|
||||
|
||||
建议:
|
||||
|
||||
- 首次启动先展示隐私政策并记录同意状态。
|
||||
- 只有用户主动同意后才能通知第三方 SDK 并初始化相关能力。
|
||||
- 用户拒绝时不得初始化非必要 SDK,并提供可用的退出或受限模式。
|
||||
|
||||
### 4. 打印成功判定不可信
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/priter/PrinterService.kt:253-296`
|
||||
|
||||
- RX1 将任务加入 SDK 队列后立即报告成功。
|
||||
- QW410 固定等待 18 秒后报告成功。
|
||||
- 两种情况都没有读取打印任务的最终完成或失败状态。
|
||||
|
||||
缺纸、卡纸、断连或 SDK 后续执行失败时,业务仍可能记录为打印成功,并进一步扣减纸张和通知服务端。
|
||||
|
||||
建议:
|
||||
|
||||
- 监听打印 SDK 的任务状态或结果回调。
|
||||
- 只在收到最终成功状态后更新本地记录、纸张数量及服务端状态。
|
||||
- 为超时、离线、卡纸、缺纸和用户取消建立明确的错误映射。
|
||||
|
||||
### 5. 打印状态通知没有真正串行执行
|
||||
|
||||
位置:
|
||||
|
||||
- `app/src/main/java/com/yzx/kiosk/ui/upload/viewmodel/PrintingViewModel.kt:107-143`
|
||||
- `app/src/main/java/com/yzx/kiosk/ui/upload/viewmodel/PrintingViewModel.kt:367-410`
|
||||
- `app/src/main/java/com/yzx/kiosk/network/result/ResultHandler.kt:27-73`
|
||||
|
||||
`PrintingViewModel` 使用 Channel 设计了通知队列,但 `executePrintNotify` 调用的 `handleResultWithData` 会再启动一个协程并立即返回。因此队列只保证发起顺序,不保证网络请求完成顺序。
|
||||
|
||||
可能产生:
|
||||
|
||||
- `print-complete` 先于部分 `print-notify` 到达服务端。
|
||||
- 网络失败后没有持久化重试。
|
||||
- 本地纸张数在服务端确认前已扣减,失败后不会回滚。
|
||||
|
||||
建议:
|
||||
|
||||
- 让单次通知成为真正可等待的挂起调用。
|
||||
- 只有接口确认成功后再消费下一条通知。
|
||||
- 将待通知状态持久化到 Room,并增加幂等键、重试和恢复机制。
|
||||
|
||||
### 6. 启动后可能错误报告打印机在线
|
||||
|
||||
位置:
|
||||
|
||||
- `app/src/main/java/com/yzx/kiosk/priter/PrintStatusManager.kt:24-30`
|
||||
- `app/src/main/java/com/yzx/kiosk/MainActivity.kt:93-100`
|
||||
- `app/src/main/java/com/yzx/kiosk/websocket/WebSocketService.kt:565-575`
|
||||
|
||||
`PrintStatusManager` 默认打印机在线,而 MainActivity 启动后会立即连接 WebSocket。打印机只在进入打印或管理流程后才初始化,因此没有连接打印机时,心跳仍可能报告“空闲可接单”。
|
||||
|
||||
建议:
|
||||
|
||||
- 默认状态改为离线或未知。
|
||||
- 应用启动时主动初始化并探测打印机。
|
||||
- 只有获得可靠设备状态后才报告可接单。
|
||||
|
||||
## 高优先级问题
|
||||
|
||||
### 7. 敏感数据使用未加密 MMKV 保存
|
||||
|
||||
位置:
|
||||
|
||||
- `app/src/main/java/com/yzx/kiosk/utils/MMKVUtils.kt`
|
||||
- `app/src/main/java/com/yzx/kiosk/datastore/AppStoreDataSource.kt:48-68`
|
||||
- `app/src/main/AndroidManifest.xml:52-64`
|
||||
|
||||
设备密钥、Socket Token、盒子 API Token 等数据均存入默认 MMKV,没有加密。同时应用开启 `allowBackup`,备份规则仍是空模板,存在敏感数据被备份或迁移的风险。
|
||||
|
||||
建议:
|
||||
|
||||
- 使用 Android Keystore 保护的密钥加密敏感字段。
|
||||
- 明确排除 MMKV、数据库和凭证文件的云备份与设备迁移。
|
||||
- 对 Token 设置有效期,并支持服务端吊销。
|
||||
|
||||
### 8. APK 更新只校验文件长度
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/utils/VersionUpdateManager.kt:312-384, 535-557`
|
||||
|
||||
- 已存在 APK 只比较 Content-Length。
|
||||
- HEAD 请求无法获取文件大小时会直接使用本地文件。
|
||||
- 新下载文件同样只验证长度,没有 SHA-256 或可信证书白名单校验。
|
||||
|
||||
建议:
|
||||
|
||||
- 版本接口下发 APK 的 SHA-256,并在安装前严格比对。
|
||||
- 校验 APK 包名、版本号和签名证书摘要。
|
||||
- 下载到临时文件,校验通过后再原子替换正式文件。
|
||||
|
||||
### 9. 下载任务的并发模型互相冲突
|
||||
|
||||
位置:
|
||||
|
||||
- `app/src/main/java/com/yzx/kiosk/utils/cloud/DownloadRepository.kt:26-167`
|
||||
- `app/src/main/java/com/yzx/kiosk/utils/cloud/CloudOssUtils.kt:422-509`
|
||||
|
||||
`DownloadRepository` 用 Map 表示可以同时运行多个下载任务,但 `CloudOssUtils` 只保存一个全局 `currentDownloadTask`。暂停或取消某个任务时,可能取消最后启动的另一个任务。
|
||||
|
||||
建议:
|
||||
|
||||
- 每个任务持有独立 OSS 请求对象和取消句柄。
|
||||
- 或显式限制为单任务队列,不暴露并发能力。
|
||||
- 使用互斥锁或单线程调度器保护任务状态。
|
||||
|
||||
### 10. ResponseInterceptor 篡改所有错误响应的数据结构
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/network/interceptor/ResponseInterceptor.kt:27-44`
|
||||
|
||||
所有业务失败响应都会被强制修改为:
|
||||
|
||||
```json
|
||||
{"data": {}}
|
||||
```
|
||||
|
||||
当接口声明的 `data` 类型是字符串、列表或其他非对象类型时,Retrofit/Gson 可能在业务错误处理之前反序列化失败,最终把业务错误误判为网络异常。同时原始错误数据也会丢失。
|
||||
|
||||
建议:
|
||||
|
||||
- 不在拦截器中修改业务 JSON。
|
||||
- 让 `data` 保持可空,并在统一结果层根据 `code` 处理。
|
||||
- 为不同错误响应建立兼容测试样例。
|
||||
|
||||
### 11. Room 数据库可能破坏性清库
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/network/db/DatabaseModule.kt:20-28`
|
||||
|
||||
数据库当前只提供 `7 -> 8` 迁移,其他版本使用 `fallbackToDestructiveMigration()`。老版本升级时可能直接清除未完成上传任务。
|
||||
|
||||
此外两个数据库都声明了 `exportSchema = true`,但未配置 `room.schemaLocation`,构建时已出现警告,也没有生成可纳入版本管理的 schema。
|
||||
|
||||
建议:
|
||||
|
||||
- 补齐所有已发布版本的迁移链。
|
||||
- 移除生产数据库的破坏性迁移回退。
|
||||
- 导出 schema 并编写 MigrationTestHelper 测试。
|
||||
|
||||
### 12. WebSocket 状态存在多线程竞争
|
||||
|
||||
位置:`app/src/main/java/com/yzx/kiosk/websocket/WebSocketService.kt`
|
||||
|
||||
OkHttp 回调线程和主线程会同时读写 `webSocket`、`isBound`、`isConnecting`、重连计数和普通 `mutableListOf` 监听器。当前缺少统一协程作用域、线程封闭或锁保护。
|
||||
|
||||
可能造成重复重连、旧连接回调覆盖新连接状态、遍历监听器时被修改等问题。
|
||||
|
||||
建议:
|
||||
|
||||
- 建立一个 Application 级 SupervisorJob。
|
||||
- 将所有连接状态归一到单线程调度器或 Mutex 保护的状态机。
|
||||
- 使用线程安全监听器集合,或统一切换到主线程通知。
|
||||
|
||||
## 工程质量与维护问题
|
||||
|
||||
### Lint 当前失败
|
||||
|
||||
执行 `:app:lintDebug` 的结果:
|
||||
|
||||
- 4 errors
|
||||
- 345 warnings
|
||||
- 8 hints
|
||||
|
||||
4 个错误包括:
|
||||
|
||||
1. Target Android 13+ 时缺少 `POST_NOTIFICATIONS` 权限处理。
|
||||
2. `AppNavHost.kt` 使用 Media3 不稳定 API 但没有 opt-in。
|
||||
3. `PosterScreenWithLiveOrSlideshow.kt` 两处使用 Media3 不稳定 API但没有 opt-in。
|
||||
|
||||
完整报告:`app/build/reports/lint-results-debug.html`
|
||||
|
||||
### 自动化测试基本为空
|
||||
|
||||
- 本地单元测试只有 `2 + 2 = 4` 示例。
|
||||
- Instrumentation 测试仍断言旧包名 `com.zhifly.follow`,当前包名为 `com.yzx.kiosk`,运行后会失败。
|
||||
- 打印状态、通知顺序、数据库迁移、WebSocket 重连、版本校验等核心逻辑均无测试覆盖。
|
||||
|
||||
### 权限申请过多
|
||||
|
||||
位置:`app/src/main/AndroidManifest.xml`
|
||||
|
||||
Manifest 申请了后台定位、电话状态、忽略电池优化、旧版外部存储等权限,但工程中未找到对应业务使用。应用同时全局允许明文流量,扩大了安全与合规风险。
|
||||
|
||||
建议按真实业务最小化权限,并用 `networkSecurityConfig` 只对白名单局域网地址放开明文流量。
|
||||
|
||||
### Release 包体积偏大
|
||||
|
||||
实际构建的 Release APK 约为 144 MB。
|
||||
|
||||
主要原因包括:
|
||||
|
||||
- Release 未开启代码压缩和资源压缩。
|
||||
- 同时配置多个 ABI。
|
||||
- 本地库约 67 MB,高德 native 库占比较高。
|
||||
- 音频资源约 24 MB。
|
||||
- 存在重复依赖和不应作为运行时依赖的构建工具依赖。
|
||||
|
||||
建议使用 ABI Split/App Bundle、开启 R8 和资源压缩,并清理重复或无效依赖。
|
||||
|
||||
### 核心类过大
|
||||
|
||||
部分文件行数:
|
||||
|
||||
| 文件 | 行数 |
|
||||
| --- | ---: |
|
||||
| `DeviceConfigViewModel.kt` | 1574 |
|
||||
| `DeviceConfigScreen.kt` | 1024 |
|
||||
| `HomeScreen.kt` | 996 |
|
||||
| `WebSocketService.kt` | 974 |
|
||||
| `VersionUpdateManager.kt` | 811 |
|
||||
| `PrivacyPrintService.kt` | 673 |
|
||||
| `UploadManager.kt` | 589 |
|
||||
|
||||
建议按设备配置、上传、播放器、连接状态机、打印订单等职责拆分,并将业务流程从 Composable 和 ViewModel 中下沉到可测试的 UseCase/Repository。
|
||||
|
||||
### Gradle Wrapper 不可直接运行
|
||||
|
||||
根目录的 `gradlew` 使用 CRLF 换行且没有执行权限,在 macOS/Linux 上会出现解析错误或 Permission denied。
|
||||
|
||||
建议恢复标准 Wrapper 文件、使用 LF 换行并设置可执行权限,同时在 CI 中验证 `./gradlew tasks`。
|
||||
|
||||
## 验证结果
|
||||
|
||||
| 项目 | 结果 |
|
||||
| --- | --- |
|
||||
| Debug 编译 | 通过 |
|
||||
| Debug 真机安装启动 | 通过 |
|
||||
| Release 编译 | 通过 |
|
||||
| Release APK 大小 | 约 144 MB |
|
||||
| Debug/Release 签名对比 | 相同证书 |
|
||||
| 单元测试 | 1 个示例测试通过 |
|
||||
| Lint | 失败,4 errors / 345 warnings / 8 hints |
|
||||
| Instrumentation 测试 | 未执行;代码中的包名断言确定错误 |
|
||||
|
||||
## 建议处理顺序
|
||||
|
||||
1. 移除仓库签名密钥和密码,修正 Release 签名,评估密钥轮换。
|
||||
2. 移除固定管理密码,改为可靠的设备管理认证。
|
||||
3. 修复打印完成判定、通知顺序、纸张扣减和失败重试。
|
||||
4. 启动时真实探测打印机,避免错误报告可接单。
|
||||
5. 补齐隐私授权流程,收敛权限和明文网络范围。
|
||||
6. 加密设备密钥及 Token,关闭敏感数据备份。
|
||||
7. 增加 APK 哈希与签名校验。
|
||||
8. 修复上传下载及 WebSocket 并发状态管理。
|
||||
9. 修复 Lint、数据库迁移和测试基础设施。
|
||||
10. 拆分大类、清理依赖并优化 APK 体积。
|
||||
Reference in New Issue
Block a user