從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之八MemoryCache與redis緩存的使用

来源:https://www.cnblogs.com/levywang/archive/2019/09/11/coreframe_8.html
-Advertisement-
Play Games

1.緩存概念 1.什麼是緩存 這裡要講到的緩存是服務端緩存,簡單的說,緩存就是將一些實時性不高,但訪問又十分頻繁,或者說要很長時間才能取到的數據給存在記憶體當中,當有請求時直接返回,不用經過資料庫或介面獲取。這樣就可以減輕資料庫的負擔。 2.為什麼要用緩存 總的來說就是為了提高響應速度(用戶體驗度), ...


 

 1.緩存概念

  1.什麼是緩存

    這裡要講到的緩存是服務端緩存,簡單的說,緩存就是將一些實時性不高,但訪問又十分頻繁,或者說要很長時間才能取到的數據給存在記憶體當中,當有請求時直接返回,不用經過資料庫或介面獲取。這樣就可以減輕資料庫的負擔。

  2.為什麼要用緩存

    總的來說就是為了提高響應速度(用戶體驗度),減少資料庫訪問頻率。

    在一個用戶看來,軟體使用的體驗度才是關鍵,在對實時性要求不高的情況下,用戶肯定會覺得打開界面的響應速度快,能保證平常工作的應用才是好的。因此為了滿足這個需求,通過使用緩存,就可以保證滿足在正常工作的前提下響應時間儘可能短。

    例如:當客戶端向伺服器請求某個數據時,伺服器先在緩存中找,如果在緩存中,就直接返回,無需查詢資料庫;如果請求的數據不在緩存中,這時再去資料庫中找,找到後返回給客戶端,並將這個資源加入緩存中。這樣下次請求相同資源時,就不需

      要連接資料庫了。而且如果把緩存放在記憶體中,因為對記憶體的操作要比對資料庫操作快得多,這樣請求時間也會縮短。每當數據發生變化的時候(比如,數據有被修改,或被刪除的情況下),要同步的更新緩存信息,確保用戶不會在緩存取到舊的數據。

    如果沒有使用緩存,用戶去請求某個數據,當用戶量和數據逐漸增加的時候,就會發現每次用戶請求的時間越來越長,且資料庫無時不刻都在工作。這樣用戶和資料庫都很痛苦,時間一長,就有可能發生下以下事情:

      1.用戶常抱怨應用打開速度太慢,頁面經常無響應,偶爾還會出現崩潰的情況。

      2.資料庫連接數滿或者說資料庫響應慢(處理不過來)。

      3.當併發量上來的時候,可能會導致資料庫崩潰,使得應用無法正常使用。

 2.選用Redis還是Memcached

  簡單說下這兩者的區別,兩者都是通過key-value的方式進行存儲的,Memcached只有簡單的字元串格式,而Redis還支持更多的格式(list、 set、sorted set、hash table ),緩存時使用到的數據都是在記憶體當中,

  不同的在於Redis支持持久化,集群、簡單事務、發佈/訂閱、主從同步等功能,當斷電或軟體重啟時,Memcached中的數據就已經不存在了,而Redis可以通過讀取磁碟中的數據再次使用。

  這裡提高Windows版的安裝包:傳送門          可視化工具:因文件太大無法上傳到博客園。代碼倉庫中有,需要的私信哦~

 3.兩者在NetCore 中的使用

  Memcached的使用還是相當簡單的,首先在 Startup 類中做以下更改,添加緩存參數  賦值給外部類來方便使用  

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMemoryCache memoryCache)
        {
            //....  省略部分代碼
            DemoWeb.MemoryCache = memoryCache;
            //....  省略部分代碼
        }    

 

DemoWeb中的代碼:
 public class DemoWeb
    {
      
        //....省略部分代碼

        /// <summary>
        /// MemoryCache
        /// </summary>
        public static IMemoryCache MemoryCache { get; set; }

        /// <summary>
        /// 獲取當前請求客戶端IP
        /// </summary>
        /// <returns></returns>
        public static string GetClientIp()
        {
            var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',')[0].Trim();
            if (string.IsNullOrEmpty(ip))
            {
                ip = HttpContext.Connection.RemoteIpAddress.ToString();
            }
            return ip;
        }
    }

  然後創建 MemoryCache 來封裝些緩存的簡單方法

    /// <summary>
    /// MemoryCache緩存
    /// </summary>
    public class MemoryCache
    {
        private static readonly HashSet<string> Keys = new HashSet<string>();

        /// <summary>
        /// 緩存首碼
        /// </summary>
        public string Prefix { get; }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="prefix"></param>
        public MemoryCache(string prefix)
        {
            Prefix = prefix + "_";
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T Get<T>(string key)
        {
            return DemoWeb.MemoryCache.Get<T>(Prefix + key);
        }

        /// <summary>
        /// 設置 無過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        public void Set(string key, object data)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="absoluteExpiration"></param>
        public void Set(string key, object data, DateTimeOffset absoluteExpiration)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, absoluteExpiration);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="absoluteExpirationRelativeToNow"></param>
        public void Set(string key, object data, TimeSpan absoluteExpirationRelativeToNow)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, absoluteExpirationRelativeToNow);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="expirationToken"></param>
        public void Set(string key, object data, IChangeToken expirationToken)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, expirationToken);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="options"></param>
        public void Set(string key, object data, MemoryCacheEntryOptions options)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Set(key, data, options);
            if (!Keys.Contains(key))
            {
                Keys.Add(key);
            }
        }

        /// <summary>
        /// 移除某個
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            key = Prefix + key;
            DemoWeb.MemoryCache.Remove(key);
            if (Keys.Contains(key))
            {
                Keys.Remove(key);
            }
        }

        /// <summary>
        /// 清空所有
        /// </summary>
        public void ClearAll()
        {
            foreach (var key in Keys)
            {
                DemoWeb.MemoryCache.Remove(key);
            }
            Keys.Clear();
        }

    }
View Code

  其實接下來就可以直接使用緩存了,但為了方便使用,再建一個緩存類別的中間類來管理。

    public class UserCache
    {
        private static readonly MemoryCache Cache = new MemoryCache("User");

        private static TimeSpan _timeout = TimeSpan.Zero;
        private static TimeSpan Timeout
        {
            get
            {
                if (_timeout != TimeSpan.Zero)
                    return _timeout;
                try
                {
                    _timeout = TimeSpan.FromMinutes(20);
                    return _timeout;
                }
                catch (Exception)
                {
                    return TimeSpan.FromMinutes(10);
                }
            }
        }
        public static void Set(string key,string cache)
        {
            if (string.IsNullOrEmpty(cache))
                return;

            Cache.Set(key, cache, Timeout);
        }


        public static string Get(string key)
        {
            if (string.IsNullOrEmpty(key))
                return default(string);

            return Cache.Get<string>(key);
        }
    }
UserCache

  測試是否可以正常使用:代碼與截圖

        [HttpGet]
        [Route("mecache")]
        public ActionResult ValidToken()
        {
            var key = "tkey";
            UserCache.Set(key, "測試數據");
            return Succeed(UserCache.Get(key));
        }    

  可以清楚的看到 MemoryCache 可以正常使用。  

  那麼接下來將講到如何使用 Redis 緩存。先在需要封裝基礎類的項目 Nuget 包中添加  StackExchange.Redis  依賴。然後添加Redis 連接類

 internal class RedisConnectionFactory
    {
        public string ConnectionString { get; set; }
        public string Password { get; set; }

        public ConnectionMultiplexer CurrentConnectionMultiplexer { get; set; }


        /// <summary>
        /// 設置連接字元串
        /// </summary>
        /// <returns></returns>
        public void SetConnectionString(string connectionString)
        {
            ConnectionString = connectionString;
        }

        /// <summary>
        /// 設置連接字元串
        /// </summary>
        /// <returns></returns>
        public void SetPassword(string password)
        {
            Password = password;
        }

        public ConnectionMultiplexer GetConnectionMultiplexer()
        {
            if (CurrentConnectionMultiplexer == null || !CurrentConnectionMultiplexer.IsConnected)
            {
                if (CurrentConnectionMultiplexer != null)
                {
                    CurrentConnectionMultiplexer.Dispose();
                }

                CurrentConnectionMultiplexer = GetConnectionMultiplexer(ConnectionString);
            }

            return CurrentConnectionMultiplexer;
        }


        private ConnectionMultiplexer GetConnectionMultiplexer(string connectionString)
        {
            ConnectionMultiplexer connectionMultiplexer;

            if (!string.IsNullOrWhiteSpace(Password) && !connectionString.ToLower().Contains("password"))
            {
                connectionString += $",password={Password}";
            }

            var redisConfiguration = ConfigurationOptions.Parse(connectionString);
            redisConfiguration.AbortOnConnectFail = true;
            redisConfiguration.AllowAdmin = false;
            redisConfiguration.ConnectRetry = 5;
            redisConfiguration.ConnectTimeout = 3000;
            redisConfiguration.DefaultDatabase = 0;
            redisConfiguration.KeepAlive = 20;
            redisConfiguration.SyncTimeout = 30 * 1000;
            redisConfiguration.Ssl = false;

            connectionMultiplexer = ConnectionMultiplexer.Connect(redisConfiguration);

            return connectionMultiplexer;
        }
    }
RedisConnectionFactory

  再添加Redis客戶端類

    /// <summary>
    /// Redis Client
    /// </summary>
    public class RedisClient : IDisposable
    {
        public int DefaultDatabase { get; set; } = 0;

        private readonly ConnectionMultiplexer _client;
        private IDatabase _db;

        public RedisClient(ConnectionMultiplexer client)
        {
            _client = client;
            UseDatabase();
        }

        public void UseDatabase(int db = -1)
        {
            if (db == -1)
                db = DefaultDatabase;
            _db = _client.GetDatabase(db);
        }


        public string StringGet(string key)
        {
            return _db.StringGet(key).ToString();
        }


        public void StringSet(string key, string data)
        {
            _db.StringSet(key, data);
        }

        public void StringSet(string key, string data, TimeSpan timeout)
        {
            _db.StringSet(key, data, timeout);
        }


        public T Get<T>(string key)
        {
            var json = StringGet(key);
            if (string.IsNullOrEmpty(json))
            {
                return default(T);
            }

            return json.ToNetType<T>();
        }

        public void Set(string key, object data)
        {
            var json = data.ToJson();
            _db.StringSet(key, json);
        }

        public void Set(string key, object data, TimeSpan timeout)
        {
            var json = data.ToJson();
            _db.StringSet(key, json, timeout);
        }

        /// <summary>
        /// Exist
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Exist(string key)
        {
            return _db.KeyExists(key);
        }

        /// <summary>
        /// Delete
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Delete(string key)
        {
            return _db.KeyDelete(key);
        }

        /// <summary>
        /// Set Expire to Key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public bool Expire(string key, TimeSpan? expiry)
        {
            return _db.KeyExpire(key, expiry);
        }

        /// <summary>
        /// 計數器  如果不存在則設置值,如果存在則添加值  如果key存在且類型不為long  則會異常
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry">只有第一次設置有效期生效</param>
        /// <returns></returns>
        public long SetStringIncr(string key, long value = 1, TimeSpan? expiry = null)
        {
            var nubmer = _db.StringIncrement(key, value);
            if (nubmer == 1 && expiry != null)//只有第一次設置有效期(防止覆蓋)
                _db.KeyExpireAsync(key, expiry);//設置有效期
            return nubmer;
        }

        /// <summary>
        /// 讀取計數器
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public long GetStringIncr(string key)
        {
            var value = StringGet(key);
            return string.IsNullOrWhiteSpace(value) ? 0 : long.Parse(value);
        }

        /// <summary>
        /// 計數器-減少 如果不存在則設置值,如果存在則減少值  如果key存在且類型不為long  則會異常
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public long StringDecrement(string key, long value = 1)
        {
            var nubmer = _db.StringDecrement(key, value);
            return nubmer;
        }



        public void Dispose()
        {
            _client?.Dispose();
        }
    }
RedisClient

  然後再添加Redis連接生成工具類

    public static class RedisFactory
    {
        private static readonly object Locker = new object();

        private static RedisConnectionFactory factory;

        private static void InitRedisConnection()
        {
            try
            {
                factory = new RedisConnectionFactory();
                var connectionString = DemoWeb.Configuration["Redis:ConnectionString"];
#if DEBUG
                connectionString = "127.0.0.1:6379";
#endif
                factory.ConnectionString = connectionString;
                factory.Password = DemoWeb.Configuration["Redis:Pwd"];

            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "Redis連接創建失敗。");
            }
        }

        public static RedisClient GetClient()
        {
            //先判斷一輪,減少鎖,提高效率
            if (factory == null || string.IsNullOrEmpty(factory.ConnectionString))
            {
                //防止併發創建
                lock (Locker)
                {
                    InitRedisConnection();
                }
            }

            return new RedisClient(factory.GetConnectionMultiplexer())
            {
                DefaultDatabase = DemoWeb.Configuration["Redis:DefaultDatabase"].ToInt()
            };

        }
    }
RedisFactory

  這裡要使用到前面的靜態擴展方法。請自行添加  傳送門 ,還需要將 Startup 類中的 Configuration 給賦值到 DemoWeb中的 Configuration 欄位值來使用

  在配置文件 appsettings.json 中添加

  "Redis": {
    "ConnectionString": "127.0.0.1:6379",
    "Pwd": "",
    "DefaultDatabase": 0
  }

  再添加Redis緩存使用類

    /// <summary>
    /// Redis緩存
    /// </summary>
    public class RedisCache
    {
        private static RedisClient _client;
        private static RedisClient Client => _client ?? (_client = RedisFactory.GetClient());

        private static string ToKey(string key)
        {
            return $"Cache_redis_{key}";
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T Get<T>(string key)
        {
            try
            {
                var redisKey = ToKey(key);
                return Client.Get<T>(redisKey);
            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "RedisCache.Get \n key:{0}", key);
                return default(T);
            }
        }

        /// <summary>
        /// 嘗試獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        private static T TryGet<T>(string key, out bool result)
        {
            result = true;
            try
            {
                var redisKey = ToKey(key);
                return Client.Get<T>(redisKey);
            }
            catch (Exception e)
            {
                LogHelper.Logger.Fatal(e, "RedisCache.TryGet \n key:{0}", key);
                result = false;
                return default(T);
            }
        }

        /// <summary>
        /// 獲取
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="setFunc"></param>
        /// <param name="expiry"></param>
        /// <param name="resolver"></param>
        /// <returns></returns>
        public static T Get<T>(string key, Func<T> setFunc, TimeSpan? expiry = null)
        {
            var redisKey = ToKey(key);
            var result = TryGet<T>(redisKey, out var success);
            if (success && result == null)
            {
                result = setFunc();
                try
                {
                    Set(redisKey, result, expiry);
                }
                catch (Exception e)
                {
                    LogHelper.Logger.Fatal(e, "RedisCache.Get<T> \n key:{0}", key);
                }
            }
            return result;
        }

        /// <summary>
        /// 設置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public static bool Set<T>(string key, T value, TimeSpan? expiry = null)
        {
            var allRedisKey = ToKey("||Keys||");
            var redisKey = ToKey(key);

            var allkeyRedisValue = Client.StringGet(allRedisKey);
            var keys = allkeyRedisValue.ToNetType<List<string>>() ?? new List<string>();
            if (!keys.Contains(redisKey))
            {
                keys.Add(redisKey);
                Client.Set(allRedisKey, keys);
            }
            if (expiry.HasValue)
            {
                Client.StringSet(redisKey, value.ToJson(), expiry.Value);
            }
            else
            {
                Client.StringSet(redisKey, value.ToJson());
            }

            return true;
        }

        /// <summary>
        /// 重新設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expiry"></param>
        public static void ResetItemTimeout(string key, TimeSpan expiry)
        {
            var redisKey = ToKey(key);
            Client.Expire(redisKey, expiry);
        }

        /// <summary>
        /// Exist
        /// </summary>
        /// <param name="key">原始key</param>
        /// <returns></returns>
        public static bool Exist(string key)
        {
            var redisKey = ToKey(key);
            return Client.Exist(redisKey);
        }

        /// <summary>
        /// 計數器 增加  能設置過期時間的都設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiry"></pa

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

-Advertisement-
Play Games
更多相關文章
  • 一、instanceof運算符 instanceof是Java語言中的一個二元運算符,它的作用是判斷一個引用類型的變數所指向的對象是否是一個類(或介面、抽象類、父類)的實例,即它左邊的對象是否是它右邊的類的實例該運算符返回boolean類型的數據。 常見的用法為:result=object inst ...
  • [TOC] 原文鏈接: "QRowTable表格控制項(四) 效率優化之 優化數據源" 一、開心一刻 一程式員第一次上女朋友家她媽板著臉問 :你想娶我女兒,有多少存款? 程式員低了下頭:五百! 她媽更鄙視了:才五百塊,買個廁所都不夠! 程式員忙說:不是人民幣! 她媽:就算是美元,還是不夠買廁所! 程式 ...
  • Java中時間API使用詳解 [TOC] 1. 時區概念 國際經度會議(又稱國際子午線會議)上,規定將全球劃分為24個時區(東、西各12個時區)。規定英國的格林尼治天文臺舊址為中時區(零時區)、東1 12區,西1 12區。每個時區橫跨經度15度,時間正好是1小時。最後的東、西第12區各跨經度7.5度 ...
  • 電腦基礎方面的知識,對於一些非科班出身的同學來講,一直是他們心中的痛,而對於科班出身的同學,很多同學在工作之後,也意識到自身所學知識的不足與欠缺,想回頭補補基礎知識。關於電腦基礎的課程很多,內容繁雜,但無論是相關書籍還是大學課程,都有點脫離工作。特別地,電腦基礎知識體系龐雜,想要從零學習或者復 ...
  • 剛剛開始寫微服務,這篇博客就紀念我第一個微服務吧。 1.新建工程 首先要搭建一個微服務環境,我用的是開發環境IDEA,詳細步驟如下: ①打開IDEA File->New->Project 選擇Spring lnitializr 然後點擊next。 填寫包名和項目名字,自己隨便寫,然後next。 進入 ...
  • 一、存儲引擎 1、InnoDB引擎 設計目標是面向線上事務(OLTP)處理的應用。 支持事務、行級鎖、通過多版本併發控制(MVCC)支持高併發、提供一致性非鎖定讀、next-key locking避免幻讀、主鍵聚集索引 2、MyISAM引擎 設計目標是面向OLAP應用。 不支持事務、不支持行鎖、表鎖 ...
  • 在畫類圖的時候,理清類和類之間的關係是重點。類的關係有泛化(Generalization)、實現(Realization)、依賴(Dependency)和關聯(Association)。其中關聯又分為一般關聯關係和聚合關係(Aggregation),合成關係(Composition)。下麵我們結合實 ...
  • 最近做項目,要用到軟體自動化的操作,正好更大家分享一下! 先看看Python操作: 首先是如何打開軟體 第一種利用os模塊,也是最最簡單的一種。假如我想打開記事本,可以: 該函數是非阻塞的。同樣是打開記事本程式,可以這樣寫: 接著是通過句柄操作軟體,給軟體發消息:比如想最大化軟體,將軟體視窗置於最前 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...