Merge pull request #61 from onerain88/leaderboard
Leaderboard
commit
45cb4a14c3
|
@ -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">
|
||||
|
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace LeanCloud.Storage {
|
|||
|
||||
}
|
||||
|
||||
LCUser(LCObjectData objectData) : this() {
|
||||
internal LCUser(LCObjectData objectData) : this() {
|
||||
Merge(objectData);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue