Files
ai_english/client/lib/shared/services/auth_service.dart
2025-11-17 13:39:05 +08:00

371 lines
11 KiB
Dart
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 'package:dio/dio.dart';
import '../../core/network/api_client.dart';
import '../../core/storage/storage_service.dart';
import '../../core/constants/app_constants.dart';
import '../../core/errors/app_error.dart';
import '../models/api_response.dart';
import '../models/user_model.dart';
/// 认证服务
class AuthService {
static final AuthService _instance = AuthService._internal();
factory AuthService() => _instance;
AuthService._internal();
final ApiClient _apiClient = ApiClient.instance;
/// 用户注册
Future<ApiResponse<AuthResponse>> register({
required String username,
required String email,
required String password,
required String nickname,
String? phone,
}) async {
try {
final response = await _apiClient.post(
'/auth/register',
data: {
'username': username,
'email': email,
'password': password,
'nickname': nickname,
if (phone != null) 'phone': phone,
},
);
if (response.statusCode == 201) {
final authResponse = AuthResponse.fromJson(response.data['data']);
await _saveTokens(authResponse);
return ApiResponse.success(
message: response.data['message'] ?? '注册成功',
data: authResponse,
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? '注册失败',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: '注册失败:$e',
error: e.toString(),
);
}
}
/// 用户登录
Future<ApiResponse<AuthResponse>> login({
required String account, // 用户名或邮箱
required String password,
bool rememberMe = false,
}) async {
try {
final response = await _apiClient.post(
'/auth/login',
data: {
'account': account,
'password': password,
},
);
if (response.statusCode == 200) {
final authResponse = AuthResponse.fromJson(response.data['data']);
await _saveTokens(authResponse);
return ApiResponse.success(
message: response.data['message'] ?? '登录成功',
data: authResponse,
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? '登录失败',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: '登录失败:$e',
error: e.toString(),
);
}
}
/// 刷新Token
Future<ApiResponse<AuthResponse>> refreshToken() async {
try {
final refreshToken = StorageService.getString(AppConstants.refreshTokenKey);
if (refreshToken == null) {
return ApiResponse.failure(
message: 'Refresh token not found',
code: 401,
);
}
final response = await _apiClient.post(
'/auth/refresh',
data: {
'refresh_token': refreshToken,
},
);
if (response.statusCode == 200) {
final authResponse = AuthResponse.fromJson(response.data['data']);
await _saveTokens(authResponse);
return ApiResponse.success(
message: 'Token refreshed successfully',
data: authResponse,
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? 'Token refresh failed',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: 'Token refresh failed: $e',
error: e.toString(),
);
}
}
/// 用户登出
Future<ApiResponse<void>> logout() async {
try {
final response = await _apiClient.post('/auth/logout');
// 无论服务器响应如何都清除本地token
await _clearTokens();
if (response.statusCode == 200) {
return ApiResponse.success(
message: response.data['message'] ?? '登出成功',
);
} else {
return ApiResponse.success(
message: '登出成功',
);
}
} on DioException catch (e) {
// 即使请求失败也清除本地token
await _clearTokens();
return ApiResponse.success(
message: '登出成功',
);
} catch (e) {
await _clearTokens();
return ApiResponse.success(
message: '登出成功',
);
}
}
/// 获取当前用户信息
Future<ApiResponse<UserModel>> getCurrentUser() async {
try {
final response = await _apiClient.get('/auth/me');
if (response.statusCode == 200) {
final user = UserModel.fromJson(response.data['data']);
await StorageService.setObject(AppConstants.userInfoKey, user.toJson());
return ApiResponse.success(
message: 'User info retrieved successfully',
data: user,
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? 'Failed to get user info',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: 'Failed to get user info: $e',
error: e.toString(),
);
}
}
/// 更新用户信息
Future<ApiResponse<UserModel>> updateProfile({
String? nickname,
String? avatar,
String? phone,
DateTime? birthday,
String? gender,
String? bio,
String? learningLevel,
String? targetLanguage,
String? nativeLanguage,
int? dailyGoal,
}) async {
try {
final data = <String, dynamic>{};
if (nickname != null) data['nickname'] = nickname;
if (avatar != null) data['avatar'] = avatar;
if (phone != null) data['phone'] = phone;
if (birthday != null) data['birthday'] = birthday.toIso8601String();
if (gender != null) data['gender'] = gender;
if (bio != null) data['bio'] = bio;
if (learningLevel != null) data['learning_level'] = learningLevel;
if (targetLanguage != null) data['target_language'] = targetLanguage;
if (nativeLanguage != null) data['native_language'] = nativeLanguage;
if (dailyGoal != null) data['daily_goal'] = dailyGoal;
final response = await _apiClient.put('/auth/profile', data: data);
if (response.statusCode == 200) {
final user = UserModel.fromJson(response.data['data']);
await StorageService.setObject(AppConstants.userInfoKey, user.toJson());
return ApiResponse.success(
message: response.data['message'] ?? '个人信息更新成功',
data: user,
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? '个人信息更新失败',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: '个人信息更新失败:$e',
error: e.toString(),
);
}
}
/// 修改密码
Future<ApiResponse<void>> changePassword({
required String currentPassword,
required String newPassword,
required String confirmPassword,
}) async {
try {
final response = await _apiClient.put(
'/auth/change-password',
data: {
'current_password': currentPassword,
'new_password': newPassword,
'confirm_password': confirmPassword,
},
);
if (response.statusCode == 200) {
return ApiResponse.success(
message: response.data['message'] ?? '密码修改成功',
);
} else {
return ApiResponse.failure(
message: response.data['message'] ?? '密码修改失败',
code: response.statusCode,
);
}
} on DioException catch (e) {
return _handleDioError(e);
} catch (e) {
return ApiResponse.failure(
message: '密码修改失败:$e',
error: e.toString(),
);
}
}
/// 检查是否已登录
bool isLoggedIn() {
final token = StorageService.getString(AppConstants.accessTokenKey);
return token != null && token.isNotEmpty;
}
/// 获取本地存储的用户信息
UserModel? getCachedUser() {
final userJson = StorageService.getObject(AppConstants.userInfoKey);
if (userJson != null) {
try {
return UserModel.fromJson(userJson);
} catch (e) {
print('Error parsing cached user: $e');
return null;
}
}
return null;
}
/// 保存tokens
Future<void> _saveTokens(AuthResponse authResponse) async {
await StorageService.setString(
AppConstants.accessTokenKey,
authResponse.accessToken,
);
await StorageService.setString(
AppConstants.refreshTokenKey,
authResponse.refreshToken,
);
if (authResponse.user != null) {
await StorageService.setObject(
AppConstants.userInfoKey,
authResponse.user!,
);
}
}
/// 清除tokens
Future<void> _clearTokens() async {
await StorageService.remove(AppConstants.accessTokenKey);
await StorageService.remove(AppConstants.refreshTokenKey);
await StorageService.remove(AppConstants.userInfoKey);
}
/// 处理Dio错误
ApiResponse<T> _handleDioError<T>(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return ApiResponse.failure(
message: '请求超时,请检查网络连接',
error: 'TIMEOUT',
);
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
final message = e.response?.data?['message'] ?? '请求失败';
return ApiResponse.failure(
message: message,
code: statusCode,
error: 'BAD_RESPONSE',
);
case DioExceptionType.cancel:
return ApiResponse.failure(
message: '请求已取消',
error: 'CANCELLED',
);
case DioExceptionType.connectionError:
return ApiResponse.failure(
message: '网络连接失败,请检查网络设置',
error: 'CONNECTION_ERROR',
);
default:
return ApiResponse.failure(
message: '未知错误:${e.message}',
error: 'UNKNOWN',
);
}
}
}