fix account profile and improve operation logs

This commit is contained in:
ngfchl
2026-07-18 22:35:47 +08:00
parent 6b202475d1
commit dbacdb854f
9 changed files with 255 additions and 126 deletions
+41 -26
View File
@@ -1,4 +1,5 @@
import 'dart:developer';
import 'dart:io';
import 'dart:math';
import 'package:dio/dio.dart';
@@ -28,10 +29,6 @@ class Http {
headers: headers,
);
final stopwatch = Stopwatch()..start();
final endpoint = useAccountDio ? 'account' : 'api';
log('[HTTP:$endpoint] -> $method $path');
try {
final res = await client.request(
path,
@@ -40,23 +37,10 @@ class Http {
cancelToken: cancelToken,
options: mergedOptions,
);
stopwatch.stop();
log(
'[HTTP:$endpoint] <- $method $path status=${res.statusCode} '
'elapsed=${stopwatch.elapsedMilliseconds}ms',
);
return res.data as T;
} on DioException catch (e) {
stopwatch.stop();
final status = e.response?.statusCode;
log(
'[HTTP:$endpoint] !! $method $path status=${status ?? '-'} '
'type=${e.type.name} elapsed=${stopwatch.elapsedMilliseconds}ms',
);
// 将业务错误信息包装为 ApiException
final status = e.response?.statusCode;
final errorMsg = e.error is String ? (e.error as String) : '';
if (errorMsg.isNotEmpty) {
throw ApiException(status: status, message: errorMsg);
@@ -64,10 +48,6 @@ class Http {
rethrow;
} catch (e) {
stopwatch.stop();
log(
'[HTTP:$endpoint] !! $method $path elapsed=${stopwatch.elapsedMilliseconds}ms',
);
rethrow;
}
}
@@ -121,6 +101,7 @@ class Http {
bool authenticated = false,
}) async {
final headers = <String, dynamic>{
..._accountHeaders(),
'did': _getDeviceID(),
'dt': '4',
...?extraHeaders,
@@ -163,9 +144,7 @@ class Http {
static void _checkTokenExpiry() {
final expiresAt = StorageManager.get<int>(StorageKeys.tokenExpiresAt);
if (expiresAt == null) return;
if (DateTime.now().millisecondsSinceEpoch > expiresAt) {
log('[HTTP] token expired, will refresh on next 401');
}
if (DateTime.now().millisecondsSinceEpoch > expiresAt) {}
}
static String _getDeviceID() {
@@ -173,6 +152,42 @@ class Http {
'flutter-${DateTime.now().millisecondsSinceEpoch}';
}
/// 账号网关会校验网页客户端的设备标识;与源客户端保持一致。
static Map<String, String> _accountHeaders() {
final deviceID = _getDeviceID();
return {
'x-client-id': 'aMe-8VSlkrbQXpUR',
'x-client-version': '0.0.1',
'x-device-id': deviceID,
'x-device-model': 'chrome%2F150.0.0.0',
'x-device-name': 'PC-Chrome',
'x-device-sign': 'wdi10.$deviceID${_randomHex(16)}',
'x-net-work-type': 'NONE',
'x-os-version': _webPlatformName(),
'x-platform-version': '1',
'x-protocol-version': '301',
'x-provider-name': 'NONE',
'x-sdk-version': '9.0.2',
};
}
static String _webPlatformName() {
if (Platform.isMacOS) return 'MacIntel';
if (Platform.isWindows) return 'Win32';
if (Platform.isLinux) return 'Linux x86_64';
if (Platform.isIOS) return 'iPhone';
if (Platform.isAndroid) return 'Linux armv8l';
return Platform.operatingSystem;
}
static String _randomHex(int bytes) {
final random = Random.secure();
return List.generate(
bytes,
(_) => random.nextInt(256).toRadixString(16).padLeft(2, '0'),
).join();
}
// ── Helpers ─────────────────────────────────────────────────────
static String _generateTraceparent() {
@@ -1,34 +1,52 @@
import 'package:dio/dio.dart';
import '../http_error.dart';
import '../../logging/app_logger.dart';
class AppLogInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
AppLogger.debug(
'HTTP',
'${options.method} ${options.uri.path}${options.uri.hasQuery ? '?${options.uri.query}' : ''}',
);
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
AppLogger.debug(
'HTTP',
'${response.statusCode ?? 0} ${response.requestOptions.method} ${response.requestOptions.uri.path}',
);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
final status = err.response?.statusCode;
final serverMessage = err.response?.data is Map
? extractHttpMessage(err.response?.data)
: null;
final detail = serverMessage?.trim().isNotEmpty == true
? serverMessage!
: (err.error?.toString().trim().isNotEmpty == true
? err.error.toString()
: err.type.name);
AppLogger.error(
'HTTP',
'${err.requestOptions.method} ${err.requestOptions.uri.path} 失败',
error: err.message,
stackTrace: err.stackTrace,
'${_actionName(err.requestOptions.path)}失败(状态码 ${status ?? '-'},原因:$detail',
);
handler.next(err);
}
static String _actionName(String path) {
if (path.contains('/v1/user/me')) return '获取账户资料';
if (path.contains('/assets/v1/get_assets')) return '获取云盘空间信息';
if (path.contains('/search_files')) return '搜索云盘文件';
if (path.contains('/get_file_list')) return '读取文件列表';
if (path.contains('/get_file_detail')) return '读取文件详情';
if (path.contains('/create_dir')) return '新建文件夹';
if (path.contains('/move_file')) return '移动文件';
if (path.contains('/copy_file')) return '复制文件';
if (path.contains('/rename')) return '重命名文件';
if (path.contains('/recycle_file')) return '移入回收站';
if (path.contains('/delete_file')) return '删除文件';
if (path.contains('/auth/token')) return '刷新登录凭据';
if (path.contains('/auth/')) return '登录认证';
if (path.contains('/tmdb')) return '获取影视资料';
return '云盘服务请求';
}
}
@@ -1,8 +1,7 @@
import 'dart:async';
import 'dart:developer';
import 'package:dio/dio.dart';
import '../../logging/app_logger.dart';
import '../../config/app_config.dart';
import '../../storage/storage_manager.dart';
import '../http_error.dart';
@@ -60,10 +59,6 @@ class AuthInterceptor extends Interceptor {
final status = err.response?.statusCode;
final alreadyLoggedOut = status == 401 && _isAlreadyLoggedOut;
if (!alreadyLoggedOut) {
log('[Auth] error path=${err.requestOptions.path} status=$status');
}
// 免认证路径的错误直接放行
if (_isAuthExemptPath(err.requestOptions.path)) {
return handler.next(err);
@@ -127,7 +122,7 @@ class AuthInterceptor extends Interceptor {
// 开始刷新
_isRefreshing = true;
log('[Auth] refreshing access token');
AppLogger.info('Auth', '登录凭据已过期,正在刷新');
try {
final dio = Dio(
@@ -158,7 +153,7 @@ class AuthInterceptor extends Interceptor {
),
);
log('[Auth] access token refreshed');
AppLogger.info('Auth', '登录凭据刷新完成');
final data = res.data;
final newAccess = _findStringDeep(data, ['access_token', 'accessToken']);
@@ -202,7 +197,7 @@ class AuthInterceptor extends Interceptor {
final response = await DioClient.dio.fetch(request);
return handler.resolve(response);
} catch (e) {
log('[Auth] token refresh failed: $e');
AppLogger.warning('Auth', '登录凭据刷新失败,将退出当前账号');
for (var c in _waitQueue) {
c.complete();
@@ -228,7 +223,7 @@ class AuthInterceptor extends Interceptor {
if (_logoutScheduled) return;
_logoutScheduled = true;
log('[Auth] logout scheduled');
AppLogger.info('Auth', '登录状态已失效,正在退出当前账号');
await StorageManager.delete(StorageKeys.accessToken);
await StorageManager.delete(StorageKeys.refreshToken);
await StorageManager.delete(StorageKeys.tokenExpiresAt);
@@ -1,6 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dio/dio.dart';
import '../http_error.dart';
@@ -72,27 +70,6 @@ class ResponseInterceptor extends Interceptor {
return handler.next(err);
}
// 网络错误
if (err.type == DioExceptionType.connectionError ||
err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.sendTimeout ||
err.type == DioExceptionType.receiveTimeout) {
if (!suppressErrorToast(err.requestOptions)) {
log(
'[HTTP] ${requestToastMessage(err.requestOptions, '请求超时或连接失败,请检查网络')}',
);
}
return handler.next(err);
}
// 其他错误
if (!suppressErrorToast(err.requestOptions)) {
final msg = err.response?.data is Map
? extractHttpMessage(err.response?.data) ?? '请求失败'
: '请求失败';
log('[HTTP] ${requestToastMessage(err.requestOptions, msg)}');
}
return handler.next(err);
}