diff --git a/Engine/Internal/Controllers/LCClassHookController.cs b/Engine/Internal/Controllers/LCClassHookController.cs index 211b4b47..a43c70bc 100644 --- a/Engine/Internal/Controllers/LCClassHookController.cs +++ b/Engine/Internal/Controllers/LCClassHookController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Cors; using LeanCloud.Storage.Internal.Object; using LeanCloud.Storage; +using LeanCloud.Storage.Internal.Codec; namespace LeanCloud.Engine { [ApiController] @@ -51,12 +52,17 @@ public async Task Hook(string className, string hookName, JsonElement bo LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject; if (result != null) { - return LCCloud.Encode(result); + Dictionary dict = LCEncoder.EncodeLCObject(result, true) as Dictionary; + dict.Remove("__type"); + dict.Remove("className"); + return dict; } } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } diff --git a/Engine/Internal/Controllers/LCFunctionController.cs b/Engine/Internal/Controllers/LCFunctionController.cs index f64b5331..c61fb67d 100644 --- a/Engine/Internal/Controllers/LCFunctionController.cs +++ b/Engine/Internal/Controllers/LCFunctionController.cs @@ -21,7 +21,7 @@ public object GetFunctions() { try { return LCEngine.GetFunctions(Request); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } @@ -44,8 +44,10 @@ public async Task Run(string funcName, JsonElement body) { } } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } @@ -68,8 +70,10 @@ public async Task RPC(string funcName, JsonElement body) { } } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } diff --git a/Engine/Internal/Controllers/LCUserHookController.cs b/Engine/Internal/Controllers/LCUserHookController.cs index 0713635e..4cf77b28 100644 --- a/Engine/Internal/Controllers/LCUserHookController.cs +++ b/Engine/Internal/Controllers/LCUserHookController.cs @@ -30,8 +30,10 @@ public async Task HookSMSVerification(JsonElement body) { return await Invoke(mi, dict); } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } @@ -50,8 +52,10 @@ public async Task HookEmailVerification(JsonElement body) { return await Invoke(mi, dict); } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } @@ -70,8 +74,10 @@ public async Task HookLogin(JsonElement body) { return await Invoke(mi, dict); } return body; + } catch (LCException e) { + return StatusCode(400, LCEngine.ConvertException(e)); } catch (Exception e) { - return StatusCode(500, e.Message); + return StatusCode(500, LCEngine.ConvertException(e)); } } diff --git a/Engine/Public/LCEngine.cs b/Engine/Public/LCEngine.cs index 8abf27f7..65aab5cf 100644 --- a/Engine/Public/LCEngine.cs +++ b/Engine/Public/LCEngine.cs @@ -220,14 +220,7 @@ internal static async Task Invoke(MethodInfo mi, object[] parameters) { } 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; + throw e.InnerException; } } @@ -245,14 +238,7 @@ internal static async Task Invoke(MethodInfo mi, object request) { } return mi.Invoke(null, ps); } 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; + throw e.InnerException; } } @@ -316,5 +302,19 @@ internal static object GetFunctions(HttpRequest request) { { "result", functions } }; } + + internal static object ConvertException(Exception e) { + LCLogger.Error(e.ToString()); + if (e is LCException lcEx) { + return new Dictionary { + { "code", lcEx.Code }, + { "error", lcEx.Message } + }; + } + return new Dictionary { + { "code", 1 }, + { "error", e.Message } + }; + } } } diff --git a/Sample/LeanEngineApp/.leancloud/current_app_id b/Sample/LeanEngineApp/.leancloud/current_app_id index 967a43f2..1fba4e63 100644 --- a/Sample/LeanEngineApp/.leancloud/current_app_id +++ b/Sample/LeanEngineApp/.leancloud/current_app_id @@ -1 +1 @@ -ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz +ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz \ No newline at end of file diff --git a/Sample/LeanEngineApp/.leancloud/current_group b/Sample/LeanEngineApp/.leancloud/current_group index c0772185..6f3439a3 100644 --- a/Sample/LeanEngineApp/.leancloud/current_group +++ b/Sample/LeanEngineApp/.leancloud/current_group @@ -1 +1 @@ -web +web \ No newline at end of file diff --git a/Sample/LeanEngineApp/web/App.cs b/Sample/LeanEngineApp/web/App.cs index e0164536..4d7507e6 100644 --- a/Sample/LeanEngineApp/web/App.cs +++ b/Sample/LeanEngineApp/web/App.cs @@ -5,9 +5,12 @@ using System.Linq; using LeanCloud.Storage; using LeanCloud.Engine; +using LeanCloud; namespace web { public class App { + private const string HOOK_CLASS_NAME = "TestHookClass"; + // Function [LCEngineFunction("ping")] public static string Ping() { @@ -41,5 +44,56 @@ public static async Task> GetObjectMap() { ReadOnlyCollection todos = await query.Find(); return todos.ToDictionary(t => t.ObjectId); } + + [LCEngineFunction("lcexception")] + public static string LCException() { + throw new LCException(123, "Runtime exception"); + } + + [LCEngineFunction("exception")] + public static string Exception() { + throw new Exception("Hello, exception"); + } + + // Class Hook + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.BeforeSave)] + public static LCObject BeforeSaveClass(LCObject obj) { + if (obj["score"] == null) { + obj["score"] = 60; + } + return obj; + } + + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.AfterSave)] + public static void AfterSaveClass(LCObject obj) { + LCLogger.Debug($"Saved {obj.ObjectId}"); + } + + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.BeforeUpdate)] + public static LCObject BeforeUpdateClass(LCObject obj) { + ReadOnlyCollection updatedKeys = obj.GetUpdatedKeys(); + if (updatedKeys.Contains("score")) { + int score = (int) obj["score"]; + if (score > 100) { + throw new Exception($"Error score: {score}"); + } + } + return obj; + } + + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.AfterUpdate)] + public static void AfterUpdateClass(LCObject obj) { + LCLogger.Debug($"Updated {obj.ObjectId}"); + } + + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.BeforeDelete)] + public static void BeforeDeleteClass(LCObject obj) { + throw new Exception($"Cannot delete {obj.ClassName}"); + } + + [LCEngineClassHook(HOOK_CLASS_NAME, LCEngineObjectHookType.AfterDelete)] + public static void AfterDeleteClass(LCObject obj) { + LCLogger.Debug($"Deleted {obj.ObjectId}"); + } } } diff --git a/Sample/LeanEngineApp/web/Libs/Common.dll b/Sample/LeanEngineApp/web/Libs/Common.dll index 5c92cb33..0be68bae 100644 Binary files a/Sample/LeanEngineApp/web/Libs/Common.dll and b/Sample/LeanEngineApp/web/Libs/Common.dll differ diff --git a/Sample/LeanEngineApp/web/Libs/Engine.dll b/Sample/LeanEngineApp/web/Libs/Engine.dll index 25e5976f..fba09b69 100644 Binary files a/Sample/LeanEngineApp/web/Libs/Engine.dll and b/Sample/LeanEngineApp/web/Libs/Engine.dll differ diff --git a/Sample/LeanEngineApp/web/Libs/LC.Newtonsoft.Json.dll b/Sample/LeanEngineApp/web/Libs/LC.Newtonsoft.Json.dll index 59ee2767..17e3d827 100644 Binary files a/Sample/LeanEngineApp/web/Libs/LC.Newtonsoft.Json.dll and b/Sample/LeanEngineApp/web/Libs/LC.Newtonsoft.Json.dll differ diff --git a/Sample/LeanEngineApp/web/Libs/Storage.Standard.dll b/Sample/LeanEngineApp/web/Libs/Storage.Standard.dll index f70d9875..1e5f5cfb 100644 Binary files a/Sample/LeanEngineApp/web/Libs/Storage.Standard.dll and b/Sample/LeanEngineApp/web/Libs/Storage.Standard.dll differ diff --git a/Sample/LeanEngineApp/web/Libs/Storage.dll b/Sample/LeanEngineApp/web/Libs/Storage.dll index 1b4b3b53..ae2c59a7 100644 Binary files a/Sample/LeanEngineApp/web/Libs/Storage.dll and b/Sample/LeanEngineApp/web/Libs/Storage.dll differ diff --git a/Storage/Storage.AOT/Storage.AOT.csproj b/Storage/Storage.AOT/Storage.AOT.csproj index 3a8ab48d..b42f452e 100644 --- a/Storage/Storage.AOT/Storage.AOT.csproj +++ b/Storage/Storage.AOT/Storage.AOT.csproj @@ -104,9 +104,6 @@ Storage\Public\LCHookObject.cs - - Storage\Public\LCUser.cs - Storage\Public\LCObject.cs @@ -170,5 +167,11 @@ Storage\Public\Friendship\LCFollowersAndFollowees.cs + + Storage\Public\User\LCUser.cs + + + Storage\Public\User\LCUserQueryCondition.cs + diff --git a/Storage/Storage.Test/ClassHookTest.cs b/Storage/Storage.Test/ClassHookTest.cs new file mode 100644 index 00000000..f0ee10f7 --- /dev/null +++ b/Storage/Storage.Test/ClassHookTest.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using LeanCloud; +using LeanCloud.Storage; + +namespace Storage.Test { + public class ClassHookTest : BaseTest { + private const string CLASS_NAME = "TestHookClass"; + + private LCObject obj; + + [Test] + [Order(0)] + public async Task Save() { + obj = new LCObject(CLASS_NAME); + await obj.Save(); + } + + [Test] + [Order(10)] + public async Task Update() { + obj["score"] = 200; + LCException e = Assert.CatchAsync(() => obj.Save()); + Assert.AreEqual(e.Code, 142); + + obj["score"] = 90; + await obj.Save(); + } + + [Test] + [Order(20)] + public void Delete() { + LCException e = Assert.CatchAsync(() => obj.Delete()); + Assert.AreEqual(e.Code, 142); + } + } +} diff --git a/Storage/Storage.Test/CloudTest.cs b/Storage/Storage.Test/CloudTest.cs index 37bcfef2..f4e600a3 100644 --- a/Storage/Storage.Test/CloudTest.cs +++ b/Storage/Storage.Test/CloudTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using System.Linq; +using LeanCloud; using LeanCloud.Storage; namespace Storage.Test { @@ -58,5 +59,19 @@ public async Task GetObjectMap() { Assert.AreEqual(kv.Key, obj.ObjectId); } } + + [Test] + public void CatchLCException() { + LCException ex = Assert.CatchAsync(() => LCCloud.Run("lcexception")); + Assert.AreEqual(ex.Code, 123); + Assert.AreEqual(ex.Message, "Runtime exception"); + } + + [Test] + public void CatchException() { + LCException ex = Assert.CatchAsync(() => LCCloud.Run("exception")); + Assert.AreEqual(ex.Code, 1); + Assert.AreEqual(ex.Message, "Hello, exception"); + } } } diff --git a/Storage/Storage.Test/UserTest.cs b/Storage/Storage.Test/UserTest.cs index d869fe59..ac652d52 100644 --- a/Storage/Storage.Test/UserTest.cs +++ b/Storage/Storage.Test/UserTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using System.Text; +using System.Collections.ObjectModel; using LeanCloud; using LeanCloud.Storage; using LC.Newtonsoft.Json; @@ -298,6 +299,27 @@ public async Task AuthData() { } } + [Test] + [Order(18)] + public async Task QueryUser() { + LCUser anoymous1 = await LCUser.LoginAnonymously(); + + string nickname = anoymous1.ObjectId; + anoymous1["nickname"] = nickname; + await anoymous1.Save(); + + await LCUser.LoginAnonymously(); + + LCUserQueryCondition condition = new LCUserQueryCondition(); + condition.WhereEqualTo("nickname", nickname); + ReadOnlyCollection users = await LCUser.StrictlyFind(condition); + + Assert.Greater(users.Count, 0); + foreach (LCUser user in users) { + Assert.AreEqual(user.ObjectId, anoymous1.ObjectId); + } + } + private string GetTestEmail() { return $"{TestPhone}@leancloud.rocks"; } diff --git a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs index f5c8318d..087bbf9b 100644 --- a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs +++ b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs @@ -8,9 +8,9 @@ public class LCCompositionalCondition : ILCQueryCondition { public const string And = "$and"; public const string Or = "$or"; - readonly string composition; + protected string composition; - List conditionList; + protected List conditionList; List orderByList; HashSet includes; @@ -217,8 +217,9 @@ public Dictionary BuildParams() { { "skip", Skip }, { "limit", Limit } }; - if (conditionList != null && conditionList.Count > 0) { - dict["where"] = JsonConvert.SerializeObject(Encode()); + string where = BuildWhere(); + if (!string.IsNullOrEmpty(where)) { + dict["where"] = where; } string order = BuildOrders(); if (!string.IsNullOrEmpty(order)) { diff --git a/Storage/Storage/Public/LCCloud.cs b/Storage/Storage/Public/LCCloud.cs index f90557eb..1115020f 100644 --- a/Storage/Storage/Public/LCCloud.cs +++ b/Storage/Storage/Public/LCCloud.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using LeanCloud.Common; using LeanCloud.Storage.Internal.Codec; -using LeanCloud.Storage.Internal.Object; namespace LeanCloud.Storage { /// diff --git a/Storage/Storage/Public/LCUser.cs b/Storage/Storage/Public/User/LCUser.cs similarity index 96% rename from Storage/Storage/Public/LCUser.cs rename to Storage/Storage/Public/User/LCUser.cs index cc29036b..06c36613 100644 --- a/Storage/Storage/Public/LCUser.cs +++ b/Storage/Storage/Public/User/LCUser.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using LeanCloud.Common; using LeanCloud.Storage.Internal.Object; @@ -778,5 +779,30 @@ public static LCUser GenerateUser(LCObjectData objectData) { user.Merge(objectData); return user; } + + public static async Task> StrictlyFind(LCUserQueryCondition condition) { + if (condition == null) { + throw new ArgumentNullException(nameof(condition)); + } + + string path = "users/strictlyQuery"; + Dictionary dict = new Dictionary {}; + string where = condition.BuildWhere(); + if (!string.IsNullOrEmpty(where)) { + dict["where"] = where; + } + + Dictionary response = await LCCore.HttpClient.Get>(path, + queryParams: dict); + List results = response["results"] as List; + List users = new List(); + foreach (object item in results) { + LCUser user = Create(CLASS_NAME) as LCUser; + LCObjectData objectData = LCObjectData.Decode(item as Dictionary); + user.Merge(objectData); + users.Add(user); + } + return users.AsReadOnly(); + } } } diff --git a/Storage/Storage/Public/User/LCUserQueryCondition.cs b/Storage/Storage/Public/User/LCUserQueryCondition.cs new file mode 100644 index 00000000..a06a4554 --- /dev/null +++ b/Storage/Storage/Public/User/LCUserQueryCondition.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using LeanCloud.Storage.Internal.Query; + +namespace LeanCloud.Storage { + public class LCUserQueryCondition : LCCompositionalCondition { + public new int Skip { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public new int Limit { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public new void WhereMatchesQuery(string key, LCQuery query) where K : LCObject { + throw new NotImplementedException(); + } + + public LCUserQueryCondition() { + composition = And; + } + + public new Dictionary BuildParams() { + Dictionary dict = base.BuildParams(); + // LCUserQueryCondition 不支持下面的参数 + dict.Remove("skip"); + dict.Remove("limit"); + return dict; + } + } +} diff --git a/Storage/Storage/Storage.csproj b/Storage/Storage/Storage.csproj index fd1ae5c3..15a2a9fc 100644 --- a/Storage/Storage/Storage.csproj +++ b/Storage/Storage/Storage.csproj @@ -20,6 +20,7 @@ + @@ -29,5 +30,6 @@ +