- 目标
- 创建WebApi
- 一.创建数据库
- 1.数据表构成
- 2.SQL脚本
- 二.链接EFCore并进行依赖注入
- 三.代码解释
- 1.重写控制器基类
- 2.创建控制器
- 3.返回值Models类
- 4.Config
- (1).Config.cs
- (2).Config的模型类
- (3).platform.json
- 5.外部WebApi的调用
- (1).ExternWebApiBase.cs
- (2).Address类
- 6.MailPlatform
- (1).MailBuilder
- (2).MailPlatform
- 7.全局引用(NET6.0)
- 三.需要导入的包
- 项目文件下载
- EMail-API服务
创建WebApi 一.创建数据库 1.数据表构成通过SMTP向请求邮箱发信,发信邮箱由API建立者创建,并设置权限
TB_RequestLog
| 列名 | 类型 | 特征 | 备注 |
|---|---|---|---|
| Index | int | 主键 自动增长 非空 | 编号 |
| Region | nchar(32) | 非空 | 控制器名称 |
| Action | nchar(32) | 非空 | 函数名称 |
| Arguments | varchar(max) | 非空 | 参数集 |
| IP | nchar(16) | 请求者IP | |
| Address | nchar(32) | 请求者地理位置 | |
| Identity | nchar(11) | 请求者身份验证 | |
| LogTime | datetime | 非空 | 记录时间 |
TB_Mail
| 列名 | 类型 | 特征 | 备注 |
|---|---|---|---|
| Guid | nchar(16) | 非空 主键 | 唯一UID |
| Address | nchar(32) | 非空 | 目前邮箱 |
| Title | nvarchar(max) | 非空 | 标题 |
| Body | nvarchar(max) | 非空 | 主体 |
| Allocated | nchar(32) | 非空 | 分配的主体号码 |
| SendTime | datetimt | 非空 | 发生时间 |
USE [db_spaceserver] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[TB_Mail]( [Guid] [nchar](16) NOT NULL, [Address] [nchar](32) NOT NULL, [Title] [nvarchar](max) NOT NULL, [Body] [nvarchar](max) NOT NULL, [Allocated] [nchar](32) NOT NULL, [SendTime] [datetime] NOT NULL, CONSTRAINT [PK_TB_Mail] PRIMARY KEY CLUSTERED ( [Guid] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[TB_RequestLog]( [Index] [int] IDENTITY(1,1) NOT NULL, [Region] [nchar](32) NOT NULL, [Action] [nchar](32) NOT NULL, [Arguments] [varchar](max) NULL, [IP] [nchar](16) NULL, [Address] [nchar](32) NULL, [Identity] [nchar](11) NULL, [LogTime] [datetime] NOT NULL, CONSTRAINT [PK_TB_RequestLog] PRIMARY KEY CLUSTERED ( [Index] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO二.链接EFCore并进行依赖注入
过程参照之前连接EFCore的步骤
EFCore的链接生成上下文和模型
完成后进行依赖注入
(笔者使用的是.NET6.0的环境,配置项在Program.cs里面,如果使用之前的版本,配置项在Startup.cs里面)
builder.Services.AddSqlServer(builder.Configuration.GetConnectionString("DB_SpaceServer"));
记着在appsettings.json里面提前配置好链接字符串
然后进行以下配置
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All);
});
builder.Services.AddRouting(options => options.LowercaseUrls = true);
上文第一个配置可以使得Json的序列化不会出现编码错误,第二个可以让URL全小写
三.代码解释 1.重写控制器基类首先我们继承一个控制器基类来代替ControllerBase,因为我们要使用到很多特定的方法,基类化了过后可以为编程提供很多便利
public class ControllerBaseEx : ControllerBase,IDisposable
{
protected Db_SpaceserverContext DBContext { set; get; }
protected string RequestIP
{
get
{
var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (string.IsNullOrEmpty(ip))
{
ip = HttpContext.Connection.RemoteIpAddress.ToString();
}
return ip;
}
}
protected Config Config { get; } = new Config();
protected List WaitTasks = new List();
protected ControllerBaseEx(Db_SpaceserverContext context)
{
DBContext = context;
}
void IDisposable.Dispose()
{
//等待所有Task执行完毕
foreach (var c in WaitTasks) c.Wait();
}
protected void Log(object args,string identity = "")
{
Address address = new();
string region = (ControllerContext.RouteData.Values["controller"] ?? "NULL").ToString();
string action = (ControllerContext.RouteData.Values["action"] ?? "NULL").ToString();
var log = new TbRequestLog()
{
Action = action,
Region = region,
Ip = RequestIP.Length < 5 ? "" : RequestIP,
Address = RequestIP.Length < 5 ? "" : address.GetAddress(RequestIP),
LogTime = DateTime.Now,
Identity = identity,
Arguments = JsonConvert.SerializeObject(args)
};
DBContext.TbRequestLog.Add(log);
DBContext.SaveChanges();
}
protected void Log(string args, string identity = "")
{
Address address = new();
string region = (ControllerContext.RouteData.Values["controller"] ?? "NULL").ToString();
string action = (ControllerContext.RouteData.Values["action"] ?? "NULL").ToString();
var log = new TbRequestLog()
{
Action = action,
Region = region,
Ip = RequestIP.Length < 5 ? "" : RequestIP,
Address = RequestIP.Length < 5 ? "" : address.GetAddress(RequestIP),
LogTime = DateTime.Now,
Identity = identity,
Arguments = args
};
DBContext.TbRequestLog.Add(log);
DBContext.SaveChanges();
}
protected string JsonString(object args) => JsonConvert.SerializeObject(args);
protected ActionResult Json(object args) => new JsonResult(args);
protected BadRequestObjectResult Error(string result) => BadRequest(new { code = 400, result = result });
protected string Random(int length,string dic = "")
{
dic = dic == "" ? "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM" : dic == "-1" ? "0123456789" : dic;
string data = "";Random rand = new Random();
for(int i= 0; i < length; i++)
{
data += dic[rand.Next(dic.Length)];
}
return data;
}
}
字段值
DBContext EFCore上下文对象,通过继承后使用注入对象初始化
RequestIP 请求IP,实际上是获取表头中的X-Forwarded-For字段,如果本地请求,这个值为’:::’
Config 用于获取其他配置文件
WaitTasks 用于保存所有异步task
函数
protected ControllerBaseEx(Db_SpaceserverContext context)
| 参数名 | 类型 | 说明 |
|---|---|---|
| context | Db_SpaceserverContext | 用于初始化DBContext属性 |
初始化函数
void IDisposable.Dispose()
释放对象时用于等待所有的task对象
protected void Log(object args,string identity = “”)
| 参数名 | 类型 | 说明 |
|---|---|---|
| args | object | 参数object对象 |
| identity | string | 请求者身份验证 |
protected void Log(string args,string identity = “”)
| 参数名 | 类型 | 说明 |
|---|---|---|
| args | string | 序列化后的参数集 |
| identity | string | 请求者身份验证 |
用于记录LOG信息
protected string JsonString(object args)
| 参数名 | 类型 | 说明 |
|---|---|---|
| args | object | 需要转换的数据 |
用于序列化对象
protected ActionResult Json(object args)
| 参数名 | 类型 | 说明 |
|---|---|---|
| args | object | 返回的json object |
用于返回JsonResult
protected BadRequestObjectResult Error(string result)
| 参数名 | 类型 | 说明 |
|---|---|---|
| result | string | 错误原因 |
用于返回BadRequest
protected string Random(int length,string dic = “”)
| 参数名 | 类型 | 说明 |
|---|---|---|
| length | int | 生成长度 |
| dic | string | 生成字典 |
用于生成指定长度的随机序列,如果dic为空,就从全字典选择,为-1,就生成数字集,其他为指定集
2.创建控制器
控制器名称MailCodeController.cs
[Route("api/[controller]/[action]")]
[ApiController]
public class MailCodeController : ControllerBaseEx
{
private ILogger _logger;
private MailPlatformSetting _setting;
public MailCodeController(Db_SpaceserverContext context,ILogger logger) : base(context)
{
_logger = logger;
_setting = Config.GetSetting("platform.json");
}
[HttpGet]
public async Task SendCode(string code,string office,string address)
{
try
{
//写入log
object log = new
{
code,
office,
address
};
Log(JsonString(log), "SYSTEM");
//判断信息
if (code.Length > 10) throw new Exception("超过最大内容长度,10位");
if (office.Length > 16) throw new Exception("超过最大的签名长度,16位");
//判断邮箱是否符合
Regex regex = new(@"^[A-Za-z0-9u4e00-u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$");
if (!regex.IsMatch(address)) throw new Exception("邮箱地址不符合");
//生成GUID与时间
string guid = Random(16);
DateTime time = DateTime.Now;
//创建平台
MailPlatform platform = new(_setting.Platform.ToArray());
//创建信息
MailBuilder builder = new MailBuilder();
builder.Address.Add(address);
builder.Subject = $"来自{office}的验证码";
builder.Body = $"您的验证码为:{code},发送方为:{office},发送时间为:{time}";
//发送邮件
var task = platform.SendMailAsync(builder.Build());
WaitTasks.Add(task);
//写入记录
TbMail mail = new TbMail()
{
Address = address,
Allocated = platform.LastAllocatedMail,
Body = builder.Body,
Title = builder.Subject,
Guid = guid,
SendTime = time,
};
//保存到数据库
DBContext.TbMail.Add(mail);
DBContext.SaveChanges();
//生成返回集
MailCodeModel model = new()
{
Address = address,
Content = $"-H {builder.Subject} -B {builder.Body}",
Guid = guid,
DateTime = time,
};
return Json(model);
}
catch(Exception ex)
{
return Error(ex.Message);
}
}
}
字段值
_logger WebApi自带log对象
_setting MailPlatform的setting对象
函数
public MailCodeController(Db_SpaceserverContext context,ILogger logger)
| 参数名 | 类型 | 说明 |
|---|---|---|
| context | Db_SpaceserverContext | 注入上下文对象 |
| logger | ILogger | 注入log对象 |
初始化函数
[HttpGet]
public async Task SendCode(string code,string office,string address)
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | string | 验证码 |
| office | string | 公司名称 |
| address | string | 目标邮箱 |
验证码发信函数
3.返回值Models类
public class MailCodeModel
{
public string Guid { set; get; }
public string Address { set; get; }
public string Content { set; get; }
public DateTime DateTime { set; get; }
}
字段值
Guid 发信标记唯一Uid
Address 目标邮箱
Content 内容
DateTime 发信时间
4.Config (1).Config.cs
public class Config
{
public T GetSetting(string path)where T:class
{
StreamReader reader = new(path);
var data = reader.ReadToEnd();
reader.Close();
return JsonConvert.DeserializeObject(data);
}
}
函数
public T GetSetting(string path)where T:class
| 参数名 | 类型 | 说明 |
|---|---|---|
| path | string | json文件地址 |
| T | T | 配置文件模型 |
获取配置文件并转为.NET对象
(2).Config的模型类
public class MailPlatformSetting
{
public List Platform { get; set; }
public int Count => Platform.Count;
}
public class EMailSetting
{
public string EMail { get; set; }
public string Key { get; set; }
}
(3).platform.json
{
"Platform": [
{
"EMail": "邮箱",
"Key": "授权码"
},
{
"EMail": "邮箱",
"Key": "授权码"
},
{
"EMail": "邮箱",
"Key": "授权码"
}
]
}
5.外部WebApi的调用
(1).ExternWebApiBase.cs
public class ExternWebApiBase
{
protected string RequestByGet(string baseurl,params string[] args)
{
string url = baseurl + "?";
url += string.Join('&', args);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
HttpResponseMessage response = client.Send(request);
return response.Content.ReadAsStringAsync().Result;
}
protected T RequestByGet(string baseurl, params string[] args)
{
string url = baseurl + "?";
url += string.Join('&', args);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
HttpResponseMessage response = client.Send(request);
return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
}
protected string RequestByPost(string baseurl, object args)
{
string url = baseurl;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
//拼凑json
JsonContent content = JsonContent.Create(args);
request.Content = content;
HttpResponseMessage response = client.Send(request);
return response.Content.ReadAsStringAsync().Result;
}
protected T RequestByPost(string baseurl, object args)
{
string url = baseurl;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
//拼凑json
JsonContent content = JsonContent.Create(args);
request.Content = content;
HttpResponseMessage response = client.Send(request);
return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
}
}
(2).Address类
public class Address: ExternWebApiBase
{
public string GetAddress(string ip)
{
string url = "http://whois.pconline.com.cn/ip.jsp";
string data = RequestByGet(url,
$"ip={ip}",
$"level=3"
);
return data;
}
}
6.MailPlatform
MailPlatform的实现
但是又不同于上方博客,我们要稍微改一下
public class MailBuilder
{
public MailBuilder() { }
public List? Address { set; get; } = new List();
public string? Body { set; get; } = string.Empty;
public Encoding? Encoding { set; get; } = Encoding.UTF8;
public bool? IsHtml { set; get; } = true;
public string? Subject { set; get; } = string.Empty;
public List? Attachments { set; get; } = new List();
public MailMessage Build()
{
var message = new MailMessage();
if (Address?.Count == 0) throw new Exception("发送人不能为空");
foreach (var c in Address)
{
message.To.Add(c);
}
message.Subject = Subject;
message.Body = Body;
message.IsBodyHtml = IsHtml ?? true;
message.BodyEncoding = Encoding;
message.Priority = MailPriority.Normal;
message.SubjectEncoding = Encoding;
foreach(var c in Attachments)
{
if (!File.Exists(c)) throw new Exception("附件文件不存在");
var data = new Attachment(c, MediaTypeNames.Application.Octet);//实例化附件
data.ContentDisposition.CreationDate = File.GetCreationTime(c);
data.ContentDisposition.ModificationDate = File.GetLastAccessTime(c);
data.ContentDisposition.ReadDate = DateTime.Now;
message.Attachments.Add(data);//添加到附件中
}
return message;
}
public Task BuildAsync()
{
Task message = new Task(() =>
{
var message = new MailMessage();
if (Address?.Count == 0) throw new Exception("发送人不能为空");
foreach (var c in Address)
{
message.To.Add(c);
}
message.Subject = Subject;
message.Body = Body;
message.IsBodyHtml = IsHtml ?? true;
message.BodyEncoding = Encoding;
message.Priority = MailPriority.Normal;
message.SubjectEncoding = Encoding;
foreach (var c in Attachments)
{
if (!File.Exists(c)) throw new Exception("附件文件不存在");
var data = new Attachment(c, MediaTypeNames.Application.Octet);//实例化附件
data.ContentDisposition.CreationDate = File.GetCreationTime(c);
data.ContentDisposition.ModificationDate = File.GetLastAccessTime(c);
data.ContentDisposition.ReadDate = DateTime.Now;
message.Attachments.Add(data);//添加到附件中
}
return message;
});
message.Start();
return message;
}
}
(2).MailPlatform
public class MailPlatform
{
private string _last;
public string LastAllocatedMail { get => _last; }
private List<(MailAddress,string)>? _mailinformation;
public MailPlatform(params (MailAddress,string)[]? pairs)
{
_mailinformation = pairs.ToList() ?? throw new Exception("错误的初始化");
}
public MailPlatform(params EMailSetting[]? pairs)
{
_mailinformation = new List<(MailAddress, string)>();
foreach(var c in pairs)
{
(MailAddress, string) data = (new MailAddress(c.EMail), c.Key);
_mailinformation.Add(data);
}
if (_mailinformation.Count == 0) throw new Exception("错误的初始化");
}
public void SendMail(MailMessage message)
{
var random = new Random();
var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault() ?? throw new Exception("未初始化任何设置");
_last = info.Item1.Address;
message.From = info.Item1;
var client = new SmtpClient()
{
EnableSsl = true,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
DeliveryMethod = SmtpDeliveryMethod.Network,
Host = "smtp." + info.Item1.Host
};
client.Send(message);
}
public void SendMailAsync(MailMessage message, SendCompletedEventHandler CompletedMethod, object args)
{
var random = new Random();
var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault() ?? throw new Exception("未初始化任何设置");
_last = info.Item1.Address;
message.From = info.Item1;
var client = new SmtpClient()
{
EnableSsl = true,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
DeliveryMethod = SmtpDeliveryMethod.Network,
Host = "smtp." + info.Item1.Host
};
client.SendCompleted += new SendCompletedEventHandler(CompletedMethod);
client.SendAsync(message, args);
}
public Task SendMailAsync(MailMessage message)
{
var random = new Random();
var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault() ?? throw new Exception("未初始化任何设置");
_last = info.Item1.Address;
Task task = new Task(() =>
{
message.From = info.Item1;
var client = new SmtpClient()
{
EnableSsl = true,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
DeliveryMethod = SmtpDeliveryMethod.Network,
Host = "smtp." + info.Item1.Host
};
client.Send(message);
});
task.Start();
return task;
}
}
因为要获取Allocate信息,需要调用LastAllocatedMail属性
7.全局引用(NET6.0)GlobalUsing.cs
//SpaceServer函数 global using SpaceServer.EFCore; global using SpaceServer.EFCore.Models; global using SpaceServer.Programs.Mail; global using SpaceServer.Programs.Address; global using SpaceServer.Programs.Config; global using SpaceServer.Programs.Config.Models; global using SpaceServer.Models; //Mincrosoft函数 global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Http; //System函数 global using System.Net; global using System.IO; global using System.Text; global using System.Text.RegularExpressions; //外部函数 global using Newtonsoft.Json; global using Newtonsoft.Json.Linq;三.需要导入的包 项目文件下载
项目文件下载
EMail-API服务
本API只能用于发验证码测试,不用于其他方面,一切问题与作者无关
请求地址: api.spaceserver.cloud/api/mailcode/sendcode?
请求方法:GET
参数集
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | string | 发送的验证码 |
| office | string | 公司名称 |
| address | string | 目标邮箱地址 |
返回集
| 参数名 | 类型 | 说明 |
|---|---|---|
| Guid | string | 唯一标识符 |
| Address | string | 目标邮箱地址 |
| Content | string | 内容 |
| DateTime | DateTime | 发送时间 |
错误集
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | int | 错误代码 |
| result | string | 错误原因 |



