chore: LCEngineRequestContext insteads of Request.

oneRain 2021-03-24 16:01:34 +08:00
parent 367c57b00f
commit 4f0d7e9e9f
14 changed files with 238 additions and 140 deletions

View File

@ -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;
}
}
}

View File

@ -25,9 +25,9 @@ namespace LeanCloud.Engine {
string classHookName = GetClassHookName(className, hookName); string classHookName = GetClassHookName(className, hookName);
if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) { if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) {
Dictionary<string, object> dict = LCEngine.Decode(body); Dictionary<string, object> data = LCEngine.Decode(body);
LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary<string, object>); LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary<string, object>);
objectData.ClassName = className; objectData.ClassName = className;
LCObject obj = LCObject.Create(className); LCObject obj = LCObject.Create(className);
obj.Merge(objectData); obj.Merge(objectData);
@ -39,24 +39,22 @@ namespace LeanCloud.Engine {
obj.DisableAfterHook(); obj.DisableAfterHook();
} }
LCEngine.InitRequestContext(Request);
LCUser user = null; LCUser user = null;
if (dict.TryGetValue("user", out object userObj) && if (data.TryGetValue("user", out object userObj) &&
userObj != null) { userObj != null) {
user = new LCUser(); user = new LCUser();
user.Merge(LCObjectData.Decode(userObj as Dictionary<string, object>)); user.Merge(LCObjectData.Decode(userObj as Dictionary<string, object>));
LCEngineRequestContext.CurrentUser = user;
} }
LCClassHookRequest req = new LCClassHookRequest { LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject;
Object = obj,
CurrentUser = user
};
LCObject result = await LCEngine.Invoke(mi, req) as LCObject;
if (result != null) { if (result != null) {
return LCCloud.Encode(result); return LCCloud.Encode(result);
} }
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); return StatusCode(500, e.Message);
} }

View File

@ -3,11 +3,11 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Linq;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using LeanCloud.Storage.Internal.Codec;
using LeanCloud.Storage; using LeanCloud.Storage;
using LeanCloud.Storage.Internal.Codec;
namespace LeanCloud.Engine { namespace LeanCloud.Engine {
[ApiController] [ApiController]
@ -32,26 +32,18 @@ namespace LeanCloud.Engine {
LCLogger.Debug(body.ToString()); LCLogger.Debug(body.ToString());
if (Functions.TryGetValue(funcName, out MethodInfo mi)) { if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
LCUser currentUser = null; LCEngine.InitRequestContext(Request);
if (Request.Headers.TryGetValue("x-lc-session", out StringValues session)) {
currentUser = await LCUser.BecomeWithSessionToken(session); object[] ps = ParseParameters(mi, body);
} object result = await LCEngine.Invoke(mi, ps.ToArray());
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) { if (result != null) {
return new Dictionary<string, object> { return new Dictionary<string, object> {
{ "result", result } { "result", result }
}; };
} }
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); return StatusCode(500, e.Message);
} }
@ -64,29 +56,44 @@ namespace LeanCloud.Engine {
LCLogger.Debug(body.ToString()); LCLogger.Debug(body.ToString());
if (Functions.TryGetValue(funcName, out MethodInfo mi)) { if (Functions.TryGetValue(funcName, out MethodInfo mi)) {
LCUser currentUser = null; LCEngine.InitRequestContext(Request);
if (Request.Headers.TryGetValue("x-lc-session", out StringValues session)) {
currentUser = await LCUser.BecomeWithSessionToken(session); object[] ps = ParseParameters(mi, body);
} object result = await LCEngine.Invoke(mi, ps);
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) { if (result != null) {
return new Dictionary<string, object> { return new Dictionary<string, object> {
{ "result", LCCloud.Encode(result) } { "result", LCCloud.Encode(result) }
}; };
} }
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); 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();
}
} }
} }

View File

@ -24,10 +24,12 @@ namespace LeanCloud.Engine {
LCEngine.CheckHookKey(Request); LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) { if (UserHooks.TryGetValue(LCEngine.OnSMSVerified, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body); Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict); return await Invoke(mi, dict);
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); return StatusCode(500, e.Message);
} }
@ -42,10 +44,12 @@ namespace LeanCloud.Engine {
LCEngine.CheckHookKey(Request); LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) { if (UserHooks.TryGetValue(LCEngine.OnEmailVerified, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body); Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict); return await Invoke(mi, dict);
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); return StatusCode(500, e.Message);
} }
@ -60,10 +64,12 @@ namespace LeanCloud.Engine {
LCEngine.CheckHookKey(Request); LCEngine.CheckHookKey(Request);
if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) { if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) {
LCEngine.InitRequestContext(Request);
Dictionary<string, object> dict = LCEngine.Decode(body); Dictionary<string, object> dict = LCEngine.Decode(body);
return await Invoke(mi, dict); return await Invoke(mi, dict);
} }
return default; return body;
} catch (Exception e) { } catch (Exception e) {
return StatusCode(500, e.Message); return StatusCode(500, e.Message);
} }
@ -73,14 +79,10 @@ namespace LeanCloud.Engine {
LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary<string, object>); LCObjectData objectData = LCObjectData.Decode(dict["object"] as Dictionary<string, object>);
objectData.ClassName = "_User"; objectData.ClassName = "_User";
LCObject obj = LCObject.Create("_User"); LCObject user = LCObject.Create("_User");
obj.Merge(objectData); user.Merge(objectData);
LCUserHookRequest req = new LCUserHookRequest { return await LCEngine.Invoke(mi, new object[] { user }) as LCObject;
CurrentUser = obj as LCUser
};
return await LCEngine.Invoke(mi, req) as LCObject;
} }
} }
} }

View File

@ -7,8 +7,9 @@ using System.Text.Json;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using LeanCloud.Common;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using LeanCloud.Common;
using LeanCloud.Storage;
namespace LeanCloud.Engine { namespace LeanCloud.Engine {
public class LCEngine { public class LCEngine {
@ -202,6 +203,27 @@ namespace LeanCloud.Engine {
LCLogger.Debug($"{key} : {Environment.GetEnvironmentVariable(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) { internal static async Task<object> Invoke(MethodInfo mi, object request) {
try { try {
object[] ps = null; object[] ps = null;
@ -234,6 +256,16 @@ namespace LeanCloud.Engine {
return dict; 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) { internal static string GetIP(HttpRequest request) {
if (request.Headers.TryGetValue("x-real-ip", out StringValues ip)) { if (request.Headers.TryGetValue("x-real-ip", out StringValues ip)) {
return ip.ToString(); return ip.ToString();

View File

@ -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);
}
}
}
}

View File

@ -1,13 +0,0 @@
using LeanCloud.Storage;
namespace LeanCloud.Engine {
public class LCClassHookRequest {
public LCObject Object {
get; set;
}
public LCUser CurrentUser {
get; set;
}
}
}

View File

@ -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<string, object> Params {
get; set;
}
public LCUser User {
get; set;
}
public string SessionToken {
get; set;
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,9 +0,0 @@
using LeanCloud.Storage;
namespace LeanCloud.Engine {
public class LCUserHookRequest {
public LCUser CurrentUser {
get; set;
}
}
}

View File

@ -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,7 @@ 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] [Test]
@ -41,5 +42,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);
}
}
} }
} }

View File

@ -55,15 +55,23 @@ namespace LeanCloud.Storage {
} }
public static object Encode(object parameters) { public static object Encode(object parameters) {
if (parameters == null) {
return new Dictionary<string, object>();
}
if (parameters is LCObject lcObj) { if (parameters is LCObject lcObj) {
return EncodeLCObject(lcObj); return EncodeLCObject(lcObj);
} else if (parameters is IList<LCObject> list) { }
if (parameters is IList<LCObject> list) {
List<object> l = new List<object>(); List<object> l = new List<object>();
foreach (LCObject obj in list) { foreach (LCObject obj in list) {
l.Add(EncodeLCObject(obj)); l.Add(EncodeLCObject(obj));
} }
return l; return l;
} else if (parameters is IDictionary<string, LCObject> dict) { }
if (parameters is IDictionary<string, LCObject> dict) {
Dictionary<string, object> d = new Dictionary<string, object>(); Dictionary<string, object> d = new Dictionary<string, object>();
foreach (KeyValuePair<string, LCObject> item in dict) { foreach (KeyValuePair<string, LCObject> item in dict) {
d[item.Key] = EncodeLCObject(item.Value); d[item.Key] = EncodeLCObject(item.Value);
@ -75,9 +83,11 @@ namespace LeanCloud.Storage {
} }
static object EncodeLCObject(LCObject obj) { static object EncodeLCObject(LCObject obj) {
obj.ApplyCustomProperties();
Dictionary<string, object> dict = LCObjectData.Encode(obj.data); Dictionary<string, object> dict = LCObjectData.Encode(obj.data);
dict["__type"] = "Object"; dict["__type"] = "Object";
foreach (KeyValuePair<string, object> kv in obj.estimatedData) {
dict[kv.Key] = kv.Value;
}
return dict; return dict;
} }
} }

View File

@ -46,8 +46,11 @@ namespace LeanCloud.Storage {
ApplyOperation(IgnoreHooksKey, op); ApplyOperation(IgnoreHooksKey, op);
} }
public ReadOnlyCollection<string> UpdatedKeys { public ReadOnlyCollection<string> GetUpdatedKeys() {
get { if (this["_updatedKeys"] == null) {
return null;
}
return (this["_updatedKeys"] as List<object>) return (this["_updatedKeys"] as List<object>)
.Cast<string>() .Cast<string>()
.ToList() .ToList()
@ -55,4 +58,3 @@ namespace LeanCloud.Storage {
} }
} }
} }
}

View File

@ -494,7 +494,7 @@ namespace LeanCloud.Storage {
data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt; data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt;
data.UpdatedAt = objectData.UpdatedAt != null ? objectData.UpdatedAt : data.UpdatedAt; data.UpdatedAt = objectData.UpdatedAt != null ? objectData.UpdatedAt : data.UpdatedAt;
// 先将本地的预估数据直接替换 // 先将本地的预估数据直接替换
ApplyCustomProperties(); data.CustomPropertyDict = estimatedData;
// 再将服务端的数据覆盖 // 再将服务端的数据覆盖
foreach (KeyValuePair<string, object> kv in objectData.CustomPropertyDict) { foreach (KeyValuePair<string, object> kv in objectData.CustomPropertyDict) {
string key = kv.Key; string key = kv.Key;
@ -509,10 +509,6 @@ namespace LeanCloud.Storage {
isNew = false; isNew = false;
} }
public void ApplyCustomProperties() {
data.CustomPropertyDict = estimatedData;
}
void RebuildEstimatedData() { void RebuildEstimatedData() {
estimatedData = new Dictionary<string, object>(); estimatedData = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> kv in data.CustomPropertyDict) { foreach (KeyValuePair<string, object> kv in data.CustomPropertyDict) {