LiteHub中間件之gzip算法

gzip算法

    • 理論部分
      • LZ777算法
      • 霍夫曼編碼算法
      • 改進型的LZ777算法
    • 代碼實現
      • 壓縮對象
      • gzip實現
    • 運行分析
      • 日志查看
      • wireshark抓包查看
      • 后臺管理界面查看

理論部分

gzip是一種無損壓縮算法,其基礎為Deflate,Deflate是LZ77與哈弗曼編碼的一個組合體。它的基本原理是:對于要壓縮的文件,首先使用LZ77算法的一個變種進行壓縮,對得到的結果再使用哈夫曼編碼(根據情況,使用靜態哈弗曼編碼或動態哈夫曼編碼)的方法進行壓縮。

LZ777算法

幾個術語

  • 等待編碼區
  • 搜索緩沖區(已經編碼的區域)
  • 滑動窗口(指定大小,包括“搜索緩沖區”和“待編碼區”)

具體的編碼過程:
接下來,介紹具體的編碼過程:
  為了編碼待編碼區, 編碼器在滑動窗口的搜索緩沖區查找直到找到匹配的字符串。匹配字符串的開始字符串與待編碼緩沖區的距離稱為“偏移值”,匹配字符串的長度稱為“匹配長度”。
  編碼器在編碼時,會一直在搜索區中搜索,直到找到最大匹配字符串,并輸出(o, l ),其中o是偏移值, l是匹配長度。然后窗口滑動l,繼續開始編碼。
  如果沒有找到匹配字符串,則輸出(0, 0, c),c為待編碼區下一個等待編碼的字符,窗口滑動“1”。

參考文章:LZ77壓縮算法編碼原理詳解(結合圖片和簡單代碼)

下面我們以字符串“abababc”為例,來了解其編碼過程:

假設滑動窗口的大小足夠大(LZ777設置的滑動窗口是32k),可以覆蓋整個字符串。

第一步
?待編碼區:abababc
?搜索緩沖區:(初始為空)
?操作:搜索緩沖區為空,無法找到匹配字符串。
?輸出:(0, 0, a)(表示沒有找到匹配,輸出字符a)
?窗口滑動:窗口向右滑動1個字符。
?結果:a已經被編碼,剩下bababc。
第二步
?待編碼區:bababc
?搜索緩沖區:a(已經編碼的部分)
?操作:在搜索緩沖區中查找b的匹配。搜索緩沖區中沒有b。
?輸出:(0, 0, b)(表示沒有找到匹配,輸出字符b)
?窗口滑動:窗口向右滑動1個字符。
?結果:ab已經被編碼,剩下ababc。
第三步
?待編碼區:ababc
?搜索緩沖區:ab(已經編碼的部分)
?操作:在搜索緩沖區中查找ab的匹配。搜索緩沖區中存在ab,匹配長度為2。
?輸出:(2, 2)(表示匹配長度為2,偏移值為2)
?窗口滑動:窗口向右滑動2個字符。
?結果:abab已經被編碼,剩下abc。
第四步
?待編碼區:abc
?搜索緩沖區:abab(已經編碼的部分)
?操作:在搜索緩沖區中查找ab的匹配。搜索緩沖區中存在ab,匹配長度為2。
?輸出:(4, 2)(表示匹配長度為2,偏移值為4)
?窗口滑動:窗口向右滑動2個字符。
?結果:ababab已經被編碼,剩下c。
第五步
?待編碼區:c
?搜索緩沖區:ababab(已經編碼的部分)
?操作:在搜索緩沖區中查找c的匹配。搜索緩沖區中沒有c。
?輸出:(0, 0, c)(表示沒有找到匹配,輸出字符c)
?窗口滑動:窗口向右滑動1個字符。
?結果:整個字符串已經編碼完成。

最終編碼結果
?經過上述步驟,字符串abababc的LZ77編碼結果為:

(0, 0, a) (0, 0, b) (2, 2) (4,2) (0, 0, c)

使用LZ77壓縮算法編碼原理詳解(結合圖片和簡單代碼)給定的代碼的編碼結果是
在這里插入圖片描述
說明上述編碼過程分析正確!!!

上面的如(0, 0, a)這個其實根本不用寫偏移和匹配長度,保留為原字符‘a’,占的編碼長度還更短一些。

(0, 0, a) (0, 0, b) (2, 2) (4,2) (0, 0, c)

變為

ab(2, 2) (4,2)c

霍夫曼編碼算法

霍夫曼編碼是一種基于字符頻率的變長編碼方法,通過構建霍夫曼樹來為每個字符分配一個唯一的二進制編碼。霍夫曼樹的構建過程依賴于字符的頻率,頻率越高的字符通常會被分配較短的編碼。
首先我們統計"abababc"中每一個字符出現的頻率,如下所示

abc
331

根據霍夫曼編碼的規則,我們需要按照字符頻率從低到高構建霍夫曼樹。以下是構建過程:

  1. 將字符串出現的頻率視為優先級,放入一個最小優先隊列中:

在這里插入圖片描述

  1. 然后彈出兩個優先級最低的字符作為子節點, 構造出第一個二叉樹; 父節點的優先級視為兩個字節優先級之和, 然后把父節點插入隊列中:
    在這里插入圖片描述
  2. 重復這個操作, 最后我們會得到一顆二叉樹. 這便是 Huffman編碼 樹.

在這里插入圖片描述
4. 我們把這棵樹的左支編碼為 0, 右支編碼為 1, 那么從根節點向下遍歷到葉子節點, 即可得出相應字符的 Huffman 編碼. 因此我們得到上面例子的 Huffman 編碼表為:
在這里插入圖片描述

a:1
b:01
c:00

現在對字符串中出現的頻率都做了一個統計,只需要解決偏移量和匹配長度的編碼就可以了。

DEFLATE 算法對偏移距離和匹配長度已經做了一個統計,見下表:
偏移距離
它有 0 至 29 一共 30 個編碼. 距離編碼的含義如下表所示:
在這里插入圖片描述

  • code表示基本的編碼表示,比如編碼9對應的基準距離是25
  • extra表示距離基準距離偏移了多少,編碼9對應的extra為3位,最大為111(7),即25+7,最大可以表示32。
  • distance,可以表示的距離范圍
    總結:code+extra可以靈活表示distance的任何數字

匹配長度
對于長度, 它與普通字符共用同一編碼空間. 這個編碼空間一共有 286 個編碼, 范圍是從 0 至 285. 其中 0 至 255 為普通字符編碼, 256 表示壓縮塊的結束; 而 257 至 285 則表示長度. 長度編碼的含義如下表所示:
在這里插入圖片描述

與距離編碼類似, 每個編碼都表示一個或多個長度, 表示多個長度時后面會有 extra 位指示具體的長度. 長度編碼能表示的長度范圍為 3 至 258.
注意:所以在 DEFLATE 中,長度 1~2 的重復不會用匹配項表示(直接把這 1~2 個字節原樣輸出(即用字面值編碼)通常比引用匹配(還要額外編碼長度和距離)更短!);只有長度 ≥ 3 時才會用匹配項 (length, distance) 來引用重復塊。
解壓時, 當讀到編碼小于等于 255, 則認為這是個普通字符, 原樣輸出即可; 若在 257 至 285 之間, 則說明遇到了一個重復標記, 這意味著當前編碼表示的是長度, 且下一個編碼表示的是距離. 解碼得到長度和距離, 再在解壓緩沖區中找到相應的部分輸出即可; 若為 256, 則說明壓縮塊結束了.

從LZ777編碼到霍夫曼編碼
上一步的LZ777編碼結果為:

ab(2, 2) (4,2)c

字符串統計頻率為:

a:1
b:01
c:00

字符編碼為:

101(2, 2) (4,2)00

然后對偏移距離和匹配長度進行編碼
但是這里發現匹配字符長度是從3開始的,那么這個2怎么編碼呢?原來這里的deflate算法是使用了改進型的LZ777算法
參考文章:Gzip 格式和 DEFLATE 壓縮算法

改進型的LZ777算法

LZ77壓縮算法將所有數據都處理成為一個三元組串,每個三元組至少需要3個字節表示,如果在動態字典中未找到匹配字符串,會將1個字節輸出為3個字節,這就導致了字節浪費。因此在DEFLATE算法中對其使用的LZ77算法進行了以下改進:

對匹配字符串長度小于3的字符串不壓縮。因為長度小于3的字符串,使用三元組表示,對原始數據不能起到壓縮的作用。
對字符串的匹配長度進行了限制,范圍為(-128~127),用8bit表示,滑動窗口大小為32KB,用15bit表示,將壓縮數據構造為標識+數據的形式,具體如表3所示。該存儲方式,降低了壓縮數據的存儲空間。
由于只查找匹配長度大于3的字符串,為提高算法速度,在查找匹配字符串時,使用了哈希鏈結構搜索算法,其中哈希算法將3字節壓縮到2字節,雖然哈希鏈結構存在搜索到錯誤結果的可能,但還是大幅提高了搜索速度。

所以最終的編碼結果為:

a:1
b:01
c:00

這里的字符串“abababc”,連續匹配都沒有超過三個字符,直接按照這個字符串常量進行編碼即可

10110110100

代碼實現

壓縮對象

在開始編寫代碼前,我們需要弄清楚,需要壓縮的對象是什么?

  1. 視頻、音頻、圖片等文件本身就是壓縮格式
    mp4、jpg、png、avi、mp3 這些格 已經過復雜壓縮算法處理(如 H.264、H.265、JPEG、LZ77 等)。
    👉 所以再次使用 GZIP 壓縮不會有太大效果,反而可能略微增加體積。
  2. GZIP 對二進制內容的壓縮效率很低
    GZIP 是為文本內容設計的壓縮算法(如 HTML、JSON、JavaScript 等)。
    它依賴數據的可預測性和重復性(如文本中的重復詞、空格等)來壓縮。
    視頻文件的數據模式看起來是“隨機的”,壓縮器無法從中找到有效的模式。

gzip實現

GzipMiddleware.h代碼:

    GzipMiddleware():clientSupportGzip_(true){};void before(HttpRequest& request) override;void after(HttpResponse& response) override;void setClientSupportGzip(bool flag){clientSupportGzip_=flag;}bool isClinetSupportGzip() const {return clientSupportGzip_;}double getGzipEnableRate() const {uint64_t total = totalRequests_.load();return total == 0 ? 0.0 : (double)gzipAppliedCount_.load() / total;}double getAverageCompressionRatio() const {uint64_t original = originalSizeSum_.load();return original == 0 ? 0.0 : (double)compressedSizeSum_.load() / original;}private:bool compressGzip(const std::string& input, std::string& output);bool clientSupportGzip_;// gzip統計信息void checkArchive();  // 檢查是否需要歸檔void resetStats();    // 重置統計信息std::atomic<uint64_t> totalRequests_{0};std::atomic<uint64_t> gzipAppliedCount_{0};std::atomic<uint64_t> originalSizeSum_{0};std::atomic<uint64_t> compressedSizeSum_{0};// std::function<void(uint64_t, uint64_t, uint64_t, uint64_t)> archiveCallback_;static constexpr uint64_t MAX_REQUESTS_BEFORE_ARCHIVE = 1e9;static constexpr uint64_t MAX_ORIGINAL_SIZE_BEFORE_ARCHIVE = 1ULL << 40; // 1 TB

GzipMiddleware.cpp代碼:

void GzipMiddleware::before(HttpRequest& request)
{   // 從客戶端請求頭中獲取 Accept-Encoding 字段std::string acceptEncoding=request.getHeader("Accept-Encoding");// 判斷是否包含 "gzip" 關鍵字// 如果包含,說明客戶端支持 gzip 壓縮// 否則,不支持 gzip 壓縮,如果不支持,就不進行gzip壓縮acceptEncoding.find("gzip") != std::string::npos?setClientSupportGzip(true):setClientSupportGzip(false);}//--------------------------------------
// GzipMiddleware::after
//--------------------------------------
void GzipMiddleware::after(HttpResponse& response) 
{   // 統計總請求數(用于后續壓縮統計歸檔)totalRequests_++;if (!isClinetSupportGzip())     // 如果客戶端不支持 gzip,則直接跳過壓縮return;//判斷是否應該壓縮,消息體大于256字節并且消息類型是文本、html等類型才可以//對于視頻、圖片等已經使用了其他壓縮算法進行壓縮了的,就不再使用gzip進行壓縮了if (!response.isShouldGzipCompress()) return;// 獲取原始響應體內容const std::string& rawBody = response.getBody();std::string compressed;// 調用實際壓縮方法if (compressGzip(rawBody, compressed)) {// LOG_INFO<<"gzipAppliedCount_:"<<gzipAppliedCount_.load()<<"originalSizeSum_"<<originalSizeSum_.load()<<"compressedSizeSum_"<<compressedSizeSum_.load();gzipAppliedCount_++;originalSizeSum_ += rawBody.size();compressedSizeSum_ +=compressed.size();response.addHeader("Content-Encoding", "gzip");  // 添加響應頭標識壓縮格式為 gzipresponse.setContentLength(compressed.size());    // 更新 Content-Length 為壓縮后大小response.setBody(compressed);                    // 設置壓縮后的響應體}// 檢查是否需要歸檔統計信息checkArchive();
}//--------------------------------------
// GzipMiddleware::compressGzip實際的壓縮處理算法
//--------------------------------------
bool GzipMiddleware::compressGzip(const std::string& input, std::string& output)
{constexpr int CHUNK = 16384; z_stream strm{};    //zlib 用于壓縮的狀態結構體,記錄輸入、輸出緩沖區狀態等char out[CHUNK];    //輸出緩沖區,用來暫存壓縮后的數據塊strm.zalloc = Z_NULL;strm.zfree = Z_NULL;strm.opaque = Z_NULL;if (deflateInit2(&strm,             //壓縮狀態Z_BEST_COMPRESSION, //壓縮等級(0~9),9 表示最高壓縮比,犧牲性能Z_DEFLATED,         //使用 DEFLATE 算法15 + 16,           //15位窗口大小(32KB), +16啟用 GZIP 格式輸出(否則是 zlib)8,                 //內部壓縮緩沖區大小參數,一般為 8Z_DEFAULT_STRATEGY) != Z_OK) //默認壓縮策略{return false;}strm.avail_in = input.size();          // 待壓縮數據長度strm.next_in = (Bytef*)input.data();   // 待壓縮數據do {strm.avail_out = CHUNK;            //待壓縮數據存儲buffer 的長度,如果多次寫,會覆蓋之前的寫的數據//當然,之前的數據已經被讀走了strm.next_out = reinterpret_cast<Bytef*>(out); //待壓縮數據存儲的bufferdeflate(&strm, Z_FINISH);            //如果輸入和待輸出的數據都被處理完,則返回 Z_STREAM_ENDsize_t have = CHUNK - strm.avail_out;//總長度-當前可寫=已經寫的數據長度output.append(out, have);} while (strm.avail_out == 0);deflateEnd(&strm);                       //釋放deflateInit2申請的空間LOG_INFO<<"原始的數據大小為:"<< input.size();LOG_INFO<<"GZIP壓縮完成,壓縮比例為:"<<(static_cast<double>(output.size()) / input.size());;return true;
}void GzipMiddleware::checkArchive() {// 如果壓縮的總請求數或原始數據累計大小超過閾值// 就清理統計數據(可用于后續監控、日志)if (totalRequests_ >= MAX_REQUESTS_BEFORE_ARCHIVE ||originalSizeSum_ >= MAX_ORIGINAL_SIZE_BEFORE_ARCHIVE) {totalRequests_ = 0;gzipAppliedCount_ = 0;originalSizeSum_ = 0;compressedSizeSum_ = 0;}
}

實現的函數有:

  • before()函數,判斷請求是否支持gzip壓縮
  • after()函數,統計請求,如果客戶端不支持gzip壓縮就返回;否則對其進行gzip壓縮并填充響應體部分
  • compressGzip()函數,實際的壓縮處理核心部分
  • checkArchive()函數,用于統計壓縮的情況,如平均壓縮率等

本代碼實現gzip的核心部分就是在compressGzip函數中進行了實際的壓縮,compressGzip調用了deflate函數進行實際的壓縮。關于deflate函數的介紹,可以參考
深入理解數據壓縮流程及 zlib 庫中相關函數

運行分析

運行服務器,查看gzip壓縮是否啟用成功,有三個地方可以查看gzip的壓縮啟用是否成功。分別是日志系統、wireshark抓包分析、LiteHub前端展示。

日志查看

在這里插入圖片描述
這個壓縮比例計算方式是:壓縮后的數據大小除以壓縮前的數據大小。可以看到gzip是有效壓縮成功了的。

wireshark抓包查看

在這里插入圖片描述
首先看客戶端發起的每一次請求都會攜帶Accept-Encoding:字段,該字段中攜帶了 gzip, deflate表示支持gzip壓縮方式。

在這里插入圖片描述
這個報文是從服務器發回的響應報文,客戶端收到壓縮后的信息包后自動解壓,從2380字節解壓到原來的9182字節,這也進一步說明了設計的gzip壓縮算法是有效的。

后臺管理界面查看

此外,在LiteHub前端界面,也是可以通過管理員賬戶查看具體的gzip的一個壓縮信息的。
在這里插入圖片描述
關于gzip的理解就分析到這了,如果有不恰當之前,請您指出。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913158.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913158.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913158.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java+vue+SpringBoo校園失物招領網站(程序+數據庫+報告+部署教程+答辯指導)

源代碼數據庫LW文檔&#xff08;1萬字以上&#xff09;開題報告答辯稿ppt部署教程代碼講解代碼時間修改工具 技術實現 開發語言&#xff1a;后端&#xff1a;Java 前端&#xff1a;vue框架&#xff1a;springboot數據庫&#xff1a;mysql 開發工具 JDK版本&#xff1a;JDK1.…

Qt Quick 與 QML(五)qml中的布局

QML布局系統主要分為三大類&#xff1a;錨布局、定位器布局、布局管理器。一、錨布局&#xff08;Anchors&#xff09;通過定義元素與其他元素或父容器的錨點關系實現精確定位&#xff0c;支持動態調整。核心特性屬性??作用??示例?anchors.left左邊緣對齊目標元素anchors.…

【Java|集合類】list遍歷的6種方式

本文主要是總結一下Java集合類中List接口的遍歷方式&#xff0c;以下面的list為例&#xff0c;為大家講解遍歷list的6種方式。 List<Integer> list new ArrayList<>();list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);文章目錄1.直接輸出2.for循環遍…

博弈論基礎-筆記

取石子1 性質一&#xff1a;12345可以確定先手贏&#xff0c;6不論取那個質數都輸&#xff0c;789 10 11可以分別取12345變成6 性質二&#xff1a;6的倍數一定不能取出之后還是6的倍數&#xff08;不能轉換輸態&#xff09; #include <bits/stdc.h> using namespace st…

多任務學習-ESMM

簡介 ESMM&#xff08;Entire Space Multi-task Model&#xff09;是2018年阿里巴巴提出的多任務學習模型。基于共享的特征表達和在用戶整個行為序列空間上的特征提取實現對CTR、CVR的聯合訓練 解決的問題 SSB&#xff08;sample selection bias&#xff09; 如下圖1所示&am…

K8S 集群配置踩坑記錄

系統版本&#xff1a;Ubuntu 22.04.5-live-server-amd64 K8S 版本&#xff1a;v1.28.2 Containerd 版本&#xff1a; 1.7.27 kubelet logs kuberuntime_sandbox.go:72] "Failed to create sandbox for pod" err"rpc error: code Unknown desc failed to cre…

超濾管使用與操作流程-實驗操作013

超濾管使用與操作流程 超濾管&#xff08;或蛋白濃縮管&#xff09;是一種重要的實驗設備&#xff0c;廣泛應用于分離與純化大分子物質&#xff0c;尤其是蛋白質、多糖和核酸等。其工作原理依賴于超濾技術&#xff0c;通過半透膜對分子進行篩分&#xff0c;精準地將大分子物質…

GitHub已破4.5w star,從“零樣本”到“少樣本”TTS,5秒克隆聲音,沖擊傳統錄音棚!

嗨&#xff0c;我是小華同學&#xff0c;專注解鎖高效工作與前沿AI工具&#xff01;每日精選開源技術、實戰技巧&#xff0c;助你省時50%、領先他人一步。&#x1f449;免費訂閱&#xff0c;與10萬技術人共享升級秘籍&#xff01;你是否為錄音成本高、聲音不靈活、又想為多語言…

【中文核心期刊推薦】《遙感信息》

《遙感信息》&#xff08;CN&#xff1a;11-5443/P&#xff09;是一份具有較高學術價值的雙月刊期刊&#xff0c;自創刊以來&#xff0c;憑借新穎的選題和廣泛的報道范圍&#xff0c;兼顧了大眾服務和理論深度&#xff0c;深受學術界和廣大讀者的關注與好評。 該期刊創辦于1986…

uniapp微信小程序css中background-image失效問題

項目場景&#xff1a;提示&#xff1a;這里簡述項目相關背景&#xff1a;在用uniapp做微信小程序的時候&#xff0c;需要一張背景圖&#xff0c;用的是當時做app的時候的框架&#xff0c;但是&#xff0c;在class的樣式中background-image失效了&#xff0c;查了后才知道&#…

iOS App無源碼安全加固實戰:如何對成品IPA實現結構混淆與資源保護

在很多iOS項目交付中&#xff0c;開發者或甲方并不總能拿到應用源碼。例如外包項目交付成品包、歷史項目維護、或者僅負責分發渠道的中間商&#xff0c;都需要在拿到成品ipa文件后對其進行安全加固。然而傳統的源碼級混淆方法&#xff08;如LLVM Obfuscator、Swift Obfuscator&…

Java 中的 ArrayList 和 LinkedList 區別詳解(源碼級理解)

&#x1f680; Java 中的 ArrayList 和 LinkedList 區別詳解&#xff08;源碼級理解&#xff09; 在日常 Java 開發中&#xff0c;ArrayList 和 LinkedList 是我們經常用到的兩種 List 實現。雖然它們都實現了 List 接口&#xff0c;但在底層結構、訪問效率、插入/刪除操作、擴…

使用OpenLayers調用geoserver發布的wms服務

1.前端vue3調用代碼 <template><div><div ref"mapContainer" class"map"></div></div> </template><script setup lang"ts"> import { ref, onMounted } from "vue"; import Map from &quo…

二十七、【測試執行篇】測試計劃:前端一鍵觸發測試 實時狀態追蹤

二十七、【測試執行篇】測試計劃:前端一鍵觸發測試 & 實時狀態追蹤 前言準備工作第一部分:后端 API 確認第二部分:前端實現 - 觸發執行與狀態輪詢第三部分:后端 API 增強第四部分:全面測試總結前言 一個完整的自動化測試流程,從測試用例的創建到報告的生成,最終都需…

60天python訓練營打卡day52

學習目標&#xff1a; 60天python訓練營打卡 學習內容&#xff1a; DAY 52 神經網絡調參指南 知識點回顧&#xff1a; 1.隨機種子 2.內參的初始化 3.神經網絡調參指南 a.參數的分類 b.調參的順序 c.各部分參數的調整心得 作業&#xff1a;對于day’41的簡單cnn&#xff0c;看…

【Modern C++ Part3】Understand-decltype

條款三&#xff1a;理解decltype decltype是一個怪異的發明。給定一個變量名或者表達式&#xff0c;decltype會告訴你這個變量名或表達式的類型。decltype的返回的類型往往也是你期望的。然而有時候&#xff0c;它提供的結果會使開發者極度抓狂而不得參考其他文獻或者在線的Q&…

前端批量請求場景

文章目錄 一、批量請求1、Promise.allSettled2、返回值穿透 二、案例1、 批量任務2、緩存優化3、另一種實現方式 一般時候前端都是簡單的查詢任務&#xff0c;復雜的數據獲取都是后臺處理好再返回&#xff0c;如果遇到接口流程化處理、數據組裝&#xff0c;可以參考一下。 一、…

芊芊妙音:智能變聲,玩轉聲音魔法

在當今豐富多彩的社交和娛樂環境中&#xff0c;聲音的魅力正逐漸被更多人發現和利用。無論是線上社交、短視頻創作還是直播互動&#xff0c;一個獨特而有趣的聲音總能讓人眼前一亮&#xff0c;甚至成為個人風格的一部分。《芊芊妙音》正是這樣一款能夠幫助用戶輕松實現聲音變換…

安防監控視頻匯聚平臺EasyCVR v3.7.2版云端錄像無法在web端播放的原因排查和解決方法

有用戶反饋&#xff0c;在使用EasyCVR視頻匯聚平臺時&#xff0c;發現云端錄像無法在Web頁面正常播放。為幫助大家高效解決類似困擾&#xff0c;本文將詳細剖析排查思路與解決方案。 用戶軟件版本信息&#xff1a; 問題排查與解決步驟&#xff1a; 1&#xff09;問題復現驗證…

vxe-upload vue 實現附件上傳、手動批量上傳附件的方式

vxe-upload vue 實現附件上傳、手動批量上傳附件的方式 查看官網&#xff1a;https://vxeui.com 安裝 npm install vxe-pc-ui4.6.47// ... import VxeUIAll from vxe-pc-ui import vxe-pc-ui/lib/style.css // ...createApp(App).use(VxeUIAll).mount(#app) // ...上傳附件支…