chore: 支持 App Router
parent
2eeb19dc18
commit
66ade7bbb6
|
@ -1,110 +1,69 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
namespace LeanCloud.Common {
|
||||||
public class AppRouter {
|
public class AppRouter {
|
||||||
// 华东应用 App Id 后缀
|
private readonly string appId;
|
||||||
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
|
|
||||||
// 美国应用 App Id 后缀
|
|
||||||
const string US_SUFFIX = "-MdYXbMMI";
|
|
||||||
|
|
||||||
[JsonProperty("ttl")]
|
private readonly string server;
|
||||||
public long TTL {
|
|
||||||
get; internal set;
|
private AppServer appServer;
|
||||||
|
|
||||||
|
public AppRouter(string appId, string server) {
|
||||||
|
if (!IsInternalApp(appId) && string.IsNullOrEmpty(server)) {
|
||||||
|
// 国内节点必须配置自定义域名
|
||||||
|
throw new Exception("Please init with your server url.");
|
||||||
|
}
|
||||||
|
this.appId = appId;
|
||||||
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("api_server")]
|
public async Task<string> GetApiServer() {
|
||||||
public string ApiServer {
|
// 优先返回用户自定义域名
|
||||||
get; internal set;
|
if (!string.IsNullOrEmpty(server)) {
|
||||||
}
|
return server;
|
||||||
|
}
|
||||||
|
// 判断节点地区
|
||||||
|
if (!IsInternalApp(appId)) {
|
||||||
|
// 国内节点必须配置自定义域名
|
||||||
|
throw new Exception("Please init with your server url.");
|
||||||
|
}
|
||||||
|
// 向 App Router 请求地址
|
||||||
|
if (appServer == null || appServer.IsExpired) {
|
||||||
|
try {
|
||||||
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
|
RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"),
|
||||||
|
Method = HttpMethod.Get
|
||||||
|
};
|
||||||
|
HttpClient client = new HttpClient();
|
||||||
|
HttpUtils.PrintRequest(client, request);
|
||||||
|
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
request.Dispose();
|
||||||
|
|
||||||
[JsonProperty("engine_server")]
|
string resultString = await response.Content.ReadAsStringAsync();
|
||||||
public string EngineServer {
|
response.Dispose();
|
||||||
get; internal set;
|
HttpUtils.PrintResponse(response, resultString);
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("push_server")]
|
Dictionary<string, object> data = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString);
|
||||||
public string PushServer {
|
appServer = new AppServer(data);
|
||||||
get; internal set;
|
} catch (Exception e) {
|
||||||
}
|
Logger.Error(e.Message);
|
||||||
|
// 拉取服务地址失败后,使用国际节点的默认服务地址
|
||||||
[JsonProperty("rtm_router_server")]
|
appServer = AppServer.GetInternalFallbackAppServer(appId);
|
||||||
public string RTMServer {
|
|
||||||
get; internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("stats_server")]
|
|
||||||
public string StatsServer {
|
|
||||||
get; internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonProperty("play_server")]
|
|
||||||
public string PlayServer {
|
|
||||||
get; internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Source {
|
|
||||||
get; internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset FetchedAt {
|
|
||||||
get; internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppRouter() {
|
|
||||||
FetchedAt = DateTimeOffset.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsExpired {
|
|
||||||
get {
|
|
||||||
if (TTL == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL);
|
|
||||||
}
|
}
|
||||||
|
return appServer.ApiServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppRouter GetFallbackServers(string appId) {
|
private static bool IsInternalApp(string appId) {
|
||||||
var prefix = appId.Substring(0, 8).ToLower();
|
if (appId.Length < 9) {
|
||||||
var suffix = appId.Substring(appId.Length - 9);
|
return false;
|
||||||
switch (suffix) {
|
|
||||||
case EAST_CHINA_SUFFIX:
|
|
||||||
// 华东
|
|
||||||
return new AppRouter {
|
|
||||||
TTL = -1,
|
|
||||||
ApiServer = $"{prefix}.api.lncldapi.com",
|
|
||||||
EngineServer = $"{prefix}.engine.lncldapi.com",
|
|
||||||
PushServer = $"{prefix}.push.lncldapi.com",
|
|
||||||
RTMServer = $"{prefix}.rtm.lncldapi.com",
|
|
||||||
StatsServer = $"{prefix}.stats.lncldapi.com",
|
|
||||||
PlayServer = $"{prefix}.play.lncldapi.com",
|
|
||||||
Source = "fallback",
|
|
||||||
};
|
|
||||||
case US_SUFFIX:
|
|
||||||
// 美国
|
|
||||||
return new AppRouter {
|
|
||||||
TTL = -1,
|
|
||||||
ApiServer = $"{prefix}.api.lncldglobal.com",
|
|
||||||
EngineServer = $"{prefix}.engine.lncldglobal.com",
|
|
||||||
PushServer = $"{prefix}.push.lncldglobal.com",
|
|
||||||
RTMServer = $"{prefix}.rtm.lncldglobal.com",
|
|
||||||
StatsServer = $"{prefix}.stats.lncldglobal.com",
|
|
||||||
PlayServer = $"{prefix}.play.lncldglobal.com",
|
|
||||||
Source = "fallback",
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
// 华北
|
|
||||||
return new AppRouter {
|
|
||||||
TTL = -1,
|
|
||||||
ApiServer = $"{prefix}.api.lncld.net",
|
|
||||||
EngineServer = $"{prefix}.engine.lncld.net",
|
|
||||||
PushServer = $"{prefix}.push.lncld.net",
|
|
||||||
RTMServer = $"{prefix}.rtm.lncld.net",
|
|
||||||
StatsServer = $"{prefix}.stats.lncld.net",
|
|
||||||
PlayServer = $"{prefix}.play.lncld.net",
|
|
||||||
Source = "fallback",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
string suffix = appId.Substring(appId.Length - 9);
|
||||||
|
return suffix == "-MdYXbMMI";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,106 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace LeanCloud.Common {
|
|
||||||
public class AppRouterController {
|
|
||||||
readonly string appId;
|
|
||||||
|
|
||||||
AppRouter currentState;
|
|
||||||
|
|
||||||
readonly SemaphoreSlim locker = new SemaphoreSlim(1);
|
|
||||||
|
|
||||||
public AppRouterController(string appId, string server) {
|
|
||||||
if (!IsInternationalApp(appId) && string.IsNullOrEmpty(server)) {
|
|
||||||
// 国内 App 必须设置域名
|
|
||||||
throw new ArgumentException("You must init with your domain.");
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(server)) {
|
|
||||||
currentState = new AppRouter {
|
|
||||||
ApiServer = server,
|
|
||||||
EngineServer = server,
|
|
||||||
PushServer = server,
|
|
||||||
RTMServer = server,
|
|
||||||
StatsServer = server,
|
|
||||||
PlayServer = server,
|
|
||||||
TTL = -1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.appId = appId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AppRouter> Get() {
|
|
||||||
if (string.IsNullOrEmpty(appId)) {
|
|
||||||
throw new ArgumentNullException(nameof(appId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentState != null && !currentState.IsExpired) {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
await locker.WaitAsync();
|
|
||||||
try {
|
|
||||||
if (currentState == null) {
|
|
||||||
try {
|
|
||||||
currentState = await QueryAsync();
|
|
||||||
} catch (Exception) {
|
|
||||||
currentState = AppRouter.GetFallbackServers(appId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
} finally {
|
|
||||||
locker.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task<AppRouter> QueryAsync() {
|
|
||||||
HttpClient client = null;
|
|
||||||
HttpRequestMessage request = null;
|
|
||||||
HttpResponseMessage response = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
string url = string.Format("https://app-router.com/2/route?appId={0}", appId);
|
|
||||||
|
|
||||||
client = new HttpClient();
|
|
||||||
request = new HttpRequestMessage {
|
|
||||||
RequestUri = new Uri(url),
|
|
||||||
Method = HttpMethod.Get
|
|
||||||
};
|
|
||||||
HttpUtils.PrintRequest(client, request);
|
|
||||||
|
|
||||||
response = await client.SendAsync(request);
|
|
||||||
string content = await response.Content.ReadAsStringAsync();
|
|
||||||
HttpUtils.PrintResponse(response, content);
|
|
||||||
|
|
||||||
AppRouter state = JsonConvert.DeserializeObject<AppRouter>(content);
|
|
||||||
state.Source = "router";
|
|
||||||
|
|
||||||
return state;
|
|
||||||
} finally {
|
|
||||||
if (client != null) {
|
|
||||||
client.Dispose();
|
|
||||||
}
|
|
||||||
if (request != null) {
|
|
||||||
request.Dispose();
|
|
||||||
}
|
|
||||||
if (response != null) {
|
|
||||||
response.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear() {
|
|
||||||
currentState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsInternationalApp(string appId) {
|
|
||||||
if (appId.Length < 9) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
string suffix = appId.Substring(appId.Length - 9);
|
|
||||||
return suffix == "-MdYXbMMI";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Common {
|
||||||
|
public class AppServer {
|
||||||
|
public string ApiServer {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EngineServer {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PushServer {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RTMServer {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpired {
|
||||||
|
get {
|
||||||
|
return ttl != -1 && DateTime.Now > expiredAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DateTime expiredAt;
|
||||||
|
|
||||||
|
private readonly int ttl;
|
||||||
|
|
||||||
|
public AppServer(Dictionary<string, object> data) {
|
||||||
|
ApiServer = GetUrlWithScheme(data["api_server"] as string);
|
||||||
|
PushServer = GetUrlWithScheme(data["push_server"] as string);
|
||||||
|
EngineServer = GetUrlWithScheme(data["engine_server"] as string);
|
||||||
|
ttl = (int)(long)data["ttl"];
|
||||||
|
expiredAt = DateTime.Now.AddSeconds(ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetUrlWithScheme(string url) {
|
||||||
|
return url.StartsWith("https://") ? url : $"https://{url}";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static AppServer GetInternalFallbackAppServer(string appId) {
|
||||||
|
string prefix = appId.Substring(0, 8).ToLower();
|
||||||
|
return new AppServer(new Dictionary<string, object> {
|
||||||
|
{ "api_server", $"https://{prefix}.api.lncldglobal.com" },
|
||||||
|
{ "push_server", $"https://{prefix}.engine.lncldglobal.com" },
|
||||||
|
{ "engine_server", $"https://{prefix}.push.lncldglobal.com" },
|
||||||
|
{ "ttl", -1 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,19 +12,21 @@ using LeanCloud.Common;
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal.Http {
|
namespace LeanCloud.Storage.Internal.Http {
|
||||||
internal class LCHttpClient {
|
internal class LCHttpClient {
|
||||||
readonly string appId;
|
private readonly string appId;
|
||||||
|
|
||||||
readonly string appKey;
|
readonly string appKey;
|
||||||
|
|
||||||
readonly string server;
|
private readonly string server;
|
||||||
|
|
||||||
readonly string sdkVersion;
|
private readonly string sdkVersion;
|
||||||
|
|
||||||
readonly string apiVersion;
|
readonly string apiVersion;
|
||||||
|
|
||||||
HttpClient client;
|
readonly AppRouter appRouter;
|
||||||
|
|
||||||
MD5 md5;
|
readonly HttpClient client;
|
||||||
|
|
||||||
|
readonly MD5 md5;
|
||||||
|
|
||||||
internal LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) {
|
internal LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) {
|
||||||
this.appId = appId;
|
this.appId = appId;
|
||||||
|
@ -33,6 +35,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
this.sdkVersion = sdkVersion;
|
this.sdkVersion = sdkVersion;
|
||||||
this.apiVersion = apiVersion;
|
this.apiVersion = apiVersion;
|
||||||
|
|
||||||
|
appRouter = new AppRouter(appId, server);
|
||||||
|
|
||||||
client = new HttpClient();
|
client = new HttpClient();
|
||||||
ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", LeanCloud.SDKVersion);
|
ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", LeanCloud.SDKVersion);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
|
||||||
|
@ -45,7 +49,7 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
internal async Task<T> Get<T>(string path,
|
internal async Task<T> Get<T>(string path,
|
||||||
Dictionary<string, object> headers = null,
|
Dictionary<string, object> headers = null,
|
||||||
Dictionary<string, object> queryParams = null) {
|
Dictionary<string, object> queryParams = null) {
|
||||||
string url = BuildUrl(path, queryParams);
|
string url = await BuildUrl(path, queryParams);
|
||||||
HttpRequestMessage request = new HttpRequestMessage {
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
RequestUri = new Uri(url),
|
RequestUri = new Uri(url),
|
||||||
Method = HttpMethod.Get
|
Method = HttpMethod.Get
|
||||||
|
@ -71,7 +75,7 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
Dictionary<string, object> headers = null,
|
Dictionary<string, object> headers = null,
|
||||||
Dictionary<string, object> data = null,
|
Dictionary<string, object> data = null,
|
||||||
Dictionary<string, object> queryParams = null) {
|
Dictionary<string, object> queryParams = null) {
|
||||||
string url = BuildUrl(path, queryParams);
|
string url = await BuildUrl(path, queryParams);
|
||||||
HttpRequestMessage request = new HttpRequestMessage {
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
RequestUri = new Uri(url),
|
RequestUri = new Uri(url),
|
||||||
Method = HttpMethod.Post,
|
Method = HttpMethod.Post,
|
||||||
|
@ -104,7 +108,7 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
Dictionary<string, object> headers = null,
|
Dictionary<string, object> headers = null,
|
||||||
Dictionary<string, object> data = null,
|
Dictionary<string, object> data = null,
|
||||||
Dictionary<string, object> queryParams = null) {
|
Dictionary<string, object> queryParams = null) {
|
||||||
string url = BuildUrl(path, queryParams);
|
string url = await BuildUrl(path, queryParams);
|
||||||
HttpRequestMessage request = new HttpRequestMessage {
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
RequestUri = new Uri(url),
|
RequestUri = new Uri(url),
|
||||||
Method = HttpMethod.Put,
|
Method = HttpMethod.Put,
|
||||||
|
@ -134,7 +138,7 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task Delete(string path) {
|
internal async Task Delete(string path) {
|
||||||
string url = BuildUrl(path);
|
string url = await BuildUrl(path);
|
||||||
HttpRequestMessage request = new HttpRequestMessage {
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
RequestUri = new Uri(url),
|
RequestUri = new Uri(url),
|
||||||
Method = HttpMethod.Delete
|
Method = HttpMethod.Delete
|
||||||
|
@ -170,8 +174,9 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
return new LCException(code, message);
|
return new LCException(code, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
string BuildUrl(string path, Dictionary<string, object> queryParams = null) {
|
async Task<string> BuildUrl(string path, Dictionary<string, object> queryParams = null) {
|
||||||
string url = $"{server}/{apiVersion}/{path}";
|
string apiServer = await appRouter.GetApiServer();
|
||||||
|
string url = $"{apiServer}/{apiVersion}/{path}";
|
||||||
if (queryParams != null) {
|
if (queryParams != null) {
|
||||||
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
|
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
|
||||||
string queries = string.Join("&", queryPairs);
|
string queries = string.Join("&", queryPairs);
|
||||||
|
|
|
@ -1,66 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using LeanCloud.Common;
|
|
||||||
|
|
||||||
namespace Common.Test {
|
|
||||||
public class AppRouterTest {
|
|
||||||
static void Print(LogLevel level, string info) {
|
|
||||||
switch (level) {
|
|
||||||
case LogLevel.Debug:
|
|
||||||
TestContext.Out.WriteLine($"[DEBUG] {info}");
|
|
||||||
break;
|
|
||||||
case LogLevel.Warn:
|
|
||||||
TestContext.Out.WriteLine($"[WARNING] {info}");
|
|
||||||
break;
|
|
||||||
case LogLevel.Error:
|
|
||||||
TestContext.Out.WriteLine($"[ERROR] {info}");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
TestContext.Out.WriteLine(info);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp() {
|
|
||||||
Logger.LogDelegate += Print;
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown() {
|
|
||||||
Logger.LogDelegate -= Print;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ChineseApp() {
|
|
||||||
Exception e = Assert.Catch(() => {
|
|
||||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz";
|
|
||||||
AppRouterController appRouter = new AppRouterController(appId, null);
|
|
||||||
TestContext.WriteLine("init done");
|
|
||||||
});
|
|
||||||
TestContext.WriteLine(e.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task ChineseAppWithDomain() {
|
|
||||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz";
|
|
||||||
string server = "https://bmyv4rks.lc-cn-n1-shared.com";
|
|
||||||
AppRouterController appRouterController = new AppRouterController(appId, server);
|
|
||||||
AppRouter appRouterState = await appRouterController.Get();
|
|
||||||
Assert.AreEqual(appRouterState.ApiServer, server);
|
|
||||||
Assert.AreEqual(appRouterState.EngineServer, server);
|
|
||||||
Assert.AreEqual(appRouterState.PushServer, server);
|
|
||||||
Assert.AreEqual(appRouterState.RTMServer, server);
|
|
||||||
Assert.AreEqual(appRouterState.StatsServer, server);
|
|
||||||
Assert.AreEqual(appRouterState.PlayServer, server);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void InternationalApp() {
|
|
||||||
string appId = "BMYV4RKSTwo8WSqt8q9ezcWF-MdYXbMMI";
|
|
||||||
_ = new AppRouterController(appId, null);
|
|
||||||
TestContext.WriteLine("International app init done");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="nunit" Version="3.12.0" />
|
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\Common\Common.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
|
@ -1,91 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace Common.Test {
|
|
||||||
public class Test {
|
|
||||||
[Test]
|
|
||||||
public async Task AsyncFor() {
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
await Task.Delay(1000);
|
|
||||||
TestContext.WriteLine($"{i} done at {DateTimeOffset.UtcNow}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ConcurrentCollection() {
|
|
||||||
List<int> list = new List<int>();
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
Task.Run(() => {
|
|
||||||
list.Add(i);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
TestContext.WriteLine($"{list.Count}");
|
|
||||||
|
|
||||||
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
Task.Run(() => {
|
|
||||||
queue.Enqueue(i);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
TestContext.WriteLine($"{queue.Count}");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void ObjectType() {
|
|
||||||
List<object> list = new List<object> { 1, "hello", 2, "world" };
|
|
||||||
TestContext.WriteLine(list is IList);
|
|
||||||
object[] objs = { 1, "hi", 3 };
|
|
||||||
TestContext.WriteLine(objs is IList);
|
|
||||||
List<object> subList = list.OfType<string>().ToList<object>();
|
|
||||||
foreach (object obj in subList) {
|
|
||||||
TestContext.WriteLine(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void CollectionExcept() {
|
|
||||||
List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
|
|
||||||
List<int> list2 = new List<int> { 4, 5, 6 };
|
|
||||||
IEnumerable<int> deltaList = list1.Except(list2).ToList();
|
|
||||||
foreach (int delta in deltaList) {
|
|
||||||
TestContext.WriteLine(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<string, object> dict1 = new Dictionary<string, object> {
|
|
||||||
{ "a", 1 },
|
|
||||||
{ "b", 2 }
|
|
||||||
};
|
|
||||||
Dictionary<string, object> dict2 = new Dictionary<string, object> {
|
|
||||||
{ "b", 2 },
|
|
||||||
{ "c", 3 }
|
|
||||||
};
|
|
||||||
IEnumerable<KeyValuePair<string, object>> deltaDict = dict1.Except(dict2);
|
|
||||||
foreach (KeyValuePair<string, object> delta in deltaDict) {
|
|
||||||
TestContext.WriteLine($"{delta.Key} : {delta.Value}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void Union() {
|
|
||||||
Dictionary<string, int> dict1 = new Dictionary<string, int> {
|
|
||||||
{ "a", 1 },
|
|
||||||
{ "b", 2 },
|
|
||||||
{ "c", 3 }
|
|
||||||
};
|
|
||||||
Dictionary<string, string> dict2 = new Dictionary<string, string> {
|
|
||||||
{ "b", "b" },
|
|
||||||
{ "c", "c" },
|
|
||||||
{ "d", "d" }
|
|
||||||
};
|
|
||||||
IEnumerable<string> keys = dict1.Keys.Union(dict2.Keys);
|
|
||||||
foreach (string key in keys) {
|
|
||||||
TestContext.WriteLine(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -72,10 +72,11 @@ namespace LeanCloud.Test {
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task AWS() {
|
public async Task AWS() {
|
||||||
Logger.LogDelegate += Utils.Print;
|
LeanCloud.Initialize("UlCpyvLm8aMzQsW6KnP6W3Wt-MdYXbMMI", "PyCTYoNoxCVoKKg394PBeS4r");
|
||||||
LeanCloud.Initialize("UlCpyvLm8aMzQsW6KnP6W3Wt-MdYXbMMI", "PyCTYoNoxCVoKKg394PBeS4r", "https://ulcpyvlm.api.lncldglobal.com");
|
LCFile file = new LCFile("avatar", AvatarFilePath);
|
||||||
LCFile file = new LCFile("avatar", APKFilePath);
|
await file.Save((count, total) => {
|
||||||
await file.Save();
|
TestContext.WriteLine($"progress: {count}/{total}");
|
||||||
|
});
|
||||||
TestContext.WriteLine(file.ObjectId);
|
TestContext.WriteLine(file.ObjectId);
|
||||||
Assert.NotNull(file.ObjectId);
|
Assert.NotNull(file.ObjectId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "Storage\Storage.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage.Test", "Test\Storage.Test\Storage.Test.csproj", "{531F8181-FFE0-476E-9D0A-93F13CAD1183}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Test", "Test\Common.Test\Common.Test.csproj", "{237B7A92-28F6-4227-BE52-7647139B1820}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -29,14 +27,9 @@ Global
|
||||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.Build.0 = Release|Any CPU
|
{531F8181-FFE0-476E-9D0A-93F13CAD1183}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{237B7A92-28F6-4227-BE52-7647139B1820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{237B7A92-28F6-4227-BE52-7647139B1820}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{237B7A92-28F6-4227-BE52-7647139B1820}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{237B7A92-28F6-4227-BE52-7647139B1820}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{531F8181-FFE0-476E-9D0A-93F13CAD1183} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
{531F8181-FFE0-476E-9D0A-93F13CAD1183} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
||||||
{237B7A92-28F6-4227-BE52-7647139B1820} = {C827DA2F-6AB4-48D8-AB5B-6DAB925F8933}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(MonoDevelopProperties) = preSolution
|
GlobalSection(MonoDevelopProperties) = preSolution
|
||||||
version = 0.1.0
|
version = 0.1.0
|
||||||
|
|
Loading…
Reference in New Issue