feat(flutter): 增加 debug 运行时服务器地址设置

This commit is contained in:
Zhou Weidong committed 2026-10-17 22:54:07 +08:00
1 parent b76228905c
commit 907562a874
14 files changed
+590 -27

No files matched your search

+26 -6
View File
@@ -15,18 +15,28 @@ class AppConfig {
/// `ipconfig getifaddr en0` 重新确认并改这里;
/// 也可以换成 Bonjour 名字 `http://<LocalHostName>.local:8081`(本机是
/// `jiangdeMacBook-Air.local`),换网络不用改,但部分办公网会拦组播解析。
static const String devHostBaseUrl =
'http://jiangdeMacBook-Air.local:8081';
static const String devHostBaseUrl = 'http://jiangdeMacBook-Air.local:8081';
static const String _loopbackBaseUrl = 'http://127.0.0.1:8081';
static const String _overrideBaseUrl = String.fromEnvironment('API_BASE_URL');
static String get apiBaseUrl =>
_overrideBaseUrl.isNotEmpty ? _overrideBaseUrl : _defaultBaseUrl;
static String _runtimeBaseUrl = '';
static final String _defaultBaseUrl =
_runsOnDevMachine ? _loopbackBaseUrl : devHostBaseUrl;
/// Build-time server URL supplied through `--dart-define=API_BASE_URL`.
static String get compiledBaseUrl => _overrideBaseUrl;
/// Existing platform-aware fallback used when no runtime or build override exists.
static String get defaultBaseUrl => _defaultBaseUrl;
/// Effective server URL, including the debug-only runtime override.
static String get apiBaseUrl => _runtimeBaseUrl.isNotEmpty
? _runtimeBaseUrl
: (compiledBaseUrl.isNotEmpty ? compiledBaseUrl : defaultBaseUrl);
static final String _defaultBaseUrl = _runsOnDevMachine
? _loopbackBaseUrl
: devHostBaseUrl;
/// 模拟器、桌面端与开发机同机,环回地址可用;iOS 真机不满足。
/// Android 没有等价的探测方式,统一按真机处理(模拟器用
@@ -39,6 +49,16 @@ class AppConfig {
(defaultTargetPlatform == TargetPlatform.iOS &&
Platform.environment.containsKey('SIMULATOR_DEVICE_NAME'));
/// Applies an already normalized runtime URL. Passing null clears it.
static void applyRuntimeBaseUrl(String? value) {
_runtimeBaseUrl = value ?? '';
}
/// Clears the in-memory runtime override, mainly for tests and restarts.
static void clearRuntimeBaseUrl() {
_runtimeBaseUrl = '';
}
static const Duration requestTimeout = Duration(seconds: 8);
/// 后端返回的是相对路径(/files/...),这里补上服务地址。
+86
View File
@@ -0,0 +1,86 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'app_config.dart';
/// Debug-only runtime server endpoint persisted outside build-time defines.
abstract final class ServerEndpoint {
/// Preference key used for the runtime server address.
static const String preferenceKey = 'dev.api_base_url';
/// Returns a normalized http(s) URL, or null for invalid input.
static String? normalize(String? raw) {
final value = raw?.trim() ?? '';
final uri = Uri.tryParse(value);
if (uri == null ||
(uri.scheme != 'http' && uri.scheme != 'https') ||
uri.host.isEmpty) {
return null;
}
return value.replaceFirst(RegExp(r'/+$'), '');
}
/// Resolves runtime > dart-define > default using normalized valid values.
static String resolve({
required String? stored,
required String compiled,
required String fallback,
}) {
return normalize(stored) ??
normalize(compiled) ??
normalize(fallback) ??
fallback;
}
/// Loads the persisted override before the app creates its ApiClient.
///
/// In release builds this returns immediately without reading preferences.
static Future<void> load({
bool debug = kDebugMode,
SharedPreferencesAsync? preferences,
}) async {
if (!debug) return;
try {
final prefs = preferences ?? SharedPreferencesAsync();
final stored = await prefs.getString(preferenceKey);
AppConfig.applyRuntimeBaseUrl(
resolve(
stored: stored,
compiled: AppConfig.compiledBaseUrl,
fallback: AppConfig.defaultBaseUrl,
),
);
} catch (_) {
AppConfig.clearRuntimeBaseUrl();
}
}
/// Persists a normalized runtime endpoint.
static Future<bool> save(
String raw, {
SharedPreferencesAsync? preferences,
}) async {
final normalized = normalize(raw);
if (normalized == null) return false;
try {
await (preferences ?? SharedPreferencesAsync()).setString(
preferenceKey,
normalized,
);
return true;
} catch (_) {
return false;
}
}
/// Removes the persisted runtime endpoint.
static Future<void> clear({SharedPreferencesAsync? preferences}) async {
try {
await (preferences ?? SharedPreferencesAsync()).remove(preferenceKey);
} catch (_) {
// Clearing an optional developer override is best effort.
}
}
}