Asp.Net Core 快速邮件队列设计与实现

发送邮件几乎是软件系统中必不可少的功能,在Asp.Net Core 中我们可以使用MailKit发送邮件,MailKit发送邮件比较简单,网上有许多可以参考的文章,但是应该注意附件名长度,和附件名不能出现中文的问题,如果你遇到了这样的问题可以参考我之前写的这篇博客Asp.Net Core MailKit 完美附件(中文名、长文件名)。

在我们简单搜索网络,并成功解决了附件的问题之后,我们已经能够发送邮件啦!不过另一个问题显现出来——发送邮件太慢了,没错,在我使用QQ邮箱发送时,单封邮件发送大概要用1.5秒左右,用户可能难以忍受请求发生1.5秒的延迟。

所以,我们必须解决这个问题,我们的解决办法就是使用邮件队列来发送邮件

设计邮件队列

Ok, 第一步就是规划我们的邮件队列有什么

EmailOptions

我们得有一个邮件Options类,来存储邮件相关的选项

/// <summary>
/// 邮件选项
/// </summary>
public class EmailOptions{  
 public bool DisableOAuth { get; set; }  
   public string DisplayName { get; set; }  
     public string Host { get; set; } // 邮件主机地址public string Password { get; set; }  
      public int Port { get; set; }  
       public string UserName { get; set; }  
        public int SleepInterval { get; set; } = 3000;...

SleepInterval 是睡眠间隔,因为目前我们实现的队列是进程内的独立线程,发送器会循环读取队列,当队列是空的时候,我们应该让线程休息一会,不然无限循环会消耗大量CPU资源

然后我们还需要的就是 一个用于存储邮件的队列,或者叫队列提供器,总之我们要将邮件存储起来。以及一个发送器,发送器不断的从队列中读取邮件并发送。还需要一个邮件写入工具,想要发送邮件的代码使用写入工具将邮件转储到队列中。

那么我们设计的邮件队列事实上就有了三个部分:

  • 队列存储提供器(邮件的事实存储)

  • 邮件发送机 (不断读取队列中的邮件,并发送)

  • 邮件服务 (想法送邮件时,调用邮件服务,邮件服务会将邮件写入队列)

队列存储提供器设计

那么我们设计的邮件队列提供器接口如下:

public interface IMailQueueProvider{   
 void Enqueue(MailBox mailBox);  
   bool TryDequeue(out MailBox mailBox);  
    int Count { get; }  
      bool IsEmpty { get; }...

四个方法,入队、出队、队列剩余邮件数量、队列是否是空,我们对队列的基本需求就是这样。

MailBox是对邮件的封装,并不复杂,稍后会介绍到

邮件服务设计

public interface IMailQueueService{   
     void Enqueue(MailBox box);

对于想要发送邮件的组件或者代码部分来讲,只需要将邮件入队,这就足够了

邮件发送机(兼邮件队列管理器)设计

public interface IMailQueueManager{  
 void Run();  
   void Stop();  
   bool IsRunning { get; }  
    int Count { get; }    

启动队列,停止队列,队列运行中状态,邮件计数

现在,三个主要部分就设计好了,我们先看下MailBox,接下来就去实现这三个接口

MailBox

MailBox 如下:

public class MailBox{ 
   public IEnumerable<IAttachment> Attachments { get; set; }
      public string Body { get; set; }  
        public IEnumerable<string> Cc { get; set; }
            public bool IsHtml { get; set; }
               public string Subject { get; set; }
                  public IEnumerable<string> To { get; set; }...

这里面没什么特殊的,大家一看便能理解,除了IEnumerable<IAttachment> Attachments { get; set; }

附件的处理

在发送邮件中最复杂的就是附件了,因为附件体积大,往往还涉及非托管资源(例如:文件),所以附件处理一定要小心,避免留下漏洞和bug。

在MailKit中附件实际上是流Stream,例如下面的代码:

attachment = new MimePart(contentType)
{Content = new MimeContent(fs),ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),ContentTransferEncoding = ContentEncoding.Base64,
};

其中new MimeContent(fs)是创建的Content,fs是Stream,MimeContent的构造函数如下:

public MimeContent(Stream stream, ContentEncoding encoding = ContentEncoding.Default)

所以我们的设计的附件是基于Stream的。

一般情况附件是磁盘上的文件,或者内存流MemoryStream或者 byte[]数据。附件需要实际的文件的流Stream和一个附件名,所以附件接口设计如下:

public interface IAttachment : IDisposable{    Stream GetFileStream();    string GetName();

那么我们默认实现了两中附件类型 物理文件附件内存文件附件,byte[]数据可以轻松的转换成 内存流,所以没有写这种

MemoryStreamAttechment

public class MemoryStreamAttechment : IAttachment{  
 private readonly MemoryStream _stream;  
  private readonly string _fileName;  
   public MemoryStreamAttechment(MemoryStream stream, string fileName)    {_stream = stream;_fileName = fileName;}  
   
   public void Dispose()        => _stream.Dispose();
   public Stream GetFileStream()        => _stream;  
   public string GetName()        => _fileName;

内存流附件实现要求在创建时传递一个 MemoryStream和附件名称,比较简单

物理文件附件

public class PhysicalFileAttachment : IAttachment{   
 public PhysicalFileAttachment(string absolutePath)    {  
 
      if (!File.Exists(absolutePath)){          
       throw new FileNotFoundException("文件未找到", absolutePath);}AbsolutePath = absolutePath;}    
       private FileStream _stream;  
         public string AbsolutePath { get; }  
           public void Dispose()    {_stream.Dispose();}  
        public Stream GetFileStream()    {    
            if (_stream == null){_stream = new FileStream(AbsolutePath, FileMode.Open);}        return _stream;}    public string GetName()    {      
              return System.IO.Path.GetFileName(AbsolutePath);...    

这里,我们要注意的是创建FileStream的时机,是在请求GetFileStream方法时,而不是构造函数中,因为创建FileStreamFileStream会占用文件,如果我们发两封邮件使用了同一个附件,那么会抛出异常。而写在GetFileStream方法中相对比较安全(除非发送器是并行的)

实现邮件队列

在我们这篇文章中,我们实现的队列提供器是基于内存的,日后呢我们还可以实现其它的基于其它存储模式的,比如数据库,外部持久性队列等等,另外基于内存的实现不是持久的,一旦程序崩溃。未发出的邮件就会boom然后消失 XD...

邮件队列提供器IMailQueueProvider实现

代码如下:

public class MailQueueProvider : IMailQueueProvider{  
 private static readonly ConcurrentQueue<MailBox> _mailQueue = new ConcurrentQueue<MailBox>();    public int Count => _mailQueue.Count;    public bool IsEmpty => _mailQueue.IsEmpty;    public void Enqueue(MailBox mailBox)    {_mailQueue.Enqueue(mailBox);}    public bool TryDequeue(out MailBox mailBox)    {        return _mailQueue.TryDequeue(out mailBox);}

本文的实现是一个 ConcurrentQueue

邮件服务IMailQueueService实现

代码如下:

public class MailQueueService : IMailQueueService{    private readonly IMailQueueProvider _provider;    /// <summary>/// 初始化实例/// </summary>/// <param name="provider"></param>public MailQueueService(IMailQueueProvider provider)    {_provider = provider;}    /// <summary>/// 入队/// </summary>/// <param name="box"></param>public void Enqueue(MailBox box)    {_provider.Enqueue(box);}    

这里,我们的服务依赖于IMailQueueProvider,使用了其入队功能

邮件发送机IMailQueueManager实现

这个相对比较复杂,我们先看下完整的类,再逐步解释:

public class MailQueueManager : IMailQueueManager{    private readonly SmtpClient _client;    private readonly IMailQueueProvider _provider;    private readonly ILogger<MailQueueManager> _logger;    private readonly EmailOptions _options;    private bool _isRunning = false;    private bool _tryStop = false;    private Thread _thread;    /// <summary>/// 初始化实例/// </summary>/// <param name="provider"></param>/// <param name="options"></param>/// <param name="logger"></param>public MailQueueManager(IMailQueueProvider provider, IOptions<EmailOptions> options, ILogger<MailQueueManager> logger)    {_options = options.Value;_client = new SmtpClient{            // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)ServerCertificateValidationCallback = (s, c, h, e) => true};        // Note: since we don't have an OAuth2 token, disable// the XOAUTH2 authentication mechanism.if (_options.DisableOAuth){_client.AuthenticationMechanisms.Remove("XOAUTH2");}_provider = provider;_logger = logger;}    /// <summary>/// 正在运行/// </summary>public bool IsRunning => _isRunning;    /// <summary>/// 计数/// </summary>public int Count => _provider.Count;    /// <summary>/// 启动队列/// </summary>public void Run()    {        if (_isRunning || (_thread != null && _thread.IsAlive)){_logger.LogWarning("已经运行,又被启动了,新线程启动已经取消");            return;}_isRunning = true;_thread = new Thread(StartSendMail){Name = "PmpEmailQueue",IsBackground = true,};_logger.LogInformation("线程即将启动");_thread.Start();_logger.LogInformation("线程已经启动,线程Id是:{0}", _thread.ManagedThreadId);}    /// <summary>/// 停止队列/// </summary>public void Stop()    {        if (_tryStop){            return;}_tryStop = true;}    private void StartSendMail()    {        var sw = new Stopwatch();        try{            while (true){                if (_tryStop){                    break;}                if (_provider.IsEmpty){_logger.LogTrace("队列是空,开始睡眠");Thread.Sleep(_options.SleepInterval);                    continue;}                if (_provider.TryDequeue(out MailBox box)){_logger.LogInformation("开始发送邮件 标题:{0},收件人 {1}", box.Subject, box.To.First());sw.Restart();SendMail(box);sw.Stop();_logger.LogInformation("发送邮件结束标题:{0},收件人 {1},耗时{2}", box.Subject, box.To.First(), sw.Elapsed.TotalSeconds);}}}        catch (Exception ex){_logger.LogError(ex, "循环中出错,线程即将结束");_isRunning = false;}_logger.LogInformation("邮件发送线程即将停止,人为跳出循环,没有异常发生");_tryStop = false;_isRunning = false;}    private void SendMail(MailBox box)    {        if (box == null){            throw new ArgumentNullException(nameof(box));}        try{MimeMessage message = ConvertToMimeMessage(box);SendMail(message);}        catch (Exception exception){_logger.LogError(exception, "发送邮件发生异常主题:{0},收件人:{1}", box.Subject, box.To.First());}        finally{            if (box.Attachments != null && box.Attachments.Any()){                foreach (var item in box.Attachments){item.Dispose();}}}}    private MimeMessage ConvertToMimeMessage(MailBox box)    {        var message = new MimeMessage();        var from = InternetAddress.Parse(_options.UserName);        from.Name = _options.DisplayName;message.From.Add(from);        if (!box.To.Any()){            throw new ArgumentNullException("to必须含有值");}message.To.AddRange(box.To.Convert());        if (box.Cc != null && box.Cc.Any()){message.Cc.AddRange(box.Cc.Convert());}message.Subject = box.Subject;        var builder = new BodyBuilder();        if (box.IsHtml){builder.HtmlBody = box.Body;}        else{builder.TextBody = box.Body;}        if (box.Attachments != null && box.Attachments.Any()){            foreach (var item in GetAttechments(box.Attachments))            {builder.Attachments.Add(item);}}message.Body = builder.ToMessageBody();        return message;}    private void SendMail(MimeMessage message)    {        if (message == null){            throw new ArgumentNullException(nameof(message));}        try{_client.Connect(_options.Host, _options.Port, false);            // Note: only needed if the SMTP server requires authenticationif (!_client.IsAuthenticated){_client.Authenticate(_options.UserName, _options.Password);}_client.Send(message);}        finally{_client.Disconnect(false);}}    private AttachmentCollection GetAttechments(IEnumerable<IAttachment> attachments)    {        if (attachments == null){            throw new ArgumentNullException(nameof(attachments));}AttachmentCollection collection = new AttachmentCollection();List<Stream> list = new List<Stream>(attachments.Count());        foreach (var item in attachments){            var fileName = item.GetName();            var fileType = MimeTypes.GetMimeType(fileName);            var contentTypeArr = fileType.Split('/');            var contentType = new ContentType(contentTypeArr[0], contentTypeArr[1]);MimePart attachment = null;Stream fs = null;            try{fs = item.GetFileStream();list.Add(fs);}            catch (Exception ex){_logger.LogError(ex, "读取文件流发生异常");fs?.Dispose();                continue;}attachment = new MimePart(contentType){Content = new MimeContent(fs),ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),ContentTransferEncoding = ContentEncoding.Base64,};            var charset = "UTF-8";attachment.ContentType.Parameters.Add(charset, "name", fileName);attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);            foreach (var param in attachment.ContentDisposition.Parameters){param.EncodingMethod = ParameterEncodingMethod.Rfc2047;}            foreach (var param in attachment.ContentType.Parameters){param.EncodingMethod = ParameterEncodingMethod.Rfc2047;}collection.Add(attachment);}        return collection;}
}

在构造函数中请求了另外三个服务,并且初始化了SmtpClient(这是MailKit中的)

    public MailQueueManager(IMailQueueProvider provider, IOptions<EmailOptions> options, ILogger<MailQueueManager> logger){_options = options.Value;_client = new SmtpClient{            // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)ServerCertificateValidationCallback = (s, c, h, e) => true};        // Note: since we don't have an OAuth2 token, disable// the XOAUTH2 authentication mechanism.if (_options.DisableOAuth){_client.AuthenticationMechanisms.Remove("XOAUTH2");}_provider = provider;_logger = logger;}

启动队列时创建了新的线程,并且将线程句柄保存起来:

    public void Run()    {        if (_isRunning || (_thread != null && _thread.IsAlive)){_logger.LogWarning("已经运行,又被启动了,新线程启动已经取消");            return;}_isRunning = true;_thread = new Thread(StartSendMail){Name = "PmpEmailQueue",IsBackground = true,};_logger.LogInformation("线程即将启动");_thread.Start();_logger.LogInformation("线程已经启动,线程Id是:{0}", _thread.ManagedThreadId);}

线程启动时运行了方法StartSendMail

    private void StartSendMail()    {        var sw = new Stopwatch();        try{            while (true){                if (_tryStop){                    break;}                if (_provider.IsEmpty){_logger.LogTrace("队列是空,开始睡眠");Thread.Sleep(_options.SleepInterval);                    continue;}                if (_provider.TryDequeue(out MailBox box)){_logger.LogInformation("开始发送邮件 标题:{0},收件人 {1}", box.Subject, box.To.First());sw.Restart();SendMail(box);sw.Stop();_logger.LogInformation("发送邮件结束标题:{0},收件人 {1},耗时{2}", box.Subject, box.To.First(), sw.Elapsed.TotalSeconds);}}}        catch (Exception ex){_logger.LogError(ex, "循环中出错,线程即将结束");_isRunning = false;}_logger.LogInformation("邮件发送线程即将停止,人为跳出循环,没有异常发生");_tryStop = false;_isRunning = false;}    

这个方法不断的从队列读取邮件并发送,当 遇到异常,或者_tryStoptrue时跳出循环,此时线程结束,注意我们会让线程睡眠,在适当的时候。

接下来就是方法SendMail了:

    private void SendMail(MailBox box)    {        if (box == null){            throw new ArgumentNullException(nameof(box));}        try{MimeMessage message = ConvertToMimeMessage(box);SendMail(message);}        catch (Exception exception){_logger.LogError(exception, "发送邮件发生异常主题:{0},收件人:{1}", box.Subject, box.To.First());}        finally{            if (box.Attachments != null && box.Attachments.Any()){                foreach (var item in box.Attachments){item.Dispose();...                

这里有一个特别要注意的就是在发送之后释放附件(非托管资源):

foreach (var item in box.Attachments)
{item.Dispose();...

发送邮件的核心代码只有两行:

MimeMessage message = ConvertToMimeMessage(box);SendMail(message);

第一行将mailbox转换成 MailKit使用的MimeMessage实体,第二步切实的发送邮件

为什么,我们的接口中没有直接使用MimeMessage而是使用MailBox?

因为MimeMessage比较繁杂,而且附件的问题不易处理,所以我们设计接口时单独封装MailBox简化了编程接口

转换一共两步,1是主体转换,比较简单。二是附件的处理这里涉及到附件名中文编码的问题。

    private MimeMessage ConvertToMimeMessage(MailBox box)    {        var message = new MimeMessage();        var from = InternetAddress.Parse(_options.UserName);        from.Name = _options.DisplayName;message.From.Add(from);        if (!box.To.Any()){            throw new ArgumentNullException("to必须含有值");}message.To.AddRange(box.To.Convert());        if (box.Cc != null && box.Cc.Any()){message.Cc.AddRange(box.Cc.Convert());}message.Subject = box.Subject;        var builder = new BodyBuilder();        if (box.IsHtml){builder.HtmlBody = box.Body;}        else{builder.TextBody = box.Body;}        if (box.Attachments != null && box.Attachments.Any()){            foreach (var item in GetAttechments(box.Attachments))            {builder.Attachments.Add(item);}}message.Body = builder.ToMessageBody();        return message;}    private AttachmentCollection GetAttechments(IEnumerable<IAttachment> attachments)    {        if (attachments == null){            throw new ArgumentNullException(nameof(attachments));}AttachmentCollection collection = new AttachmentCollection();List<Stream> list = new List<Stream>(attachments.Count());        foreach (var item in attachments){            var fileName = item.GetName();            var fileType = MimeTypes.GetMimeType(fileName);            var contentTypeArr = fileType.Split('/');            var contentType = new ContentType(contentTypeArr[0], contentTypeArr[1]);MimePart attachment = null;Stream fs = null;            try{fs = item.GetFileStream();list.Add(fs);}            catch (Exception ex){_logger.LogError(ex, "读取文件流发生异常");fs?.Dispose();                continue;}attachment = new MimePart(contentType){Content = new MimeContent(fs),ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),ContentTransferEncoding = ContentEncoding.Base64,};            var charset = "UTF-8";attachment.ContentType.Parameters.Add(charset, "name", fileName);attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);            foreach (var param in attachment.ContentDisposition.Parameters){param.EncodingMethod = ParameterEncodingMethod.Rfc2047;}            foreach (var param in attachment.ContentType.Parameters){param.EncodingMethod = ParameterEncodingMethod.Rfc2047;}collection.Add(attachment);}        return collection;}

在转化附件时下面的代码用来处理附件名编码问题:

var charset = "UTF-8";
attachment.ContentType.Parameters.Add(charset, "name", fileName);
attachment.ContentDisposition.Parameters.Add(charset, "filename", fileName);foreach (var param in attachment.ContentDisposition.Parameters)
{param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
}foreach (var param in attachment.ContentType.Parameters)
{param.EncodingMethod = ParameterEncodingMethod.Rfc2047;
}

到这了我们的邮件队列就基本完成了,接下来就是在程序启动后,启动队列,找到 Program.cs文件,并稍作改写如下:

var host = BuildWebHost(args);var provider = host.Services;
provider.GetRequiredService<IMailQueueManager>().Run();
host.Run();

这里在host.Run()主机启动之前,我们获取了IMailQueueManager并启动队列(别忘了注册服务)。

运行程序我们会看到控制台每隔3秒就会打出日志:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]User profile is available. Using 'C:\Users\Administrator\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.info: MailQueueManager[0]线程即将启动info: MailQueueManager[0]线程已经启动,线程Id是:9trce: MailQueueManager[0]队列是空,开始睡眠
Hosting environment: Development
Content root path: D:\publish
Now listening on: http://[::]:5000Application started. Press Ctrl+C to shut down.trce: MailQueueManager[0]队列是空,开始睡眠trce: MailQueueManager[0]队列是空,开始睡眠

到此,我们的邮件队列就完成了! :D

原文地址http://www.cnblogs.com/rocketRobin/p/9294845.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/320769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net core redis 驱动推荐,为什么不使用 StackExchange.Redis

前言本人从事 .netcore 转型已两年有余&#xff0c;对 .net core 颇有好感&#xff0c;这一切得益于优秀的语法、框架设计。2006年开始使用 .net 2.0&#xff0c;从 asp.net 到 winform 到 winservice 等等领域开发都些许涉猎。对.net和大多数同胞有着类似的感触&#xff0c;那…

[开源]开放域实体抽取泛用工具 NetCore2.1

开放域实体抽取泛用工具https://github.com/magicdict/FDDC更新时间 2018年7月16日 By 带着兔子去旅行开发这个工具的起源是天池大数据竞赛&#xff0c;FDDC2018金融算法挑战赛02&#xff0d;A股上市公司公告信息抽取。这个比赛是针对金融公告开展的信息抽取比赛。在参赛过程中…

【DP】树塔狂想曲

树塔狂想曲 题目大意&#xff1a; 有一个数字金字塔&#xff0c;让你求出去掉一个点后&#xff0c;从最顶端走到最低端的最大值&#xff08;只能往下或右下走&#xff09; 原题: 题目描述 相信大家都在长训班学过树塔问题&#xff0c;题目很简单求最大化一个三角形数塔从上…

.NET Core开发日志——HttpClientFactory

当需要向某特定URL地址发送HTTP请求并得到相应响应时&#xff0c;通常会用到HttpClient类。该类包含了众多有用的方法&#xff0c;可以满足绝大多数的需求。但是如果对其使用不当时&#xff0c;可能会出现意想不到的事情。博客园官方团队就遇上过这样的问题&#xff0c;国外博主…

【asp.net Core MVC + angular6实战】 - 1. 环境搭建

为什么打算写这些文章&#xff1f;没有为什么&#xff0c;只是为了学习Angular和更了解.Net Core等技术需要用到的技术&#xff1f;后端使用.Net Core 2.1 EF Core 2.1 Mysql 5.7 Identity &#xff08;不知道Identity算不算一个独立的技术点&#xff09;前端主要使用的是An…

微软宣布公开预览Dev Spaces for AKS

微软宣布公开预览面向Azure Kubernetes Services&#xff08;AKS&#xff09;的Dev Spaces&#xff0c;为团队提供了一种快速的Kubernetes迭代开发体验。该版本是继5月份Build 2018大会上Dev Spaces内部预览之后的版本。借助这个版本&#xff0c;微软希望为开发人员提供一种在A…

解析Visual C# 7.2中的private protected访问修饰符

去年12月份&#xff0c;随着Visual Studio 2017 Update 15.5的发布&#xff0c;Visual C#迎来了它的最新版本&#xff1a;7.2. 在这个版本中&#xff0c;有个让人难以理解的新特性&#xff0c;就是private protected访问修饰符&#xff08;Access Modifier&#xff09;。至此&a…

【Floyed】廉价最短路径

廉价最短路径 题目大意&#xff1a; 一个图中&#xff0c;在满足最短路的前提下&#xff0c;求最小代价 原题&#xff1a; 题目描述 图是由一组顶点和一组边组成的。一条边连接两个顶点。例如&#xff0c;图1表示了一个有4个顶点V、5条边的图。图中&#xff0c;每条边e是有…

Net Core集成Exceptionless分布式日志功能以及全局异常过滤

相信很多朋友都看过我的上篇关于Exceptionless的简单入门教程[asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程][https://www.cnblogs.com/yilezhu/p/9193723.html] 上篇文章只是简单的介绍了Exceptionless是什么&#xff1f;能做什么呢…

AspNetCore微服务下的网关-Kong(一)

Kong是Mashape开源的高性能高可用API网关和API服务管理层。它基于OpenResty&#xff0c;进行API管理&#xff0c;并提供了插件实现API的AOP。Kong在Mashape 管理了超过15,000 个API&#xff0c;为200,000开发者提供了每月数十亿的请求支持。本文将从架构、API管理、插件三个层面…

拥抱开源, Office 365开发迎来新时代

这个话题我曾经写过文章&#xff0c;也在一些场合做过专题分享。今天换一种方式&#xff0c;你可以直接点击下面这个小程序&#xff0c;用十分钟左右的时间&#xff0c;听我再讲一讲吧。你需要在微信里面才能看到下面的小程序链接&#xff0c;并且可以直接点击你可以打开该文档…

【模拟】俄罗斯方块

俄罗斯方块 题目大意&#xff1a; 在俄罗斯方块中&#xff0c;放一块方块进一个图中&#xff0c;问刚好和上的可能性有多少种 原题&#xff1a; 题目描述 相信大家都玩过“俄罗斯方块”游戏吧&#xff0c;“俄罗斯方块”是一个有趣的电脑小游戏&#xff0c;现有一个有C列、…

实体类的动态生成(一)

前言在应用开发中&#xff0c;通常都会涉及各种 POJO/POCO 实体类&#xff08;DO, DTO, BO, VO&#xff09;的编写&#xff0c;有时这些实体类还需要实现 INotifyPropertyChanged 接口以支持属性变更通知&#xff0c;一般我们都会手写这些代码或者通过工具根据数据库表定义抑或…

实体类的动态生成(二)

前言实体类的动态生成&#xff08;一&#xff09;由于采用字典的方式来保存属性变更值的底层设计思想&#xff0c;导致了性能问题&#xff0c;虽然.NET的字典实现已经很高效了&#xff0c;但相对于直接读写字段的方式而言依然有巨大的性能差距&#xff0c;同时也会导致对属性的…

ASP.NET Core URL Rewrite中间件

URL重写是基于一个或多个预置规则修改请求URL的行为。URL重写在资源位置和访问地址之间创建了一种抽象&#xff0c;这样二者之间就减少了紧密的联系。URL重写有多种适用的场景&#xff1a;临时或永久移动或替换服务器资源&#xff0c;同时为这些资源保持稳定的访问为不同应用程…

打击犯罪【并查集】

打击犯罪 题目大意&#xff1a; 有n个人&#xff0c;相互之间有一些关系&#xff0c;从而形成一个图&#xff0c;现在要从1……n1……n1……n按顺序去掉k个人&#xff08;即去掉1……k1……k1……k&#xff09;&#xff0c;使最大的子图的点数 <n/2<n/2<n/2&#xf…

StackExchange.Redis性能调优

编者&#xff1a;.net core redis 驱动推荐&#xff0c;为什么不使用 StackExchange.Redis 引起了很大的反响&#xff0c;大家反应过度&#xff0c;其实StackExchange.Redis 2.0已经从重构了异步队列&#xff0c;使用管道方式解决异步慢的问题。这篇文章也可以帮助大家正确认识…

用HttpClientFactory来实现简单的熔断降级

前言在2.1之后&#xff0c;有不少新东西&#xff0c;其中HttpClientFactory算是一个。HttpClientFactory涉及的东西也不算少&#xff0c;三四种clients , 请求中间件&#xff0c;与Polly的结合&#xff0c;生命周期等。Steeltoe的组件升级到2.1后&#xff0c;不少示例代码已经使…

【前缀和】【DP】登机(jzoj 5535)

登机 jzoj 5535 题目大意&#xff1a; 有一架飞机&#xff0c;有n个人要登机&#xff0c;每个人的不满值为登机时当前机舱在他所在行前方的人数总和&#xff0c;现在可以把飞机分为k个机舱&#xff0c;使不满值总和最小 原题&#xff1a; 题目描述 小H是机场登机的执行经…

基于.net standard 的动态编译实现

在前文[基于.net core 微服务的另类实现]结尾处&#xff0c;提到了如何方便自动的生成微服务的客户端代理&#xff0c;使对于调用方透明&#xff0c;同时将枯燥的东西使用框架集成&#xff0c;以提高使用便捷性。在尝试了基于 Emit 中间语言后&#xff0c;最终决定使用生成代码…