前言:前段时间根据 [老张的哲学] 大佬讲解的视频做的笔记,讲的很不错。此文主要记录JWT/DI依赖注入/AOP面向切面编程/DTO/解决跨域等相关知识,还包含一些.NET Core项目实战的一些案例。我是西瓜程序猿,感谢大家的支持!
(1)为什么要学习.NET Core?
.NET Core是为了重新启动某些Framework组件而为其他人提供平台工作的机会,由于.NET Framework主要以委托(C#)代码位基础构建了因此这些部分不需要改代码即可移至.NET Core。
(2).NET Core运用的多吗?
微信支付、网易游戏、三星电子、Adobe、Stackoverflow等
(3)什么是.NET Core?
跨平台、自托管、开源、高性能,.NETCore是基于Kestrel执行的控制台程序。
(4)中间件的执行过程?
(5)中间件3种写法?
ES:对称可逆加密算法
RSA:非对称可逆算法
Base64
MD5:不可逆加密
数据传输对象(DTO全称为Data Transfer Object):是一种设计模式之间传输数据的软件应用系统。数据传输对象往往是数据访问对象从数据库检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以下不具有任何行为除了存储和检索的数据(范文和存取器)。
相同点:
不同点:
域名组成:
(1)西瓜程序猿创建的项目结构如下:
项目相关依赖如下:
(2)在【Autofac依赖注入】层导入相关Nuget包:
(3)在Program.cs使用Autofac工厂:
(4)在AutofacModuleRegister写入以下代码,继承自Modele,并重写Load方法。
protected override void Load(ContainerBuilder builder)
{
var basePath = AppContext.BaseDirectory;
#region 带有接口层的服务注入
var servicesDllFile = Path.Combine(basePath, "Autofac.Service.dll");
var repositoryDllFile = Path.Combine(basePath, "Autofac.Repository.dll");
if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile)))
{
var msg = "Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。";
throw new Exception(msg);
}
// 获取 Service.dll 程序集服务,并注册
var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces()
.InstancePerDependency();
// 获取 Repository.dll 程序集服务,并注册
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
builder.RegisterAssemblyTypes(assemblysRepository)
.AsImplementedInterfaces()
.PropertiesAutowired()
.InstancePerDependency();
#endregion
}
(5)在控制器中使用构造函数依赖注入。
(6)然后编译项目,最后运行项目试试,可以发现报错了,因为Repository.dll和service.dll 丢失,因为项目解耦了。解决方案如下:
将这下面2个实现层输出位置如下:
(1)引入包。
(2)创建对应的数据实体,和要返回的视图模型实体。
实体:
视图模型实体:
(3)创建一个【CustomProfile】类,需要继承自【Profile】,用来创建关系映射。
(4)创建一个【AutoMapperConfig】类,用来静态全局 AutoMapper 配置文件。
(5)创建一个【AutoMapperSetup】类,用于Automapper的启动服务。
(6)然后在【Startup.cs】的ConfigreServices方法注册相关服务。
2.3.1-什么是限流?
2.3.2-时间窗口算法
2.3.3-漏斗算法
2.3.4-令牌算法
2.3.5-时间窗口(代码实现)
(1)安装Nuget包。
(2)创建【IpPolicyRateLimitSetup】类,用于限流,启用服务。
(3)在【Startup.cs】的ConfigreServices方法注册相关服务。
(4)在【appsettings.json】中根节点写入一下配置。
"IpRateLimiting": {
"EnableEndpointRateLimiting": true, //false: 全局执行API——true:针对每个API
"StackBlockedRequests": false, //False:应在另一个计数器上记录拒绝次数
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"IpWhitelist": [], //添加白名单
"EndpointWhitelist": [ "get:/api/xxx", "*:/api/yyy" ],//添加API的白名单
"ClientWhitelist": [ "dev-client-1", "dev-client-2" ],//添加客户端的白名单
"QuotaExceededResponse": {
"Content": "{{\"status\":429,\"msg\":\"时间限流:访问过于频繁,请稍后重试\",\"success\":false}}",
"ContentType": "application/json",
"StatusCode": 429
},
"HttpStatusCode": 429, //返回状态码
"GeneralRules": [ //api规则,结尾一定要带*
{
//针对blog的API,1分钟最多访问20次
"Endpoint": "*:/api/blog*", //规则
"Period": "1m", //时间
"Limit": 20 //次数
},
{
//无论什么API,1秒最能请求3次
"Endpoint": "*/api/*",
"Period": "1s",
"Limit": 3
},
{
//无论什么API,1分钟最能请求30次
"Endpoint": "*/api/*",
"Period": "1m",
"Limit": 30
},
{
//无论什么API,12小时秒最能请求50次
"Endpoint": "*/api/*",
"Period": "12h",
"Limit": 500
}
]
}
(5)写一个中间件,用于IP限流。
(6)在【Startup.cs】的Configure中配置中间件。
(7)平凡请求接口时。
2.4.1-没有跨域会导致什么问题?
2.4.2-CORS跨域代码实现
(1)安装Nuget包。
(2)创建【CorsSetup】类,用于限流,启用服务。
(3)在【Startup.cs】的ConfigreServices方法注册相关服务。
(4)在【appsettings.json】中根节点写入一下配置。
"Cors": {
"PolicyName": "CorsIpAccess", //策略名称
"EnableAllIPs": true, //当为true时,开放所有IP均可访问。
// 支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的
// 注意,http://127.0.0.1:1818 和 http://localhost:1818 是不一样的
"IPs": "http://127.0.0.1:2364,http://localhost:2364"
},
(5)在【Startup.cs】的Configure中配置中间件。
// CORS跨域
app.UseCors(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" }));
(1)导入Nuget包。
(2)创建【SwaggerSetup】类,启动Swagger服务。
/// <summary>
/// Swagger 启动服务
/// </summary>
public static class SwaggerSetup
{
private static readonly ILog log =LogManager.GetLogger(typeof(SwaggerSetup));
public static void AddSwaggerSetup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
//获取项目的根路径
var basePath = AppContext.BaseDirectory;
//获取项目名
var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" });
services.AddSwaggerGen(c =>
{
//遍历出全部的版本,做文档信息展示
typeof(ApiVersions).GetEnumNames().ToList().ForEach(version =>
{
c.SwaggerDoc(version, new OpenApiInfo
{
Version = version,
//RuntimeInformation.FrameworkDescription:运行时版本
Title = $"{ApiName} 西瓜程序猿 - 接口文档——{RuntimeInformation.FrameworkDescription}",
Description = $"{ApiName} HTTP API " + version,
//Contact:联系
//License:声明
});
c.OrderActionsBy(o => o.RelativePath);//接口排序
});
try
{
//这个就是刚刚配置的xml文件名【文档API的注释】
var xmlPath = Path.Combine(basePath, "Blog.Core.xml");
//默认的第二个参数是false,这个是controller的注释,记得修改
c.IncludeXmlComments(xmlPath, true);
//这个就是Model层的xml文件名【Model相关的注释】
var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");
c.IncludeXmlComments(xmlModelPath);
}
catch (Exception ex)
{
log.Error("Blog.Core.xml和Blog.Core.Model.xml 丢失,请检查并拷贝。\n" + ex.Message);
}
// 开启加权小锁
c.OperationFilter<AddResponseHeadersFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
// 在header中添加token,传递到后台
c.OperationFilter<SecurityRequirementsOperationFilter>();
// ids4和jwt切换
if (Permissions.IsUseIds4)
{
//接入identityserver4
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"{Appsettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" })}/connect/authorize"),
Scopes = new Dictionary<string, string> {
{
"blog.core.api","ApiResource id"
}
}
}
}
});
}
else
{
// Jwt Bearer 认证,必须是 oauth2
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "描述:西瓜程序猿 - JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey
});
}
});
}
}
/// <summary>
/// 自定义版本
/// </summary>
public class CustomApiVersion
{
/// <summary>
/// Api接口版本 自定义
/// </summary>
public enum ApiVersions
{
/// <summary>
/// V1 版本
/// </summary>
V1 = 1,
/// <summary>
/// V2 版本
/// </summary>
V2 = 2,
}
}
(3)在【Startup.cs】的ConfigreServices方法注册相关服务。
(4)创建一个【UseSwaggerMildd】类,开启并处理Swagger中间件。
private static readonly ILog log = LogManager.GetLogger(typeof(SwaggerMildd));
public static void UseSwaggerMildd(this IApplicationBuilder app, Func<Stream> streamHtml)
{
if (app == null) throw new ArgumentNullException(nameof(app));
app.UseSwagger();
app.UseSwaggerUI(c =>
{
//根据版本名称倒序 遍历展示
var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" });
typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>
{
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}");
});
c.SwaggerEndpoint($"https://petstore.swagger.io/v2/swagger.json", $"{ApiName} pet");
// 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:{项目名.index.html}
if (streamHtml.Invoke() == null)
{
var msg = "index.html的属性,必须设置为嵌入的资源";
log.Error(msg);
throw new Exception(msg);
}
c.IndexStream = streamHtml;
if (Permissions.IsUseIds4)
{
c.OAuthClientId("blogadminjs");
}
// 路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc";
c.RoutePrefix = "";
});
}
(5)在【Startup.cs】中配置中间件,注意顺序。
(6)index.html记得设置为【嵌套的资源】
(1)导入Nuget包。
(2)创建【AddMiniProfilerSetup】类,启动Swagger服务。
public static void AddMiniProfilerSetup(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
if(Appsettings.app(new string[] { "Startup", "MiniProfiler", "Enabled" }).ObjToBool())
{
services.AddMiniProfiler();
}
// 3.x使用MiniProfiler,必须要注册MemoryCache服务
// services.AddMiniProfiler(options =>
// {
// options.RouteBasePath = "/profiler";
// //(options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10);
// options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.Left;
// options.PopupShowTimeWithChildren = true;
// // 可以增加权限
// //options.ResultsAuthorize = request => request.HttpContext.User.IsInRole("Admin");
// //options.UserIdProvider = request => request.HttpContext.User.Identity.Name;
// }
//);
}
(3)在【Startup.cs】的ConfigreServices方法注册相关服务。
(4)创建一个【UseMiniProfilerMildd】类,开启并处理Swagger中间件。
private static readonly ILog log = LogManager.GetLogger(typeof(MiniProfilerMildd));
public static void UseMiniProfilerMildd(this IApplicationBuilder app)
{
if (app == null) throw new ArgumentNullException(nameof(app));
try
{
if (Appsettings.app("Startup", "MiniProfiler", "Enabled").ObjToBool())
{
// 性能分析
app.UseMiniProfiler();
}
}
catch (Exception e)
{
log.Error($"An error was reported when starting the MiniProfilerMildd.\n{e.Message}");
throw;
}
}
(5)在【Startup.cs】中配置中间件,注意顺序。
(1)导入Nuget包。
(2)创建【RedisCacheSetup】类,启动Redis服务。
(3)在【Startup.cs】的ConfigreServices方法注册相关服务。
(4)创建一个名为【IRedisCacheManager】接口,用于Redis缓存。
/// <summary>
/// Redis缓存接口
/// </summary>
public interface IRedisCacheManager
{
//获取 Reids 缓存值
string GetValue(string key);
//获取值,并序列化
TEntity Get<TEntity>(string key);
//保存
void Set(string key, object value, TimeSpan cacheTime);
//判断是否存在
bool Get(string key);
//移除某一个缓存值
void Remove(string key);
//全部清除
void Clear();
}
(5)创建一个【RedisCacheManager】类,并实现【IRedisCacheManager】接口
public class RedisCacheManager : IRedisCacheManager
{
private readonly string redisConnenctionString;
public volatile ConnectionMultiplexer redisConnection;
private readonly object redisConnectionLock = new object();
public RedisCacheManager()
{
string redisConfiguration = Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "ConnectionString" });//获取连接字符串
if (string.IsNullOrWhiteSpace(redisConfiguration))
{
throw new ArgumentException("Redis配置为空", nameof(redisConfiguration));
}
this.redisConnenctionString = redisConfiguration;
this.redisConnection = GetRedisConnection();
}
/// <summary>
/// 核心代码,获取连接实例
/// 通过双if 夹lock的方式,实现单例模式
/// </summary>
/// <returns></returns>
private ConnectionMultiplexer GetRedisConnection()
{
//如果已经连接实例,直接返回
if (this.redisConnection != null && this.redisConnection.IsConnected)
{
return this.redisConnection;
}
//加锁,防止异步编程中,出现单例无效的问题
lock (redisConnectionLock)
{
if (this.redisConnection != null)
{
//释放redis连接
this.redisConnection.Dispose();
}
try
{
var config = new ConfigurationOptions
{
AbortOnConnectFail = false,
AllowAdmin = true,
ConnectTimeout = 15000,//改成15s
SyncTimeout = 5000,
//Password = "Pwd",//Redis数据库密码
EndPoints = { redisConnenctionString }// connectionString 为IP:Port 如”192.168.2.110:6379”
};
this.redisConnection = ConnectionMultiplexer.Connect(config);
}
catch (Exception)
{
throw new Exception("Redis服务未启用,请开启该服务,并且请注意端口号,本项目使用的的6319,而且我的是没有设置密码。");
}
}
return this.redisConnection;
}
/// <summary>
/// 清除
/// </summary>
public void Clear()
{
foreach (var endPoint in this.GetRedisConnection().GetEndPoints())
{
var server = this.GetRedisConnection().GetServer(endPoint);
foreach (var key in server.Keys())
{
redisConnection.GetDatabase().KeyDelete(key);
}
}
}
/// <summary>
/// 判断是否存在
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Get(string key)
{
return redisConnection.GetDatabase().KeyExists(key);
}
/// <summary>
/// 查询
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string GetValue(string key)
{
return redisConnection.GetDatabase().StringGet(key);
}
/// <summary>
/// 获取
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public TEntity Get<TEntity>(string key)
{
var value = redisConnection.GetDatabase().StringGet(key);
if (value.HasValue)
{
//需要用的反序列化,将Redis存储的Byte[],进行反序列化
return SerializeHelper.Deserialize<TEntity>(value);
}
else
{
return default(TEntity);
}
}
/// <summary>
/// 移除
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
redisConnection.GetDatabase().KeyDelete(key);
}
/// <summary>
/// 设置
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="cacheTime"></param>
public void Set(string key, object value, TimeSpan cacheTime)
{
if (value != null)
{
//序列化,将object值生成RedisValue
redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime);
}
}
/// <summary>
/// 增加/修改
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool SetValue(string key, byte[] value)
{
return redisConnection.GetDatabase().StringSet(key, value, TimeSpan.FromSeconds(120));
}
}
(6)修改【appsetting.json】配置文件。
(4)创建一个【UseSwaggerMildd】类,开启并处理Swagger中间件。
private static readonly ILog log = LogManager.GetLogger(typeof(SwaggerMildd));
public static void UseSwaggerMildd(this IApplicationBuilder app, Func<Stream> streamHtml)
{
if (app == null) throw new ArgumentNullException(nameof(app));
app.UseSwagger();
app.UseSwaggerUI(c =>
{
//根据版本名称倒序 遍历展示
var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" });
typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>
{
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}");
});
c.SwaggerEndpoint($"https://petstore.swagger.io/v2/swagger.json", $"{ApiName} pet");
// 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:{项目名.index.html}
if (streamHtml.Invoke() == null)
{
var msg = "index.html的属性,必须设置为嵌入的资源";
log.Error(msg);
throw new Exception(msg);
}
c.IndexStream = streamHtml;
if (Permissions.IsUseIds4)
{
c.OAuthClientId("blogadminjs");
}
// 路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc";
c.RoutePrefix = "";
});
}
(5)在【Startup.cs】中配置中间件,注意顺序。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章