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

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

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

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

分布式邮件系统说明

其实由上图可以知晓这里我主要采用了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 <= 0) { 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 > 0);

if (values.Count() > 0)

{

moResponse.Msg = values.First().Errors.First().ErrorMessage;

return;

}

}

#endregion

moResponse.Status = 1;

}

catch (Exception ex)

{

moResponse.Msg = "O No请求信息错误";

}

finally

{

if (moResponse.Status == 0) { 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 = 587,

string userName = "841202396@qq.com", string userPwd = "123123")

{

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 = 0;

#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 = 0)

{

var db = this.GetDb();

if (expireMinutes > 0)

{

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 = 0)

{

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 0;

}

/// <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,这样来生成跨平台包

原文地址:http://www.cnblogs.com/wangrudong003/p/6898386.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

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

  1. 分布式爬虫系统设计、实现与实战:爬取京东、苏宁易购全网手机商品数据+MySQL、HBase存储

    1 概述 在不用爬虫框架的情况,经过多方学习,尝试实现了一个分布式爬虫系统,并且可以将数据保存到不同地方,类似MySQL.HBase等. 基于面向接口的编码思想来开发,因此这个系统具有一定的扩展性,有 ...

  2. hadoop搭建伪分布式集群(centos7+hadoop-3.1.1)

    原文地址:https://www.cnblogs.com/zhengna/p/9316424.html Hadoop三种安装模式 搭建伪分布式集群准备条件 第一部分 安装前部署 1.查看虚拟机版本 2 ...

  3. 金笛邮件分布式邮件设置指南

    分布式邮件设置指南<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> ...

  4. Zookeeper集群搭建伪分布式

    集群搭建 伪分布式,一台物理机 启动三个不同端口 搭建步骤 复制文件 修改配置文件 启动节点 测试连接 第一步 复制文件 将Zookeeper复制两份 第二步 修改配置文件 第一个节点 添加配置 保存 ...

  5. 8.Hadoop的学习(Hadoop的配置--搭建完全分布式)

    搭建伪分布式请查看本博客https://blog.csdn.net/wei18791957243   里面的三个搭建伪分布式的博客 前提是伪分布式可以正常启动. 点开虚拟机上边的菜单栏中的虚拟机内的快 ...

  6. nginx搭建tomcat分布式集群

    nginx搭建tomcat分布式集群 1. nginx使用80端口,所有对服务器80端口的访问都被nginx拦截.例: 127.0.0.1 www.jthinking.com 127.0.0.1 ad ...

  7. Storm环境搭建(分布式集群)

    作为流计算的开篇,笔者首先给出storm的安装和部署,storm的第二篇,笔者将详细的介绍storm的工作原理.下边直接上干货,跟笔者的步伐一块儿安装storm. 原文链接:Storm环境搭建(分布式 ...

  8. 搭建FastDFS分布式文件存储系统教程

    转载来源:https://github.com/happyfish100/fastdfs/wiki 搭建FastDFS分布式文件存储系统教程 环境准备 使用的系统软件 名称 说明 centos 7.x ...

  9. 搭建ELK日志分析平台(上)—— ELK介绍及搭建 Elasticsearch 分布式集群

    笔记内容:搭建ELK日志分析平台(上)-- ELK介绍及搭建 Elasticsearch 分布式集群 笔记日期:2018-03-02 27.1 ELK介绍 27.2 ELK安装准备工作 27.3 安装 ...

最新文章

  1. 四因素三水平正交试验表_案例 | 螺栓装配失效试验研究
  2. USTC小道消息20220119
  3. linux 火狐无法执行二进制文件_尝试在Linux上运行Shell脚本时“无法执行二进制文件”...
  4. mvc可以运行在linux下吗,asp.netmvc部署到linux(centos)
  5. js将一维数组分割成每三个一组的算法
  6. node.js的C++入门
  7. java编程思想读后感
  8. 蜂考数据结构c语言版答案
  9. 中行网银安全控件 v1.0 官方版
  10. 对三款软件的测评、分析和建议
  11. 国内好的破解软件下载站
  12. SoftICE使用(3)—在VMware中配置远程SoftICE的另一种办法 zz xfocus
  13. VTK:输出将样条拟合到刀具Cutter用法实战
  14. 2020-09-22
  15. 查询表空间建立表空间和删除表空间
  16. 2014九月十月百度,迅雷,华为,阿里巴巴,最新校招笔试面试题
  17. QCharts QValueAxis使用
  18. 脉冲触发器(JK触发器)
  19. 【因果学习】因果推断分析
  20. 麦当劳宣布20年来最大规模收购 提高服务智能化

热门文章

  1. 易成新能加码光伏产业链 作价28.29亿收购赛维两子公司
  2. window 效率神器:Wox
  3. “”和“” java
  4. EFDC水模型 初学者入门 及软件下载学习指导
  5. 第二十五章补充内容 5 不能为0的变量
  6. 【Silverlight5矢量打印】如何用C#代码检测打印机和驱动是否支持PostScript
  7. zabbix2.0安装与配置
  8. 分享一个CSS3的网格系统架构 - ResponsiveAeon
  9. 一个检查SPN的小工具
  10. Npgsql 6.0.2 发布,赶紧升级!!!