using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using UnityEngine.Networking; /// /// 玩家空间模块。 /// public sealed class BriskSpaceModule : BriskModuleBase { /// /// 按玩家 ID 获取空间数据。 /// public async Task GetByPlayerIdAsync(string playerId) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetDataAsync($"/spaces/{playerId}", null, true); return BriskModelMapper.ToSpaceView(data); }); } /// /// 按登录身份获取空间数据。 /// public async Task GetByLoginIdentityAsync(string loginProvider, string loginUserId) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetDataAsync("/spaces/by-login", CreateLoginIdentityQuery(loginProvider, loginUserId), true); return BriskModelMapper.ToSpaceView(data); }); } /// /// 按玩家 ID 获取空间统计。 /// public async Task GetStatsByPlayerIdAsync(string playerId) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetDataAsync($"/spaces/{playerId}/stats", null, true); return BriskModelMapper.ToSpaceStats(data); }); } /// /// 按登录身份获取空间统计。 /// public async Task GetStatsByLoginIdentityAsync(string loginProvider, string loginUserId) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetDataAsync("/spaces/by-login/stats", CreateLoginIdentityQuery(loginProvider, loginUserId), true); return BriskModelMapper.ToSpaceStats(data); }); } /// /// 按玩家 ID 获取最近点赞列表。 /// public async Task> GetLikesByPlayerIdAsync(string playerId, int limit = 20) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLimitQuery(limit), true); return (IReadOnlyList)BriskModelMapper.ToSpaceLikeItems(data); }); } /// /// 按登录身份获取最近点赞列表。 /// public async Task> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var query = CreateLoginIdentityQuery(loginProvider, loginUserId); query["limit"] = NormalizeLimit(limit); var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true); return (IReadOnlyList)BriskModelMapper.ToSpaceLikeItems(data); }); } /// /// 按玩家 ID 点赞空间。 /// public async Task LikeByPlayerIdAsync(string playerId) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var data = await context.HttpClient.PostJsonRawAsync($"/spaces/{playerId}/like", new Dictionary(), true); return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data)); }); } /// /// 按玩家 ID 取消点赞空间。 /// public async Task UnlikeByPlayerIdAsync(string playerId) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var data = await context.HttpClient.SendDeleteJsonRawAsync($"/spaces/{playerId}/like", null, true); return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data)); }); } /// /// 按登录身份点赞空间。 /// public async Task LikeByLoginIdentityAsync(string loginProvider, string loginUserId) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var data = await context.HttpClient.PostJsonRawAsync("/spaces/by-login/like", new Dictionary(), true, CreateLoginIdentityQuery(loginProvider, loginUserId)); return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data)); }); } /// /// 按登录身份取消点赞空间。 /// public async Task UnlikeByLoginIdentityAsync(string loginProvider, string loginUserId) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var data = await context.HttpClient.SendDeleteJsonRawAsync("/spaces/by-login/like", CreateLoginIdentityQuery(loginProvider, loginUserId), true); return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data)); }); } /// /// 上传当前玩家自己的空间内容。 /// public async Task UploadMyContentAsync(long? baseVersion, string contentType, string checksum, byte[] bytes) { RequireNotNull(bytes, nameof(bytes)); return await ExecuteAsync(async context => { var finalChecksum = string.IsNullOrWhiteSpace(checksum) ? ComputeSha256(bytes) : NormalizeChecksum(checksum); var sections = new List { new MultipartFormDataSection("base_version", (baseVersion ?? 0L).ToString()), new MultipartFormFileSection("file", bytes, "space-content.bin", string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType) }; if (!string.IsNullOrWhiteSpace(contentType)) { sections.Insert(1, new MultipartFormDataSection("content_type", contentType)); } if (!string.IsNullOrWhiteSpace(finalChecksum)) { sections.Insert(string.IsNullOrWhiteSpace(contentType) ? 1 : 2, new MultipartFormDataSection("checksum", finalChecksum)); } var data = await context.HttpClient.PutMultipartAsync("/spaces/me/content", sections, true); return BriskModelMapper.ToSpaceContentUpdateResult(data); }); } /// /// 更新当前玩家自己的空间内容。 /// public Task UpdateMyAsync(object payload, long? baseVersion = null, string contentType = null, string checksum = null) { RequireNotNull(payload, nameof(payload)); if (payload is byte[] bytes) { return UploadMyContentAsync(baseVersion, contentType ?? "application/octet-stream", checksum, bytes); } if (payload is string text) { return UploadMyContentAsync(baseVersion, contentType ?? "text/plain", checksum, Encoding.UTF8.GetBytes(text)); } var json = BriskJson.Serialize(payload); return UploadMyContentAsync(baseVersion, contentType ?? "application/json", checksum, Encoding.UTF8.GetBytes(json)); } /// /// 按玩家 ID 下载空间内容。 /// public async Task DownloadContentByPlayerIdAsync(string playerId) { ValidatePlayerId(playerId); return await ExecuteAsync(async context => { var response = await context.HttpClient.GetBytesAsync($"/spaces/{playerId}/content", null, true); return CreateDownloadResult(response); }); } /// /// 按登录身份下载空间内容。 /// public async Task DownloadContentByLoginIdentityAsync(string loginProvider, string loginUserId) { ValidateLoginIdentity(loginProvider, loginUserId); return await ExecuteAsync(async context => { var response = await context.HttpClient.GetBytesAsync("/spaces/by-login/content", CreateLoginIdentityQuery(loginProvider, loginUserId), true); return CreateDownloadResult(response); }); } /// /// 获取我的访客列表。 /// public async Task> GetMyVisitsAsync(int limit = 20) { return await ExecuteAsync(async context => { var data = await context.HttpClient.GetRawDataAsync("/spaces/me/visits", CreateLimitQuery(limit), true); return (IReadOnlyList)BriskModelMapper.ToSpaceVisits(data); }); } private static Dictionary CreateLoginIdentityQuery(string loginProvider, string loginUserId) { return new Dictionary { { "login_provider", loginProvider }, { "login_user_id", loginUserId } }; } private static Dictionary CreateLimitQuery(int limit) { return new Dictionary { { "limit", NormalizeLimit(limit) } }; } private static void ValidatePlayerId(string playerId) { RequireNotEmpty(playerId, nameof(playerId)); } private static void ValidateLoginIdentity(string loginProvider, string loginUserId) { RequireNotEmpty(loginProvider, nameof(loginProvider)); RequireNotEmpty(loginUserId, nameof(loginUserId)); } private static string NormalizeLimit(int limit) { return (limit > 0 ? limit : 20).ToString(); } private static BriskSpaceContentDownloadResult CreateDownloadResult(BriskBinaryResponse response) { return new BriskSpaceContentDownloadResult { Bytes = response.Bytes, Version = ReadHeaderLong(response.Headers, "X-Space-Version"), Checksum = ReadHeader(response.Headers, "X-Space-Checksum"), ContentType = ReadHeader(response.Headers, "Content-Type") }; } private static string ComputeSha256(byte[] bytes) { using (var sha = SHA256.Create()) { var hash = sha.ComputeHash(bytes); var builder = new StringBuilder(hash.Length * 2); for (var i = 0; i < hash.Length; i++) { builder.Append(hash[i].ToString("x2")); } return builder.ToString(); } } private static string NormalizeChecksum(string checksum) { if (string.IsNullOrWhiteSpace(checksum)) { return checksum; } var value = checksum.Trim(); return value.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase) ? value.Substring("sha256:".Length) : value; } private static long ReadHeaderLong(Dictionary headers, string key) { var value = ReadHeader(headers, key); return long.TryParse(value, out var result) ? result : 0L; } private static string ReadHeader(Dictionary headers, string key) { if (headers == null || string.IsNullOrWhiteSpace(key)) { return null; } foreach (var pair in headers) { if (string.Equals(pair.Key, key, StringComparison.OrdinalIgnoreCase)) { return pair.Value; } } return null; } }