.Net Core应用搭建的分布式邮件系统设计
本篇分享的是由NetCore搭建的分布式邮件系统,主要采用NetCore的Api和控制台应用程序,由于此系统属于公司的所以这里只能分享设计图和一些单纯不设计业务的类或方法;
为什么要在公司中首例采用NetCore做开发
为什么要在公司中首例采用NetCore做开发,有些netcoreapi不是还不全面么,您都敢尝试?恐怕会有人这样问我,我只能告诉你NetCore现在出2.0版本了,很多Framwork的常用封装都已经有了,况且她主打的是MVC模式,能够高效的开发系统,也有很多Core的Nuget包支持了,已经到达了几乎可以放心大胆使用的地步,退一万不说有些东西不支持那这又如何,可以采用接口的方式从其他地方对接过来也是一种不错的处理方案。为了让C#这门优秀的语言被广泛应用,默默努力着。
目前我写的NetCore方面的文章
开源一个跨平台运行的服务插件 - TaskCore.MainForm
Asp.NetCore1.1版本没了project.json,这样来生成跨平台包
正片环节 - 分布式邮件系统设计图
分布式邮件系统说明
其实由上图可以知晓这里我主要采用了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应用搭建的分布式邮件系统设计的更多相关文章
- Hadoop HDFS分布式文件系统设计要点与架构
Hadoop HDFS分布式文件系统设计要点与架构 Hadoop简介:一个分布式系统基础架构,由Apache基金会开发.用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群 ...
- hadoop搭建伪分布式集群(centos7+hadoop-3.1.0/2.7.7)
目录: Hadoop三种安装模式 搭建伪分布式集群准备条件 第一部分 安装前部署 1.查看虚拟机版本2.查看IP地址3.修改主机名为hadoop4.修改 /etc/hosts5.关闭防火墙6.关闭SE ...
- ASP.NET Core 使用 Redis 实现分布式缓存:Docker、IDistributedCache、StackExchangeRedis
ASP.NET Core 使用 Redis 实现分布式缓存:Docker.IDistributedCache.StackExchangeRedis 前提:一台 Linux 服务器.已安装 Docker ...
- .NetCore快速搭建ELK分布式日志中心
懒人必备:.NetCore快速搭建ELK分布式日志中心 该篇内容由个人博客点击跳转同步更新!转载请注明出处! 前言 ELK是什么 它是一个分布式日志解决方案,是Logstash.Elastaics ...
- Linux学习之十--.Net Core环境搭建以及Nginx的搭建
一.Centos7下.Net Core 环境安装: 链接:https://www.microsoft.com/net/core#linuxcentos 按照步骤来: yum install libun ...
- Apache James搭建内网邮件服务器
Apache James搭建内网邮件服务器 极客521 | 极客521 2014-08-21 148 阅读 java 大概之前两个礼拜的日子,讨论会介绍了关于了.net内网邮件服务器的搭建.所以自己也 ...
- 超快速使用docker在本地搭建hadoop分布式集群
超快速使用docker在本地搭建hadoop分布式集群 超快速使用docker在本地搭建hadoop分布式集群 学习hadoop集群环境搭建是hadoop入门的必经之路.搭建分布式集群通常有两个办法: ...
- NET Core 环境搭建和命令行CLI入门
NET Core 环境搭建和命令行CLI入门 2016年6月27日.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布,社区里涌现了很多文章,我也计划写个系列文 ...
- hadoop(二)搭建伪分布式集群
前言 前面只是大概介绍了一下Hadoop,现在就开始搭建集群了.我们下尝试一下搭建一个最简单的集群.之后为什么要这样搭建会慢慢的分享,先要看一下效果吧! 一.Hadoop的三种运行模式(启动模式) 1 ...
随机推荐
- ASP.NET Core 网站发布到Linux服务器
长期以来,使用.NET开发的应用只能运行在Windows平台上面,而目前国内蓬勃发展的互联网公司由于成本的考虑,大量使用免费的Linux平台,这就使得.NET空有一身绝技但无法得到广大的施展空间,.N ...
- python 自动化接口测试(6)
迎接新的一波更新吧,这次是基于图灵机器人的一个api接口的测试. 这是api的接口:http://www.tuling123.com/openapi/api 我们试着通过浏览器直接访问看下 这是反馈的 ...
- 数据库习题(oracle)
学生表 Student 字段值分别是 Sid ,Sname ,Sage ,Ssex 教师表 Teacher 字段值分别是 Tid ,Tname 课程表 Course 字段值分别是Cid ,Cname ...
- JavaScript 简易版 自动轮播 手动轮播 菜鸟交流
本人刚刚接触前端,许多知识还不了解,以前经常到博客园查询自己需要的东西,现在也终于反客为主了.作为新手,所展示的东西也是浅显易懂,希望同是新手的伙伴们共同交流.共同进步,若是成功捕获一位大大,也请您赐 ...
- 学习MVC之租房网站(二)-框架搭建及准备工作
在上一篇<学习MVC之租房网站(一)-项目概况>中,确定了UI+Service的“双层”架构,并据此建立了项目 接下来要编写Common类库.配置AdminWeb和FrontWeb 一.编 ...
- STAR法则的感想
STAR法则百度百科上被解释为,面试官用于收集面试者信息的工具,而我个人理解,它更像是一个表达技巧,叙述结构,我们先来看看什么是STAR法则: STAR法则,即为Situation Task Acti ...
- appium+python做移动端自动化测试
1 导言 1.1 编制目的 该文档为选用Appium作为移动设备原生(Native).混合(Hybrid).移动Web(Mobile Web)应用UI自动化测试的相关自动化测试人员.开发人员等提供 ...
- CSAcademy Beta Round #4 Swap Pairing
题目链接:https://csacademy.com/contest/arhiva/#task/swap_pairing/ 大意是给2*n个包含n种数字,每种数字出现恰好2次的数列,每一步操作可以交换 ...
- Java中双向链表的代码实现
写在前面: 双向链表是一种对称结构,它克服了单链表上指针单向性的缺点,其中每一个节点即可向前引用,也可向后引用,这样可以更方便的插入.删除数据元素. 由于双向链表需要同时维护两个方向的指针,因此添加节 ...
- phpmyadmin 免登陆
第一步: 打开 phpmyadmin/libraries/plugins/auth/AuthenticationCookie.class.php 找到 authCheck 和 authSetUser ...