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((ref) => AuthApi(ref.watch(dioProvider))); class AuthApi { final Dio _dio; AuthApi(this._dio); /// 登录 → {token, username, nickname} Future 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 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 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 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 uploadAvatar(String filePath) async { try { final form = FormData.fromMap({ 'file': await MultipartFile.fromFile(filePath, filename: _fileName(filePath)), }); final res = await _dio.post( '/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 _map(Object? data) { if (data is Map) return data; throw ApiException(-1, '响应格式异常'); } Never _unwrap(DioException e) { final err = e.error; if (err is ApiException) throw err; throw ApiException(-1, '请求失败'); } }