初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+296
View File
@@ -0,0 +1,296 @@
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hgdj/config/address.dart';
import 'package:hgdj/config/config.dart';
import 'package:hgdj/hj_model/home/plate_model.dart';
import 'package:hgdj/hj_model/splash/ads_model.dart';
import 'package:hgdj/hj_model/splash/cut_info.dart';
import 'package:hgdj/hj_model/splash/domain_source_model.dart';
import 'package:hgdj/hj_model/user/user_info_model.dart';
import 'package:hgdj/hj_page/main_page/main_logic.dart';
import 'package:hgdj/hj_page/main_page/main_page.dart';
import 'package:hgdj/hj_page/pre_sale/limit_time_provider.dart';
import 'package:hgdj/hj_page/pre_sale/pre_sale_provider.dart';
import 'package:hgdj/hj_utils/api_service/common_service.dart';
import 'package:hgdj/hj_utils/detect_line_manager.dart';
import 'package:hgdj/hj_utils/local_server.dart';
import 'package:hgdj/hj_utils/local_server_guard.dart';
import 'package:hgdj/hj_utils/text_util.dart';
import 'package:hgdj/hj_utils/version_util.dart';
import 'package:hgdj/tools_base/ad_manager.dart';
import 'package:hgdj/tools_base/debug_log.dart';
import 'package:hgdj/tools_base/global_store/store.dart';
import 'package:hgdj/tools_base/net/api_exception.dart';
import 'package:hgdj/tools_base/net/net_manager.dart';
import 'package:hgdj/tools_base/widget/common_alert.dart';
import 'package:path/path.dart' as path;
import '../../alert/splash/line_error_dialog.dart';
import '../../alert/splash/update_dialog.dart';
import '../../hj_utils/free_play_manager.dart';
import '../../tools_base/loading/loading_alert_widget.dart';
import '../../track_event_manager/device_service.dart';
import '../live/live_main_page.dart';
import '../main_page/provider/msg_provider.dart';
//启动页
class SplashLogic extends GetxController {
// ===== 开屏广告 =====
List<AdsInfoModel>? adsList; //开屏广告,没有就直接过
bool isAdLoaded = false; //广告数据已就位(可能为空),页面据此决定要不要挂广告层
bool isAdDone = false; //广告已放完/跳过,只等数据就绪
// ===== 启动数据 =====
bool isDataReady = false; //模块列表已落地
bool _isExiting = false; //强更被拒、已请求退出 app:后续流程一律不再放行首页
@override
onReady() {
super.onReady();
_start();
}
Future _start() async {
netManager.clearUserAgent(); //清除老的ua
await AdManager().preload(); //预热广告内存缓存,后续统一用 AdManager().adsByType
_loadAds(isLast: true); //获取上次启动的广告
final line = await _detectLine(); //选线
//拿不到线路就到此为止:要么用户放弃了重试,要么弹框回调里重试成功、
//已经自己走了一遍 _initByLine。这里再调一次会重复初始化
if (TextUtil.isEmpty(line)) return;
_initByLine(line!); //初始化流程
}
//选到线路后的完整启动流程
Future _initByLine(String line) async {
//1. 初始化网络层
_initNet(line);
//2.先登录,后升级,不然渠道一升级就没有量了,先登录才能获取配置信息
await _devLogin();
// 模块列表只依赖登录拿到的 token,请求先发出去跟下面的配置/更新检查并行跑,
// 但结果留到更新检查之后才放行(见末尾)——强更期间不能让首页变成可跳
// ignore():先占个监听位,免得错误在 await 到之前被当成未捕获异常上报;真正的错误照旧在 await 处抛
final tagTask = CommonService.getTagMarks()..ignore();
//2. 获取远程配置
final versions = await _fetchConfig();
PreSaleProvider().refreshConfig();
MineMsgProvider().startPayTimer(); //冷启动 /ping/domain 下发支付分层配置后启动倒计时
FreePlayManager().refresh(); //免费次数进首页才用得到,不 await、不挡启动
await _checkUpdate(versions); //强更时会停在启动页,必须挡在放行之前
if (_isExiting) return; //强更被拒,app 正在退出,别再往下放行
_initServer(); // 初始化localserver缓存
await _waitModules(tagTask); //放行跳首页
}
///isLast = true 取出上次展示的广告
Future _loadAds({bool isLast = true}) async {
final list =
isLast ? await AdManager().lastAdsByType(1) : AdManager().adsByType(1);
if (AdManager().showAbTestAd) adsList = list;
isAdLoaded = true;
update();
}
///一直检查网络直到成功
Future<String?> _detectLine() async {
LoadingAlertWidget.show(title: "选线中...");
final net = await Connectivity().checkConnectivity();
// 有网络,开始选线
if (net != ConnectivityResult.none) {
final line = await DetectLineManager().detectLineOnce();
LoadingAlertWidget.cancel();
if (TextUtil.isNotEmpty(line)) return line;
}
await LineErrorDialog.show(callback: () async {
final line = await DetectLineManager().detectLineOnce();
LoadingAlertWidget.cancel();
if (TextUtil.isNotEmpty(line)) _initByLine(line); //初始化流程
});
LoadingAlertWidget.cancel();
return null;
}
void _initNet(String host) {
Address.baseHost = host;
Address.baseApiPath = path.join(Address.baseHost ?? "", Address.apiPrefix);
netManager.init(Address.baseApiPath ?? "");
}
///登录
Future<UserInfoModel?> _devLogin() async {
String paste = "";
//2.获取渠道码
final channel = await DeviceInfoService.getChannel();
if (TextUtil.isNotEmpty(channel)) {
final traceId = await DeviceInfoService.fetchTraceId();
final cutInfo = CutInfo()
..dc = channel
..tid = traceId;
paste = json.encode(cutInfo.toJson());
} else {
//1.获取粘贴板(走缓存,避免 iOS 多次弹系统读取提示)
final clipText = await DeviceInfoService.getClipboardTextCached();
if (TextUtil.isNotEmpty(clipText)) {
paste = clipText!;
}
}
final deviceId = DeviceInfoService.deviceId;
await netManager.refreshUserAgent();
//登录 + 重试
while (true) {
final userInfo = await globalStore.loginByDevice(deviceId, paste: paste);
if (userInfo != null) return userInfo; //免广告策略已在 globalStore 登录成功时算好
final retry = await CommonAlert.show(
content: "登录失败,是否重试?",
confirmText: '重试',
);
if (retry != true) return null;
}
}
//拉远端配置并落地,失败弹框重试;用户放弃就返回空版本列表(等于不检查更新)
Future<List<CheckVersionInfo>> _fetchConfig() async {
List<CheckVersionInfo>? versions;
while (versions == null) {
try {
final info = await CommonService.fetchRemoteConfig();
if (info == null) {
// 配置拉取失败(无 token / 网络异常 / 返回体异常),走下面 catch 的重试逻辑
throw Exception('获取远端配置为空');
}
await _applyDomains(info);
await AdManager().saveAds(info.ads?.adsInfoList);
await _loadAds(isLast: false);
Config.aiUndressPrice = info.aiUndressPrice ?? '';
Config.aiVideoPrice = info.aiImageToVideoPrice ?? '';
Config.aiDrawPrice = info.aiTextToImagePrice ?? '';
Config.aiNovelPrice = info.aiTextToNovelPrice ?? '';
Config.isStoreOpen = info.storeIsOpen ?? false;
Config.jgArea = info.jgArea;
versions = info.ver ?? [];
//直播模块开关
LiveMainPage.broadcast = info.broadcast ?? false;
//预售相关
presaleProvider.advanceStatus = info.advanceStatus;
presaleProvider.advancePage = info.advancePage;
//幸运抽奖相关
presaleProvider.luckyDrawH5 = info.luckyDrawH5;
presaleProvider.luckyDrawIcon = info.luckyDrawIcon;
//限时活动相关
limitTimeProvider.bannerJump = info.bannerJump;
Config.proxyBanner = info.proxyBannerJump;
Config.hotWords = info.hotSearchTerms ?? [];
Config.searchHints = info.searchHintWord ?? [];
Config.darkWebVipId = info.darkWebVipId;
Config.darkWebVipName = info.darkWebVipName;
Config.shortDramaCardId = info.shortDramaCardId;
} catch (e) {
debugLog('getRemoteConfig', e.toString());
final retry = await CommonAlert.show(
content: "获取配置信息失败${(e is ApiException) ? e.code : 1500},是否重试?",
confirmText: '重试',
);
if (retry != true) break;
}
}
return versions ?? [];
}
//域名/公告落地
Future _applyDomains(DomainSourceModel info) async {
//域名列表信息
for (final source in info.sourceList!) {
switch (source.type) {
case "IMAGE":
Address.baseImagePath = source.domain![0].url;
break;
case "VID":
Address.cdnAddressLists = source.domain!;
Address.cdnAddress = Address.cdnAddressLists[0].url;
break;
case "GUIDE":
Address.groundUrl = source.domain![0].url;
break;
case 'AUDIO':
Address.audioCdnAddress = source.domain?.firstOrNull?.url;
break;
}
}
AdManager().announceList = info.ads?.announList ?? [];
}
///检查是否需要更新
Future _checkUpdate(List<CheckVersionInfo> versions) async {
//存内存,设置页的「检查更新」直接读这份比对,不再发请求
saveVersion(versions);
final target = checkUpdate();
if (target == null) return;
// 用户是否点了更新
final isUpdate = await UpdateDialog.show(target);
if (isUpdate != true && target.forcedUpdate == true) {
//SystemNavigator.pop 只 finish Activityengine 还能活几百毫秒,
//这期间后面的流程会照跑、够把首页放行出去,所以打标记让调用方直接收尾
_isExiting = true;
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
}
//本地缓存服务:守护进程被内部 Timer 持有,不用再存字段
Future _initServer() async {
final server = CacheServer(cacheManager: null, openSubManager: true);
await LocalServerGuard(server).run();
//其余任意文件拦截
server.addReqFilter(LOCAL_ALL_FILTER, Address.baseImagePath!);
}
//模块数据落地 → 放行跳首页。请求在登录后就发出去了,这里只等结果。
//拿不到模块一律不放行:首页 tab 全靠它,宁可停在启动页重试,也不进只剩"最新"的降级首页。
//弹框只给"确定"一个选择,且不判返回值——点遮罩关掉也照样重试,不给静默卡死的出口
Future _waitModules(Future<HomePlateModel?> task) async {
var pending = task;
while (true) {
HomePlateModel? model;
try {
model = await pending;
} catch (e) {
//网络异常和 fromJson 异常都被网络层吞成 null,走不到这;兜的是请求前置(取token/签名)抛的异常
debugLog('getTagMarks', e.toString());
}
if (model != null) {
Config.plateModule = model;
isDataReady = true;
_tryJump();
return;
}
await CommonAlert.show(content: "获取模块信息失败,请重试", showCancel: false);
pending = CommonService.getTagMarks(); //旧 Future 已完成,重试必须重新发请求
}
}
//广告结束:标记可跳转、刷新出过渡遮罩,再尝试进首页
void onAdFinish() {
isAdDone = true;
update();
_tryJump();
}
//广告和数据两边都就绪才进首页
void _tryJump() {
//opaque 在 offAll 里默认 false,不显式打开会在转场时透出下层
if (isAdDone && isDataReady)
Get.offAll(() => const MainPage(),
binding: MainPageBinding(), opaque: true);
}
}