108 lines
3.3 KiB
Dart
108 lines
3.3 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../core/network/api_exception.dart';
|
|
import '../core/network/dio_client.dart';
|
|
import '../models/auth_response.dart';
|
|
|
|
final authApiProvider = Provider<AuthApi>((ref) => AuthApi(ref.watch(dioProvider)));
|
|
|
|
class AuthApi {
|
|
final Dio _dio;
|
|
|
|
AuthApi(this._dio);
|
|
|
|
/// 登录 → {token, username, nickname}
|
|
Future<AuthResponse> login(String username, String password) async {
|
|
try {
|
|
final res = await _dio.post('/auth/login', data: {
|
|
'username': username,
|
|
'password': password,
|
|
});
|
|
return AuthResponse.fromJson(_map(res.data));
|
|
} on DioException catch (e) {
|
|
throw _unwrap(e);
|
|
}
|
|
}
|
|
|
|
/// 注册 → {token, username, nickname}
|
|
Future<AuthResponse> register(String username, String password, String nickname) async {
|
|
try {
|
|
final res = await _dio.post('/auth/register', data: {
|
|
'username': username,
|
|
'password': password,
|
|
'nickname': nickname,
|
|
});
|
|
return AuthResponse.fromJson(_map(res.data));
|
|
} on DioException catch (e) {
|
|
throw _unwrap(e);
|
|
}
|
|
}
|
|
|
|
/// 拉当前用户资料。冷启动时本地缓存的 userInfo 可能过期(在别处换过头像),
|
|
/// 用它刷一次。
|
|
Future<AuthResponse> profile() async {
|
|
try {
|
|
final res = await _dio.get('/auth/profile');
|
|
return AuthResponse.fromJson(_map(res.data));
|
|
} on DioException catch (e) {
|
|
throw _unwrap(e);
|
|
}
|
|
}
|
|
|
|
/// 更新资料。目前只有头像。
|
|
///
|
|
/// [avatar] 传 null = 不改动;[clearAvatar] 传 true = 清空。
|
|
/// 这两种语义要分清 —— 混淆会让「只改昵称」意外把头像清掉。
|
|
Future<AuthResponse> updateProfile({String? avatar, bool clearAvatar = false}) async {
|
|
try {
|
|
final res = await _dio.put('/auth/profile', data: {
|
|
'avatar': ?avatar,
|
|
if (clearAvatar) 'clearAvatar': true,
|
|
});
|
|
return AuthResponse.fromJson(_map(res.data));
|
|
} on DioException catch (e) {
|
|
throw _unwrap(e);
|
|
}
|
|
}
|
|
|
|
/// 上传头像,返回**相对路径**(再由 updateProfile 提交)。
|
|
Future<String> uploadAvatar(String filePath) async {
|
|
try {
|
|
final form = FormData.fromMap({
|
|
'file': await MultipartFile.fromFile(filePath, filename: _fileName(filePath)),
|
|
});
|
|
final res = await _dio.post<dynamic>(
|
|
'/auth/avatar',
|
|
data: form,
|
|
options: Options(receiveTimeout: const Duration(seconds: 60)),
|
|
);
|
|
final data = res.data;
|
|
if (data is Map && data['path'] != null) {
|
|
return data['path'].toString();
|
|
}
|
|
throw ApiException(-1, '上传失败:服务端没返回路径');
|
|
} on DioException catch (e) {
|
|
throw _unwrap(e);
|
|
}
|
|
}
|
|
|
|
/// 从路径取文件名,后端靠扩展名判断类型
|
|
static String _fileName(String path) {
|
|
final normalized = path.replaceAll('\\', '/');
|
|
final name = normalized.substring(normalized.lastIndexOf('/') + 1);
|
|
return name.isEmpty ? 'avatar.jpg' : name;
|
|
}
|
|
|
|
Map<String, dynamic> _map(Object? data) {
|
|
if (data is Map<String, dynamic>) return data;
|
|
throw ApiException(-1, '响应格式异常');
|
|
}
|
|
|
|
Never _unwrap(DioException e) {
|
|
final err = e.error;
|
|
if (err is ApiException) throw err;
|
|
throw ApiException(-1, '请求失败');
|
|
}
|
|
}
|