From 4f0d7e9e9f8ae6ffa0f2a957ec112b04b4d56c1d Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 24 Mar 2021 16:01:34 +0800 Subject: [PATCH] chore: LCEngineRequestContext insteads of Request. --- .../LCEngineFunctionParamAttribute.cs | 17 +++++ Engine/Controllers/LCClassHookController.cs | 18 ++--- Engine/Controllers/LCFunctionController.cs | 71 ++++++++++-------- Engine/Controllers/LCUserHookController.cs | 22 +++--- Engine/LCEngine.cs | 34 ++++++++- Engine/LCEngineRequestContext.cs | 74 +++++++++++++++++++ Engine/Requests/LCClassHookRequest.cs | 13 ---- Engine/Requests/LCCloudFunctionRequest.cs | 28 ------- Engine/Requests/LCCloudRPCRequest.cs | 21 ------ Engine/Requests/LCUserHookRequest.cs | 9 --- Storage/Storage.Test/CloudTest.cs | 35 ++++++++- Storage/Storage/LCCloud.cs | 16 +++- Storage/Storage/LCHookObject.cs | 14 ++-- Storage/Storage/LCObject.cs | 6 +- 14 files changed, 238 insertions(+), 140 deletions(-) create mode 100644 Engine/Attributes/LCEngineFunctionParamAttribute.cs create mode 100644 Engine/LCEngineRequestContext.cs delete mode 100644 Engine/Requests/LCClassHookRequest.cs delete mode 100644 Engine/Requests/LCCloudFunctionRequest.cs delete mode 100644 Engine/Requests/LCCloudRPCRequest.cs delete mode 100644 Engine/Requests/LCUserHookRequest.cs diff --git a/Engine/Attributes/LCEngineFunctionParamAttribute.cs b/Engine/Attributes/LCEngineFunctionParamAttribute.cs new file mode 100644 index 0000000..157608c --- /dev/null +++ b/Engine/Attributes/LCEngineFunctionParamAttribute.cs @@ -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; + } + } +} diff --git a/Engine/Controllers/LCClassHookController.cs b/Engine/Controllers/LCClassHookController.cs index 0fe7ba1..211b4b4 100644 --- a/Engine/Controllers/LCClassHookController.cs +++ b/Engine/Controllers/LCClassHookController.cs @@ -25,9 +25,9 @@ namespace LeanCloud.Engine { string classHookName = GetClassHookName(className, hookName); if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) { - Dictionary dict = LCEngine.Decode(body); + Dictionary data = LCEngine.Decode(body); - LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); + LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary); objectData.ClassName = className; LCObject obj = LCObject.Create(className); obj.Merge(objectData); @@ -39,24 +39,22 @@ namespace LeanCloud.Engine { obj.DisableAfterHook(); } + LCEngine.InitRequestContext(Request); + LCUser user = null; - if (dict.TryGetValue("user", out object userObj) && + if (data.TryGetValue("user", out object userObj) && userObj != null) { user = new LCUser(); user.Merge(LCObjectData.Decode(userObj as Dictionary)); + LCEngineRequestContext.CurrentUser = user; } - LCClassHookRequest req = new LCClassHookRequest { - Object = obj, - CurrentUser = user - }; - - LCObject result = await LCEngine.Invoke(mi, req) as LCObject; + LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject; if (result != null) { return LCCloud.Encode(result); } } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } diff --git a/Engine/Controllers/LCFunctionController.cs b/Engine/Controllers/LCFunctionController.cs index 00f84a2..f64b533 100644 --- a/Engine/Controllers/LCFunctionController.cs +++ b/Engine/Controllers/LCFunctionController.cs @@ -3,11 +3,11 @@ using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; using System.Reflection; +using System.Linq; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; using Microsoft.AspNetCore.Cors; -using LeanCloud.Storage.Internal.Codec; using LeanCloud.Storage; +using LeanCloud.Storage.Internal.Codec; namespace LeanCloud.Engine { [ApiController] @@ -32,26 +32,18 @@ namespace LeanCloud.Engine { 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); + LCEngine.InitRequestContext(Request); + + object[] ps = ParseParameters(mi, body); + object result = await LCEngine.Invoke(mi, ps.ToArray()); + if (result != null) { return new Dictionary { { "result", result } }; } } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } @@ -64,29 +56,44 @@ namespace LeanCloud.Engine { 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); + LCEngine.InitRequestContext(Request); + + object[] ps = ParseParameters(mi, body); + object result = await LCEngine.Invoke(mi, ps); + if (result != null) { return new Dictionary { - { "result", LCCloud.Encode(result) } - }; + { "result", LCCloud.Encode(result) } + }; } } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } } + + private static object[] ParseParameters(MethodInfo mi, JsonElement body) { + Dictionary parameters = LCEngine.Decode(body); + List ps = new List(); + + if (mi.GetParameters().Length > 0) { + if (Array.Exists(mi.GetParameters(), + p => p.GetCustomAttribute() != null)) { + // 如果包含 LCEngineFunctionParamAttribute 的参数,则按照配对方式传递参数 + foreach (ParameterInfo pi in mi.GetParameters()) { + LCEngineFunctionParamAttribute attr = pi.GetCustomAttribute(); + if (attr != null) { + string paramName = attr.ParamName; + ps.Add(parameters[paramName]); + } + } + } else { + ps.Add(LCDecoder.Decode(LCEngine.Decode(body))); + } + } + + return ps.ToArray(); + } } } diff --git a/Engine/Controllers/LCUserHookController.cs b/Engine/Controllers/LCUserHookController.cs index 196a929..0713635 100644 --- a/Engine/Controllers/LCUserHookController.cs +++ b/Engine/Controllers/LCUserHookController.cs @@ -24,10 +24,12 @@ namespace LeanCloud.Engine { LCEngine.CheckHookKey(Request); if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) { + LCEngine.InitRequestContext(Request); + Dictionary dict = LCEngine.Decode(body); return await Invoke(mi, dict); } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } @@ -42,10 +44,12 @@ namespace LeanCloud.Engine { LCEngine.CheckHookKey(Request); if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) { + LCEngine.InitRequestContext(Request); + Dictionary dict = LCEngine.Decode(body); return await Invoke(mi, dict); } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } @@ -60,10 +64,12 @@ namespace LeanCloud.Engine { LCEngine.CheckHookKey(Request); if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) { + LCEngine.InitRequestContext(Request); + Dictionary dict = LCEngine.Decode(body); return await Invoke(mi, dict); } - return default; + return body; } catch (Exception e) { return StatusCode(500, e.Message); } @@ -73,14 +79,10 @@ namespace LeanCloud.Engine { LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary); objectData.ClassName = "_User"; - LCObject obj = LCObject.Create("_User"); - obj.Merge(objectData); + LCObject user = LCObject.Create("_User"); + user.Merge(objectData); - LCUserHookRequest req = new LCUserHookRequest { - CurrentUser = obj as LCUser - }; - - return await LCEngine.Invoke(mi, req) as LCObject; + return await LCEngine.Invoke(mi, new object[] { user }) as LCObject; } } } diff --git a/Engine/LCEngine.cs b/Engine/LCEngine.cs index 301cf2f..7b7b074 100644 --- a/Engine/LCEngine.cs +++ b/Engine/LCEngine.cs @@ -7,8 +7,9 @@ using System.Text.Json; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Microsoft.Extensions.Primitives; -using LeanCloud.Common; using Microsoft.Extensions.DependencyInjection; +using LeanCloud.Common; +using LeanCloud.Storage; namespace LeanCloud.Engine { public class LCEngine { @@ -202,6 +203,27 @@ namespace LeanCloud.Engine { LCLogger.Debug($"{key} : {Environment.GetEnvironmentVariable(key)}"); } + internal static async Task 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 { + { "code", lcEx.Code }, + { "message", lcEx.Message } + })); + } + throw ex; + } + } + internal static async Task Invoke(MethodInfo mi, object request) { try { object[] ps = null; @@ -234,6 +256,16 @@ namespace LeanCloud.Engine { 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(); diff --git a/Engine/LCEngineRequestContext.cs b/Engine/LCEngineRequestContext.cs new file mode 100644 index 0000000..1eb7d8b --- /dev/null +++ b/Engine/LCEngineRequestContext.cs @@ -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> requestContext = new ThreadLocal>(); + + public static void Init() { + if (requestContext.IsValueCreated) { + requestContext.Value.Clear(); + } + requestContext.Value = new Dictionary(); + } + + public static void Set(string key, object value) { + if (!requestContext.IsValueCreated) { + requestContext.Value = new Dictionary(); + } + 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); + } + } + } +} diff --git a/Engine/Requests/LCClassHookRequest.cs b/Engine/Requests/LCClassHookRequest.cs deleted file mode 100644 index 553cff0..0000000 --- a/Engine/Requests/LCClassHookRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCClassHookRequest { - public LCObject Object { - get; set; - } - - public LCUser CurrentUser { - get; set; - } - } -} diff --git a/Engine/Requests/LCCloudFunctionRequest.cs b/Engine/Requests/LCCloudFunctionRequest.cs deleted file mode 100644 index d4ecb3b..0000000 --- a/Engine/Requests/LCCloudFunctionRequest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCCloudFunctionRequestMeta { - public string RemoteAddress { - get; set; - } - } - - public class LCCloudFunctionRequest { - public LCCloudFunctionRequestMeta Meta { - get; set; - } - - public Dictionary Params { - get; set; - } - - public LCUser User { - get; set; - } - - public string SessionToken { - get; set; - } - } -} diff --git a/Engine/Requests/LCCloudRPCRequest.cs b/Engine/Requests/LCCloudRPCRequest.cs deleted file mode 100644 index 6c0b65d..0000000 --- a/Engine/Requests/LCCloudRPCRequest.cs +++ /dev/null @@ -1,21 +0,0 @@ -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCCloudRPCRequest { - public LCCloudFunctionRequestMeta Meta { - get; set; - } - - public object Params { - get; set; - } - - public LCUser User { - get; set; - } - - public string SessionToken { - get; set; - } - } -} diff --git a/Engine/Requests/LCUserHookRequest.cs b/Engine/Requests/LCUserHookRequest.cs deleted file mode 100644 index 8e4ba97..0000000 --- a/Engine/Requests/LCUserHookRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LeanCloud.Storage; - -namespace LeanCloud.Engine { - public class LCUserHookRequest { - public LCUser CurrentUser { - get; set; - } - } -} diff --git a/Storage/Storage.Test/CloudTest.cs b/Storage/Storage.Test/CloudTest.cs index 2084a74..8cf2d51 100644 --- a/Storage/Storage.Test/CloudTest.cs +++ b/Storage/Storage.Test/CloudTest.cs @@ -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,7 @@ namespace Storage.Test { { "name", "world" } }); TestContext.WriteLine(response["result"]); - Assert.AreEqual(response["result"], "hello, world"); + Assert.AreEqual(response["result"], "Hello, world!"); } [Test] @@ -41,5 +42,35 @@ namespace Storage.Test { Assert.NotNull(item.ObjectId); } } + + [Test] + public async Task RPCObject() { + LCQuery query = new LCQuery("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 parameters = new Dictionary { + { "limit", 20 } + }; + List result = await LCCloud.RPC("getTodos", parameters) as List; + IEnumerable todos = result.Cast(); + foreach (LCObject todo in todos) { + TestContext.WriteLine(todo.ObjectId); + } + } + + [Test] + public async Task RPCObjectMap() { + Dictionary result = await LCCloud.RPC("getTodoMap") as Dictionary; + foreach (KeyValuePair kv in result) { + LCObject todo = kv.Value as LCObject; + TestContext.WriteLine(todo.ObjectId); + } + } } } diff --git a/Storage/Storage/LCCloud.cs b/Storage/Storage/LCCloud.cs index a27cc30..a133a3e 100644 --- a/Storage/Storage/LCCloud.cs +++ b/Storage/Storage/LCCloud.cs @@ -55,15 +55,23 @@ namespace LeanCloud.Storage { } public static object Encode(object parameters) { + if (parameters == null) { + return new Dictionary(); + } + if (parameters is LCObject lcObj) { return EncodeLCObject(lcObj); - } else if (parameters is IList list) { + } + + if (parameters is IList list) { List l = new List(); foreach (LCObject obj in list) { l.Add(EncodeLCObject(obj)); } return l; - } else if (parameters is IDictionary dict) { + } + + if (parameters is IDictionary dict) { Dictionary d = new Dictionary(); foreach (KeyValuePair item in dict) { d[item.Key] = EncodeLCObject(item.Value); @@ -75,9 +83,11 @@ namespace LeanCloud.Storage { } static object EncodeLCObject(LCObject obj) { - obj.ApplyCustomProperties(); Dictionary dict = LCObjectData.Encode(obj.data); dict["__type"] = "Object"; + foreach (KeyValuePair kv in obj.estimatedData) { + dict[kv.Key] = kv.Value; + } return dict; } } diff --git a/Storage/Storage/LCHookObject.cs b/Storage/Storage/LCHookObject.cs index e1282cd..afb8568 100644 --- a/Storage/Storage/LCHookObject.cs +++ b/Storage/Storage/LCHookObject.cs @@ -46,13 +46,15 @@ namespace LeanCloud.Storage { ApplyOperation(IgnoreHooksKey, op); } - public ReadOnlyCollection UpdatedKeys { - get { - return (this["_updatedKeys"] as List) - .Cast() - .ToList() - .AsReadOnly(); + public ReadOnlyCollection GetUpdatedKeys() { + if (this["_updatedKeys"] == null) { + return null; } + + return (this["_updatedKeys"] as List) + .Cast() + .ToList() + .AsReadOnly(); } } } diff --git a/Storage/Storage/LCObject.cs b/Storage/Storage/LCObject.cs index 8a20ba9..422aa4b 100644 --- a/Storage/Storage/LCObject.cs +++ b/Storage/Storage/LCObject.cs @@ -494,7 +494,7 @@ namespace LeanCloud.Storage { data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt; data.UpdatedAt = objectData.UpdatedAt != null ? objectData.UpdatedAt : data.UpdatedAt; // 先将本地的预估数据直接替换 - ApplyCustomProperties(); + data.CustomPropertyDict = estimatedData; // 再将服务端的数据覆盖 foreach (KeyValuePair kv in objectData.CustomPropertyDict) { string key = kv.Key; @@ -509,10 +509,6 @@ namespace LeanCloud.Storage { isNew = false; } - public void ApplyCustomProperties() { - data.CustomPropertyDict = estimatedData; - } - void RebuildEstimatedData() { estimatedData = new Dictionary(); foreach (KeyValuePair kv in data.CustomPropertyDict) {