网络请求打通,ws 打通
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import '../../../core/foundation/api_paths.dart';
|
||||
import '../../../domain/entities/user.dart';
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
import 'package:im_app/domain/entities/user.dart';
|
||||
|
||||
part 'get_profile_request.g.dart';
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import '../../../core/foundation/api_paths.dart';
|
||||
import '../../../domain/entities/user.dart';
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
import 'package:im_app/domain/entities/user.dart';
|
||||
|
||||
part 'login_request.g.dart';
|
||||
|
||||
/// # /auth/login — 登录接口
|
||||
/// # /app/api/auth/login-user — 使用 vcode_token 完成登录
|
||||
///
|
||||
/// 流程:发送验证码([SendOtpRequest])→ 校验验证码([VerifyOtpRequest])
|
||||
/// → ★ 用 vcode_token 登录(本请求)★ → 获得 access_token
|
||||
///
|
||||
/// ## 数据流位置
|
||||
///
|
||||
/// ```
|
||||
/// AuthRepositoryImpl.login(email, password)
|
||||
/// AuthRepositoryImpl.login(countryCode, contact, vcodeToken)
|
||||
/// → _client.executeRequest( ★ LoginRequest ★ ) ← 你在这里
|
||||
/// → 服务端 POST /auth/login
|
||||
/// → SDK 内部 ApiResponseWrapper 拆包 { code, message, data }
|
||||
/// → ★ LoginResponse ★ = data 字段,T in APIResponseWrapper<T> ← 也在这里
|
||||
/// → 服务端 POST /app/api/auth/login-user
|
||||
/// → SDK 拆包 {code, message, data} envelope
|
||||
/// → ★ LoginResponse ★ ← 也在这里
|
||||
/// → LoginResponse.toEntity() → User
|
||||
/// ```
|
||||
|
||||
@@ -100,24 +103,27 @@ class LoginResponse {
|
||||
@JsonKey(name: 'account_id')
|
||||
final String accountId;
|
||||
final LoginProfile profile;
|
||||
final String nonce;
|
||||
@JsonKey(name: 'access_token')
|
||||
final String accessToken;
|
||||
@JsonKey(name: 'refresh_token')
|
||||
final String refreshToken;
|
||||
@JsonKey(name: 'device_id')
|
||||
final String deviceId;
|
||||
final String nonce;
|
||||
@JsonKey(name: 'login_data')
|
||||
final String loginData;
|
||||
@JsonKey(name: 'is_verified')
|
||||
final bool? isVerified;
|
||||
|
||||
const LoginResponse({
|
||||
required this.accountId,
|
||||
required this.profile,
|
||||
required this.nonce,
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
required this.deviceId,
|
||||
required this.loginData,
|
||||
this.nonce = '',
|
||||
this.loginData = '',
|
||||
this.isVerified,
|
||||
});
|
||||
|
||||
User toEntity() => profile.toEntity();
|
||||
@@ -127,11 +133,10 @@ class LoginResponse {
|
||||
// Request
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/// 登录请求
|
||||
/// 使用 vcode_token 完成登录的请求
|
||||
///
|
||||
/// `@ApiRequest` 一个注解搞定一切:
|
||||
/// - mixin 自动生成 path / method / requestType / includeToken / toJson
|
||||
/// - parameters getter 自动注册 `_$LoginResponseFromJson` 到 SDK 全局注册表
|
||||
/// 上游:[VerifyOtpRequest] 返回的 `token` 即 vcodeToken。
|
||||
/// 成功后 [LoginResponse.accessToken] 写入 ApiConfig,后续请求自动携带。
|
||||
@ApiRequest(
|
||||
path: ApiPaths.authLogin,
|
||||
method: HttpMethod.post,
|
||||
@@ -140,8 +145,15 @@ class LoginResponse {
|
||||
)
|
||||
class LoginRequest extends ApiRequestable<LoginResponse>
|
||||
with _$LoginRequestApi {
|
||||
final String email;
|
||||
final String password;
|
||||
@JsonKey(name: 'country_code')
|
||||
final String countryCode;
|
||||
final String contact;
|
||||
@JsonKey(name: 'vcode_token')
|
||||
final String vcodeToken;
|
||||
|
||||
LoginRequest({required this.email, required this.password});
|
||||
LoginRequest({
|
||||
required this.countryCode,
|
||||
required this.contact,
|
||||
required this.vcodeToken,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import '../../../core/foundation/api_paths.dart';
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
|
||||
part 'logout_request.g.dart';
|
||||
|
||||
|
||||
70
apps/im_app/lib/data/remote/send_otp_request.dart
Normal file
70
apps/im_app/lib/data/remote/send_otp_request.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
|
||||
part 'send_otp_request.g.dart';
|
||||
|
||||
/// 发送验证码响应
|
||||
///
|
||||
/// code: 0 时 [expiryTime] 有值;
|
||||
/// code: 30174(触发图片验证)时 [android] / [ios] / [web] / [extras] 有值。
|
||||
class SendOtpResponse {
|
||||
/// 验证码有效期(秒)
|
||||
@JsonKey(name: 'expiry_time')
|
||||
final int? expiryTime;
|
||||
|
||||
/// Android CAPTCHA SDK token
|
||||
final String? android;
|
||||
|
||||
/// iOS CAPTCHA SDK token
|
||||
final String? ios;
|
||||
|
||||
/// Web CAPTCHA SDK token
|
||||
final String? web;
|
||||
|
||||
/// CAPTCHA 平台扩展参数(预留字段)
|
||||
final Map<String, dynamic>? extras;
|
||||
|
||||
const SendOtpResponse({
|
||||
this.expiryTime,
|
||||
this.android,
|
||||
this.ios,
|
||||
this.web,
|
||||
this.extras,
|
||||
});
|
||||
}
|
||||
|
||||
/// # /app/api/auth/vcode/get — 发送手机验证码
|
||||
///
|
||||
/// 响应 `data: { "expiry_time": 180 }`,返回验证码有效期(秒)。
|
||||
///
|
||||
/// ## 数据流位置
|
||||
///
|
||||
/// ```
|
||||
/// AuthRepositoryImpl.sendOtp(countryCode, contact)
|
||||
/// → _client.executeRequest( ★ SendOtpRequest ★ ) ← 你在这里
|
||||
/// → 服务端 POST /app/api/auth/vcode/get
|
||||
/// → 响应 { expiry_time: 180 } → SendOtpResponse
|
||||
/// ```
|
||||
@ApiRequest(
|
||||
path: ApiPaths.authSendOtp,
|
||||
method: HttpMethod.post,
|
||||
responseType: SendOtpResponse,
|
||||
requestType: ApiRequestType.login,
|
||||
)
|
||||
class SendOtpRequest extends ApiRequestable<SendOtpResponse>
|
||||
with _$SendOtpRequestApi {
|
||||
@JsonKey(name: 'country_code')
|
||||
final String countryCode;
|
||||
final String contact;
|
||||
|
||||
/// type=1 表示手机号验证
|
||||
final int type;
|
||||
|
||||
SendOtpRequest({
|
||||
required this.countryCode,
|
||||
required this.contact,
|
||||
this.type = 1,
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import 'dart:typed_data';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import '../../../core/foundation/api_paths.dart';
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
|
||||
part 'upload_file_request.g.dart';
|
||||
|
||||
|
||||
61
apps/im_app/lib/data/remote/verify_otp_request.dart
Normal file
61
apps/im_app/lib/data/remote/verify_otp_request.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import 'package:im_app/core/foundation/api_paths.dart';
|
||||
|
||||
part 'verify_otp_request.g.dart';
|
||||
|
||||
/// 校验验证码接口的响应(服务端 data 字段)。
|
||||
///
|
||||
/// `token` 是 vcode_token,用于后续 login-user 请求换取 access_token。
|
||||
/// 纯 Dart 类,无需任何注解。`_$VerifyOtpResponseFromJson` 由生成器自动推导生成。
|
||||
class VerifyOtpResponse {
|
||||
/// 验证令牌,传给登录接口换取 access_token
|
||||
final String token;
|
||||
|
||||
const VerifyOtpResponse({required this.token});
|
||||
}
|
||||
|
||||
/// # /app/api/auth/vcode/check — 校验手机验证码
|
||||
///
|
||||
/// 校验成功后返回 [VerifyOtpResponse.token](即 vcode_token),
|
||||
/// 用于 [LoginRequest] 的 `vcode_token` 字段。
|
||||
///
|
||||
/// ## 数据流位置
|
||||
///
|
||||
/// ```
|
||||
/// AuthRepositoryImpl.verifyOtp(countryCode, contact, code)
|
||||
/// → _client.executeRequest( ★ VerifyOtpRequest ★ ) ← 你在这里
|
||||
/// → 服务端 POST /app/api/auth/vcode/check
|
||||
/// → SDK 拆包 {code, message, data} envelope
|
||||
/// ← ★ VerifyOtpResponse ★ — token 即 vcode_token
|
||||
/// ```
|
||||
@ApiRequest(
|
||||
path: ApiPaths.authVerifyOtp,
|
||||
method: HttpMethod.post,
|
||||
responseType: VerifyOtpResponse,
|
||||
requestType: ApiRequestType.login,
|
||||
)
|
||||
class VerifyOtpRequest extends ApiRequestable<VerifyOtpResponse>
|
||||
with _$VerifyOtpRequestApi {
|
||||
@JsonKey(name: 'country_code')
|
||||
final String countryCode;
|
||||
final String contact;
|
||||
|
||||
/// 邮箱(手机号登录传空字符串)
|
||||
final String email;
|
||||
|
||||
/// 用户输入的验证码
|
||||
final String code;
|
||||
|
||||
/// type=1 表示手机号验证
|
||||
final int type;
|
||||
|
||||
VerifyOtpRequest({
|
||||
required this.countryCode,
|
||||
required this.contact,
|
||||
required this.code,
|
||||
this.email = '',
|
||||
this.type = 1,
|
||||
});
|
||||
}
|
||||
@@ -1,27 +1,34 @@
|
||||
import 'package:networks_sdk/networks_sdk.dart';
|
||||
|
||||
import '../../domain/entities/user.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../remote/login_request.dart';
|
||||
import '../remote/logout_request.dart';
|
||||
import 'package:im_app/domain/entities/user.dart';
|
||||
import 'package:im_app/domain/repositories/auth_repository.dart';
|
||||
import 'package:im_app/data/remote/login_request.dart';
|
||||
import 'package:im_app/data/remote/logout_request.dart';
|
||||
import 'package:im_app/data/remote/send_otp_request.dart';
|
||||
import 'package:im_app/data/remote/verify_otp_request.dart';
|
||||
|
||||
/// 认证 Repository 实现
|
||||
///
|
||||
/// implements [AuthRepository] 接口(domain/repositories/ 中定义)。
|
||||
/// 直接使用 [NetworksSdkApi] 发送请求,将 DTO 转为 Domain Entity。
|
||||
/// 后续可加 Local DataSource 实现离线缓存。
|
||||
///
|
||||
/// ## 数据流位置
|
||||
/// ## 登录流程
|
||||
///
|
||||
/// ```
|
||||
/// LoginUseCase.execute(email, password)
|
||||
/// → ★ AuthRepositoryImpl.login() ★ ← 你在这里
|
||||
/// LoginUseCase.sendOtp(countryCode, contact)
|
||||
/// → ★ AuthRepositoryImpl.sendOtp() ★ ← 你在这里(步骤 1)
|
||||
/// → NetworksSdkApi.executeRequest(SendOtpRequest)
|
||||
/// → 服务端 POST /app/api/auth/otp/send
|
||||
///
|
||||
/// LoginUseCase.verifyAndLogin(countryCode, contact, code)
|
||||
/// → ★ AuthRepositoryImpl.verifyOtp() ★ ← 你在这里(步骤 2)
|
||||
/// → NetworksSdkApi.executeRequest(VerifyOtpRequest)
|
||||
/// → 服务端 POST /app/api/auth/vcode/check
|
||||
/// ← VerifyOtpResponse.token = vcode_token
|
||||
/// → ★ AuthRepositoryImpl.login() ★ ← 你在这里(步骤 3)
|
||||
/// → NetworksSdkApi.executeRequest(LoginRequest)
|
||||
/// → 服务端 POST /auth/login
|
||||
/// ← LoginResponse(SDK 已拆包 { code, message, data } envelope)
|
||||
/// → _onTokenUpdate(accessToken) ← 回调写入 Token
|
||||
/// ← LoginResponse.toEntity() → User ← DTO → Entity 转换在这里
|
||||
/// ← User(Domain Entity)
|
||||
/// → 服务端 POST /app/api/auth/login-user
|
||||
/// ← LoginResponse → _onTokenUpdate(accessToken) → User
|
||||
/// ```
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final NetworksSdkApi _client;
|
||||
@@ -34,29 +41,62 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
_onTokenUpdate = onTokenUpdate;
|
||||
|
||||
@override
|
||||
Future<User> login({required String email, required String password}) async {
|
||||
final LoginResponse? loginResponse = await _client.executeRequest(
|
||||
LoginRequest(email: email, password: password),
|
||||
Future<void> sendOtp({
|
||||
required String countryCode,
|
||||
required String contact,
|
||||
}) async {
|
||||
await _client.executeRequest(
|
||||
SendOtpRequest(countryCode: countryCode, contact: contact),
|
||||
);
|
||||
|
||||
if (loginResponse == null) {
|
||||
throw Exception('Login failed: empty response');
|
||||
}
|
||||
|
||||
_onTokenUpdate(loginResponse.accessToken);
|
||||
|
||||
return loginResponse.toEntity();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<User?> getCurrentUser() async {
|
||||
// TODO: 从本地存储获取用户信息
|
||||
return null;
|
||||
Future<String> verifyOtp({
|
||||
required String countryCode,
|
||||
required String contact,
|
||||
required String code,
|
||||
}) async {
|
||||
final response = await _client.executeRequest(
|
||||
VerifyOtpRequest(
|
||||
countryCode: countryCode,
|
||||
contact: contact,
|
||||
code: code,
|
||||
),
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
throw Exception('Verify OTP failed: empty response');
|
||||
}
|
||||
|
||||
return response.token;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<User> login({
|
||||
required String countryCode,
|
||||
required String contact,
|
||||
required String vcodeToken,
|
||||
}) async {
|
||||
final response = await _client.executeRequest(
|
||||
LoginRequest(
|
||||
countryCode: countryCode,
|
||||
contact: contact,
|
||||
vcodeToken: vcodeToken,
|
||||
),
|
||||
);
|
||||
|
||||
if (response == null) {
|
||||
throw Exception('Login failed: empty response');
|
||||
}
|
||||
|
||||
_onTokenUpdate(response.accessToken);
|
||||
|
||||
return response.toEntity();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
await _client.executeRequest(LogoutRequest());
|
||||
_onTokenUpdate(null); // 回调清除 Token(内存 + 持久化由 Provider 层组合)
|
||||
_onTokenUpdate(null); // 清除 Token(内存 + 持久化由 Provider 层组合)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user