Linux(寶塔)部署.Net Core完整記錄

果凍棟吖發表於2020-12-07

前言

最近在V站上看到一個外賣推廣的小程式,意思大概是類似淘寶聯盟那種,別人走自己的連結後,自己可以抽取大概4%-6%的提成。覺得還蠻有意思的,一開始開源的是靜態頁面寫死的,所以我這邊用.Net Core寫了個簡單的後臺。

左邊是無後臺的,右邊紅色框是後臺配置的。當然功能是很簡單的,主要是記錄釋出到Ubuntu18.4的時候遇到的問題與解決辦法。

Linux(寶塔)部署.Net Core完整記錄 · · · Linux(寶塔)部署.Net Core完整記錄

安裝寶塔

寶塔Linux皮膚是提升運維效率的伺服器管理軟體,支援一鍵LAMP/LNMP/叢集/監控/網站/FTP/資料庫/JAVA等100多項伺服器管理功能。

這裡節省時間直接使用寶塔皮膚了,這個真的是太方便了,哈哈。安裝也非常簡單。

因為我使用的是Ubuntu,安裝指令碼

wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh

其他版本請參考官方文件:https://www.bt.cn/download/linux.html

安裝完成後會顯示登入地址、使用者名稱、密碼資訊。登入後瀏覽器將彈出推薦安裝套件,為方便直接一鍵安裝LNMP。

安裝.NetCore SDK 3.1

微軟官方文件:https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-ubuntu

因為我使用的18.04,所以找到對應文件。

使用 APT 進行安裝可通過幾個命令來完成。 安裝 .NET 之前,請執行以下命令,將 Microsoft 包簽名金鑰新增到受信任金鑰列表,並新增包儲存庫。

開啟終端並執行以下命令:

wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

安裝 SDK

.NET SDK 使你可以通過 .NET 開發應用。 如果安裝 .NET SDK,則無需安裝相應的執行時。 若要安裝 .NET SDK,請執行以下命令:

sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-3.1

安裝執行時

sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y aspnetcore-runtime-3.1

作為 ASP.NET Core 執行時的一種替代方法,你可以安裝不包含 ASP.NET Core 支援的 .NET 執行時:將上一命令中的 aspnetcore-runtime-5.0 替換為 dotnet-runtime-5.0

sudo apt-get install -y dotnet-runtime-5.0

其實上述就是照搬微軟的官方文件,官方文件還是寫的很清楚的。

釋出.NetCore專案

我一開始目標執行時選擇的Linux-64,但是出現了這樣的錯誤`錯誤 NU1605: 檢測到包降級: XXXXXXXXXXXXX 從 4.3.0 降級到 XXXXXXXXXXXXX。直接從專案引用包以選擇不同版本。

image-20201207114647335

通過檢視微軟官方文件:https://docs.microsoft.com/zh-cn/nuget/reference/errors-and-warnings/nu1605

問題當在 .NET Core 3.0 或更高版本的專案中同時引用時,與 .NET Core 1.0 和1.1 隨附的某些包組合不相容。 問題包通常以或開頭 System. Microsoft. ,並具有4.0.0 和4.3.1 之間的版本號。 在這種情況下,降級訊息將具有從執行時開始的包。 依賴關係鏈。

解決方案若要解決此問題,請新增以下 PackageReference:

<PackageReference Include="Microsoft.NETCore.Targets" Version="3.0.0" PrivateAssets="all" />

就是新增引用,但實際上你要保證所有專案的包引用版本是一致的。

另一種方法

釋出的時候目標執行時直接選擇可移植吧~

寶塔皮膚釋出.Net Core專案,並啟動專案

在檔案 wwwroot新建專案資料夾,將本地釋出檔案打包拷貝至伺服器解壓。

在伺服器上終端命令進入部署檔案所在目錄,然後使用dotnet命令啟動服務:

dotnet XXXXXX.Admin.dll --urls "http://localhost:5000"

image-20201207115503272

Nginx設定反代訪問

現在我們還不能直接訪問到我們新部署專案,需要使用Nginx設定反向代理,將特定的埠代理到http://localhost:5000,這一步可以通過寶塔皮膚來完成,步驟如下:

  • 在寶塔皮膚上新建一個網站,設定為靜態網站即可,並繫結好域名。

  • 在剛才新建的網站中設定反向代理,目標URL填寫http://localhost:5000即可,傳送域名localhost

image-20201207115748843

image-20201207115850382

瀏覽器正式可訪問專案,此處可能需要重啟一下。

使用 Supervisor 守護程式

現在還有個問題,就是當我們關閉xShell等SSH工具的時候服務程式也會停止執行,我們可使用 Supervisor 守護程式執行。

  • 在寶塔皮膚上安裝Supervisor
  • 新增守護程式(使用者建議選擇www,不要使用root)

image-20201207120537225

問題

問題1

錯誤 NU1605: 檢測到包降級: XXXXXXXXXXXXX 從 4.3.0 降級到 XXXXXXXXXXXXX。直接從專案引用包以選擇不同版本

這個問題一開始我按照官方文件修改了,實際還是不可以。所以我選擇了可移植髮布的。而我在寫這篇文章的時候又可以了。

問題2

驗證碼我使用了System.Drawing,不過在Linux下的話,這個是無法顯示的。

解決辦法

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

Ubuntu一鍵命令

sudo curl https://raw.githubusercontent.com/stulzq/awesome-dotnetcore-image/master/install/ubuntu.sh|sh

參考:https://www.cnblogs.com/stulzq/p/10172550.html

問題3

指定埠啟動

修改Program.cs

增加程式碼

.ConfigureAppConfiguration(builder =>
                {
                    //dotnet test.dll --urls "http://*:5000;https://*:5001"
                    builder.AddCommandLine(args);//設定新增命令列
                })

完整程式碼

 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                //將預設ServiceProviderFactory指定為AutofacServiceProviderFactory https://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html#asp-net-core-3-0-and-generic-hosting
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureAppConfiguration(builder =>
                {
                    //dotnet test.dll --urls "http://*:5200;https://*:5100"
                    builder.AddCommandLine(args);//設定新增命令列
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

問題4

驗證碼生成程式碼

驗證碼生成程式碼應該是蠻多的,我把我的分享下

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace XXX.Util
{
   public static class ValidateCodeHelper
   {
       /// <summary>
       /// 驗證碼的最大長度
       /// </summary>
       public static int MaxLength => 10;

       /// <summary>
       /// 驗證碼的最小長度
       /// </summary>
       public static int MinLength => 1;

       /// <summary>
       /// 生成驗證碼
       /// </summary>
       /// <param name="length">指定驗證碼的長度</param>
       /// <returns></returns>
       public static string CreateValidateCode(int length)
       {
           int[] randMembers = new int[length];
           int[] validateNums = new int[length];
           string validateNumberStr = "";
           //生成起始序列值
           int seekSeek = unchecked((int)DateTime.Now.Ticks);
           Random seekRand = new Random(seekSeek);
           int beginSeek = (int)seekRand.Next(0, Int32.MaxValue - length * 10000);
           int[] seeks = new int[length];
           for (int i = 0; i < length; i++)
           {
               beginSeek += 10000;
               seeks[i] = beginSeek;
           }
           //生成隨機數字
           for (int i = 0; i < length; i++)
           {
               Random rand = new Random(seeks[i]);
               int pownum = 1 * (int)Math.Pow(10, length);
               randMembers[i] = rand.Next(pownum, Int32.MaxValue);
           }
           //抽取隨機數字
           for (int i = 0; i < length; i++)
           {
               string numStr = randMembers[i].ToString();
               int numLength = numStr.Length;
               Random rand = new Random();
               int numPosition = rand.Next(0, numLength - 1);
               validateNums[i] = Int32.Parse(numStr.Substring(numPosition, 1));
           }
           //生成驗證碼
           for (int i = 0; i < length; i++)
           {
               validateNumberStr += validateNums[i].ToString();
           }
           return validateNumberStr;
       }
       /// <summary>
       /// 得到驗證碼圖片的長度
       /// </summary>
       /// <param name="validateNumLength">驗證碼的長度</param>
       /// <returns></returns>
       public static int GetImageWidth(int validateNumLength)
       {
           return (int)(validateNumLength * 12.0);
       }
       /// <summary>
       /// 得到驗證碼的高度
       /// </summary>
       /// <returns></returns>
       public static double GetImageHeight()
       {
           return 22.5;
       }


       //C# MVC 升級版
       /// <summary>
       /// 建立驗證碼的圖片
       /// </summary> 
       /// <param name="validateCode">驗證碼</param>
       public static byte[] CreateValidateGraphic(string validateCode)
       {
           Bitmap image = new Bitmap((int)Math.Ceiling(validateCode.Length * 12.0), 22);
           Graphics g = Graphics.FromImage(image);
           try
           {
               //生成隨機生成器
               Random random = new Random();
               //清空圖片背景色
               g.Clear(Color.White);
               //畫圖片的干擾線
               for (int i = 0; i < 25; i++)
               {
                   int x1 = random.Next(image.Width);
                   int x2 = random.Next(image.Width);
                   int y1 = random.Next(image.Height);
                   int y2 = random.Next(image.Height);
                   g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
               }
               Font font = new Font("Arial", 12, (FontStyle.Bold | FontStyle.Italic));
               LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height),
                Color.Blue, Color.DarkRed, 1.2f, true);
               g.DrawString(validateCode, font, brush, 3, 2);
               //畫圖片的前景干擾點
               for (int i = 0; i < 100; i++)
               {
                   int x = random.Next(image.Width);
                   int y = random.Next(image.Height);
                   image.SetPixel(x, y, Color.FromArgb(random.Next()));
               }
               //畫圖片的邊框線
               g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
               //儲存圖片資料
               MemoryStream stream = new MemoryStream();
               image.Save(stream, ImageFormat.Jpeg);
               //輸出圖片流
               return stream.ToArray();
           }
           finally
           {
               g.Dispose();
               image.Dispose();
           }
       }
   }
}

總結

其實就是一篇流水賬,記錄了釋出的過程和遇到的問題及解決辦法。之前伺服器一直是使用的WinServer,因為熟悉。勇於嘗試並去解決問題,慢慢進步~

大學裡也學過Linux,受不了。但是真的去使用了,去探索了,嗯,真香~

相關文章