Przeglądaj źródła

细节优化,包括日志优化增加ES、增加单用户登录等

zuohuaijun 3 lat temu
rodzic
commit
8f9f2f3328
29 zmienionych plików z 400 dodań i 146 usunięć
  1. 138 0
      Admin.NET/.editorconfig
  2. 0 1
      Admin.NET/Admin.NET.Application/GlobalUsings.cs
  3. 4 4
      Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
  4. 66 17
      Admin.NET/Admin.NET.Core/Admin.NET.Core.xml
  5. 0 4
      Admin.NET/Admin.NET.Core/AdminNETConfig.json
  6. 5 0
      Admin.NET/Admin.NET.Core/Const/CommonConst.cs
  7. 2 2
      Admin.NET/Admin.NET.Core/Entity/SysTimer.cs
  8. 6 0
      Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs
  9. 3 5
      Admin.NET/Admin.NET.Core/Hub/ChatHub.cs
  10. 53 2
      Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs
  11. 7 3
      Admin.NET/Admin.NET.Core/Logging/ElasticSearchSetup.cs
  12. 0 17
      Admin.NET/Admin.NET.Core/Option/ElasticSearchOptions.cs
  13. 4 1
      Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs
  14. 6 0
      Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
  15. 4 4
      Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs
  16. 1 2
      Admin.NET/Admin.NET.Core/Service/DataBase/SysDataBaseService.cs
  17. 2 4
      Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs
  18. 2 4
      Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs
  19. 2 0
      Admin.NET/Admin.NET.Core/Service/OnlineUser/ISysOnlineUserService.cs
  20. 30 5
      Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs
  21. 2 4
      Admin.NET/Admin.NET.Core/Service/WeChat/WeChatPayService.cs
  22. 1 2
      Admin.NET/Admin.NET.Core/Service/WeChat/WeChatService.cs
  23. 5 10
      Admin.NET/Admin.NET.Core/Util/ServerUtil.cs
  24. 1 1
      Admin.NET/Admin.NET.UnitTest/Admin.NET.UnitTest.csproj
  25. 0 1
      Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs
  26. 27 43
      Admin.NET/Admin.NET.Web.Core/Startup.cs
  27. 12 5
      Admin.NET/Admin.NET.Web.Entry/appsettings.Development.json
  28. 12 5
      Admin.NET/Admin.NET.Web.Entry/appsettings.json
  29. 5 0
      Admin.NET/Admin.NET.sln

+ 138 - 0
Admin.NET/.editorconfig

@@ -0,0 +1,138 @@
+# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Baiqian\Workplaces\Gitee\Furion\framework codebase based on best match to current usage at 2021-5-16
+# You can modify the rules from these initially generated values to suit your own policies
+# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
+[*.cs]
+
+
+#Core editorconfig formatting - indentation
+
+#use soft tabs (spaces) for indentation
+indent_style = space
+
+#Formatting - indentation options
+
+#indent switch case contents.
+csharp_indent_case_contents = true
+#labels are placed at one less indent to the current context
+csharp_indent_labels = one_less_than_current
+#indent switch labels
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+
+#place catch statements on a new line
+csharp_new_line_before_catch = true
+#place else statements on a new line
+csharp_new_line_before_else = true
+#require finally statements to be on a new line after the closing brace
+csharp_new_line_before_finally = true
+#require members of object intializers to be on separate lines
+csharp_new_line_before_members_in_object_initializers = true
+#require braces to be on a new line for properties, accessors, methods, control_blocks, types, lambdas, and object_collection_array_initializers (also known as "Allman" style)
+csharp_new_line_before_open_brace = properties, accessors, methods, control_blocks, types, lambdas, object_collection_array_initializers
+
+#Formatting - organize using options
+
+#do not place System.* using directives before other using directives
+dotnet_sort_system_directives_first = false
+
+#Formatting - spacing options
+
+#require NO space between a cast and the value
+csharp_space_after_cast = false
+#require a space before the colon for bases or interfaces in a type declaration
+csharp_space_after_colon_in_inheritance_clause = true
+#require a space after a keyword in a control flow statement such as a for loop
+csharp_space_after_keywords_in_control_flow_statements = true
+#require a space before the colon for bases or interfaces in a type declaration
+csharp_space_before_colon_in_inheritance_clause = true
+#remove space within empty argument list parentheses
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+#remove space between method call name and opening parenthesis
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
+csharp_space_between_method_call_parameter_list_parentheses = false
+#remove space within empty parameter list parentheses for a method declaration
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+
+#leave code block on single line
+csharp_preserve_single_line_blocks = true
+#leave statements and member declarations on the same line
+csharp_preserve_single_line_statements = true
+
+#Style - Code block preferences
+
+#prefer no curly braces if allowed
+csharp_prefer_braces = false:suggestion
+
+#Style - expression bodied member options
+
+#prefer block bodies for constructors
+csharp_style_expression_bodied_constructors = false:suggestion
+#prefer block bodies for methods
+csharp_style_expression_bodied_methods = false:suggestion
+#prefer expression-bodied members for properties
+csharp_style_expression_bodied_properties = true:suggestion
+
+#Style - expression level options
+
+#prefer out variables to be declared inline in the argument list of a method call when possible
+csharp_style_inlined_variable_declaration = true:suggestion
+#prefer tuple names to ItemX properties
+dotnet_style_explicit_tuple_names = true:suggestion
+#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+#Style - Expression-level  preferences
+
+#prefer default over default(T)
+csharp_prefer_simple_default_expression = true:suggestion
+#prefer objects to be initialized using object initializers when possible
+dotnet_style_object_initializer = true:suggestion
+#prefer inferred tuple element names
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+
+#Style - implicit and explicit types
+
+#prefer var over explicit type in all cases, unless overridden by another code style rule
+csharp_style_var_elsewhere = true:suggestion
+#prefer var is used to declare variables with built-in system types such as int
+csharp_style_var_for_built_in_types = true:suggestion
+#prefer var when the type is already mentioned on the right-hand side of a declaration expression
+csharp_style_var_when_type_is_apparent = true:suggestion
+
+#Style - language keyword and framework type options
+
+#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+
+#Style - Miscellaneous preferences
+
+#prefer anonymous functions over local functions
+csharp_style_pattern_local_over_anonymous_function = false:suggestion
+
+#Style - Modifier preferences
+
+#when this rule is set to a list of modifiers, prefer the specified ordering.
+csharp_preferred_modifier_order = public,private,internal,protected,static,virtual,readonly,async,override,abstract,new:suggestion
+
+#Style - Pattern matching
+
+#prefer pattern matching instead of is expression with type casts
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+
+#Style - qualification options
+
+#prefer fields not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_field = false:suggestion
+#prefer methods not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_method = false:suggestion
+#prefer properties not to be prefaced with this. or Me. in Visual Basic
+dotnet_style_qualification_for_property = false:suggestion
+
+# Add copyright file header
+file_header_template = Apache-2.0 License\nCopyright (c) 2021-2022 zuohuaijun\n电话/微信:18020030720  QQ群:87333204

+ 0 - 1
Admin.NET/Admin.NET.Application/GlobalUsings.cs

@@ -3,7 +3,6 @@ global using Admin.NET.Core;
 global using Furion;
 global using Furion.DependencyInjection;
 global using Furion.DynamicApiController;
-global using Mapster;
 global using Microsoft.AspNetCore.Mvc;
 global using Microsoft.Extensions.DependencyInjection;
 global using SqlSugar;

+ 4 - 4
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -25,16 +25,16 @@
   <ItemGroup>
     <PackageReference Include="AspNetCoreRateLimit" Version="4.0.2" />
     <PackageReference Include="Caching.CSRedis" Version="3.8.668" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.4.0" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.4.0" />
-    <PackageReference Include="Furion.Pure" Version="4.4.0" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.4.2" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.4.2" />
+    <PackageReference Include="Furion.Pure" Version="4.4.2" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.6.4" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.6.4" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.8" />
     <PackageReference Include="NEST" Version="7.17.4" />
     <PackageReference Include="NETCore.MailKit" Version="2.1.0" />
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.1.8" />
-    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.16.0" />
+    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="2.17.0" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="2.12.0" />
     <PackageReference Include="SqlSugarCore" Version="5.1.2.7" />
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.20" />

+ 66 - 17
Admin.NET/Admin.NET.Core/Admin.NET.Core.xml

@@ -170,6 +170,11 @@
             开启操作日志
             </summary>
         </member>
+        <member name="F:Admin.NET.Core.CommonConst.SysSingleLogin">
+            <summary>
+            开启当用户登录
+            </summary>
+        </member>
         <member name="F:Admin.NET.Core.CommonConst.SysSensitiveDetection">
             <summary>
             开启全局脱敏处理(默认不开启)
@@ -1966,6 +1971,11 @@
             禁止删除默认租户
             </summary>
         </member>
+        <member name="F:Admin.NET.Core.ErrorCodeEnum.D1024">
+            <summary>
+            已将其他地方登录账号下线
+            </summary>
+        </member>
         <member name="F:Admin.NET.Core.ErrorCodeEnum.D2000">
             <summary>
             父机构不存在
@@ -3088,6 +3098,46 @@
             ES日志写入器
             </summary>
         </member>
+        <member name="T:Admin.NET.Core.LogContent">
+            <summary>
+            日志内容
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.LogName">
+            <summary>
+            记录器类别名称
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.LogLevel">
+            <summary>
+            日志级别
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.EventId">
+            <summary>
+             事件 Id
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.Exception">
+            <summary>
+            异常对象
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.Context">
+            <summary>
+            日志上下文
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.Message">
+            <summary>
+            日志消息
+            </summary>
+        </member>
+        <member name="P:Admin.NET.Core.LogContent.Time">
+            <summary>
+            日志时间
+            </summary>
+        </member>
         <member name="T:Admin.NET.Core.ElasticSearchSetup">
             <summary>
             ES服务注册
@@ -3143,21 +3193,6 @@
             数据库配置集合
             </summary>
         </member>
-        <member name="T:Admin.NET.Core.ElasticSearchOptions">
-            <summary>
-            ElasticSearch配置选项
-            </summary>
-        </member>
-        <member name="P:Admin.NET.Core.ElasticSearchOptions.ServerUris">
-            <summary>
-            ES地址集合
-            </summary>
-        </member>
-        <member name="P:Admin.NET.Core.ElasticSearchOptions.DefaultIndex">
-            <summary>
-            默认索引
-            </summary>
-        </member>
         <member name="T:Admin.NET.Core.EmailOptions">
             <summary>
             邮件配置选项
@@ -4489,9 +4524,9 @@
             <param name="input"></param>
             <returns></returns>
         </member>
-        <member name="M:Admin.NET.Core.Service.SysConfigService.GetConfigCache(System.String)">
+        <member name="M:Admin.NET.Core.Service.SysConfigService.GetConfigValue``1(System.String)">
             <summary>
-            获取参数配置缓存
+            获取参数配置
             </summary>
             <param name="code"></param>
             <returns></returns>
@@ -5862,6 +5897,20 @@
             <param name="user"></param>
             <returns></returns>
         </member>
+        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.PushNotice(Admin.NET.Core.SysNotice,System.Collections.Generic.List{System.Int64})">
+            <summary>
+            发送消息
+            </summary>
+            <param name="notice"></param>
+            <param name="userIds"></param>
+            <returns></returns>
+        </member>
+        <member name="M:Admin.NET.Core.Service.SysOnlineUserService.SignleLogin(System.Int64)">
+            <summary>
+            单用户登录
+            </summary>
+            <returns></returns>
+        </member>
         <member name="P:Admin.NET.Core.Service.OrgInput.Pid">
             <summary>
             父Id

+ 0 - 4
Admin.NET/Admin.NET.Core/AdminNETConfig.json

@@ -113,9 +113,5 @@
             "ClientId": "wxd77174ddc828b65b",
             "ClientSecret": "6224502b24d31acf8b4e0dc8e482317c"
         }
-    },
-    "ElasticSearch": {
-        "ServerUris": [ "http://dilon:123456@192.168.1.100:9200" ],
-        "DefaultIndex": "adminnet"
     }
 }

+ 5 - 0
Admin.NET/Admin.NET.Core/Const/CommonConst.cs

@@ -30,6 +30,11 @@ public class CommonConst
     /// </summary>
     public const string SysOpLog = "sys_op_log";
 
+    /// <summary>
+    /// 开启当用户登录
+    /// </summary>
+    public const string SysSingleLogin = "sys_single_login";
+
     /// <summary>
     /// 开启全局脱敏处理(默认不开启)
     /// </summary>

+ 2 - 2
Admin.NET/Admin.NET.Core/Entity/SysTimer.cs

@@ -41,8 +41,8 @@ public class SysTimer : EntityBase
     /// <summary>
     /// Cron表达式
     /// </summary>
-    [SugarColumn(ColumnDescription = "Cron表达式", Length = 32)]
-    [MaxLength(32)]
+    [SugarColumn(ColumnDescription = "Cron表达式", Length = 128)]
+    [MaxLength(128)]
     public string Cron { get; set; }
 
     /// <summary>

+ 6 - 0
Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs

@@ -150,6 +150,12 @@ public enum ErrorCodeEnum
     [ErrorCodeItemMetadata("禁止删除默认租户")]
     D1023,
 
+    /// <summary>
+    /// 已将其他地方登录账号下线
+    /// </summary>
+    [ErrorCodeItemMetadata("已将其他地方登录账号下线")]
+    D1024,
+
     /// <summary>
     /// 父机构不存在
     /// </summary>

+ 3 - 5
Admin.NET/Admin.NET.Core/Hub/ChatHub.cs

@@ -9,17 +9,17 @@ namespace Admin.NET.Core;
 [MapHub("/hubs/chathub")]
 public class ChatHub : Hub<IChatClient>
 {
-    private readonly ISysCacheService _cache;
+    private readonly ISysCacheService _sysCache;
     private readonly ISysMessageService _sysMessageService;
     private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
     private readonly IHubContext<ChatHub, IChatClient> _chatHubContext;
 
-    public ChatHub(ISysCacheService cache,
+    public ChatHub(ISysCacheService sysCache,
         ISysMessageService sysMessageService,
         SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
         IHubContext<ChatHub, IChatClient> chatHubContext)
     {
-        _cache = cache;
+        _sysCache = sysCache;
         _sysMessageService = sysMessageService;
         _sysOnlineUerRep = sysOnlineUerRep;
         _chatHubContext = chatHubContext;
@@ -42,7 +42,6 @@ public class ChatHub : Hub<IChatClient>
         var account = claims.FirstOrDefault(e => e.Type == ClaimConst.UserName)?.Value;
         var name = claims.FirstOrDefault(e => e.Type == ClaimConst.RealName)?.Value;
         var tenantId = claims.FirstOrDefault(e => e.Type == ClaimConst.TenantId)?.Value;
-        var onlineUsers = await _cache.GetAsync<List<SysOnlineUser>>(CacheConst.KeyOnlineUser) ?? new List<SysOnlineUser>();
         var user = new SysOnlineUser
         {
             ConnectionId = Context.ConnectionId,
@@ -69,7 +68,6 @@ public class ChatHub : Hub<IChatClient>
 
         //onlineUsers.Add();
         //await _cache.SetAsync($"{CacheConst.KeyOnlineUser}{ Context.ConnectionId}", user);
-
         //await _sendMessageService.SendMessageToUserByConnectionId("asdasd但凡生得分", "下线吧", MessageTypeEnum.Offline, Context.ConnectionId);
     }
 

+ 53 - 2
Admin.NET/Admin.NET.Core/Logging/ElasticSearchLoggingWriter.cs

@@ -1,4 +1,6 @@
-using Nest;
+using Microsoft.Extensions.Logging;
+using Nest;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
 
 namespace Admin.NET.Core;
 
@@ -16,6 +18,55 @@ public class ElasticSearchLoggingWriter : IDatabaseLoggingWriter
 
     public void Write(LogMessage logMsg, bool flush)
     {
-        _esClient.IndexDocument(JSON.Serialize(logMsg));
+        _esClient.IndexDocument(new LogContent
+        {
+            Time = DateTime.Now,
+            LogLevel = logMsg.LogLevel,
+            LogName = logMsg.LogName,
+            EventId = logMsg.EventId,
+            Message = logMsg.Message,
+            Exception = logMsg.Exception
+        });
     }
+}
+
+/// <summary>
+/// 日志内容
+/// </summary>
+public class LogContent
+{
+    /// <summary>
+    /// 记录器类别名称
+    /// </summary>
+    public string LogName { get; set; }
+
+    /// <summary>
+    /// 日志级别
+    /// </summary>
+    public LogLevel LogLevel { get; set; }
+
+    /// <summary>
+    ///  事件 Id
+    /// </summary>
+    public EventId EventId { get; set; }
+
+    /// <summary>
+    /// 异常对象
+    /// </summary>
+    public Exception Exception { get; set; }
+
+    /// <summary>
+    /// 日志上下文
+    /// </summary>
+    public LogContext Context { get; set; }
+
+    /// <summary>
+    /// 日志消息
+    /// </summary>
+    public string Message { get; internal set; }
+
+    /// <summary>
+    /// 日志时间
+    /// </summary>
+    public DateTime Time { get; internal set; }
 }

+ 7 - 3
Admin.NET/Admin.NET.Core/Logging/ElasticSearchSetup.cs

@@ -10,11 +10,15 @@ public static class ElasticSearchSetup
 {
     public static void AddElasticSearch(this IServiceCollection services)
     {
-        var elkOptions = App.GetOptions<ElasticSearchOptions>();
+        var enabled = App.GetConfig<bool>("Logging:ElasticSearch:Enabled");
+        if (!enabled) return;
 
-        var uris = elkOptions.ServerUris.Select(u => new Uri(u));
+        var serverUris = App.GetConfig<List<string>>("Logging:ElasticSearch:ServerUris");
+        var defaultIndex = App.GetConfig<string>("Logging:ElasticSearch:DefaultIndex");
+
+        var uris = serverUris.Select(u => new Uri(u));
         var connectionPool = new SniffingConnectionPool(uris);
-        var settings = new ConnectionSettings(connectionPool).DefaultIndex(elkOptions.DefaultIndex);
+        var settings = new ConnectionSettings(connectionPool).DefaultIndex(defaultIndex);
         var client = new ElasticClient(settings);
 
         services.AddSingleton(client); // 单例注册

+ 0 - 17
Admin.NET/Admin.NET.Core/Option/ElasticSearchOptions.cs

@@ -1,17 +0,0 @@
-namespace Admin.NET.Core;
-
-/// <summary>
-/// ElasticSearch配置选项
-/// </summary>
-public sealed class ElasticSearchOptions : IConfigurableOptions
-{
-    /// <summary>
-    /// ES地址集合
-    /// </summary>
-    public List<string> ServerUris { get; set; }
-
-    /// <summary>
-    /// 默认索引
-    /// </summary>
-    public string DefaultIndex { get; set; }
-}

+ 4 - 1
Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs

@@ -14,9 +14,12 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
         return new[]
         {
             new SysConfig{ Id=252885263003800, Name="演示环境", Code="sys_demo_env", Value="False", SysFlag=YesNoEnum.Y, Remark="演示环境", Order=1, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=252885263003801, Name="默认密码", Code="sys_default_password", Value="123456", SysFlag=YesNoEnum.Y, Remark="默认密码", Order=2, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=252885263003801, Name="默认密码", Code="sys_password", Value="123456", SysFlag=YesNoEnum.Y, Remark="默认密码", Order=2, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=252885263003802, Name="Token过期时间", Code="sys_token_expire", Value="10080", SysFlag=YesNoEnum.Y, Remark="Token过期时间", Order=3, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=252885263003803, Name="操作日志", Code="sys_op_log", Value="True", SysFlag=YesNoEnum.Y, Remark="开启操作日志", Order=4, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=252885263003804, Name="单用户登录", Code="sys_single_login", Value="True", SysFlag=YesNoEnum.Y, Remark="开启单用户登录", Order=5, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=252885263003804, Name="验证码", Code="sys_captcha", Value="True", SysFlag=YesNoEnum.Y, Remark="开启验证码", Order=6, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=252885263003804, Name="管理员角色编码", Code="sys_admin_role", Value="True", SysFlag=YesNoEnum.Y, Remark="管理员角色编码", Order=7, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
         };
     }
 }

+ 6 - 0
Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs

@@ -16,6 +16,7 @@ public class SysAuthService : IDynamicApiController, ITransient
     private readonly IEventPublisher _eventPublisher;
     private readonly SysUserService _sysUserService;
     private readonly SysUserRoleService _sysUserRoleService;
+    private readonly ISysOnlineUserService _sysOnlineUserService;
     private readonly IMemoryCache _cache;
 
     public SysAuthService(SqlSugarRepository<SysUser> sysUserRep,
@@ -25,6 +26,7 @@ public class SysAuthService : IDynamicApiController, ITransient
         IEventPublisher eventPublisher,
         SysUserService sysUserService,
         SysUserRoleService sysUserRoleService,
+        ISysOnlineUserService sysOnlineUserService,
         IMemoryCache cache)
     {
         _sysUserRep = sysUserRep;
@@ -34,6 +36,7 @@ public class SysAuthService : IDynamicApiController, ITransient
         _eventPublisher = eventPublisher;
         _sysUserService = sysUserService;
         _sysUserRoleService = sysUserRoleService;
+        _sysOnlineUserService = sysOnlineUserService;
         _cache = cache;
     }
 
@@ -59,6 +62,9 @@ public class SysAuthService : IDynamicApiController, ITransient
         if (user.Status == StatusEnum.Disable)
             throw Oops.Oh(ErrorCodeEnum.D1017);
 
+        // 单用户登录(强制下线其他地方登录账号)
+        await _sysOnlineUserService.SignleLogin(user.Id);
+
         // 生成Token令牌
         var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
         {

+ 4 - 4
Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs

@@ -102,20 +102,20 @@ public class SysConfigService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 获取参数配置缓存
+    /// 获取参数配置
     /// </summary>
     /// <param name="code"></param>
     /// <returns></returns>
     [NonAction]
-    public async Task<string> GetConfigCache(string code)
+    public async Task<T> GetConfigValue<T>(string code)
     {
         var value = await _sysCacheService.GetStringAsync(code);
         if (string.IsNullOrEmpty(value))
         {
             var config = await _sysConfigRep.GetFirstAsync(u => u.Code == code);
-            value = config != null ? config.Value : "";
+            value = config != null ? config.Value : default;
             await _sysCacheService.SetStringAsync(code, value);
         }
-        return value;
+        return (T)Convert.ChangeType(value, typeof(T));
     }
 }

+ 1 - 2
Admin.NET/Admin.NET.Core/Service/DataBase/SysDataBaseService.cs

@@ -205,8 +205,7 @@ public class SysDataBaseService : IDynamicApiController, ITransient
             m.DataType = CodeGenUtil.ConvertDataType(m.DataType);
         });
         var tContent = File.ReadAllText(templatePath);
-        var tResult = _viewEngine.RunCompileFromCached(tContent, new
-        {
+        var tResult = _viewEngine.RunCompileFromCached(tContent, new {
             input.TableName,
             input.EntityName,
             input.BaseClassName,

+ 2 - 4
Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs

@@ -144,8 +144,7 @@ public class SysDictDataService : IDynamicApiController, ITransient
             new JoinQueryInfos(JoinType.Left, a.Id == b.DictTypeId))
             .Where(a => a.Code == code)
             .Where((a, b) => a.Status == StatusEnum.Enable && b.Status == StatusEnum.Enable)
-            .Select((a, b) => new
-            {
+            .Select((a, b) => new {
                 Label = b.Value,
                 Value = b.Code
             }).ToListAsync();
@@ -163,8 +162,7 @@ public class SysDictDataService : IDynamicApiController, ITransient
             new JoinQueryInfos(JoinType.Left, a.Id == b.DictTypeId))
             .Where((a, b) => a.Code == input.Code)
             .WhereIF(input.Status.HasValue, (a, b) => b.Status == (StatusEnum)input.Status.Value)
-            .Select((a, b) => new
-            {
+            .Select((a, b) => new {
                 Label = b.Value,
                 Value = b.Code
             }).ToListAsync();

+ 2 - 4
Admin.NET/Admin.NET.Core/Service/Notice/SysNoticeService.cs

@@ -226,16 +226,14 @@ public class SysNoticeService : ISysNoticeService, IDynamicApiController, ITrans
         int index = 0;
         foreach (var item in dic)
         {
-            noticeClays.Add(new
-            {
+            noticeClays.Add(new {
                 Index = index++,
                 Key = item.Describe,
                 Value = item.Value,
                 NoticeData = notices.Where(m => m.Type == item.Value).ToList()
             });
         }
-        return new
-        {
+        return new {
             Rows = noticeClays,
             TotalRows = count
         };

+ 2 - 0
Admin.NET/Admin.NET.Core/Service/OnlineUser/ISysOnlineUserService.cs

@@ -7,4 +7,6 @@ public interface ISysOnlineUserService
     Task ForceExist(SysOnlineUser user);
 
     Task PushNotice(SysNotice notice, List<long> userIds);
+
+    Task SignleLogin(long userId);
 }

+ 30 - 5
Admin.NET/Admin.NET.Core/Service/OnlineUser/SysOnlineUserService.cs

@@ -8,17 +8,17 @@ namespace Admin.NET.Core.Service;
 [ApiDescriptionSettings(Order = 100)]
 public class SysOnlineUserService : ISysOnlineUserService, IDynamicApiController, ITransient
 {
-    private readonly ISysCacheService _sysCacheService;
     private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
     private readonly IHubContext<ChatHub, IChatClient> _chatHubContext;
+    private readonly SysConfigService _sysConfigService;
 
-    public SysOnlineUserService(ISysCacheService sysCacheService,
-        SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
-        IHubContext<ChatHub, IChatClient> chatHubContext)
+    public SysOnlineUserService(SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
+        IHubContext<ChatHub, IChatClient> chatHubContext,
+        SysConfigService sysConfigService)
     {
-        _sysCacheService = sysCacheService;
         _sysOnlineUerRep = sysOnlineUerRep;
         _chatHubContext = chatHubContext;
+        _sysConfigService = sysConfigService;
     }
 
     /// <summary>
@@ -44,6 +44,12 @@ public class SysOnlineUserService : ISysOnlineUserService, IDynamicApiController
         await _sysOnlineUerRep.DeleteAsync(user);
     }
 
+    /// <summary>
+    /// 发送消息
+    /// </summary>
+    /// <param name="notice"></param>
+    /// <param name="userIds"></param>
+    /// <returns></returns>
     [NonAction]
     public async Task PushNotice(SysNotice notice, List<long> userIds)
     {
@@ -56,4 +62,23 @@ public class SysOnlineUserService : ISysOnlineUserService, IDynamicApiController
             }
         }
     }
+
+    /// <summary>
+    /// 单用户登录
+    /// </summary>
+    /// <returns></returns>
+    [NonAction]
+    public async Task SignleLogin(long userId)
+    {
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysSingleLogin))
+        {
+            var onlineUsers = await _sysOnlineUerRep.GetListAsync();
+            if (onlineUsers == null) return;
+
+            var loginUser = onlineUsers.FirstOrDefault(u => u.UserId == userId);
+            if (loginUser == null) return;
+
+            await ForceExist(loginUser);
+        }
+    }
 }

+ 2 - 4
Admin.NET/Admin.NET.Core/Service/WeChat/WeChatPayService.cs

@@ -85,8 +85,7 @@ public class WeChatPayService : IDynamicApiController, ITransient
         };
         await _weChatPayUserRep.InsertAsync(wechatPay);
 
-        return new
-        {
+        return new {
             response.PrepayId,
             request.OutTradeNumber
         };
@@ -133,8 +132,7 @@ public class WeChatPayService : IDynamicApiController, ITransient
         };
         await _weChatPayUserRep.InsertAsync(wechatPay);
 
-        return new
-        {
+        return new {
             response.PrepayId,
             request.OutTradeNumber
         };

+ 1 - 2
Admin.NET/Admin.NET.Core/Service/WeChat/WeChatService.cs

@@ -80,8 +80,7 @@ public class WeChatService : IDynamicApiController, ITransient
         var wxUser = await _weChatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
         if (wxUser == null)
             throw Oops.Oh("微信登录");
-        return new
-        {
+        return new {
             wxUser.Avatar,
             accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
             {

+ 5 - 10
Admin.NET/Admin.NET.Core/Util/ServerUtil.cs

@@ -13,8 +13,7 @@ public class ServerUtil
     {
         var furionAssembly = typeof(Furion.App).Assembly.GetName();
         var sqlSugarAssembly = typeof(ISqlSugarClient).Assembly.GetName();
-        return new
-        {
+        return new {
             HostName = Environment.MachineName, // HostName
             SystemOs = RuntimeInformation.OSDescription, // 系统名称
             OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
@@ -32,8 +31,7 @@ public class ServerUtil
     public static dynamic GetServerUseInfo()
     {
         var ramInfo = GetRamInfo();
-        return new
-        {
+        return new {
             TotalRam = Math.Ceiling(ramInfo.Total / 1024).ToString() + " GB", // 总内存
             RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total) + " %", // 内存使用率
             CpuRate = Math.Ceiling(double.Parse(GetCpuRate())) + " %", // Cpu使用率
@@ -47,8 +45,7 @@ public class ServerUtil
     /// <returns></returns>
     public static async Task<dynamic> GetServerNetWorkInfo()
     {
-        return new
-        {
+        return new {
             WanIp = await GetWanIpFromPCOnline(), // 外网IP
             LocalIp = App.HttpContext?.Connection?.LocalIpAddress.ToString(),
             SendAndReceived = "", //"上行" + Math.Round(networkInfo.SendLength / 1024.0 / 1024 / 1024, 2) + "GB 下行" + Math.Round(networkInfo.ReceivedLength / 1024.0 / 1024 / 1024, 2) + "GB", // 上下行流量统计
@@ -67,8 +64,7 @@ public class ServerUtil
             var output = ShellUtil.Bash("free -m");
             var lines = output.Split("\n");
             var memory = lines[1].Split(" ", StringSplitOptions.RemoveEmptyEntries);
-            return new
-            {
+            return new {
                 Total = double.Parse(memory[1]),
                 Used = double.Parse(memory[2]),
                 Free = double.Parse(memory[3])
@@ -82,8 +78,7 @@ public class ServerUtil
             var totalMemoryParts = lines[1].Split("=", StringSplitOptions.RemoveEmptyEntries);
             var total = Math.Round(double.Parse(totalMemoryParts[1]) / 1024, 2);
             var free = Math.Round(double.Parse(freeMemoryParts[1]) / 1024, 2);
-            return new
-            {
+            return new {
                 Total = total,
                 Free = free,
                 Used = total - free

+ 1 - 1
Admin.NET/Admin.NET.UnitTest/Admin.NET.UnitTest.csproj

@@ -8,7 +8,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Furion.Pure.Xunit" Version="4.4.0" />
+    <PackageReference Include="Furion.Pure.Xunit" Version="4.4.2" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.8" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
     <PackageReference Include="xunit" Version="2.4.2" />

+ 0 - 1
Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs

@@ -26,7 +26,6 @@ public static class ProjectOptions
         services.AddConfigurableOptions<CodeGenOptions>();
         services.AddConfigurableOptions<EmailOptions>();
         services.AddConfigurableOptions<OAuthOptions>();
-        services.AddConfigurableOptions<ElasticSearchOptions>();
         //services.AddConfigurableOptions<IpRateLimitingOptions>();
         //services.AddConfigurableOptions<IpRateLimitPoliciesOptions>();
         //services.AddConfigurableOptions<ClientRateLimitingOptions>();

+ 27 - 43
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -1,7 +1,6 @@
 using Admin.NET.Core;
 using AspNetCoreRateLimit;
 using Furion;
-using Furion.Logging;
 using Furion.SpecificationDocument;
 using IGeekFan.AspNetCore.Knife4jUI;
 using Microsoft.AspNetCore.Builder;
@@ -88,16 +87,16 @@ public class Startup : AppStartup
         });
 
         // OSS对象存储(必须一个个赋值)
-        var opt = App.GetOptions<OSSProviderOptions>();
-        services.AddOSSService(opt.ProviderName, options =>
+        var ossOpt = App.GetOptions<OSSProviderOptions>();
+        services.AddOSSService(ossOpt.ProviderName, options =>
         {
-            options.Provider = opt.Provider;
-            options.Endpoint = opt.Endpoint;
-            options.AccessKey = opt.AccessKey;
-            options.SecretKey = opt.SecretKey;
-            options.Region = opt.Region;
-            options.IsEnableCache = opt.IsEnableCache;
-            options.IsEnableHttps = opt.IsEnableHttps;
+            options.Provider = ossOpt.Provider;
+            options.Endpoint = ossOpt.Endpoint;
+            options.AccessKey = ossOpt.AccessKey;
+            options.SecretKey = ossOpt.SecretKey;
+            options.Region = ossOpt.Region;
+            options.IsEnableCache = ossOpt.IsEnableCache;
+            options.IsEnableHttps = ossOpt.IsEnableHttps;
         });
 
         // 电子邮件
@@ -118,45 +117,30 @@ public class Startup : AppStartup
         // logo显示
         services.AddLogoDisplay();
 
-        // 日志写入文件-消息、警告、错误
-        Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel =>
+        // 日志记录
+        if (App.GetConfig<bool>("Logging:File:Enabled")) // 日志写入文件
         {
-            services.AddFileLogging("logs/{0:yyyyMMdd}_{1}.log", options =>
+            Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel =>
             {
-                options.FileNameRule = fileName => string.Format(fileName, DateTime.Now, logLevel.ToString()); // 每天创建一个文件
-                options.WriteFilter = logMsg => logMsg.LogLevel == logLevel; // 日志级别
-                options.FileSizeLimitBytes = 10 * 1024 * 1024; // 每个文件10M
-                options.MaxRollingFiles = 30; // 只保留30个文件
-                // 输出Json格式,对接阿里云日志、Elastaicsearch第三方日志
-                options.MessageFormat = (logMsg) =>
+                services.AddFileLogging(options =>
                 {
-                    return logMsg.WriteArray(writer =>
+                    options.FileNameRule = fileName => string.Format(fileName, DateTime.Now, logLevel.ToString()); // 每天创建一个文件
+                    options.WriteFilter = logMsg => logMsg.LogLevel == logLevel; // 日志级别
+                    options.HandleWriteError = (writeError) => // 写入失败时启用备用文件
                     {
-                        writer.WriteStringValue(DateTime.Now.ToString("o"));
-                        writer.WriteStringValue(logMsg.LogLevel.ToString());
-                        writer.WriteStringValue(logMsg.LogName);
-                        writer.WriteNumberValue(logMsg.EventId.Id);
-                        writer.WriteStringValue(logMsg.Message);
-                        writer.WriteStringValue(logMsg.Exception?.ToString());
-                    });
-                };
-                // 写入失败时启用备用文件
-                options.HandleWriteError = (writeError) =>
-                {
-                    writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
-                };
+                        writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
+                    };
+                });
             });
-        });
-        // 日志写入数据库
-        services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
+        }
+        if (App.GetConfig<bool>("Logging:Database:Enabled")) // 日志写入数据库
         {
-            options.MinimumLevel = LogLevel.Information;
-        });
-        //// 日志写入ElasticSearch
-        //services.AddDatabaseLogging<ESLoggingWriter>(options =>
-        //{
-        //    options.MinimumLevel = LogLevel.Information;
-        //});
+            services.AddDatabaseLogging<DatabaseLoggingWriter>();
+        }
+        if (App.GetConfig<bool>("Logging:ElasticSearch:Enabled")) // 日志写入ElasticSearch
+        {
+            services.AddDatabaseLogging<ElasticSearchLoggingWriter>();
+        }
 
         // 设置雪花Id算法机器码
         YitIdHelper.SetIdGenerator(new IdGeneratorOptions

+ 12 - 5
Admin.NET/Admin.NET.Web.Entry/appsettings.Development.json

@@ -10,15 +10,22 @@
             "Microsoft.Hosting.Lifetime": "Information"
         },
         "File": {
-            "FileName": "logs/info.log",
-            "Append": true,
-            "MinimumLevel": "Information",
-            "FileSizeLimitBytes": 5120,
-            "MaxRollingFiles": 30
+            "Enabled": true, // 启用文件日志
+            "FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件
+            "Append": true, // 追加覆盖
+            // "MinimumLevel": "Information", // 日志级别
+            "FileSizeLimitBytes": 10485760, // 10M=10*1024*1024
+            "MaxRollingFiles": 30 // 只保留30个文件
         },
         "Database": {
+            "Enabled": false, // 启用数据库日志
             "MinimumLevel": "Information"
         },
+        "ElasticSearch": {
+            "Enabled": false, // 启用ES日志
+            "ServerUris": [ "http://dilon:123456@192.168.1.100:9200" ], // 地址
+            "DefaultIndex": "adminnet" // 索引
+        },
         "Monitor": {
             "GlobalEnabled": true, // 启用全局拦截日志
             "IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效

+ 12 - 5
Admin.NET/Admin.NET.Web.Entry/appsettings.json

@@ -10,15 +10,22 @@
             "Microsoft.Hosting.Lifetime": "Information"
         },
         "File": {
-            "FileName": "logs/info.log",
-            "Append": true,
-            "MinimumLevel": "Information",
-            "FileSizeLimitBytes": 5120,
-            "MaxRollingFiles": 30
+            "Enabled": true, // 启用文件日志
+            "FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件
+            "Append": true, // 追加覆盖
+            // "MinimumLevel": "Information", // 日志级别
+            "FileSizeLimitBytes": 10485760, // 10M=10*1024*1024
+            "MaxRollingFiles": 30 // 只保留30个文件
         },
         "Database": {
+            "Enabled": false, // 启用数据库日志
             "MinimumLevel": "Information"
         },
+        "ElasticSearch": {
+            "Enabled": false, // 启用ES日志
+            "ServerUris": [ "http://dilon:123456@192.168.1.100:9200" ], // 地址
+            "DefaultIndex": "adminnet" // 索引
+        },
         "Monitor": {
             "GlobalEnabled": true, // 启用全局拦截日志
             "IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效

+ 5 - 0
Admin.NET/Admin.NET.sln

@@ -13,6 +13,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Web.Entry", "Admi
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.UnitTest", "Admin.NET.UnitTest\Admin.NET.UnitTest.csproj", "{0B2B5465-A4A7-4C1D-BD45-CF410E939D68}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{662E0B8E-F23E-4C7D-80BD-CAA5707503CC}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU