hi-client/lib/app/services/api_service/kr_auth_api.dart
speakeloudest ccf9a76de9 feat: 1.调整项目初始化,不用等待site/config接口,仅在hi_user_info中调用;将site_config_service拆成loadDomain和获取配置,在splash中只做loadDomain
2. 优化接口拦截的解密日志
3. 调整获取订阅到kr_home,与源版一致
4. 修复闪连抖动问题,增加套餐有效期判断
2026-01-13 18:07:11 -08:00

507 lines
18 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io' as io;
import 'dart:math';
import 'dart:convert';
import 'package:fpdart/fpdart.dart';
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/mixins/kr_app_bar_opacity_mixin.dart';
import 'package:kaer_with_panels/app/services/api_service/api.dart';
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/model/response/kr_is_register.dart';
import 'package:kaer_with_panels/app/model/response/kr_check_subscription.dart';
import 'package:kaer_with_panels/app/model/response/kr_login_data.dart';
import 'package:kaer_with_panels/app/network/base_response.dart';
import 'package:kaer_with_panels/app/network/http_error.dart';
import 'package:kaer_with_panels/app/network/http_util.dart';
import '../../utils/kr_common_util.dart';
import '../../utils/kr_log_util.dart';
import '../../utils/kr_aes_util.dart';
import '../kr_device_info_service.dart';
import '../kr_site_config_service.dart';
import '../../common/app_config.dart';
import 'package:dio/dio.dart' as dio;
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
import 'package:kaer_with_panels/app/utils/kr_http_adapter_util.dart';
import 'package:flutter/foundation.dart';
class KRAuthApi {
/// 检查账号是否已注册(仅支持邮箱)
Future<Either<HttpError, bool>> kr_isRegister(String email) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_isRegister, data,
method: HttpMethod.GET, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_isRegister);
}
/// 检查账号和邮箱是否都已经有订阅(只有两者都订阅才返回 true
Future<Either<HttpError, bool>> kr_checkSubscription(String email) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
// 获取当前设备 ID
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
// 发起请求
BaseResponse<KRCheckSubscription> baseResponse = await HttpUtil.getInstance()
.request<KRCheckSubscription>(
Api.kr_checkSubscription,
data,
method: HttpMethod.POST,
isShowLoading: true,
);
// 请求失败
if (!baseResponse.isSuccess) {
return left(HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
final subscription = baseResponse.model;
// 只有设备和邮箱都已订阅才返回 true
return right(subscription.isFullySubscribed);
}
/// 忘记密码-设置新密码(仅支持邮箱)
Future<Either<HttpError, String>> kr_setNewPsdByForgetPsd(
String email, String code, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['password'] = password;
data["code"] = code;
data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_setNewPsdByForgetPsd, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 注册(仅支持邮箱+密码,验证码和邀请码可选)
Future<Either<HttpError, String>> kr_register(
String email,
String password,
{String? code,
String? inviteCode}) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data["code"] = code;
// data['password'] = password;
// data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
// 验证码是可选的,只有在提供时才发送
// if (code != null && code.isNotEmpty) {
//
// }
// 邀请码是可选的
// if (inviteCode != null && inviteCode.isNotEmpty) {
// data["invite"] = inviteCode;
// }
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_register, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 手机号注册(手机号+区号+密码+验证码,邀请码可选)
Future<Either<HttpError, String>> kr_telephoneRegister(
String telephone,
String areaCode,
String password,
String code,
{String? inviteCode}) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['telephone'] = telephone;
data['telephone_area_code'] = areaCode;
data['password'] = password;
data['code'] = code;
data["identifier"] = KRDeviceInfoService().deviceId ?? 'unknown';
// 邀请码是可选的
if (inviteCode != null && inviteCode.isNotEmpty) {
data["invite"] = inviteCode;
}
// cf_token 为空字符串(如果后续需要可以添加)
data["cf_token"] = "";
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>('/v1/auth/register/telephone', data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 验证验证码(仅支持邮箱)
Future<Either<HttpError, bool>> kr_checkVerificationCode(
String email, String code, int type) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['code'] = code;
data['type'] = type;
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_checkVerificationCode, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
if (baseResponse.model.kr_isRegister) {
return right(true);
} else {
return left(HttpError(msg: "error.70001".tr, code: 70001));
}
}
/// 登录(仅支持邮箱+密码)
Future<Either<HttpError, String>> kr_login(
String email, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['password'] = password;
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
// data["identifier"] = deviceId;
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_login, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 手机号密码登录(手机号+区号+密码)
Future<Either<HttpError, String>> kr_telephoneLogin(
String telephone, String areaCode, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['telephone'] = telephone;
data['telephone_area_code'] = areaCode;
data['password'] = password;
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>('/v1/auth/login/telephone', data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 手机号验证码登录(手机号+区号+验证码)
Future<Either<HttpError, String>> kr_telephoneCodeLogin(
String telephone, String areaCode, String code) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['telephone'] = telephone;
data['telephone_area_code'] = areaCode;
data['telephone_code'] = code;
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>('/v1/auth/login/telephone', data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 发送验证码(仅支持邮箱)
/// type: 1=登录, 2=注册, 3=重置密码
Future<Either<HttpError, bool>> kr_sendCode(String email, int type) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['type'] = type;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(Api.kr_sendCode, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(true);
}
/// 发送短信验证码(支持手机号)
/// type: 1=注册, 2=验证登录
Future<Either<HttpError, bool>> kr_sendSmsCode(
String telephone, String areaCode, int type) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['telephone'] = telephone;
data['telephone_area_code'] = areaCode;
data['type'] = type;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>('/v1/common/send_sms_code', data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(true);
}
/// 删除账号
Future<Either<HttpError, String>> kr_deleteAccount(String email, String code) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['code'] = code;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(Api.kr_deleteAccount, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right("");
}
/// 忘记密码-邮箱重置密码
Future<Either<HttpError, String>> kr_resetPassword(
String email, String code, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['identifier'] = KRDeviceInfoService().deviceId ?? 'unknown';
data['email'] = email;
data['password'] = password;
data['code'] = code;
data['cf_token'] = ""; // Cloudflare token暂时留空
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_resetPassword, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 忘记密码-手机号重置密码
Future<Either<HttpError, String>> kr_resetPasswordByTelephone(
String telephone, String areaCode, String code, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['identifier'] = KRDeviceInfoService().deviceId ?? 'unknown';
data['telephone'] = telephone;
data['telephone_area_code'] = areaCode;
data['password'] = password;
data['code'] = code;
data['cf_token'] = ""; // Cloudflare token暂时留空
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_resetPasswordByTelephone, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_token.toString());
}
/// 设备登录(游客登录)
Future<Either<HttpError, String>> kr_deviceLogin() async {
try {
KRLogUtil.kr_i('🔐 开始设备登录', tag: 'KRAuthApi');
// 获取设备信息
final deviceInfoService = KRDeviceInfoService();
final deviceId = deviceInfoService.deviceId;
final userAgent = deviceInfoService.getUserAgent();
if (deviceId == null) {
print('❌ 设备ID为空无法登录');
return left(HttpError(msg: '设备ID获取失败', code: -1));
}
print('📱 设备ID: $deviceId');
print('📱 User-Agent: $userAgent');
KRLogUtil.kr_i('📱 设备ID: $deviceId', tag: 'KRAuthApi');
KRLogUtil.kr_i('📱 User-Agent: $userAgent', tag: 'KRAuthApi');
// 构建请求数据
Map<String, dynamic> data = {
'identifier': deviceId,
'user_agent': userAgent,
};
print('📤 原始请求数据: $data');
final needEncryption = true;
String? requestBody;
if (needEncryption) {
// 加密请求数据
final encrypted = KRAesUtil.encryptJson(data, AppConfig.kr_encryptionKey);
requestBody = '{"data":"${encrypted['data']}","time":"${encrypted['time']}"}';
// print('🔐 加密后请求体: $requestBody');
// KRLogUtil.kr_i('🔐 加密后请求体', tag: 'KRAuthApi');
} else {
requestBody = jsonEncode(data);
print('📝 明文请求体: $requestBody');
}
// 使用 Dio 直接发送请求(因为需要特殊的加密处理)
final dioInstance = dio.Dio();
// ✅ 关键修复:统一使用配置好的 Adapter包含 SSL 验证绕过和代理处理
dioInstance.httpClientAdapter = KRHttpAdapterUtil.createAdapter(
useSingBoxProxy: false, // 设备登录建议先直连
forceDirect: true,
timeout: const Duration(seconds: 10),
);
final baseUrl = AppConfig.getInstance().baseUrl;
final url = '$baseUrl${Api.kr_deviceLogin}';
print('📤 请求URL: $url');
KRLogUtil.kr_i('📤 请求URL: $url', tag: 'KRAuthApi');
// 设置请求头
final headers = <String, String>{
'Content-Type': 'application/json',
};
if (needEncryption) {
headers['Login-Type'] = 'device';
}
print('📤 请求头: $headers');
// 配置Dio实例的超时设置
dioInstance.options.connectTimeout = const Duration(seconds: 10);
dioInstance.options.sendTimeout = const Duration(seconds: 10);
dioInstance.options.receiveTimeout = const Duration(seconds: 10);
final response = await dioInstance.post(
url,
data: requestBody,
options: dio.Options(
headers: headers,
),
);
// print('📥 响应状态码: ${response.statusCode}');
// print('📥 响应数据: ${response.data}');
// KRLogUtil.kr_i('📥 响应状态码: ${response.statusCode}', tag: 'KRAuthApi');
// KRLogUtil.kr_i('📥 响应数据: ${response.data}', tag: 'KRAuthApi');
if (response.statusCode == 200) {
Map<String, dynamic> responseData = response.data as Map<String, dynamic>;
// 检查是否需要解密响应
if (needEncryption && responseData.containsKey('data')) {
final dataField = responseData['data'];
if (dataField is Map<String, dynamic> &&
dataField.containsKey('data') &&
dataField.containsKey('time')) {
print('🔓 解密响应数据...');
final decrypted = KRAesUtil.decryptJson(
dataField['data'] as String,
dataField['time'] as String,
AppConfig.kr_encryptionKey,
);
responseData['data'] = decrypted;
print('🔓 解密后数据: ${responseData['data']}');
KRLogUtil.kr_i('🔓 解密成功', tag: 'KRAuthApi');
}
}
if (responseData['code'] == 200) {
final token = responseData['data']['token'] as String;
print('✅ 设备登录成功');
print('🎫 Token: ${token.substring(0, min(20, token.length))}...');
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
KRLogUtil.kr_i('✅ 设备登录成功', tag: 'KRAuthApi');
return right(token);
} else {
final msg = responseData['msg'] ?? '登录失败';
print('❌ 登录失败: $msg');
return left(HttpError(msg: msg, code: responseData['code']));
}
} else {
print('❌ HTTP错误: ${response.statusCode}');
return left(HttpError(msg: 'HTTP错误', code: response.statusCode ?? -1));
}
} on dio.DioException catch (e) {
print('❌ Dio异常: ${e.type}');
print('❌ 错误信息: ${e.message}');
KRLogUtil.kr_e('❌ 设备登录Dio异常: ${e.message}', tag: 'KRAuthApi');
return left(HttpError(msg: '网络请求失败: ${e.message}', code: -1));
} catch (e, stackTrace) {
print('❌ 设备登录异常: $e');
print('📚 堆栈跟踪: $stackTrace');
KRLogUtil.kr_e('❌ 设备登录异常: $e', tag: 'KRAuthApi');
return left(HttpError(msg: '设备登录失败: $e', code: -1));
}
}
String _kr_getUserAgent() {
if (io.Platform.isAndroid) {
return 'android';
} else if (io.Platform.isIOS) {
return 'ios';
} else if (io.Platform.isMacOS) {
return 'mac';
} else if (io.Platform.isWindows) {
return 'windows';
} else if (io.Platform.isLinux) {
return 'linux';
} else if (io.Platform.isFuchsia) {
return 'harmony';
} else {
return 'unknown';
}
}
}