From a4eb9dfa0cb1aed32b96c3da11bdbaf832c3d1fd Mon Sep 17 00:00:00 2001 From: oneRain Date: Thu, 7 May 2020 10:56:45 +0800 Subject: [PATCH 1/4] chore --- Storage/Storage.Test/QueryTest.cs | 4 +++- Storage/Storage/Leaderboard/LCLeaderboard.cs | 7 +++++++ Storage/Storage/Leaderboard/LCLeaderboardArchive.cs | 7 +++++++ Storage/Storage/Leaderboard/LCRanking.cs | 7 +++++++ Storage/Storage/Leaderboard/LCStatistic.cs | 7 +++++++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Storage/Storage/Leaderboard/LCLeaderboard.cs create mode 100644 Storage/Storage/Leaderboard/LCLeaderboardArchive.cs create mode 100644 Storage/Storage/Leaderboard/LCRanking.cs create mode 100644 Storage/Storage/Leaderboard/LCStatistic.cs diff --git a/Storage/Storage.Test/QueryTest.cs b/Storage/Storage.Test/QueryTest.cs index 27d3a89..5c7a9b1 100644 --- a/Storage/Storage.Test/QueryTest.cs +++ b/Storage/Storage.Test/QueryTest.cs @@ -334,7 +334,9 @@ namespace Storage.Test { Assert.Greater(hellos.Count, 0); foreach (LCObject item in hellos) { LCObject world = item["objectValue"] as LCObject; - Assert.IsTrue(world == null || world["content"] != "7788"); + Assert.IsTrue(world == null || + world["content"] == null || + world["content"] as string != "7788"); } } } diff --git a/Storage/Storage/Leaderboard/LCLeaderboard.cs b/Storage/Storage/Leaderboard/LCLeaderboard.cs new file mode 100644 index 0000000..3bccca9 --- /dev/null +++ b/Storage/Storage/Leaderboard/LCLeaderboard.cs @@ -0,0 +1,7 @@ +using System; +namespace LeanCloud.Storage.Leaderboard { + public class LCLeaderboard { + public LCLeaderboard() { + } + } +} diff --git a/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs b/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs new file mode 100644 index 0000000..ca58837 --- /dev/null +++ b/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs @@ -0,0 +1,7 @@ +using System; +namespace LeanCloud.Storage.Leaderboard { + public class LCLeaderboardArchive { + public LCLeaderboardArchive() { + } + } +} diff --git a/Storage/Storage/Leaderboard/LCRanking.cs b/Storage/Storage/Leaderboard/LCRanking.cs new file mode 100644 index 0000000..58808db --- /dev/null +++ b/Storage/Storage/Leaderboard/LCRanking.cs @@ -0,0 +1,7 @@ +using System; +namespace LeanCloud.Storage.Leaderboard { + public class LCRanking { + public LCRanking() { + } + } +} diff --git a/Storage/Storage/Leaderboard/LCStatistic.cs b/Storage/Storage/Leaderboard/LCStatistic.cs new file mode 100644 index 0000000..e60d0ae --- /dev/null +++ b/Storage/Storage/Leaderboard/LCStatistic.cs @@ -0,0 +1,7 @@ +using System; +namespace LeanCloud.Storage.Leaderboard { + public class LCStatistic { + public LCStatistic() { + } + } +} From 1171f4b0de4a56d68cbcf1a732df0102aaeacb5d Mon Sep 17 00:00:00 2001 From: oneRain Date: Thu, 7 May 2020 15:25:12 +0800 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20=E6=94=AF=E6=8C=81=20master=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Storage/LCApplication.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Storage/Storage/LCApplication.cs b/Storage/Storage/LCApplication.cs index 2eb9168..a49cc23 100644 --- a/Storage/Storage/LCApplication.cs +++ b/Storage/Storage/LCApplication.cs @@ -22,6 +22,10 @@ namespace LeanCloud { get; private set; } + public static string MasterKey { + get; private set; + } + public static bool UseProduction { get; set; } @@ -34,7 +38,14 @@ namespace LeanCloud { get; private set; } - public static void Initialize(string appId, string appKey, string server = null) { + public static bool UseMasterKey { + get; set; + } + + public static void Initialize(string appId, + string appKey, + string masterKey = null, + string server = null) { if (string.IsNullOrEmpty(appId)) { throw new ArgumentException(nameof(appId)); } @@ -44,6 +55,7 @@ namespace LeanCloud { AppId = appId; AppKey = appKey; + MasterKey = masterKey; // 注册 LeanCloud 内部子类化类型 LCObject.RegisterSubclass(LCUser.CLASS_NAME, () => new LCUser()); From 7d87b51aa448dabeea4835ba7cf9440702e98e91 Mon Sep 17 00:00:00 2001 From: oneRain Date: Thu, 7 May 2020 15:47:41 +0800 Subject: [PATCH 3/4] chore --- Storage/Storage/LCApplication.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Storage/Storage/LCApplication.cs b/Storage/Storage/LCApplication.cs index a49cc23..1b81e6c 100644 --- a/Storage/Storage/LCApplication.cs +++ b/Storage/Storage/LCApplication.cs @@ -44,8 +44,8 @@ namespace LeanCloud { public static void Initialize(string appId, string appKey, - string masterKey = null, - string server = null) { + string server = null, + string masterKey = null) { if (string.IsNullOrEmpty(appId)) { throw new ArgumentException(nameof(appId)); } From 933d4dd0361e818c986d5eb8140669d93b241450 Mon Sep 17 00:00:00 2001 From: oneRain Date: Thu, 7 May 2020 15:49:49 +0800 Subject: [PATCH 4/4] =?UTF-8?q?chore:=20=E6=94=AF=E6=8C=81=E6=8E=92?= =?UTF-8?q?=E8=A1=8C=E6=A6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Storage-Unity/Storage-Unity.csproj | 12 + Storage/Storage.Test/LeaderboardTest.cs | 116 +++++ Storage/Storage.Test/Utils.cs | 1 + Storage/Storage/Internal/Codec/LCDecoder.cs | 10 +- Storage/Storage/Internal/Codec/LCEncoder.cs | 18 +- Storage/Storage/Internal/Http/LCHttpClient.cs | 21 +- Storage/Storage/LCUser.cs | 2 +- Storage/Storage/Leaderboard/LCLeaderboard.cs | 451 +++++++++++++++++- .../Leaderboard/LCLeaderboardArchive.cs | 74 ++- Storage/Storage/Leaderboard/LCRanking.cs | 70 ++- Storage/Storage/Leaderboard/LCStatistic.cs | 41 +- Storage/Storage/Storage.csproj | 1 + 12 files changed, 786 insertions(+), 31 deletions(-) create mode 100644 Storage/Storage.Test/LeaderboardTest.cs diff --git a/Storage/Storage-Unity/Storage-Unity.csproj b/Storage/Storage-Unity/Storage-Unity.csproj index 1151018..7f8ff6f 100644 --- a/Storage/Storage-Unity/Storage-Unity.csproj +++ b/Storage/Storage-Unity/Storage-Unity.csproj @@ -123,6 +123,18 @@ LCSMSClient.cs + + Leaderboard\LCLeaderboardArchive.cs + + + Leaderboard\LCStatistic.cs + + + Leaderboard\LCLeaderboard.cs + + + Leaderboard\LCRanking.cs + diff --git a/Storage/Storage.Test/LeaderboardTest.cs b/Storage/Storage.Test/LeaderboardTest.cs new file mode 100644 index 0000000..97cf386 --- /dev/null +++ b/Storage/Storage.Test/LeaderboardTest.cs @@ -0,0 +1,116 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using LeanCloud; +using LeanCloud.Storage; + +using static NUnit.Framework.TestContext; + +namespace Storage.Test { + public class LeaderboardTest { + private string leaderboardName; + + [SetUp] + public void SetUp() { + LCLogger.LogDelegate += Utils.Print; + LCApplication.Initialize(Utils.AppId, Utils.AppKey, Utils.AppServer, Utils.MasterKey); + LCApplication.UseMasterKey = true; + leaderboardName = $"Leaderboard_{DateTimeOffset.Now.DayOfYear}"; + } + + [TearDown] + public void TearDown() { + LCApplication.UseMasterKey = false; + LCLogger.LogDelegate -= Utils.Print; + } + + [Test] + [Order(0)] + public async Task Create() { + try { + LCLeaderboard oldLeaderboard = LCLeaderboard.CreateWithoutData(leaderboardName); + await oldLeaderboard.Destroy(); + } catch (Exception e) { + WriteLine(e.Message); + } + LCLeaderboard leaderboard = await LCLeaderboard.CreateLeaderboard(leaderboardName); + Assert.AreEqual(leaderboard.StatisticName, leaderboardName); + } + + [Test] + [Order(1)] + public async Task Update() { + for (int i = 0; i < 10; i++) { + int today = DateTimeOffset.Now.DayOfYear; + string username = $"{today}_{i}"; + string password = "leancloud"; + LCUser user; + try { + user = await LCUser.Login(username, password); + } catch (Exception) { + user = new LCUser { + Username = username, + Password = password + }; + await user.SignUp(); + } + await LCLeaderboard.UpdateStatistics(user, new Dictionary { + { leaderboardName, i * 10 } + }); + } + } + + [Test] + [Order(2)] + public async Task GetStatistics() { + int today = DateTimeOffset.Now.DayOfYear; + string username = $"{today}_0"; + string password = "leancloud"; + LCUser user = await LCUser.Login(username, password); + ReadOnlyCollection statistics = await LCLeaderboard.GetStatistics(user); + foreach (LCStatistic statistic in statistics) { + WriteLine($"{statistic.Name} : {statistic.Value}"); + } + } + + [Test] + [Order(3)] + public async Task GetResults() { + LCLeaderboard leaderboard = LCLeaderboard.CreateWithoutData(leaderboardName); + ReadOnlyCollection rankings = await leaderboard.GetResults(); + foreach (LCRanking ranking in rankings) { + WriteLine($"{ranking.Rank} : {ranking.User.ObjectId}, {ranking.Value}"); + } + } + + [Test] + [Order(4)] + public async Task GetResultsOfMe() { + int today = DateTimeOffset.Now.DayOfYear; + string username = $"{today}_0"; + string password = "leancloud"; + await LCUser.Login(username, password); + LCLeaderboard leaderboard = LCLeaderboard.CreateWithoutData(leaderboardName); + ReadOnlyCollection rankings = await leaderboard.GetResultsAroundUser(limit: 5); + foreach (LCRanking ranking in rankings) { + WriteLine($"{ranking.Rank} : {ranking.User.ObjectId}, {ranking.Value}"); + } + } + + [Test] + [Order(5)] + public async Task GetOtherStatistics() { + int today = DateTimeOffset.Now.DayOfYear; + string username = $"{today}_0"; + string password = "leancloud"; + LCUser user = await LCUser.Login(username, password); + await LCUser.Login($"{today}_1", password); + ReadOnlyCollection statistics = await LCLeaderboard.GetStatistics(user); + foreach (LCStatistic statistic in statistics) { + WriteLine($"{statistic.Name}, {statistic.Value}"); + } + } + } +} diff --git a/Storage/Storage.Test/Utils.cs b/Storage/Storage.Test/Utils.cs index 3876751..7a58982 100644 --- a/Storage/Storage.Test/Utils.cs +++ b/Storage/Storage.Test/Utils.cs @@ -5,6 +5,7 @@ namespace Storage.Test { public static class Utils { internal const string AppId = "ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz"; internal const string AppKey = "NUKmuRbdAhg1vrb2wexYo1jo"; + internal const string MasterKey = "pyvbNSh5jXsuFQ3C8EgnIdhw"; internal const string AppServer = "https://ikggdre2.lc-cn-n1-shared.com"; internal static void Print(LCLogLevel level, string info) { diff --git a/Storage/Storage/Internal/Codec/LCDecoder.cs b/Storage/Storage/Internal/Codec/LCDecoder.cs index b5acfa2..f9dd00a 100644 --- a/Storage/Storage/Internal/Codec/LCDecoder.cs +++ b/Storage/Storage/Internal/Codec/LCDecoder.cs @@ -41,19 +41,19 @@ namespace LeanCloud.Storage.Internal.Codec { return obj; } - static DateTime DecodeDate(IDictionary dict) { + public static DateTime DecodeDate(IDictionary dict) { string str = dict["iso"].ToString(); DateTime dateTime = DateTime.Parse(str); return dateTime.ToLocalTime(); } - static byte[] DecodeBytes(IDictionary dict) { + public static byte[] DecodeBytes(IDictionary dict) { string str = dict["base64"].ToString(); byte[] bytes = Convert.FromBase64String(str); return bytes; } - static LCObject DecodeObject(IDictionary dict) { + public static LCObject DecodeObject(IDictionary dict) { string className = dict["className"].ToString(); LCObject obj = LCObject.Create(className); LCObjectData objectData = LCObjectData.Decode(dict as Dictionary); @@ -61,13 +61,13 @@ namespace LeanCloud.Storage.Internal.Codec { return obj; } - static LCRelation DecodeRelation(IDictionary dict) { + public static LCRelation DecodeRelation(IDictionary dict) { LCRelation relation = new LCRelation(); relation.TargetClass = dict["className"].ToString(); return relation; } - static LCGeoPoint DecodeGeoPoint(IDictionary data) { + public static LCGeoPoint DecodeGeoPoint(IDictionary data) { double latitude = double.Parse(data["latitude"].ToString()); double longitude = double.Parse(data["longitude"].ToString()); LCGeoPoint geoPoint = new LCGeoPoint(latitude, longitude); diff --git a/Storage/Storage/Internal/Codec/LCEncoder.cs b/Storage/Storage/Internal/Codec/LCEncoder.cs index 5af75bf..383247b 100644 --- a/Storage/Storage/Internal/Codec/LCEncoder.cs +++ b/Storage/Storage/Internal/Codec/LCEncoder.cs @@ -31,7 +31,7 @@ namespace LeanCloud.Storage.Internal.Codec { return obj; } - static object EncodeDateTime(DateTime dateTime) { + public static object EncodeDateTime(DateTime dateTime) { DateTime utc = dateTime.ToUniversalTime(); string str = utc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); return new Dictionary { @@ -40,7 +40,7 @@ namespace LeanCloud.Storage.Internal.Codec { }; } - static object EncodeBytes(byte[] bytes) { + public static object EncodeBytes(byte[] bytes) { string str = Convert.ToBase64String(bytes); return new Dictionary { { "__type", "Bytes" }, @@ -48,7 +48,7 @@ namespace LeanCloud.Storage.Internal.Codec { }; } - static object EncodeList(IList list) { + public static object EncodeList(IList list) { List l = new List(); foreach (object obj in list) { l.Add(Encode(obj)); @@ -56,7 +56,7 @@ namespace LeanCloud.Storage.Internal.Codec { return l; } - static object EncodeDictionary(IDictionary dict) { + public static object EncodeDictionary(IDictionary dict) { Dictionary d = new Dictionary(); foreach (DictionaryEntry entry in dict) { string key = entry.Key.ToString(); @@ -66,7 +66,7 @@ namespace LeanCloud.Storage.Internal.Codec { return d; } - static object EncodeLCObject(LCObject obj) { + public static object EncodeLCObject(LCObject obj) { return new Dictionary { { "__type", "Pointer" }, { "className", obj.ClassName }, @@ -78,11 +78,11 @@ namespace LeanCloud.Storage.Internal.Codec { return operation.Encode(); } - static object EncodeQueryCondition(ILCQueryCondition cond) { + public static object EncodeQueryCondition(ILCQueryCondition cond) { return cond.Encode(); } - static object EncodeACL(LCACL acl) { + public static object EncodeACL(LCACL acl) { HashSet readers = acl.readers; HashSet writers = acl.writers; HashSet union = new HashSet(readers); @@ -97,14 +97,14 @@ namespace LeanCloud.Storage.Internal.Codec { return dict; } - static object EncodeRelation(LCRelation relation) { + public static object EncodeRelation(LCRelation relation) { return new Dictionary { { "__type", "Relation" }, { "className", relation.TargetClass } }; } - static object EncodeGeoPoint(LCGeoPoint geoPoint) { + public static object EncodeGeoPoint(LCGeoPoint geoPoint) { return new Dictionary { { "__type", "GeoPoint" }, { "latitude", geoPoint.Latitude }, diff --git a/Storage/Storage/Internal/Http/LCHttpClient.cs b/Storage/Storage/Internal/Http/LCHttpClient.cs index 9e9e306..af4eb76 100644 --- a/Storage/Storage/Internal/Http/LCHttpClient.cs +++ b/Storage/Storage/Internal/Http/LCHttpClient.cs @@ -69,7 +69,7 @@ namespace LeanCloud.Storage.Internal.Http { public async Task Post(string path, Dictionary headers = null, - Dictionary data = null, + object data = null, Dictionary queryParams = null) { string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { @@ -102,7 +102,7 @@ namespace LeanCloud.Storage.Internal.Http { public async Task Put(string path, Dictionary headers = null, - Dictionary data = null, + object data = null, Dictionary queryParams = null) { string url = await BuildUrl(path, queryParams); HttpRequestMessage request = new HttpRequestMessage { @@ -188,12 +188,17 @@ namespace LeanCloud.Storage.Internal.Http { headers.Add(kv.Key, kv.Value.ToString()); } } - // 签名 - long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - string data = $"{timestamp}{appKey}"; - string hash = GetMd5Hash(md5, data); - string sign = $"{hash},{timestamp}"; - headers.Add("X-LC-Sign", sign); + if (LCApplication.UseMasterKey && !string.IsNullOrEmpty(LCApplication.MasterKey)) { + // Master Key + headers.Add("X-LC-Key", $"{LCApplication.MasterKey},master"); + } else { + // 签名 + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + string data = $"{timestamp}{appKey}"; + string hash = GetMd5Hash(md5, data); + string sign = $"{hash},{timestamp}"; + headers.Add("X-LC-Sign", sign); + } // 当前用户 Session Token LCUser currentUser = await LCUser.GetCurrent(); if (currentUser != null) { diff --git a/Storage/Storage/LCUser.cs b/Storage/Storage/LCUser.cs index 7af2457..df51104 100644 --- a/Storage/Storage/LCUser.cs +++ b/Storage/Storage/LCUser.cs @@ -85,7 +85,7 @@ namespace LeanCloud.Storage { } - LCUser(LCObjectData objectData) : this() { + internal LCUser(LCObjectData objectData) : this() { Merge(objectData); } diff --git a/Storage/Storage/Leaderboard/LCLeaderboard.cs b/Storage/Storage/Leaderboard/LCLeaderboard.cs index 3bccca9..75c360a 100644 --- a/Storage/Storage/Leaderboard/LCLeaderboard.cs +++ b/Storage/Storage/Leaderboard/LCLeaderboard.cs @@ -1,7 +1,454 @@ using System; -namespace LeanCloud.Storage.Leaderboard { +using System.Collections; +using System.Linq; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Collections.Generic; +using LeanCloud.Storage.Internal.Codec; + +namespace LeanCloud.Storage { + /// + /// 排行榜顺序 + /// + public enum LCLeaderboardOrder { + /// + /// 升序 + /// + Ascending, + /// + /// 降序 + /// + Descending + } + + /// + /// 排行榜更新策略 + /// + public enum LCLeaderboardUpdateStrategy { + /// + /// 更好的 + /// + Better, + /// + /// 最近的 + /// + Last, + /// + /// 总和 + /// + Sum + } + + /// + /// 排行榜刷新频率 + /// + public enum LCLeaderboardVersionChangeInterval { + /// + /// 从不 + /// + Never, + /// + /// 每天 + /// + Day, + /// + /// 每周 + /// + Week, + /// + /// 每月 + /// + Month + } + + /// + /// 排行榜 + /// public class LCLeaderboard { - public LCLeaderboard() { + /// + /// 成绩名字 + /// + public string StatisticName { + get; private set; + } + + /// + /// 排名顺序 + /// + public LCLeaderboardOrder Order { + get; private set; + } + + /// + /// 排名更新策略 + /// + public LCLeaderboardUpdateStrategy UpdateStrategy { + get; private set; + } + + /// + /// 版本更新频率 + /// + public LCLeaderboardVersionChangeInterval VersionChangeInterval { + get; private set; + } + + /// + /// 版本号 + /// + public int Version { + get; private set; + } + + /// + /// 下次重置时间 + /// + public DateTime NextResetAt { + get; private set; + } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { + get; private set; + } + + /// + /// 创建排行榜 + /// + /// + /// + /// + /// + /// + public static async Task CreateLeaderboard(string statisticName, + LCLeaderboardOrder order = LCLeaderboardOrder.Descending, + LCLeaderboardUpdateStrategy updateStrategy = LCLeaderboardUpdateStrategy.Better, + LCLeaderboardVersionChangeInterval versionChangeInterval = LCLeaderboardVersionChangeInterval.Week) { + if (string.IsNullOrEmpty(statisticName)) { + throw new ArgumentNullException(nameof(statisticName)); + } + Dictionary data = new Dictionary { + { "statisticName", statisticName }, + { "order", order.ToString().ToLower() }, + { "versionChangeInterval", versionChangeInterval.ToString().ToLower() }, + { "updateStrategy", updateStrategy.ToString().ToLower() }, + }; + string path = "leaderboard/leaderboards"; + Dictionary result = await LCApplication.HttpClient.Post>(path, + data:data); + LCLeaderboard leaderboard = new LCLeaderboard(); + leaderboard.Merge(result); + return leaderboard; + } + + /// + /// 创建只包含名称的排行榜对象 + /// + /// + /// + public static LCLeaderboard CreateWithoutData(string statisticName) { + if (string.IsNullOrEmpty(statisticName)) { + throw new ArgumentNullException(nameof(statisticName)); + } + return new LCLeaderboard { + StatisticName = statisticName + }; + } + + /// + /// 获取排行榜 + /// + /// + /// + public static Task GetLeaderboard(string statisticName) { + LCLeaderboard leaderboard = CreateWithoutData(statisticName); + return leaderboard.Fetch(); + } + + /// + /// 更新用户成绩 + /// + /// + /// + /// + /// + public static async Task> UpdateStatistics(LCUser user, + Dictionary statistics, + bool overwrite = true) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + if (statistics == null || statistics.Count == 0) { + throw new ArgumentNullException(nameof(statistics)); + } + List> data = statistics.Select(statistic => new Dictionary { + { "statisticName", statistic.Key }, + { "statisticValue", statistic.Value }, + }).ToList(); + string path = $"leaderboard/users/{user.ObjectId}/statistics"; + if (overwrite) { + path = $"{path}?overwrite=1"; + } + Dictionary result = await LCApplication.HttpClient.Post>(path, + data: data); + if (result.TryGetValue("results", out object results) && + results is List list) { + List statisticList = new List(); + foreach (object item in list) { + LCStatistic statistic = LCStatistic.Parse(item as IDictionary); + statisticList.Add(statistic); + } + return statisticList.AsReadOnly(); + } + return null; + } + + /// + /// 获得用户成绩 + /// + /// + /// + /// + public static async Task> GetStatistics(LCUser user, + IEnumerable statisticNames = null) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + string path = $"leaderboard/users/{user.ObjectId}/statistics"; + if (statisticNames != null && statisticNames.Count() > 0) { + string names = string.Join(",", statisticNames); + path = $"{path}?statistics={names}"; + } + Dictionary result = await LCApplication.HttpClient.Get>(path); + if (result.TryGetValue("results", out object results) && + results is List list) { + List statistics = new List(); + foreach (object item in list) { + LCStatistic statistic = LCStatistic.Parse(item as Dictionary); + statistics.Add(statistic); + } + return statistics.AsReadOnly(); + } + return null; + } + + /// + /// 删除用户成绩 + /// + /// + /// + /// + public static async Task DeleteStatistics(LCUser user, + IEnumerable statisticNames) { + if (user == null) { + throw new ArgumentNullException(nameof(user)); + } + if (statisticNames == null || statisticNames.Count() == 0) { + throw new ArgumentNullException(nameof(statisticNames)); + } + string names = string.Join(",", statisticNames); + string path = $"leaderboard/users/{user.ObjectId}/statistics?statistics={names}"; + await LCApplication.HttpClient.Delete(path); + } + + /// + /// 获取排行榜历史数据 + /// + /// + /// + /// + public async Task> GetArchives(int skip = 0, + int limit = 10) { + if (skip < 0) { + throw new ArgumentOutOfRangeException(nameof(skip)); + } + if (limit <= 0) { + throw new ArgumentOutOfRangeException(nameof(limit)); + } + string path = $"leaderboard/leaderboards/{StatisticName}/archives?skip={skip}&limit={limit}"; + Dictionary result = await LCApplication.HttpClient.Get>(path); + if (result.TryGetValue("results", out object results) && + results is List list) { + List archives = new List(); + foreach (object item in list) { + if (item is IDictionary dict) { + LCLeaderboardArchive archive = LCLeaderboardArchive.Parse(dict); + archives.Add(archive); + } + } + return archives.AsReadOnly(); + } + return null; + } + + /// + /// 获取排行榜结果 + /// + /// + /// + /// + /// + /// + /// + public Task> GetResults(int version = -1, + int skip = 0, + int limit = 10, + IEnumerable selectUserKeys = null, + IEnumerable includeStatistics = null) { + return GetResults(null, version, skip, limit, selectUserKeys, includeStatistics); + } + + /// + /// 获取用户及附近的排名 + /// + /// + /// + /// + /// + /// + /// + public async Task> GetResultsAroundUser(int version = -1, + int skip = 0, + int limit = 10, + IEnumerable selectUserKeys = null, + IEnumerable includeStatistics = null) { + LCUser user = await LCUser.GetCurrent(); + return await GetResults(user, version, skip, limit, selectUserKeys, includeStatistics); + } + + private async Task> GetResults(LCUser user, + int version, + int skip, + int limit, + IEnumerable selectUserKeys, + IEnumerable includeStatistics) { + string path = $"leaderboard/leaderboards/{StatisticName}/ranks"; + if (user != null) { + path = $"{path}/{user.ObjectId}"; + } + path = $"{path}?skip={skip}&limit={limit}"; + if (version != -1) { + path = $"{path}&version={version}"; + } + if (selectUserKeys != null) { + string keys = string.Join(",", selectUserKeys); + path = $"{path}&includeUser={keys}"; + } + if (includeStatistics != null) { + string statistics = string.Join(",", includeStatistics); + path = $"{path}&includeStatistics={statistics}"; + } + Dictionary result = await LCApplication.HttpClient.Get>(path); + if (result.TryGetValue("results", out object results) && + results is List list) { + List rankings = new List(); + foreach (object item in list) { + LCRanking ranking = LCRanking.Parse(item as IDictionary); + rankings.Add(ranking); + } + return rankings.AsReadOnly(); + } + return null; + } + + /// + /// 设置更新策略 + /// + /// + /// + public async Task UpdateUpdateStrategy(LCLeaderboardUpdateStrategy updateStrategy) { + Dictionary data = new Dictionary { + { "updateStrategy", updateStrategy.ToString().ToLower() } + }; + string path = $"leaderboard/leaderboards/{StatisticName}"; + Dictionary result = await LCApplication.HttpClient.Put>(path, + data: data); + if (result.TryGetValue("updateStrategy", out object strategy) && + Enum.TryParse(strategy as string, true, out LCLeaderboardUpdateStrategy s)) { + UpdateStrategy = s; + } + return this; + } + + /// + /// 设置版本更新频率 + /// + /// + /// + public async Task UpdateVersionChangeInterval(LCLeaderboardVersionChangeInterval versionChangeInterval) { + Dictionary data = new Dictionary { + { "versionChangeInterval", versionChangeInterval.ToString().ToLower() } + }; + string path = $"leaderboard/leaderboards/{StatisticName}"; + Dictionary result = await LCApplication.HttpClient.Put>(path, + data: data); + if (result.TryGetValue("versionChangeInterval", out object interval) && + Enum.TryParse(interval as string, true, out LCLeaderboardVersionChangeInterval i)) { + VersionChangeInterval = i; + } + return this; + } + + /// + /// 拉取排行榜数据 + /// + /// + public async Task Fetch() { + string path = $"leaderboard/leaderboards/{StatisticName}"; + Dictionary result = await LCApplication.HttpClient.Get>(path); + Merge(result); + return this; + } + + /// + /// 重置排行榜 + /// + /// + public async Task Reset() { + string path = $"leaderboard/leaderboards/{StatisticName}/incrementVersion"; + Dictionary result = await LCApplication.HttpClient.Put>(path); + Merge(result); + return this; + } + + /// + /// 销毁排行榜 + /// + /// + public async Task Destroy() { + string path = $"leaderboard/leaderboards/{StatisticName}"; + await LCApplication.HttpClient.Delete(path); + } + + private void Merge(Dictionary data) { + if (data.TryGetValue("statisticName", out object statisticName)) { + StatisticName = statisticName as string; + } + if (data.TryGetValue("order", out object order) && + Enum.TryParse(order as string, true, out LCLeaderboardOrder o)) { + Order = o; + } + if (data.TryGetValue("updateStrategy", out object strategy) && + Enum.TryParse(strategy as string, true, out LCLeaderboardUpdateStrategy s)) { + UpdateStrategy = s; + } + if (data.TryGetValue("versionChangeInterval", out object interval) && + Enum.TryParse(interval as string, true, out LCLeaderboardVersionChangeInterval i)) { + VersionChangeInterval = i; + } + if (data.TryGetValue("version", out object version)) { + Version = Convert.ToInt32(version); + } + if (data.TryGetValue("createdAt", out object createdAt) && + createdAt is DateTime dt) { + CreatedAt = dt; + } + if (data.TryGetValue("expiredAt", out object expiredAt) && + expiredAt is IDictionary dict) { + NextResetAt = LCDecoder.DecodeDate(dict); + } } } } diff --git a/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs b/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs index ca58837..a53e353 100644 --- a/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs +++ b/Storage/Storage/Leaderboard/LCLeaderboardArchive.cs @@ -1,7 +1,77 @@ using System; -namespace LeanCloud.Storage.Leaderboard { +using System.Collections.Generic; +using LeanCloud.Storage.Internal.Codec; + +namespace LeanCloud.Storage { + /// + /// 归档的排行榜 + /// public class LCLeaderboardArchive { - public LCLeaderboardArchive() { + /// + /// 名称 + /// + public string StatisticName { + get; internal set; + } + + /// + /// 版本号 + /// + public int Version { + get; internal set; + } + + /// + /// 状态 + /// + public string Status { + get; internal set; + } + + /// + /// 下载地址 + /// + public string Url { + get; internal set; + } + + /// + /// 激活时间 + /// + public DateTime ActivatedAt { + get; internal set; + } + + /// + /// 归档时间 + /// + public DateTime DeactivatedAt { + get; internal set; + } + + internal static LCLeaderboardArchive Parse(IDictionary data) { + LCLeaderboardArchive archive = new LCLeaderboardArchive(); + if (data.TryGetValue("statisticName", out object statisticName)) { + archive.StatisticName = statisticName as string; + } + if (data.TryGetValue("version", out object version)) { + archive.Version = Convert.ToInt32(version); + } + if (data.TryGetValue("status", out object status)) { + archive.Status = status as string; + } + if (data.TryGetValue("url", out object url)) { + archive.Url = url as string; + } + if (data.TryGetValue("activatedAt", out object activatedAt) && + activatedAt is System.Collections.IDictionary actDt) { + archive.ActivatedAt = LCDecoder.DecodeDate(actDt); + } + if (data.TryGetValue("deactivatedAt", out object deactivatedAt) && + deactivatedAt is System.Collections.IDictionary deactDt) { + archive.DeactivatedAt = LCDecoder.DecodeDate(deactDt); + } + return archive; } } } diff --git a/Storage/Storage/Leaderboard/LCRanking.cs b/Storage/Storage/Leaderboard/LCRanking.cs index 58808db..6fdd3bf 100644 --- a/Storage/Storage/Leaderboard/LCRanking.cs +++ b/Storage/Storage/Leaderboard/LCRanking.cs @@ -1,7 +1,73 @@ using System; -namespace LeanCloud.Storage.Leaderboard { +using System.Collections.Generic; +using System.Collections.ObjectModel; +using LeanCloud.Storage.Internal.Object; + +namespace LeanCloud.Storage { + /// + /// 排名 + /// public class LCRanking { - public LCRanking() { + /// + /// 名次 + /// + public int Rank { + get; private set; + } + + /// + /// 用户 + /// + public LCUser User { + get; private set; + } + + /// + /// 成绩名称 + /// + public string StatisticName { + get; private set; + } + + /// + /// 分数 + /// + public double Value { + get; private set; + } + + /// + /// 成绩 + /// + public ReadOnlyCollection IncludedStatistics { + get; private set; + } + + internal static LCRanking Parse(IDictionary data) { + LCRanking ranking = new LCRanking(); + if (data.TryGetValue("rank", out object rank)) { + ranking.Rank = Convert.ToInt32(rank); + } + if (data.TryGetValue("user", out object user)) { + LCObjectData objectData = LCObjectData.Decode(user as System.Collections.IDictionary); + ranking.User = new LCUser(objectData); + } + if (data.TryGetValue("statisticName", out object statisticName)) { + ranking.StatisticName = statisticName as string; + } + if (data.TryGetValue("statisticValue", out object value)) { + ranking.Value = Convert.ToDouble(value); + } + if (data.TryGetValue("statistics", out object statistics) && + statistics is List list) { + List statisticList = new List(); + foreach (object item in list) { + LCStatistic statistic = LCStatistic.Parse(item as IDictionary); + statisticList.Add(statistic); + } + ranking.IncludedStatistics = statisticList.AsReadOnly(); + } + return ranking; } } } diff --git a/Storage/Storage/Leaderboard/LCStatistic.cs b/Storage/Storage/Leaderboard/LCStatistic.cs index e60d0ae..d7402e6 100644 --- a/Storage/Storage/Leaderboard/LCStatistic.cs +++ b/Storage/Storage/Leaderboard/LCStatistic.cs @@ -1,7 +1,44 @@ using System; -namespace LeanCloud.Storage.Leaderboard { +using System.Collections.Generic; + +namespace LeanCloud.Storage { + /// + /// 成绩 + /// public class LCStatistic { - public LCStatistic() { + /// + /// 排行榜名字 + /// + public string Name { + get; private set; + } + + /// + /// 成绩值 + /// + public double Value { + get; private set; + } + + /// + /// 排行榜版本 + /// + public int Version { + get; internal set; + } + + internal static LCStatistic Parse(IDictionary data) { + LCStatistic statistic = new LCStatistic(); + if (data.TryGetValue("statisticName", out object statisticName)) { + statistic.Name = statisticName as string; + } + if (data.TryGetValue("statisticValue", out object value)) { + statistic.Value = Convert.ToDouble(value); + } + if (data.TryGetValue("version", out object version)) { + statistic.Version = Convert.ToInt32(version); + } + return statistic; } } } diff --git a/Storage/Storage/Storage.csproj b/Storage/Storage/Storage.csproj index 9d8932c..b2f835f 100644 --- a/Storage/Storage/Storage.csproj +++ b/Storage/Storage/Storage.csproj @@ -21,6 +21,7 @@ +