Linux(宝塔)部署.Net Core完整记录

前言

最近在V站上看到一个外卖推广的小程序,意思大概是类似淘宝联盟那种,别人走自己的链接后,自己可以抽取大概4%-6%的提成。觉得还蛮有意思的,一开始开源的是静态页面写死的,所以我这边用.Net Core写了个简单的后台。

左边是无后台的,右边红色框是后台配置的。当然功能是很简单的,主要是记录发布到Ubuntu18.4的时候遇到的问题与解决办法。

· · ·

安装宝塔

宝塔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,受不了。但是真的去使用了,去探索了,嗯,真香~

posted @ 2020-12-07 12:28  果冻栋吖  阅读(6915)  评论(5编辑  收藏  举报