优化:登录流程,错误上报
This commit is contained in:
parent
e44a09e7e1
commit
982bed4802
@ -96,7 +96,7 @@ AES 加密
|
|||||||
发送请求
|
发送请求
|
||||||
```
|
```
|
||||||
|
|
||||||
**请求头**:`ProxyClient` 会将 [AppConfig.packageName] 写入映射后的包名字段(原始名 `pkg`);若已设置用户 token,默认还会写入 `User_token`。**`UserApi.fast_login` 等无需登录态接口**内部使用 `includeUserTokenInHeader: false`,避免把旧 token 打进 `filter_type`。其余请求也可在直接调用 `ProxyClient.request` 时传入该参数。
|
**请求头**:`ProxyClient` 默认将 [AppConfig.packageName] 写入映射后的包名字段(原始名 `pkg`);若已设置用户 token,默认还会写入 `User_token`。**`UserApi.fastLogin`** 对内层请求使用 `includeUserTokenInHeader: false`(不传 token,`pkg` 仍按默认行为传递)。其余接口可按需在 `ProxyClient.request` 上传入上述开关。
|
||||||
|
|
||||||
**请求体**:各 `*Api` 方法使用与《客户端指南》解密表一致的**原始字段名**(如 `referer`、`deviceId`、`fileUrls` / `contentType` / `content`)。
|
**请求体**:各 `*Api` 方法使用与《客户端指南》解密表一致的**原始字段名**(如 `referer`、`deviceId`、`fileUrls` / `contentType` / `content`)。
|
||||||
|
|
||||||
@ -146,9 +146,9 @@ final res = await UserApi.fastLogin(
|
|||||||
deviceId: '设备ID',
|
deviceId: '设备ID',
|
||||||
sign: 'MD5(deviceId)大写',
|
sign: 'MD5(deviceId)大写',
|
||||||
app: 'HAndroid', // 必填:HIOS / HAndroid
|
app: 'HAndroid', // 必填:HIOS / HAndroid
|
||||||
referer: '归因来源', // 可选
|
referer: '归因来源', // 可选;`gg` 时常为 Play Install Referrer;框架 start 取不到时用 utm_source=google-play&utm_medium=organic
|
||||||
ch: '渠道号', // 可选
|
ch: '渠道号', // 可选
|
||||||
type: 'fb', // 可选;未传时默认 fb
|
type: 'gg', // 可选;未传时默认 gg(Google Play 归因)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
|
|||||||
@ -49,7 +49,7 @@ sequenceDiagram
|
|||||||
### 3.1 启动与快速登录
|
### 3.1 启动与快速登录
|
||||||
|
|
||||||
- 延迟与重试策略由 `FrameworkAuthService.start` 控制(默认启动延迟、登录重试等)。
|
- 延迟与重试策略由 `FrameworkAuthService.start` 控制(默认启动延迟、登录重试等)。
|
||||||
- **`UserApi.fastLogin`**:`POST /v1/user/fast_login`。请求头**仅**带 `pkg`,**不带** `User_token`(见 `UserApi` 文档注释)。
|
- **`UserApi.fastLogin`**:`POST /v1/user/fast_login`。线网内层 headers 保留 `pkg`,但**不注入** `User_token`(见 `UserApi` / `ProxyClient` 注释)。Query `type` 未传时默认为 **`gg`**(Google Play 归因)。`FrameworkAuthService` 强制 `type=gg`:`referer` 优先 Play Install Referrer,取不到时用 `utm_source=google-play&utm_medium=organic`。
|
||||||
- 成功后框架将返回的 `userToken` 写入 **`ApiClient.instance.setUserToken`**,此后代理请求自动附带 `pkg` 与 `User_token`(见 `ProxyClient` 行为与 `UserApi` 说明)。
|
- 成功后框架将返回的 `userToken` 写入 **`ApiClient.instance.setUserToken`**,此后代理请求自动附带 `pkg` 与 `User_token`(见 `ProxyClient` 行为与 `UserApi` 说明)。
|
||||||
|
|
||||||
### 3.2 归因上报(与首页数据并行准备)
|
### 3.2 归因上报(与首页数据并行准备)
|
||||||
|
|||||||
@ -32,12 +32,14 @@ export 'src/services/analytics_attribution_callbacks.dart';
|
|||||||
export 'src/services/analytics_events.dart';
|
export 'src/services/analytics_events.dart';
|
||||||
export 'src/services/analytics_service.dart';
|
export 'src/services/analytics_service.dart';
|
||||||
export 'src/services/auth_service.dart';
|
export 'src/services/auth_service.dart';
|
||||||
|
export 'src/services/login_identity_cache.dart';
|
||||||
export 'src/services/facebook_service.dart';
|
export 'src/services/facebook_service.dart';
|
||||||
export 'src/services/feedback_api.dart';
|
export 'src/services/feedback_api.dart';
|
||||||
export 'src/services/image_api.dart';
|
export 'src/services/image_api.dart';
|
||||||
export 'src/services/image_progress_poll.dart';
|
export 'src/services/image_progress_poll.dart';
|
||||||
export 'src/services/image_compress.dart';
|
export 'src/services/image_compress.dart';
|
||||||
export 'src/services/image_presigned_upload_create_flow.dart';
|
export 'src/services/image_presigned_upload_create_flow.dart';
|
||||||
|
export 'src/services/image_upload_expected_size_cache.dart';
|
||||||
export 'src/services/image_task_history.dart';
|
export 'src/services/image_task_history.dart';
|
||||||
export 'src/services/task_upload_cover_store.dart';
|
export 'src/services/task_upload_cover_store.dart';
|
||||||
export 'src/services/user_account_refresh.dart';
|
export 'src/services/user_account_refresh.dart';
|
||||||
|
|||||||
@ -104,11 +104,11 @@ class ProxyClient {
|
|||||||
/// 响应 data 会自动从线网转回逻辑字段名。
|
/// 响应 data 会自动从线网转回逻辑字段名。
|
||||||
///
|
///
|
||||||
/// **请求头(自动注入)**
|
/// **请求头(自动注入)**
|
||||||
/// - [AppConfig.packageName] → 逻辑字段名 `pkg`(再映射为线网请求头键)
|
/// - 若 [includePackageInHeader] 为 true(默认):[AppConfig.packageName] → 逻辑字段名 `pkg`(再映射为线网请求头键)
|
||||||
/// - 若已 [userToken] 且 [includeUserTokenInHeader] 为 true,则注入 `User_token`
|
/// - 若已 [userToken] 且 [includeUserTokenInHeader] 为 true,则注入 `User_token`
|
||||||
///
|
///
|
||||||
/// 与文档一致:**设备快速登录等无需登录态接口**应传 `includeUserTokenInHeader: false`,
|
/// 与文档一致:**设备快速登录等无需登录态接口**应传 `includeUserTokenInHeader: false`,
|
||||||
/// 避免历史 token 进入 `filter_type`。
|
/// 避免历史 token 进入 `filter_type`。若同时 `includePackageInHeader: false`,则内层 headers 不注入 `pkg`(由 query 等自行携带包名)。
|
||||||
Future<ApiResponse> request({
|
Future<ApiResponse> request({
|
||||||
required String path,
|
required String path,
|
||||||
required String method,
|
required String method,
|
||||||
@ -116,12 +116,13 @@ class ProxyClient {
|
|||||||
Map<String, String>? queryParams,
|
Map<String, String>? queryParams,
|
||||||
Map<String, dynamic>? body,
|
Map<String, dynamic>? body,
|
||||||
bool includeUserTokenInHeader = true,
|
bool includeUserTokenInHeader = true,
|
||||||
|
bool includePackageInHeader = true,
|
||||||
}) async {
|
}) async {
|
||||||
final pk = config.proxyKeys;
|
final pk = config.proxyKeys;
|
||||||
final mapping = config.fieldMapping;
|
final mapping = config.fieldMapping;
|
||||||
|
|
||||||
var headersMap = Map<String, dynamic>.from(headers ?? {});
|
var headersMap = Map<String, dynamic>.from(headers ?? {});
|
||||||
if (config.packageName.isNotEmpty) {
|
if (includePackageInHeader && config.packageName.isNotEmpty) {
|
||||||
headersMap[mapping.headerPackageNameField] = config.packageName;
|
headersMap[mapping.headerPackageNameField] = config.packageName;
|
||||||
}
|
}
|
||||||
if (includeUserTokenInHeader &&
|
if (includeUserTokenInHeader &&
|
||||||
@ -179,7 +180,7 @@ class ProxyClient {
|
|||||||
/// [headers]、[queryParams]、[body] 使用**业务逻辑字段名**。
|
/// [headers]、[queryParams]、[body] 使用**业务逻辑字段名**。
|
||||||
/// [entityFactory] 用于将映射后的 data 转换为实体对象。
|
/// [entityFactory] 用于将映射后的 data 转换为实体对象。
|
||||||
///
|
///
|
||||||
/// 参见 [request] 的 [includeUserTokenInHeader] 说明。
|
/// 参见 [request] 的 [includeUserTokenInHeader]、[includePackageInHeader] 说明。
|
||||||
Future<EntityResponse<T>> requestEntity<T extends Entity>({
|
Future<EntityResponse<T>> requestEntity<T extends Entity>({
|
||||||
required String path,
|
required String path,
|
||||||
required String method,
|
required String method,
|
||||||
@ -188,6 +189,7 @@ class ProxyClient {
|
|||||||
Map<String, String>? queryParams,
|
Map<String, String>? queryParams,
|
||||||
Map<String, dynamic>? body,
|
Map<String, dynamic>? body,
|
||||||
bool includeUserTokenInHeader = true,
|
bool includeUserTokenInHeader = true,
|
||||||
|
bool includePackageInHeader = true,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await request(
|
final response = await request(
|
||||||
path: path,
|
path: path,
|
||||||
@ -196,6 +198,7 @@ class ProxyClient {
|
|||||||
queryParams: queryParams,
|
queryParams: queryParams,
|
||||||
body: body,
|
body: body,
|
||||||
includeUserTokenInHeader: includeUserTokenInHeader,
|
includeUserTokenInHeader: includeUserTokenInHeader,
|
||||||
|
includePackageInHeader: includePackageInHeader,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.isSuccess) {
|
if (response.isSuccess) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ class FeedbackUploadPresignedUrlResponse extends Entity {
|
|||||||
this.uploadUrl,
|
this.uploadUrl,
|
||||||
this.filePath,
|
this.filePath,
|
||||||
this.putHeaders,
|
this.putHeaders,
|
||||||
|
this.expectedSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? uploadUrl;
|
final String? uploadUrl;
|
||||||
@ -16,6 +17,9 @@ class FeedbackUploadPresignedUrlResponse extends Entity {
|
|||||||
/// 与 [UploadPresignedUrlResponse.putHeaders] 一致:PUT 到对象存储时的额外头。
|
/// 与 [UploadPresignedUrlResponse.putHeaders] 一致:PUT 到对象存储时的额外头。
|
||||||
final Map<String, String>? putHeaders;
|
final Map<String, String>? putHeaders;
|
||||||
|
|
||||||
|
/// 服务端返回的上传大小上限(字节),逻辑字段名 `expectedSize`;未下发时为 `null`。
|
||||||
|
final int? expectedSize;
|
||||||
|
|
||||||
static String _headerValueToString(dynamic v) {
|
static String _headerValueToString(dynamic v) {
|
||||||
if (v == null) return '';
|
if (v == null) return '';
|
||||||
if (v is String) return v;
|
if (v is String) return v;
|
||||||
@ -57,6 +61,14 @@ class FeedbackUploadPresignedUrlResponse extends Entity {
|
|||||||
return out.isEmpty ? null : out;
|
return out.isEmpty ? null : out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int? _readIntField(Map<String, dynamic> json, String key) {
|
||||||
|
final v = json[key];
|
||||||
|
if (v == null) return null;
|
||||||
|
if (v is int) return v;
|
||||||
|
if (v is num) return v.toInt();
|
||||||
|
return int.tryParse(v.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
factory FeedbackUploadPresignedUrlResponse.fromJson(
|
factory FeedbackUploadPresignedUrlResponse.fromJson(
|
||||||
Map<String, dynamic> json) {
|
Map<String, dynamic> json) {
|
||||||
@ -64,6 +76,9 @@ class FeedbackUploadPresignedUrlResponse extends Entity {
|
|||||||
uploadUrl: json['uploadUrl'] as String?,
|
uploadUrl: json['uploadUrl'] as String?,
|
||||||
filePath: json['filePath'] as String?,
|
filePath: json['filePath'] as String?,
|
||||||
putHeaders: _parsePutHeaders(json),
|
putHeaders: _parsePutHeaders(json),
|
||||||
|
expectedSize: _readIntField(json, 'expectedSize') ??
|
||||||
|
_readIntField(json, 'maxFileSize') ??
|
||||||
|
_readIntField(json, 'maxSize'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +87,7 @@ class FeedbackUploadPresignedUrlResponse extends Entity {
|
|||||||
'uploadUrl': uploadUrl,
|
'uploadUrl': uploadUrl,
|
||||||
'filePath': filePath,
|
'filePath': filePath,
|
||||||
if (putHeaders != null) 'putHeaders': putHeaders,
|
if (putHeaders != null) 'putHeaders': putHeaders,
|
||||||
|
if (expectedSize != null) 'expectedSize': expectedSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -405,6 +405,7 @@ class UploadPresignedUrlResponse extends Entity {
|
|||||||
this.uploadUrl,
|
this.uploadUrl,
|
||||||
this.filePath,
|
this.filePath,
|
||||||
this.putHeaders,
|
this.putHeaders,
|
||||||
|
this.expectedSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? uploadUrl;
|
final String? uploadUrl;
|
||||||
@ -413,6 +414,9 @@ class UploadPresignedUrlResponse extends Entity {
|
|||||||
/// 上传到对象存储时额外请求头(如服务端返回的签名头;解密后为 business 字段名)。
|
/// 上传到对象存储时额外请求头(如服务端返回的签名头;解密后为 business 字段名)。
|
||||||
final Map<String, String>? putHeaders;
|
final Map<String, String>? putHeaders;
|
||||||
|
|
||||||
|
/// 服务端返回的单次上传大小上限(字节),逻辑字段名 `expectedSize`;未下发时为 `null`。
|
||||||
|
final int? expectedSize;
|
||||||
|
|
||||||
/// 将任意 JSON 头值压成 [http] 要求的 [String](避免 `TypeError`)。
|
/// 将任意 JSON 头值压成 [http] 要求的 [String](避免 `TypeError`)。
|
||||||
static String _headerValueToString(dynamic v) {
|
static String _headerValueToString(dynamic v) {
|
||||||
if (v == null) return '';
|
if (v == null) return '';
|
||||||
@ -462,6 +466,14 @@ class UploadPresignedUrlResponse extends Entity {
|
|||||||
return v.toString();
|
return v.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int? _readIntField(Map<String, dynamic> json, String key) {
|
||||||
|
final v = json[key];
|
||||||
|
if (v == null) return null;
|
||||||
|
if (v is int) return v;
|
||||||
|
if (v is num) return v.toInt();
|
||||||
|
return int.tryParse(v.toString().trim());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
factory UploadPresignedUrlResponse.fromJson(Map<String, dynamic> json) {
|
factory UploadPresignedUrlResponse.fromJson(Map<String, dynamic> json) {
|
||||||
// FunyMee 等换皮:`uploadUrl1`/`filePath1`(文档 wire:harden / generate)。
|
// FunyMee 等换皮:`uploadUrl1`/`filePath1`(文档 wire:harden / generate)。
|
||||||
@ -476,6 +488,9 @@ class UploadPresignedUrlResponse extends Entity {
|
|||||||
uploadUrl: upload,
|
uploadUrl: upload,
|
||||||
filePath: path,
|
filePath: path,
|
||||||
putHeaders: _parsePutHeaders(json),
|
putHeaders: _parsePutHeaders(json),
|
||||||
|
expectedSize: _readIntField(json, 'expectedSize') ??
|
||||||
|
_readIntField(json, 'maxFileSize') ??
|
||||||
|
_readIntField(json, 'maxSize'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,6 +499,7 @@ class UploadPresignedUrlResponse extends Entity {
|
|||||||
'uploadUrl': uploadUrl,
|
'uploadUrl': uploadUrl,
|
||||||
'filePath': filePath,
|
'filePath': filePath,
|
||||||
if (putHeaders != null) 'putHeaders': putHeaders,
|
if (putHeaders != null) 'putHeaders': putHeaders,
|
||||||
|
if (expectedSize != null) 'expectedSize': expectedSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,14 @@ import '../config/video_home_runtime.dart';
|
|||||||
import '../entities/user_entities.dart';
|
import '../entities/user_entities.dart';
|
||||||
import 'adjust_service.dart';
|
import 'adjust_service.dart';
|
||||||
import 'analytics_attribution_callbacks.dart';
|
import 'analytics_attribution_callbacks.dart';
|
||||||
|
import 'facebook_service.dart';
|
||||||
|
import 'login_identity_cache.dart';
|
||||||
import 'user_api.dart';
|
import 'user_api.dart';
|
||||||
|
|
||||||
|
/// [FrameworkAuthService.start] 中 `fast_login` 在拿不到 Play Install Referrer 时使用的 `referer` 兜底(自然安装)。
|
||||||
|
const String _fastLoginPlayReferrerFallback =
|
||||||
|
'utm_source=google-play&utm_medium=organic';
|
||||||
|
|
||||||
/// 认证服务回调
|
/// 认证服务回调
|
||||||
/// 用于在认证流程各阶段通知调用方
|
/// 用于在认证流程各阶段通知调用方
|
||||||
abstract class AuthServiceCallbacks {
|
abstract class AuthServiceCallbacks {
|
||||||
@ -37,6 +43,14 @@ abstract class AuthServiceCallbacks {
|
|||||||
/// 1. 快速登录
|
/// 1. 快速登录
|
||||||
/// 2. 归因上报
|
/// 2. 归因上报
|
||||||
/// 3. 获取通用信息
|
/// 3. 获取通用信息
|
||||||
|
///
|
||||||
|
/// **换皮默认行为**(无需宿主接入):
|
||||||
|
/// - `fast_login`:`type` **强制**为 `gg`(Google 归因);`referer` 优先 Google Play Install Referrer,取不到则使用 [_fastLoginPlayReferrerFallback],不再切换 Adjust/Facebook 的 type。
|
||||||
|
/// - 登录成功且 `userId` 非空:将 `userId` 与本次 `deviceId` 写入 [LoginIdentityCache]。
|
||||||
|
/// - 登录失败(含无响应、异常、`code != 0`、或成功体无 `userId`):上报 Facebook 自定义事件 [facebookLoginFailedEventName],
|
||||||
|
/// 参数:`server_error_code`、`user_id`(响应中有则带,否则空串)、`device_id`(本次启动已解析的设备 ID,若尚未取到则为空串);
|
||||||
|
/// 未能取得用户 ID 时另带 `register_faild` = `register faild`。
|
||||||
|
/// - [UserApi.getCommonInfo] 失败、请求异常,或成功但响应体无 `extConfig` 字符串:上报 Facebook [facebookExtConfigFailedEventName](`user_id`、`device_id`、`server_error_code`、可选 `server_error_msg`)。
|
||||||
abstract class FrameworkAuthService {
|
abstract class FrameworkAuthService {
|
||||||
static AuthServiceCallbacks? _callbacks;
|
static AuthServiceCallbacks? _callbacks;
|
||||||
static Future<void>? _loginFuture;
|
static Future<void>? _loginFuture;
|
||||||
@ -91,10 +105,14 @@ abstract class FrameworkAuthService {
|
|||||||
debugPrint('[AuthService] start: 开始登录流程');
|
debugPrint('[AuthService] start: 开始登录流程');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 供 [catch] 上报 Facebook 使用:若在 [getDeviceId] 成功前抛错则为空串。
|
||||||
|
var deviceIdForFacebookFailure = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Future<void>.delayed(Duration(seconds: delaySeconds));
|
await Future<void>.delayed(Duration(seconds: delaySeconds));
|
||||||
|
|
||||||
final deviceId = await _callbacks!.getDeviceId();
|
final deviceId = await _callbacks!.getDeviceId();
|
||||||
|
deviceIdForFacebookFailure = deviceId;
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] start: deviceId=$deviceId');
|
debugPrint('[AuthService] start: deviceId=$deviceId');
|
||||||
}
|
}
|
||||||
@ -104,26 +122,17 @@ abstract class FrameworkAuthService {
|
|||||||
debugPrint('[AuthService] start: sign=$sign');
|
debugPrint('[AuthService] start: sign=$sign');
|
||||||
}
|
}
|
||||||
|
|
||||||
final referer = await AttributionService.getReferrer();
|
// fast_login:强制使用 Google 归因类型 `gg`;referer 优先 Play Install Referrer,无则自然安装 UTM 兜底。
|
||||||
if (kDebugMode && referer != null) {
|
final playReferrer = AdjustService.cachedPlayReferrer;
|
||||||
debugPrint('[AuthService] start: referer=$referer');
|
final fastLoginReferer = (playReferrer != null && playReferrer.isNotEmpty)
|
||||||
}
|
? playReferrer
|
||||||
|
: _fastLoginPlayReferrerFallback;
|
||||||
// 确定归因类型
|
const fastLoginType = 'gg';
|
||||||
String? referrerType;
|
|
||||||
final adjustReferrer = await AttributionService.getAdjustReferrer();
|
|
||||||
final fbReferrer = await AttributionService.getFacebookReferrer();
|
|
||||||
|
|
||||||
if (adjustReferrer != null && adjustReferrer.isNotEmpty) {
|
|
||||||
referrerType = defaultTargetPlatform == TargetPlatform.iOS
|
|
||||||
? 'ios_adjust'
|
|
||||||
: 'android_adjust';
|
|
||||||
} else if (fbReferrer != null && fbReferrer.isNotEmpty) {
|
|
||||||
referrerType = 'fb';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] start: referrerType=$referrerType');
|
debugPrint(
|
||||||
|
'[AuthService] start: fast_login type=$fastLoginType refererLen=${fastLoginReferer.length}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试快速登录
|
// 尝试快速登录
|
||||||
@ -143,9 +152,9 @@ abstract class FrameworkAuthService {
|
|||||||
res = await UserApi.fastLogin(
|
res = await UserApi.fastLogin(
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
sign: sign,
|
sign: sign,
|
||||||
referer: referer ?? '',
|
referer: fastLoginReferer,
|
||||||
app: appType,
|
app: appType,
|
||||||
type: referrerType,
|
type: fastLoginType,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -160,6 +169,12 @@ abstract class FrameworkAuthService {
|
|||||||
lastLoggedInUserId = null;
|
lastLoggedInUserId = null;
|
||||||
VideoHomeRuntime.reset();
|
VideoHomeRuntime.reset();
|
||||||
ExtConfigRuntime.applyCommonInfoFailure();
|
ExtConfigRuntime.applyCommonInfoFailure();
|
||||||
|
_logFacebookLoginFailed(
|
||||||
|
serverCode: -1,
|
||||||
|
missingUserId: true,
|
||||||
|
userIdFromServer: null,
|
||||||
|
deviceId: deviceId,
|
||||||
|
);
|
||||||
completer.complete();
|
completer.complete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -171,8 +186,7 @@ abstract class FrameworkAuthService {
|
|||||||
if (res.isSuccess && res.data != null) {
|
if (res.isSuccess && res.data != null) {
|
||||||
final loginData = res.data!;
|
final loginData = res.data!;
|
||||||
final uid = loginData.userId?.trim();
|
final uid = loginData.userId?.trim();
|
||||||
lastLoggedInUserId =
|
lastLoggedInUserId = uid != null && uid.isNotEmpty ? uid : null;
|
||||||
uid != null && uid.isNotEmpty ? uid : null;
|
|
||||||
|
|
||||||
// 设置 Token
|
// 设置 Token
|
||||||
if (loginData.userToken != null && loginData.userToken!.isNotEmpty) {
|
if (loginData.userToken != null && loginData.userToken!.isNotEmpty) {
|
||||||
@ -182,6 +196,22 @@ abstract class FrameworkAuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uid != null && uid.isNotEmpty) {
|
||||||
|
unawaited(
|
||||||
|
LoginIdentityCache.writeUserAndDeviceId(
|
||||||
|
userId: uid,
|
||||||
|
deviceId: deviceId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_logFacebookLoginFailed(
|
||||||
|
serverCode: res.code,
|
||||||
|
missingUserId: true,
|
||||||
|
userIdFromServer: loginData.userId,
|
||||||
|
deviceId: deviceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 回调登录成功
|
// 回调登录成功
|
||||||
_callbacks!.onLoginSuccess(loginData);
|
_callbacks!.onLoginSuccess(loginData);
|
||||||
|
|
||||||
@ -194,6 +224,7 @@ abstract class FrameworkAuthService {
|
|||||||
lastLoggedInUserId = null;
|
lastLoggedInUserId = null;
|
||||||
VideoHomeRuntime.reset();
|
VideoHomeRuntime.reset();
|
||||||
ExtConfigRuntime.applyCommonInfoFailure();
|
ExtConfigRuntime.applyCommonInfoFailure();
|
||||||
|
_logFacebookLoginFailedFromResponse(res, deviceId: deviceId);
|
||||||
_callbacks!.onLoginFailed(res.msg);
|
_callbacks!.onLoginFailed(res.msg);
|
||||||
}
|
}
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
@ -203,6 +234,12 @@ abstract class FrameworkAuthService {
|
|||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] start: 异常 $e\n$st');
|
debugPrint('[AuthService] start: 异常 $e\n$st');
|
||||||
}
|
}
|
||||||
|
_logFacebookLoginFailed(
|
||||||
|
serverCode: -1,
|
||||||
|
missingUserId: true,
|
||||||
|
userIdFromServer: null,
|
||||||
|
deviceId: deviceIdForFacebookFailure,
|
||||||
|
);
|
||||||
_callbacks!.onLoginFailed(e.toString());
|
_callbacks!.onLoginFailed(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
@ -234,8 +271,14 @@ abstract class FrameworkAuthService {
|
|||||||
: config.backendAppTypeAndroid;
|
: config.backendAppTypeAndroid;
|
||||||
|
|
||||||
// 上报 Adjust 归因
|
// 上报 Adjust 归因
|
||||||
|
var adjustReferrerTried = false;
|
||||||
|
var adjustReferrerOk = false;
|
||||||
|
var adjustReferrerCode = 0;
|
||||||
|
var adjustReferrerMsg = '';
|
||||||
|
|
||||||
final adjustReferer = await AttributionService.getAdjustReferrer();
|
final adjustReferer = await AttributionService.getAdjustReferrer();
|
||||||
if (adjustReferer != null && adjustReferer.isNotEmpty) {
|
if (adjustReferer != null && adjustReferer.isNotEmpty) {
|
||||||
|
adjustReferrerTried = true;
|
||||||
final adjustType = defaultTargetPlatform == TargetPlatform.iOS
|
final adjustType = defaultTargetPlatform == TargetPlatform.iOS
|
||||||
? 'ios_adjust'
|
? 'ios_adjust'
|
||||||
: 'android_adjust';
|
: 'android_adjust';
|
||||||
@ -247,11 +290,19 @@ abstract class FrameworkAuthService {
|
|||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
type: adjustType,
|
type: adjustType,
|
||||||
);
|
);
|
||||||
|
if (rAdjust.isSuccess) {
|
||||||
|
adjustReferrerOk = true;
|
||||||
|
} else {
|
||||||
|
adjustReferrerCode = rAdjust.code;
|
||||||
|
adjustReferrerMsg = rAdjust.msg;
|
||||||
|
}
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'[AuthService] referrer($adjustType): ${rAdjust.isSuccess ? "成功" : "失败"}');
|
'[AuthService] referrer($adjustType): ${rAdjust.isSuccess ? "成功" : "失败"}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
adjustReferrerCode = -110;
|
||||||
|
adjustReferrerMsg = e.toString();
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] referrer($adjustType): 异常 $e');
|
debugPrint('[AuthService] referrer($adjustType): 异常 $e');
|
||||||
}
|
}
|
||||||
@ -259,8 +310,14 @@ abstract class FrameworkAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 上报 Google Play 归因(从 AdjustService 获取缓存的 referrer)
|
// 上报 Google Play 归因(从 AdjustService 获取缓存的 referrer)
|
||||||
|
var ggReferrerTried = false;
|
||||||
|
var ggReferrerOk = false;
|
||||||
|
var ggReferrerCode = 0;
|
||||||
|
var ggReferrerMsg = '';
|
||||||
|
|
||||||
final playReferrer = AdjustService.cachedPlayReferrer;
|
final playReferrer = AdjustService.cachedPlayReferrer;
|
||||||
if (playReferrer != null && playReferrer.isNotEmpty) {
|
if (playReferrer != null && playReferrer.isNotEmpty) {
|
||||||
|
ggReferrerTried = true;
|
||||||
try {
|
try {
|
||||||
final rGg = await UserApi.referrer(
|
final rGg = await UserApi.referrer(
|
||||||
app: backendApp,
|
app: backendApp,
|
||||||
@ -269,17 +326,39 @@ abstract class FrameworkAuthService {
|
|||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
type: 'gg',
|
type: 'gg',
|
||||||
);
|
);
|
||||||
|
if (rGg.isSuccess) {
|
||||||
|
ggReferrerOk = true;
|
||||||
|
} else {
|
||||||
|
ggReferrerCode = rGg.code;
|
||||||
|
ggReferrerMsg = rGg.msg;
|
||||||
|
}
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'[AuthService] referrer(gg): ${rGg.isSuccess ? "成功" : "失败"}');
|
'[AuthService] referrer(gg): ${rGg.isSuccess ? "成功" : "失败"}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
ggReferrerCode = -110;
|
||||||
|
ggReferrerMsg = e.toString();
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] referrer(gg): 异常 $e');
|
debugPrint('[AuthService] referrer(gg): 异常 $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (adjustReferrerTried &&
|
||||||
|
ggReferrerTried &&
|
||||||
|
!adjustReferrerOk &&
|
||||||
|
!ggReferrerOk) {
|
||||||
|
_logFacebookReferrerBothFailed(
|
||||||
|
userId: uid,
|
||||||
|
deviceId: deviceId,
|
||||||
|
adjustCode: adjustReferrerCode,
|
||||||
|
adjustMsg: adjustReferrerMsg,
|
||||||
|
ggCode: ggReferrerCode,
|
||||||
|
ggMsg: ggReferrerMsg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 获取通用信息
|
// 获取通用信息
|
||||||
try {
|
try {
|
||||||
final commonRes = await UserApi.getCommonInfo(
|
final commonRes = await UserApi.getCommonInfo(
|
||||||
@ -289,8 +368,11 @@ abstract class FrameworkAuthService {
|
|||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
);
|
);
|
||||||
if (commonRes.isSuccess && commonRes.data != null) {
|
if (commonRes.isSuccess && commonRes.data != null) {
|
||||||
ExtConfigRuntime.applyCommonInfoSuccess(commonRes.data!);
|
final info = commonRes.data!;
|
||||||
_callbacks?.onCommonInfoLoaded(commonRes.data!);
|
final extRaw = info.extConfig?.trim();
|
||||||
|
final extConfigMissing = extRaw == null || extRaw.isEmpty;
|
||||||
|
ExtConfigRuntime.applyCommonInfoSuccess(info);
|
||||||
|
_callbacks?.onCommonInfoLoaded(info);
|
||||||
unawaited(
|
unawaited(
|
||||||
VideoHomeRuntime.hydrateAfterCommonInfo(
|
VideoHomeRuntime.hydrateAfterCommonInfo(
|
||||||
userId: uid,
|
userId: uid,
|
||||||
@ -300,9 +382,23 @@ abstract class FrameworkAuthService {
|
|||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] common_info: 获取成功');
|
debugPrint('[AuthService] common_info: 获取成功');
|
||||||
}
|
}
|
||||||
|
if (extConfigMissing) {
|
||||||
|
_logFacebookExtConfigFailed(
|
||||||
|
userId: uid,
|
||||||
|
deviceId: deviceId,
|
||||||
|
serverCode: 0,
|
||||||
|
serverMsg: 'ext_config_missing',
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
VideoHomeRuntime.reset();
|
VideoHomeRuntime.reset();
|
||||||
ExtConfigRuntime.applyCommonInfoFailure();
|
ExtConfigRuntime.applyCommonInfoFailure();
|
||||||
|
_logFacebookExtConfigFailed(
|
||||||
|
userId: uid,
|
||||||
|
deviceId: deviceId,
|
||||||
|
serverCode: commonRes.code,
|
||||||
|
serverMsg: commonRes.msg,
|
||||||
|
);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'[AuthService] common_info: 失败 code=${commonRes.code} msg=${commonRes.msg}');
|
'[AuthService] common_info: 失败 code=${commonRes.code} msg=${commonRes.msg}');
|
||||||
@ -311,6 +407,12 @@ abstract class FrameworkAuthService {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
VideoHomeRuntime.reset();
|
VideoHomeRuntime.reset();
|
||||||
ExtConfigRuntime.applyCommonInfoFailure();
|
ExtConfigRuntime.applyCommonInfoFailure();
|
||||||
|
_logFacebookExtConfigFailed(
|
||||||
|
userId: uid,
|
||||||
|
deviceId: deviceId,
|
||||||
|
serverCode: -1,
|
||||||
|
serverMsg: e.toString(),
|
||||||
|
);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint('[AuthService] common_info: 异常 $e');
|
debugPrint('[AuthService] common_info: 异常 $e');
|
||||||
}
|
}
|
||||||
@ -321,4 +423,108 @@ abstract class FrameworkAuthService {
|
|||||||
static Map<String, dynamic>? parseExtConfig(String? extConfigStr) {
|
static Map<String, dynamic>? parseExtConfig(String? extConfigStr) {
|
||||||
return ExtConfigData.parseRawMap(extConfigStr);
|
return ExtConfigData.parseRawMap(extConfigStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Facebook 自定义事件名(与产品约定一致:`LoginFaild`)。
|
||||||
|
static const String facebookLoginFailedEventName = 'LoginFaild';
|
||||||
|
|
||||||
|
/// 两次归因上报(Adjust + `gg`)均请求且均失败时上报(与产品约定:`Referer_faild`)。
|
||||||
|
static const String facebookReferrerBothFailedEventName = 'Referer_faild';
|
||||||
|
|
||||||
|
/// `common_info` 失败或响应中无 `extConfig` 时上报(与产品约定:`ExtConfigFaild`)。
|
||||||
|
static const String facebookExtConfigFailedEventName = 'ExtConfigFaild';
|
||||||
|
|
||||||
|
static const int _facebookParamMaxLen = 500;
|
||||||
|
|
||||||
|
static String _truncateForFacebookParam(String s) {
|
||||||
|
final t = s.trim();
|
||||||
|
if (t.length <= _facebookParamMaxLen) return t;
|
||||||
|
return '${t.substring(0, _facebookParamMaxLen)}…';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjust 与 Google Play 两条 [UserApi.referrer] 都发起且都未成功时调用(不阻塞、失败静默)。
|
||||||
|
static void _logFacebookReferrerBothFailed({
|
||||||
|
required String userId,
|
||||||
|
required String deviceId,
|
||||||
|
required int adjustCode,
|
||||||
|
required String adjustMsg,
|
||||||
|
required int ggCode,
|
||||||
|
required String ggMsg,
|
||||||
|
}) {
|
||||||
|
final parts = <String>[
|
||||||
|
if (adjustMsg.trim().isNotEmpty)
|
||||||
|
'adjust: ${_truncateForFacebookParam(adjustMsg)}',
|
||||||
|
if (ggMsg.trim().isNotEmpty) 'gg: ${_truncateForFacebookParam(ggMsg)}',
|
||||||
|
];
|
||||||
|
final combinedMsg = parts.join(' | ');
|
||||||
|
final params = <String, dynamic>{
|
||||||
|
'user_id': userId.trim(),
|
||||||
|
'device_id': deviceId.trim(),
|
||||||
|
'server_error_code': '$adjustCode/$ggCode',
|
||||||
|
if (combinedMsg.isNotEmpty) 'server_error_msg': combinedMsg,
|
||||||
|
};
|
||||||
|
FacebookService.logEvent(
|
||||||
|
facebookReferrerBothFailedEventName,
|
||||||
|
parameters: params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [UserApi.getCommonInfo] 失败、异常,或成功但缺少 `extConfig` 时调用(不阻塞、失败静默)。
|
||||||
|
static void _logFacebookExtConfigFailed({
|
||||||
|
required String userId,
|
||||||
|
required String deviceId,
|
||||||
|
required int serverCode,
|
||||||
|
String serverMsg = '',
|
||||||
|
}) {
|
||||||
|
final msg = serverMsg.trim();
|
||||||
|
final params = <String, dynamic>{
|
||||||
|
'user_id': userId.trim(),
|
||||||
|
'device_id': deviceId.trim(),
|
||||||
|
'server_error_code': '$serverCode',
|
||||||
|
if (msg.isNotEmpty) 'server_error_msg': _truncateForFacebookParam(msg),
|
||||||
|
};
|
||||||
|
FacebookService.logEvent(
|
||||||
|
facebookExtConfigFailedEventName,
|
||||||
|
parameters: params,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _logFacebookLoginFailedFromResponse(
|
||||||
|
EntityResponse<FastLoginResponse> res, {
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
final uid = res.data?.userId?.trim();
|
||||||
|
final missingUserId = uid == null || uid.isEmpty;
|
||||||
|
_logFacebookLoginFailed(
|
||||||
|
serverCode: res.code,
|
||||||
|
missingUserId: missingUserId,
|
||||||
|
userIdFromServer: res.data?.userId,
|
||||||
|
deviceId: deviceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 登录失败或未拿到用户 ID 时上报 Meta / Facebook App Events(不阻塞、失败静默)。
|
||||||
|
///
|
||||||
|
/// - [serverCode]:接口 [EntityResponse.code];无响应或异常时为 `-1`。
|
||||||
|
/// - [userIdFromServer]:解密后响应里的 `userId`;无则上报空串。
|
||||||
|
/// - [deviceId]:本次流程使用的设备 ID;未取到时为空串。
|
||||||
|
/// - [missingUserId] 为 `true` 时附带参数 `register_faild` = `register faild`。
|
||||||
|
static void _logFacebookLoginFailed({
|
||||||
|
required int serverCode,
|
||||||
|
required bool missingUserId,
|
||||||
|
String? userIdFromServer,
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
final uid = userIdFromServer?.trim() ?? '';
|
||||||
|
final did = deviceId.trim();
|
||||||
|
final params = <String, dynamic>{
|
||||||
|
'server_error_code': '$serverCode',
|
||||||
|
'user_id': uid,
|
||||||
|
'device_id': did,
|
||||||
|
if (missingUserId) 'register_faild': 'register faild',
|
||||||
|
};
|
||||||
|
FacebookService.logEvent(
|
||||||
|
facebookLoginFailedEventName,
|
||||||
|
parameters: params,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import '../entities/image_entities.dart';
|
|||||||
import '../log/app_logger.dart';
|
import '../log/app_logger.dart';
|
||||||
import 'image_api.dart';
|
import 'image_api.dart';
|
||||||
import 'image_compress.dart';
|
import 'image_compress.dart';
|
||||||
|
import 'image_upload_expected_size_cache.dart';
|
||||||
import 'task_upload_cover_store.dart';
|
import 'task_upload_cover_store.dart';
|
||||||
|
|
||||||
final _presignedPutLog = AppLogger('PresignedUpload');
|
final _presignedPutLog = AppLogger('PresignedUpload');
|
||||||
@ -99,6 +100,9 @@ abstract final class ImagePresignedUploadCreateTaskFlow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final presigned = presignedRes.data!;
|
final presigned = presignedRes.data!;
|
||||||
|
await ImageUploadExpectedSizeCache.writeImageExpectedSize(
|
||||||
|
presigned.expectedSize,
|
||||||
|
);
|
||||||
final uploadUrl = presigned.uploadUrl;
|
final uploadUrl = presigned.uploadUrl;
|
||||||
final filePath = presigned.filePath;
|
final filePath = presigned.filePath;
|
||||||
if (uploadUrl == null ||
|
if (uploadUrl == null ||
|
||||||
|
|||||||
44
lib/src/services/image_upload_expected_size_cache.dart
Normal file
44
lib/src/services/image_upload_expected_size_cache.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// 缓存 [ImageApi.getUploadPresignedUrl] / [FeedbackApi.getUploadPresignedUrl] 响应中的
|
||||||
|
/// [UploadPresignedUrlResponse.expectedSize] / [FeedbackUploadPresignedUrlResponse.expectedSize],
|
||||||
|
/// 供选图前展示与校验;未缓存时由调用方使用 [fallbackMaxBytes]。
|
||||||
|
abstract final class ImageUploadExpectedSizeCache {
|
||||||
|
ImageUploadExpectedSizeCache._();
|
||||||
|
|
||||||
|
/// 未命中服务端下发的 `expectedSize` 时,客户端使用的默认上限(字节)。
|
||||||
|
static const int fallbackMaxBytes = 20 * 1024 * 1024;
|
||||||
|
|
||||||
|
static const String _kImage =
|
||||||
|
'client_proxy_image_presigned_expected_size_bytes_v1';
|
||||||
|
static const String _kFeedback =
|
||||||
|
'client_proxy_feedback_presigned_expected_size_bytes_v1';
|
||||||
|
|
||||||
|
static Future<int> readImageMaxBytesForUi() async {
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
final v = p.getInt(_kImage);
|
||||||
|
if (v != null && v > 0) return v;
|
||||||
|
return fallbackMaxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<int> readFeedbackMaxBytesForUi() async {
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
final v = p.getInt(_kFeedback);
|
||||||
|
if (v != null && v > 0) return v;
|
||||||
|
return fallbackMaxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在生图预签名成功后写入;仅当 [bytes] 为正整数时持久化。
|
||||||
|
static Future<void> writeImageExpectedSize(int? bytes) async {
|
||||||
|
if (bytes == null || bytes <= 0) return;
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
await p.setInt(_kImage, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在反馈预签名成功后写入;仅当 [bytes] 为正整数时持久化。
|
||||||
|
static Future<void> writeFeedbackExpectedSize(int? bytes) async {
|
||||||
|
if (bytes == null || bytes <= 0) return;
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
await p.setInt(_kFeedback, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
lib/src/services/login_identity_cache.dart
Normal file
43
lib/src/services/login_identity_cache.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// 登录成功后缓存 [FrameworkAuthService] 使用的用户 ID 与设备 ID,供换皮应用读取(如离线展示、诊断)。
|
||||||
|
///
|
||||||
|
/// 与业务 token 无关;仅作本地副本。
|
||||||
|
abstract final class LoginIdentityCache {
|
||||||
|
LoginIdentityCache._();
|
||||||
|
|
||||||
|
static const String _kUserId = 'client_proxy_framework_cached_login_user_id_v1';
|
||||||
|
static const String _kDeviceId =
|
||||||
|
'client_proxy_framework_cached_login_device_id_v1';
|
||||||
|
|
||||||
|
/// 最近一次成功登录写入的 `userId`;未写入过则为 `null`。
|
||||||
|
static Future<String?> readCachedUserId() async {
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
final v = p.getString(_kUserId)?.trim();
|
||||||
|
if (v == null || v.isEmpty) return null;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 最近一次成功登录写入的 `deviceId`;未写入过则为 `null`。
|
||||||
|
static Future<String?> readCachedDeviceId() async {
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
final v = p.getString(_kDeviceId)?.trim();
|
||||||
|
if (v == null || v.isEmpty) return null;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在快速登录成功且 [userId] 非空时由框架调用。
|
||||||
|
static Future<void> writeUserAndDeviceId({
|
||||||
|
required String userId,
|
||||||
|
required String deviceId,
|
||||||
|
}) async {
|
||||||
|
final uid = userId.trim();
|
||||||
|
if (uid.isEmpty) return;
|
||||||
|
final did = deviceId.trim();
|
||||||
|
final p = await SharedPreferences.getInstance();
|
||||||
|
await p.setString(_kUserId, uid);
|
||||||
|
if (did.isNotEmpty) {
|
||||||
|
await p.setString(_kDeviceId, did);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ import '../entities/user_entities.dart';
|
|||||||
/// 用户相关 API(**业务逻辑字段名**,经 [FieldMapping] 映射为线网字段名)
|
/// 用户相关 API(**业务逻辑字段名**,经 [FieldMapping] 映射为线网字段名)
|
||||||
///
|
///
|
||||||
/// **请求头**:除 [UserApi.fastLogin] 外,需登录接口均由 [ProxyClient] 自动附带
|
/// **请求头**:除 [UserApi.fastLogin] 外,需登录接口均由 [ProxyClient] 自动附带
|
||||||
/// `pkg`(包名)与 `User_token`(已设置 token 时)。fast_login 仅带 `pkg`,不带 token。
|
/// `pkg`(包名)与 `User_token`(已设置 token 时)。**fast_login** 保留 `pkg`,但不注入 `User_token`。
|
||||||
///
|
///
|
||||||
/// **请求体**:与《客户端指南》一致,使用**业务逻辑字段名**(如 `referer`、`deviceId`)。
|
/// **请求体**:与《客户端指南》一致,使用**业务逻辑字段名**(如 `referer`、`deviceId`)。
|
||||||
abstract final class UserApi {
|
abstract final class UserApi {
|
||||||
@ -15,8 +15,8 @@ abstract final class UserApi {
|
|||||||
|
|
||||||
/// 设备快速登录
|
/// 设备快速登录
|
||||||
///
|
///
|
||||||
/// **请求头**:仅 `pkg`(无 `User_token`)。
|
/// **请求头**:保留 `pkg`,不注入 `User_token`(见 [ProxyClient.request] 的 `includeUserTokenInHeader`)。
|
||||||
/// **Query**:`app`、`type`(默认 `fb`)、`pkg`、`ch`(可选)。
|
/// **Query**:`app`、`type`(未传时默认 `gg`,Google Play Install Referrer 归因)、`pkg`、`ch`(可选)。
|
||||||
/// **Body**:`referer`、`sign`、`deviceId`。
|
/// **Body**:`referer`、`sign`、`deviceId`。
|
||||||
static Future<EntityResponse<FastLoginResponse>> fastLogin({
|
static Future<EntityResponse<FastLoginResponse>> fastLogin({
|
||||||
required String deviceId,
|
required String deviceId,
|
||||||
@ -34,7 +34,7 @@ abstract final class UserApi {
|
|||||||
queryParams: {
|
queryParams: {
|
||||||
if (ch != null && ch.isNotEmpty) 'ch': ch,
|
if (ch != null && ch.isNotEmpty) 'ch': ch,
|
||||||
'pkg': config.packageName,
|
'pkg': config.packageName,
|
||||||
'type': type ?? 'fb',
|
'type': type ?? 'gg',
|
||||||
'app': app,
|
'app': app,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user