記一次.Net Core通過GDI+在CentOS 7(Docker)環境中繪圖報錯The type initializer for 'Gdip' threw an exception的問題及處理方式


一、前言

今天在AspNetCore3.1環境中做了一個用戶登錄頁面,在登錄頁面中有一個功能就是需要后端動態繪制一個驗證碼圖片,防止前端通過機器或爬蟲工具模擬自動登錄。
在開發機器上(windows10)調試正常,但是部署到centos7容器(容器基礎環境mcr.microsoft.com/dotnet/aspnet:3.1)中,驗證碼一直顯示不出來,通過前端調試發現請求500(服務器內部錯誤)

然后就登錄服務器查看容器日志
果然報錯“'Gdip'的類型初始化器拋出了一個異常”

代碼如下:

/// <summary>
/// 生成驗證碼
/// </summary>
/// <returns></returns>
public static byte[] ValidateCode(string code)
{
    using (var bitmap = new Bitmap(100, 30))
    {
        using (var gph = Graphics.FromImage(bitmap))
        {
            gph.FillRectangle(Brushes.White, 0, 0, bitmap.Width, bitmap.Height);
            gph.DrawRectangle(new Pen(Color.Black), 1, 1, bitmap.Width - 2, bitmap.Height - 2);
            using (Brush bush = new SolidBrush(Color.SteelBlue))
            {
                gph.DrawString(code, new Font("黑體", 20, FontStyle.Italic), bush, 10, 2);
                var random = new Random();
                // 畫線條
                for (int i = 0; i < 5; i++)
                {
                    gph.DrawLine(new Pen(GetRandomColor()), random.Next(bitmap.Width), random.Next(bitmap.Height), random.Next(bitmap.Width), random.Next(bitmap.Height));
                }

                // 畫躁點
                for (int i = 0; i < 100; i++)
                {
                    bitmap.SetPixel(random.Next(bitmap.Width), random.Next(bitmap.Height), GetRandomColor());
                }
                using (var ms = new MemoryStream())
                {
                    bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                    var imgData = ms.GetBuffer();
                    return imgData;
                }
            }
        }
    }
}

二、問題分析

查看相關資料發現.Net Core本身不包括和圖片有關的Image、Bitmap等類型。用過.Net框架的同學應該都知道Bitmap、Image是放在System.Drawing.dll中,通過COM引用就可使用。
但在.Net Core中對於圖片的操作在我們開發中很常見,比如:生成驗證碼、二維碼等等。在.NET Core 的早期版本中,也有.NET社區開發者實現了一些 System.Drawing的Image等類型實現的組件,比如CoreCompat.System.DrawingZKWeb.System.Drawing等。后來微軟官方提供了一個組件System.Drawing.Common實現了System.Drawing的常用類型,以Nuget 包的方式發布的。然后我當前的項目的驗證碼繪制就是使用的微軟官方的System.Drawing.Common

System.Drawing.Common組件提供對GDI+圖形功能的訪問。它是依賴於GDI+的,那么在Linux上它如何使用GDI+,因為Linux上是沒有GDI+的。Mono團隊使用C語言實現了GDI+接口,提供對非Windows系統的GDI+接口訪問能力(個人認為是模擬GDI+,與系統圖像接口對接),這個就是libgdiplus。進而可以推測 System.Drawing.Common 這個組件實現時,對於非Windows系統肯定依賴了ligdiplus這個組件。如果我們當前系統不存在這個組件,那么自然會報錯,找不到它,安裝它即可解決。

三、問題處理

首先進入Gdip報錯項目容器中安裝libgdiplus

  1. 更新基礎鏡像(mcr.microsoft.com/dotnet/aspnet:3.1)中的apt-get應用程序管理器,這一步務必需要更新哦,不然會報找不到libgdiplusapt-get update -y
  2. 安裝libgdiplus:apt-get install -y libgdiplus
  3. 創建符號鏈接:ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
  4. 重啟容器

這時我們再訪問一下發現可以了

四、優化

由於當前項目基於gitlab+jenkins做了持續部署,但在下載安裝libgdiplus時發現使用的軟件包源又是國外的地址,所以造成我們使用國內網絡非常慢,進而造成整體構建過程非常慢。
如果在Dockerfile中這么寫

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
COPY . .
RUN apt-get update -y && apt-get install -y libgdiplus && apt-get clean && ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
...
...

那么當提交代碼后整個構建時間就會非常長,我們可以基於mcr.microsoft.com/dotnet/aspnet:3.1基礎鏡像構建一個帶libgdiplus的自定義鏡像。然后Dockerfile中就基於該鏡像構建就可以了。

五、構建一個帶libgdiplus的DotNetCore基礎鏡像

  1. 通過docker拉取一個.netcore3.1基礎鏡像:docker pull mcr.microsoft.com/dotnet/aspnet:3.1

  2. 進入容器部署libgdiplus(步驟和上面一樣)

apt-get update -y
apt-get install -y libgdiplus
apt-get clean
ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll

  1. 經過漫長的部署,成功后我們退出當前容器並將當前容器重新打包成一個新的鏡像:docker commit -a="simple" -m="added libgdiplus based on .netcore3.1" 28a66ebccd55 dotnetcore-with-libgdiplus:v3.1

  2. 修改項目Dockerfile基礎鏡像為剛剛構建的自定義鏡像dotnetcore-with-libgdiplus:v3.1

  3. 提交代碼查看jenkins構建日志,通過截圖發現構建時已經成功使用我們自定義打包的基礎鏡像(dotnetcore-with-libgdiplus:v3.1)

  4. 查看項目看是否可以正常通過GDI+在CentOS 7(Docker)環境中繪圖

至此就全部大功告成了,完美解決.Net Core通過GDI+在CentOS 7(Docker)環境中繪圖報錯The type initializer for 'Gdip' threw an exception的問題
當然如果您有更好的解決方式歡迎評論區留言,以上解決步驟如有什么不妥,歡迎留言指正。謝謝~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM