視頻線上率統計——基於驅動匯流排設備的領域驅動設計方法落地

来源:https://www.cnblogs.com/JerryMouseLi/archive/2020/02/28/12381098.html
-Advertisement-
Play Games

視頻線上率統計——基於驅動匯流排設備的領域驅動設計方法落地 [toc] 1.應用背景 本司智能信息箱產品是管控攝像頭電源,監控攝像頭視頻線上率的一個有效運維工具。因為統計視頻線上率是業主十分關心的問題,所以如何有效地統計視頻線上率是工程師需要努力解決的問題。 2.各視頻線上率統計方法比較 |方案|是否 ...


目錄

視頻線上率統計——基於驅動匯流排設備的領域驅動設計方法落地

1.應用背景

本司智能信息箱產品是管控攝像頭電源,監控攝像頭視頻線上率的一個有效運維工具。因為統計視頻線上率是業主十分關心的問題,所以如何有效地統計視頻線上率是工程師需要努力解決的問題。

2.各視頻線上率統計方法比較

方案 是否需要攝像頭密碼 是否能與攝像頭交互信息 是否能知道攝像頭的網路狀態
ping
onvif
ffmpeg

ping,onvif,ffmpeg三種協議應用場合不同,各有優劣。onvif會多出用戶名,密碼欄位,方法上會多StartStreaming,StopStreaming,及識別視頻的編碼解析度等信息,從而從媒體信息地址URI獲取視頻流;ffmpeg則更進一步,可以直接調用方法分析視頻質量等等。

3.本文側重點

這裡需要聲明本文側重點有兩方面:

  • 面向領域編程,不面向資料庫,下文會做詳解;
  • 第2點的三種協議都可以借鑒linux的設備,匯流排,驅動與攝像頭之間的交互協議設計思想,只是創建子領域對象時onvif會多用戶名,密碼欄位,方法上會多StartStreaming,StopStreaming。ffmpeg則可以直接將需要的方法包裝到子領域進行調用。因此本文側重講解設備,匯流排,驅動來開發與硬體設備交互的思想。

4.基於領域驅動來設計攝像頭網路狀態這一領域

4.1 值對象driverContext

driverContext是用來配置ping驅動軟體(new System.Net.Ping())介面正常工作的上下文配置。這裡主要是interval,timeout,minSuccess三個欄位,其中timeout是驅動內配置,interval,minSuccess為驅動外配置,下文6.1中會有詳細舉例,按下不表。

3個欄位含義詳見註釋。

    public class PingDriverContext
    {
        ///可以增加name欄位,表示驅動名稱。

        /// <summary>
        /// ping間隔
        /// </summary>
        public int interval { get; set; }
        /// <summary>
        /// ping超時時間
        /// </summary>
        public int timeout { get; set; }

        /// <summary>
        /// ping成功最小次數
        /// </summary>
        public int minSuccess { get; set; }
    }

4.2 子領域CameraPingDM

Camera表示攝像頭,ping表示檢查攝像頭網路狀態的驅動,DM表示Domain Model即領域模型。

4.2.1 枚舉類型攝像頭網路狀態CameraState

    public enum CameraState
    {
        Online = 0,
        Offline = 1,
        Unknown = 2
    }   

4.2.2 屬性

        private PingDriverContext _driverContext;

        private Timer _timer;

        private readonly object _lock = new object();
        private readonly ReadOnlyMemory<byte> _buffer;

        public int CurrentSuccess { get; private set; }
        /// <summary>
        /// IP地址
        /// </summary>
        public string Ip { get; set; }
     
        ///// <summary>
        ///// 狀態
        ///// </summary>
        private CameraState cameraState;

        public CameraState CameraState
        {
            get { return cameraState; }
            set { cameraState = value; }
        }
        /// <summary>
        /// 攝像頭狀態更新時間
        /// </summary>
        public DateTime UpdateTime { get; set; }
  • _driverContext
    值對象,每個攝像頭的timeout,interval,minSuccess都可以配置不同,這裡在匯流排類文件里寫死成
        interval = 3,//每個攝像頭間隔3秒ping一次
        timeout = 200,//單次ping等待返回結果200ms超時
        minSuccess = 0,//一次ping成功即可認為online
  • _lock
    多線程併發互斥鎖
  • _buffer
    ping發送的數據包。
  • CurrentSuccess
    當前ping成功次數,根據minSuccess進行適當的計數清零。
  • cameraState
    網路狀態,詳見4.2.1
  • UpdateTime
    攝像頭狀態更新時間。

4.2.3 子領域的劃分

4.2.3.1析構體

在析構體中,主要傳入創建子領域對象所必須的參數。

        #region Constructors
        /// <summary>
        /// 根據IP,以及driverContext創建攝像頭領域模型
        /// </summary>
        /// <param name="driverContext">聚合CameraPingBus中的驅動上下文driverContext</param>
        /// <param name="iP">資料庫中的攝像頭的IP地址</param>
        public CameraPingDM(PingDriverContext driverContext, string iP)
        {
            string data = "ping-pong";
            _buffer = Encoding.ASCII.GetBytes(data);
            Ip = iP;
            _driverContext = driverContext;

            LoggerHelper.Debug($"Ping camera IPList every {_driverContext.interval}s");
        }
        //Dapper數據模型需要
        public CameraPingDM(){ }
        #endregion

4.2.3.2 創建子領域對象

這裡就是傳入所需要的參數直接new子領域對象。一般是直接調用,所以它是靜態方法。

=>這裡是lambda表達式的語法糖,表示返回一個創建好的CameraPingDM子領域對象。

#region Creations
public static CameraPingDM Create(PingDriverContext driverContext, string iP) => new CameraPingDM(driverContext, iP);
#endregion 

4.2.3.3 子領域對象內的修改屬性行為

主要表現為修改屬性值等。這裡CameraStateUpdate方法更新攝像頭網路狀態,同時保存更新時間。

        #region Behaviors
        /// <summary>
        /// 更新攝像頭網路狀態,同時保存更新時間
        /// </summary>
        /// <param name="_cameraState"></param>
        public void CameraStateUpdate(CameraState _cameraState)
        {
            cameraState = _cameraState;
            UpdateTime  = DateTime.Now;
        }
        #endregion

4.2.3.4 攝像頭的網路驅動——ping驅動相關的行為

        #region Behaviors with Ping
        /// <summary>
        /// 表示為ping單個攝像頭,檢查其網路狀態。
        /// </summary>
        /// <returns></returns>
        public async Task<bool> Start()
        {
            if (_driverContext.interval >= 0)
            {
                var interval = Convert.ToInt32(TimeSpan.FromSeconds(_driverContext.interval).TotalMilliseconds);

                _timer = new Timer(state =>
                {
                    lock (_lock)
                    {
                        DoPing();
                    }
                }, null, interval, interval);
            }

            return true;
        }
        /// <summary>
        /// 根據ping對應IP返回的結果來對當前ping成功次數計數,滿足要求為online,否則為offline。
        /// </summary>
        private void DoPing()
        {
            var pingSender = new Ping();
            var options = new PingOptions
            {
                //不分包
                DontFragment = true
            };

            try
            {
                PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _driverContext.timeout, _buffer.ToArray(), options);

                LoggerHelper.Debug($"Ping reply for {Ip} is {reply.Status}");

                if (reply?.Status == IPStatus.Success)
                {
                    Increment();
                }
                else
                {
                    Decrement();
                }
            }
            catch (Exception)
            {
                LoggerHelper.Debug($"Ping reply for {Ip} failed");
                Decrement();
            }
        }
        /// <summary>
        /// 當前ping成功次數CurrentSuccess減1,CurrentSuccess為非負數
        /// </summary>
        private void Decrement()
        {
            if (CurrentSuccess <= 0)
            {
                CurrentSuccess = 0;
                CameraStateUpdate(CameraState.Offline);
            }
            else
            {
                CurrentSuccess--;
            }
        }
        /// <summary>
        /// 當前ping成功次數CurrentSuccess+1,如果大於等於設置的最小ping成功次數,則更新攝像頭的網路狀態
        /// </summary>
        private void Increment()
        {
            if (CurrentSuccess >= _driverContext.minSuccess)
            {
                CameraStateUpdate(CameraState.Online);
            }
            else
            {
                CurrentSuccess++;
            }
        }
        /// <summary>
        /// 定時ping定時器關閉
        /// </summary>
        /// <returns></returns>
        public async Task<bool> Stop()
        {
            _timer?.Dispose();
            return true;
        }
        #endregion

所有方法的作用詳見註釋,不明白的可以在評論區評論,我會耐心解答,有更好建議的懇請提出。
這裡上層聚合CameraPingBus主要調用的就是Start()表示開始ping對應ip的攝像頭,根據ping結果刷新攝像頭網路狀態更新時間;Stop()方法停止ping。這裡Timer定時器會在4.3.5中詳細介紹,按下不表。

4.3 聚合CameraPingBus

也可稱之為CameraPingBus領域,也就是需要我們去解決與攝像頭協議交互查看攝像頭是否線上的問題域。領域是從需要解決的問題域命名,聚合是從功能角度命名,該類是聚合了許多子領域CameraPingDM,它是去ping 攝像頭Camera的行為,返回的是online/offline網路狀態值,通過子領域聚合而解決了一整個問題域。

4.3.1 屬性

        private Timer _dbTimer;
        ICamera_Services _camera_Services;
        public IList<CameraPingDM> CameraPingDMList = new List<CameraPingDM>();

        ///可以增加name欄位,表示驅動名稱。
        
        ///寫一個IP地址 對應狀態變化的方法,將有變化的ADD進差異集合。 如果差異集合不為空,再保存進資料庫。

        ///通過winform修改pingDriverContext  3個參數

        //預設參數5/100/0
        static PingDriverContext pingDriverContext = new PingDriverContext()
        {
            interval = 3,
            timeout = 200,
            minSuccess = 0,
        };
  • _dbTimer
    定時器,資料庫定時4秒保存一下攝像頭的網路狀態。
  • _camera_Services
    攝像頭的資料庫數據模型讀寫服務,依賴註入,析構體調用,簡單,如果對依賴註入有疑問可以參照筆者的在net Core3.1上基於winform實現依賴註入實例,這裡不做贅述。
  • CameraPingDMList
    ping攝像頭子領域的集合,也就是將所有的CameraPingDM子領域掛載到了Bus匯流排上。可通過該集合調用CameraPingDM子領域的ping Start, Stop方法。
  • pingDriverContext
    值對象,這裡將所有攝像頭的ping驅動配置為同樣參數去創建CameraPingDM子領域對象。

    4.3.2 析構函數

    析構函數調用_camera_Services
        public CameraPingBus(ICamera_Services camera_Services)
        {
            _camera_Services = camera_Services;
        }

4.3.3 與CameraPingDM所有子領域相關的行為

        #region Behaviors with all the CameraPingDMList
        /// <summary>
        /// 從資料庫的數據模型獲取所有攝像頭的IP地址載入到CameraPingDM對象集合,由Dapper完成數據模
        /// 型的IP到CameraPingDM領域模型IP賦值的轉換工作,啟動所有CameraPingDM對象集合的ping方法
        /// </summary>
        /// <returns></returns>
        public async Task<bool> CreateAndStartAllCameraPing()
        {
            var CameraIpList = await GetCameraIpList();
             try
             {
                foreach (var item in CameraIpList)
                {
                    var cameraPingDM = CameraPingDM.Create(pingDriverContext, item.Ip);
                    await cameraPingDM.Start();
                    CameraPingDMList.Add(cameraPingDM);
                }
           }
           catch  { return false; }
            return true;
        }
        /// <summary>
        /// 停止所有CameraPingDM對象集合的ping方法
        /// </summary>
        /// <returns></returns>
        public async Task<bool> StopPing()
        {
            try
            {
                foreach (var item in CameraPingDMList)
                {
                    await item.Stop();
                }
            }
            catch { return false; }
            return true;
        }
        /// <summary>
        /// 非同步獲取CameraPingDM對象集合元素數量
        /// </summary>
        /// <returns></returns>
        public async Task<int> CameraIpCount()
        {
            var CameraIpList =  await _camera_Services.GetAllCameraIPAsync();
            return CameraIpList.Count(); 
        }
        #endregion

所用方法的作用我都做了詳細的註釋,詳見註釋。有問題可在評論區提出,我會耐心解答。

4.3.4 領域模型欄位在資料庫中的讀寫行為

        #region Behaviors with DataBase
        /// <summary>
        /// 從資料庫中載入所有攝像頭IP地址到CameraPingDM的IP欄位
        /// </summary>
        /// <returns></returns>
        public async Task<IEnumerable<CameraPingDM>> GetCameraIpList()
        {
            return await _camera_Services.GetAllCameraIPAsync();
        }

        /// <summary>
        /// 將所有Cameara的線上狀態根據IP地址匹配定時5秒更新到資料庫
        /// </summary>
        /// <returns></returns>
        public async Task<bool> Save2DbTimerStart()
        {
            _dbTimer = new Timer(state =>
            {
                   _camera_Services.UpdateList(CameraPingDMList);

            }, null, 4000, 4000);
           
            return true;
        }
        /// <summary>
        /// 關閉資料庫定時保存定時器
        /// </summary>
        /// <returns></returns>
        public async Task<bool> Save2DbTimerStop()
        {
            _dbTimer?.Dispose();
            return true;
        }

        #endregion

所用方法的作用我都做了詳細的註釋,詳見註釋。有問題可在評論區提出,我會耐心解答。

這裡需要註意的是由Dapper完成數據模型的IP到CameraPingDM領域模型IP賦值的轉換工作,保存也是由Dapper進行了從領域模型的IP,CameraState到數據模型的無縫對接,礙於篇幅過長,時間也很晚了,感興趣的請在評論區留言。筆者將根據讀者反饋情況看是否有必要另起一篇,寫一下基於Dapper進行數據模型與領域模型之間的互相轉換。

4.3.5 定時器timer介紹

4.3.5.1 定義一個定時器的引用類

用來指向下麵的定時器實例。

using System.Threading;
private Timer _dbTimer;

4.3.5.2 定時器使用

定時器的引用類型指向new Timer()實例,目的是為了去寫定時器的關閉方法。

            _dbTimer = new Timer(state =>
            {
                   _camera_Services.UpdateList(CameraPingDMList);

            }, null, 4000, 4000);

這裡定時器有4個參數,F12可得如下

        //   callback:
        //     A System.Threading.TimerCallback delegate representing a method to be executed.
        //
        //   state:
        //     An object containing information to be used by the callback method, or null.
        //
        //   dueTime:
        //     The amount of time to delay before callback is invoked, in milliseconds. Specify
        //     System.Threading.Timeout.Infinite to prevent the timer from starting. Specify
        //     zero (0) to start the timer immediately.
        //
        //   period:
        //     The time interval between invocations of callback, in milliseconds. Specify System.Threading.Timeout.Infinite
        //     to disable periodic signaling.
  • 第一個參數callback即回調函數,也就是定時執行的方法;
  • 第二個參數state回調函數的包含信息,這裡為null即可;
  • 第三個參數dueTime,定時器啟動之後延遲調用回調函數的毫秒數;
  • 第四個參數period定時周期。

    4.3.5.3 定時器的關閉

_dbTimer?.Dispose();

4.3.5.4 與Java的調度器ScheduledExecutorService相比

熟悉Java的道友有沒有發現,C#里的Timer與Java的ScheduledExecutorService很相似,也不知道是誰抄誰,或者是異曲同工之妙吧。

import java.util.concurrent.ScheduledExecutorService;

private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(DeviceSetting.MAX_GROUP_ID);

executorService.scheduleWithFixedDelay(() -> {
            try {
                BaseMsg baseMsg = deque.take();
                Thread.sleep(AWAKE_TO_PROCESS_INTERVAL);
                Channel channel = touchChannel(channelId);
                if (channel == null || !channel.isActive()) {
                    logger.warn("「Channel」" + " Channel「" + channelId + "」無效,無法下發該幀");
                    removeChannelCompleted(channel);
                    deque.clear();
                    return;
                }

        }, channelId, CommSetting.FRAME_SEND_INTERVAL, TimeUnit.MILLISECONDS);

4.3.5.5 Timers調用庫的問題

註意:這裡必須要調用System.Threading庫里的定時器可以多線程併發執行回調方法,否則的話,將沒有此功能,System.Timers定時器使用較為複雜,且無法多線程併發,需要自己寫多線程併發的方法,System.Timers定時器只能提供定時功能。

5.依賴註入CameraPingBus,窗體程式析構法調用

5.1 CameraPingBus匯流排依賴註入

            //Domain 
            services.AddScoped(typeof(CameraPingBus));

5.2 窗體程式析構法調用

        public PingSetting(CameraPingBus cameraPingBus)
        {
            _cameraPingBus = cameraPingBus;
            InitializeComponent();
            LoggerHelper.Debug("視頻線上率配置工具啟動");
        }

5.3 CameraPingBus使用實例

        /// <summary>
        /// 按下啟動按鈕執行操作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void button1_Click(object sender, EventArgs e)
        {
            //IP地址從資料庫數據模型賦值到領域模型的IP欄位,並且每隔3秒開始ping攝像頭,保存其網路狀態
            await _cameraPingBus.CreateAndStartAllCameraPing();
            //每隔4s將攝像頭網路狀態更新到IP地址相等的資料庫數據模型中去
            await _cameraPingBus.Save2DbTimerStart();
        }
        /// <summary>
        /// 按下停止按鈕執行操作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void button2_Click(object sender, EventArgs e)
        {   
            //停止攝像頭定時ping行為
            await _cameraPingBus.StopPing();
            //停止保存攝像頭網路狀態到資料庫
            await _cameraPingBus.Save2DbTimerStop();
        }

各調用方法含義詳見註釋。

5.4 基於net Core3.1的winform工具效果圖

工具圖示如下:

6.匯流排,驅動,設備

6.1.驅動

驅動即軟體介面,ping是驅動,modbus協議庫也是驅動,驅動配置(driverContext)分為驅動內配置與驅動外配置。值對象驅動上下文driverContext就是包含了驅動內配置與驅動外配置。

6.1.1驅動內配置舉例

var pingSender = new Ping();
 PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _driverContext.timeout, _buffer.ToArray(), options);

_driverContext.timeout即為ping驅動內配置。

6.1.2驅動外配置舉例

            if (CurrentSuccess >= _driverContext.minSuccess)
            {
                CameraStateUpdate(CameraState.Online);
            }

_driverContext.minSuccess即為驅動外配置。

            if (_driverContext.interval >= 0)
            {
                var interval = Convert.ToInt32(TimeSpan.FromSeconds(_driverContext.interval).TotalMilliseconds);

                _timer = new Timer(state =>
                {
                    lock (_lock)
                    {
                        DoPing();
                    }
                }, null, interval, interval);
            }

_driverContext.interval也為驅動外配置。

6.2 設備

攝像頭即設備,這裡驅動跟設備是1對1的關係,驅動是設備的一個被動行為,SC平臺通過載入驅動的所需配置(driverContext)來獲取對應設備的數據(信號或者說狀態)。

6.3 匯流排

匯流排就像高速公路,他需要有名稱,是否關閉,起點,終點,限速(介面參數)。所以這裡的IP地址就好比是終點地址,故這裡的攝像頭IP是屬於匯流排的概念範疇。
具體驅動協議的上一層,一根匯流排可以對應多個驅動,也可以對應多個設備。

6.4 類比

設備上的信號值(網路狀態值)相當於是要寄的快遞,驅動相當於是運快遞的車,保持車間距,按時到達終點,而匯流排相當於是車開著的高速路。

7.多線程併發ping攝像頭效果圖

7.1分析日誌記錄內容1時刻值

得到結果ping行為為併發

7.2分析日誌記錄內容2線程號T

得到結果為多線程ping

7.3分析日誌記錄內容3消息

ping成功與超時與實際線上離線IP地址結果相符。

8. 小結

  • 如果設備端是Modbus協議類比可得:Modbus驅動需要載入它的配置信息(所屬匯流排ID,使能,驅動名稱,協議,設備地址,發送間隔,接收超時時間,接收超時告警次數,對應設備的寄存器地址等),也即driverContext,載入到Modbus的驅動,驅動配置會包含設備地址,每台設備都會有他自己的驅動配置,將Modbus驅動(也就是new ping())封裝到設備的方法裡面去(可以封裝成AI,DI,AO,DO),將這些配置信息裝載到設備的驅動方法里,即可從設備返回值,而新建信號時,就會對該值定義(也或者可以模板形式解析值的顯示含義),而這就是最頂層的用戶工作組,也就是最大的聚合,也可建立匯流排,將各類驅動進行分類。以後有時間也會分享相關的信息,不過會稍微複雜一些,但是道理思想類似。
  • 一個項目中不一定只有一個聚合像我現在做的智能箱它就需要兩個聚合,就有兩個問題域

    一個是智能設備箱(也就是點位),所有的AI,DI,DO,AO(包含歷史數據),攝像頭,A介面配置數據,用戶,角色,升級,運維公司,運維人員,區域,設備箱告警,協議模板,歷史告警都是它的子領域。

攝像頭也是一個聚合,攝像頭的告警(離線,停電告警),歷史告警,攝像頭的型號,攝像頭的廠商,區域,設備箱,運維公司,運維人員,攝像頭驅動,攝像頭匯流排都是攝像頭的子領域。

  • driver與driverContext
    driver就是Ping驅動。
var pingSender = new Ping();

驅動上下文driverContext的欄位(配置信息)會載入到驅動pingSender上去,去獲取所需要的值,即為軟體介面

PingReply reply = pingSender.Send(IPAddress.Parse(Ip), _timeout, _buffer.ToArray(), options);

9.最終

自己對於領域驅動設計的理解並不深刻,但是憑著對設備域,以及協議,匯流排,驅動的甚微瞭解,以及看了不少開源項目,不斷地學習同行的資料庫,硬生生地拼湊成了此文,可能有些概念上或者實現上會有不合適的地方,請路過的高手們不吝賜教。當然如果你有不明白的地方也請提出,我也會耐心解答。



本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名JerryMouseLi(包含鏈接:https://www.cnblogs.com/JerryMouseLi/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。
————————————————
版權聲明:本文為博客園博主「JerryMouseLi」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://www.cnblogs.com/JerryMouseLi/p/12381098.html


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

-Advertisement-
Play Games
更多相關文章
  • 可以通過 watch 來響應數據的變化 以下實例通過使用 watch 實現計數器: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <script src="https:/ ...
  • 計算屬性關鍵詞: computed。 計算屬性在處理一些複雜邏輯時是很有用的。 下麵是字元串反轉的一些寫法,看起來有點複雜 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> ...
  • 迴圈使用 v-for 指令。 v-for 指令需要以 site in sites 形式的特殊語法, sites 是源數據數組並且 site 是數組元素迭代的別名。 v-for 可以綁定數據到數組來渲染一個列表: <!DOCTYPE html> <html> <head> <meta charset= ...
  • 條件判斷使用 v-if 指令: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <script src="https://cdn.staticfile.org/vue/2. ...
  • 通過實例來看下 Vue 構造器中需要哪些內容 測試時這段代碼我直接寫在index.html中 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue 測試實例 - 菜鳥教程(runoob.com)</title> <script ...
  • 給項目的入口文件做點小改變: 【補充:編輯器建議使用vscode,我還沒裝,暫時先用phpstorm】 打開 APP.vue 文件,代碼如下(解釋在註釋中) <!-- 展示模板 --> <template> <div id="app"> <img src="./assets/logo.png"> < ...
  • 我之前是有安裝過npm的 使用淘寶 NPM 鏡像 $ npm install -g cnpm --registry=https://registry.npm.taobao.org 查看nmp版本 $ npm -v 使用 NPM 安裝vue $ cnpm install vue 在命令行中快速搭建大型 ...
  • 為什麼要用flex 基於css3簡單方便,更優雅的實現,瀏覽器相容性好,傳統的css實現一個div居中佈局要寫一堆代碼,而現在幾行代碼就搞定了,沒有理由不用flex。 相容性: Base Browsers: IE8.0+, Firefox40.0+, Chrome40.0+, iOS8.0+, An ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...