.Net Core 3.0後臺使用httpclient請求網路網頁和圖片_使用Core3.0做一個簡單的代理伺服器

来源:https://www.cnblogs.com/lxhbky/archive/2019/10/25/11740862.html
-Advertisement-
Play Games

目標:使用.net core最新的3.0版本,藉助httpclient和本機的host功能變數名稱代理,實現網路請求轉發和內容獲取,最終顯示到目標客戶端! 背景:本人在core領域是個新手,對core的使用不多,因此在實現的過程中遇到了很多坑,在這邊博客中,逐一介紹下。下麵進入正文 正文: 1-啟用http ...


目標:使用.net core最新的3.0版本,藉助httpclient和本機的host功能變數名稱代理,實現網路請求轉發和內容獲取,最終顯示到目標客戶端!

背景:本人在core領域是個新手,對core的使用不多,因此在實現的過程中遇到了很多坑,在這邊博客中,逐一介紹下。下麵進入正文

 

正文:

 

1-啟用httpClient註入:

參考文檔:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.0

services.AddHttpClient("configured-inner-handler").ConfigurePrimaryHttpMessageHandler(() =>
            {
                return new HttpClientHandler()
                {
                    AllowAutoRedirect = false,
                    UseDefaultCredentials = true,
                    Proxy = new MyProxy(new Uri("你的代理Host"))
                };
            });

這裡添加了httpClient的服務,且設置了一些其他選項:代理等

 

2-添加和配置接受請求的中間件:

 

參考文檔:1:  官網-中間件      2:    ASP.NET到ASP.NET Core Http模塊的遷移

a-創建中間件:

public class DomainMappingMiddleware : BaseMiddleware
    {
        public ConfigSetting ConfigSetting { get; set; }

        public ILogger<DomainMappingMiddleware> Logger { get; set; }

        public HttpClient HttpClient = null;

        private static object _Obj = new object();
        public DomainMappingMiddleware(RequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, ConfigSetting configSetting, ILogger<DomainMappingMiddleware> logger, IHttpClientFactory clientFactory) : base(next, configuration, memoryCache)
        {
            this.ConfigSetting = configSetting;
            this.Logger = logger;
            this.HttpClient = clientFactory.CreateClient("domainServiceClient");
        }


        public async Task Invoke(HttpContext httpContext)
        {
            string requestUrl = null;
            string requestHost = null;

            string dateFlag = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:fff");

            requestUrl = httpContext.Request.GetDisplayUrl();

            bool isExistDomain = false;
            bool isLocalWebsite = this.ConfigSetting.GetValue("IsLocalDomainService") == "true";

            if (httpContext.Request.Query.ContainsKey("returnurl"))
            {
                requestUrl = httpContext.Request.Query["returnurl"].ToString();
                requestUrl = HttpUtility.UrlDecode(requestUrl);
                isLocalWebsite = false;
            }

            Match match = Regex.Match(requestUrl, this.ConfigSetting.GetValue("DomainHostRegex"));
            if (match.Success)
            {
                isExistDomain = true;
                requestHost = match.Value;
            }

#if DEBUG
            requestUrl = "http://139.199.128.86:444/?returnurl=https%3A%2F%2F3w.huanqiu.com%2Fa%2Fc36dc8%2F9CaKrnKnonm";
#endif

            if (isExistDomain)
            {
                this.Logger.LogInformation($"{dateFlag}_記錄請求地址:{requestUrl},是否存在當前域:{isExistDomain},是否是本地環境:{isLocalWebsite}");

                bool isFile = false;

                //1-設置響應的內容類型
                MediaTypeHeaderValue mediaType = null;

                if (requestUrl.Contains(".js"))
                {
                    mediaType = new MediaTypeHeaderValue("application/x-javascript");
                    //mediaType.Encoding = System.Text.Encoding.UTF8;
                }
                else if (requestUrl.Contains(".css"))
                {
                    mediaType = new MediaTypeHeaderValue("text/css");
                    //mediaType.Encoding = System.Text.Encoding.UTF8;
                }
                else if (requestUrl.Contains(".png"))
                {
                    mediaType = new MediaTypeHeaderValue("image/png");
                    isFile = true;
                }
                else if (requestUrl.Contains(".jpg"))
                {
                    mediaType = new MediaTypeHeaderValue("image/jpeg");
                    isFile = true;
                }
                else if (requestUrl.Contains(".ico"))
                {
                    mediaType = new MediaTypeHeaderValue("image/x-icon");
                    isFile = true;
                }
                else if (requestUrl.Contains(".gif"))
                {
                    mediaType = new MediaTypeHeaderValue("image/gif");
                    isFile = true;
                }
                else if (requestUrl.Contains("/api/") && !requestUrl.Contains("/views"))
                {
                    mediaType = new MediaTypeHeaderValue("application/json");
                }
                else
                {
                    mediaType = new MediaTypeHeaderValue("text/html");
                    mediaType.Encoding = System.Text.Encoding.UTF8;
                }

                //2-獲取響應結果

                if (isLocalWebsite)
                {
                    //本地伺服器將請求轉發到遠程伺服器
                    requestUrl = this.ConfigSetting.GetValue("MyDomainAgentHost") + "?returnurl=" + HttpUtility.UrlEncode(requestUrl);
                }

                if (isFile == false)
                {
                    string result = await this.HttpClient.MyGet(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_長度{result.Length}");

                        //請求結果展示在客戶端,需要重新請求本地伺服器,因此需要將https轉為http
                        result = result.Replace("https://", "http://");
                        //替換"/a.ico" 為:"http://www.baidu.com/a.ico"
                        result = Regex.Replace(result, "\"\\/(?=[a-zA-Z0-9]+)", $"\"{requestHost}/");
                        //替換"//www.baidu.com/a.ico" 為:"http://www.baidu.com/a.ico"
                        result = Regex.Replace(result, "\"\\/\\/(?=[a-zA-Z0-9]+)", "\"http://");

                        //必須有請求結果才能給內容類型賦值;如果請求過程出了異常再賦值,會報錯:The response headers cannot be modified because the response has already started.
                        httpContext.Response.ContentType = mediaType.ToString();

                        await httpContext.Response.WriteAsync(result ?? "");
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_圖片位元組流長度{result.Length}_Response已啟動");
                    }
                }
                else
                {
                    byte[] result = await this.HttpClient.MyGetFile(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_圖片位元組流長度{result.Length}");

                        httpContext.Response.ContentType = mediaType.ToString();
                        await httpContext.Response.Body.WriteAsync(result, 0, result.Length);
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_圖片位元組流長度{result.Length}_Response已啟動");
                    }
                }
            }
        }
    }
View Code

繼承類:

/// <summary>
    /// 中間件基類
    /// </summary>
    public abstract class BaseMiddleware
    {
        /// <summary>
        /// 等同於ASP.NET裡面的WebCache(HttpRuntime.Cache)
        /// </summary>
        protected IMemoryCache MemoryCache { get; set; }

        /// <summary>
        /// 獲取配置文件裡面的配置內容
        /// </summary>
        protected IConfiguration Configuration { get; set; }

        public BaseMiddleware(RequestDelegate next, params object[] @params)
        {
            foreach (var item in @params)
            {
                if (item is IMemoryCache)
                {
                    this.MemoryCache = (IMemoryCache)item;
                }
                else if (item is IConfiguration)
                {
                    this.Configuration = (IConfiguration)item;
                }
            }
        }

    }
View Code

httpClient擴展類:

public static class HttpClientSingleston
    {
        public async static Task<string> MyGet(this HttpClient httpClient, string url)
        {
            string result = null;

            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        using (Stream stream = await response.Content.ReadAsStreamAsync())
                        {
                            using (StreamReader streamReader = new StreamReader(stream, Encoding.UTF8))
                            {
                                result = await streamReader.ReadToEndAsync();
                            }
                        }

                    }
                }
            }
            return result ?? "";
        }

        public async static Task<byte[]> MyGetFile(this HttpClient httpClient, string url)
        {
            byte[] result = null;
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        result = await response.Content.ReadAsByteArrayAsync();
                    }
                }
            }
            return result ?? new byte[0];
        }

    }
View Code

 

b-註冊中間件:

在Startup.cs的Configure方法中:

app.UseMiddleware<DomainMappingMiddleware>();

 

小結:該中間件負責接受請求,並處理請求(由於項目是用來專門處理網路網頁和圖片的,因此沒有對請求的Url篩選過濾,實際使用時需要註意);該中間件即負責處理請求的轉發,又負責處理網路圖片和內容的獲取;

轉發的目的,當然是為了規避網路IP的限制,當你想訪問某一網站卻發現被禁止訪問的時候,而這時候你又有一臺可以正常訪問的伺服器且你和你的伺服器能正常連接的時候,那麼你就可以用這個方式了,做一個簡單的代理伺服器做中轉,來間接訪問我們想看的網站,是不是很神奇?  哈哈,我覺得是的,因為沒這麼乾過。

踩過的坑有:   

  bug0-HTTP Error 500.0 - ANCM In-Process Handler Load Failure

  bug1-The response headers cannot be modified because the response has already started.

  bug2-An unhandled exception was thrown by the application. IFeatureCollection has been disposed

  bug3-An unhandled exception was thrown by the application. The SSL connection could not be established, see inner exception.

  bug4-this request has no response data

  bug5-獲取的網路圖片返回字元串亂碼

  bug6-瀏覽器顯示網頁各種資源請求錯誤:IIS7.5 500 Internal Server Error

  bug7-response如何添加響應頭?

  bug8-如何設置在core中設置伺服器允許跨域請求?

  bug9-如何在Core中使用NLog日誌記錄請求信息和錯誤?

 

逐一解答:

  bug0:一般會在第一次在IIS上調試core項目會遇到,一般是因為電腦未安裝AspNetCoreModuleV2對IIS支持Core的模塊導致,還需要檢查項目的應用程式池的.Net Framework版本是否是選擇的無托管模式。

  

 

 

 參考其他道友文章:https://www.cnblogs.com/leoxjy/p/10282148.html

  

  bug1:這是因為response發送響應消息後,又修改了response的頭部的值拋出的異常,我上面列舉的代碼已經處理了該問題,該問題導致了我的大部分坑的產生,也是我遇到的最大的主要問題。這個錯誤描述很清楚,但是我從始至終的寫法並沒有在response寫入消息後,又修改response的頭部,且為了修改該問題,使用了很多輔助手段:

  在發送消息前使用:if (httpContext.Response.HasStarted == false) 做判斷後再發送,結果是錯誤少了一些,但是還是有的,後來懷疑是多線程可能導致的問題,我又加上了了lock鎖,使用lock鎖和response的狀態一起判斷使用,最後是堵住了該錯誤,但是我想要的內容並沒有出現,且瀏覽器端顯示了很多bug6錯誤。

  

  最後是在解決bug2的時候,終於在google上搜索到正確的答案:Disposed IFeatureCollection for subsequent API requests    通過左邊的文檔找到了關鍵的開髮指南: ASP.NET核心指南

  通過指南發現我的一個嚴重錯誤:

    a-將httpContext及其屬性(request,response等)存到了中間件的屬性中使用!!!    X

    b-將httpContext及其屬性(request,response等)存到了中間件的屬性中使用!!!    XX

    c-將httpContext及其屬性(request,response等)存到了中間件的屬性中使用!!!    XXX

  這個我自己挖的深坑導致我很多的錯誤!

  不讓這樣用的原因主要是以為Core的特性,沒錯,就是註入,其中中間件是一個註入進來的單例模式的類,在啟動後會初始化一次構造函數,但是之後的請求就不會再執行了,因此如果把context放到單例的屬性中,結果可想而知,單例的屬性在多線程下,數據不亂才改,response在發送消息後不被再次修改才怪!!

 

  

 

 

 

  bug2:同bug1.

  bug3:不記得怎麼處理的了,可能和許可權和https請求有關,遇到在修改解決方案吧,大家也可以百度和谷歌,是能搜到的,能不能解決問題,大家去試吧。

  bug4:是請求沒有響應的意思,這裡是我在獲取內容的時候使用的非同步方法,沒有使用await等待結果導致的。一般使用httpClient獲取影響內容要加上:await httpClient.SendAsync(request) ,等待結果後再做下一步處理。

  bug5:獲取響應的圖片亂碼是困擾我的另一個主要問題:

    初步的實現方式是:請求圖片地址,獲取響應字元,直接返回給客戶端,這肯定不行。因為你需要在response的內容類型上加上對應的類型值:

      mediaType = new MediaTypeHeaderValue("image/jpeg");

      httpContext.Response.ContentType = mediaType.ToString();

      await httpContext.Response.WriteAsync(result ?? "")

    藍後,上面雖然加了響應的內容類型依然不行,因為圖片是一種特殊的數據流,不能簡單實用字元串傳輸的方式,位元組數據在轉換的過程中可能丟失。後來在領導的項目中看到了以下發送圖片響應的方法:

//直接輸出文件
await response.SendFileAsync(physicalFileInfo);

    嘗試後發現,我只能將response的響應內容讀取中字元串,怎麼直接轉成圖片文件呢?  難道我要先存下來,再通過這種方式發送出去,哎呀!物理空間有限啊,不能這麼乾,必須另想他發,百度和google搜索後都沒有找到解決方案,終於想了好久,突然發現Response對象的Body屬性是一個Stream類型,是可以直接出入位元組數據的,於是最終的解決方案出爐啦:

    本解決方案獨一無二,百度谷歌獨家一份,看到就是賺到哈!!!

    一段神奇的代碼產生了:await httpContext.Response.Body.WriteAsync(result, 0, result.Length);
public async static Task<byte[]> MyGetFile(this HttpClient httpClient, string url)
        {
            byte[] result = null;
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
            {
                using (var response = await httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        result = await response.Content.ReadAsByteArrayAsync();
                    }
                }
            }
            return result ?? new byte[0];
        }
byte[] result = await this.HttpClient.MyGetFile(requestUrl);

                    if (httpContext.Response.HasStarted == false)
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_圖片位元組流長度{result.Length}");
                        MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("image/gif");
                        httpContext.Response.ContentType = mediaType.ToString();
                        await httpContext.Response.Body.WriteAsync(result, 0, result.Length);
                    }
                    else
                    {
                        this.Logger.LogInformation($"{dateFlag}_請求結束_{requestUrl}_圖片位元組流長度{result.Length}_Response已啟動");
                    }

  bug6:同bug1.

  bug7:官網文檔給瞭解決方案,總之就是,你不要在response寫入消息後再修改response就好了。    參照官方文檔:  發送HttpContext.Response.Headers

  

 

 

 

  bug8:直接上代碼吧:

    在Setup.cs的ConfigService方法中添加:

services.AddCors(options =>
            {
                options.AddPolicy("AllowSameDomain", builder =>
                {
                    //允許任何來源的主機訪問
                    builder.AllowAnyOrigin()
                    .AllowAnyHeader();
                });
            });

    在Setup.cs的Configure方法中添加:

app.UseCors();

 

  bug9:使用NLog日誌的代碼如下:

    在Program.cs其中類的方法CreateHostBuilder添加以下加粗代碼:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureLogging(logging =>
                {
                    //https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Information);
                }).UseNLog();

    添加Nlog的配置文件:nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Warn"
      internalLogFile="internal-nlog.txt">

  <!--define various log targets-->
  <targets>
    <!--write logs to file-->
    <target xsi:type="File" name="allfile" fileName="${basedir}/logs/${shortdate}.log"
                 layout="${longdate}|${logger}|${uppercase:${level}}${newline}${message} ${exception}${newline}" />
    
    <target xsi:type="Console" name="console"
          layout= "${longdate}|${logger}|${uppercase:${level}}${newline}${message} ${exception}${newline}"/>
  </targets>
  <rules>
    <!--All logs, including from Microsoft-->
    <!--<logger name="*" minlevel="Trace" writeTo="allfile" />-->
    <!--Skip Microsoft logs and so log only own logs-->
    <logger name="*" minlevel="Info" writeTo="allfile" />
  </rules>
</nlog>

 

  最後是給項目註入NLog的Nuget核心包引用:

  

 

 

  使用方式是註入的方式:

public ILogger<DomainMappingMiddleware> Logger { get; set; }

        public HttpClient HttpClient = null;

        private static object _Obj = new object();
        public DomainMappingMiddleware(RequestDelegate next, IConfiguration configuration, IMemoryCache memoryCache, ConfigSetting configSetting, ILogger<DomainMappingMiddleware> logger, IHttpClientFactory clientFactory) : base(next, configuration, memoryCache)
        {
            this.ConfigSetting = configSetting;
            this.Logger = logger;
            this.HttpClient = clientFactory.CreateClient("domainServiceClient");
        }
this.Logger.LogInformation($"{dateFlag}_記錄請求地址:{requestUrl},是否存在當前域:{isExistDomain},是否是本地環境:{isLocalWebsite}");

 

  3-坑說完了,最後說說怎麼繞過IP限制吧:

    首先我們需要將https請求改成http請求,當然如果你的IIS支持Https可以不改;然後你需要修改本機的Host功能變數名稱解析規則,將你要繞的域指向本機IIS伺服器:127.0.0.1,不知道的小伙伴可以百度怎麼修改本機功能變數名稱解析;

    

     IIS接收到請求後,你還需要在項目中加上功能變數名稱配置,埠號一定是80哦:

    

 

     應用程式池配置:

    

 

 

     這樣就實現了將網路請求轉到IIS中了,那麼通過IIS部署的項目接收後,使用Core3.0最新的httpClient技術將請求轉發到你的伺服器中,當然你的伺服器也需要一個項目來接收發來的請求;

    最後是通過伺服器項目發送網路請求到目標網站請求真正的內容,最後再依次返回給用戶,也就是我們的瀏覽器,進行展示。。。

 

    結束了。。。寫了2個小時的博客,有點累,歡迎大家留言討論哈,不足之處歡迎指教!


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

-Advertisement-
Play Games
更多相關文章
  • 本文講解python列表的常用操作: 1.list函數,可以將任何序列作為list的參數 2.基本操作(多數方法為就地改變,不返回新列表) (1)賦值 ‘=’;切片賦值;刪除列表元素 結果: (2)列表方法 append用於將一個對象附加到列表末尾;註意: append就地修改列表,不會返回新列表; ...
  • PIL是Python Imaging Library的簡稱,PIL是一個Python處理圖片的庫,提供了一系列模塊和方法,比如:裁切,平移,旋轉,改變尺寸等等。已經是Python平臺事實上的圖像處理標準庫了。PIL功能非常強大,但API卻非常簡單易用。 PIL有如下幾個模塊:Image模塊、Imag ...
  • pythonic如果翻譯成中文的話就是很python。很+名詞結構的用法在中國不少,比如:很娘,很國足,很CCTV等等。· 以下為了簡略,我們用P表示pythonic的寫法,NP表示non-pythonic的寫法,當然此P-NP非彼P-NP。 為什麼要追求pythonic? 相比於NP,P的寫法簡練 ...
  • 多線程概述 多線程使得程式內部可以分出多個線程來做多件事情,充分利用CPU空閑時間,提升處理效率。python提供了兩個模塊來實現多線程thread 和threading ,thread 有一些缺點,在threading 得到了彌補。並且在Python3中廢棄了thread模塊,保留了更強大的thr ...
  • 對於每一個學習 Python 的同學,想必對 @ 符號一定不陌生了,正如你所知, @ 符號是裝飾器的語法糖,@符號後面的函數就是我們本文的主角:裝飾器。 裝飾器放在一個函數開始定義的地方,它就像一頂帽子一樣戴在這個函數的頭上。和這個函數綁定在一起。在我們調用這個函數的時候,第一件事並不是執行這個函數 ...
  • 併發和並行 併發(concurrency)和並行(parallellism): 所以併發編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。 並行(parallel):指在同一時刻,有多條指令在多個處理器上同時執行。所以無論從微觀還是從巨集觀來看,二者都是一起執行的。 併發(concurre ...
  • bit:位,一個二進位數據(0或者1),是1bit byte:位元組,存儲空間的基本單位,1byte=8bit 一個英文占一個位元組,1字母=1byte=8bit 一個中文占兩個位元組,1漢字=2byte=16bit byte:一個位元組(8位)(-128~127)(-2的7次方到2的7次方-1) shor ...
  • 【ASP.NET Core學習】入門,介紹創建項目,信任開發證書,運行項目,編輯頁面 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...