Merge pull request #107 from onerain88/leanengine
LeanEngine C# SDK
commit
6139bdd914
|
@ -28,6 +28,9 @@ namespace LeanCloud.Common {
|
||||||
return Convert.ToInt32(reader.Value);
|
return Convert.ToInt32(reader.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (reader.TokenType == JsonToken.Float) {
|
||||||
|
return Convert.ToSingle(reader.Value);
|
||||||
|
}
|
||||||
|
|
||||||
return serializer.Deserialize(reader);
|
return serializer.Deserialize(reader);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
public enum LCEngineObjectHookType {
|
||||||
|
BeforeSave,
|
||||||
|
AfterSave,
|
||||||
|
BeforeUpdate,
|
||||||
|
AfterUpdate,
|
||||||
|
BeforeDelete,
|
||||||
|
AfterDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LCEngineClassHookAttribute : Attribute {
|
||||||
|
public string ClassName {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineObjectHookType HookType {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineClassHookAttribute(string className, LCEngineObjectHookType hookType) {
|
||||||
|
if (string.IsNullOrEmpty(className)) {
|
||||||
|
throw new ArgumentNullException(nameof(className));
|
||||||
|
}
|
||||||
|
ClassName = className;
|
||||||
|
HookType = hookType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||||
|
public class LCEngineFunctionAttribute : Attribute {
|
||||||
|
public string FunctionName {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineFunctionAttribute(string funcName) {
|
||||||
|
if (string.IsNullOrEmpty(funcName)) {
|
||||||
|
throw new ArgumentNullException(nameof(funcName));
|
||||||
|
}
|
||||||
|
FunctionName = funcName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||||||
|
public class LCEngineFunctionParamAttribute : Attribute {
|
||||||
|
public string ParamName {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineFunctionParamAttribute(string paramName) {
|
||||||
|
if (string.IsNullOrEmpty(paramName)) {
|
||||||
|
throw new ArgumentNullException(nameof(paramName));
|
||||||
|
}
|
||||||
|
ParamName = paramName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
public enum LCEngineRealtimeHookType {
|
||||||
|
// 消息
|
||||||
|
MessageReceived,
|
||||||
|
MessageSent,
|
||||||
|
MessageUpdate,
|
||||||
|
ReceiversOffline,
|
||||||
|
// 对话
|
||||||
|
ConversationStart,
|
||||||
|
ConversationStarted,
|
||||||
|
ConversationAdd,
|
||||||
|
ConversationAdded,
|
||||||
|
ConversationRemove,
|
||||||
|
ConversationRemoved,
|
||||||
|
ConversationUpdate,
|
||||||
|
// 客户端
|
||||||
|
ClientOnline,
|
||||||
|
ClientOffline,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LCEngineRealtimeHookAttribute : Attribute {
|
||||||
|
public LCEngineRealtimeHookType HookType {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineRealtimeHookAttribute(LCEngineRealtimeHookType hookType) {
|
||||||
|
HookType = hookType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
public enum LCEngineUserHookType {
|
||||||
|
OnSMSVerified,
|
||||||
|
OnEmailVerified,
|
||||||
|
OnLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LCEngineUserHookAttribute : Attribute {
|
||||||
|
public LCEngineUserHookType HookType {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCEngineUserHookAttribute(LCEngineUserHookType hookType) {
|
||||||
|
HookType = hookType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[ApiController]
|
||||||
|
[Route("{1,1.1}")]
|
||||||
|
[EnableCors(LCEngine.LCEngineCORS)]
|
||||||
|
public class LCClassHookController : ControllerBase {
|
||||||
|
private Dictionary<string, MethodInfo> ClassHooks => LCEngine.ClassHooks;
|
||||||
|
|
||||||
|
[HttpPost("functions/{className}/{hookName}")]
|
||||||
|
public async Task<object> Hook(string className, string hookName, JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug($"Hook: {className}#{hookName}");
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
LCEngine.CheckHookKey(Request);
|
||||||
|
|
||||||
|
string classHookName = GetClassHookName(className, hookName);
|
||||||
|
if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) {
|
||||||
|
Dictionary<string, object> data = LCEngine.Decode(body);
|
||||||
|
|
||||||
|
LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary<string, object>);
|
||||||
|
objectData.ClassName = className;
|
||||||
|
LCObject obj = LCObject.Create(className);
|
||||||
|
obj.Merge(objectData);
|
||||||
|
|
||||||
|
// 避免死循环
|
||||||
|
if (hookName.StartsWith("before")) {
|
||||||
|
obj.DisableBeforeHook();
|
||||||
|
} else {
|
||||||
|
obj.DisableAfterHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
LCUser user = null;
|
||||||
|
if (data.TryGetValue("user", out object userObj) &&
|
||||||
|
userObj != null) {
|
||||||
|
user = new LCUser();
|
||||||
|
user.Merge(LCObjectData.Decode(userObj as Dictionary<string, object>));
|
||||||
|
LCEngineRequestContext.CurrentUser = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject;
|
||||||
|
if (result != null) {
|
||||||
|
return LCCloud.Encode(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetClassHookName(string className, string hookName) {
|
||||||
|
switch (hookName) {
|
||||||
|
case "beforeSave":
|
||||||
|
return $"__before_save_for_{className}";
|
||||||
|
case "afterSave":
|
||||||
|
return $"__after_save_for_{className}";
|
||||||
|
case "beforeUpdate":
|
||||||
|
return $"__before_update_for_{className}";
|
||||||
|
case "afterUpdate":
|
||||||
|
return $"__after_update_for_{className}";
|
||||||
|
case "beforeDelete":
|
||||||
|
return $"__before_delete_for_{className}";
|
||||||
|
case "afterDelete":
|
||||||
|
return $"__after_delete_for_{className}";
|
||||||
|
default:
|
||||||
|
throw new Exception($"Error hook name: {hookName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[ApiController]
|
||||||
|
[Route("{1,1.1}")]
|
||||||
|
[EnableCors(LCEngine.LCEngineCORS)]
|
||||||
|
public class LCFunctionController : ControllerBase {
|
||||||
|
private Dictionary<string, MethodInfo> Functions => LCEngine.Functions;
|
||||||
|
|
||||||
|
[HttpGet("functions/_ops/metadatas")]
|
||||||
|
public object GetFunctions() {
|
||||||
|
try {
|
||||||
|
return LCEngine.GetFunctions(Request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("functions/{funcName}")]
|
||||||
|
public async Task<object> Run(string funcName, JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug($"Run: {funcName}");
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
object[] ps = ParseParameters(mi, body);
|
||||||
|
object result = await LCEngine.Invoke(mi, ps.ToArray());
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "result", result }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("call/{funcName}")]
|
||||||
|
public async Task<object> RPC(string funcName, JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug($"RPC: {funcName}");
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
object[] ps = ParseParameters(mi, body);
|
||||||
|
object result = await LCEngine.Invoke(mi, ps);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "result", LCCloud.Encode(result) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object[] ParseParameters(MethodInfo mi, JsonElement body) {
|
||||||
|
Dictionary<string, object> parameters = LCEngine.Decode(body);
|
||||||
|
List<object> ps = new List<object>();
|
||||||
|
|
||||||
|
if (mi.GetParameters().Length > 0) {
|
||||||
|
if (Array.Exists(mi.GetParameters(),
|
||||||
|
p => p.GetCustomAttribute<LCEngineFunctionParamAttribute>() != null)) {
|
||||||
|
// 如果包含 LCEngineFunctionParamAttribute 的参数,则按照配对方式传递参数
|
||||||
|
foreach (ParameterInfo pi in mi.GetParameters()) {
|
||||||
|
LCEngineFunctionParamAttribute attr = pi.GetCustomAttribute<LCEngineFunctionParamAttribute>();
|
||||||
|
if (attr != null) {
|
||||||
|
string paramName = attr.ParamName;
|
||||||
|
ps.Add(parameters[paramName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ps.Add(LCDecoder.Decode(LCEngine.Decode(body)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[ApiController]
|
||||||
|
[Route("__engine/{1,1.1}")]
|
||||||
|
[EnableCors(LCEngine.LCEngineCORS)]
|
||||||
|
public class LCPingController : ControllerBase {
|
||||||
|
[HttpGet("ping")]
|
||||||
|
public object Get() {
|
||||||
|
LCLogger.Debug("Ping ~~~");
|
||||||
|
|
||||||
|
return new Dictionary<string, string> {
|
||||||
|
{ "runtime", $"dotnet-{Environment.Version}" },
|
||||||
|
{ "version", LCApplication.SDKVersion }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
[ApiController]
|
||||||
|
[Route("{1,1.1}/functions")]
|
||||||
|
[EnableCors(LCEngine.LCEngineCORS)]
|
||||||
|
public class LCUserHookController : ControllerBase {
|
||||||
|
private Dictionary<string, MethodInfo> UserHooks => LCEngine.UserHooks;
|
||||||
|
|
||||||
|
[HttpPost("onVerified/sms")]
|
||||||
|
public async Task<object> HookSMSVerification(JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug(LCEngine.OnSMSVerified);
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
LCEngine.CheckHookKey(Request);
|
||||||
|
|
||||||
|
if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) {
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
Dictionary<string, object> dict = LCEngine.Decode(body);
|
||||||
|
return await Invoke(mi, dict);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("onVerified/email")]
|
||||||
|
public async Task<object> HookEmailVerification(JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug(LCEngine.OnEmailVerified);
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
LCEngine.CheckHookKey(Request);
|
||||||
|
|
||||||
|
if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) {
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
Dictionary<string, object> dict = LCEngine.Decode(body);
|
||||||
|
return await Invoke(mi, dict);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("_User/onLogin")]
|
||||||
|
public async Task<object> HookLogin(JsonElement body) {
|
||||||
|
try {
|
||||||
|
LCLogger.Debug(LCEngine.OnLogin);
|
||||||
|
LCLogger.Debug(body.ToString());
|
||||||
|
|
||||||
|
LCEngine.CheckHookKey(Request);
|
||||||
|
|
||||||
|
if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) {
|
||||||
|
LCEngine.InitRequestContext(Request);
|
||||||
|
|
||||||
|
Dictionary<string, object> dict = LCEngine.Decode(body);
|
||||||
|
return await Invoke(mi, dict);
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return StatusCode(500, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<object> Invoke(MethodInfo mi, Dictionary<string, object> dict) {
|
||||||
|
LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary<string, object>);
|
||||||
|
objectData.ClassName = "_User";
|
||||||
|
|
||||||
|
LCObject user = LCObject.Create("_User");
|
||||||
|
user.Merge(objectData);
|
||||||
|
|
||||||
|
return await LCEngine.Invoke(mi, new object[] { user }) as LCObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
|
<RootNamespace>LeanCloud.Engine</RootNamespace>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Common\Common\Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Storage\Storage\Storage.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Controllers\" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,312 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using LeanCloud.Common;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
public class LCEngine {
|
||||||
|
public const string LCEngineCORS = "LCEngineCORS";
|
||||||
|
|
||||||
|
const string LCMasterKeyName = "x-avoscloud-master-key";
|
||||||
|
const string LCHookKeyName = "x-lc-hook-key";
|
||||||
|
|
||||||
|
const string BeforeSave = "__before_save_for_";
|
||||||
|
const string AfterSave = "__after_save_for_";
|
||||||
|
const string BeforeUpdate = "__before_update_for_";
|
||||||
|
const string AfterUpdate = "__after_update_for_";
|
||||||
|
const string BeforeDelete = "__before_delete_for_";
|
||||||
|
const string AfterDelete = "__after_delete_for_";
|
||||||
|
|
||||||
|
internal const string OnSMSVerified = "__on_verified_sms";
|
||||||
|
internal const string OnEmailVerified = "__on_verified_email";
|
||||||
|
internal const string OnLogin = "__on_login__User";
|
||||||
|
|
||||||
|
const string ClientOnline = "_clientOnline";
|
||||||
|
const string ClientOffline = "_clientOffline";
|
||||||
|
|
||||||
|
const string MessageSent = "_messageSent";
|
||||||
|
const string MessageReceived = "_messageReceived";
|
||||||
|
const string ReceiversOffline = "_receiversOffline";
|
||||||
|
const string MessageUpdate = "_messageUpdate";
|
||||||
|
|
||||||
|
const string ConversationStart = "_conversationStart";
|
||||||
|
const string ConversationStarted = "_conversationStarted";
|
||||||
|
const string ConversationAdd = "_conversationAdd";
|
||||||
|
const string ConversationAdded = "_conversationAdded";
|
||||||
|
const string ConversationRemove = "_conversationRemove";
|
||||||
|
const string ConversationRemoved = "_conversationRemoved";
|
||||||
|
const string ConversationUpdate = "_conversationUpdate";
|
||||||
|
|
||||||
|
static readonly string[] LCEngineCORSMethods = new string[] {
|
||||||
|
"PUT",
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"DELETE",
|
||||||
|
"OPTIONS"
|
||||||
|
};
|
||||||
|
static readonly string[] LCEngineCORSHeaders = new string[] {
|
||||||
|
"Content-Type",
|
||||||
|
"X-AVOSCloud-Application-Id",
|
||||||
|
"X-AVOSCloud-Application-Key",
|
||||||
|
"X-AVOSCloud-Application-Production",
|
||||||
|
"X-AVOSCloud-Client-Version",
|
||||||
|
"X-AVOSCloud-Request-Sign",
|
||||||
|
"X-AVOSCloud-Session-Token",
|
||||||
|
"X-AVOSCloud-Super-Key",
|
||||||
|
"X-LC-Hook-Key",
|
||||||
|
"X-LC-Id",
|
||||||
|
"X-LC-Key",
|
||||||
|
"X-LC-Prod",
|
||||||
|
"X-LC-Session",
|
||||||
|
"X-LC-Sign",
|
||||||
|
"X-LC-UA",
|
||||||
|
"X-Requested-With",
|
||||||
|
"X-Uluru-Application-Id",
|
||||||
|
"X-Uluru-Application-Key",
|
||||||
|
"X-Uluru-Application-Production",
|
||||||
|
"X-Uluru-Client-Version",
|
||||||
|
"X-Uluru-Session-Token"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<string, MethodInfo> Functions = new Dictionary<string, MethodInfo>();
|
||||||
|
public static Dictionary<string, MethodInfo> ClassHooks = new Dictionary<string, MethodInfo>();
|
||||||
|
public static Dictionary<string, MethodInfo> UserHooks = new Dictionary<string, MethodInfo>();
|
||||||
|
|
||||||
|
public static void Initialize(IServiceCollection services) {
|
||||||
|
// 获取环境变量
|
||||||
|
LCLogger.Debug("-------------------------------------------------");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_ID");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_KEY");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_MASTER_KEY");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_HOOK_KEY");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_API_SERVER");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_PROD");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_ENV");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_INSTANCE");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_REGION");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_ID");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_DOMAIN");
|
||||||
|
PrintEnvironmentVar("LEANCLOUD_APP_PORT");
|
||||||
|
LCLogger.Debug("-------------------------------------------------");
|
||||||
|
|
||||||
|
LCApplication.Initialize(Environment.GetEnvironmentVariable("LEANCLOUD_APP_ID"),
|
||||||
|
Environment.GetEnvironmentVariable("LEANCLOUD_APP_KEY"),
|
||||||
|
Environment.GetEnvironmentVariable("LEANCLOUD_API_SERVER"));
|
||||||
|
LCApplication.AddHeader(LCHookKeyName, Environment.GetEnvironmentVariable("LEANCLOUD_APP_HOOK_KEY"));
|
||||||
|
|
||||||
|
Assembly assembly = Assembly.GetCallingAssembly();
|
||||||
|
ClassHooks = assembly.GetTypes()
|
||||||
|
.SelectMany(t => t.GetMethods())
|
||||||
|
.Where(m => m.GetCustomAttribute<LCEngineClassHookAttribute>() != null)
|
||||||
|
.ToDictionary(mi => {
|
||||||
|
LCEngineClassHookAttribute attr = mi.GetCustomAttribute<LCEngineClassHookAttribute>();
|
||||||
|
switch (attr.HookType) {
|
||||||
|
case LCEngineObjectHookType.BeforeSave:
|
||||||
|
return $"{BeforeSave}{attr.ClassName}";
|
||||||
|
case LCEngineObjectHookType.AfterSave:
|
||||||
|
return $"{AfterSave}{attr.ClassName}";
|
||||||
|
case LCEngineObjectHookType.BeforeUpdate:
|
||||||
|
return $"{BeforeUpdate}{attr.ClassName}";
|
||||||
|
case LCEngineObjectHookType.AfterUpdate:
|
||||||
|
return $"{AfterUpdate}{attr.ClassName}";
|
||||||
|
case LCEngineObjectHookType.BeforeDelete:
|
||||||
|
return $"{BeforeDelete}{attr.ClassName}";
|
||||||
|
case LCEngineObjectHookType.AfterDelete:
|
||||||
|
return $"{AfterDelete}{attr.ClassName}";
|
||||||
|
default:
|
||||||
|
throw new Exception($"Error hook type: {attr.HookType}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UserHooks = assembly.GetTypes()
|
||||||
|
.SelectMany(t => t.GetMethods())
|
||||||
|
.Where(m => m.GetCustomAttribute<LCEngineUserHookAttribute>() != null)
|
||||||
|
.ToDictionary(mi => {
|
||||||
|
LCEngineUserHookAttribute attr = mi.GetCustomAttribute<LCEngineUserHookAttribute>();
|
||||||
|
switch (attr.HookType) {
|
||||||
|
case LCEngineUserHookType.OnSMSVerified:
|
||||||
|
return OnSMSVerified;
|
||||||
|
case LCEngineUserHookType.OnEmailVerified:
|
||||||
|
return OnEmailVerified;
|
||||||
|
case LCEngineUserHookType.OnLogin:
|
||||||
|
return OnLogin;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Error hook type: {attr.HookType}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Functions = assembly.GetTypes()
|
||||||
|
.SelectMany(t => t.GetMethods())
|
||||||
|
.Where(m => m.GetCustomAttribute<LCEngineFunctionAttribute>() != null)
|
||||||
|
.ToDictionary(mi => mi.GetCustomAttribute<LCEngineFunctionAttribute>().FunctionName);
|
||||||
|
|
||||||
|
assembly.GetTypes()
|
||||||
|
.SelectMany(t => t.GetMethods())
|
||||||
|
.Where(m => m.GetCustomAttribute<LCEngineRealtimeHookAttribute>() != null)
|
||||||
|
.ToDictionary(mi => {
|
||||||
|
LCEngineRealtimeHookAttribute attr = mi.GetCustomAttribute<LCEngineRealtimeHookAttribute>();
|
||||||
|
switch (attr.HookType) {
|
||||||
|
case LCEngineRealtimeHookType.ClientOnline:
|
||||||
|
return ClientOnline;
|
||||||
|
case LCEngineRealtimeHookType.ClientOffline:
|
||||||
|
return ClientOffline;
|
||||||
|
case LCEngineRealtimeHookType.MessageSent:
|
||||||
|
return MessageSent;
|
||||||
|
case LCEngineRealtimeHookType.MessageReceived:
|
||||||
|
return MessageReceived;
|
||||||
|
case LCEngineRealtimeHookType.ReceiversOffline:
|
||||||
|
return ReceiversOffline;
|
||||||
|
case LCEngineRealtimeHookType.MessageUpdate:
|
||||||
|
return MessageUpdate;
|
||||||
|
case LCEngineRealtimeHookType.ConversationStart:
|
||||||
|
return ConversationStart;
|
||||||
|
case LCEngineRealtimeHookType.ConversationStarted:
|
||||||
|
return ConversationStarted;
|
||||||
|
case LCEngineRealtimeHookType.ConversationAdd:
|
||||||
|
return ConversationAdd;
|
||||||
|
case LCEngineRealtimeHookType.ConversationAdded:
|
||||||
|
return ConversationAdded;
|
||||||
|
case LCEngineRealtimeHookType.ConversationRemove:
|
||||||
|
return ConversationRemove;
|
||||||
|
case LCEngineRealtimeHookType.ConversationRemoved:
|
||||||
|
return ConversationRemoved;
|
||||||
|
case LCEngineRealtimeHookType.ConversationUpdate:
|
||||||
|
return ConversationUpdate;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Error hook type: {attr.HookType}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.ForEach(item => {
|
||||||
|
Functions.TryAdd(item.Key, item.Value);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddCors(options => {
|
||||||
|
options.AddPolicy(LCEngineCORS, builder => {
|
||||||
|
builder.AllowAnyOrigin()
|
||||||
|
.WithMethods(LCEngineCORSMethods)
|
||||||
|
.WithHeaders(LCEngineCORSHeaders)
|
||||||
|
.SetPreflightMaxAge(TimeSpan.FromSeconds(86400));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PrintEnvironmentVar(string key) {
|
||||||
|
LCLogger.Debug($"{key} : {Environment.GetEnvironmentVariable(key)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task<object> Invoke(MethodInfo mi, object[] parameters) {
|
||||||
|
try {
|
||||||
|
if (mi.ReturnType == typeof(Task) ||
|
||||||
|
(mi.ReturnType.IsGenericType && mi.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))) {
|
||||||
|
Task task = mi.Invoke(null, parameters) as Task;
|
||||||
|
await task;
|
||||||
|
return task.GetType().GetProperty("Result")?.GetValue(task);
|
||||||
|
}
|
||||||
|
return mi.Invoke(null, parameters);
|
||||||
|
} catch (TargetInvocationException e) {
|
||||||
|
Exception ex = e.InnerException;
|
||||||
|
if (ex is LCException lcEx) {
|
||||||
|
throw new Exception(JsonConvert.SerializeObject(new Dictionary<string, object> {
|
||||||
|
{ "code", lcEx.Code },
|
||||||
|
{ "message", lcEx.Message }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task<object> Invoke(MethodInfo mi, object request) {
|
||||||
|
try {
|
||||||
|
object[] ps = null;
|
||||||
|
if (mi.GetParameters().Length > 0) {
|
||||||
|
ps = new object[] { request };
|
||||||
|
}
|
||||||
|
if (mi.ReturnType == typeof(Task) ||
|
||||||
|
(mi.ReturnType.IsGenericType && mi.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))) {
|
||||||
|
Task task = mi.Invoke(null, ps) as Task;
|
||||||
|
await task;
|
||||||
|
return task.GetType().GetProperty("Result")?.GetValue(task);
|
||||||
|
}
|
||||||
|
return mi.Invoke(null, ps);
|
||||||
|
} catch (TargetInvocationException e) {
|
||||||
|
Exception ex = e.InnerException;
|
||||||
|
if (ex is LCException lcEx) {
|
||||||
|
throw new Exception(JsonConvert.SerializeObject(new Dictionary<string, object> {
|
||||||
|
{ "code", lcEx.Code },
|
||||||
|
{ "message", lcEx.Message }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Dictionary<string, object> Decode(JsonElement jsonElement) {
|
||||||
|
string json = System.Text.Json.JsonSerializer.Serialize(jsonElement);
|
||||||
|
Dictionary<string, object> dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json,
|
||||||
|
LCJsonConverter.Default);
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void InitRequestContext(HttpRequest request) {
|
||||||
|
LCEngineRequestContext.Init();
|
||||||
|
|
||||||
|
LCEngineRequestContext.RemoteAddress = GetIP(request);
|
||||||
|
|
||||||
|
if (request.Headers.TryGetValue("x-lc-session", out StringValues session)) {
|
||||||
|
LCEngineRequestContext.SessionToken = session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetIP(HttpRequest request) {
|
||||||
|
if (request.Headers.TryGetValue("x-real-ip", out StringValues ip)) {
|
||||||
|
return ip.ToString();
|
||||||
|
}
|
||||||
|
if (request.Headers.TryGetValue("x-forwarded-for", out StringValues forward)) {
|
||||||
|
return forward.ToString();
|
||||||
|
}
|
||||||
|
return request.HttpContext.Connection.RemoteIpAddress.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void CheckMasterKey(HttpRequest request) {
|
||||||
|
if (!request.Headers.TryGetValue(LCMasterKeyName, out StringValues masterKey)) {
|
||||||
|
throw new Exception("No master key");
|
||||||
|
}
|
||||||
|
if (!masterKey.Equals(Environment.GetEnvironmentVariable("LEANCLOUD_APP_MASTER_KEY"))) {
|
||||||
|
throw new Exception("Mismatch master key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void CheckHookKey(HttpRequest request) {
|
||||||
|
if (!request.Headers.TryGetValue(LCHookKeyName, out StringValues hookKey)) {
|
||||||
|
throw new Exception("No hook key");
|
||||||
|
}
|
||||||
|
if (!hookKey.Equals(Environment.GetEnvironmentVariable("LEANCLOUD_APP_HOOK_KEY"))) {
|
||||||
|
throw new Exception("Mismatch hook key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetFunctions(HttpRequest request) {
|
||||||
|
CheckMasterKey(request);
|
||||||
|
|
||||||
|
List<string> functions = new List<string>();
|
||||||
|
functions.AddRange(Functions.Keys);
|
||||||
|
functions.AddRange(ClassHooks.Keys);
|
||||||
|
functions.AddRange(UserHooks.Keys);
|
||||||
|
foreach (string func in functions) {
|
||||||
|
LCLogger.Debug(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Dictionary<string, List<string>> {
|
||||||
|
{ "result", functions }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Engine {
|
||||||
|
public class LCEngineRequestContext {
|
||||||
|
public const string RemoteAddressKey = "__remoteAddressKey";
|
||||||
|
public const string SessionTokenKey = "__sessionToken";
|
||||||
|
public const string CurrentUserKey = "__currentUser";
|
||||||
|
|
||||||
|
private static ThreadLocal<Dictionary<string, object>> requestContext = new ThreadLocal<Dictionary<string, object>>();
|
||||||
|
|
||||||
|
public static void Init() {
|
||||||
|
if (requestContext.IsValueCreated) {
|
||||||
|
requestContext.Value.Clear();
|
||||||
|
}
|
||||||
|
requestContext.Value = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Set(string key, object value) {
|
||||||
|
if (!requestContext.IsValueCreated) {
|
||||||
|
requestContext.Value = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
requestContext.Value[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object Get(string key) {
|
||||||
|
if (!requestContext.IsValueCreated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return requestContext.Value[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RemoteAddress {
|
||||||
|
get {
|
||||||
|
object remoteAddress = Get(RemoteAddressKey);
|
||||||
|
if (remoteAddress != null) {
|
||||||
|
return remoteAddress as string;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
Set(RemoteAddressKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SessionToken {
|
||||||
|
get {
|
||||||
|
object sessionToken = Get(SessionTokenKey);
|
||||||
|
if (sessionToken != null) {
|
||||||
|
return sessionToken as string;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
Set(SessionTokenKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LCUser CurrentUser {
|
||||||
|
get {
|
||||||
|
object currentUser = Get(CurrentUserKey);
|
||||||
|
if (currentUser != null) {
|
||||||
|
return currentUser as LCUser;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
Set(CurrentUserKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace Storage.Test {
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() {
|
public void SetUp() {
|
||||||
LCLogger.LogDelegate += Utils.Print;
|
LCLogger.LogDelegate += Utils.Print;
|
||||||
LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
|
//LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
|
||||||
|
LCApplication.Initialize("8ijVI3gBAnPGynW0rVfh5gHP-gzGzoHsz", "265r8JSHhNYpV0qIJBvUWrQY", "https://8ijvi3gb.lc-cn-n1-shared.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
@ -24,7 +25,16 @@ namespace Storage.Test {
|
||||||
{ "name", "world" }
|
{ "name", "world" }
|
||||||
});
|
});
|
||||||
TestContext.WriteLine(response["result"]);
|
TestContext.WriteLine(response["result"]);
|
||||||
Assert.AreEqual(response["result"], "hello, world");
|
Assert.AreEqual(response["result"], "Hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task RunAverageScore() {
|
||||||
|
float score = await LCCloud.Run<float>("averageStars", new Dictionary<string, object> {
|
||||||
|
{ "movie", "夏洛特烦恼" }
|
||||||
|
});
|
||||||
|
TestContext.WriteLine($"score: {score}");
|
||||||
|
Assert.True(score.Equals(3.8f));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -41,5 +51,35 @@ namespace Storage.Test {
|
||||||
Assert.NotNull(item.ObjectId);
|
Assert.NotNull(item.ObjectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task RPCObject() {
|
||||||
|
LCQuery<LCObject> query = new LCQuery<LCObject>("Todo");
|
||||||
|
LCObject todo = await query.Get("6052cd87b725a143ea83dbf8");
|
||||||
|
object result = await LCCloud.RPC("getTodo", todo);
|
||||||
|
LCObject obj = result as LCObject;
|
||||||
|
TestContext.WriteLine(obj.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task RPCObjects() {
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object> {
|
||||||
|
{ "limit", 20 }
|
||||||
|
};
|
||||||
|
List<object> result = await LCCloud.RPC("getTodos", parameters) as List<object>;
|
||||||
|
IEnumerable<LCObject> todos = result.Cast<LCObject>();
|
||||||
|
foreach (LCObject todo in todos) {
|
||||||
|
TestContext.WriteLine(todo.ObjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task RPCObjectMap() {
|
||||||
|
Dictionary<string, object> result = await LCCloud.RPC("getTodoMap") as Dictionary<string, object>;
|
||||||
|
foreach (KeyValuePair<string, object> kv in result) {
|
||||||
|
LCObject todo = kv.Value as LCObject;
|
||||||
|
TestContext.WriteLine(todo.ObjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace LeanCloud.Storage.Internal.Codec {
|
||||||
return DecodeBytes(dict);
|
return DecodeBytes(dict);
|
||||||
} else if (type == "Object") {
|
} else if (type == "Object") {
|
||||||
return DecodeObject(dict);
|
return DecodeObject(dict);
|
||||||
} else if (type == "Pointer") {
|
} else if (type == "Pointer" || type == "Object") {
|
||||||
return DecodeObject(dict);
|
return DecodeObject(dict);
|
||||||
} else if (type == "Relation") {
|
} else if (type == "Relation") {
|
||||||
return DecodeRelation(dict);
|
return DecodeRelation(dict);
|
||||||
|
@ -80,10 +80,10 @@ namespace LeanCloud.Storage.Internal.Codec {
|
||||||
string key = kv.Key;
|
string key = kv.Key;
|
||||||
Dictionary<string, object> access = kv.Value as Dictionary<string, object>;
|
Dictionary<string, object> access = kv.Value as Dictionary<string, object>;
|
||||||
if (access.TryGetValue("read", out object ra)) {
|
if (access.TryGetValue("read", out object ra)) {
|
||||||
acl.readAccess[key] = Convert.ToBoolean(ra);
|
acl.ReadAccess[key] = Convert.ToBoolean(ra);
|
||||||
}
|
}
|
||||||
if (access.TryGetValue("write", out object wa)) {
|
if (access.TryGetValue("write", out object wa)) {
|
||||||
acl.writeAccess[key] = Convert.ToBoolean(wa);
|
acl.WriteAccess[key] = Convert.ToBoolean(wa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return acl;
|
return acl;
|
||||||
|
|
|
@ -84,19 +84,19 @@ namespace LeanCloud.Storage.Internal.Codec {
|
||||||
|
|
||||||
public static object EncodeACL(LCACL acl) {
|
public static object EncodeACL(LCACL acl) {
|
||||||
HashSet<string> keys = new HashSet<string>();
|
HashSet<string> keys = new HashSet<string>();
|
||||||
if (acl.readAccess.Count > 0) {
|
if (acl.ReadAccess.Count > 0) {
|
||||||
keys.UnionWith(acl.readAccess.Keys);
|
keys.UnionWith(acl.ReadAccess.Keys);
|
||||||
}
|
}
|
||||||
if (acl.writeAccess.Count > 0) {
|
if (acl.WriteAccess.Count > 0) {
|
||||||
keys.UnionWith(acl.writeAccess.Keys);
|
keys.UnionWith(acl.WriteAccess.Keys);
|
||||||
}
|
}
|
||||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||||
foreach (string key in keys) {
|
foreach (string key in keys) {
|
||||||
Dictionary<string, bool> access = new Dictionary<string, bool>();
|
Dictionary<string, bool> access = new Dictionary<string, bool>();
|
||||||
if (acl.readAccess.TryGetValue(key, out bool ra)) {
|
if (acl.ReadAccess.TryGetValue(key, out bool ra)) {
|
||||||
access["read"] = ra;
|
access["read"] = ra;
|
||||||
}
|
}
|
||||||
if (acl.writeAccess.TryGetValue(key, out bool wa)) {
|
if (acl.WriteAccess.TryGetValue(key, out bool wa)) {
|
||||||
access["write"] = wa;
|
access["write"] = wa;
|
||||||
}
|
}
|
||||||
result[key] = access;
|
result[key] = access;
|
||||||
|
|
|
@ -148,6 +148,11 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
string sign = $"{hash},{timestamp}";
|
string sign = $"{hash},{timestamp}";
|
||||||
headers.Add("X-LC-Sign", sign);
|
headers.Add("X-LC-Sign", sign);
|
||||||
}
|
}
|
||||||
|
if (LCApplication.AdditionalHeaders.Count > 0) {
|
||||||
|
foreach (KeyValuePair<string, string> kv in LCApplication.AdditionalHeaders) {
|
||||||
|
headers.Add(kv.Key, kv.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 当前用户 Session Token
|
// 当前用户 Session Token
|
||||||
LCUser currentUser = await LCUser.GetCurrent();
|
LCUser currentUser = await LCUser.GetCurrent();
|
||||||
if (!headers.Contains("X-LC-Session") && currentUser != null) {
|
if (!headers.Contains("X-LC-Session") && currentUser != null) {
|
||||||
|
|
|
@ -65,10 +65,10 @@ namespace LeanCloud.Storage.Internal.Object {
|
||||||
if (!string.IsNullOrEmpty(objectData.ObjectId)) {
|
if (!string.IsNullOrEmpty(objectData.ObjectId)) {
|
||||||
dict["objectId"] = objectData.ObjectId;
|
dict["objectId"] = objectData.ObjectId;
|
||||||
}
|
}
|
||||||
if (objectData.CreatedAt != null) {
|
if (!objectData.CreatedAt.Equals(default)) {
|
||||||
dict["createdAt"] = objectData.CreatedAt.ToUniversalTime();
|
dict["createdAt"] = objectData.CreatedAt.ToUniversalTime();
|
||||||
}
|
}
|
||||||
if (objectData.UpdatedAt != null) {
|
if (!objectData.UpdatedAt.Equals(default)) {
|
||||||
dict["updatedAt"] = objectData.UpdatedAt.ToUniversalTime();
|
dict["updatedAt"] = objectData.UpdatedAt.ToUniversalTime();
|
||||||
}
|
}
|
||||||
if (objectData.CustomPropertyDict != null) {
|
if (objectData.CustomPropertyDict != null) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal.Operation {
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
internal interface ILCOperation {
|
internal interface ILCOperation {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
internal class LCIgnoreHookOperation : ILCOperation {
|
||||||
|
internal HashSet<string> ignoreHooks;
|
||||||
|
|
||||||
|
internal LCIgnoreHookOperation(IEnumerable<string> hooks) {
|
||||||
|
ignoreHooks = new HashSet<string>(hooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Apply(object oldValue, string key) {
|
||||||
|
HashSet<object> set = new HashSet<object>();
|
||||||
|
if (oldValue != null) {
|
||||||
|
set.UnionWith(oldValue as IEnumerable<object>);
|
||||||
|
}
|
||||||
|
set.UnionWith(ignoreHooks);
|
||||||
|
return set.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Encode() {
|
||||||
|
return ignoreHooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable GetNewObjectList() {
|
||||||
|
return ignoreHooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||||
|
if (previousOp is LCIgnoreHookOperation ignoreHookOp) {
|
||||||
|
ignoreHooks.UnionWith(ignoreHookOp.ignoreHooks);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,13 @@ namespace LeanCloud.Storage {
|
||||||
|
|
||||||
const string RoleKeyPrefix = "role:";
|
const string RoleKeyPrefix = "role:";
|
||||||
|
|
||||||
internal Dictionary<string, bool> readAccess = new Dictionary<string, bool>();
|
public Dictionary<string, bool> ReadAccess {
|
||||||
internal Dictionary<string, bool> writeAccess = new Dictionary<string, bool>();
|
get;
|
||||||
|
} = new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
public Dictionary<string, bool> WriteAccess {
|
||||||
|
get;
|
||||||
|
} = new Dictionary<string, bool>();
|
||||||
|
|
||||||
public static LCACL CreateWithOwner(LCUser owner) {
|
public static LCACL CreateWithOwner(LCUser owner) {
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
|
@ -25,17 +30,17 @@ namespace LeanCloud.Storage {
|
||||||
|
|
||||||
public bool PublicReadAccess {
|
public bool PublicReadAccess {
|
||||||
get {
|
get {
|
||||||
return GetAccess(readAccess, PublicKey);
|
return GetAccess(ReadAccess, PublicKey);
|
||||||
} set {
|
} set {
|
||||||
SetAccess(readAccess, PublicKey, value);
|
SetAccess(ReadAccess, PublicKey, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool PublicWriteAccess {
|
public bool PublicWriteAccess {
|
||||||
get {
|
get {
|
||||||
return GetAccess(writeAccess, PublicKey);
|
return GetAccess(WriteAccess, PublicKey);
|
||||||
} set {
|
} set {
|
||||||
SetAccess(writeAccess, PublicKey, value);
|
SetAccess(WriteAccess, PublicKey, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,28 +48,28 @@ namespace LeanCloud.Storage {
|
||||||
if (string.IsNullOrEmpty(userId)) {
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
throw new ArgumentNullException(nameof(userId));
|
throw new ArgumentNullException(nameof(userId));
|
||||||
}
|
}
|
||||||
return GetAccess(readAccess, userId);
|
return GetAccess(ReadAccess, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUserIdReadAccess(string userId, bool value) {
|
public void SetUserIdReadAccess(string userId, bool value) {
|
||||||
if (string.IsNullOrEmpty(userId)) {
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
throw new ArgumentNullException(nameof(userId));
|
throw new ArgumentNullException(nameof(userId));
|
||||||
}
|
}
|
||||||
SetAccess(readAccess, userId, value);
|
SetAccess(ReadAccess, userId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetUserIdWriteAccess(string userId) {
|
public bool GetUserIdWriteAccess(string userId) {
|
||||||
if (string.IsNullOrEmpty(userId)) {
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
throw new ArgumentNullException(nameof(userId));
|
throw new ArgumentNullException(nameof(userId));
|
||||||
}
|
}
|
||||||
return GetAccess(writeAccess, userId);
|
return GetAccess(WriteAccess, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUserIdWriteAccess(string userId, bool value) {
|
public void SetUserIdWriteAccess(string userId, bool value) {
|
||||||
if (string.IsNullOrEmpty(userId)) {
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
throw new ArgumentNullException(nameof(userId));
|
throw new ArgumentNullException(nameof(userId));
|
||||||
}
|
}
|
||||||
SetAccess(writeAccess, userId, value);
|
SetAccess(WriteAccess, userId, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetUserReadAccess(LCUser user) {
|
public bool GetUserReadAccess(LCUser user) {
|
||||||
|
@ -100,7 +105,7 @@ namespace LeanCloud.Storage {
|
||||||
throw new ArgumentNullException(nameof(role));
|
throw new ArgumentNullException(nameof(role));
|
||||||
}
|
}
|
||||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
return GetAccess(readAccess, roleKey);
|
return GetAccess(ReadAccess, roleKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRoleReadAccess(LCRole role, bool value) {
|
public void SetRoleReadAccess(LCRole role, bool value) {
|
||||||
|
@ -108,7 +113,7 @@ namespace LeanCloud.Storage {
|
||||||
throw new ArgumentNullException(nameof(role));
|
throw new ArgumentNullException(nameof(role));
|
||||||
}
|
}
|
||||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
SetAccess(readAccess, roleKey, value);
|
SetAccess(ReadAccess, roleKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetRoleWriteAccess(LCRole role) {
|
public bool GetRoleWriteAccess(LCRole role) {
|
||||||
|
@ -116,7 +121,7 @@ namespace LeanCloud.Storage {
|
||||||
throw new ArgumentNullException(nameof(role));
|
throw new ArgumentNullException(nameof(role));
|
||||||
}
|
}
|
||||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
return GetAccess(writeAccess, roleKey);
|
return GetAccess(WriteAccess, roleKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRoleWriteAccess(LCRole role, bool value) {
|
public void SetRoleWriteAccess(LCRole role, bool value) {
|
||||||
|
@ -124,7 +129,7 @@ namespace LeanCloud.Storage {
|
||||||
throw new ArgumentNullException(nameof(role));
|
throw new ArgumentNullException(nameof(role));
|
||||||
}
|
}
|
||||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
SetAccess(writeAccess, roleKey, value);
|
SetAccess(WriteAccess, roleKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetAccess(Dictionary<string, bool> access, string key) {
|
bool GetAccess(Dictionary<string, bool> access, string key) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using LeanCloud.Common;
|
using LeanCloud.Common;
|
||||||
using LeanCloud.Storage;
|
using LeanCloud.Storage;
|
||||||
using LeanCloud.Storage.Internal.Http;
|
using LeanCloud.Storage.Internal.Http;
|
||||||
|
@ -9,7 +10,7 @@ namespace LeanCloud {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LCApplication {
|
public class LCApplication {
|
||||||
// SDK 版本号,用于 User-Agent 统计
|
// SDK 版本号,用于 User-Agent 统计
|
||||||
internal const string SDKVersion = "0.6.4";
|
public const string SDKVersion = "0.6.4";
|
||||||
|
|
||||||
// 接口版本号,用于接口版本管理
|
// 接口版本号,用于接口版本管理
|
||||||
internal const string APIVersion = "1.1";
|
internal const string APIVersion = "1.1";
|
||||||
|
@ -42,6 +43,10 @@ namespace LeanCloud {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static Dictionary<string, string> AdditionalHeaders {
|
||||||
|
get;
|
||||||
|
} = new Dictionary<string, string>();
|
||||||
|
|
||||||
public static void Initialize(string appId,
|
public static void Initialize(string appId,
|
||||||
string appKey,
|
string appKey,
|
||||||
string server = null,
|
string server = null,
|
||||||
|
@ -68,5 +73,9 @@ namespace LeanCloud {
|
||||||
|
|
||||||
HttpClient = new LCHttpClient(appId, appKey, server, SDKVersion, APIVersion);
|
HttpClient = new LCHttpClient(appId, appKey, server, SDKVersion, APIVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddHeader(string key, string value) {
|
||||||
|
AdditionalHeaders.Add(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
|
||||||
namespace LeanCloud.Storage {
|
namespace LeanCloud.Storage {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -35,6 +36,15 @@ namespace LeanCloud.Storage {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<T> Run<T>(string name,
|
||||||
|
Dictionary<string, object> parameters = null) {
|
||||||
|
Dictionary<string, object> response = await Run(name, parameters);
|
||||||
|
if (response.TryGetValue("result", out object result)) {
|
||||||
|
return (T)result;
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes a cloud function as a remote procedure call.
|
/// Invokes a cloud function as a remote procedure call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -46,11 +56,48 @@ namespace LeanCloud.Storage {
|
||||||
Dictionary<string, object> headers = new Dictionary<string, object> {
|
Dictionary<string, object> headers = new Dictionary<string, object> {
|
||||||
{ PRODUCTION_KEY, IsProduction ? 1 : 0 }
|
{ PRODUCTION_KEY, IsProduction ? 1 : 0 }
|
||||||
};
|
};
|
||||||
object encodeParams = LCEncoder.Encode(parameters);
|
object encodeParams = Encode(parameters);
|
||||||
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
|
Dictionary<string, object> response = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
data: encodeParams);
|
data: encodeParams);
|
||||||
return LCDecoder.Decode(response["result"]);
|
return LCDecoder.Decode(response["result"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static object Encode(object parameters) {
|
||||||
|
if (parameters == null) {
|
||||||
|
return new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters is LCObject lcObj) {
|
||||||
|
return EncodeLCObject(lcObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters is IList<LCObject> list) {
|
||||||
|
List<object> l = new List<object>();
|
||||||
|
foreach (LCObject obj in list) {
|
||||||
|
l.Add(EncodeLCObject(obj));
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters is IDictionary<string, LCObject> dict) {
|
||||||
|
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||||
|
foreach (KeyValuePair<string, LCObject> item in dict) {
|
||||||
|
d[item.Key] = EncodeLCObject(item.Value);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeLCObject(LCObject obj) {
|
||||||
|
Dictionary<string, object> dict = LCObjectData.Encode(obj.data);
|
||||||
|
dict["__type"] = "Object";
|
||||||
|
foreach (KeyValuePair<string, object> kv in obj.estimatedData) {
|
||||||
|
dict[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using LeanCloud.Storage.Internal.Operation;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public static class LCClassHook {
|
||||||
|
public const string BeforeSave = "beforeSave";
|
||||||
|
public const string AfterSave = "afterSave";
|
||||||
|
public const string BeforeUpdate = "beforeUpdate";
|
||||||
|
public const string AfterUpdate = "afterUpdate";
|
||||||
|
public const string BeforeDelete = "beforeDelete";
|
||||||
|
public const string AfterDelete = "afterDelete";
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class LCObject {
|
||||||
|
internal const string IgnoreHooksKey = "__ignore_hooks";
|
||||||
|
|
||||||
|
public void DisableBeforeHook() {
|
||||||
|
Ignore(
|
||||||
|
LCClassHook.BeforeSave,
|
||||||
|
LCClassHook.BeforeUpdate,
|
||||||
|
LCClassHook.BeforeDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableAfterHook() {
|
||||||
|
Ignore(
|
||||||
|
LCClassHook.AfterSave,
|
||||||
|
LCClassHook.AfterUpdate,
|
||||||
|
LCClassHook.AfterDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void IgnoreHook(string hookName) {
|
||||||
|
if (hookName != LCClassHook.BeforeSave && hookName != LCClassHook.AfterSave &&
|
||||||
|
hookName != LCClassHook.BeforeUpdate && hookName != LCClassHook.AfterUpdate &&
|
||||||
|
hookName != LCClassHook.BeforeDelete && hookName != LCClassHook.AfterDelete) {
|
||||||
|
throw new ArgumentException($"Invalid {hookName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ignore(hookName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Ignore(params string[] hooks) {
|
||||||
|
LCIgnoreHookOperation op = new LCIgnoreHookOperation(hooks);
|
||||||
|
ApplyOperation(IgnoreHooksKey, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<string> GetUpdatedKeys() {
|
||||||
|
if (this["_updatedKeys"] == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this["_updatedKeys"] as List<object>)
|
||||||
|
.Cast<string>()
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,11 +12,11 @@ namespace LeanCloud.Storage {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// LeanCloud Object
|
/// LeanCloud Object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LCObject {
|
public partial class LCObject {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last synced data.
|
/// Last synced data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LCObjectData data;
|
internal LCObjectData data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Estimated data.
|
/// Estimated data.
|
||||||
|
@ -349,18 +349,18 @@ namespace LeanCloud.Storage {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<List<LCObject>> SaveAll(List<LCObject> objectList) {
|
public static async Task<List<LCObject>> SaveAll(IEnumerable<LCObject> objects) {
|
||||||
if (objectList == null) {
|
if (objects == null) {
|
||||||
throw new ArgumentNullException(nameof(objectList));
|
throw new ArgumentNullException(nameof(objects));
|
||||||
}
|
}
|
||||||
foreach (LCObject obj in objectList) {
|
foreach (LCObject obj in objects) {
|
||||||
if (LCBatch.HasCircleReference(obj, new HashSet<LCObject>())) {
|
if (LCBatch.HasCircleReference(obj, new HashSet<LCObject>())) {
|
||||||
throw new ArgumentException("Found a circle dependency when save.");
|
throw new ArgumentException("Found a circle dependency when save.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stack<LCBatch> batches = LCBatch.BatchObjects(objectList, true);
|
Stack<LCBatch> batches = LCBatch.BatchObjects(objects, true);
|
||||||
await SaveBatches(batches);
|
await SaveBatches(batches);
|
||||||
return objectList;
|
return objects.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete() {
|
public async Task Delete() {
|
||||||
|
@ -371,12 +371,11 @@ namespace LeanCloud.Storage {
|
||||||
await LCApplication.HttpClient.Delete(path);
|
await LCApplication.HttpClient.Delete(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task DeleteAll(List<LCObject> objectList) {
|
public static async Task DeleteAll(IEnumerable<LCObject> objects) {
|
||||||
if (objectList == null || objectList.Count == 0) {
|
if (objects == null || objects.Count() == 0) {
|
||||||
throw new ArgumentNullException(nameof(objectList));
|
throw new ArgumentNullException(nameof(objects));
|
||||||
}
|
}
|
||||||
IEnumerable<LCObject> objects = objectList.Where(item => item.ObjectId != null);
|
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects.Where(item => item.ObjectId != null));
|
||||||
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects);
|
|
||||||
List<Dictionary<string, object>> requestList = objectSet.Select(item => {
|
List<Dictionary<string, object>> requestList = objectSet.Select(item => {
|
||||||
string path = $"/{LCApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
|
string path = $"/{LCApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
|
||||||
return new Dictionary<string, object> {
|
return new Dictionary<string, object> {
|
||||||
|
|
|
@ -37,6 +37,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery-Unity", "LiveQuer
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQueryApp", "Sample\LiveQueryApp\LiveQueryApp.csproj", "{9D5E6A37-8925-48ED-B7EA-12C89291B59D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQueryApp", "Sample\LiveQueryApp\LiveQueryApp.csproj", "{9D5E6A37-8925-48ED-B7EA-12C89291B59D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{8087ABCD-629C-4EE5-9ECE-8BDAE631236F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{0A6AEBC9-9A36-4EA7-8F58-8B951126092D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -95,6 +99,10 @@ Global
|
||||||
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9D5E6A37-8925-48ED-B7EA-12C89291B59D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{26CDAE2A-6D79-4981-8D80-3EA34FDFB134} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A}
|
{26CDAE2A-6D79-4981-8D80-3EA34FDFB134} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A}
|
||||||
|
@ -110,6 +118,7 @@ Global
|
||||||
{0F61B6D7-4948-4D98-B6CC-41CF33B55669} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}
|
{0F61B6D7-4948-4D98-B6CC-41CF33B55669} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}
|
||||||
{12482E48-C0CF-46B1-8FDD-5885D1B7DC4D} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}
|
{12482E48-C0CF-46B1-8FDD-5885D1B7DC4D} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}
|
||||||
{9D5E6A37-8925-48ED-B7EA-12C89291B59D} = {2D980281-F060-4363-AB7A-D4B6C30ADDBB}
|
{9D5E6A37-8925-48ED-B7EA-12C89291B59D} = {2D980281-F060-4363-AB7A-D4B6C30ADDBB}
|
||||||
|
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D} = {8087ABCD-629C-4EE5-9ECE-8BDAE631236F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(MonoDevelopProperties) = preSolution
|
GlobalSection(MonoDevelopProperties) = preSolution
|
||||||
version = 0.6.4
|
version = 0.6.4
|
||||||
|
|
Loading…
Reference in New Issue