Files
CC-Framework.BriskGameServer/Assets/BriskSdk/Runtime/Space/BriskSpaceModule.cs

359 lines
12 KiB
C#
Raw Normal View History

2026-04-10 22:04:51 +08:00
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
2026-04-10 22:04:51 +08:00
using System.Threading.Tasks;
using UnityEngine.Networking;
2026-04-10 22:04:51 +08:00
/// <summary>
/// 玩家空间模块。
/// </summary>
2026-04-10 22:04:51 +08:00
public sealed class BriskSpaceModule
: BriskModuleBase
{
/// <summary>
/// 按玩家 ID 获取空间数据。
/// </summary>
2026-04-10 22:04:51 +08:00
public async Task<BriskSpaceView> GetByPlayerIdAsync(string playerId)
{
ValidatePlayerId(playerId);
return await ExecuteAsync(async context =>
{
var data = await context.HttpClient.GetDataAsync($"/spaces/{playerId}", null, true);
return BriskModelMapper.ToSpaceView(data);
});
}
/// <summary>
/// 按登录身份获取空间数据。
/// </summary>
2026-04-10 22:04:51 +08:00
public async Task<BriskSpaceView> 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);
});
}
/// <summary>
/// 按玩家 ID 获取空间统计。
/// </summary>
2026-04-10 22:04:51 +08:00
public async Task<BriskSpaceStats> 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);
});
}
/// <summary>
/// 按登录身份获取空间统计。
/// </summary>
2026-04-10 22:04:51 +08:00
public async Task<BriskSpaceStats> 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);
});
}
/// <summary>
/// 按玩家 ID 获取点赞列表。
/// </summary>
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20, bool currentCycleOnly = false)
{
ValidatePlayerId(playerId);
return await ExecuteAsync(async context =>
{
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLikesQuery(limit, currentCycleOnly), true);
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
});
}
/// <summary>
/// 按登录身份获取点赞列表。
/// </summary>
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20, bool currentCycleOnly = false)
{
ValidateLoginIdentity(loginProvider, loginUserId);
return await ExecuteAsync(async context =>
{
var query = CreateLoginIdentityQuery(loginProvider, loginUserId);
query["limit"] = NormalizeLimit(limit);
if (currentCycleOnly)
{
query["scope"] = "cycle";
}
var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true);
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
});
}
/// <summary>
/// 按玩家 ID 点赞空间。
/// </summary>
public async Task<BriskSpaceLikeResult> LikeByPlayerIdAsync(string playerId)
2026-04-10 22:04:51 +08:00
{
ValidatePlayerId(playerId);
return await ExecuteAsync(async context =>
2026-04-10 22:04:51 +08:00
{
var data = await context.HttpClient.PostJsonRawAsync($"/spaces/{playerId}/like", new Dictionary<string, object>(), true);
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
2026-04-10 22:04:51 +08:00
});
}
/// <summary>
/// 按玩家 ID 取消点赞空间。
/// </summary>
public async Task<BriskSpaceLikeResult> UnlikeByPlayerIdAsync(string playerId)
2026-04-10 22:04:51 +08:00
{
ValidatePlayerId(playerId);
return await ExecuteAsync(async context =>
2026-04-10 22:04:51 +08:00
{
var data = await context.HttpClient.SendDeleteJsonRawAsync($"/spaces/{playerId}/like", null, true);
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
2026-04-10 22:04:51 +08:00
});
}
/// <summary>
/// 按登录身份点赞空间。
/// </summary>
public async Task<BriskSpaceLikeResult> LikeByLoginIdentityAsync(string loginProvider, string loginUserId)
2026-04-10 22:04:51 +08:00
{
ValidateLoginIdentity(loginProvider, loginUserId);
return await ExecuteAsync(async context =>
2026-04-10 22:04:51 +08:00
{
var data = await context.HttpClient.PostJsonRawAsync("/spaces/by-login/like", new Dictionary<string, object>(), true, CreateLoginIdentityQuery(loginProvider, loginUserId));
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
2026-04-10 22:04:51 +08:00
});
}
/// <summary>
/// 按登录身份取消点赞空间。
/// </summary>
public async Task<BriskSpaceLikeResult> UnlikeByLoginIdentityAsync(string loginProvider, string loginUserId)
2026-04-10 22:04:51 +08:00
{
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));
});
}
/// <summary>
/// 上传当前玩家自己的空间内容。
/// </summary>
public async Task<BriskSpaceContentUpdateResult> UploadMyContentAsync(long? baseVersion, string contentType, string checksum, byte[] bytes)
{
RequireNotNull(bytes, nameof(bytes));
return await ExecuteAsync(async context =>
2026-04-10 22:04:51 +08:00
{
var finalChecksum = string.IsNullOrWhiteSpace(checksum) ? ComputeSha256(bytes) : NormalizeChecksum(checksum);
var sections = new List<IMultipartFormSection>
{
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);
2026-04-10 22:04:51 +08:00
});
}
/// <summary>
/// 更新当前玩家自己的空间内容。
/// </summary>
public Task<BriskSpaceContentUpdateResult> UpdateMyAsync(object payload, long? baseVersion = null, string contentType = null, string checksum = null)
2026-04-10 22:04:51 +08:00
{
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));
}
/// <summary>
/// 按玩家 ID 下载空间内容。
/// </summary>
public async Task<BriskSpaceContentDownloadResult> DownloadContentByPlayerIdAsync(string playerId)
{
ValidatePlayerId(playerId);
return await ExecuteAsync(async context =>
{
var response = await context.HttpClient.GetBytesAsync($"/spaces/{playerId}/content", null, true);
return CreateDownloadResult(response);
});
}
/// <summary>
/// 按登录身份下载空间内容。
/// </summary>
public async Task<BriskSpaceContentDownloadResult> DownloadContentByLoginIdentityAsync(string loginProvider, string loginUserId)
{
ValidateLoginIdentity(loginProvider, loginUserId);
return await ExecuteAsync(async context =>
2026-04-10 22:04:51 +08:00
{
var response = await context.HttpClient.GetBytesAsync("/spaces/by-login/content", CreateLoginIdentityQuery(loginProvider, loginUserId), true);
return CreateDownloadResult(response);
2026-04-10 22:04:51 +08:00
});
}
/// <summary>
/// 获取我的访客列表。
/// </summary>
2026-04-21 17:07:45 +08:00
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 50)
2026-04-10 22:04:51 +08:00
{
return await ExecuteAsync(async context =>
{
var data = await context.HttpClient.GetRawDataAsync("/spaces/me/visits", CreateLimitQuery(limit), true);
2026-04-10 22:04:51 +08:00
return (IReadOnlyList<BriskSpaceVisit>)BriskModelMapper.ToSpaceVisits(data);
});
}
private static Dictionary<string, string> CreateLoginIdentityQuery(string loginProvider, string loginUserId)
{
return new Dictionary<string, string>
{
{ "login_provider", loginProvider },
{ "login_user_id", loginUserId }
};
}
private static Dictionary<string, string> CreateLimitQuery(int limit)
{
return new Dictionary<string, string>
{
{ "limit", NormalizeLimit(limit) }
};
}
private static Dictionary<string, string> CreateLikesQuery(int limit, bool currentCycleOnly)
{
var query = CreateLimitQuery(limit);
if (currentCycleOnly)
{
query["scope"] = "cycle";
}
return query;
}
2026-04-10 22:04:51 +08:00
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<string, string> headers, string key)
{
var value = ReadHeader(headers, key);
return long.TryParse(value, out var result) ? result : 0L;
}
private static string ReadHeader(Dictionary<string, string> 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;
}
2026-04-10 22:04:51 +08:00
}