基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)

上一篇(http://www.javashuo.com/article/p-vpwbauee-en.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操做作一个提醒。当每次抓取完数据后,自动发送邮件进行提醒。html

在开始正题以前仍是先玩一玩以前的说到却没有用到的一个库PuppeteerSharplinux

PuppeteerSharp:Headless Chrome .NET API ,它运用最多的应该是自动化测试和抓取异步加载的网页数据,更多介绍能够看GitHub:https://github.com/hardkoded/puppeteer-sharpgit

我这里主要来试试它的异步抓取功能,同时它还能帮咱们生成网页截图或者PDF。github

若是没有安装能够先安装一下,在.BackgroundJobs层安装PuppeteerSharpInstall-Package PuppeteerSharpchrome

在Jobs文件夹下新建一个PuppeteerTestJob.cs,继承IBackgroundJob,一样是在ExecuteAsync()方法中执行操做。npm

//PuppeteerTestJob.cs
using System;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.PuppeteerTest
{
    public class PuppeteerTestJob : IBackgroundJob
    {
        public async Task ExecuteAsync()
        {
            throw new NotImplementedException();
        }
    }
}

使用 await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision); 第一次检测到没有浏览器文件会默认帮咱们下载 chromium 浏览器。json

DownloadAsync(...)能够指定 Chromium 版本,BrowserFetcher.DefaultRevision 下载当前默认最稳定的版本。浏览器

而后配置浏览器启动的方式。服务器

using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = true,
    Args = new string[] { "--no-sandbox" }
});

感兴趣的能够本身看看LaunchOptions有哪些参数,我这里指定了Headless = true 以无头模式运行浏览器,而后加了一个启动参数 "--no-sandbox"。针对Linux环境下,若是是运行在 root 权限下,在启动 Puppeteer 时要添加 "--no-sandbox" 参数,不然 Chromium 会启动失败。网络

咱们打开一个异步加载的网页,而后获取到页面加载完后的HTML,以我我的博客中的某个单页为例:https://meowv.com/wallpaper

//PuppeteerTestJob.cs
using PuppeteerSharp;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.PuppeteerTest
{
    public class PuppeteerTestJob : IBackgroundJob
    {
        public async Task ExecuteAsync()
        {
            await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);

            using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new string[] { "--no-sandbox" }
            });

            using var page = await browser.NewPageAsync();

            await page.SetViewportAsync(new ViewPortOptions
            {
                Width = 1920,
                Height = 1080
            });

            var url = "https://meowv.com/wallpaper";
            await page.GoToAsync(url, WaitUntilNavigation.Networkidle0);

            var content = await page.GetContentAsync();
        }
    }
}

page.SetViewportAsync()设置网页预览大小,page.GoToAsync()语法打开网页,WaitUntilNavigation.Networkidle0等待网页加载完毕,使用page.GetContentAsync()获取到HTML。

新建扩展方法,调用这个PuppeteerTestJobExecuteAsync()方法,调试看看效果。

1

HTML已经出来了,此时该干吗就干吗就能够了。

第一次运行可能会很慢,由于若是你本地不存在 Chromium 是会去帮咱们下载的,由于网络缘由可能会下载的很慢,因此推荐你们手动下载。

可使用淘宝的源:https://npm.taobao.org/mirrors/chromium-browser-snapshots/

要注意的是,下载完成后的解压的路径不能出错,默认下载地址是在启动目录下面。

Windows:..\.local-chromium\Win64-706915\chrome-win 、 Linux:../.local-chromium/Linux-706915/chrome-linux

接下来试试生成PDF和保存图片功能,使用方式也很简单。

await page.PdfAsync("meowv.pdf",new PdfOptions { });
await page.ScreenshotAsync("meowv.png", new ScreenshotOptions
{
    FullPage = true,
    Type = ScreenshotType.Png
});

这里只作简单的展现,page.PdfAsync()直接生成PDF文件,同时还有不少方法能够本身调用page.试试,PdfOptions选项中能够设置各类参数。

page.ScreenshotAsync()保存图片,ScreenshotOptions中FullPage能够设置保存图片为全屏模式,图片格式为Png类型。

2

能够看到项目根目录已经生成了图片和PDF,感受去试试吧。

接下里来实现发送邮件的功能。

我这里发邮件的帐号是用的腾讯企业邮箱,也能够用普通邮箱开通SMTP服务便可。

appsettings.json配置收发邮件的帐号等信息。

//appsettings.json
  "Email": {
    "Host": "smtp.exmail.qq.com",
    "Port": 465,
    "UseSsl": true,
    "From": {
      "Username": "123@meowv.com",
      "Password": "[Password]",
      "Name": "MEOWV.COM",
      "Address": "123@meowv.com"
    },
    "To": [
      {
        "Name": "test1",
        "Address": "test1@meowv.com"
      },
      {
        "Name": "test2",
        "Address": "test2@meowv.com"
      }
    ]
  }

而后再AppSettings中读取配置的项。

//AppSettings.cs
public static class Email
{
    /// <summary>
    /// Host
    /// </summary>
    public static string Host => _config["Email:Host"];

    /// <summary>
    /// Port
    /// </summary>
    public static int Port => Convert.ToInt32(_config["Email:Port"]);

    /// <summary>
    /// UseSsl
    /// </summary>
    public static bool UseSsl => Convert.ToBoolean(_config["Email:UseSsl"]);

    /// <summary>
    /// From
    /// </summary>
    public static class From
    {
        /// <summary>
        /// Username
        /// </summary>
        public static string Username => _config["Email:From:Username"];

        /// <summary>
        /// Password
        /// </summary>
        public static string Password => _config["Email:From:Password"];

        /// <summary>
        /// Name
        /// </summary>
        public static string Name => _config["Email:From:Name"];

        /// <summary>
        /// Address
        /// </summary>
        public static string Address => _config["Email:From:Address"];
    }

    /// <summary>
    /// To
    /// </summary>
    public static IDictionary<string, string> To
    {
        get
        {
            var dic = new Dictionary<string, string>();

            var emails = _config.GetSection("Email:To");
            foreach (IConfigurationSection section in emails.GetChildren())
            {
                var name = section["Name"];
                var address = section["Address"];

                dic.Add(name, address);
            }
            return dic;
        }
    }
}

分别介绍下每项的含义:

  • Host:发送邮件服务器地址。
  • Port:服务器地址端口号。
  • UseSsl:是否使用SSL方式。
  • From:发件人的帐号密码,名称及邮箱地址,通常邮箱地址和帐号是相同的。
  • To:收件人邮箱列表,也包含名称和邮箱地址。

收件人邮箱列表我将其读取为IDictionary<string, string>了,key是名称,value是邮箱地址。

接着在.ToolKits层添加一个EmailHelper.cs,收发邮件我选择了MailKitMailKit两个库,没有安装的先安装一下,Install-Package MailKitInstall-Package MimeKit

直接新建一个发送邮件的方法SendAsync(),按照要求将基本的配置信息填进去,而后直接调用便可。

//EmailHelper.cs
using MailKit.Net.Smtp;
using Meowv.Blog.Domain.Configurations;
using MimeKit;
using System.Linq;
using System.Threading.Tasks;

namespace Meowv.Blog.ToolKits.Helper
{
    public static class EmailHelper
    {
        /// <summary>
        /// 发送Email
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static async Task SendAsync(MimeMessage message)
        {
            if (!message.From.Any())
            {
                message.From.Add(new MailboxAddress(AppSettings.Email.From.Name, AppSettings.Email.From.Address));
            }
            if (!message.To.Any())
            {
                var address = AppSettings.Email.To.Select(x => new MailboxAddress(x.Key, x.Value));
                message.To.AddRange(address);
            }

            using var client = new SmtpClient
            {
                ServerCertificateValidationCallback = (s, c, h, e) => true
            };
            client.AuthenticationMechanisms.Remove("XOAUTH2");

            await client.ConnectAsync(AppSettings.Email.Host, AppSettings.Email.Port, AppSettings.Email.UseSsl);
            await client.AuthenticateAsync(AppSettings.Email.From.Username, AppSettings.Email.From.Password);
            await client.SendAsync(message);
            await client.DisconnectAsync(true);
        }
    }
}

SendAsync(...)接收一个参数MimeMessage对象,这样就完成了一个通用的发邮件方法,接着咱们去须要发邮件的地方构造MimeMessage,调用SendAsync()

//WallpaperJob.cs
...
    // 发送Email
    var message = new MimeMessage
    {
        Subject = "【定时任务】壁纸数据抓取任务推送",
        Body = new BodyBuilder
        {
            HtmlBody = $"本次抓取到{wallpapers.Count()}条数据,时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"
        }.ToMessageBody()
    };
    await EmailHelper.SendAsync(message);
...
//HotNewsJob.cs
...
    // 发送Email
    var message = new MimeMessage
    {
        Subject = "【定时任务】每日热点数据抓取任务推送",
        Body = new BodyBuilder
        {
            HtmlBody = $"本次抓取到{hotNews.Count()}条数据,时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"
        }.ToMessageBody()
    };
    await EmailHelper.SendAsync(message);
...

分别在两个爬虫脚本中添加发送Email,MimeMessage中设置了邮件主题Subject,正文Body,最后调用await EmailHelper.SendAsync(message)执行发送邮件操做。

编译运行执行两个定时任务,看看可否收到邮件提醒。

3

成功了,邮箱收到了两条提醒。

还有一种比较特殊的用法,也介绍一下,若是想要发送带图片的邮件怎么操做呢?注意不是附件,是将图片内嵌在邮箱中。

通常常规都是有邮件模板的,将图片的具体地址插入到img标签中,这就不说了,这里选择另一种方式。之前面添加的PuppeteerTestJob为例,正好咱们生成了一张图片的。将这种图片以邮件的形式发出去。

public class PuppeteerTestJob : IBackgroundJob
{
    public async Task ExecuteAsync()
    {
        var path = Path.Combine(Path.GetTempPath(), "meowv.png");
        
        ...
        
        await page.ScreenshotAsync(path, new ScreenshotOptions
        {
            FullPage = true,
            Type = ScreenshotType.Png
        });

        // 发送带图片的Email
        var builder = new BodyBuilder();

        var image = builder.LinkedResources.Add(path);
        image.ContentId = MimeUtils.GenerateMessageId();

        builder.HtmlBody = "当前时间:{0}.<img src=\"cid:{1}\"/>".FormatWith(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), image.ContentId);

        var message = new MimeMessage
        {
            Subject = "【定时任务】每日热点数据抓取任务推送",
            Body = builder.ToMessageBody()
        };
        await EmailHelper.SendAsync(message);
    }
}

先肯定咱们生成图片的路径 path ,将图片生成Message-Id,而后赋值给ContentId,给模板中<img src=\"cid:{1}\"/>图片标签cid赋上值在调用发送邮件方法便可。

4

成功收到邮件,搞定了,你学会了吗?😁😁😁

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial


基于 abp vNext 和 .NET Core 开发博客项目,截止到本篇所用到的基础模块算是写完了,若是对您有些许帮助请多多分享,个人全部原创文章都首发于我发我的公众号:阿星Plus 。

下面有二维码能够直接扫一扫,若是你不想关注也没有关系,博客园我也会同步过来的。

无论由于什么,若是你在学习这个项目或者跟着我一块儿作这个项目,里面确定仍是有瑕疵的,你们能够根据本身的需求自行修改。

接下来应该还会更新博客所用到的接口,这个纯属于CRUD,能够本身先行开发,我这边目前也不知道以什么样的方式展示给你们是最好的选择。

相关文章
相关标签/搜索