diff --git a/Engine/Controllers/LCClassHookController.cs b/Engine/Controllers/LCClassHookController.cs new file mode 100644 index 0000000..0fe7ba1 --- /dev/null +++ b/Engine/Controllers/LCClassHookController.cs @@ -0,0 +1,84 @@ +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 ClassHooks => LCEngine.ClassHooks; + + [HttpPost("functions/{className}/{hookName}")] + public async Task 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 dict = LCEngine.Decode(body); + + LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); + objectData.ClassName = className; + LCObject obj = LCObject.Create(className); + obj.Merge(objectData); + + // 避免死循环 + if (hookName.StartsWith("before")) { + obj.DisableBeforeHook(); + } else { + obj.DisableAfterHook(); + } + + LCUser user = null; + if (dict.TryGetValue("user", out object userObj) && + userObj != null) { + user = new LCUser(); + user.Merge(LCObjectData.Decode(userObj as Dictionary)); + } + + LCClassHookRequest req = new LCClassHookRequest { + Object = obj, + CurrentUser = user + }; + + LCObject result = await LCEngine.Invoke(mi, req) as LCObject; + if (result != null) { + return LCCloud.Encode(result); + } + } + return default; + } 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}"); + } + } + } +} diff --git a/Engine/Controllers/LCFunctionController.cs b/Engine/Controllers/LCFunctionController.cs new file mode 100644 index 0000000..dec02b2 --- /dev/null +++ b/Engine/Controllers/LCFunctionController.cs @@ -0,0 +1,92 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using Microsoft.AspNetCore.Cors; +using LeanCloud.Storage.Internal.Codec; +using LeanCloud.Storage; + +namespace LeanCloud.Engine { + [ApiController] + [Route("{1,1.1}")] + [EnableCors(LCEngine.LCEngineCORS)] + public class LCFunctionController : ControllerBase { + private Dictionary 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 Run(string funcName, JsonElement body) { + try { + LCLogger.Debug($"Run: {funcName}"); + LCLogger.Debug(body.ToString()); + + if (Functions.TryGetValue(funcName, out MethodInfo mi)) { + LCUser currentUser = null; + if (Request.Headers.TryGetValue("x-lc-session", out StringValues session)) { + currentUser = await LCUser.BecomeWithSessionToken(session); + } + LCCloudFunctionRequest req = new LCCloudFunctionRequest { + Meta = new LCCloudFunctionRequestMeta { + RemoteAddress = LCEngine.GetIP(Request) + }, + Params = LCEngine.Decode(body), + SessionToken = session.ToString(), + User = currentUser + }; + object result = await LCEngine.Invoke(mi, req); + if (result != null) { + return new Dictionary { + { "result", result } + }; + } + } + return default; + } catch (Exception e) { + return StatusCode(500, e.Message); + } + } + + [HttpPost("call/{funcName}")] + public async Task RPC(string funcName, JsonElement body) { + try { + LCLogger.Debug($"RPC: {funcName}"); + LCLogger.Debug(body.ToString()); + + if (Functions.TryGetValue(funcName, out MethodInfo mi)) { + LCUser currentUser = null; + if (Request.Headers.TryGetValue("x-lc-session", out StringValues session)) { + currentUser = await LCUser.BecomeWithSessionToken(session); + } + LCCloudRPCRequest req = new LCCloudRPCRequest { + Meta = new LCCloudFunctionRequestMeta { + RemoteAddress = LCEngine.GetIP(Request) + }, + Params = LCDecoder.Decode(LCEngine.Decode(body)), + SessionToken = session.ToString(), + User = currentUser + }; + object result = await LCEngine.Invoke(mi, req); + if (result != null) { + return new Dictionary { + { "result", LCCloud.Encode(result) } + }; + } + } + return default; + } catch (Exception e) { + return StatusCode(500, e.Message); + } + } + } +} diff --git a/Engine/Handlers/LCPingHandler.cs b/Engine/Controllers/LCPingController.cs similarity index 54% rename from Engine/Handlers/LCPingHandler.cs rename to Engine/Controllers/LCPingController.cs index fcdecac..8e17193 100644 --- a/Engine/Handlers/LCPingHandler.cs +++ b/Engine/Controllers/LCPingController.cs @@ -1,10 +1,17 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Cors; namespace LeanCloud.Engine { - public class LCPingHandler { - public static object HandlePing() { + [ApiController] + [Route("__engine/{1,1.1}")] + [EnableCors(LCEngine.LCEngineCORS)] + public class LCPingController : ControllerBase { + [HttpGet("ping")] + public object Get() { LCLogger.Debug("Ping ~~~"); + return new Dictionary { { "runtime", $"dotnet-{Environment.Version}" }, { "version", LCApplication.SDKVersion } diff --git a/Engine/Controllers/LCUserHookController.cs b/Engine/Controllers/LCUserHookController.cs new file mode 100644 index 0000000..196a929 --- /dev/null +++ b/Engine/Controllers/LCUserHookController.cs @@ -0,0 +1,86 @@ +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 UserHooks => LCEngine.UserHooks; + + [HttpPost("onVerified/sms")] + public async Task HookSMSVerification(JsonElement body) { + try { + LCLogger.Debug(LCEngine.OnSMSVerified); + LCLogger.Debug(body.ToString()); + + LCEngine.CheckHookKey(Request); + + if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) { + Dictionary dict = LCEngine.Decode(body); + return await Invoke(mi, dict); + } + return default; + } catch (Exception e) { + return StatusCode(500, e.Message); + } + } + + [HttpPost("onVerified/email")] + public async Task HookEmailVerification(JsonElement body) { + try { + LCLogger.Debug(LCEngine.OnEmailVerified); + LCLogger.Debug(body.ToString()); + + LCEngine.CheckHookKey(Request); + + if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) { + Dictionary dict = LCEngine.Decode(body); + return await Invoke(mi, dict); + } + return default; + } catch (Exception e) { + return StatusCode(500, e.Message); + } + } + + [HttpPost("_User/onLogin")] + public async Task HookLogin(JsonElement body) { + try { + LCLogger.Debug(LCEngine.OnLogin); + LCLogger.Debug(body.ToString()); + + LCEngine.CheckHookKey(Request); + + if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) { + Dictionary dict = LCEngine.Decode(body); + return await Invoke(mi, dict); + } + return default; + } catch (Exception e) { + return StatusCode(500, e.Message); + } + } + + private static async Task Invoke(MethodInfo mi, Dictionary dict) { + LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); + objectData.ClassName = "_User"; + + LCObject obj = LCObject.Create("_User"); + obj.Merge(objectData); + + LCUserHookRequest req = new LCUserHookRequest { + CurrentUser = obj as LCUser + }; + + return await LCEngine.Invoke(mi, req) as LCObject; + } + } +} diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj index 9ea072a..d6c4b94 100644 --- a/Engine/Engine.csproj +++ b/Engine/Engine.csproj @@ -14,4 +14,7 @@ + + + diff --git a/Engine/Handlers/LCClassHookHandler.cs b/Engine/Handlers/LCClassHookHandler.cs deleted file mode 100644 index 8ee8698..0000000 --- a/Engine/Handlers/LCClassHookHandler.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using System.Text.Json; -using Microsoft.AspNetCore.Http; -using LeanCloud.Storage.Internal.Object; -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCClassHookHandler { - private static Dictionary ClassHooks => LCEngine.ClassHooks; - - public static void Hello() { - - } - - public static async Task HandleClassHook(string className, string hookName, HttpRequest request, JsonElement body) { - 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 dict = LCEngine.Decode(body); - - LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); - objectData.ClassName = className; - LCObject obj = LCObject.Create(className); - obj.Merge(objectData); - - // 避免死循环 - if (hookName.StartsWith("before")) { - obj.DisableBeforeHook(); - } else { - obj.DisableAfterHook(); - } - - LCUser user = null; - if (dict.TryGetValue("user", out object userObj) && - userObj != null) { - user = new LCUser(); - user.Merge(LCObjectData.Decode(userObj as Dictionary)); - } - - LCClassHookRequest req = new LCClassHookRequest { - Object = obj, - CurrentUser = user - }; - - LCObject result = await LCEngine.Invoke(mi, req) as LCObject; - if (result != null) { - return LCCloud.Encode(result); - } - } - return default; - } - - 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}"); - } - } - } -} diff --git a/Engine/Handlers/LCFunctionHandler.cs b/Engine/Handlers/LCFunctionHandler.cs deleted file mode 100644 index d632041..0000000 --- a/Engine/Handlers/LCFunctionHandler.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using System.Text.Json; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using LeanCloud.Storage.Internal.Codec; -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCFunctionHandler { - private static Dictionary Functions => LCEngine.Functions; - - /// - /// 云函数 - /// - /// - /// - /// - /// - public static async Task HandleRun(string funcName, HttpRequest request, JsonElement body) { - LCLogger.Debug($"Run: {funcName}"); - LCLogger.Debug(body.ToString()); - - if (Functions.TryGetValue(funcName, out MethodInfo mi)) { - LCUser currentUser = null; - if (request.Headers.TryGetValue("x-lc-session", out StringValues session)) { - currentUser = await LCUser.BecomeWithSessionToken(session); - } - LCCloudFunctionRequest req = new LCCloudFunctionRequest { - Meta = new LCCloudFunctionRequestMeta { - RemoteAddress = LCEngine.GetIP(request) - }, - Params = LCEngine.Decode(body), - SessionToken = session.ToString(), - User = currentUser - }; - object result = await LCEngine.Invoke(mi, req); - if (result != null) { - return new Dictionary { - { "result", result } - }; - } - } - return default; - } - - /// - /// RPC - /// - /// - /// - /// - /// - public static async Task HandleRPC(string funcName, HttpRequest request, JsonElement body) { - LCLogger.Debug($"RPC: {funcName}"); - LCLogger.Debug(body.ToString()); - - if (Functions.TryGetValue(funcName, out MethodInfo mi)) { - LCUser currentUser = null; - if (request.Headers.TryGetValue("x-lc-session", out StringValues session)) { - currentUser = await LCUser.BecomeWithSessionToken(session); - } - LCCloudRPCRequest req = new LCCloudRPCRequest { - Meta = new LCCloudFunctionRequestMeta { - RemoteAddress = LCEngine.GetIP(request) - }, - Params = LCDecoder.Decode(LCEngine.Decode(body)), - SessionToken = session.ToString(), - User = currentUser - }; - object result = await LCEngine.Invoke(mi, req); - if (result != null) { - return new Dictionary { - { "result", LCCloud.Encode(result) } - }; - } - } - return default; - } - } -} diff --git a/Engine/Handlers/LCUserHookHandler.cs b/Engine/Handlers/LCUserHookHandler.cs deleted file mode 100644 index eb65197..0000000 --- a/Engine/Handlers/LCUserHookHandler.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using System.Text.Json; -using Microsoft.AspNetCore.Http; -using LeanCloud.Storage.Internal.Object; -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCUserHookHandler { - private static Dictionary UserHooks => LCEngine.UserHooks; - - public static async Task HandleVerifiedSMS(HttpRequest request, JsonElement body) { - LCLogger.Debug(LCEngine.OnSMSVerified); - LCLogger.Debug(body.ToString()); - - LCEngine.CheckHookKey(request); - - if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) { - Dictionary dict = LCEngine.Decode(body); - return await Invoke(mi, dict); - } - return default; - } - - public static async Task HandleVerifiedEmail(HttpRequest request, JsonElement body) { - LCLogger.Debug(LCEngine.OnEmailVerified); - LCLogger.Debug(body.ToString()); - - LCEngine.CheckHookKey(request); - - if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) { - Dictionary dict = LCEngine.Decode(body); - return await Invoke(mi, dict); - } - return default; - } - - public static async Task HandleLogin(HttpRequest request, JsonElement body) { - LCLogger.Debug(LCEngine.OnLogin); - LCLogger.Debug(body.ToString()); - - LCEngine.CheckHookKey(request); - - if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) { - Dictionary dict = LCEngine.Decode(body); - return await Invoke(mi, dict); - } - return default; - } - - private static async Task Invoke(MethodInfo mi, Dictionary dict) { - LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); - objectData.ClassName = "_User"; - - LCObject obj = LCObject.Create("_User"); - obj.Merge(objectData); - - LCUserHookRequest req = new LCUserHookRequest { - CurrentUser = obj as LCUser - }; - - return await LCEngine.Invoke(mi, req) as LCObject; - } - } -} diff --git a/Engine/LCEngine.cs b/Engine/LCEngine.cs index 98174e7..4951329 100644 --- a/Engine/LCEngine.cs +++ b/Engine/LCEngine.cs @@ -8,9 +8,12 @@ using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Microsoft.Extensions.Primitives; using LeanCloud.Common; +using Microsoft.Extensions.DependencyInjection; 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"; @@ -41,11 +44,42 @@ namespace LeanCloud.Engine { 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 Functions = new Dictionary(); public static Dictionary ClassHooks = new Dictionary(); public static Dictionary UserHooks = new Dictionary(); - public static void Initialize() { + public static void Initialize(IServiceCollection services) { // 获取环境变量 LCLogger.Debug("-------------------------------------------------"); PrintEnvironmentVar("LEANCLOUD_APP_ID"); @@ -153,6 +187,14 @@ namespace LeanCloud.Engine { .ForEach(item => { Functions.TryAdd(item.Key, item.Value); }); + + services.AddCors(options => { + options.AddPolicy(LCEngineCORS, builder => { + builder.AllowAnyOrigin() + .WithMethods(LCEngineCORSMethods) + .WithHeaders(LCEngineCORSHeaders); + }); + }); } public static void PrintEnvironmentVar(string key) {