import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:unionapp/service/steganography_util.dart'; import '../model/song_model.dart'; import '../model/user_model.dart'; import '../tool/encryption_util.dart'; import 'dart:typed_data'; class UserService { static final Dio _dio = Dio(); static const String baseUrl = "https://union.godserver.cn"; // 注册 static Future register(String username, String password, String inviter) async { try { await _dio.post( '$baseUrl/user/register', data: { "username": username, "password": password, "inviter": inviter, }, ); } on DioException catch (e) { throw _getErrorMessage(e); } } // 登录 static Future login(String username, String twoKeyCode) async { try { final raw = jsonEncode({ "username": username, "twoKeyCode": twoKeyCode, }); final encrypted = EncryptionUtil.encrypt(raw); final res = await _dio.post( '$baseUrl/api/user/user', data: { "code": 200, "timestamp": DateTime.now().millisecondsSinceEpoch, "msg": "login", "data": encrypted, }, ); return EncryptionUtil.decrypt(res.data["data"]?.toString() ?? ''); } on DioException catch (e) { throw _getErrorMessage(e); } } // 获取用户信息 static Future getUserInfo(String token) async { try { final res = await _dio.post( '$baseUrl/api/user/data', data: { "msg": "getUser", "timestamp": DateTime.now().millisecondsSinceEpoch, "data": token, }, options: Options(headers: {"Authorization": token}), ); final decrypted = EncryptionUtil.decrypt(res.data["data"]?.toString() ?? ''); return UserModel.fromJson(jsonDecode(decrypted)); } on DioException catch (e) { throw _getErrorMessage(e); } } // 保存/修改用户信息(updateUser) static Future updateUser(String token, String encryptedData) async { try { final res = await _dio.put( '$baseUrl/api/user/data', data: { "msg": "updateUser", "timestamp": DateTime.now().millisecondsSinceEpoch, "data": encryptedData, }, options: Options(headers: {"Authorization": token}), ); return res.data.toString(); } on DioException catch (e) { throw _getErrorMessage(e); } return ""; } // 检查登录状态 static Future isLogin(String token) async { try { final res = await _dio.get( '$baseUrl/api/user/isLogin', options: Options(headers: {"Authorization": token}), ); return res.data["code"] == 200; } on DioException { return false; } } // 刷新Token(接口通用逻辑) static Future refreshToken(String oldToken) async { try { final res = await _dio.post( '$baseUrl/api/user/refreshToken', options: Options(headers: {"Authorization": oldToken}), ); if (res.data["code"] == 200) { return res.data["data"]?.toString(); } return null; } on DioException { return null; } } // 获取角色 static Future getUserRole(String token) async { try { final res = await _dio.get( '$baseUrl/api/user/role', options: Options(headers: {"Authorization": token}), ); if (res.data["code"] == 200) { return res.data["data"]?.toString(); } return null; } on DioException { return null; } } // 获取性别标签 static Future> fetchSexTags() async { try { final res = await _dio.get('$baseUrl/api/union/sextag'); if (res.data is List) { return List.from(res.data); } return []; } on DioException { return []; } } // 获取好友列表 static Future> fetchFriends(String token) async { try { final res = await _dio.get( '$baseUrl/api/user/friend', options: Options(headers: {"Authorization": token}), ); if (res.data is List) { return res.data; } return []; } on DioException { return []; } } // 更新API Key static Future updateApiKey(String token) async { try { await _dio.post( '$baseUrl/api/user/updateKey', data: { "msg": "updateApiKey", "timestamp": DateTime.now().millisecondsSinceEpoch, }, options: Options(headers: {"Authorization": token}), ); } on DioException catch (e) { throw _getErrorMessage(e); } } // 绑定GitHub - 获取跳转链接 static Future bindGithub(String token) async { try { final res = await _dio.get( '$baseUrl/api/user/bind/github', options: Options(headers: {"Authorization": token}), ); return res.realUri.toString(); } on DioException catch (e) { throw _getErrorMessage(e); } } // 确认绑定GitHub static Future confirmBindGithub(String token, String uuid) async { try { await _dio.post( '$baseUrl/api/user/bind/github', options: Options(headers: { "Authorization": token, "uuid": uuid, }), ); } on DioException catch (e) { throw _getErrorMessage(e); } } // 验证SegaId static Future> verifySegaId(String token, String segaId) async { try { final res = await _dio.post( '$baseUrl/api/user/verifySegaId', queryParameters: {"useSegaId": segaId}, options: Options(headers: {"Authorization": token}), ); return res.data; } on DioException catch (e) { throw _getErrorMessage(e); } } // 获取雷达图数据 static Future> getRadarData(String token, String id) async { try { final res = await _dio.get( '$baseUrl/api/union/radar', queryParameters: {"id": id}, options: Options(headers: {"Authorization": token}), ); // 返回雷达图原始数据 return Map.from(res.data); } on DioException catch (e) { throw _getErrorMessage(e); } } // 统一错误处理 static String _getErrorMessage(DioException e) { if (e.response?.data != null) { final data = e.response!.data; if (data is Map && data["msg"] != null) { return data["msg"].toString(); } } switch (e.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.receiveTimeout: case DioExceptionType.sendTimeout: return "网络超时"; case DioExceptionType.connectionError: return "网络异常"; default: return "请求失败"; } } static Future> getUserAllScores(String token, {String? name}) async { try { // 构建查询参数 final queryParameters = {}; if (name != null && name.isNotEmpty) { queryParameters['name'] = name; } final res = await _dio.get( '$baseUrl/api/union/reisaRating', queryParameters: queryParameters, options: Options(headers: {"Authorization": token}), ); if (res.data is Map) { return Map.from(res.data); } return {}; } on DioException catch (e) { throw _getErrorMessage(e); } } static Future> getSegaRatingData(String token, String segaId) async { try { final res = await _dio.get( '$baseUrl/api/union/segaReisaRating', queryParameters: {"segaId": segaId}, options: Options(headers: {"Authorization": token}), ); return Map.from(res.data); } on DioException catch (e) { throw _getErrorMessage(e); } } // 上传 Sega 成绩数据 static Future> uploadSegaRating( String token, String segaId, Map segaResult, ) async { try { final res = await _dio.post( '$baseUrl/api/union/segaReisaRating', queryParameters: { "segaId": segaId, }, data: segaResult, options: Options( headers: { "Authorization": token, }, ), ); return Map.from(res.data); } on DioException catch (e) { throw _getErrorMessage(e); } } static Future> getChartLog(String token, List musicIds) async { try { print("getChartLog"); // 构建查询参数 // 如果 API 期望的是逗号分隔的字符串 "11434,11435" final String idsParam = musicIds.map((e) => e.toString()).join(','); final res = await _dio.get( '$baseUrl/api/union/chartlog', queryParameters: { "musicId": idsParam, }, options: Options(headers: {"Authorization": token}), ); // 检查返回数据是否为 List if (res.data is List) { return (res.data as List) .map((item) => ChartLogModel.fromJson(item as Map)) .toList(); } // 如果后端在某些错误情况下返回 Map 而不是 List,这里返回空列表或抛出异常 return []; } on DioException catch (e) { throw _getErrorMessage(e); } } /// 上传隐写图片 /// [token] 用户令牌 /// [segaId] 要隐藏的 Sega ID /// [originalImageBytes] 原始图片字节数据 (Uint8List) static Future> uploadStegImage({ required String token, required String segaId, required Uint8List originalImageBytes, }) async { try { // 1. 拼接隐写数据(SGWCMAID 开头) final secretData = 'SGWCMAID$segaId'; // 2. 执行隐写 + 加密 // 使用 Uint8List.fromList 确保类型兼容,避免 typed_data_patch 错误 final safeImageBytes = Uint8List.fromList(originalImageBytes); final stegImageBytes = SteganographyUtil.hideDataInImage( safeImageBytes, secretData, ); // 3. 构建上传表单 final formData = FormData.fromMap({ 'path': MultipartFile.fromBytes( stegImageBytes, filename: 'encoded.png', contentType: DioMediaType.parse('image/png'), ), }); // 4. 前置接口(完全对齐 JS 逻辑) await _dio.get( '$baseUrl/api/sega/3egrsz53et/w35eshdk76e', options: Options(headers: {"Authorization": token}), ); // 5. 【核心上传】隐写图片 final res = await _dio.post( '$baseUrl/api/sega/steg', data: formData, options: Options( headers: { "Authorization": token, // Dio 会自动处理 multipart/form-data 的 boundary,通常不需要手动指定 Content-Type // 但为了严格对齐 JS,如果后端强校验,可以保留,不过 Dio 推荐让它在 FormData 时自动设置 }, ), ); // 6. 后续接口链(对齐 JS 逻辑) await _dio.post( '$baseUrl/api/sega/454szhghs/45ustzdxzsd', options: Options(headers: {"Authorization": token}), ); await _dio.get( '$baseUrl/api/sega/stxfghb347/b46se', options: Options(headers: {"Authorization": token}), ); await _dio.put( '$baseUrl/api/sega/sertjdffgh/4666drzzf', options: Options(headers: {"Authorization": token}), ); // 返回最终结果 if (res.data is Map) { return Map.from(res.data); } return {"code": 200, "msg": "success", "data": res.data}; } on DioException catch (e) { throw _getErrorMessage(e); } catch (e) { throw Exception("隐写或上传失败: $e"); } } }