Files
UnionApp/lib/service/user_service.dart
spasolreisa c5c3f7c8f5 ver1.00.00
bindQR
2026-04-19 22:22:04 +08:00

420 lines
12 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 '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<void> 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<String> 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<UserModel> 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<String> 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<bool> 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<String?> 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<String?> 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<List<String>> fetchSexTags() async {
try {
final res = await _dio.get('$baseUrl/api/union/sextag');
if (res.data is List) {
return List<String>.from(res.data);
}
return [];
} on DioException {
return [];
}
}
// 获取好友列表
static Future<List<dynamic>> 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<void> 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<String> 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<void> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<String, dynamic>.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<Map<String, dynamic>> getUserAllScores(String token, {String? name}) async {
try {
// 构建查询参数
final queryParameters = <String, dynamic>{};
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<String, dynamic>.from(res.data);
}
return {};
} on DioException catch (e) {
throw _getErrorMessage(e);
}
}
static Future<Map<String, dynamic>> 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<String, dynamic>.from(res.data);
} on DioException catch (e) {
throw _getErrorMessage(e);
}
}
// 上传 Sega 成绩数据
static Future<Map<String, dynamic>> uploadSegaRating(
String token,
String segaId,
Map<String, dynamic> segaResult,
) async {
try {
final res = await _dio.post(
'$baseUrl/api/union/segaReisaRating',
queryParameters: {
"segaId": segaId,
},
data: segaResult,
options: Options(
headers: {
"Authorization": token,
},
),
);
return Map<String, dynamic>.from(res.data);
} on DioException catch (e) {
throw _getErrorMessage(e);
}
}
static Future<List<ChartLogModel>> getChartLog(String token, List<int> 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<String, dynamic>))
.toList();
}
// 如果后端在某些错误情况下返回 Map 而不是 List这里返回空列表或抛出异常
return [];
} on DioException catch (e) {
throw _getErrorMessage(e);
}
}
/// 上传隐写图片
/// [token] 用户令牌
/// [segaId] 要隐藏的 Sega ID
/// [originalImageBytes] 原始图片字节数据 (Uint8List)
static Future<Map<String, dynamic>> 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<String, dynamic>.from(res.data);
}
return {"code": 200, "msg": "success", "data": res.data};
} on DioException catch (e) {
throw _getErrorMessage(e);
} catch (e) {
throw Exception("隐写或上传失败: $e");
}
}
}