feat: 更新 H5 线路配置逻辑,添加默认线路处理和相关单元测试
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Flutter WebView 套壳 App,启动后会请求 `/client_config/query` 获取 H5 线路配置,并加载第一条可用线路。
|
Flutter WebView 套壳 App,启动后会请求 `/client_config/query` 获取 H5 线路配置,并加载第一条可用线路。
|
||||||
|
|
||||||
|
默认情况下,非本地 Web 访问会使用当前服务地址的 origin 作为 H5 线路和接口域名,例如访问 `https://app.example.com/app/` 时会请求 `https://app.example.com/client_config/query`。本地访问(如 `localhost`、`127.0.0.1`)继续使用固定兜底域名 `https://h5-test.imharry.work/`。
|
||||||
|
|
||||||
## H5 线路切换
|
## H5 线路切换
|
||||||
|
|
||||||
线路切换在 Flutter 套壳层完成。远程配置不可用时,会先使用启动兜底线路;也可以在打包时覆盖启动线路或配置接口地址:
|
线路切换在 Flutter 套壳层完成。远程配置不可用时,会先使用启动兜底线路;也可以在打包时覆盖启动线路或配置接口地址:
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ class AppConfig {
|
|||||||
|
|
||||||
static List<H5Line> get h5Lines {
|
static List<H5Line> get h5Lines {
|
||||||
final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty
|
final configuredUrls = _dartDefinedH5LineUrls.trim().isEmpty
|
||||||
? _normalizedUniqueUrls(_environmentHosts[currentEnvironment] ??
|
? _normalizedUniqueUrls(defaultH5LineUrlsForBaseUri(Uri.base))
|
||||||
const [_bootstrapH5LineUrl])
|
|
||||||
: _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(','));
|
: _normalizedUniqueUrls(_dartDefinedH5LineUrls.split(','));
|
||||||
final urls = configuredUrls.isEmpty
|
final urls = configuredUrls.isEmpty
|
||||||
? _normalizedUniqueUrls(const [_bootstrapH5LineUrl])
|
? _normalizedUniqueUrls(const [_bootstrapH5LineUrl])
|
||||||
@@ -71,18 +70,26 @@ class AppConfig {
|
|||||||
return const [];
|
return const [];
|
||||||
}
|
}
|
||||||
|
|
||||||
final homeUri = lines.first.uri;
|
return clientConfigQueryUrisForHomeUri(lines.first.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> defaultH5LineUrlsForBaseUri(Uri baseUri) {
|
||||||
|
final runtimeHomeUrl = _runtimeHomeUrlFromBaseUri(baseUri);
|
||||||
|
if (runtimeHomeUrl != null) {
|
||||||
|
return [runtimeHomeUrl];
|
||||||
|
}
|
||||||
|
|
||||||
|
final environmentHosts = _environmentHosts[currentEnvironment];
|
||||||
|
if (environmentHosts == null || environmentHosts.isEmpty) {
|
||||||
|
return const [_bootstrapH5LineUrl];
|
||||||
|
}
|
||||||
|
return environmentHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Uri> clientConfigQueryUrisForHomeUri(Uri homeUri) {
|
||||||
return [
|
return [
|
||||||
homeUri.replace(
|
_originUriWithPath(homeUri, '/client_config/query'),
|
||||||
path: '/client_config/query',
|
_originUriWithPath(homeUri, '/api/user/client_config/query'),
|
||||||
queryParameters: const {},
|
|
||||||
fragment: '',
|
|
||||||
),
|
|
||||||
homeUri.replace(
|
|
||||||
path: '/api/user/client_config/query',
|
|
||||||
queryParameters: const {},
|
|
||||||
fragment: '',
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +198,69 @@ class AppConfig {
|
|||||||
return uri.replace(path: path).toString();
|
return uri.replace(path: path).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String? _runtimeHomeUrlFromBaseUri(Uri baseUri) {
|
||||||
|
if (_isLocalRuntimeUri(baseUri)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _originUriWithPath(baseUri, '/').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uri _originUriWithPath(Uri uri, String path) {
|
||||||
|
final normalizedPath = path.startsWith('/') ? path : '/$path';
|
||||||
|
return Uri.parse('${uri.scheme}://${uri.authority}$normalizedPath');
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _isLocalRuntimeUri(Uri uri) {
|
||||||
|
if (uri.scheme != 'http' && uri.scheme != 'https') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final host = _normalizeHostForCompare(uri.host);
|
||||||
|
if (host.isEmpty) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host == 'localhost' || host.endsWith('.localhost') || host == '::1') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ipv4Parts = _tryParseIpv4Address(host);
|
||||||
|
if (ipv4Parts == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipv4Parts[0] == 127 || ipv4Parts.every((part) => part == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _normalizeHostForCompare(String host) {
|
||||||
|
final normalized = host.trim().toLowerCase();
|
||||||
|
if (normalized.endsWith('.')) {
|
||||||
|
return normalized.substring(0, normalized.length - 1);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<int>? _tryParseIpv4Address(String host) {
|
||||||
|
final parts = host.split('.');
|
||||||
|
if (parts.length != 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final octets = <int>[];
|
||||||
|
for (final part in parts) {
|
||||||
|
if (part.isEmpty || !RegExp(r'^\d+$').hasMatch(part)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final value = int.tryParse(part);
|
||||||
|
if (value == null || value < 0 || value > 255) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
octets.add(value);
|
||||||
|
}
|
||||||
|
return octets;
|
||||||
|
}
|
||||||
|
|
||||||
static String _replaceWildcardHost(
|
static String _replaceWildcardHost(
|
||||||
String value, {
|
String value, {
|
||||||
String Function()? wildcardFactory,
|
String Function()? wildcardFactory,
|
||||||
|
|||||||
@@ -20,6 +20,30 @@ void main() {
|
|||||||
expect(Uri.parse(lines.first.url).path, '/');
|
expect(Uri.parse(lines.first.url).path, '/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('uses current non-local service origin as default H5 line', () {
|
||||||
|
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
||||||
|
Uri.parse('https://app.example.com/shell/index.html?from=local#hash'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(urls, ['https://app.example.com/']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves current service port when building default H5 line', () {
|
||||||
|
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
||||||
|
Uri.parse('https://chat.example.com:8443/app/'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(urls, ['https://chat.example.com:8443/']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps fixed bootstrap H5 line for local browser access', () {
|
||||||
|
final urls = AppConfig.defaultH5LineUrlsForBaseUri(
|
||||||
|
Uri.parse('http://localhost:3000/'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(urls, ['https://h5-test.imharry.work/']);
|
||||||
|
});
|
||||||
|
|
||||||
test('uses fixed client config query parameters', () {
|
test('uses fixed client config query parameters', () {
|
||||||
expect(AppConfig.clientConfigQueryPayload, {
|
expect(AppConfig.clientConfigQueryPayload, {
|
||||||
'device': 'h5',
|
'device': 'h5',
|
||||||
@@ -31,6 +55,20 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('builds client config query APIs from current service origin', () {
|
||||||
|
final uris = AppConfig.clientConfigQueryUrisForHomeUri(
|
||||||
|
Uri.parse('https://app.example.com/app/index.html?from=local#hash'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
uris.map((uri) => uri.toString()),
|
||||||
|
[
|
||||||
|
'https://app.example.com/client_config/query',
|
||||||
|
'https://app.example.com/api/user/client_config/query',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('splits client config resourceUrl into indexed H5 lines', () {
|
test('splits client config resourceUrl into indexed H5 lines', () {
|
||||||
final lines = AppConfig.h5LinesFromResourceUrl(
|
final lines = AppConfig.h5LinesFromResourceUrl(
|
||||||
'line-one.example\nhttps://line-two.example/app\n\nline-three.example',
|
'line-one.example\nhttps://line-two.example/app\n\nline-three.example',
|
||||||
|
|||||||
Reference in New Issue
Block a user