CrazyIter_Bin 2 éve
szülő
commit
161d115766

+ 1 - 4
TEAMModelOS.SDK/DI/AzureCosmos/AzureCosmosFactory.cs

@@ -10,20 +10,18 @@ namespace TEAMModelOS.SDK.DI
     {
         private readonly IServiceProvider _services;
         private readonly IOptionsMonitor<AzureCosmosFactoryOptions> _optionsMonitor;
-        private readonly ILogger _logger;
         //private Option _option;
         private ConcurrentDictionary<string, CosmosClient> CosmosClients { get; } = new ConcurrentDictionary<string, CosmosClient>();
         
         //   private CosmosDatabase database { get; set; }
         
-        public AzureCosmosFactory(IServiceProvider services, IOptionsMonitor<AzureCosmosFactoryOptions> optionsMonitor, ILogger<AzureCosmosFactory> logger)
+        public AzureCosmosFactory(IServiceProvider services, IOptionsMonitor<AzureCosmosFactoryOptions> optionsMonitor )
         {
             if (services == null) throw new ArgumentNullException(nameof(services));
             if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor));
 
             _services = services;
             _optionsMonitor = optionsMonitor;
-            _logger = logger;  
         }
 
         /// <summary>
@@ -43,7 +41,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (Exception e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }

+ 1 - 9
TEAMModelOS.SDK/DI/AzureStorage/AzureStorageFactory.cs

@@ -23,15 +23,13 @@ namespace TEAMModelOS.SDK.DI
     {
         private readonly IServiceProvider _services;
         private readonly IOptionsMonitor<AzureStorageFactoryOptions> _optionsMonitor;
-        private readonly ILogger _logger;
-        public AzureStorageFactory(IServiceProvider services, IOptionsMonitor<AzureStorageFactoryOptions> optionsMonitor, ILogger<AzureStorageFactory> logger)
+        public AzureStorageFactory(IServiceProvider services, IOptionsMonitor<AzureStorageFactoryOptions> optionsMonitor)
         {
             if (services == null) throw new ArgumentNullException(nameof(services));
             if (optionsMonitor == null) throw new ArgumentNullException(nameof(optionsMonitor));
 
             _services = services;
             _optionsMonitor = optionsMonitor;
-            _logger = logger;
         }
 
         public BlobServiceClient GetBlobServiceClient(string name = "Default")
@@ -43,7 +41,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
 
@@ -58,7 +55,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -73,7 +69,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -321,7 +316,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -342,7 +336,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }
@@ -363,7 +356,6 @@ namespace TEAMModelOS.SDK.DI
             }
             catch (OptionsValidationException e)
             {
-                _logger?.LogWarning(e, e.Message);
                 return null;
             }
         }

+ 146 - 0
TEAMModelOS.SDK/Extension/HttpContextExtensions.cs

@@ -1,7 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
+using System.Linq;
 using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
 using Microsoft.Extensions.Primitives;
 
 namespace TEAMModelOS.SDK.Extension
@@ -15,7 +20,148 @@ namespace TEAMModelOS.SDK.Extension
         {
             return httpContext.Request.Headers["Authorization"].ToString();
         }
+        /// <summary>
+        /// 获取 Action 特性
+        /// </summary>
+        /// <typeparam name="TAttribute"></typeparam>
+        /// <param name="httpContext"></param>
+        /// <returns></returns>
+        public static TAttribute GetMetadata<TAttribute>(this HttpContext httpContext)
+            where TAttribute : class
+        {
+            return httpContext.GetEndpoint()?.Metadata?.GetMetadata<TAttribute>();
+        }
+        /// <summary>
+        /// 获取 控制器/Action 描述器
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <returns></returns>
+        public static ControllerActionDescriptor GetControllerActionDescriptor(this HttpContext httpContext)
+        {
+            return httpContext.GetEndpoint()?.Metadata?.FirstOrDefault(u => u is ControllerActionDescriptor) as ControllerActionDescriptor;
+        }
+        /// <summary>
+        /// 获取本机 IPv4地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetLocalIpAddressToIPv4(this HttpContext context)
+        {
+            return context.Connection.LocalIpAddress?.MapToIPv4()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取本机 IPv6地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetLocalIpAddressToIPv6(this HttpContext context)
+        {
+            return context.Connection.LocalIpAddress?.MapToIPv6()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取远程 IPv4地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetRemoteIpAddressToIPv4(this HttpContext context)
+        {
+            return context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取远程 IPv6地址
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static string GetRemoteIpAddressToIPv6(this HttpContext context)
+        {
+            return context.Connection.RemoteIpAddress?.MapToIPv6()?.ToString();
+        }
+
+        /// <summary>
+        /// 获取完整请求地址
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public static string GetRequestUrlAddress(this HttpRequest request)
+        {
+            return new StringBuilder()
+                    .Append(request.Scheme)
+                    .Append("://")
+                    .Append(request.Host)
+                    .Append(request.PathBase)
+                    .Append(request.Path)
+                    .Append(request.QueryString)
+                    .ToString();
+        }
+
+        /// <summary>
+        /// 获取来源地址
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="refererHeaderKey"></param>
+        /// <returns></returns>
+        public static string GetRefererUrlAddress(this HttpRequest request, string refererHeaderKey = "Referer")
+        {
+            return request.Headers[refererHeaderKey].ToString();
+        }
 
+        /// <summary>
+        /// 读取 Body 内容
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
+        /// <returns></returns>
+        public static async Task<string> ReadBodyContentAsync(this HttpContext httpContext)
+        {
+            if (httpContext == null) return default;
+            return await httpContext.Request.ReadBodyContentAsync();
+        }
+
+        /// <summary>
+        /// 读取 Body 内容
+        /// </summary>
+        /// <param name="request"></param>
+        /// <remarks>需先在 Startup 的 Configure 中注册 app.EnableBuffering()</remarks>
+        /// <returns></returns>
+        public static async Task<string> ReadBodyContentAsync(this HttpRequest request)
+        {
+            request.Body.Seek(0, SeekOrigin.Begin);
+
+            using var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true);
+            var body = await reader.ReadToEndAsync();
+
+            // 回到顶部,解决此类问题 https://gitee.com/dotnetchina/Furion/issues/I6NX9E
+            request.Body.Seek(0, SeekOrigin.Begin);
+            return body;
+        }
+
+      
+        /// <summary>
+        /// 判断是否是 WebSocket 请求
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public static bool IsWebSocketRequest(this HttpContext context)
+        {
+            return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws";
+        }
+        /// <summary>
+        /// 设置响应头 Tokens
+        /// </summary>
+        /// <param name="httpContext"></param>
+        /// <param name="accessToken"></param>
+        /// <param name="refreshToken"></param>
+        public static void SetTokensOfResponseHeaders(this HttpContext httpContext, string accessToken, string refreshToken = null)
+        {
+            httpContext.Response.Headers["access-token"] = accessToken;
+            if (!string.IsNullOrWhiteSpace(refreshToken))
+            {
+                httpContext.Response.Headers["x-access-token"] = refreshToken;
+            }
+        }
         /// <summary>
         /// 取得JWT驗證金鑰,Authorization Bearer
         /// </summary>

+ 32 - 0
TEAMModelOS/Filter/AspNetCoreBuilderServiceCollectionExtensions.cs

@@ -0,0 +1,32 @@
+using Google.Protobuf.WellKnownTypes;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+namespace TEAMModelOS.Filter
+{
+    public static class AspNetCoreBuilderServiceCollectionExtensions
+    {  /// <summary>
+       /// 注册 Mvc 过滤器
+       /// </summary>
+       /// <typeparam name="TFilter"></typeparam>
+       /// <param name="services"></param>
+       /// <param name="configure"></param>
+       /// <returns></returns>
+        public static IServiceCollection AddMvcFilter<TFilter>(this IServiceCollection services, Action<MvcOptions> configure = default)
+            where TFilter : IFilterMetadata
+        {
+            services.Configure<MvcOptions>(options =>
+            {
+                options.Filters.Add<TFilter>();
+
+                // 其他额外配置
+                configure?.Invoke(options);
+            });
+
+            return services;
+        }
+    }
+
+}

+ 80 - 0
TEAMModelOS/Filter/BlobLoggerProvider.cs

@@ -0,0 +1,80 @@
+using Azure.Storage.Blobs;
+using Azure.Storage.Blobs.Models;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.IO;
+using System.Text;
+using System;
+using TEAMModelOS.SDK.DI;
+using Azure;
+using Azure.Storage.Blobs.Specialized;
+
+namespace TEAMModelOS.Filter
+{
+    public class BlobLoggerProvider: ILoggerProvider
+    {
+        private readonly AzureStorageFactory _storageFactory;
+
+        public BlobLoggerProvider(AzureStorageFactory storageFactory)
+        {
+            _storageFactory=storageFactory;
+            var container = _storageFactory.GetBlobContainerClient("0-service-log");
+            container.CreateIfNotExists(PublicAccessType.None); //嘗試創建School容器,如存在則不做任何事,保障容器一定存在
+        }
+
+        public ILogger CreateLogger(string categoryName)
+        {
+            return new BlobLogger(categoryName, _storageFactory.GetBlobContainerClient("0-service-log"));
+        }
+
+        public void Dispose()
+        {
+            // Dispose any resources used by the provider.
+        }
+    }
+
+    public class BlobLogger : ILogger
+    {
+        private readonly string _categoryName;
+        private readonly BlobContainerClient _containerClient;
+
+        public BlobLogger(string categoryName, BlobContainerClient containerClient)
+        {
+            _categoryName = categoryName;
+            _containerClient = containerClient;
+        }
+
+        public IDisposable BeginScope<TState>(TState state)
+        {
+            return null;
+        }
+
+        public bool IsEnabled(LogLevel logLevel)
+        {
+            return true;
+        }
+
+        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+        {
+            if (formatter == null)
+            {
+                throw new ArgumentNullException(nameof(formatter));
+            }
+
+            var message = formatter(state, exception);
+            var appendBlob = _containerClient.GetAppendBlobClient($"{_categoryName}/{DateTimeOffset.UtcNow:yyyy-MM-dd}.log");
+            // var blobClient = _containerClient.GetBlobClient($"{_categoryName}/{DateTimeOffset.UtcNow:yyyy-MM-dd}.log");
+            if (!appendBlob.Exists())
+            {
+                appendBlob.Create();
+                using var stream = new MemoryStream(Encoding.UTF8.GetBytes(message));
+                appendBlob.AppendBlock(stream);
+            }
+            else {
+                using var stream = new MemoryStream(Encoding.UTF8.GetBytes($"\n,{message}"));
+                appendBlob.AppendBlock(stream);
+            }
+            
+        }
+    }
+}

+ 82 - 0
TEAMModelOS/Filter/RequestAuditFilter.cs

@@ -0,0 +1,82 @@
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System.Security.Claims;
+using System;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK.Extension;
+using Microsoft.Extensions.Logging;
+using TEAMModelOS.SDK;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using DocumentFormat.OpenXml.Wordprocessing;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using Azure.Core;
+using DocumentFormat.OpenXml.Office2016.Excel;
+
+namespace TEAMModelOS.Filter
+{
+    public class RequestAuditFilter : IAsyncActionFilter
+    {
+        private readonly ILogger _logger;
+      
+        public RequestAuditFilter(ILogger<RequestAuditFilter> logger)
+        {
+            
+            _logger = logger;
+        }
+        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+        {
+            //============== 这里是执行方法之前获取数据 ====================
+
+            // 获取控制器、路由信息
+            var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
+
+            // 获取请求的方法
+            var method = actionDescriptor.MethodInfo;
+
+            // 获取 HttpContext 和 HttpRequest 对象
+            var httpContext = context.HttpContext;
+            var httpRequest = httpContext.Request;
+
+            // 获取客户端 Ipv4 地址
+            var remoteIPv4 = httpContext.GetRemoteIpAddressToIPv4();
+
+            // 获取请求的 Url 地址
+           // var requestUrl = httpRequest.GetRequestUrlAddress();
+
+            
+            // 获取来源 Url 地址
+           //var refererUrl = httpRequest.GetRefererUrlAddress();
+
+            // 获取请求参数(写入日志,需序列化成字符串后存储)
+            var parameters = context.ActionArguments;
+
+            // 获取操作人(必须授权访问才有值)"userId" 为你存储的 claims type,jwt 授权对应的是 payload 中存储的键名
+            var userId = httpContext.User?.FindFirstValue("userId");
+            var authtoken = context.HttpContext.GetXAuth("AuthToken");
+            string id = string.Empty, name = string.Empty, picture = string.Empty, school = string.Empty;
+            if (!string.IsNullOrWhiteSpace(authtoken)) {
+                var jwt = new JwtSecurityTokenHandler().ReadJwtToken(authtoken);
+                id = jwt.Payload.Sub;
+                school = jwt.Payload.Azp;
+                name = jwt.Claims.FirstOrDefault(claim => claim.Type.Equals("name"))?.Value;
+            }
+            // 请求时间
+            var requestedTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+
+            //============== 这里是执行方法之后获取数据 ====================
+            var actionContext = await next();
+
+            // 获取返回的结果
+            var returnResult = actionContext.Result;
+
+            // 判断是否请求成功,没有异常就是请求成功
+            var isRequestSucceed = actionContext.Exception == null;
+
+            // 获取调用堆栈信息,提供更加简单明了的调用和异常堆栈
+           // var stackTrace = EnhancedStackTrace.Current();
+           // string region = await _searcher.SearchIpAsync(remoteIPv4);
+            _logger.LogInformation(new{ip=remoteIPv4,time=requestedTime,path =$"{httpRequest.PathBase}{httpRequest.Path}",host= httpRequest.Host, param=parameters,id ,name ,school,succeed =isRequestSucceed }.ToJsonString());
+        }
+    }
+}

+ 3 - 0
TEAMModelOS/Startup.cs

@@ -25,6 +25,7 @@ using Microsoft.AspNetCore.StaticFiles;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Primitives;
 using Microsoft.IdentityModel.Tokens;
 using TEAMModelOS.Controllers;
@@ -171,6 +172,8 @@ namespace TEAMModelOS
             services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
             services.AddXkwAPIHttpService(Configuration);
             //services.AddHostedService<>
+            services.AddSingleton<ILoggerProvider, BlobLoggerProvider>();
+            services.AddMvcFilter<RequestAuditFilter>();
         }
 
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.