Merge pull request #107 from onerain88/leanengine
LeanEngine C# SDK
commit
6139bdd914
|
@ -28,6 +28,9 @@ namespace LeanCloud.Common {
|
|||
return Convert.ToInt32(reader.Value);
|
||||
}
|
||||
}
|
||||
if (reader.TokenType == JsonToken.Float) {
|
||||
return Convert.ToSingle(reader.Value);
|
||||
}
|
||||
|
||||
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">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ namespace Storage.Test {
|
|||
[SetUp]
|
||||
public void SetUp() {
|
||||
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]
|
||||
|
@ -24,7 +25,16 @@ namespace Storage.Test {
|
|||
{ "name", "world" }
|
||||
});
|
||||
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]
|
||||
|
@ -41,5 +51,35 @@ namespace Storage.Test {
|
|||
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">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.6.4</ReleaseVersion>
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace LeanCloud.Storage.Internal.Codec {
|
|||
return DecodeBytes(dict);
|
||||
} else if (type == "Object") {
|
||||
return DecodeObject(dict);
|
||||
} else if (type == "Pointer") {
|
||||
} else if (type == "Pointer" || type == "Object") {
|
||||
return DecodeObject(dict);
|
||||
} else if (type == "Relation") {
|
||||
return DecodeRelation(dict);
|
||||
|
@ -80,10 +80,10 @@ namespace LeanCloud.Storage.Internal.Codec {
|
|||
string key = kv.Key;
|
||||
Dictionary<string, object> access = kv.Value as Dictionary<string, object>;
|
||||
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)) {
|
||||
acl.writeAccess[key] = Convert.ToBoolean(wa);
|
||||
acl.WriteAccess[key] = Convert.ToBoolean(wa);
|
||||
}
|
||||
}
|
||||
return acl;
|
||||
|
|
|
@ -84,19 +84,19 @@ namespace LeanCloud.Storage.Internal.Codec {
|
|||
|
||||
public static object EncodeACL(LCACL acl) {
|
||||
HashSet<string> keys = new HashSet<string>();
|
||||
if (acl.readAccess.Count > 0) {
|
||||
keys.UnionWith(acl.readAccess.Keys);
|
||||
if (acl.ReadAccess.Count > 0) {
|
||||
keys.UnionWith(acl.ReadAccess.Keys);
|
||||
}
|
||||
if (acl.writeAccess.Count > 0) {
|
||||
keys.UnionWith(acl.writeAccess.Keys);
|
||||
if (acl.WriteAccess.Count > 0) {
|
||||
keys.UnionWith(acl.WriteAccess.Keys);
|
||||
}
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
foreach (string key in keys) {
|
||||
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;
|
||||
}
|
||||
if (acl.writeAccess.TryGetValue(key, out bool wa)) {
|
||||
if (acl.WriteAccess.TryGetValue(key, out bool wa)) {
|
||||
access["write"] = wa;
|
||||
}
|
||||
result[key] = access;
|
||||
|
|
|
@ -148,6 +148,11 @@ namespace LeanCloud.Storage.Internal.Http {
|
|||
string sign = $"{hash},{timestamp}";
|
||||
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
|
||||
LCUser currentUser = await LCUser.GetCurrent();
|
||||
if (!headers.Contains("X-LC-Session") && currentUser != null) {
|
||||
|
|
|
@ -65,10 +65,10 @@ namespace LeanCloud.Storage.Internal.Object {
|
|||
if (!string.IsNullOrEmpty(objectData.ObjectId)) {
|
||||
dict["objectId"] = objectData.ObjectId;
|
||||
}
|
||||
if (objectData.CreatedAt != null) {
|
||||
if (!objectData.CreatedAt.Equals(default)) {
|
||||
dict["createdAt"] = objectData.CreatedAt.ToUniversalTime();
|
||||
}
|
||||
if (objectData.UpdatedAt != null) {
|
||||
if (!objectData.UpdatedAt.Equals(default)) {
|
||||
dict["updatedAt"] = objectData.UpdatedAt.ToUniversalTime();
|
||||
}
|
||||
if (objectData.CustomPropertyDict != null) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
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:";
|
||||
|
||||
internal Dictionary<string, bool> readAccess = new Dictionary<string, bool>();
|
||||
internal Dictionary<string, bool> writeAccess = new Dictionary<string, bool>();
|
||||
public Dictionary<string, bool> ReadAccess {
|
||||
get;
|
||||
} = new Dictionary<string, bool>();
|
||||
|
||||
public Dictionary<string, bool> WriteAccess {
|
||||
get;
|
||||
} = new Dictionary<string, bool>();
|
||||
|
||||
public static LCACL CreateWithOwner(LCUser owner) {
|
||||
if (owner == null) {
|
||||
|
@ -25,17 +30,17 @@ namespace LeanCloud.Storage {
|
|||
|
||||
public bool PublicReadAccess {
|
||||
get {
|
||||
return GetAccess(readAccess, PublicKey);
|
||||
return GetAccess(ReadAccess, PublicKey);
|
||||
} set {
|
||||
SetAccess(readAccess, PublicKey, value);
|
||||
SetAccess(ReadAccess, PublicKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool PublicWriteAccess {
|
||||
get {
|
||||
return GetAccess(writeAccess, PublicKey);
|
||||
return GetAccess(WriteAccess, PublicKey);
|
||||
} set {
|
||||
SetAccess(writeAccess, PublicKey, value);
|
||||
SetAccess(WriteAccess, PublicKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,28 +48,28 @@ namespace LeanCloud.Storage {
|
|||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
return GetAccess(readAccess, userId);
|
||||
return GetAccess(ReadAccess, userId);
|
||||
}
|
||||
|
||||
public void SetUserIdReadAccess(string userId, bool value) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
SetAccess(readAccess, userId, value);
|
||||
SetAccess(ReadAccess, userId, value);
|
||||
}
|
||||
|
||||
public bool GetUserIdWriteAccess(string userId) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
return GetAccess(writeAccess, userId);
|
||||
return GetAccess(WriteAccess, userId);
|
||||
}
|
||||
|
||||
public void SetUserIdWriteAccess(string userId, bool value) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
SetAccess(writeAccess, userId, value);
|
||||
SetAccess(WriteAccess, userId, value);
|
||||
}
|
||||
|
||||
public bool GetUserReadAccess(LCUser user) {
|
||||
|
@ -100,7 +105,7 @@ namespace LeanCloud.Storage {
|
|||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
return GetAccess(readAccess, roleKey);
|
||||
return GetAccess(ReadAccess, roleKey);
|
||||
}
|
||||
|
||||
public void SetRoleReadAccess(LCRole role, bool value) {
|
||||
|
@ -108,7 +113,7 @@ namespace LeanCloud.Storage {
|
|||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
SetAccess(readAccess, roleKey, value);
|
||||
SetAccess(ReadAccess, roleKey, value);
|
||||
}
|
||||
|
||||
public bool GetRoleWriteAccess(LCRole role) {
|
||||
|
@ -116,7 +121,7 @@ namespace LeanCloud.Storage {
|
|||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
return GetAccess(writeAccess, roleKey);
|
||||
return GetAccess(WriteAccess, roleKey);
|
||||
}
|
||||
|
||||
public void SetRoleWriteAccess(LCRole role, bool value) {
|
||||
|
@ -124,7 +129,7 @@ namespace LeanCloud.Storage {
|
|||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
SetAccess(writeAccess, roleKey, value);
|
||||
SetAccess(WriteAccess, roleKey, value);
|
||||
}
|
||||
|
||||
bool GetAccess(Dictionary<string, bool> access, string key) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Common;
|
||||
using LeanCloud.Storage;
|
||||
using LeanCloud.Storage.Internal.Http;
|
||||
|
@ -9,7 +10,7 @@ namespace LeanCloud {
|
|||
/// </summary>
|
||||
public class LCApplication {
|
||||
// SDK 版本号,用于 User-Agent 统计
|
||||
internal const string SDKVersion = "0.6.4";
|
||||
public const string SDKVersion = "0.6.4";
|
||||
|
||||
// 接口版本号,用于接口版本管理
|
||||
internal const string APIVersion = "1.1";
|
||||
|
@ -42,6 +43,10 @@ namespace LeanCloud {
|
|||
get; set;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, string> AdditionalHeaders {
|
||||
get;
|
||||
} = new Dictionary<string, string>();
|
||||
|
||||
public static void Initialize(string appId,
|
||||
string appKey,
|
||||
string server = null,
|
||||
|
@ -68,5 +73,9 @@ namespace LeanCloud {
|
|||
|
||||
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.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
|
@ -35,6 +36,15 @@ namespace LeanCloud.Storage {
|
|||
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>
|
||||
/// Invokes a cloud function as a remote procedure call.
|
||||
/// </summary>
|
||||
|
@ -46,11 +56,48 @@ namespace LeanCloud.Storage {
|
|||
Dictionary<string, object> headers = new Dictionary<string, object> {
|
||||
{ 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,
|
||||
headers: headers,
|
||||
data: encodeParams);
|
||||
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>
|
||||
/// LeanCloud Object
|
||||
/// </summary>
|
||||
public class LCObject {
|
||||
public partial class LCObject {
|
||||
/// <summary>
|
||||
/// Last synced data.
|
||||
/// </summary>
|
||||
LCObjectData data;
|
||||
internal LCObjectData data;
|
||||
|
||||
/// <summary>
|
||||
/// Estimated data.
|
||||
|
@ -349,18 +349,18 @@ namespace LeanCloud.Storage {
|
|||
return this;
|
||||
}
|
||||
|
||||
public static async Task<List<LCObject>> SaveAll(List<LCObject> objectList) {
|
||||
if (objectList == null) {
|
||||
throw new ArgumentNullException(nameof(objectList));
|
||||
public static async Task<List<LCObject>> SaveAll(IEnumerable<LCObject> objects) {
|
||||
if (objects == null) {
|
||||
throw new ArgumentNullException(nameof(objects));
|
||||
}
|
||||
foreach (LCObject obj in objectList) {
|
||||
foreach (LCObject obj in objects) {
|
||||
if (LCBatch.HasCircleReference(obj, new HashSet<LCObject>())) {
|
||||
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);
|
||||
return objectList;
|
||||
return objects.ToList();
|
||||
}
|
||||
|
||||
public async Task Delete() {
|
||||
|
@ -371,12 +371,11 @@ namespace LeanCloud.Storage {
|
|||
await LCApplication.HttpClient.Delete(path);
|
||||
}
|
||||
|
||||
public static async Task DeleteAll(List<LCObject> objectList) {
|
||||
if (objectList == null || objectList.Count == 0) {
|
||||
throw new ArgumentNullException(nameof(objectList));
|
||||
public static async Task DeleteAll(IEnumerable<LCObject> objects) {
|
||||
if (objects == null || objects.Count() == 0) {
|
||||
throw new ArgumentNullException(nameof(objects));
|
||||
}
|
||||
IEnumerable<LCObject> objects = objectList.Where(item => item.ObjectId != null);
|
||||
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects);
|
||||
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects.Where(item => item.ObjectId != null));
|
||||
List<Dictionary<string, object>> requestList = objectSet.Select(item => {
|
||||
string path = $"/{LCApplication.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
|
||||
return new Dictionary<string, object> {
|
||||
|
|
|
@ -37,6 +37,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery-Unity", "LiveQuer
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQueryApp", "Sample\LiveQueryApp\LiveQueryApp.csproj", "{9D5E6A37-8925-48ED-B7EA-12C89291B59D}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{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}
|
||||
{12482E48-C0CF-46B1-8FDD-5885D1B7DC4D} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}
|
||||
{9D5E6A37-8925-48ED-B7EA-12C89291B59D} = {2D980281-F060-4363-AB7A-D4B6C30ADDBB}
|
||||
{0A6AEBC9-9A36-4EA7-8F58-8B951126092D} = {8087ABCD-629C-4EE5-9ECE-8BDAE631236F}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
version = 0.6.4
|
||||
|
|
Loading…
Reference in New Issue