Merge pull request #61 from onerain88/leaderboard

Leaderboard
oneRain 2020-05-07 16:01:36 +08:00 committed by GitHub
commit 45cb4a14c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 822 additions and 25 deletions

View File

@ -123,6 +123,18 @@
<Compile Include="..\Storage\LCSMSClient.cs">
<Link>LCSMSClient.cs</Link>
</Compile>
<Compile Include="..\Storage\Leaderboard\LCLeaderboardArchive.cs">
<Link>Leaderboard\LCLeaderboardArchive.cs</Link>
</Compile>
<Compile Include="..\Storage\Leaderboard\LCStatistic.cs">
<Link>Leaderboard\LCStatistic.cs</Link>
</Compile>
<Compile Include="..\Storage\Leaderboard\LCLeaderboard.cs">
<Link>Leaderboard\LCLeaderboard.cs</Link>
</Compile>
<Compile Include="..\Storage\Leaderboard\LCRanking.cs">
<Link>Leaderboard\LCRanking.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">

View File

@ -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<string, double> {
{ 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<LCStatistic> 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<LCRanking> 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<LCRanking> 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<LCStatistic> statistics = await LCLeaderboard.GetStatistics(user);
foreach (LCStatistic statistic in statistics) {
WriteLine($"{statistic.Name}, {statistic.Value}");
}
}
}
}

View File

@ -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");
}
}
}

View File

@ -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) {

View File

@ -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<string, object>);
@ -61,13 +61,13 @@ namespace LeanCloud.Storage.Internal.Codec {
return obj;
}
static LCRelation<LCObject> DecodeRelation(IDictionary dict) {
public static LCRelation<LCObject> DecodeRelation(IDictionary dict) {
LCRelation<LCObject> relation = new LCRelation<LCObject>();
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);

View File

@ -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<string, object> {
@ -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<string, object> {
{ "__type", "Bytes" },
@ -48,7 +48,7 @@ namespace LeanCloud.Storage.Internal.Codec {
};
}
static object EncodeList(IList list) {
public static object EncodeList(IList list) {
List<object> l = new List<object>();
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<string, object> d = new Dictionary<string, object>();
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<string, object> {
{ "__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<string> readers = acl.readers;
HashSet<string> writers = acl.writers;
HashSet<string> union = new HashSet<string>(readers);
@ -97,14 +97,14 @@ namespace LeanCloud.Storage.Internal.Codec {
return dict;
}
static object EncodeRelation(LCRelation<LCObject> relation) {
public static object EncodeRelation(LCRelation<LCObject> relation) {
return new Dictionary<string, object> {
{ "__type", "Relation" },
{ "className", relation.TargetClass }
};
}
static object EncodeGeoPoint(LCGeoPoint geoPoint) {
public static object EncodeGeoPoint(LCGeoPoint geoPoint) {
return new Dictionary<string, object> {
{ "__type", "GeoPoint" },
{ "latitude", geoPoint.Latitude },

View File

@ -69,7 +69,7 @@ namespace LeanCloud.Storage.Internal.Http {
public async Task<T> Post<T>(string path,
Dictionary<string, object> headers = null,
Dictionary<string, object> data = null,
object data = null,
Dictionary<string, object> queryParams = null) {
string url = await BuildUrl(path, queryParams);
HttpRequestMessage request = new HttpRequestMessage {
@ -102,7 +102,7 @@ namespace LeanCloud.Storage.Internal.Http {
public async Task<T> Put<T>(string path,
Dictionary<string, object> headers = null,
Dictionary<string, object> data = null,
object data = null,
Dictionary<string, object> 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) {

View File

@ -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 server = null,
string masterKey = 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());

View File

@ -85,7 +85,7 @@ namespace LeanCloud.Storage {
}
LCUser(LCObjectData objectData) : this() {
internal LCUser(LCObjectData objectData) : this() {
Merge(objectData);
}

View File

@ -0,0 +1,454 @@
using System;
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 {
/// <summary>
/// 排行榜顺序
/// </summary>
public enum LCLeaderboardOrder {
/// <summary>
/// 升序
/// </summary>
Ascending,
/// <summary>
/// 降序
/// </summary>
Descending
}
/// <summary>
/// 排行榜更新策略
/// </summary>
public enum LCLeaderboardUpdateStrategy {
/// <summary>
/// 更好的
/// </summary>
Better,
/// <summary>
/// 最近的
/// </summary>
Last,
/// <summary>
/// 总和
/// </summary>
Sum
}
/// <summary>
/// 排行榜刷新频率
/// </summary>
public enum LCLeaderboardVersionChangeInterval {
/// <summary>
/// 从不
/// </summary>
Never,
/// <summary>
/// 每天
/// </summary>
Day,
/// <summary>
/// 每周
/// </summary>
Week,
/// <summary>
/// 每月
/// </summary>
Month
}
/// <summary>
/// 排行榜
/// </summary>
public class LCLeaderboard {
/// <summary>
/// 成绩名字
/// </summary>
public string StatisticName {
get; private set;
}
/// <summary>
/// 排名顺序
/// </summary>
public LCLeaderboardOrder Order {
get; private set;
}
/// <summary>
/// 排名更新策略
/// </summary>
public LCLeaderboardUpdateStrategy UpdateStrategy {
get; private set;
}
/// <summary>
/// 版本更新频率
/// </summary>
public LCLeaderboardVersionChangeInterval VersionChangeInterval {
get; private set;
}
/// <summary>
/// 版本号
/// </summary>
public int Version {
get; private set;
}
/// <summary>
/// 下次重置时间
/// </summary>
public DateTime NextResetAt {
get; private set;
}
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt {
get; private set;
}
/// <summary>
/// 创建排行榜
/// </summary>
/// <param name="statisticName"></param>
/// <param name="order"></param>
/// <param name="updateStrategy"></param>
/// <param name="versionChangeInterval"></param>
/// <returns></returns>
public static async Task<LCLeaderboard> 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<string, object> data = new Dictionary<string, object> {
{ "statisticName", statisticName },
{ "order", order.ToString().ToLower() },
{ "versionChangeInterval", versionChangeInterval.ToString().ToLower() },
{ "updateStrategy", updateStrategy.ToString().ToLower() },
};
string path = "leaderboard/leaderboards";
Dictionary<string, object> result = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
data:data);
LCLeaderboard leaderboard = new LCLeaderboard();
leaderboard.Merge(result);
return leaderboard;
}
/// <summary>
/// 创建只包含名称的排行榜对象
/// </summary>
/// <param name="statisticName"></param>
/// <returns></returns>
public static LCLeaderboard CreateWithoutData(string statisticName) {
if (string.IsNullOrEmpty(statisticName)) {
throw new ArgumentNullException(nameof(statisticName));
}
return new LCLeaderboard {
StatisticName = statisticName
};
}
/// <summary>
/// 获取排行榜
/// </summary>
/// <param name="statisticName"></param>
/// <returns></returns>
public static Task<LCLeaderboard> GetLeaderboard(string statisticName) {
LCLeaderboard leaderboard = CreateWithoutData(statisticName);
return leaderboard.Fetch();
}
/// <summary>
/// 更新用户成绩
/// </summary>
/// <param name="user"></param>
/// <param name="statistics"></param>
/// <param name="overwrite"></param>
/// <returns></returns>
public static async Task<ReadOnlyCollection<LCStatistic>> UpdateStatistics(LCUser user,
Dictionary<string, double> statistics,
bool overwrite = true) {
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
if (statistics == null || statistics.Count == 0) {
throw new ArgumentNullException(nameof(statistics));
}
List<Dictionary<string, object>> data = statistics.Select(statistic => new Dictionary<string, object> {
{ "statisticName", statistic.Key },
{ "statisticValue", statistic.Value },
}).ToList();
string path = $"leaderboard/users/{user.ObjectId}/statistics";
if (overwrite) {
path = $"{path}?overwrite=1";
}
Dictionary<string, object> result = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
data: data);
if (result.TryGetValue("results", out object results) &&
results is List<object> list) {
List<LCStatistic> statisticList = new List<LCStatistic>();
foreach (object item in list) {
LCStatistic statistic = LCStatistic.Parse(item as IDictionary<string, object>);
statisticList.Add(statistic);
}
return statisticList.AsReadOnly();
}
return null;
}
/// <summary>
/// 获得用户成绩
/// </summary>
/// <param name="user"></param>
/// <param name="statisticNames"></param>
/// <returns></returns>
public static async Task<ReadOnlyCollection<LCStatistic>> GetStatistics(LCUser user,
IEnumerable<string> 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<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) &&
results is List<object> list) {
List<LCStatistic> statistics = new List<LCStatistic>();
foreach (object item in list) {
LCStatistic statistic = LCStatistic.Parse(item as Dictionary<string, object>);
statistics.Add(statistic);
}
return statistics.AsReadOnly();
}
return null;
}
/// <summary>
/// 删除用户成绩
/// </summary>
/// <param name="user"></param>
/// <param name="statisticNames"></param>
/// <returns></returns>
public static async Task DeleteStatistics(LCUser user,
IEnumerable<string> 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);
}
/// <summary>
/// 获取排行榜历史数据
/// </summary>
/// <param name="skip"></param>
/// <param name="limit"></param>
/// <returns></returns>
public async Task<ReadOnlyCollection<LCLeaderboardArchive>> 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<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) &&
results is List<object> list) {
List<LCLeaderboardArchive> archives = new List<LCLeaderboardArchive>();
foreach (object item in list) {
if (item is IDictionary<string, object> dict) {
LCLeaderboardArchive archive = LCLeaderboardArchive.Parse(dict);
archives.Add(archive);
}
}
return archives.AsReadOnly();
}
return null;
}
/// <summary>
/// 获取排行榜结果
/// </summary>
/// <param name="version"></param>
/// <param name="skip"></param>
/// <param name="limit"></param>
/// <param name="selectUserKeys"></param>
/// <param name="includeStatistics"></param>
/// <returns></returns>
public Task<ReadOnlyCollection<LCRanking>> GetResults(int version = -1,
int skip = 0,
int limit = 10,
IEnumerable<string> selectUserKeys = null,
IEnumerable<string> includeStatistics = null) {
return GetResults(null, version, skip, limit, selectUserKeys, includeStatistics);
}
/// <summary>
/// 获取用户及附近的排名
/// </summary>
/// <param name="version"></param>
/// <param name="skip"></param>
/// <param name="limit"></param>
/// <param name="selectUserKeys"></param>
/// <param name="includeStatistics"></param>
/// <returns></returns>
public async Task<ReadOnlyCollection<LCRanking>> GetResultsAroundUser(int version = -1,
int skip = 0,
int limit = 10,
IEnumerable<string> selectUserKeys = null,
IEnumerable<string> includeStatistics = null) {
LCUser user = await LCUser.GetCurrent();
return await GetResults(user, version, skip, limit, selectUserKeys, includeStatistics);
}
private async Task<ReadOnlyCollection<LCRanking>> GetResults(LCUser user,
int version,
int skip,
int limit,
IEnumerable<string> selectUserKeys,
IEnumerable<string> 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<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path);
if (result.TryGetValue("results", out object results) &&
results is List<object> list) {
List<LCRanking> rankings = new List<LCRanking>();
foreach (object item in list) {
LCRanking ranking = LCRanking.Parse(item as IDictionary<string, object>);
rankings.Add(ranking);
}
return rankings.AsReadOnly();
}
return null;
}
/// <summary>
/// 设置更新策略
/// </summary>
/// <param name="updateStrategy"></param>
/// <returns></returns>
public async Task<LCLeaderboard> UpdateUpdateStrategy(LCLeaderboardUpdateStrategy updateStrategy) {
Dictionary<string, object> data = new Dictionary<string, object> {
{ "updateStrategy", updateStrategy.ToString().ToLower() }
};
string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path,
data: data);
if (result.TryGetValue("updateStrategy", out object strategy) &&
Enum.TryParse(strategy as string, true, out LCLeaderboardUpdateStrategy s)) {
UpdateStrategy = s;
}
return this;
}
/// <summary>
/// 设置版本更新频率
/// </summary>
/// <param name="versionChangeInterval"></param>
/// <returns></returns>
public async Task<LCLeaderboard> UpdateVersionChangeInterval(LCLeaderboardVersionChangeInterval versionChangeInterval) {
Dictionary<string, object> data = new Dictionary<string, object> {
{ "versionChangeInterval", versionChangeInterval.ToString().ToLower() }
};
string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path,
data: data);
if (result.TryGetValue("versionChangeInterval", out object interval) &&
Enum.TryParse(interval as string, true, out LCLeaderboardVersionChangeInterval i)) {
VersionChangeInterval = i;
}
return this;
}
/// <summary>
/// 拉取排行榜数据
/// </summary>
/// <returns></returns>
public async Task<LCLeaderboard> Fetch() {
string path = $"leaderboard/leaderboards/{StatisticName}";
Dictionary<string, object> result = await LCApplication.HttpClient.Get<Dictionary<string, object>>(path);
Merge(result);
return this;
}
/// <summary>
/// 重置排行榜
/// </summary>
/// <returns></returns>
public async Task<LCLeaderboard> Reset() {
string path = $"leaderboard/leaderboards/{StatisticName}/incrementVersion";
Dictionary<string, object> result = await LCApplication.HttpClient.Put<Dictionary<string, object>>(path);
Merge(result);
return this;
}
/// <summary>
/// 销毁排行榜
/// </summary>
/// <returns></returns>
public async Task Destroy() {
string path = $"leaderboard/leaderboards/{StatisticName}";
await LCApplication.HttpClient.Delete(path);
}
private void Merge(Dictionary<string, object> 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);
}
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using LeanCloud.Storage.Internal.Codec;
namespace LeanCloud.Storage {
/// <summary>
/// 归档的排行榜
/// </summary>
public class LCLeaderboardArchive {
/// <summary>
/// 名称
/// </summary>
public string StatisticName {
get; internal set;
}
/// <summary>
/// 版本号
/// </summary>
public int Version {
get; internal set;
}
/// <summary>
/// 状态
/// </summary>
public string Status {
get; internal set;
}
/// <summary>
/// 下载地址
/// </summary>
public string Url {
get; internal set;
}
/// <summary>
/// 激活时间
/// </summary>
public DateTime ActivatedAt {
get; internal set;
}
/// <summary>
/// 归档时间
/// </summary>
public DateTime DeactivatedAt {
get; internal set;
}
internal static LCLeaderboardArchive Parse(IDictionary<string, object> 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;
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using LeanCloud.Storage.Internal.Object;
namespace LeanCloud.Storage {
/// <summary>
/// 排名
/// </summary>
public class LCRanking {
/// <summary>
/// 名次
/// </summary>
public int Rank {
get; private set;
}
/// <summary>
/// 用户
/// </summary>
public LCUser User {
get; private set;
}
/// <summary>
/// 成绩名称
/// </summary>
public string StatisticName {
get; private set;
}
/// <summary>
/// 分数
/// </summary>
public double Value {
get; private set;
}
/// <summary>
/// 成绩
/// </summary>
public ReadOnlyCollection<LCStatistic> IncludedStatistics {
get; private set;
}
internal static LCRanking Parse(IDictionary<string, object> 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<object> list) {
List<LCStatistic> statisticList = new List<LCStatistic>();
foreach (object item in list) {
LCStatistic statistic = LCStatistic.Parse(item as IDictionary<string, object>);
statisticList.Add(statistic);
}
ranking.IncludedStatistics = statisticList.AsReadOnly();
}
return ranking;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
namespace LeanCloud.Storage {
/// <summary>
/// 成绩
/// </summary>
public class LCStatistic {
/// <summary>
/// 排行榜名字
/// </summary>
public string Name {
get; private set;
}
/// <summary>
/// 成绩值
/// </summary>
public double Value {
get; private set;
}
/// <summary>
/// 排行榜版本
/// </summary>
public int Version {
get; internal set;
}
internal static LCStatistic Parse(IDictionary<string, object> 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;
}
}
}

View File

@ -21,6 +21,7 @@
<Folder Include="Internal\Object\" />
<Folder Include="Internal\Operation\" />
<Folder Include="Internal\Query\" />
<Folder Include="Leaderboard\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Common\Common\Common.csproj" />