Browse Source

更新1.3.1
新增 接口、服务和仓储测试
新增 Ip限流,支持内存和Redis缓存
新增 权限管理增加权限编码字段,接口表单增加权限编码保存
新增 权限指令v-permission="'权限编码'" 或 v-permission="{ permission: '权限编码', disabled:true }"
新增 权限检查方法v-if="checkPermission('权限编码')"
修复 高级查询窗口点击关闭图标按钮,无法再打开高级查询窗口

xiaoxue 4 years ago
parent
commit
2aaed38032
32 changed files with 519 additions and 75 deletions
  1. 2 2
      Admin.Core.Common/Admin.Core.Common.csproj
  2. 10 0
      Admin.Core.Common/Configs/AppConfig.cs
  3. 10 0
      Admin.Core.Common/Configs/CacheConfig.cs
  4. 1 1
      Admin.Core.Common/Helpers/ConfigHelper.cs
  5. 4 4
      Admin.Core.Common/Helpers/HtmlHelper.cs
  6. 6 0
      Admin.Core.Model/Admin/PermissionEntity.cs
  7. 34 18
      Admin.Core.Services/Admin/Auth/AuthService.cs
  8. 1 1
      Admin.Core.Services/Admin/Auth/Input/AuthLoginInput.cs
  9. 17 0
      Admin.Core.Services/Admin/Auth/Output/AuthGetVerifyCodeOutput.cs
  10. 10 8
      Admin.Core.Services/Admin/LoginLog/LoginLogService.cs
  11. 5 0
      Admin.Core.Services/Admin/Permission/Input/PermissionAddApiInput.cs
  12. 2 3
      Admin.Core.Tests/Admin.Core.Tests.csproj
  13. 82 0
      Admin.Core.Tests/BaseTest.cs
  14. 0 32
      Admin.Core.Tests/Controller.Tests/ApiTest.cs
  15. 27 0
      Admin.Core.Tests/Controller/Admin/ApiControllerTest.cs
  16. 88 0
      Admin.Core.Tests/Controller/BaseControllerTest.cs
  17. 24 0
      Admin.Core.Tests/Repository/Admin/RepositoryBaseTest.cs
  18. 22 0
      Admin.Core.Tests/Service/Admin/ApiServiceTest.cs
  19. 23 0
      Admin.Core/Admin.Core.Common.xml
  20. 5 0
      Admin.Core/Admin.Core.Model.xml
  21. 15 0
      Admin.Core/Admin.Core.Service.xml
  22. 3 1
      Admin.Core/Admin.Core.csproj
  23. 8 0
      Admin.Core/Admin.Core.xml
  24. 0 0
      Admin.Core/Db/Data/data.json
  25. 1 0
      Admin.Core/Db/DbHelper.cs
  26. 43 0
      Admin.Core/Extensions/RateLimitServiceCollectionExtensions.cs
  27. 14 2
      Admin.Core/Program.cs
  28. 17 2
      Admin.Core/Startup.cs
  29. 3 0
      Admin.Core/configs/appconfig.json
  30. 6 1
      Admin.Core/configs/cacheconfig.json
  31. 3 0
      Admin.Core/configs/ratelimitconfig.Development.json
  32. 33 0
      Admin.Core/configs/ratelimitconfig.json

+ 2 - 2
Admin.Core.Common/Admin.Core.Common.csproj

@@ -33,9 +33,9 @@
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.4" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.4" />
     <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.4" />
-    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.5.1" />
+    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.6.0" />
     <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
-    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.5.1" />
+    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" />
     <PackageReference Include="UAParser" Version="3.1.44" />
   </ItemGroup>
 

+ 10 - 0
Admin.Core.Common/Configs/AppConfig.cs

@@ -25,6 +25,11 @@
         /// </summary>
         public LogConfig Log { get; set; } = new LogConfig();
 
+        /// <summary>
+        /// 限流
+        /// </summary>
+        public bool RateLimit { get; set; } = true;
+
         /// <summary>
         /// 验证码配置
         /// </summary>
@@ -58,6 +63,11 @@
     /// </summary>
     public class VarifyCodeConfig
     {
+        /// <summary>
+        /// 启用
+        /// </summary>
+        public bool Enabled { get; set; } = true;
+        
         /// <summary>
         /// 操作日志
         /// </summary>

+ 10 - 0
Admin.Core.Common/Configs/CacheConfig.cs

@@ -14,6 +14,11 @@ namespace Admin.Core.Common.Configs
         /// </summary>
         public CacheType Type { get; set; } = CacheType.Memory;
 
+        /// <summary>
+        /// 限流缓存类型
+        /// </summary>
+        public CacheType TypeRateLimit { get; set; } = CacheType.Memory;
+
         /// <summary>
         /// Redis配置
         /// </summary>
@@ -29,5 +34,10 @@ namespace Admin.Core.Common.Configs
         /// 连接字符串
         /// </summary>
         public string ConnectionString { get; set; } = "127.0.0.1:6379,password=,defaultDatabase=2";
+
+        /// <summary>
+        /// 限流连接字符串
+        /// </summary>
+        public string ConnectionStringRateLimit { get; set; } = "127.0.0.1:6379,password=,defaultDatabase=1";
     }
 }

+ 1 - 1
Admin.Core.Common/Helpers/ConfigHelper.cs

@@ -39,7 +39,7 @@ namespace Admin.Core.Common.Helpers
 
             if (environmentName.NotNull())
             {
-                builder.AddJsonFile(fileName.ToLower() + "." + environmentName + ".json", true, reloadOnChange);
+                builder.AddJsonFile(fileName.ToLower() + "." + environmentName + ".json", optional: true, reloadOnChange: reloadOnChange);
             }
 
             return builder.Build();

+ 4 - 4
Admin.Core.Common/Helpers/HtmlHelper.cs

@@ -15,7 +15,7 @@ namespace Admin.Core.Common.Helpers
     public class HtmlHelper
     {
         #region 私有字段
-        private readonly string _ContentType = "application/x-www-form-urlencoded";
+        private readonly string _ContentType = "application/json";
         private readonly string _Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/x-silverlight, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-silverlight-2-b1, */*";
         private readonly string _UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)";
         private int _Delay = 1000;
@@ -144,15 +144,15 @@ namespace Admin.Core.Common.Helpers
         #endregion
 
         #region 获取字符流
-        /// <summary>
-        /// 获取字符流
-        /// </summary>
         //---------------------------------------------------------------------------------------------------------------
         // 示例:
         // System.Net.CookieContainer cookie = new System.Net.CookieContainer(); 
         // Stream s = HttpHelper.GetStream("http://ptlogin2.qq.com/getimage?aid=15000102&0.43878429697395826", cookie);
         // picVerify.Image = Image.FromStream(s);
         //---------------------------------------------------------------------------------------------------------------
+        /// <summary>
+        /// 获取字符流
+        /// </summary>
         /// <param name="url">地址</param>
         /// <param name="cookieContainer">cookieContainer</param>
         public Stream GetStream(string url, CookieContainer cookieContainer)

+ 6 - 0
Admin.Core.Model/Admin/PermissionEntity.cs

@@ -21,6 +21,12 @@ namespace Admin.Core.Model.Admin
         [Column(StringLength = 50)]
         public string Label { get; set; }
 
+        /// <summary>
+        /// 权限编码
+        /// </summary>
+        [Column(StringLength = 550)]
+        public string Code { get; set; }
+
         /// <summary>
         /// 权限类型
         /// </summary>

+ 34 - 18
Admin.Core.Services/Admin/Auth/AuthService.cs

@@ -1,14 +1,15 @@
 using System;
 using System.Linq;
 using System.Threading.Tasks;
+using AutoMapper;
 using Admin.Core.Model.Admin;
 using Admin.Core.Common.Output;
 using Admin.Core.Repository.Admin;
-using Admin.Core.Common.Helpers;
 using Admin.Core.Common.Auth;
 using Admin.Core.Common.Cache;
+using Admin.Core.Common.Configs;
+using Admin.Core.Common.Helpers;
 using Admin.Core.Service.Admin.Auth.Input;
-using AutoMapper;
 using Admin.Core.Service.Admin.Auth.Output;
 
 namespace Admin.Core.Service.Admin.Auth
@@ -18,6 +19,7 @@ namespace Admin.Core.Service.Admin.Auth
         private readonly IUser _user;
         private readonly ICache _cache;
         private readonly IMapper _mapper;
+        private readonly AppConfig _appConfig;
         private readonly VerifyCodeHelper _verifyCodeHelper;
         private readonly IUserRepository _userRepository;
         private readonly IPermissionRepository _permissionRepository;
@@ -26,6 +28,7 @@ namespace Admin.Core.Service.Admin.Auth
             IUser user,
             ICache cache,
             IMapper mapper,
+            AppConfig appConfig,
             VerifyCodeHelper verifyCodeHelper,
             IUserRepository userRepository,
             IPermissionRepository permissionRepository
@@ -34,6 +37,7 @@ namespace Admin.Core.Service.Admin.Auth
             _user = user;
             _cache = cache;
             _mapper = mapper;
+            _appConfig = appConfig;
             _verifyCodeHelper = verifyCodeHelper;
             _userRepository = userRepository;
             _permissionRepository = permissionRepository;
@@ -42,24 +46,27 @@ namespace Admin.Core.Service.Admin.Auth
         public async Task<IResponseOutput> LoginAsync(AuthLoginInput input)
         {
             #region 验证码校验
-            var verifyCodeKey = string.Format(CacheKey.VerifyCodeKey, input.VerifyCodeKey);
-            var exists = await _cache.ExistsAsync(verifyCodeKey);
-            if (exists)
+            if (_appConfig.VarifyCode.Enabled)
             {
-                var verifyCode = await _cache.GetAsync(verifyCodeKey);
-                if (string.IsNullOrEmpty(verifyCode))
+                var verifyCodeKey = string.Format(CacheKey.VerifyCodeKey, input.VerifyCodeKey);
+                var exists = await _cache.ExistsAsync(verifyCodeKey);
+                if (exists)
                 {
-                    return ResponseOutput.NotOk("验证码已过期!", 1);
+                    var verifyCode = await _cache.GetAsync(verifyCodeKey);
+                    if (string.IsNullOrEmpty(verifyCode))
+                    {
+                        return ResponseOutput.NotOk("验证码已过期!", 1);
+                    }
+                    if (verifyCode.ToLower() != input.VerifyCode.ToLower())
+                    {
+                        return ResponseOutput.NotOk("验证码输入有误!", 2);
+                    }
+                    await _cache.DelAsync(verifyCodeKey);
                 }
-                if (verifyCode.ToLower() != input.VerifyCode.ToLower())
+                else
                 {
-                    return ResponseOutput.NotOk("验证码输入有误!", 2);
+                    return ResponseOutput.NotOk("验证码已过期!", 1);
                 }
-                await _cache.DelAsync(verifyCodeKey);
-            }
-            else
-            {
-                return ResponseOutput.NotOk("验证码已过期!", 1);
             }
             #endregion
 
@@ -143,7 +150,17 @@ namespace Admin.Core.Service.Admin.Auth
                     a.External
                 });
 
-            return ResponseOutput.Ok(new { user, menus });
+            var permissions = await _permissionRepository.Select
+                .Where(a => a.Type == PermissionType.Api)
+                .Where(a =>
+                    _permissionRepository.Orm.Select<RolePermissionEntity>()
+                    .InnerJoin<UserRoleEntity>((b, c) => b.RoleId == c.RoleId && c.UserId == _user.Id)
+                    .Where(b => b.PermissionId == a.Id)
+                    .Any()
+                )
+                .ToListAsync(a => a.Code);
+
+            return ResponseOutput.Ok(new { user, menus, permissions });
         }
 
         public async Task<IResponseOutput> GetVerifyCodeAsync(string lastKey)
@@ -161,8 +178,7 @@ namespace Admin.Core.Service.Admin.Auth
             var key = string.Format(CacheKey.VerifyCodeKey, guid);
             await _cache.SetAsync(key, code, TimeSpan.FromMinutes(5));
 
-            var data = new { key = guid, img };
-
+            var data = new AuthGetVerifyCodeOutput { Key = guid, Img = img };
             return ResponseOutput.Ok(data);
         }
 

+ 1 - 1
Admin.Core.Services/Admin/Auth/Input/AuthLoginInput.cs

@@ -27,7 +27,7 @@ namespace Admin.Core.Service.Admin.Auth.Input
         /// <summary>
         /// 验证码
         /// </summary>
-        [Required(ErrorMessage = "验证码不能为空!")]
+        //[Required(ErrorMessage = "验证码不能为空!")]
         public string VerifyCode { get; set; }
 
         /// <summary>

+ 17 - 0
Admin.Core.Services/Admin/Auth/Output/AuthGetVerifyCodeOutput.cs

@@ -0,0 +1,17 @@
+using System;
+
+namespace Admin.Core.Service.Admin.Auth.Output
+{
+    public class AuthGetVerifyCodeOutput
+    {
+        /// <summary>
+        /// 缓存键
+        /// </summary>
+        public string Key { get; set; }
+
+        /// <summary>
+        /// 图片
+        /// </summary>
+        public string Img { get; set; }
+    }
+}

+ 10 - 8
Admin.Core.Services/Admin/LoginLog/LoginLogService.cs

@@ -54,14 +54,16 @@ namespace Admin.Core.Service.Admin.LoginLog
             input.IP = IPHelper.GetIP(_context?.HttpContext?.Request);
 
             string ua = _context.HttpContext.Request.Headers["User-Agent"];
-            var client = UAParser.Parser.GetDefault().Parse(ua);
-            var device = client.Device.Family;
-            device = device.ToLower() == "other" ? "" : device;
-            input.Browser = client.UA.Family;
-            input.Os = client.OS.Family;
-            input.Device = device;
-            input.BrowserInfo = ua;
-
+            if (ua.NotNull())
+            {
+                var client = UAParser.Parser.GetDefault().Parse(ua);
+                var device = client.Device.Family;
+                device = device.ToLower() == "other" ? "" : device;
+                input.Browser = client.UA.Family;
+                input.Os = client.OS.Family;
+                input.Device = device;
+                input.BrowserInfo = ua;
+            }
             var entity = _mapper.Map<LoginLogEntity>(input);
             var id = (await _loginLogRepository.InsertAsync(entity)).Id;
 

+ 5 - 0
Admin.Core.Services/Admin/Permission/Input/PermissionAddApiInput.cs

@@ -24,6 +24,11 @@ namespace Admin.Core.Service.Admin.Permission.Input
         /// </summary>
         public string Label { get; set; }
 
+        /// <summary>
+        /// ȨÏÞ±àÂë
+        /// </summary>
+        public string Code { get; set; }
+
         /// <summary>
         /// ˵Ã÷
         /// </summary>

+ 2 - 3
Admin.Core.Tests/Admin.Core.Tests.csproj

@@ -7,7 +7,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Moq" Version="4.13.1" />
+    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.4" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
       <PrivateAssets>all</PrivateAssets>
@@ -16,8 +17,6 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\Admin.Core.Repository\Admin.Core.Repository.csproj" />
-    <ProjectReference Include="..\Admin.Core.Services\Admin.Core.Service.csproj" />
     <ProjectReference Include="..\Admin.Core\Admin.Core.csproj" />
   </ItemGroup>
 

+ 82 - 0
Admin.Core.Tests/BaseTest.cs

@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Autofac.Extensions.DependencyInjection;
+using NLog.Web;
+using Admin.Core.Common.Configs;
+using Admin.Core.Common.Helpers;
+using EnvironmentName = Microsoft.AspNetCore.Hosting.EnvironmentName;
+
+namespace Admin.Core.Tests
+{
+    public class BaseTest
+    {
+        protected AppConfig AppConfig { get; }
+        protected TestServer Server { get; }
+        protected HttpClient Client { get; }
+        protected IServiceProvider ServiceProvider { get; }
+
+        protected BaseTest()
+        {
+            AppConfig = new ConfigHelper().Get<AppConfig>("appconfig") ?? new AppConfig();
+
+            var builder = CreateHostBuilder();
+            var host = builder.Build();
+            host.Start();
+
+            Server = host.GetTestServer();
+            Client = host.GetTestClient();
+
+            ServiceProvider = Server.Services;
+        }
+
+        private IHostBuilder CreateHostBuilder()
+        {
+            var configsPath = Path.Combine(AppContext.BaseDirectory, "configs").ToPath();
+            
+
+            return Host.CreateDefaultBuilder()
+                  .UseServiceProviderFactory(new AutofacServiceProviderFactory())
+                  .ConfigureWebHostDefaults(webBuilder =>
+                  {
+                      webBuilder
+                      .UseEnvironment(EnvironmentName.Development)
+                      .UseStartup<Startup>()
+                      .ConfigureAppConfiguration((host, config) =>
+                      {
+                          if (AppConfig.RateLimit)
+                          {
+                              config.AddJsonFile($"{configsPath}/ratelimitconfig.json", optional: true, reloadOnChange: true)
+#if DEBUG
+                        .AddJsonFile($"{configsPath}/ratelimitconfig.Development.json", false)
+#endif
+                    ;
+                          }
+                      });
+                      webBuilder.UseTestServer();
+                  })
+                  .ConfigureLogging(logging =>
+                  {
+                      logging.ClearProviders();
+                      logging.SetMinimumLevel(LogLevel.Trace);
+                  })
+                  .UseNLog();
+        }
+
+        public T GetService<T>()
+        {
+            return ServiceProvider.GetService<T>();
+        }
+
+        public T GetRequiredService<T>()
+        {
+            return ServiceProvider.GetRequiredService<T>();
+        }
+    }
+}

+ 0 - 32
Admin.Core.Tests/Controller.Tests/ApiTest.cs

@@ -1,32 +0,0 @@
-using Xunit;
-using Moq;
-using Admin.Core.Model.Admin;
-using Admin.Core.Service.Admin.Api;
-using Admin.Core.Common.Input;
-using Admin.Core.Controllers.Admin;
-
-namespace Admin.Core.Tests.Controller.Tests
-{
-    public class ApiTest
-    {
-        Mock<IApiService> mockIApiService = new Mock<IApiService>();
-
-        ApiController _apiController;
-
-        public ApiTest()
-        {
-            _apiController = new ApiController(mockIApiService.Object);
-        }
-
-        [Fact]
-        public async void GetList()
-        {
-            var p = new PageInput<ApiEntity>();
-            var res = await _apiController.GetPage(p);
-            Assert.True(res.Success);
-                
-            //var data = res.;
-            //Assert.NotNull(data);
-        }
-    }
-}

+ 27 - 0
Admin.Core.Tests/Controller/Admin/ApiControllerTest.cs

@@ -0,0 +1,27 @@
+using Xunit;
+using System.Net;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Admin.Core.Tests.Controller.Admin
+{
+    public class ApiControllerTest : BaseControllerTest
+    {
+        public ApiControllerTest()
+        {
+        }
+
+        [Fact]
+        public async void GetList()
+        {
+            await Login();
+
+            var res = await Client.GetAsync("/api/admin/api/getlist");
+            Assert.Equal(HttpStatusCode.OK, res.StatusCode);
+
+            var content = await res.Content.ReadAsStringAsync();
+            var jObject = JsonConvert.DeserializeObject<JObject>(content);
+            Assert.Equal(1, jObject["code"]);
+        }
+    }
+}

+ 88 - 0
Admin.Core.Tests/Controller/BaseControllerTest.cs

@@ -0,0 +1,88 @@
+
+using System.Net;
+using System.Text;
+using System.Linq;
+using System.Net.Http;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Net.Http.Headers;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Admin.Core.Service.Admin.Auth.Input;
+using Admin.Core.Service.Admin.Auth;
+using Admin.Core.Common.Cache;
+using Admin.Core.Common.Output;
+using Admin.Core.Service.Admin.Auth.Output;
+using Admin.Core.Common.Configs;
+
+namespace Admin.Core.Tests.Controller
+{
+    public class BaseControllerTest : BaseTest
+    {
+        private readonly ICache _cache;
+        private readonly IAuthService _authService;
+        private readonly AppConfig _appConfig;
+
+        protected BaseControllerTest()
+        {
+            _cache = GetService<ICache>();
+            _authService = GetService<IAuthService>();
+            _appConfig = GetService<AppConfig>();
+        }
+
+        public ByteArrayContent GetHttpContent(object input)
+        {
+            var content = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(input));
+            var httpContent = new ByteArrayContent(content);
+            httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;charset=UTF-8");
+            return httpContent;
+        }
+
+        public async Task Login(AuthLoginInput input = null)
+        {
+            if(input == null && _appConfig.VarifyCode.Enabled)
+            {
+                var res = await _authService.GetVerifyCodeAsync("") as IResponseOutput<AuthGetVerifyCodeOutput>;
+                var verifyCodeKey = string.Format(CacheKey.VerifyCodeKey, res.Data.Key);
+                var verifyCode = await _cache.GetAsync(verifyCodeKey);
+                input = new AuthLoginInput() 
+                { 
+                    UserName = "xiaoxue",
+                    Password = "111111",
+                    VerifyCodeKey = res.Data.Key,
+                    VerifyCode = verifyCode
+                };
+            }
+
+            //Client.DefaultRequestHeaders.Connection.Add("keep-alive");
+            Client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36");
+            
+            var result = await Client.PostAsync($"/api/admin/auth/login", GetHttpContent(input));
+            var content = await result.Content.ReadAsStringAsync();
+            var jObject = JsonConvert.DeserializeObject<JObject>(content);
+
+            Client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jObject["data"]["token"]}");
+        }
+
+        public string ToParams(object source)
+        {
+            var stringBuilder = new StringBuilder(string.Empty);
+            if (source == null)
+            {
+                return "";
+            }
+
+            var entries = from PropertyDescriptor property in TypeDescriptor.GetProperties(source)
+                    let value = property.GetValue(source)
+                    where value != null
+                    select (property.Name, value);
+
+            foreach (var (name, value) in entries)
+            {
+                stringBuilder.Append(WebUtility.UrlEncode(name) + "=" + WebUtility.UrlEncode(value + "") + "&");
+            }
+
+            return stringBuilder.ToString().Trim('&');
+        }
+    }
+}

+ 24 - 0
Admin.Core.Tests/Repository/Admin/RepositoryBaseTest.cs

@@ -0,0 +1,24 @@
+using Xunit;
+using Admin.Core.Repository;
+using Admin.Core.Model.Admin;
+
+namespace Admin.Core.Tests.Service.Repository.Admin
+{
+    public class RepositoryBaseTest : BaseTest
+    {
+        private readonly IRepositoryBase<UserEntity, long> _repositoryBase;
+
+        public RepositoryBaseTest()
+        {
+            _repositoryBase = GetService<IRepositoryBase<UserEntity, long>>();
+        }
+
+        [Fact]
+        public async void GetAsyncByExpression()
+        {
+            var id = 1;
+            var user = await _repositoryBase.GetAsync(a => a.Id == id);
+            Assert.Equal(id, user?.Id);
+        }
+    }
+}

+ 22 - 0
Admin.Core.Tests/Service/Admin/ApiServiceTest.cs

@@ -0,0 +1,22 @@
+using Xunit;
+using Admin.Core.Service.Admin.Api;
+
+namespace Admin.Core.Tests.Service.Admin
+{
+    public class ApiServiceTest : BaseTest
+    {
+        private readonly IApiService _apiService;
+
+        public ApiServiceTest()
+        {
+            _apiService = GetService<IApiService>();
+        }
+
+        [Fact]
+        public async void GetAsync()
+        {
+            var res = await _apiService.GetAsync(1);
+            Assert.True(res.Success);
+        }
+    }
+}

+ 23 - 0
Admin.Core/Admin.Core.Common.xml

@@ -375,6 +375,11 @@
             日志配置
             </summary>
         </member>
+        <member name="P:Admin.Core.Common.Configs.AppConfig.RateLimit">
+            <summary>
+            限流
+            </summary>
+        </member>
         <member name="P:Admin.Core.Common.Configs.AppConfig.VarifyCode">
             <summary>
             验证码配置
@@ -405,6 +410,11 @@
             验证码配置
             </summary>
         </member>
+        <member name="P:Admin.Core.Common.Configs.VarifyCodeConfig.Enabled">
+            <summary>
+            启用
+            </summary>
+        </member>
         <member name="P:Admin.Core.Common.Configs.VarifyCodeConfig.Font">
             <summary>
             操作日志
@@ -420,6 +430,11 @@
             缓存类型
             </summary>
         </member>
+        <member name="P:Admin.Core.Common.Configs.CacheConfig.TypeRateLimit">
+            <summary>
+            限流缓存类型
+            </summary>
+        </member>
         <member name="P:Admin.Core.Common.Configs.CacheConfig.Redis">
             <summary>
             Redis配置
@@ -435,6 +450,11 @@
             连接字符串
             </summary>
         </member>
+        <member name="P:Admin.Core.Common.Configs.RedisConfig.ConnectionStringRateLimit">
+            <summary>
+            限流连接字符串
+            </summary>
+        </member>
         <member name="T:Admin.Core.Common.Configs.DbConfig">
             <summary>
             数据库配置
@@ -902,6 +922,9 @@
             <param name="cookieContainer">CookieContainer</param>
         </member>
         <member name="M:Admin.Core.Common.Helpers.HtmlHelper.GetStream(System.String,System.Net.CookieContainer)">
+            <summary>
+            获取字符流
+            </summary>
             <param name="url">地址</param>
             <param name="cookieContainer">cookieContainer</param>
         </member>

+ 5 - 0
Admin.Core/Admin.Core.Model.xml

@@ -274,6 +274,11 @@
             权限名称
             </summary>
         </member>
+        <member name="P:Admin.Core.Model.Admin.PermissionEntity.Code">
+            <summary>
+            权限编码
+            </summary>
+        </member>
         <member name="P:Admin.Core.Model.Admin.PermissionEntity.Type">
             <summary>
             权限类型

+ 15 - 0
Admin.Core/Admin.Core.Service.xml

@@ -232,6 +232,16 @@
             验证码键
             </summary>
         </member>
+        <member name="P:Admin.Core.Service.Admin.Auth.Output.AuthGetVerifyCodeOutput.Key">
+            <summary>
+            缓存键
+            </summary>
+        </member>
+        <member name="P:Admin.Core.Service.Admin.Auth.Output.AuthGetVerifyCodeOutput.Img">
+            <summary>
+            图片
+            </summary>
+        </member>
         <member name="P:Admin.Core.Service.Admin.Auth.Output.AuthLoginOutput.Id">
             <summary>
             主键Id
@@ -835,6 +845,11 @@
             权限名称
             </summary>
         </member>
+        <member name="P:Admin.Core.Service.Admin.Permission.Input.PermissionAddApiInput.Code">
+            <summary>
+            权限编码
+            </summary>
+        </member>
         <member name="P:Admin.Core.Service.Admin.Permission.Input.PermissionAddApiInput.Description">
             <summary>
             说明

+ 3 - 1
Admin.Core/Admin.Core.csproj

@@ -40,8 +40,10 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="AspNetCoreRateLimit" Version="3.0.5" />
     <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="6.0.0" />
     <PackageReference Include="Autofac.Extras.DynamicProxy" Version="5.0.0" />
+    <PackageReference Include="Caching.CSRedis" Version="3.6.3" />
     <PackageReference Include="FluentValidation.AspNetCore" Version="8.6.2" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.4" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
@@ -49,7 +51,7 @@
     <PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
 
     <PackageReference Include="NLog.Web.AspNetCore" Version="4.9.2" />
-    <PackageReference Include="NLog" Version="4.7.0" />
+    <PackageReference Include="NLog" Version="4.7.2" />
   </ItemGroup>
 
   <ItemGroup>

+ 8 - 0
Admin.Core/Admin.Core.xml

@@ -746,6 +746,14 @@
             系统内部错误(非业务代码里显式抛出的异常,例如由于数据不正确导致空指针异常、数据库异常等等)
             </summary>
         </member>
+        <member name="M:Admin.Core.Extensions.RateLimitServiceCollectionExtensions.AddIpRateLimit(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.Configuration.IConfiguration,Admin.Core.Common.Configs.CacheConfig)">
+            <summary>
+            添加Ip限流
+            </summary>
+            <param name="services"></param>
+            <param name="configuration"></param>
+            <param name="cacheConfig"></param>
+        </member>
         <member name="T:Admin.Core.Filters.AdminExceptionFilter">
             <summary>
             Admin异常错误过滤

File diff suppressed because it is too large
+ 0 - 0
Admin.Core/Db/Data/data.json


+ 1 - 0
Admin.Core/Db/DbHelper.cs

@@ -284,6 +284,7 @@ namespace Admin.Core.Db
                     a.Id,
                     a.ParentId,
                     a.Label,
+                    a.Code,
                     a.Type,
                     a.ViewId,
                     a.ApiId,

+ 43 - 0
Admin.Core/Extensions/RateLimitServiceCollectionExtensions.cs

@@ -0,0 +1,43 @@
+using AspNetCoreRateLimit;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Redis;
+using Admin.Core.Common.Configs;
+
+namespace Admin.Core.Extensions
+{
+    public static class RateLimitServiceCollectionExtensions
+    {
+        /// <summary>
+        /// 添加Ip限流
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="configuration"></param>
+        /// <param name="cacheConfig"></param>
+        public static void AddIpRateLimit(this IServiceCollection services, IConfiguration configuration, CacheConfig cacheConfig)
+        {
+            #region IP限流
+            services.Configure<IpRateLimitOptions>(configuration.GetSection("IpRateLimiting"));
+            services.Configure<IpRateLimitPolicies>(configuration.GetSection("IpRateLimitPolicies"));
+            
+            if (cacheConfig.TypeRateLimit == Common.Cache.CacheType.Redis)
+            {
+                //redis
+                var redisRateLimit = new CSRedis.CSRedisClient(cacheConfig.Redis.ConnectionStringRateLimit);
+                services.AddSingleton<IDistributedCache>(new CSRedisCache(redisRateLimit));
+                services.AddSingleton<IIpPolicyStore, DistributedCacheIpPolicyStore>();
+                services.AddSingleton<IRateLimitCounterStore, DistributedCacheRateLimitCounterStore>();
+            }
+            else
+            {
+                //内存
+                services.AddMemoryCache();
+                services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
+                services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
+            }
+            services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
+            #endregion
+        }
+    }
+}

+ 14 - 2
Admin.Core/Program.cs

@@ -2,11 +2,12 @@
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
-using Autofac.Extensions.DependencyInjection;
 using NLog.Web;
+using Autofac.Extensions.DependencyInjection;
 using Admin.Core.Common.Helpers;
-using LogLevel = Microsoft.Extensions.Logging.LogLevel;
 using Admin.Core.Common.Configs;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+using Microsoft.Extensions.Configuration;
 //using NLog;
 //using NLog.Extensions.Logging;
 //using EnvironmentName = Microsoft.AspNetCore.Hosting.EnvironmentName;
@@ -36,6 +37,17 @@ namespace Admin.Core
                 webBuilder
                 //.UseEnvironment(EnvironmentName.Production)
                 .UseStartup<Startup>()
+                .ConfigureAppConfiguration((host, config) =>
+                {
+                    if (appConfig.RateLimit)
+                    {
+                        config.AddJsonFile("./configs/ratelimitconfig.json", optional: true, reloadOnChange: true)
+#if DEBUG
+                        .AddJsonFile("./configs/ratelimitconfig.Development.json", false)
+#endif
+                    ;
+                    }
+                })
                 .UseUrls(appConfig.Urls);
             })
             .ConfigureLogging(logging =>

+ 17 - 2
Admin.Core/Startup.cs

@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.OpenApi.Models;
 using Microsoft.IdentityModel.Tokens;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection.Extensions;
 using Newtonsoft.Json;
@@ -33,19 +34,21 @@ using Admin.Core.Logs;
 using Admin.Core.Extensions;
 using Admin.Core.Common.Attributes;
 using Admin.Core.Common.Auth;
-
+using AspNetCoreRateLimit;
 
 namespace Admin.Core
 {
     public class Startup
     {
         private static string basePath => AppContext.BaseDirectory;
+        private readonly IConfiguration _configuration;
         private readonly IHostEnvironment _env;
         private readonly ConfigHelper _configHelper;
         private readonly AppConfig _appConfig;
 
-        public Startup(IWebHostEnvironment env)
+        public Startup(IConfiguration configuration, IWebHostEnvironment env)
         {
+            _configuration = configuration;
             _env = env;
             _configHelper = new ConfigHelper();
             _appConfig = _configHelper.Get<AppConfig>("appconfig", env.EnvironmentName) ?? new AppConfig();
@@ -228,6 +231,12 @@ namespace Admin.Core
             }
             #endregion
 
+            //IP限流
+            if (_appConfig.RateLimit)
+            {
+                services.AddIpRateLimit(_configuration, cacheConfig);
+            }
+
             //阻止NLog接收状态消息
             services.Configure<ConsoleLifetimeOptions>(opts => opts.SuppressStatusMessages = true);
         }
@@ -292,6 +301,12 @@ namespace Admin.Core
             //    Console.WriteLine($"{_appConfig.Urls}\r\n");
             //});
 
+            //IP限流
+            if (_appConfig.RateLimit)
+            {
+                app.UseIpRateLimiting();
+            }
+
             #region app配置
             //异常
             app.UseExceptionHandler("/Error");

+ 3 - 0
Admin.Core/configs/appconfig.json

@@ -13,8 +13,11 @@
     //操作日志
     "operation": true
   },
+  //限流
+  "rateLimit": true,
   //验证码
   "varifyCode": {
+    "enabled": true,
     // 字体
     "font": [ "Times New Roman", "Verdana", "Arial", "Gungsuh", "Impact" ]
   }

+ 6 - 1
Admin.Core/configs/cacheconfig.json

@@ -1,8 +1,13 @@
 {
   //缓存类型 Memory = 0,Redis = 1
   "type": 0,
+  //限流缓存类型 Memory = 0,Redis = 1
+  "typeRateLimit": 0,
   //Redis配置
   "redis": {
-    "connectionString": "127.0.0.1:6379,password=,defaultDatabase=2"
+    //连接字符串
+    "connectionString": "127.0.0.1:6379,password=,defaultDatabase=2",
+    //限流连接字符串
+    "connectionStringRateLimit": "127.0.0.1:6379,password=,defaultDatabase=0"
   }
 }

+ 3 - 0
Admin.Core/configs/ratelimitconfig.Development.json

@@ -0,0 +1,3 @@
+{
+
+}

+ 33 - 0
Admin.Core/configs/ratelimitconfig.json

@@ -0,0 +1,33 @@
+{
+  /*
+  https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/IpRateLimitMiddleware
+  https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/Using-Redis-as-a-distributed-counter-store
+  */
+  "IpRateLimiting": {
+    "EnableEndpointRateLimiting": true,
+    "StackBlockedRequests": false,
+    "RealIpHeader": "X-Real-IP",
+    "ClientIdHeader": "X-ClientId",
+    "IpWhitelist": [], // "127.0.0.1"
+    "EndpointWhitelist": [], // "get:/api/a", "*:/api/b"
+    "ClientWhitelist": [],
+    "HttpStatusCode": 429,
+    "QuotaExceededResponse": {
+      "Content": "{{\"code\":429,\"msg\":\"访问过于频繁!\"}}",
+      "ContentType": "application/json",
+      "StatusCode": 429
+    },
+    "GeneralRules": [
+      {
+        "Endpoint": "*",
+        "Period": "1s",
+        "Limit": 2
+      },
+      {
+        "Endpoint": "*",
+        "Period": "10m",
+        "Limit": 100
+      }
+    ]
+  }
+}

Some files were not shown because too many files changed in this diff