# Z  主要知识点



## AOP 

本项目多处采用面向切面编程思想——AOP，除了广义上的过滤器和中间件以外，主要通过动态代理的形式来实现AOP编程思想，主要的案例共有四个，分别是：  
1、服务日志AOP；  
2、服务InMemory缓存AOP；  
3、服务Redis缓存AOP；  
4、服务事务AOP；  
  
   
具体的代码可以在 `New_College\New_College\AOP` 文件夹下查看。  
  
与此同时，多个AOP也设置了阀门来控制是否开启，具体的可以查看 `appsettings.json` 中的：  

```
  "AppSettings": {
    "RedisCachingAOP": {
      "Enabled": false,
      "ConnectionString": "127.0.0.1:6319"
    },
    "MemoryCachingAOP": {
      "Enabled": true
    },
    "LogAOP": {
      "Enabled": false
    },
    "TranAOP": {
      "Enabled": false
    },
    "SqlAOP": {
      "Enabled": false
    }
  },

```

## Appsettings 

整个系统通过一个封装的操作类 `Appsettings.cs` 来控制配置文件 `appsettings.json` 文件，  
操作类地址在：`\New_College.Common\Helper` 文件夹下。  
具体的使用方法是：  

```
Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" })

// 里边的参数，按照 appsettings.json 中设置的层级顺序来写，可以获取到指定的任意内容。

```



## AspNetCoreRateLimit

系统使用 `AspNetCoreRateLimit` 组件来实现ip限流：
1、添加 `nuget` 包：
```
<PackageReference Include="AspNetCoreRateLimit" Version="3.0.5" />
```

2、注入服务 `IpPolicyRateLimitSetup.cs`
```
services.AddIpPolicyRateLimitSetup(Configuration);
```

3、配置中间件
```
 // Ip限流,尽量放管道外层
 app.UseIpRateLimiting();
```

4、配置数据  

具体的内容，自行百度即可
```
  "IpRateLimiting": {
    "EnableEndpointRateLimiting": true,
    "StackBlockedRequests": false,
    "RealIpHeader": "X-Real-IP",
    "ClientIdHeader": "X-ClientId",
    "HttpStatusCode": 429,//返回状态码
    "GeneralRules": [//规则,结尾一定要带*
      {
        "Endpoint": "*",
        "Period": "1m",
        "Limit": 120
      },
      {
        "Endpoint": "*:/api/blog*",
        "Period": "1m",
        "Limit": 30
      }
    ]

  }
```



## Async-Await 

整个系统采用 async/await 异步编程，符合主流的开发模式，   
特别是对多线程开发很友好。



## Authorization-Ids4

本系统 v2.0 版本（目前的系统已经集成 `ids4` 和 `jwt`，并且可以自由切换），已经支持了统一授权认证，和 `blog` 项目、`Admin` 项目、`DDD` 项目等一起，使用一个统一的认证中心。  
  
具体的代码参考：`.\New_College\Extensions` 文件夹下的 `Authorization_Ids4Setup.cs` ，注意需要引用指定的 `nuget` 包，核心代码如下：   

```
    //【认证】
  services.AddAuthentication(o =>
  {
      o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
      o.DefaultChallengeScheme = nameof(ApiResponseHandler);
      o.DefaultForbidScheme = nameof(ApiResponseHandler);
  })
  // 2.添加Identityserver4认证
  .AddIdentityServerAuthentication(options =>
  {
      options.Authority = Appsettings.app(new string[] { "Startup", "IdentityServer4", "AuthorizationUrl" });
      options.RequireHttpsMetadata = false;
      options.ApiName = Appsettings.app(new string[] { "Startup", "IdentityServer4", "ApiName" });
      options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
      options.ApiSecret = "api_secret";

  })


```
  
### 如何在Swagger中配置Ids4？
很简单，直接在 `SwaggerSetup.cs` 中直接接入 `oauth、Implicit` 即可：  

```
 //接入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> {
             {
                 "new_college.api","ApiResource id"
             }
         }
         }
     }
 });

```
  
然后在 `IdentityServer4`  项目中，做指定的修改，配置 `8081` 的回调地址：  

```
 new Client {
     ClientId = "blogadminjs",
     ClientName = "Blog.Admin JavaScript Client",
     AllowedGrantTypes = GrantTypes.Implicit,
     AllowAccessTokensViaBrowser = true,

     RedirectUris =
     {
         "http://vueadmin.neters.club/callback",
         // 这里要配置回调地址
         "http://localhost:8081/oauth2-redirect.html" 
     },
     PostLogoutRedirectUris = { "http://vueadmin.neters.club" },
     AllowedCorsOrigins =     { "http://vueadmin.neters.club" },

     AllowedScopes = {
         IdentityServerConstants.StandardScopes.OpenId,
         IdentityServerConstants.StandardScopes.Profile,
         "roles",
         "new_college.api"
     }
 },

```

然后再 `Swagger` 中，配置登录授权：  

<img src="http://apk.neters.club/images/20200507213830.png" alt="swagger" width="600" >


## Authorization-JWT 

如果你不想使用 `IdentityServer4` 的话，也可以使用 `JWT` 认证，同样是是`New_College\New_College\Extensions` 文件夹下的 `AuthorizationSetup.cs` 中有关认证的部分：  

```
 1.添加JwtBearer认证服务
.AddJwtBearer(o =>
{
    o.TokenValidationParameters = tokenValidationParameters;
    o.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            // 如果过期，则把<是否过期>添加到，返回头信息中
            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
                context.Response.Headers.Add("Token-Expired", "true");
            }
            return Task.CompletedTask;
        }
    };
})

```


## AutoMapper

使用 `AutoMapper` 组件来实现 `Dto` 模型的传输转换，具体的用法，可以查看：   
`New_College\New_College\Extensions` 文件夹下的 `AutoMapperSetup.cs` 扩展类，  
通过引用 `AutoMapper` 和 `AutoMapper.Extensions.Microsoft.DependencyInjection` 两个 `nuget` 包，并设置指定的 `profile` 文件，来实现模型转换控制。

```
// 比如如何定义：
 public class CustomProfile : Profile
 {
     /// <summary>
     /// 配置构造函数，用来创建关系映射
     /// </summary>
     public CustomProfile()
     {
         CreateMap<BlogArticle, BlogViewModels>();
         CreateMap<BlogViewModels, BlogArticle>();
     }
 }


// 比如如何使用
models = _mapper.Map<BlogViewModels>(blogArticle);

```
  
具体的查看项目中代码即可。  




## CORS

在线项目使用的是 `nginx` 跨域代理，但是同时也是支持 `CORS` 代理：    
1、注入服务 `services.AddCorsSetup();` 具体代码 `New_College\New_College\Extensions` 文件夹下的 `CorsSetup.cs` 扩展类；  
2、配置中间件 `app.UseCors("LimitRequests");` ,要注意中间件顺序；  
3、配置自己项目的前端端口，通过在 `appsettings.json` 文件中配置自己的前端项目 `ip:端口` ，来实现跨域：  

```
  "Startup": {
    "Cors": {
      "IPs": "http://127.0.0.1:2364,http://localhost:2364,http://localhost:8080,http://localhost:8021,http://localhost:1818"
    }
  },

```


## DI-AutoFac

项目使用了依赖注入，除了原生的依赖注入以外，更多的使用的是第三方组件 `Autofac` ：  
1、引用依赖包：
```
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="5.0.1" />
    <PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" />

```
主要是第一个 `nuget` 包，下边的是为了实现动态代理 `AOP` 操作；  

2、项目之间采用引用解耦的方式，通过反射来注入服务层和仓储层的程序集 `dll` 来实现批量注入，更方便，以后每次新增和修改 `Service` 层和 `Repository` 层，只需要 `F6` 编译一下即可，具体代码查看 `Startup.cs`：  

```


        // 注意在CreateDefaultBuilder中，添加Autofac服务工厂
        public void ConfigureContainer(ContainerBuilder builder)
        {
            var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
            //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();


            #region 带有接口层的服务注入


            var servicesDllFile = Path.Combine(basePath, "New_College.Services.dll");
            var repositoryDllFile = Path.Combine(basePath, "New_College.Repository.dll");

            if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile)))
            {
                throw new Exception("Repository.dll和service.dll 丢失，因为项目解耦了，所以需要先F6编译，再F5运行，请检查 bin 文件夹，并拷贝。");
            }



            // AOP 开关，如果想要打开指定的功能，只需要在 appsettigns.json 对应对应 true 就行。
            var cacheType = new List<Type>();
            if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool())
            {
                builder.RegisterType<BlogRedisCacheAOP>();
                cacheType.Add(typeof(BlogRedisCacheAOP));
            }
            if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool())
            {
                builder.RegisterType<BlogCacheAOP>();
                cacheType.Add(typeof(BlogCacheAOP));
            }
            if (Appsettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool())
            {
                builder.RegisterType<BlogTranAOP>();
                cacheType.Add(typeof(BlogTranAOP));
            }
            if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool())
            {
                builder.RegisterType<BlogLogAOP>();
                cacheType.Add(typeof(BlogLogAOP));
            }

            // 获取 Service.dll 程序集服务，并注册
            var assemblysServices = Assembly.LoadFrom(servicesDllFile);
            builder.RegisterAssemblyTypes(assemblysServices)
                      .AsImplementedInterfaces()
                      .InstancePerDependency()
                      .EnableInterfaceInterceptors()//引用Autofac.Extras.DynamicProxy;
                      .InterceptedBy(cacheType.ToArray());//允许将拦截器服务的列表分配给注册。

            // 获取 Repository.dll 程序集服务，并注册
            var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
            builder.RegisterAssemblyTypes(assemblysRepository)
                   .AsImplementedInterfaces()
                   .InstancePerDependency();

            #endregion

            #region 没有接口层的服务层注入

            //因为没有接口层，所以不能实现解耦，只能用 Load 方法。
            //注意如果使用没有接口的服务，并想对其使用 AOP 拦截，就必须设置为虚方法
            //var assemblysServicesNoInterfaces = Assembly.Load("New_College.Services");
            //builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces);

            #endregion

            #region 没有接口的单独类 class 注入

            //只能注入该类中的虚方法
            builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
                .EnableClassInterceptors()
                .InterceptedBy(cacheType.ToArray());

            #endregion


            // 这里和注入没关系，只是获取注册列表，请忽略
            tsDIAutofac.AddRange(assemblysServices.GetTypes().ToList());
            tsDIAutofac.AddRange(assemblysRepository.GetTypes().ToList());
        }

```

3、然后 `Program.cs` 中也要加一句话：` .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<--NOTE THIS `  



## DI-NetCore

除了主要的 `Autofac` 依赖注入以外，也减少的使用了原生的依赖注入方式，很简单，比如这样的：  
```

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            // 注入权限处理器
            services.AddScoped<IAuthorizationHandler, PermissionHandler>();
            services.AddSingleton(permissionRequirement);
```


## Filter

项目中一共有四个过滤器
```
1、GlobalAuthorizeFilter.cs —— 全局授权配置，添加后，就可以不用在每一个控制器上添加 [Authorize] 特性，但是3.1版本好像有些问题，【暂时放弃使用】；
2、GlobalExceptionFilter.cs —— 全局异常处理，实现 actionContext 级别的异常日志收集；
3、GlobalRoutePrefixFilter.cs —— 全局路由前缀公约，统计在路由上加上前缀；
4、UseServiceDIAttribute.cs —— 测试注入，【暂时无用】；
```
文件地址在 `.\New_College\Filter` 文件夹下，其中核心的是 `2` 个，重点使用的是 `1` 个 —— 全局异常错误日志 `GlobalExceptionsFilter`:
通过注册在 `MVC` 服务 `services.AddControllers()` 中，实现全局异常过滤：
```
 services.AddControllers(o =>
 {
     // 全局异常过滤
     o.Filters.Add(typeof(GlobalExceptionsFilter));
     // 全局路由权限公约
     //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention());
     // 全局路由前缀，统一修改路由
     o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name)));
 })
```



## Framework 

项目采用 `服务+仓储+接口` 的多层结构，使用依赖注入，并且通过解耦项目，较完整的实现了 `DIP` 原则：  
高层模块不应该依赖于底层模块，二者都应该依赖于抽象。  
抽象不应该依赖于细节，细节应该依赖于抽象。  

同时项目也封装了:  
`CodeFirst` 初始化数据库以及数据；  
`DbFirst` 根据数据库（支持多库），生成多层代码，算是简单代码生成器；  
其他功能，[核心功能与进度](http://apk.neters.club/.doc/guide/#%E5%8A%9F%E8%83%BD%E4%B8%8E%E8%BF%9B%E5%BA%A6)


 

## Log 

通过集成 `Log4Net` 组件，完美配合 `NetCore` 官方的 `ILogger<T>` 接口，实现对日志的管控，引用 `nuget` 包 `Microsoft.Extensions.Logging.Log4Net.AspNetCore`:
Program.cs
```
  webBuilder
  .UseStartup<Startup>()
  .ConfigureLogging((hostingContext, builder) =>
  {
      //该方法需要引入Microsoft.Extensions.Logging名称空间
      builder.AddFilter("System", LogLevel.Error); //过滤掉系统默认的一些日志
      builder.AddFilter("Microsoft", LogLevel.Error);//过滤掉系统默认的一些日志

      //添加Log4Net
      //var path = Directory.GetCurrentDirectory() + "\\log4net.config"; 
      //不带参数：表示log4net.config的配置文件就在应用程序根目录下，也可以指定配置文件的路径
      //需要添加nuget包：Microsoft.Extensions.Logging.Log4Net.AspNetCore
      builder.AddLog4Net();
  });

```

然后直接在需要的地方注入使用，比如在控制器中
` public UserController(ILogger<UserController> logger)`

然后就可以使用了。  

> 注意：日志 其实是分为两部分的：  
> netcore输出(控制台、输出窗口等) 和 `ILogger` 持久化  
> 两者对应配置也不一样，就比如上边的过滤，是针对日志持久化的，如果想要对控制台进行控制，需要配置 `appsettings.json` 中的 `Logging` 节点


## MemoryCache

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广

## Middleware

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## MiniProfiler

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广

## publish
精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广


## Redis

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## Repository
精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## SeedData

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## SignalR

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## SqlSugar

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## SqlSugar-Codefirst&DataSeed

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## SqlSugar-SqlAOP

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## Swagger

精力有限，还是更新中...   
如果你愿意帮忙，可以直接在GitHub中，提交pull request，   
我会在后边的贡献者页面里，列出你的名字和项目地址做推广
## T4

项目集成 `T4` 模板 `.\New_College.FrameWork` 层，目的是可以一键生成项目模板代码。  
1、需要在 `DbHelper.ttinclude` 中配置连接数据库连接字符串；  
2、针对每一层的代码，就去指定的 `.tt` 模板，直接 `CTRL+S` 保存即可；  

> 注意，目前的代码是 `SqlServer` 版本的，其他数据库版本的，可以去群文件查看。


## Test-xUnit

项目简单使用了单元测试，通过 `xUnit` 组件，具体的可以查看 `New_College.Tests` 层相关代码。  
目前单元测试用例还比较少，大家可以自行添加。  


## Temple-Nuget 

本项目封装了 `Nuget` 自定义模板，你可以根据这个模板，一键创建自己的项目名，具体的操作，可以双击项目根目录下的 `CreateYourProject.bat` ，可以参考 [#如何项目重命名](http://apk.neters.club/.doc/guide/getting-started.html#%E5%A6%82%E4%BD%95%E9%A1%B9%E7%9B%AE%E9%87%8D%E5%91%BD%E5%90%8D)  

同时，你也可以再 `Nuget` 管理器中，搜索到：
<img src="http://apk.neters.club/images/20200507223058.png" alt="nuget" width="600" >



## UserInfo 


项目中封装了获取用户信息的代码：  
在 `.\New_College.Common\HttpContextUser` 文件夹下 `AspNetUser.cs` 实现类和 `IUser.cs` 接口。  

如果使用，首先需要注册相应的服务，参见：`.\New_College\Extensions` 文件夹下的 `HttpContextSetup.cs`；    
然后，就直接在控制器构造函数中，注入接口 `IUser` 即可；  

> `注意`：  
> 1、如果要想获取指定的服务，必须登录，也就是必须要在 `Header` 中传递有效 `Token` ，这是肯定的。    
> 2、如果要获取用户信息，一定要在中间件 `app.UseAuthentication()` 之后（不要问为什么），控制器肯定在它之后，所以能获取到；  
> 3、`【并不是】`一定需要添加 `[Authorize]` 特性，如果你加了这个特性，可以直接获取，但是如果不加，可以从我的 `AspNetUser.cs` 方法中，有一个直接从 `Header` 中解析的方法 `List<string> GetUserInfoFromToken(string ClaimType);`：

```
 public string GetToken()
 {
     return _accessor.HttpContext.Request.Headers["Authorization"].ObjToString().Replace("Bearer ", "");
 }

 public List<string> GetUserInfoFromToken(string ClaimType)
 {

     var jwtHandler = new JwtSecurityTokenHandler();
     if (!string.IsNullOrEmpty(GetToken()))
     {
         JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(GetToken());

         return (from item in jwtToken.Claims
                 where item.Type == ClaimType
                 select item.Value).ToList();
     }
     else
     {
         return new List<string>() { };
     }
 }

```
