.Net Core应用搭建的分布式邮件系统设计

时间:2023-01-21 09:09:05

本篇分享的是由NetCore搭建的分布式邮件系统,主要采用NetCore的Api控制台应用程序,由于此系统属于公司的所以这里只能分享设计图和一些单纯不设计业务的类或方法;

为什么要在公司中首例采用NetCore做开发

为什么要在公司中首例采用NetCore做开发,有些netcoreapi不是还不全面么,您都敢尝试?恐怕会有人这样问我,我只能告诉你NetCore现在出2.0版本了,很多Framwork的常用封装都已经有了,况且她主打的是MVC模式,能够高效的开发系统,也有很多Core的Nuget包支持了,已经到达了几乎可以放心大胆使用的地步,退一万不说有些东西不支持那这又如何,可以采用接口的方式从其他地方对接过来也是一种不错的处理方案。为了让C#这门优秀的语言被广泛应用,默默努力着。

目前我写的NetCore方面的文章

AspNetCore - MVC实战系列目录

.NetCore上传多文件的几种示例

开源一个跨平台运行的服务插件 - TaskCore.MainForm

NET Core-学习笔记

Asp.NetCore1.1版本没了project.json,这样来生成跨平台包

正片环节 - 分布式邮件系统设计图

.Net Core应用搭建的分布式邮件系统设计

分布式邮件系统说明

其实由上图可以知晓这里我主要采用了Api+服务的模式,这也是现在互联网公司经常采用的一种搭配默认;利用api接受请求插入待发送邮件队列和入库,然后通过部署多个NetCore跨平台服务(这里服务指的是:控制台应用)来做分布式处理操作,跨平台服务主要操作有:

. 邮件发送

. 邮件发送状态的通知(如果需要通知子业务,那么需要通知业务方邮件发送的状态)

. 通知失败处理(自动往绑定的责任人发送一封邮件)

. 填充队列(如果待发邮件队列或者通知队列数据不完整,需要修复队列数据)

Api接口的统一验证入口

这里我用最简单的方式,继承Controller封装了一个父级的BaseController,来让各个api的Controller基础统一来做身份验证;来看看重写 public override void OnActionExecuting(ActionExecutingContext context) 的验证代码:

 public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context); var moResponse = new MoBaseRp();
try
{ #region 安全性验证 var key = "request";
if (!context.ActionArguments.ContainsKey(key)) { moResponse.Msg = "请求方式不正确"; return; }
var request = context.ActionArguments[key];
var baseRq = request as MoBaseRq;
//暂时不验证登录账号密码
if (string.IsNullOrWhiteSpace(baseRq.UserName) || string.IsNullOrWhiteSpace(baseRq.UserPwd)) { moResponse.Msg = "登录账号或密码不能为空"; return; }
else if (baseRq.AccId <= ) { moResponse.Msg = "发送者Id无效"; return; }
else if (string.IsNullOrWhiteSpace(baseRq.FuncName)) { moResponse.Msg = "业务方法名不正确"; return; } //token验证
var strToken = PublicClass._Md5($"{baseRq.UserName}{baseRq.AccId}", "");
if (!strToken.Equals(baseRq.Token, StringComparison.OrdinalIgnoreCase)) { moResponse.Msg = "Token验证失败"; return; } //验证发送者Id
if (string.IsNullOrWhiteSpace(baseRq.Ip))
{
var account = _db.EmailAccount.SingleOrDefault(b => b.Id == baseRq.AccId);
if (account == null) { moResponse.Msg = "发送者Id无效。"; return; }
else
{
if (account.Status != (int)EnumHelper.EmStatus.启用)
{
moResponse.Msg = "发送者Id已禁用"; return;
} //验证ip
var ipArr = account.AllowIps.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
//当前请求的Ip
var nowIp = this.GetUserIp();
baseRq.Ip = nowIp;
//默认*为所有ip , 匹配ip
if (!ipArr.Any(b => b.Equals("*")) && !ipArr.Any(b => b.Equals(nowIp)))
{
moResponse.Msg = "请求IP为授权"; return;
}
}
}
else
{
var account = _db.EmailAccount.SingleOrDefault(b => b.Id == baseRq.AccId && b.AllowIps.Any(bb => bb.Equals(baseRq.Ip)));
if (account == null) { moResponse.Msg = "发送者未授权"; return; }
else if (account.Status != (int)EnumHelper.EmStatus.启用)
{
moResponse.Msg = "发送者Id已禁用"; return;
}
} //内容非空,格式验证
if (!context.ModelState.IsValid)
{
var values = context.ModelState.Values.Where(b => b.Errors.Count > );
if (values.Count() > )
{
moResponse.Msg = values.First().Errors.First().ErrorMessage;
return;
}
} #endregion moResponse.Status = ;
}
catch (Exception ex)
{
moResponse.Msg = "O No请求信息错误";
}
finally
{
if (moResponse.Status == ) { context.Result = Json(moResponse); }
}
}

邮件请求父类实体:

 /// <summary>
/// 邮件请求父类
/// </summary>
public class MoBaseRq
{ public string UserName { get; set; } public string UserPwd { get; set; } /// <summary>
/// 验证token(Md5(账号+配置发送者账号信息的Id+Ip)) 必填
/// </summary>
public string Token { get; set; } /// <summary>
/// 配置发送者账号信息的Id 必填
/// </summary>
public int AccId { get; set; } /// <summary>
/// 业务方法名称
/// </summary>
public string FuncName { get; set; } /// <summary>
/// 请求者Ip,如果客户端没赋值,默认服务端获取
/// </summary>
public string Ip { get; set; } }

第三方Nuget包的便利

此邮件系统使用到了第三方包,这也能够看出有很多朋友正为开源,便利,NetCore的推广努力着;

首先看看MailKit(邮件发送)包,通过安装下载命令: Install-Package MailKit 能够下载最新包,然后你不需要做太花哨的分装,只需要正对于邮件发送的服务器,端口,账号,密码做一些设置基本就行了,如果可以您可以直接使用我的代码:

 /// <summary>
/// 发送邮件
/// </summary>
/// <param name="dicToEmail"></param>
/// <param name="title"></param>
/// <param name="content"></param>
/// <param name="name"></param>
/// <param name="fromEmail"></param>
/// <returns></returns>
public static bool _SendEmail(
Dictionary<string, string> dicToEmail,
string title, string content,
string name = "爱留图网", string fromEmail = "841202396@qq.com",
string host = "smtp.qq.com", int port = ,
string userName = "841202396@qq.com", string userPwd = "")
{
var isOk = false;
try
{
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content)) { return isOk; } //设置基本信息
var message = new MimeMessage();
message.From.Add(new MailboxAddress(name, fromEmail));
foreach (var item in dicToEmail.Keys)
{
message.To.Add(new MailboxAddress(item, dicToEmail[item]));
}
message.Subject = title;
message.Body = new TextPart("html")
{
Text = content
}; //链接发送
using (var client = new SmtpClient())
{
// For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS)
client.ServerCertificateValidationCallback = (s, c, h, e) => true; //采用qq邮箱服务器发送邮件
client.Connect(host, port, false); // Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2"); //qq邮箱,密码(安全设置短信获取后的密码) ufiaszkkulbabejh
client.Authenticate(userName, userPwd); client.Send(message);
client.Disconnect(true);
}
isOk = true;
}
catch (Exception ex)
{ }
return isOk;
}

Redis方面的操作包StackExchange.Redis,现在NetCore支持很多数据库驱动(例如:Sqlserver,mysql,postgressql,db2等)这么用可以参考下这篇文章AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型,不仅如此还支持很多缓存服务(如:Memorycach,Redis),这里讲到的就是Redis,我利用Redis的list的队列特性来做分布式任务存储,尽管目前我用到的只有一个主Redis服务还没有业务场景需要用到主从复制等功能;这里分享的代码是基于StackExchange.Redis基础上封装对于string,list的操作:

   public class StackRedis : IDisposable
{
#region 配置属性 基于 StackExchange.Redis 封装
//连接串 (注:IP:端口,属性=,属性=)
public string _ConnectionString = "127.0.0.1:6377,password=shenniubuxing3";
//操作的库(注:默认0库)
public int _Db = ;
#endregion #region 管理器对象 /// <summary>
/// 获取redis操作类对象
/// </summary>
private static StackRedis _StackRedis;
private static object _locker_StackRedis = new object();
public static StackRedis Current
{
get
{
if (_StackRedis == null)
{
lock (_locker_StackRedis)
{
_StackRedis = _StackRedis ?? new StackRedis();
return _StackRedis;
}
} return _StackRedis;
}
} /// <summary>
/// 获取并发链接管理器对象
/// </summary>
private static ConnectionMultiplexer _redis;
private static object _locker = new object();
public ConnectionMultiplexer Manager
{
get
{
if (_redis == null)
{
lock (_locker)
{
_redis = _redis ?? GetManager(this._ConnectionString);
return _redis;
}
} return _redis;
}
} /// <summary>
/// 获取链接管理器
/// </summary>
/// <param name="connectionString"></param>
/// <returns></returns>
public ConnectionMultiplexer GetManager(string connectionString)
{
return ConnectionMultiplexer.Connect(connectionString);
} /// <summary>
/// 获取操作数据库对象
/// </summary>
/// <returns></returns>
public IDatabase GetDb()
{
return Manager.GetDatabase(_Db);
}
#endregion #region 操作方法 #region string 操作 /// <summary>
/// 根据Key移除
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task<bool> Remove(string key)
{
var db = this.GetDb(); return await db.KeyDeleteAsync(key);
} /// <summary>
/// 根据key获取string结果
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task<string> Get(string key)
{
var db = this.GetDb();
return await db.StringGetAsync(key);
} /// <summary>
/// 根据key获取string中的对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> Get<T>(string key)
{
var t = default(T);
try
{
var _str = await this.Get(key);
if (string.IsNullOrWhiteSpace(_str)) { return t; } t = JsonConvert.DeserializeObject<T>(_str);
}
catch (Exception ex) { }
return t;
} /// <summary>
/// 存储string数据
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expireMinutes"></param>
/// <returns></returns>
public async Task<bool> Set(string key, string value, int expireMinutes = )
{
var db = this.GetDb();
if (expireMinutes > )
{
return db.StringSet(key, value, TimeSpan.FromMinutes(expireMinutes));
}
return await db.StringSetAsync(key, value);
} /// <summary>
/// 存储对象数据到string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expireMinutes"></param>
/// <returns></returns>
public async Task<bool> Set<T>(string key, T value, int expireMinutes = )
{
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var _str = JsonConvert.SerializeObject(value, jsonOption);
if (string.IsNullOrWhiteSpace(_str)) { return false; } return await this.Set(key, _str, expireMinutes);
}
catch (Exception ex) { }
return false;
}
#endregion #region List操作(注:可以当做队列使用) /// <summary>
/// list长度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<long> GetListLen<T>(string key)
{
try
{
var db = this.GetDb();
return await db.ListLengthAsync(key);
}
catch (Exception ex) { }
return ;
} /// <summary>
/// 获取队列出口数据并移除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> GetListAndPop<T>(string key)
{
var t = default(T);
try
{
var db = this.GetDb();
var _str = await db.ListRightPopAsync(key);
if (string.IsNullOrWhiteSpace(_str)) { return t; }
t = JsonConvert.DeserializeObject<T>(_str);
}
catch (Exception ex) { }
return t;
} /// <summary>
/// 集合对象添加到list左边
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="values"></param>
/// <returns></returns>
public async Task<long> SetLists<T>(string key, List<T> values)
{
var result = 0L;
try
{
var jsonOption = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var db = this.GetDb();
foreach (var item in values)
{
var _str = JsonConvert.SerializeObject(item, jsonOption);
result += await db.ListLeftPushAsync(key, _str);
}
return result;
}
catch (Exception ex) { }
return result;
} /// <summary>
/// 单个对象添加到list左边
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task<long> SetList<T>(string key, T value)
{
var result = 0L;
try
{
result = await this.SetLists(key, new List<T> { value });
}
catch (Exception ex) { }
return result;
} #endregion #region 额外扩展 /// <summary>
/// 手动回收管理器对象
/// </summary>
public void Dispose()
{
this.Dispose(_redis);
} public void Dispose(ConnectionMultiplexer con)
{
if (con != null)
{
con.Close();
con.Dispose();
}
} #endregion #endregion
}

用到Redis的那些操作就添加哪些就行了,也不用太花哨能用就行;

如何生成跨平台的api服务和应用程序服务

这小节的内容最重要,由于之前有相关的文章,这里就不用再赘述了,来这里看看:Asp.NetCore1.1版本没了project.json,这样来生成跨平台包

.Net Core应用搭建的分布式邮件系统设计的更多相关文章

  1. Hadoop HDFS分布式文件系统设计要点与架构

      Hadoop HDFS分布式文件系统设计要点与架构     Hadoop简介:一个分布式系统基础架构,由Apache基金会开发.用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群 ...

  2. hadoop搭建伪分布式集群(centos7&plus;hadoop-3&period;1&period;0&sol;2&period;7&period;7)

    目录: Hadoop三种安装模式 搭建伪分布式集群准备条件 第一部分 安装前部署 1.查看虚拟机版本2.查看IP地址3.修改主机名为hadoop4.修改 /etc/hosts5.关闭防火墙6.关闭SE ...

  3. ASP&period;NET Core 使用 Redis 实现分布式缓存:Docker、IDistributedCache、StackExchangeRedis

    ASP.NET Core 使用 Redis 实现分布式缓存:Docker.IDistributedCache.StackExchangeRedis 前提:一台 Linux 服务器.已安装 Docker ...

  4. &period;NetCore快速搭建ELK分布式日志中心

    懒人必备:.NetCore快速搭建ELK分布式日志中心   该篇内容由个人博客点击跳转同步更新!转载请注明出处! 前言 ELK是什么 它是一个分布式日志解决方案,是Logstash.Elastaics ...

  5. Linux学习之十--&period;Net Core环境搭建以及Nginx的搭建

    一.Centos7下.Net Core 环境安装: 链接:https://www.microsoft.com/net/core#linuxcentos 按照步骤来: yum install libun ...

  6. Apache James搭建内网邮件服务器

    Apache James搭建内网邮件服务器 极客521 | 极客521 2014-08-21 148 阅读 java 大概之前两个礼拜的日子,讨论会介绍了关于了.net内网邮件服务器的搭建.所以自己也 ...

  7. 超快速使用docker在本地搭建hadoop分布式集群

    超快速使用docker在本地搭建hadoop分布式集群 超快速使用docker在本地搭建hadoop分布式集群 学习hadoop集群环境搭建是hadoop入门的必经之路.搭建分布式集群通常有两个办法: ...

  8. NET Core 环境搭建和命令行CLI入门

    NET Core 环境搭建和命令行CLI入门 2016年6月27日.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布,社区里涌现了很多文章,我也计划写个系列文 ...

  9. hadoop(二)搭建伪分布式集群

    前言 前面只是大概介绍了一下Hadoop,现在就开始搭建集群了.我们下尝试一下搭建一个最简单的集群.之后为什么要这样搭建会慢慢的分享,先要看一下效果吧! 一.Hadoop的三种运行模式(启动模式) 1 ...

随机推荐

  1. javascript-模板方法模式-提示框归一化插件

    模板方法模式笔记   父类中定义一组算法操作骨架,而将一些实现步骤延迟到子类中,使得子类可以不改变父类的算法结构的同时可重新定义算法中某些实现步骤   实例:弹出框归一化插件 css样式 ;width ...

  2. CSS3 Animation Cheat Sheet:实用的 CSS3 动画库

    CSS3 Animation Cheat Sheet 是一组预设的动画库,为您的 Web 项目添加各种很炫的动画.所有你需要做的是添加样式表到你的网站,为你想要添加动画效果的元素应用预制的 CSS 类 ...

  3. sencha做个简单的登录界面

    很多人都在群里问要一个好看的登录界面,我表示很无奈,哪有好看的,每个人的要求不一样,要好看的只有自己做. 下面是我自己整理的一个通用版的登录界面,稍做修改,很容易能变成你想要的界面, 不说废话,直接上 ...

  4. &lbrack;LeetCode&rsqb;题解(python):147-Insertion Sort List

    题目来源: https://leetcode.com/problems/insertion-sort-list/ 题意分析: 用插入排序排序一个链表. 题目思路: 这题没什么好说的,直接用插入排序就行 ...

  5. Cesium 中由 Logarithmic Depth Buffer 引起的模型显示不完整的问题

    当 Cesium 单个模型过长时,会遇到某些视角模型显示不完整的问题,如下图所示: 经过在官方论坛上询问,该问题由 viewer.scene.logarithmicDepthBuffer 开启造成,关 ...

  6. 使用openlayers 3 在线加载天地图及GeoServer发布的地图

    使用openlayers3来加载天地图卫星图和标注图层,GeoServer发布地图,一并用openlayers测试加载出来,顺便实现了7种地图控件.下面直接贴代码: <!DOCTYPE html ...

  7. git 查看提交的信息diff

    git log --stat git show <hashcode> <filename> git log --pretty=oneline <filename> ...

  8. canvas移动端常用技巧图片loading

    核心知识点:drawImage 作用:将图片加载在canvas html: <canvas id="myCanvas" width="200" heigh ...

  9. Java-JUC(十):线程按序交替执行

    问题: 有a.b.c三个线程,使得它们按照abc依次执行10次. 实现: package com.dx.juc.test; import java.util.concurrent.locks.Cond ...

  10. Android之ConnectivityManager

    在android平台中ConnectivityManager主要负责查询网络连接状态以及在连接状态有变化的时候发出通知.其主要的功能职责如下: 1.  监视网络状态,包括(Wi-Fi.GPRS.UMT ...