9.1.3 .net framework通過業務邏輯層自動生成WebApi的做法

来源:http://www.cnblogs.com/BenDan2002/archive/2016/11/17/6072631.html
-Advertisement-
Play Games

首先需要說明的是這是.net framework的一個組件,而不是針對.net core的。目前工作比較忙,因此.net core的轉換正在編寫過程中,有了實現會第一時間貼出來。 接下來進入正題。對於大型的分層系統,會有一個應用程式層,應用程式層的主要作用是封裝業務領域層的業務邏輯層,並對界面展示層 ...


首先需要說明的是這是.net framework的一個組件,而不是針對.net core的。目前工作比較忙,因此.net core的轉換正在編寫過程中,有了實現會第一時間貼出來。

接下來進入正題。對於大型的分層系統,會有一個應用程式層,應用程式層的主要作用是封裝業務領域層的業務邏輯層,並對界面展示層提供服務。界面展示層例如有Web網站、移動應用、WPF等等,例如下圖。

很多情況下,業務領域層中間的業務邏輯層方法和應用服務層的服務介面幾乎是一致的。在業務邏輯方法編寫完成後,編程人員,也會重覆性的編寫應用服務層。該層難度不大,但是屬於重覆性勞動並且工作量不小。對於一個有敬業精神的程式員來說,問題就來了,寫一大堆不加思考的、工作量大的代碼,還不如寫一個框架自動通過業務邏輯層生成WebApi。

為了簡化編程人員的工作量,減少錯誤的出現,我們編寫了這個框架,就是通過業務邏輯層的方法自動生成應用服務層的服務。

 

其實ABP也有這個東西,但是發現ABP中的實現與Castle Windsor結合太緊密了,用了些Castle Windsor的特性。我等用Unity作為DI/IoC的無法借鑒。

 

要瞭解這個自動生成WebApi的框架,我們得簡要的講解下.net framework下webapi的請求處理過程。

Web API是微軟的主導的一種面向服務的實現方式,已經集成在visual studio的模板中,是一種比較成熟的SOA數據服務方式。Web API的服務提供方式實現過程由三個步驟組成:路由匹配階段;控制器選擇和構建階段;執行器選擇和執行階段。

 

 

 

 

 

 

 

 

 

 

預設情況下,一個mvc項目包含webapi,會在app_start目錄下有一個webapiconfig.cs文件。這個文件是webapi路由的設置,我們假設按照controller和action的名稱進行路由,寫法見下(尤其是routetemplate行): 

 1         public static void Register(HttpConfiguration config)
 2         {
 3             // Web API 配置和服務
 4 
 5             // Web API 路由
 6             config.MapHttpAttributeRoutes();
 7 
 8             config.Routes.MapHttpRoute(
 9                 name: "DefaultApi",
10                 routeTemplate: "api/{controller}/{action}/{id}",
11                 defaults: new { id = RouteParameter.Optional }
12             );
13         }

  

系統運行過程中,如果框架發現了URI的一個匹配,它會創建一個包含了每個占位符適用的值的字典集合。鍵是不包含大括弧的占位符名稱,例如controller、action。值是提取自URI路徑或者表單提交的數據。該字典被存儲在IHttpRouteData對象中。

在路由匹配階段,"{controller}"和"{action}"占位符會被像其他占位符一樣對待。它們被同其他值一起簡單地存儲在字典中。http://[server]/[appName]/api/user/get/1,路由字典將包含:

  • Controller: user
  • Action: get
  • Id: 1

 

控制器選擇和構建階段

在一個路徑匹配路由規則後,可以獲得到Controller和Action,並存放與路由字典中。Web API的消息處理管道由一組HttpMessageHandler經過"首尾相連"而成。WebApi 最終會引導到預設的HttpControllerDispatcher處理,其中HttpControllerDispatcher實現了HttpMessageHandler介面。HttpControllerDispatcher實現了目標HttpController對象的激活、執行。

  • HttpControllerDispatcher接收請求之後,會獲取IHttpControllerSelector的實現(預設是DefaultHttpControllerSelector),然後調用SelectController方法,創建HttpController的描述類HttpControllerDescriptor。
  • HttpControllerDispatcher接下來調用HttpControllerDescriptor對象的CreateController方法得到激活的HttpController對象。對於這個HttpControllerDescriptor對象來說,當它的CreateController方法被調用之後,它會獲取註冊的IHttpControllerActivator對象(預設是DefaultHttpControllerActivator),並調用其Create方法實現針對目標HttpController對象的激活並將激活的對象返回。
  • DefaultHttpControllerActivator對象根據HttpController類型去獲取代表目標HttpController實例的對象。如果後者返回一個具體的HttpController對象,該對象將直接作為方法的返回值,否則DefaultHttpControllerActivator直接採用反射的形式創建目標HttpController對象並返回。

 

執行器選擇和調用階段

ApiController是HttpController的基類。ApiController中的ExecuteAsync方法實現了Action的選擇和執行。

  • 首先執行GetActionSelector()方法,獲取IHttpActionSelector的實現ApiControllerActionSelector,ApiControllerActionSelector調用SelectAction方法,構建用來描述HttpAction的HttpActionDescriptor..
  • 然後創建Action的上下文,HttpActionContext。
  • 最後執行GetActionInvoker方法,獲取IHttpActionInvoker。緊接著調用ActionInvoker的InvokeActionAsync方法,執行Action並反饋HttpResponseMessage格式的數據。

至此,整個從路由到執行並返回結果的整個流程就結束了。

 

上面洋洋灑灑介紹了整個WeApi的路由和執行過程,下麵我們就根據這個流程來確定如何加入我們修改的內容,以通過業務邏輯層的方法自動實現WebApi。業務邏輯層是一個個的邏輯實現類,類中包含了一個個的業務邏輯方法,例如UserService類包含了GetUser方法。我們最終的實現就是生成http://[server]/[appName]/User/GetUser的WebApi調用方式。做法是:

  • 替換IHttpControllerSelector的實現DefaultHttpControllerSelector,將Controller的查找重定向到邏輯層的邏輯類
  • 替換IHttpActionSelector的實現ApiControllerActionSelector,將Action的查找重定向到邏輯類的方法
  • 替換IHttpActionInvoker的實現ApiControllerActionInvoker,將Action執行重定向到方法的執行

 

在替換之前,我們預先做了部分處理,在所有程式集中查找所有以AppService結尾的邏輯類,並將邏輯類生成為DynamicApiControllerInfo,註冊進DynamicApiControllerManager中,邏輯類中的GetUser等方法生成為DynamicApiActionInfo,存放在DynamicApiControllerInfo的Actions屬性中。該方式可以避免每次查找Controller和Action都要做反射的做法,提高系統執行效率。

這裡,我們約定所有以AppService結尾的邏輯類都要自動生成WebApi,也可以根據情況寫成繼承IApplicationService介面的類自動生成WebApi。

 1     public static class DynamicApiBuilder
 2     {
 3         public static void Build()
 4         {
 5             IEnumerable<Type> types = ReflectionHelper.GetSubTypes<object>().Where(type => !type.IsAbstract && type.IsPublic && type.FullName.EndsWith("AppService"));
 6 
 7             foreach (Type type in types)
 8             {
 9                 DynamicApiControllerInfo controllerInfo = GenerateApiControllerInfo(type);
10 
11                 DynamicApiControllerManager.Register(controllerInfo);
12             }
13         }
14 
15         private static DynamicApiController GenerateApiController()
16         {
17             DynamicApiController controller = new DynamicApiController();
18 
19             return controller;
20         }
21 
22         private static DynamicApiControllerInfo GenerateApiControllerInfo(Type type)
23         {
24             //10位是AppService
25             DynamicApiControllerInfo controllerInfo = new DynamicApiControllerInfo(type);
26 
27             foreach (MethodInfo methodInfo in GetMethodsOfType(type))
28             {
29                 DynamicApiActionInfo actionInfo = new DynamicApiActionInfo(methodInfo.Name, GetNormalizedVerb(methodInfo), methodInfo);
30 
31                 controllerInfo.Actions.Add(actionInfo.ActionName, actionInfo);
32             }
33 
34             return controllerInfo;
35         }
36 
37         private static IEnumerable<MethodInfo> GetMethodsOfType(Type type)
38         {
39             var allMethods = new List<MethodInfo>();
40 
41             FillMethodsRecursively(type, BindingFlags.Public | BindingFlags.Instance, allMethods);
42             //method.DeclaringType != typeof(ApplicationService) &&
43             return allMethods.Where(method => method.DeclaringType != typeof(object) && !IsPropertyAccessor(method));
44         }
45 
46         private static void FillMethodsRecursively(Type type, BindingFlags flags, List<MethodInfo> members)
47         {
48             members.AddRange(type.GetMethods(flags).Where(m => !members.Exists(mm => m.Name == mm.Name)));
49 
50             foreach (var interfaceType in type.GetInterfaces())
51             {
52                 FillMethodsRecursively(interfaceType, flags, members);
53             }
54         }
55 
56         private static bool IsPropertyAccessor(MethodInfo method)
57         {
58             return method.IsSpecialName && (method.Attributes & MethodAttributes.HideBySig) != 0;
59         }
60     }

 

接下來實現DynamicApiControllerSelector,替換預設的DefaultHttpControllerSelector。這個程式的主要做法是從路由信息RouteData中獲取當前的Controller和Action等信息。根據Controller信息從DynamicApiControllerManager中獲取已註冊的DynamicApiControllerInfo,創建Controller的描述類DynamicApiControllerDescriptor。DynamicApiControllerManager就是預處理部分,將DynamicApiControllerInfo註冊的類。

 1     public class DynamicApiControllerSelector : DefaultHttpControllerSelector
 2     {
 3         private readonly HttpConfiguration _Configuration;
 4 
 5         public DynamicApiControllerSelector(HttpConfiguration configuration)
 6             : base(configuration)
 7         {
 8             _Configuration = configuration;
 9         }
10 
11         public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
12         {
13             if (request == null)
14             {
15                 return base.SelectController(null);
16             }
17 
18             var routeData = request.GetRouteData();
19             if (routeData == null)
20             {
21                 return base.SelectController(request);
22             }
23 
24             //Get Area/Controller/Action from route
25             object objArea;
26             if (!routeData.Values.TryGetValue("area", out objArea))
27             {
28                 return base.SelectController(request);
29             }
30             object objController;
31             if (!routeData.Values.TryGetValue("controller", out objController))
32             {
33                 return base.SelectController(request);
34             }
35             object objAction;
36             if (!routeData.Values.TryGetValue("action", out objAction))
37             {
38                 return base.SelectController(request);
39             }
40 
41             string areaName = (string)objArea;
42             string controllerName = (string)objController;
43             string actionName = (string)objAction;
44 
45             //Normalize serviceNameWithAction
46             if (actionName.EndsWith("/"))
47             {
48                 actionName = actionName.Substring(0, actionName.Length - 1);
49                 routeData.Values["action"] = actionName;
50             }
51 
52             DynamicApiControllerInfo controllerInfo = DynamicApiControllerManager.Get(areaName, controllerName);
53 
54             //Create the controller descriptor
55             var controllerDescriptor = new DynamicApiControllerDescriptor(_Configuration, controllerInfo.AreaName, controllerInfo.ControllerName);
56             controllerDescriptor.Properties["__MicroLibraryDynamicApiControllerInfo"] = controllerInfo;
57             return controllerDescriptor;
58         }
59     }

 

下一步就是實現DynamicApiActionSelector,替換預設的ApiControllerActionSelector。DynamicApiActionSelector重寫了SelectAction方法,主要做法是Controller的上下文HttpControllerContext中獲取當前的DynamicApiControllerInfo,創建Action的描述類DynamicApiActionDescriptor。

 1     public class DynamicApiActionSelector : ApiControllerActionSelector
 2     {
 3        public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
 4         {
 5             object controllerInfoObj;
 6             if (!controllerContext.ControllerDescriptor.Properties.TryGetValue("__MicroLibraryDynamicApiControllerInfo", out controllerInfoObj))
 7             {
 8                 return GetDefaultActionDescriptor(controllerContext);
 9             }
10 
11             //Get controller information which is selected by HttpControllerSelector.
12             var controllerInfo = controllerInfoObj as DynamicApiControllerInfo;
13             MicroLibraryExceptionHelper.IsNull(controllerInfo, this.GetType().FullName, TraceLogType.Error, "DynamicApiControllerInfo in ControllerDescriptor.Properties is not a " + typeof(DynamicApiControllerInfo).FullName + " class.");
14 
15             string areaName = (string)controllerContext.RouteData.Values["area"];
16             string controllerName = (string)controllerContext.RouteData.Values["controller"];
17             string actionName = (string)controllerContext.RouteData.Values["action"];
18 
19             return GetActionDescriptorByActionName(controllerContext, controllerInfo, actionName);
20         }
21 
22         private HttpActionDescriptor GetActionDescriptorByActionName(HttpControllerContext controllerContext, DynamicApiControllerInfo controllerInfo, string actionName)
23         {
24             //Get action information by action name
25             DynamicApiActionInfo actionInfo;
26             MicroLibraryExceptionHelper.FalseThrow(controllerInfo.Actions.TryGetValue(actionName, out actionInfo), this.GetType().FullName, TraceLogType.Error, "在Api Controller:" + controllerInfo.ControllerName + "中,沒有對應的Action:" + actionName);
27             MicroLibraryExceptionHelper.FalseThrow(actionInfo.Verb.IsEqualTo(controllerContext.Request.Method), this.GetType().FullName, TraceLogType.Error, "在Api Controller:" + controllerInfo.ControllerName + "中的Action:" + actionName + "的HttpVerb不正確,應該為:" + actionInfo.Verb);
28 
29             return new DynamicApiActionDescriptor(controllerContext.ControllerDescriptor, actionInfo.Method);
30         }
31 
32         private HttpActionDescriptor GetDefaultActionDescriptor(HttpControllerContext controllerContext)
33         {
34             return base.SelectAction(controllerContext);
35         }
36     }

 

需要說明的是,我們只是按照ActionName來確定最終的Action。還可以按照HttpVeb來確定Actin的方式,我們沒有使用該方式,因此沒有實現,可以參照如下代碼實現:

 1         private HttpVerb GetNormalizedVerb(MethodInfo methodInfo)
 2         {
 3             if (methodInfo.IsDefined(typeof(HttpGetAttribute)))
 4             {
 5                 return HttpVerb.Get;
 6             }
 7 
 8             if (methodInfo.IsDefined(typeof(HttpPostAttribute)))
 9             {
10                 return HttpVerb.Post;
11             }
12 
13             if (methodInfo.IsDefined(typeof(HttpPutAttribute)))
14             {
15                 return HttpVerb.Put;
16             }
17 
18             if (methodInfo.IsDefined(typeof(HttpDeleteAttribute)))
19             {
20                 return HttpVerb.Delete;
21             }
22 
23             if (methodInfo.IsDefined(typeof(HttpOptionsAttribute)))
24             {
25                 return HttpVerb.Options;
26             }
27 
28             if (methodInfo.IsDefined(typeof(HttpHeadAttribute)))
29             {
30                 return HttpVerb.Head;
31             }
32 
33             return HttpVerb.Get;
34         }
35 
36         private HttpActionDescriptor GetActionDescriptorByCurrentHttpVerb(HttpControllerContext controllerContext, DynamicApiControllerInfo controllerInfo)
37         {
38             //Check if there is only one action with the current http verb
39             var actionsByVerb = controllerInfo.Actions.Values.Where(action => action.Verb.IsEqualTo(controllerContext.Request.Method));
40 
41             MicroLibraryExceptionHelper.TrueThrow(actionsByVerb.Count() == 0, this.GetType().FullName, TraceLogType.Error, "在Api Controller:" + controllerInfo.ServiceName + "中,沒有HttpVerb: " + controllerContext.Request.Method + "的Action");
42             MicroLibraryExceptionHelper.TrueThrow(actionsByVerb.Count() > 1, this.GetType().FullName, TraceLogType.Error, "在Api Controller:" + controllerInfo.ServiceName + "中,HttpVerb: " + controllerContext.Request.Method + "的Action有多個");
43 
44             //Return the single action by the current http verb
45             return new DynamicApiActionDescriptor(controllerContext.ControllerDescriptor, actionsByVerb.First().Method);
46         }
View Code

 

再下一步就是實現DynamicApiActionInvoker,替換預設的ApiControllerActionInvoker。DynamicApiActionInvoker實現了InvokeActionAsync方法,主要做法是獲取Controller和Action的描述類DynamicApiControllerDescriptor、DynamicApiActionDescriptor。根據Controller的描述類獲取DynamicApiControllerInfo,進一步獲取ControllerInfo的ServiceType,這就是邏輯類的類型信息。通過我們自己的Ioc管理器IocManager,從DI容器中獲取SerivceType的具體實現obj對象。然後從Action描述中獲取MethodInfo,執行方法。返回結果信息,返回前在頭部增加Access-Control-Allow-Origin等信息。

還有一點說明的是需要在Web.config中進行修改,modules中移除WebDavModule,handlers中移除WebDav和OPTIONSVerbHandler。

 1     public class DynamicApiActionInvoker : IHttpActionInvoker
 2     {
 3         private bool authenticatedFlag = DynamicApiConfiguration.GetConfig().AuthenticatedFlag;
 4 
 5         public Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
 6         {
 7             DynamicApiActionDescriptor actionDescriptor = actionContext.ActionDescriptor as DynamicApiActionDescriptor;
 8             DynamicApiControllerDescriptor controllerDescriptor = actionContext.ControllerContext.ControllerDescriptor as DynamicApiControllerDescriptor;
 9 
10             DynamicApiControllerInfo controllerInfo = controllerDescriptor.Properties["__MicroLibraryDynamicApiControllerInfo"] as DynamicApiControllerInfo;
11 
12             object obj = IocManager.Instance.Resolve(controllerInfo.ServiceType);
13 
14             MicroLibraryExceptionHelper.FalseThrow(Verify(actionContext), this.GetType().FullName, TraceLogType.Error, "Token驗證不通過!");
15 
16             //todo: 異常處理以及參數順序問題
17             object result = actionDescriptor.MethodInfo.Invoke(obj, actionContext.ActionArguments.Where(kvp => !string.Equals(kvp.Key, "sign", StringComparison.OrdinalIgnoreCase) || !string.Equals(kvp.Key, "appKey", StringComparison.OrdinalIgnoreCase)).Select(kvp => kvp.Value).ToArray());
18 
19             return Task.Run<HttpResponseMessage>(() =>
20              {
21                  string strResult;
22                  if (result is String || result is Char)
23                  {
24                      strResult = obj.ToString();
25                  }
26                  else
27                  {
28                      strResult = SerializerHelper.ToJson(result);
29                  }
30 
31                  var response = new HttpResponseMessage()
32                  {
33                      Content = new StringContent(strResult, Encoding.GetEncoding("UTF-8"), "application/json")
34                  };
35 
36                  //需要在web.config中,system.webServer---modules---remove name="WebDavModule"
37                  //---handlers---remove name="WebDav"和remove name="OPTIONSVerbHandler"
38                  response.Headers.Add("Access-Control-Allow-Origin", "*");
39                  response.Headers.Add("Access-Control-Allow-Headers", "X-Requested-With");
40                  if (actionContext.Request.Method.Method == "Options")
41                  {
42                      response.Headers.Add("Access-Control-Allow-Methods", "*");
43                      response.Headers.Add("Access-Control-Allow-Headers", "*");
44                  }
45                  return response;
46              });
47         }
48 
49         private bool Verify(HttpActionContext actionContext)
50         {
51             if (!authenticatedFlag) return true;
52 
53             var attrs = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
54             if (attrs.Any()) return true;
55 
56             string appKey = (string)actionContext.ActionArguments["appCode"];
57 
58             return DynamicApiHelper.ValidAppSecretSign(actionContext.ActionArguments, appKey);
59         }
60     }

 

大家可能還註意到我們有Verify方法。這個方法是判斷對WebApi是否有許可權訪問。在這種Rest形式的服務中,服務介面對外部暴露出來,因此對用戶調用的授權尤為重要。在Dynamic WebAPI的安全實現過程中,使用了JWT(JsonWebToken)技術,這裡就不做贅述了。

 

面向雲的.net core開發框架


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 字元串轉組件名 字元串轉變數名 或 ...
  • EntityFramework 一對一關係映射有很多種,比如主鍵作為關聯,配置比較簡單,示例代碼: 上面代碼表示 Teacher 和 Student 一對一關係,Fluent API 配置如下: 測試代碼: 生成 SQL 代碼: 另一種 Fluent API 配置如下: 執行同樣測試代碼,生成 SQ ...
  • ...
  • 本文主要涉及兩個概念: 阿裡雲OSS:對象存儲(Object Storage Service,簡稱OSS),是阿裡雲對外提供的海量、安全和高可靠的雲存儲服務。 bootstrap-fileinput:An enhanced HTML 5 file input for Bootstrap 3.x wi ...
  • 刪除重覆的文件功能 使用方法: 建一個BAT文件,如1.bat,裡面寫入:RemoveDuplicate.exe path1 path2 (或者在命令行下輸入以上內容) 其中path1表示原文件夾,path2表示要檢測和刪除的文件夾 例如文件夾path1中有:1.txt、2.txt、3.txt、4. ...
  • 吃飯的時候翻開推特發現巨硬在開大會,真是後知後覺啊。整理了一下幾個大事分享出來: 1.谷歌雲加入了.NET 基金會的一個小組。 2.三星Tizen系統將整合.NET Core平臺,並於2017年正式推出。這個系統目前貌似主要用在三星電視中,原文描述如下: Tizen’s .NET support w... ...
  • 本演練介紹瞭如何使用新資料庫進行 Code First 開發。我們用類定義一個模型,然後使用該模型創建一個資料庫,然後存儲和檢索數據。資料庫創建之後,我們使用 Code First 遷移將架構更改為我們發展後的模型。此外還介紹瞭如何使用數據註釋和 Fluent API 來配置模型。 ...
  • 在IIS中瀏覽某個網站時,出錯案例現場: 編譯器錯誤消息: CS0016: 未能寫入輸出文件“c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\41c262f4\874fe77f\App_Web_ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...