You've already forked CC-Framework.BriskGameServer
Release v0.2.0 archive and space API update
This commit is contained in:
@@ -50,7 +50,7 @@ public sealed class BriskArchiveModule
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
var finalChecksum = string.IsNullOrWhiteSpace(checksum) ? ComputeSha256(bytes) : checksum;
|
||||
var finalChecksum = string.IsNullOrWhiteSpace(checksum) ? ComputeSha256(bytes) : NormalizeChecksum(checksum);
|
||||
var sections = new List<IMultipartFormSection>
|
||||
{
|
||||
new MultipartFormDataSection("base_version", (baseVersion ?? 0).ToString()),
|
||||
@@ -63,6 +63,24 @@ public sealed class BriskArchiveModule
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 UTF-8 文本形式上传指定槽位的存档。
|
||||
/// </summary>
|
||||
public Task<BriskArchiveUploadResult> UploadTextAsync(int slotNo, string text, int? baseVersion = null, string checksum = null)
|
||||
{
|
||||
RequireNotNull(text, nameof(text));
|
||||
return UploadAsync(slotNo, Encoding.UTF8.GetBytes(text), baseVersion, checksum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 JSON 文本形式上传指定槽位的存档。
|
||||
/// </summary>
|
||||
public Task<BriskArchiveUploadResult> UploadJsonAsync(int slotNo, object payload, int? baseVersion = null, string checksum = null)
|
||||
{
|
||||
RequireNotNull(payload, nameof(payload));
|
||||
return UploadAsync(slotNo, Encoding.UTF8.GetBytes(BriskJson.Serialize(payload)), baseVersion, checksum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载指定槽位的二进制存档。
|
||||
/// </summary>
|
||||
@@ -82,6 +100,24 @@ public sealed class BriskArchiveModule
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 UTF-8 文本形式下载指定槽位的存档。
|
||||
/// </summary>
|
||||
public async Task<string> DownloadTextAsync(int slotNo)
|
||||
{
|
||||
var result = await DownloadAsync(slotNo);
|
||||
return result == null || result.Bytes == null ? string.Empty : Encoding.UTF8.GetString(result.Bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 JSON 对象形式下载指定槽位的存档。
|
||||
/// </summary>
|
||||
public async Task<object> DownloadJsonAsync(int slotNo)
|
||||
{
|
||||
var text = await DownloadTextAsync(slotNo);
|
||||
return string.IsNullOrWhiteSpace(text) ? null : BriskJson.Deserialize(text);
|
||||
}
|
||||
|
||||
private static void ValidateSlotNo(int slotNo)
|
||||
{
|
||||
RequirePositive(slotNo, nameof(slotNo), "slotNo must be greater than 0.");
|
||||
@@ -92,8 +128,7 @@ public sealed class BriskArchiveModule
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
var hash = sha.ComputeHash(bytes);
|
||||
var builder = new StringBuilder(hash.Length * 2 + 7);
|
||||
builder.Append("sha256:");
|
||||
var builder = new StringBuilder(hash.Length * 2);
|
||||
for (var i = 0; i < hash.Length; i++)
|
||||
{
|
||||
builder.Append(hash[i].ToString("x2"));
|
||||
@@ -103,6 +138,19 @@ public sealed class BriskArchiveModule
|
||||
}
|
||||
}
|
||||
|
||||
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 int ReadHeaderInt(Dictionary<string, string> headers, string key)
|
||||
{
|
||||
var value = ReadHeader(headers, key);
|
||||
|
||||
@@ -51,23 +51,26 @@ public sealed class BriskHttpClient
|
||||
|
||||
public async Task<Dictionary<string, object>> PostMultipartAsync(string path, List<IMultipartFormSection> formSections, bool auth = false)
|
||||
{
|
||||
using (var request = UnityWebRequest.Post(BuildUrl(path, null), formSections))
|
||||
return await SendMultipartAsync(UnityWebRequest.kHttpVerbPOST, path, formSections, auth);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object>> PutMultipartAsync(string path, List<IMultipartFormSection> formSections, bool auth = false)
|
||||
{
|
||||
return await SendMultipartAsync(UnityWebRequest.kHttpVerbPUT, path, formSections, auth);
|
||||
}
|
||||
|
||||
public async Task<BriskBinaryResponse> GetBytesAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
using (var request = new UnityWebRequest(BuildUrl(path, query), UnityWebRequest.kHttpVerbGET))
|
||||
{
|
||||
request.downloadHandler = new DownloadHandlerBuffer();
|
||||
|
||||
if (auth)
|
||||
{
|
||||
AddAuthorizationHeader(request);
|
||||
}
|
||||
|
||||
request.SetRequestHeader("Accept", "application/json");
|
||||
await SendRequestAsync(request);
|
||||
return ParseEnvelope(request) as Dictionary<string, object> ?? new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BriskBinaryResponse> GetBytesAsync(string path, Dictionary<string, string> query = null, bool auth = false)
|
||||
{
|
||||
using (var request = BuildRequest(UnityWebRequest.kHttpVerbGET, path, query, null, auth))
|
||||
{
|
||||
request.SetRequestHeader("Accept", "*/*");
|
||||
await SendRequestAsync(request);
|
||||
EnsureSuccessOrThrow(request);
|
||||
return new BriskBinaryResponse
|
||||
@@ -93,6 +96,23 @@ public sealed class BriskHttpClient
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, object>> SendMultipartAsync(string method, string path, List<IMultipartFormSection> formSections, bool auth)
|
||||
{
|
||||
using (var request = UnityWebRequest.Post(BuildUrl(path, null), formSections))
|
||||
{
|
||||
request.method = method;
|
||||
|
||||
if (auth)
|
||||
{
|
||||
AddAuthorizationHeader(request);
|
||||
}
|
||||
|
||||
request.SetRequestHeader("Accept", "application/json");
|
||||
await SendRequestAsync(request);
|
||||
return ParseEnvelope(request) as Dictionary<string, object> ?? new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
private UnityWebRequest BuildRequest(string method, string path, Dictionary<string, string> query, object body, bool auth)
|
||||
{
|
||||
var url = BuildUrl(path, query);
|
||||
|
||||
@@ -237,10 +237,21 @@ internal static class BriskModelMapper
|
||||
|
||||
return new BriskSpaceView
|
||||
{
|
||||
ProjectAccountId = BriskValueReader.GetString(data, "project_account_id"),
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
LoginProvider = BriskValueReader.GetString(data, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(data, "login_user_id"),
|
||||
Payload = BriskValueReader.GetDictionary(data, "payload_json") ?? BriskValueReader.GetDictionary(data, "payload")
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url"),
|
||||
ContentExists = BriskValueReader.GetBool(data, "content_exists"),
|
||||
ContentVersion = BriskValueReader.GetLong(data, "content_version"),
|
||||
ContentType = BriskValueReader.GetString(data, "content_type"),
|
||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -253,8 +264,52 @@ internal static class BriskModelMapper
|
||||
|
||||
return new BriskSpaceStats
|
||||
{
|
||||
LikeCount = BriskValueReader.GetInt(data, "like_count"),
|
||||
VisitCount = BriskValueReader.GetInt(data, "visit_count")
|
||||
ProjectAccountId = BriskValueReader.GetString(data, "project_account_id"),
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
LoginProvider = BriskValueReader.GetString(data, "login_provider"),
|
||||
LoginUserId = BriskValueReader.GetString(data, "login_user_id"),
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url"),
|
||||
ContentExists = BriskValueReader.GetBool(data, "content_exists"),
|
||||
ContentVersion = BriskValueReader.GetLong(data, "content_version"),
|
||||
ContentType = BriskValueReader.GetString(data, "content_type"),
|
||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskSpaceContentUpdateResult ToSpaceContentUpdateResult(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceContentUpdateResult
|
||||
{
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
ContentVersion = BriskValueReader.GetLong(data, "content_version"),
|
||||
ContentType = BriskValueReader.GetString(data, "content_type"),
|
||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static BriskSpaceLikeResult ToSpaceLikeResult(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceLikeResult
|
||||
{
|
||||
Liked = BriskValueReader.GetBool(data, "liked"),
|
||||
LikeCount = BriskValueReader.GetLong(data, "like_count")
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,6 +339,30 @@ internal static class BriskModelMapper
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static BriskSpaceLikeItem ToSpaceLikeItem(Dictionary<string, object> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BriskSpaceLikeItem
|
||||
{
|
||||
PlayerId = BriskValueReader.GetString(data, "player_id"),
|
||||
Nickname = BriskValueReader.GetString(data, "nickname"),
|
||||
AvatarUrl = BriskValueReader.GetString(data, "avatar_url"),
|
||||
CreatedAt = BriskValueReader.GetString(data, "created_at")
|
||||
};
|
||||
}
|
||||
|
||||
public static List<BriskSpaceLikeItem> ToSpaceLikeItems(object data)
|
||||
{
|
||||
return ExtractList(data)
|
||||
.Select(item => ToSpaceLikeItem(item as Dictionary<string, object>))
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static Dictionary<string, object> ExtractObject(object data)
|
||||
{
|
||||
return data as Dictionary<string, object>;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
public sealed class BriskSpaceContentDownloadResult
|
||||
{
|
||||
public byte[] Bytes;
|
||||
public long Version;
|
||||
public string Checksum;
|
||||
public string ContentType;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6f27d1ba8b24f5e85362b9c6c9f4c01
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
public sealed class BriskSpaceContentUpdateResult
|
||||
{
|
||||
public string PlayerId;
|
||||
public long ContentVersion;
|
||||
public string ContentType;
|
||||
public long ContentSizeBytes;
|
||||
public string ContentChecksum;
|
||||
public string UpdatedAt;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bd67f378d264cc69c71f1d03d5b564b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
public sealed class BriskSpaceLikeItem
|
||||
{
|
||||
public string PlayerId;
|
||||
public string Nickname;
|
||||
public string AvatarUrl;
|
||||
public string CreatedAt;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef7d53b3f0ab43e7b74a4c23a76f84f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
public sealed class BriskSpaceLikeResult
|
||||
{
|
||||
public bool Liked;
|
||||
public long LikeCount;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 966f47c348db46dfb60120dba20f1ffb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,17 @@
|
||||
public sealed class BriskSpaceStats
|
||||
{
|
||||
public int LikeCount;
|
||||
public int VisitCount;
|
||||
public string ProjectAccountId;
|
||||
public string PlayerId;
|
||||
public string LoginProvider;
|
||||
public string LoginUserId;
|
||||
public string Nickname;
|
||||
public string AvatarUrl;
|
||||
public bool ContentExists;
|
||||
public long ContentVersion;
|
||||
public string ContentType;
|
||||
public long ContentSizeBytes;
|
||||
public string ContentChecksum;
|
||||
public long LikeCount;
|
||||
public long VisitCount;
|
||||
public string UpdatedAt;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
public sealed class BriskSpaceView
|
||||
{
|
||||
public string ProjectAccountId;
|
||||
public string PlayerId;
|
||||
public string LoginProvider;
|
||||
public string LoginUserId;
|
||||
public object Payload;
|
||||
public string Nickname;
|
||||
public string AvatarUrl;
|
||||
public bool ContentExists;
|
||||
public long ContentVersion;
|
||||
public string ContentType;
|
||||
public long ContentSizeBytes;
|
||||
public string ContentChecksum;
|
||||
public long LikeCount;
|
||||
public long VisitCount;
|
||||
public bool LikedByMe;
|
||||
public string UpdatedAt;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家空间模块。
|
||||
@@ -65,77 +68,179 @@ public sealed class BriskSpaceModule
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按玩家 ID 点赞空间。
|
||||
/// 按玩家 ID 获取最近点赞列表。
|
||||
/// </summary>
|
||||
public async Task LikeByPlayerIdAsync(string playerId)
|
||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20)
|
||||
{
|
||||
ValidatePlayerId(playerId);
|
||||
await ExecuteAsync(async context =>
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
await context.HttpClient.PostJsonRawAsync($"/spaces/{playerId}/like", new Dictionary<string, object>(), true);
|
||||
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLimitQuery(limit), true);
|
||||
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按登录身份获取最近点赞列表。
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> 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<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按玩家 ID 点赞空间。
|
||||
/// </summary>
|
||||
public async Task<BriskSpaceLikeResult> LikeByPlayerIdAsync(string playerId)
|
||||
{
|
||||
ValidatePlayerId(playerId);
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
var data = await context.HttpClient.PostJsonRawAsync($"/spaces/{playerId}/like", new Dictionary<string, object>(), true);
|
||||
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按玩家 ID 取消点赞空间。
|
||||
/// </summary>
|
||||
public async Task UnlikeByPlayerIdAsync(string playerId)
|
||||
public async Task<BriskSpaceLikeResult> UnlikeByPlayerIdAsync(string playerId)
|
||||
{
|
||||
ValidatePlayerId(playerId);
|
||||
await ExecuteAsync(async context =>
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
await context.HttpClient.SendDeleteJsonRawAsync($"/spaces/{playerId}/like", null, true);
|
||||
var data = await context.HttpClient.SendDeleteJsonRawAsync($"/spaces/{playerId}/like", null, true);
|
||||
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按登录身份点赞空间。
|
||||
/// </summary>
|
||||
public async Task LikeByLoginIdentityAsync(string loginProvider, string loginUserId)
|
||||
public async Task<BriskSpaceLikeResult> LikeByLoginIdentityAsync(string loginProvider, string loginUserId)
|
||||
{
|
||||
ValidateLoginIdentity(loginProvider, loginUserId);
|
||||
await ExecuteAsync(async context =>
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
await context.HttpClient.PostJsonRawAsync("/spaces/by-login/like", new Dictionary<string, object>(), true, CreateLoginIdentityQuery(loginProvider, loginUserId));
|
||||
var data = await context.HttpClient.PostJsonRawAsync("/spaces/by-login/like", new Dictionary<string, object>(), true, CreateLoginIdentityQuery(loginProvider, loginUserId));
|
||||
return BriskModelMapper.ToSpaceLikeResult(BriskModelMapper.ExtractObject(data));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按登录身份取消点赞空间。
|
||||
/// </summary>
|
||||
public async Task UnlikeByLoginIdentityAsync(string loginProvider, string loginUserId)
|
||||
public async Task<BriskSpaceLikeResult> UnlikeByLoginIdentityAsync(string loginProvider, string loginUserId)
|
||||
{
|
||||
ValidateLoginIdentity(loginProvider, loginUserId);
|
||||
await ExecuteAsync(async context =>
|
||||
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
await context.HttpClient.SendDeleteJsonRawAsync("/spaces/by-login/like", CreateLoginIdentityQuery(loginProvider, loginUserId), true);
|
||||
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 =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新当前玩家自己的空间内容。
|
||||
/// </summary>
|
||||
public async Task UpdateMyAsync(object payload)
|
||||
public Task<BriskSpaceContentUpdateResult> UpdateMyAsync(object payload, long? baseVersion = null, string contentType = null, string checksum = null)
|
||||
{
|
||||
RequireNotNull(payload, nameof(payload));
|
||||
|
||||
await ExecuteAsync(async context =>
|
||||
if (payload is byte[] bytes)
|
||||
{
|
||||
await context.HttpClient.SendPutJsonRawAsync(
|
||||
"/spaces/me",
|
||||
new Dictionary<string, object> { { "payload_json", payload } },
|
||||
true);
|
||||
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 =>
|
||||
{
|
||||
var response = await context.HttpClient.GetBytesAsync("/spaces/by-login/content", CreateLoginIdentityQuery(loginProvider, loginUserId), true);
|
||||
return CreateDownloadResult(response);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取我的访客列表。
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync()
|
||||
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 20)
|
||||
{
|
||||
return await ExecuteAsync(async context =>
|
||||
{
|
||||
var data = await context.HttpClient.GetRawDataAsync("/spaces/me/visits", null, true);
|
||||
var data = await context.HttpClient.GetRawDataAsync("/spaces/me/visits", CreateLimitQuery(limit), true);
|
||||
return (IReadOnlyList<BriskSpaceVisit>)BriskModelMapper.ToSpaceVisits(data);
|
||||
});
|
||||
}
|
||||
@@ -149,6 +254,14 @@ public sealed class BriskSpaceModule
|
||||
};
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> CreateLimitQuery(int limit)
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{ "limit", NormalizeLimit(limit) }
|
||||
};
|
||||
}
|
||||
|
||||
private static void ValidatePlayerId(string playerId)
|
||||
{
|
||||
RequireNotEmpty(playerId, nameof(playerId));
|
||||
@@ -159,4 +272,72 @@ public sealed class BriskSpaceModule
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user