Files
2026-09-15 15:44:13 +07:00

218 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
项目名称: 91porn #如果在dmp则使用dmp的名称 如果不在则使用自定义名称
一级部门: 北斗 #请填写一级部门名称 北斗
二级部门: 瑞升 #请填写二级部门名称,无则留空, 如无极
DMP编码: #无则留空
项目类型: APP
# 可选值:
# H5 / APP / PC / 服务 / 组件 / 工具 / AI / 中间件
开发语言: Dart
# 可选值:
# GO / Java / PHP / Node
项目负责人: DN5368 # 员工编号, 多个用逗号隔开,这里的负责是这套代码的负责人
运维负责人: DN9460,DN9651,DN4792 # 负责部署、监控、K8S、发布、证书等
产品负责人: YY1145,YY1208 # 项目产品经理
项目成员: DN5368,DN1180
项目状态: 开发中
# 可选值:
# 开发中 / 维护中 / 已停止 / 已归档
项目级别:
# 可选值:
# 高 / 中 / 低 , 北斗体系 s和a为 高 b为中 c d为低
核心分支: main
# 即部署时候使用的分支,可选值:
# master / main / dev / release
代码仓库:
- ssh://git@bd-git-sh.jsyyds.com:8082/ruisheng/jha-204-91pron/frontend/91porn-android-client.git
最后审计人: DN5368
最后审计时间: 2026-08-06
告警群: tg://blibli-alert-group
备注: Flutter 客户端(Android + iOS 双端),当前版本 1.12.1Android 包名 com.noporn.newchatone_twelve_one(版本号变更需同步替换)
---
# 1. 项目说明
91porn 移动客户端(Android + iOS 同一套代码),基于 Flutter 3.38.3 + Dart 3.10.0,面向 C 端用户的视频/社区/AI/暗网综合内容 APP。
核心模块:
- **首页 (`hj_page/home/`)**:视频/短视频/漫画/动漫等多 Tab 内容流,含搜索、片库、排行
- **发现 (`hj_page/find/`)**:榜单、分类、片单
- **AI (`hj_page/mine/ai/`)**:AI 换脸、AI 脱衣、文生图、图生视频、文生小说等付费功能
- **会员/支付 (`hj_page/mine/mine_vip/``hj_page/pre_sale/`)**:VIP 购买、金币充值、代理充值、预售
- **个人中心 (`hj_page/user_center_page/``hj_page/mine/`)**:用户主页、客服、QA、设置
- **社区 (`hj_page/community/`)**:帖子、评论、互动
- **IM (`im_manager/`)**:自研私信/会话,无三方 IM SDK(会话列表、聊天详情、消息通知、聊天页广告位)
- **暗网 (`hj_page/darkweb/`)**:分类专区,独立入口与 VIP 体系
- **直播 (`hj_page/live/`)**:直播入口
- **在线客服 (`hj_page/zaixiankefu/`)**H5 客服对接
- **播放器**:基于 video_player + ffmpeg_kit_flutter_new_https + 自研 m3u8_downloader
- **视频下载**Android 走 `m3u8_downloader` 原生 isolateiOS 走 `IOSVideoDownloader`(用 ffmpeg 拉远程 m3u8 转 mp4 自动入相册,命令 `-c copy -bsf:a aac_adtstoasc -movflags +faststart`,不重编码)
技术栈:
- 状态管理:GetX (`GetxController` + `GetBuilder`) + Provider(全局状态)
- 网络:Dio + 自研 `httpManager` + RSA 请求加密
- 屏幕适配:flutter_screenutil(设计稿 428×927
- 路由:GetX 路由
- 本地存储:MMKV + shared_preferences
- 视频下载:`m3u8_downloader-master`(本仓库内)
- 调试工具:ume_core + ume_kit_dio / ume_kit_perf / ume_kit_ui(均取自 pub,不再本地维护)
项目目录结构:
```
hgdj/
├── lib/
│ ├── alert/ # 全局弹窗
│ ├── assets_tool/ # 颜色、图片、资源路径
│ ├── config/ # 配置常量(域名、版本、AB 等)
│ ├── extension/ # Dart 扩展方法
│ ├── hj_model/ # 数据模型(手写 fromJson
│ ├── hj_page/ # 业务页面(按模块分目录)
│ ├── hj_utils/ # 工具方法 + API service
│ ├── im_manager/ # 自研 IM(会话/聊天页/通知)
│ ├── routers/ # 路由定义与跳转
│ ├── tools_base/ # 基础工具(网络/事件/loading/toast/refresh/缓存)
│ ├── track_event_manager/ # 设备信息 / 请求头 sid 会话
│ └── main.dart # 入口
├── android/ # Android 工程
├── ios/ # iOS 工程
├── assets/ # 静态资源(图片/flare/字体)
├── m3u8_downloader-master/ # 自研 m3u8 下载库
├── third_party/ # 三方原生库
├── dist/ # 打包产物输出目录
├── build_apk.sh # 双端打包脚本(apk / ipa
└── pubspec.yaml
```
# 2. 基础设施与中间件
- 后端服务:91porn-serverGo,独立仓库)— 提供 APP API、Web 管理、Skd 任务
- CDN:视频/图片/动漫资源分发(Cloudfront + 自有域名多线路)
- 推送:Telegram Bot 客服群对接
- 客户端依赖:
- DioHTTP 网络层
- cached_network_image:图片缓存
- mmkv / shared_preferences:本地 KV
- ffmpeg_kit_flutter_new_https:视频处理(社区维护 fork,自托管二进制带 SSL,原 minimal 包二进制已下架)
- flutter_inappwebviewH5 嵌入(充值/客服/活动页)
- permission_handler:权限申请
- device_info_plus / package_info_plus:设备信息
- flutter_native_image / image_pickers:图片选择压缩
# 3. 运维部署
客户端通过 `build_apk.sh` 本地打包后分发,**不走 K8S/CI 流水线**。
发布流程:
1. 切到 `main` 分支,更新 `pubspec.yaml` 版本号 + `Config.innerVersion` + `local.properties` + applicationId(按版本号转换规则)
2. 确认 `Config.isDebug = false`(开发分支常年为 `true`,最容易漏)
3. 打包(脚本自动检测 FVM PATH,产物统一复制至 `dist/`):
```
sh build_apk.sh <target> [mode] [export_method] [export_options_plist] [sign_mode]
target a(android) | i(ios) | all
mode release(默认) | debug | profile
export_method (iOS) app-store(默认) | ad-hoc | development | enterprise
export_options_plist ExportOptions.plist 路径,留空用默认
sign_mode (iOS) codesign(默认) | nocodesign
```
常用:`sh build_apk.sh a`Android releasearm64 单 ABI)、`sh build_apk.sh i release ad-hoc`iOS 内测包)、`sh build_apk.sh all`
4. 产物命名(`APP_NAME` 取自 `pubspec.yaml` 的 `name`,即 `hgdj`):
- Android`hgdj_<mode>_<version>_<MMdd-HHmm>.apk`
- iOS`hgdj_<mode>_<exportMethod>_<version>_<MMdd-HHmm>.ipa`,如 `hgdj_release_adhoc_1.12.1_0806-0925.ipa`
5. Android 发版前跑 VirusTotal 包毒检测
6. 上传到分发平台(落地页 / 蒲公英 / 自有 CDN),Telegram 告警群通知
真机调试(不出正式包):`flutter build ios --release && flutter install --release -d <deviceId>`Xcode 自动签名,Team `NWP977LR25`。
回滚方案:
- 仅客户端无服务端回滚概念
- 旧版本安装包保留在分发平台,用户可重新下载
- 服务端通过 `Config.dns` 接口下发动态域名实现服务端切换
# 4. 风险说明
- **包名风险**`com.noporn.newchatone_twelve_one` 与版本号强绑定,每次升版需替换 applicationId(数字转英文单词规则),遗漏会导致 push/支付失败
- **加密密钥风险**`Config.encryptKey` / `ANTI_REPLAY_ATTACK_KEY` / `Config.appId = JHA-204` 泄漏会被刷接口
- **包毒检测**:APK 容易被部分杀毒软件误报,需走 VirusTotal 检测后再分发
- **包体积**:当前接入 ffmpeg_kit_flutter_new_https(带 SSL,比 minimal 大 +5~10MB/ video_player / 多个三方 SDK,需定期 `analyze-size` 控制体积
- **iOS 最低系统版本**Podfile + Runner.xcodeproj 锁定 15.0Xcode 16+ 要求 + ffmpeg 7.x 要求),低于此版本无法安装
- **Android SDK 版本**minSdk 24 / targetSdk 36targetSdk 升级需重新验证权限与后台限制
- **三方依赖**image_pickers / m3u8_downloader 为内部维护,Flutter 升级时需同步适配
- **原生插件升级**`flutter pub get` + `flutter analyze` 全过也不代表能用,含原生代码的插件必须真机 build 验证(audioplayers 升 v6 即栽在这里)
- **release-only 缺陷**:混淆 + strip + 独立 manifest 让 release 有一整类 debug 不复现的问题(详见第 5 节),每次发版必须真机跑 release 包,不能只测 debug
- **域名兜底**`Config.dns = 91porn.zmnchs.com` 走运维下发,运维接口挂掉会导致 APP 启动失败
- **Flutter 版本锁定**3.38.3 强制 fvm,避免系统 SDK 切换触发 flutter_ume / leak_tracker 冲突
- **暗网模块**`Config.darkWebEnable` 通过运维下发开关,下发异常会暴露入口
- **DEBUG 标志**`Config.isDebug` 切换会影响 `lineList` 兜底线路与日志输出,正式包必须 `false`(开发分支当前为 `true`
# 5. 历史事故(可选)
## 5.1 release 包专有(debug 一律不复现,最贵的一类)
debug 与 release 在**混淆、符号 strip、manifest 合并**三处行为不同,以下问题只能靠真机 release 包发现:
| 现象 | 根因 | 修复 |
|---|---|---|
| iOS 启动卡死 / 本地存储静默读不到 | FFI(MMKV)符号被 strip 掉,运行时找不到 | `STRIP_INSTALLED_PRODUCT = NO`(只设 `STRIP_STYLE = non-global` 不够) |
| Android 安装后直接闪退(SIGSEGV | 含 native 方法的插件(ffmpeg-kit)被 R8 混淆,`RegisterNatives` 失败 | proguard 补 `-keep`;**新增带 JNI 的插件都要补** |
| 启动弹「线路检测失败」,host lookup errno=7 | `INTERNET` 权限只声明在 debug/profile 的 manifest`src/main` 漏了 | 权限写进 `src/main/AndroidManifest.xml`,别指望三方插件替你声明 |
| 页面灰屏 / 列表静默空白 | 用 `T.toString() == '类名'` 做分支,混淆后类名变了必然失配 | 一律改 `T == 类型`(比较 Type 对象,不碰名字字符串) |
## 5.2 其他
- **视频下载进度不刷新**(退出重进才更新):本仓库内 `m3u8_downloader` 的 `callbackDispatcher` 缺 `@pragma('vm:entry-point')`,后台 isolate 起不来。教训:本地插件不是黑盒,出问题要读它的源码
- **刷新控制器双重释放崩溃**`refreshCtr` 归 `CustomRefreshView` 释放,logic / 子 widget 又 dispose 一次 → `headerMode!` 空断言崩在 unmount 阶段 → 中断整棵树的卸载并连带泄漏。规则:只释放自己 new 的
- **dio 5.9 偶发 `DioException[unknown] Null check operator`**:仅 CDN 接口偶发,根因不可复现(已证伪 adapter / FusedTransformer 两个方向),用幂等 GET 的 `RetryInterceptor` 自愈
- **截图保存「提示成功但相册没有」**:`RepaintBoundary` 套在 `SingleChildScrollView` 内被裁剪,截出空图。改用 `Center + FittedBox` 完整渲染,且调用处必须 `await` 判断结果
- **设置锁屏密码当次不生效、重启后才生效**:把内部可变 list 直接交给未 `await` 的异步回调,回调在 `await` 之后读到的已是被 clear 的空 list(内存态空、持久化态有值)。传副本 `List<int>.of(x)` 修复
- **audioplayers 无法升 v6**`pub get` / `analyze` 全过但 Android 侧 build 挂(5.2.1 与 platform_interface 7.1.1 错配 + Kotlin 2.1),已停在 v5.2.1
- **`pull_to_refresh` 升级后回弹手感夸张**Flutter 3.16.8 → 3.38.3):改 `pull_refresh.dart` 参数解决;**不迁移 EasyRefresh**——logic 层有 305 处依赖 SmartRefresher 专有 API
- **华为多任务卡片隐私遮盖失败**:系统截快照早于 Flutter 生命周期回调,任何遮盖层(View / 独立 Window)都盖不住,也无法把黑屏换成自定义图。结论:只能用 `FLAG_SECURE`
# 6. 交接说明(可选)
- **Flutter 版本**:必须使用 `fvm flutter`,版本锁定 3.38.3(见 `.fvmrc`),`build_apk.sh` 已内置 FVM PATH 检测
- **打包**`build_apk.sh a` 打 Android`build_apk.sh i` 打 iOS`build_apk.sh all` 双端;完整参数见第 3 节
- **applicationId 规则**`com.noporn.newchat` + `<major>_<minor>_<patch>`(数字转英文),如 1.12.1 → `com.noporn.newchatone_twelve_one`
- **版本号同步点**`pubspec.yaml` / `Config.innerVersion` / `android/local.properties` / `android/app/build.gradle` 四处必须一致
- **加密密钥**RSA 公私钥位于 Android 端 native 层 + Dart 层,调整时需双端同步
- **AB 配置**`Config` 类中集中维护(`aiSwitchConf` / `showAbTestAd` 等),新增 AB 需走运维下发
- **客服地址**`/customer/url` 接口动态下发完整 URL,前端不再拼 `baseHost`
- **provider 注入**`main.dart` 中 `MultiProvider` 注入若干 `ChangeNotifier`,新增全局状态走该处
- **图片资源**`assets_tool/` 统一管理路径扩展(`.commonImgPath` / `.homePath` / `.darkWebPath` 等)
- **域名错误兜底**:DNS 接口失败时降级到 `Config.LINE_LIST` 本地兜底
- **网络层异常约定**:dio 异常已被网络层吞成 `BaseRespBean`(业务层不要写 `DioException` catch),但**请求前的签名加密会抛**,需 try 兜底;拦截器只在「有响应但失败」时弹 toast,超时/断网(code 2001-2003)不弹,要各自兜底
- **列表分页**:统一继承 `tools_base/base_list_controller.dart` 的 `ListBaseLogic`(配 `pullYsRefresh`),别自己重写分页
- **支付下单**:走 `PayManager().buy / buyVip`(含 loading、余额不足跳充值、回调、source 透传),不要直接调 `BuyService`
- **弹窗**:提示类统一用 `tools_base/widget/common_alert.dart`;「停留 N 分钟后满足条件才弹」走 `TimedPopupManager``onClose` 必须 `cancel`,否则泄漏)
- **包毒检测**:发版前必须用 `/check-apk-virus` 跑 VirusTotal
- **包体积分析**:使用 `apk-analyzer` skill 跑 `flutter build apk --analyze-size`