api_domains.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:dio/dio.dart';
  4. import 'package:dio/io.dart';
  5. import 'package:nomo/app/api/router/api_router.dart';
  6. import 'package:shared_preferences/shared_preferences.dart';
  7. import '../../utils/crypto.dart';
  8. import '../../utils/developer/ix_developer_tools.dart';
  9. import '../../utils/log/logger.dart';
  10. import '../api/core/api_core.dart';
  11. import '../api/file/api_file.dart';
  12. import '../api/log/api_log.dart';
  13. import 'configs.dart';
  14. import '../data/models/launch/launch.dart';
  15. import 'keys.dart';
  16. class ApiDomains {
  17. static final ApiDomains _instance = ApiDomains._internal();
  18. static ApiDomains get instance => _instance;
  19. factory ApiDomains() => _instance;
  20. ApiDomains._internal();
  21. // 常量定义
  22. static const String _STORAGE_API_URLS = 'api_urls';
  23. static const String _STORAGE_ROUTER_URLS = 'router_urls';
  24. static const String _STORAGE_LOG_URLS = 'log_urls';
  25. static const String _STORAGE_FILE_URLS = 'file_urls';
  26. static const String _STORAGE_BACKUP_URLS = 'backup_api_urls';
  27. static const String _STORAGE_API_URLS_INDEX = 'api_urls_index';
  28. static const String _STORAGE_ROUTER_URLS_INDEX = 'router_urls_index';
  29. static const String _STORAGE_LOG_URLS_INDEX = 'log_urls_index';
  30. static const String _STORAGE_FILE_URLS_INDEX = 'file_urls_index';
  31. static const String _STORAGE_IS_HARDCODED = 'is_hardcoded';
  32. static const int RETRY_COUNT_PER_LAUNCH = 2;
  33. final Dio dio = Dio(
  34. BaseOptions(
  35. connectTimeout: const Duration(seconds: 30),
  36. receiveTimeout: const Duration(seconds: 30),
  37. sendTimeout: const Duration(seconds: 30),
  38. ),
  39. );
  40. // 标记是否已经添加了监控拦截器
  41. bool _monitorInterceptorAdded = false;
  42. setProxy(String proxy) {
  43. dio.httpClientAdapter = IOHttpClientAdapter(
  44. createHttpClient: () {
  45. final client = HttpClient();
  46. client.findProxy = (uri) => proxy;
  47. client.badCertificateCallback =
  48. (X509Certificate cert, String host, int port) => true;
  49. return client;
  50. },
  51. );
  52. }
  53. // 默认API URL列表
  54. final List<String> _defaultApiUrls = ['https://api.znomo.com'];
  55. // 默认Router URL列表
  56. final List<String> _defaultRouterUrls = [];
  57. // 默认LOG API URL列表
  58. final List<String> _defaultLogUrls = [];
  59. // 默认FILE API URL列表
  60. final List<String> _defaultFileUrls = [];
  61. // 默认备用URL列表(txt文件地址)
  62. final List<String> _defaultBackupApiUrls = [];
  63. // 当前使用的URL列表
  64. List<String> _apiUrls = [];
  65. // 当前使用的Router URL列表
  66. List<String> _routerUrls = [];
  67. // 当前使用的LogURL列表
  68. List<String> _logUrls = [];
  69. // 当前使用的FILE URL列表
  70. List<String> _fileUrls = [];
  71. // 当前使用的备用URL列表
  72. List<String> _backupApiUrls = [];
  73. // 当前尝试的索引
  74. int _currentIndex = 0;
  75. // 当前使用的Router URL索引
  76. int _currentRouterIndex = 0;
  77. // 当前使用的LogURL索引
  78. int _currentLogIndex = 0;
  79. // 当前使用的FILE URL索引
  80. int _currentFileIndex = 0;
  81. // 当前正在使用的备用列表索引
  82. int _currentBackupListIndex = 0;
  83. // 添加标志位,标识是否使用的是硬编码URL
  84. bool _isUsingHardcodedUrls = true;
  85. // 添加标志位,记录硬编码请求次数
  86. int _hardcodedRequestCount = 0;
  87. // 初始化方法
  88. Future<void> init() async {
  89. initUrls();
  90. // 尝试加载保存的URLs
  91. await _loadSavedUrls();
  92. }
  93. void initUrls() {
  94. if (Configs.debug) {
  95. _apiUrls = [
  96. 'https://api.znomo.com', // 测试环境
  97. // "https://nomo-api.clickto.dev", // 开发环境
  98. ];
  99. _routerUrls = [];
  100. _logUrls = [];
  101. _fileUrls = [];
  102. _backupApiUrls = [];
  103. } else {
  104. _apiUrls = List.from(_defaultApiUrls);
  105. _routerUrls = List.from(_defaultRouterUrls);
  106. _logUrls = List.from(_defaultLogUrls);
  107. _fileUrls = List.from(_defaultFileUrls);
  108. _backupApiUrls = List.from(_defaultBackupApiUrls);
  109. }
  110. }
  111. // 修改加载保存的URL方法,返回是否成功加载
  112. Future<void> _loadSavedUrls() async {
  113. try {
  114. final prefs = await SharedPreferences.getInstance();
  115. _currentIndex = prefs.getInt(_STORAGE_API_URLS_INDEX) ?? 0;
  116. _currentRouterIndex = prefs.getInt(_STORAGE_ROUTER_URLS_INDEX) ?? 0;
  117. _currentLogIndex = prefs.getInt(_STORAGE_LOG_URLS_INDEX) ?? 0;
  118. _currentFileIndex = prefs.getInt(_STORAGE_FILE_URLS_INDEX) ?? 0;
  119. _isUsingHardcodedUrls = prefs.getBool(_STORAGE_IS_HARDCODED) ?? true;
  120. final savedApiUrls = prefs.getStringList(_STORAGE_API_URLS);
  121. final savedRouterUrls = prefs.getStringList(_STORAGE_ROUTER_URLS);
  122. final savedBackupUrls = prefs.getStringList(_STORAGE_BACKUP_URLS);
  123. final savedLogUrls = prefs.getStringList(_STORAGE_LOG_URLS);
  124. final savedFileUrls = prefs.getStringList(_STORAGE_FILE_URLS);
  125. if (savedApiUrls != null && savedApiUrls.isNotEmpty) {
  126. _apiUrls = savedApiUrls;
  127. }
  128. if (savedRouterUrls != null && savedRouterUrls.isNotEmpty) {
  129. _routerUrls = savedRouterUrls;
  130. }
  131. if (savedBackupUrls != null && savedBackupUrls.isNotEmpty) {
  132. _backupApiUrls = savedBackupUrls;
  133. }
  134. if (savedLogUrls != null && savedLogUrls.isNotEmpty) {
  135. _logUrls = savedLogUrls;
  136. }
  137. if (savedFileUrls != null && savedFileUrls.isNotEmpty) {
  138. _fileUrls = savedFileUrls;
  139. }
  140. log(
  141. 'ApiDomains',
  142. 'Loaded saved URLs: ${_apiUrls.length} APIs, ${_backupApiUrls.length} backups, ${_logUrls.length} logs, ${_fileUrls.length} files',
  143. );
  144. } catch (e) {
  145. log('ApiDomains', 'Error loading saved URLs: $e');
  146. }
  147. }
  148. Future<void> setApiUrls(List<String> urls) async {
  149. try {
  150. final prefs = await SharedPreferences.getInstance();
  151. await prefs.setStringList(_STORAGE_API_URLS, urls);
  152. _currentIndex = 0;
  153. await prefs.setInt(_STORAGE_API_URLS_INDEX, _currentIndex);
  154. ApiCore().setbaseUrl(urls[_currentIndex]);
  155. } catch (e) {
  156. log('ApiDomains', 'Error saving URLs: $e');
  157. }
  158. }
  159. // 添加Router URL
  160. Future<void> addRouterUrls(List<String> urls) async {
  161. try {
  162. var routerList = List<String>.from(_routerUrls);
  163. routerList.insertAll(0, urls);
  164. _routerUrls = routerList.toSet().toList();
  165. await _saveRouterUrls();
  166. } catch (e) {
  167. log('ApiDomains', 'Error add Router URL: $e');
  168. }
  169. }
  170. // 添加logUrl
  171. Future<void> addLogUrls(List<String> urls) async {
  172. try {
  173. var logList = List<String>.from(_logUrls);
  174. logList.insertAll(0, urls);
  175. _logUrls = logList.toSet().toList();
  176. await _saveLogUrls();
  177. } catch (e) {
  178. log('ApiDomains', 'Error add Log URL: $e');
  179. }
  180. }
  181. // 添加fileUrl
  182. Future<void> addFileUrls(List<String> urls) async {
  183. try {
  184. var fileList = List<String>.from(_fileUrls);
  185. fileList.insertAll(0, urls);
  186. _fileUrls = fileList.toSet().toList();
  187. await _saveFileUrls();
  188. } catch (e) {
  189. log('ApiDomains', 'Error add File URL: $e');
  190. }
  191. }
  192. // 保存当前URL列表到持久化存储
  193. Future<void> _saveApiUrls() async {
  194. try {
  195. final prefs = await SharedPreferences.getInstance();
  196. await prefs.setStringList(_STORAGE_API_URLS, _apiUrls);
  197. await prefs.setStringList(_STORAGE_BACKUP_URLS, _backupApiUrls);
  198. } catch (e) {
  199. log('ApiDomains', 'Error saving URLs: $e');
  200. }
  201. }
  202. // 保存当前Router URL列表到持久化存储
  203. Future<void> _saveRouterUrls() async {
  204. try {
  205. final prefs = await SharedPreferences.getInstance();
  206. await prefs.setStringList(_STORAGE_ROUTER_URLS, _routerUrls);
  207. } catch (e) {
  208. log('ApiDomains', 'Error saving Router URLs: $e');
  209. }
  210. }
  211. // 保存当前LogURL列表到持久化存储
  212. Future<void> _saveLogUrls() async {
  213. try {
  214. final prefs = await SharedPreferences.getInstance();
  215. await prefs.setStringList(_STORAGE_LOG_URLS, _logUrls);
  216. } catch (e) {
  217. log('ApiDomains', 'Error saving Log URLs: $e');
  218. }
  219. }
  220. // 保存当前FILE URL列表到持久化存储
  221. Future<void> _saveFileUrls() async {
  222. try {
  223. final prefs = await SharedPreferences.getInstance();
  224. await prefs.setStringList(_STORAGE_FILE_URLS, _fileUrls);
  225. } catch (e) {
  226. log('ApiDomains', 'Error saving File URLs: $e');
  227. }
  228. }
  229. // 保存硬编码状态
  230. Future<void> _saveIsHardcodedUrls() async {
  231. try {
  232. final prefs = await SharedPreferences.getInstance();
  233. await prefs.setBool(_STORAGE_IS_HARDCODED, _isUsingHardcodedUrls);
  234. } catch (e) {
  235. log('ApiDomains', 'Error saving is hardcoded URLs: $e');
  236. }
  237. }
  238. // 保存硬编码索引
  239. Future<void> _saveApiUrlsIndex() async {
  240. try {
  241. final prefs = await SharedPreferences.getInstance();
  242. await prefs.setInt(_STORAGE_API_URLS_INDEX, _currentIndex);
  243. } catch (e) {
  244. log('ApiDomains', 'Error saving hardcoded index: $e');
  245. }
  246. }
  247. // 保存硬编码索引
  248. Future<void> _saveRouterUrlsIndex() async {
  249. try {
  250. final prefs = await SharedPreferences.getInstance();
  251. await prefs.setInt(_STORAGE_ROUTER_URLS_INDEX, _currentRouterIndex);
  252. } catch (e) {
  253. log('ApiDomains', 'Error saving Router URLs index: $e');
  254. }
  255. }
  256. // 保存LogURL硬编码索引
  257. Future<void> _saveLogUrlsIndex() async {
  258. try {
  259. final prefs = await SharedPreferences.getInstance();
  260. await prefs.setInt(_STORAGE_LOG_URLS_INDEX, _currentLogIndex);
  261. } catch (e) {
  262. log('ApiDomains', 'Error saving Log URLs index: $e');
  263. }
  264. }
  265. // 保存FILE URL硬编码索引
  266. Future<void> _saveFileUrlsIndex() async {
  267. try {
  268. final prefs = await SharedPreferences.getInstance();
  269. await prefs.setInt(_STORAGE_FILE_URLS_INDEX, _currentFileIndex);
  270. } catch (e) {
  271. log('ApiDomains', 'Error saving File URLs index: $e');
  272. }
  273. }
  274. // 从Launch数据更新URL列表
  275. Future<void> updateFromLaunch(Launch launch) async {
  276. try {
  277. if (launch.appConfig?.apiUrls != null &&
  278. launch.appConfig!.apiUrls!.isNotEmpty) {
  279. _apiUrls = List<String>.from(launch.appConfig!.apiUrls!);
  280. // 如果ApiCore的baseUrl不在launch的apiUrls中,或者使用硬编码的url,则设置ApiCore的baseUrl为launch的apiUrls的第一个
  281. if (!_apiUrls.contains(ApiCore().baseUrl) || _isUsingHardcodedUrls) {
  282. ApiCore().setbaseUrl(_apiUrls[0]);
  283. }
  284. final baseUrl = ApiCore().baseUrl;
  285. _apiUrls.insert(0, baseUrl);
  286. _apiUrls = _apiUrls.toSet().toList();
  287. _currentIndex = 0;
  288. log('ApiDomains', 'Add ApiCore baseUrl to index 0: $baseUrl');
  289. }
  290. if (launch.appConfig?.routerApiUrls != null &&
  291. launch.appConfig!.routerApiUrls!.isNotEmpty) {
  292. _routerUrls = launch.appConfig!.routerApiUrls!;
  293. final baseUrl = _routerUrls[0];
  294. ApiRouter().setbaseUrl(baseUrl);
  295. _currentRouterIndex = 0;
  296. log('ApiDomains', 'Add Router baseUrl to index 0: $baseUrl');
  297. }
  298. if (launch.appConfig?.backupApiUrls != null &&
  299. launch.appConfig!.backupApiUrls!.isNotEmpty) {
  300. _backupApiUrls = launch.appConfig!.backupApiUrls!;
  301. _currentBackupListIndex = 0;
  302. }
  303. if (launch.appConfig?.appStatUrls != null &&
  304. launch.appConfig!.appStatUrls!.isNotEmpty) {
  305. _logUrls = launch.appConfig!.appStatUrls!;
  306. // 设置ApiLog的baseUrl为launch的appStatUrls的第一个
  307. final baseUrl = _logUrls[0];
  308. ApiLog().setbaseUrl(baseUrl);
  309. _currentLogIndex = 0;
  310. log('ApiDomains', 'Add ApiLog baseUrl to index 0: $baseUrl');
  311. }
  312. if (launch.appConfig?.logFileUploadUrls != null &&
  313. launch.appConfig!.logFileUploadUrls!.isNotEmpty) {
  314. _fileUrls = launch.appConfig!.logFileUploadUrls!;
  315. // 设置ApiFile的baseUrl为launch的logFileUploadUrls的第一个
  316. final baseUrl = _fileUrls[0];
  317. ApiFile().setbaseUrl(baseUrl);
  318. _currentFileIndex = 0;
  319. log('ApiDomains', 'Add ApiFile baseUrl to index 0: $baseUrl');
  320. }
  321. _isUsingHardcodedUrls = false;
  322. await _saveIsHardcodedUrls();
  323. await _saveApiUrls();
  324. await _saveApiUrlsIndex();
  325. await _saveRouterUrls();
  326. await _saveRouterUrlsIndex();
  327. await _saveLogUrls();
  328. await _saveLogUrlsIndex();
  329. await _saveFileUrls();
  330. await _saveFileUrlsIndex();
  331. log('ApiDomains', 'Updated URLs from launch data');
  332. } catch (e) {
  333. log('ApiDomains', 'Error updating URLs from launch data: $e');
  334. }
  335. }
  336. // 获取当前ApiURL
  337. String getApiUrl() {
  338. if (_currentIndex >= _apiUrls.length) {
  339. _currentIndex = 0;
  340. }
  341. return _apiUrls[_currentIndex];
  342. }
  343. // 获取当前Router URL
  344. String getRouterUrl() {
  345. if (_currentRouterIndex >= _routerUrls.length) {
  346. _currentRouterIndex = 0;
  347. }
  348. return _routerUrls[_currentRouterIndex];
  349. }
  350. // 获取当前LogURL
  351. String getLogUrl() {
  352. if (_currentLogIndex >= _logUrls.length) {
  353. _currentLogIndex = 0;
  354. }
  355. return _logUrls[_currentLogIndex];
  356. }
  357. // 获取当前FILE URL
  358. String getFileUrl() {
  359. if (_currentFileIndex >= _fileUrls.length) {
  360. _currentFileIndex = 0;
  361. }
  362. return _fileUrls[_currentFileIndex];
  363. }
  364. Future<bool> isHardcodedUrls() async {
  365. return _isUsingHardcodedUrls;
  366. }
  367. // 获取下一次要尝试的URL
  368. Future<String> getNextApiUrl() async {
  369. // 使用硬编码URLs的情况
  370. if (_isUsingHardcodedUrls) {
  371. // 如果硬编码请求次数大于等于最大重试次数,返回空字符串
  372. if (_hardcodedRequestCount >= RETRY_COUNT_PER_LAUNCH) {
  373. _hardcodedRequestCount = 0;
  374. return '';
  375. }
  376. // 获取下一个索引
  377. int nextIndex = _currentIndex + 1;
  378. if (nextIndex >= _apiUrls.length) {
  379. if (_hardcodedRequestCount < RETRY_COUNT_PER_LAUNCH) {
  380. final isSuccess = await _switchToBackupList();
  381. if (isSuccess) {
  382. return getApiUrl();
  383. } else {
  384. _hardcodedRequestCount = 0;
  385. return '';
  386. }
  387. }
  388. return '';
  389. }
  390. final url = _apiUrls[nextIndex];
  391. _currentIndex++;
  392. _hardcodedRequestCount++;
  393. await _saveApiUrlsIndex();
  394. return url;
  395. }
  396. // 使用Launch数据的情况
  397. else {
  398. // 如果当前列表已用完
  399. int nextIndex = _currentIndex + 1;
  400. if (nextIndex >= _apiUrls.length) {
  401. // 尝试切换到备用列表
  402. final isSuccess = await _switchToBackupList();
  403. if (isSuccess) {
  404. return getApiUrl();
  405. } else {
  406. return '';
  407. }
  408. }
  409. // 返回当前URL
  410. final url = _apiUrls[nextIndex];
  411. _currentIndex++;
  412. await _saveApiUrlsIndex();
  413. return url;
  414. }
  415. }
  416. // 获取下一次要尝试的Router URL
  417. Future<String> getNextRouterUrl() async {
  418. // 获取下一个索引
  419. int nextIndex = _currentRouterIndex + 1;
  420. if (nextIndex >= _routerUrls.length) {
  421. _currentRouterIndex = 0;
  422. await _saveRouterUrlsIndex();
  423. return '';
  424. }
  425. // 返回所有剩余的URL
  426. final url = _routerUrls[nextIndex];
  427. _currentRouterIndex++;
  428. await _saveRouterUrlsIndex();
  429. return url;
  430. }
  431. // 获取下一次要尝试的LogURL
  432. Future<String> getNextLogUrl() async {
  433. // 获取下一个索引
  434. int nextIndex = _currentLogIndex + 1;
  435. if (nextIndex >= _logUrls.length) {
  436. _currentLogIndex = 0;
  437. await _saveLogUrlsIndex();
  438. return '';
  439. }
  440. // 返回所有剩余的URL
  441. final url = _logUrls[nextIndex];
  442. _currentLogIndex++;
  443. await _saveLogUrlsIndex();
  444. return url;
  445. }
  446. // 获取下一次要尝试的FILE URL
  447. Future<String> getNextFileUrl() async {
  448. // 获取下一个索引
  449. int nextIndex = _currentFileIndex + 1;
  450. if (nextIndex >= _fileUrls.length) {
  451. _currentFileIndex = 0;
  452. await _saveFileUrlsIndex();
  453. return '';
  454. }
  455. // 返回所有剩余的URL
  456. final url = _fileUrls[nextIndex];
  457. _currentFileIndex++;
  458. await _saveFileUrlsIndex();
  459. return url;
  460. }
  461. // 切换到备用列表
  462. Future<bool> _switchToBackupList() async {
  463. while (_currentBackupListIndex < _backupApiUrls.length) {
  464. try {
  465. if (_currentBackupListIndex >= _backupApiUrls.length) {
  466. log('ApiDomains', 'Error: Backup URL index out of bounds');
  467. break;
  468. }
  469. final backupUrl = _backupApiUrls[_currentBackupListIndex];
  470. log('ApiDomains', 'Trying backup URL: $backupUrl');
  471. // 添加talker日志(避免重复添加)
  472. if (!_monitorInterceptorAdded) {
  473. dio.interceptors.add(SimpleNoSignApiMonitorInterceptor());
  474. _monitorInterceptorAdded = true;
  475. }
  476. final response = await dio.get(backupUrl);
  477. if (response.statusCode == 200) {
  478. log('Before decryption: ${response.data}');
  479. final decryptedBytes = Crypto.decryptBytes(
  480. response.data.toString().trim(),
  481. Keys.aesKey,
  482. Keys.aesIv,
  483. );
  484. final jsonText = utf8.decode(decryptedBytes);
  485. log('Decryption results: $jsonText');
  486. final Map<String, dynamic> map = jsonDecode(jsonText);
  487. if (map['apiUrls'] != null) {
  488. final list = List<String>.from(map['apiUrls']);
  489. _apiUrls = list;
  490. _currentIndex = 0;
  491. // 当前备用文件成功,标志可以尝试下一个
  492. _currentBackupListIndex++;
  493. await _saveApiUrls();
  494. await _saveApiUrlsIndex();
  495. log('ApiDomains', 'Switched to backup list: ${list.length} URLs');
  496. return true;
  497. }
  498. }
  499. // 当前备用文件失败,尝试下一个
  500. _currentBackupListIndex++;
  501. } catch (e) {
  502. log('ApiDomains', 'Error switching to backup list: $e');
  503. _currentBackupListIndex++;
  504. }
  505. }
  506. // 如果所有备用URL都失败了,重置为硬编码模式
  507. _isUsingHardcodedUrls = true;
  508. _currentIndex = 0;
  509. _currentBackupListIndex = 0;
  510. initUrls();
  511. await _saveIsHardcodedUrls();
  512. await _saveApiUrls();
  513. await _saveApiUrlsIndex();
  514. return false;
  515. }
  516. // 获取所有logUrl
  517. List<String> getAllLogUrls() {
  518. return _logUrls;
  519. }
  520. }