feat: 切换邮箱认证并重构前后端启动与门禁

This commit is contained in:
qzl
2026-04-02 18:39:35 +08:00
parent 92cdfd9fca
commit 31594558eb
116 changed files with 5608 additions and 628 deletions
@@ -0,0 +1,41 @@
import '../../../../data/network/api_client.dart';
import '../models/session_response.dart';
class AuthApi {
AuthApi({required ApiClient apiClient}) : _apiClient = apiClient;
final ApiClient _apiClient;
Future<void> sendOtp({required String email}) async {
await _apiClient.postNoContent(
'/api/v1/auth/otp/send',
data: {'email': email},
);
}
Future<SessionResponse> createEmailSession({
required String email,
required String token,
}) async {
final json = await _apiClient.postJson(
'/api/v1/auth/email-session',
data: {'email': email, 'token': token},
);
return SessionResponse.fromJson(json);
}
Future<void> deleteSession({required String refreshToken}) async {
await _apiClient.deleteNoContent(
'/api/v1/auth/sessions',
data: {'refresh_token': refreshToken},
);
}
Future<SessionResponse> refreshSession({required String refreshToken}) async {
final json = await _apiClient.postJson(
'/api/v1/auth/sessions/refresh',
data: {'refresh_token': refreshToken},
);
return SessionResponse.fromJson(json);
}
}
@@ -0,0 +1,6 @@
class AuthUser {
const AuthUser({required this.id, required this.email});
final String id;
final String email;
}
@@ -0,0 +1,45 @@
class SessionResponse {
SessionResponse({
required this.accessToken,
required this.refreshToken,
required this.expiresIn,
required this.tokenType,
required this.userId,
required this.userEmail,
});
final String accessToken;
final String refreshToken;
final int expiresIn;
final String tokenType;
final String userId;
final String userEmail;
factory SessionResponse.fromJson(Map<String, dynamic> json) {
final user = (json['user'] as Map<String, dynamic>?) ?? <String, dynamic>{};
final accessToken = json['access_token'] as String?;
final refreshToken = json['refresh_token'] as String?;
final expiresIn = json['expires_in'] as int?;
final tokenType = json['token_type'] as String?;
final userId = user['id'] as String?;
final userEmail = user['email'] as String?;
if (accessToken == null ||
refreshToken == null ||
expiresIn == null ||
tokenType == null ||
userId == null ||
userEmail == null) {
throw const FormatException('Invalid session response payload');
}
return SessionResponse(
accessToken: accessToken,
refreshToken: refreshToken,
expiresIn: expiresIn,
tokenType: tokenType,
userId: userId,
userEmail: userEmail,
);
}
}
@@ -0,0 +1,86 @@
import '../../../../core/auth/session_store.dart';
import '../apis/auth_api.dart';
import '../models/auth_user.dart';
abstract class AuthRepository {
Future<void> sendOtp(String email);
Future<AuthUser> loginWithEmailOtp({
required String email,
required String otp,
});
Future<AuthUser?> recoverSession();
Future<void> logout();
Future<void> clearLocalSession();
}
class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({
required AuthApi authApi,
required SessionStore sessionStore,
}) : _authApi = authApi,
_sessionStore = sessionStore;
final AuthApi _authApi;
final SessionStore _sessionStore;
@override
Future<void> sendOtp(String email) async {
await _authApi.sendOtp(email: email);
}
@override
Future<AuthUser> loginWithEmailOtp({
required String email,
required String otp,
}) async {
final session = await _authApi.createEmailSession(email: email, token: otp);
await _sessionStore.saveToken(session.accessToken);
await _sessionStore.saveRefreshToken(session.refreshToken);
await _sessionStore.saveEmail(email);
return AuthUser(id: session.userId, email: email);
}
@override
Future<AuthUser?> recoverSession() async {
final refreshToken = await _sessionStore.getRefreshToken();
if (refreshToken == null || refreshToken.isEmpty) {
return null;
}
final session = await _authApi.refreshSession(refreshToken: refreshToken);
await _sessionStore.saveToken(session.accessToken);
await _sessionStore.saveRefreshToken(session.refreshToken);
final savedEmail = await _sessionStore.getEmail();
final email = savedEmail?.isNotEmpty == true
? savedEmail!
: session.userEmail;
if (email.isNotEmpty) {
await _sessionStore.saveEmail(email);
}
return AuthUser(id: session.userId, email: email);
}
@override
Future<void> logout() async {
final refreshToken = await _sessionStore.getRefreshToken();
try {
if (refreshToken != null && refreshToken.isNotEmpty) {
await _authApi.deleteSession(refreshToken: refreshToken);
}
} finally {
await clearLocalSession();
}
}
@override
Future<void> clearLocalSession() async {
await _sessionStore.clearToken();
await _sessionStore.clearRefreshToken();
await _sessionStore.clearEmail();
}
}