Browse Source

Import audit; Compile Invoke in ApiBind

HOME 2 years ago
parent
commit
79747b7a20

+ 3 - 6
VCommon.Ioc/InterceptionBehaviorAdapter.cs

@@ -18,19 +18,16 @@ namespace VCommon.Ioc
 
         public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
         {
-            long msUsed = 0;
-            var begin = DateTime.Now;
-
             var svcClass = input.Target.GetType();
             var svcMethod = input.MethodBase;
             IReadOnlyDictionary<string, object> paramDic = Enumerable.Range(0, input.Arguments.Count).ToDictionary(p => input.Arguments.ParameterName(p), p => input.Arguments[p]);
 
-            _interceptor.BeforeInvoke(begin, svcClass, svcMethod, paramDic);
+            _interceptor.BeforeInvoke(svcClass, svcMethod, paramDic);
 
             IMethodReturn result;
-            using (new TimingMeasureAction(p => msUsed = p)) result = getNext()(input, getNext);
+             result = getNext()(input, getNext);
 
-            _interceptor.AfterInvoke(begin, msUsed, svcClass, svcMethod, paramDic, result.ReturnValue, result.Exception);
+            _interceptor.AfterInvoke(svcClass, svcMethod, paramDic, result.ReturnValue, result.Exception);
 
             return result;
         }

+ 2 - 2
VCommon.Ioc/Interfaces.cs

@@ -22,7 +22,7 @@ namespace VCommon.Ioc
     /// <summary> 拦截器 </summary>
     public interface IInterceptor
     {
-        void BeforeInvoke(DateTime begin, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic);
-        void AfterInvoke(DateTime begin, long msUsed, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object resultReturnValue, Exception resultException);
+        void BeforeInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic);
+        void AfterInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object resultReturnValue, Exception resultException);
     }
 }

+ 3 - 1
VCommon.Ioc/IocManager.cs

@@ -115,11 +115,13 @@ namespace VCommon.Ioc
             children.RegisterInstanceToContainer<IIocManager>(children);
             return children;
         }
+
+        public void Dispose() => Container?.Dispose();
     }
 
     //TOD: Child Container
 
-    public interface IIocManager
+    public interface IIocManager:IDisposable
     {
         bool IsRegistered(Type type);
 

+ 6 - 2
VCommon.Logging/VJsonFileLogger.cs

@@ -8,16 +8,20 @@ namespace VCommon.Logging
 {
     public class VJsonFileLogger : FileLogger
     {
+        private readonly bool _includeSourceStackFrameOnly;
         private readonly IVJsonSerializer _jsonSerializer;
 
-        public VJsonFileLogger(IVJsonSerializer jsonSerializer = null, int mbSplit = 10, int? preserveDays = null, string logTo = null, bool enableAll = false) : base(mbSplit, preserveDays, logTo, enableAll)
+        public VJsonFileLogger(IVJsonSerializer jsonSerializer = null,
+            int mbSplit = 10, int? preserveDays = null, string logTo = null, bool enableAll = false,
+            bool includeSourceStackFrameOnly = true) : base(mbSplit, preserveDays, logTo, enableAll)
         {
+            _includeSourceStackFrameOnly = includeSourceStackFrameOnly;
             _jsonSerializer = jsonSerializer ?? VJsonLogSerializer.Instance;
         }
 
         protected override string FormatMessage(DateTime time, Level level, string summary, object moreInfo)
         {
-            var logAt = new StackTrace(true).GetFormattedDetailStackTraceText(typeof(Logger));
+            var logAt = new StackTrace(true).GetFormattedDetailStackTraceText(typeof(Logger),_includeSourceStackFrameOnly);
 
             var obj = new { moreInfo, logAt };
 

+ 5 - 3
VCommon.VApplication/ApplicationServiceBase.cs

@@ -1,10 +1,12 @@
 using Unity;
+using VCommon.Ioc;
 
 namespace VCommon.VApplication
 {
     public abstract class ApplicationServiceBase
     {
-        [Dependency]
-        public IVSession VSession { get; set; }
+        [Dependency] public IVSession VSession { get; set; }
+
+        [Dependency] public IIocManager IocManager { get; set; }
     }
-}
+}

+ 33 - 39
VCommon.VApplication/Auditing/ApplicationServiceAuditInterceptorBase.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Reflection;
+using Unity;
 using VCommon.Collections;
 using VCommon.Ioc;
 using VCommon.Logging;
@@ -16,19 +17,12 @@ namespace VCommon.VApplication.Auditing
 {
     public abstract class ApplicationServiceAuditInterceptorBase : IInterceptor, ITransientIocClass
     {
-        private readonly IVSession _session;
-        private readonly IIocManager _iocManager;
-
         private Guid? _beforeTenantId;
         private Guid? _beforeUserId;
 
-        protected ApplicationServiceAuditInterceptorBase(IVSession session, IIocManager iocManager)
-        {
-            _session = session;
-            _iocManager = iocManager;
-        }
-
-        // --------
+        [Dependency] public IVSession Session { get; set; }
+        [Dependency] public IIocManager IocManager { get; set; }
+        [Dependency] public ServiceInvokeTiming InvokeTiming { get; set; }
 
         //built-in PreProcess Interceptor
 
@@ -53,56 +47,56 @@ namespace VCommon.VApplication.Auditing
             }
         }
 
-        protected void AuthPermissionIntercept(DateTime time, Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
+        protected void AuthPermissionIntercept(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
         {
             var svcAuthInClass = svcClass.GetCustomAttributeIncludeInterface<VServiceAuthorizeAttribute>();
-            if (0 != svcAuthInClass.Length) _session.DemandAuth();
+            if (0 != svcAuthInClass.Length) Session.DemandAuth();
 
             var svcAuthInMethod = svcMethod.GetCustomAttribute<VServiceAuthorizeAttribute>(true);
-            if (null != svcAuthInMethod) _session.DemandAuth();
+            if (null != svcAuthInMethod) Session.DemandAuth();
 
-            if (false == _iocManager.IsRegistered<IPermissionManager>()) return;
+            if (false == IocManager.IsRegistered<IPermissionManager>()) return;
 
             if (0 == svcAuthInClass.Length && null == svcAuthInMethod) return;
 
             if (0 == svcAuthInClass.Sum(p => p.AnyPermissionsRequired.Length)
                 && 0 == (svcAuthInMethod?.AnyPermissionsRequired.Length ?? 0)) return;
 
-            var permMgr = _iocManager.Resolve<IPermissionManager>();
+            var permMgr = IocManager.Resolve<IPermissionManager>();
 
-            if ((0 == svcAuthInClass.Length || permMgr.CheckPermission(_session.GetTenantId(), _session.GetUserId(), svcAuthInClass.SelectMany(p => p.AnyPermissionsRequired).ToArray())) &&
-                (null == svcAuthInMethod || permMgr.CheckPermission(_session.GetTenantId(), _session.GetUserId(), svcAuthInMethod.AnyPermissionsRequired))) return;
+            if ((0 == svcAuthInClass.Length || permMgr.CheckPermission(Session.GetTenantId(), Session.GetUserId(), svcAuthInClass.SelectMany(p => p.AnyPermissionsRequired).ToArray())) &&
+                (null == svcAuthInMethod || permMgr.CheckPermission(Session.GetTenantId(), Session.GetUserId(), svcAuthInMethod.AnyPermissionsRequired))) return;
 
             var vrwFriendlyException = new VApplicationAuthException("拒绝访问:您没有权限进行此操作", AuthReason.AccessDenied);
 
-            PreWriteAuditLog(time, -1, tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);
+            PreWriteAuditLog(tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);
 
             throw vrwFriendlyException;
         }
 
-        protected void AuthOrgIntercept(DateTime time, Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
+        protected void AuthOrgIntercept(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
         {
             var mustHaveOrg = paramDic.Values.OfType<IMustHaveOrg>().ToArray();
             if (mustHaveOrg.Length == 0) return;
 
-            if (false == _iocManager.IsRegistered<IOrgManager>()) return;
+            if (false == IocManager.IsRegistered<IOrgManager>()) return;
 
-            var orgMgr = _iocManager.Resolve<IOrgManager>();
+            var orgMgr = IocManager.Resolve<IOrgManager>();
             if (false == orgMgr.Enabled) return;
 
-            var uvOrg = orgMgr.GetUserVisionOrgs(_session.GetTenantId(), _session.GetUserId()).ToArray();
+            var uvOrg = orgMgr.GetUserVisionOrgs(Session.GetTenantId(), Session.GetUserId()).ToArray();
             if (mustHaveOrg.All(argument => argument.OrgId.In(uvOrg))) return;
 
             var vrwFriendlyException = new VApplicationAuthException("拒绝访问:您没有权限操作此对象", AuthReason.AccessDenied);
 
-            PreWriteAuditLog(time, -1, tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);
+            PreWriteAuditLog(tenantId, userId, svcClass, svcMethod, paramDic, null, vrwFriendlyException);
 
             throw vrwFriendlyException;
         }
 
         //built-in PostProcess LogAction
 
-        protected void LogServiceInvoke(DateTime time, long msUsed, Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
+        protected void LogServiceInvoke(Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
         {
             object ex = exception;
 
@@ -122,45 +116,45 @@ namespace VCommon.VApplication.Auditing
                 });
             }
 
-            PreWriteAuditLog(time, msUsed, tenantIdToLog, userIdToLog, svcClass, svcMethod, paramDic, returnValue, ex);
+            PreWriteAuditLog(tenantIdToLog, userIdToLog, svcClass, svcMethod, paramDic, returnValue, ex);
         }
 
         //ovr able
 
-        public void BeforeInvoke(DateTime time, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
+        public void BeforeInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
         {
-            _beforeTenantId = _session.TenantId;
-            _beforeUserId = _session.UserId;
+            _beforeTenantId = Session.TenantId;
+            _beforeUserId = Session.UserId;
 
-            BeforeInvokeService(time, _beforeTenantId, _beforeUserId, svcClass, svcMethod, paramDic);
+            BeforeInvokeService(_beforeTenantId, _beforeUserId, svcClass, svcMethod, paramDic);
         }
 
-        public void AfterInvoke(DateTime time, long msUsed, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
+        public void AfterInvoke(Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
         {
-            AfterInvokeService(time, msUsed, _beforeTenantId, _beforeUserId, _session.TenantId, _session.UserId, svcClass, svcMethod, paramDic, returnValue, exception);
+            AfterInvokeService(_beforeTenantId, _beforeUserId, Session.TenantId, Session.UserId, svcClass, svcMethod, paramDic, returnValue, exception);
         }
 
-        public virtual void BeforeInvokeService(DateTime time, Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
+        public virtual void BeforeInvokeService(Guid? tenantId, Guid? userId, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic)
         {
             InputValidationIntercept(paramDic);
-            AuthPermissionIntercept(time, tenantId, userId, svcClass, svcMethod, paramDic);
-            AuthOrgIntercept(time, tenantId, userId, svcClass, svcMethod, paramDic);
+            AuthPermissionIntercept(tenantId, userId, svcClass, svcMethod, paramDic);
+            AuthOrgIntercept(tenantId, userId, svcClass, svcMethod, paramDic);
         }
 
-        public virtual void AfterInvokeService(DateTime time, long msUsed, Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
+        public virtual void AfterInvokeService(Guid? tenantIdBefore, Guid? userIdBefore, Guid? tenantIdAfter, Guid? userIdAfter, Type svcClass, MethodBase svcMethod, IReadOnlyDictionary<string, object> paramDic, object returnValue, Exception exception)
         {
-            LogServiceInvoke(time, msUsed, tenantIdBefore, userIdBefore, tenantIdAfter, userIdAfter, svcClass, svcMethod, paramDic, returnValue, exception);
+            LogServiceInvoke(tenantIdBefore, userIdBefore, tenantIdAfter, userIdAfter, svcClass, svcMethod, paramDic, returnValue, exception);
         }
 
-        protected virtual void PreWriteAuditLog(DateTime time, long msUsed, Guid? tenantId, Guid? userId, Type serviceClass, MethodBase serviceMethod, IReadOnlyDictionary<string, object> input, object output, object exception)
+        protected virtual void PreWriteAuditLog(Guid? tenantId, Guid? userId, Type serviceClass, MethodBase serviceMethod, IReadOnlyDictionary<string, object> input, object output, object exception)
         {
             if (serviceClass.IsDefinedIncludeInterface(typeof(DisableServiceAuditingLogAttribute)) || serviceMethod.IsDefined(typeof(DisableServiceAuditingLogAttribute))) return;
 
-            WriteAuditLog(time, msUsed, tenantId, userId, serviceClass.FullName, serviceMethod.Name, input, output, exception);
+            WriteAuditLog(tenantId, userId, serviceClass.FullName, serviceMethod.Name, input, output, exception);
         }
 
         //abstract
 
-        protected abstract void WriteAuditLog(DateTime time, long msUsed, Guid? tenantId, Guid? userId, string serviceClassName, string serviceMethodName, IReadOnlyDictionary<string, object> input, object output, object exception);
+        protected abstract void WriteAuditLog(Guid? tenantId, Guid? userId, string serviceClassName, string serviceMethodName, IReadOnlyDictionary<string, object> input, object output, object exception);
     }
 }

+ 13 - 0
VCommon.VApplication/Auditing/DataAnnotations/SimplifyAuditingLogAttribute.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace VCommon.VApplication.Auditing.DataAnnotations
+{
+    /// <summary> 简化写入审计日志, 当序列化结果超过指定值时,简化信息并带上类名 如集合只保留个数,字符串只保留长度 </summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
+    public sealed class SimplifyAuditingLogAttribute : Attribute
+    {
+        public int MaxLengthAllowed { get; }
+
+        public SimplifyAuditingLogAttribute(int maxLengthAllowed = 1024) => MaxLengthAllowed = maxLengthAllowed;
+    }
+}

+ 34 - 0
VCommon.VApplication/Auditing/Json/JsonApplicationServiceAuditInterceptorBase.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using VCommon.Logging;
+
+namespace VCommon.VApplication.Auditing.Json
+{
+    public abstract class JsonApplicationServiceAuditInterceptorBase : ApplicationServiceAuditInterceptorBase
+    {
+        private static readonly JsonAuditLogSerializer JsonSerializer = new JsonAuditLogSerializer();
+
+        private static string TryJsonSerialize(object obj)
+        {
+            try
+            {
+                return JsonSerializer.SerializeObject(obj);
+            }
+            catch (Exception e)
+            {
+                Logger.Error("Audit json serialize fail", e);
+                return JsonSerializer.SerializeObject(obj?.ToString());
+            }
+        }
+
+        protected override void WriteAuditLog(Guid? tenantId, Guid? userId, string serviceClassName, string serviceMethodName, IReadOnlyDictionary<string, object> input, object output, object exception)
+        {
+            WriteJsonAuditLog(tenantId, userId, serviceClassName, serviceMethodName,
+                JsonSerializer.SerializeObject(input),
+                JsonSerializer.SerializeObject(output),
+                TryJsonSerialize(exception));
+        }
+
+        protected abstract void WriteJsonAuditLog(Guid? tenantId, Guid? userId, string serviceClassName, string serviceMethodName, string input, string output, string exception);
+    }
+}

+ 34 - 0
VCommon.VApplication/Auditing/Json/JsonAuditLogContractResolver.cs

@@ -0,0 +1,34 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using VCommon.VApplication.Auditing.DataAnnotations;
+
+namespace VCommon.VApplication.Auditing.Json
+{
+    internal class JsonAuditLogContractResolver : DefaultContractResolver
+    {
+        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
+        {
+            var properties = base.CreateProperties(type, memberSerialization);
+
+            foreach (var prop in properties)
+            {
+                var attrs = prop.AttributeProvider.GetAttributes(true);
+
+                //检测并禁止JsonIgnore, 提示使用DisableAuditingAttribute
+                if (attrs.Any(p => p is JsonIgnoreAttribute)) throw new VAuditModelException($"检测到{nameof(JsonIgnoreAttribute)}特性,要禁用审计日志请使用{nameof(DisableAuditingLogAttribute)}代替. 类:{type.FullName},成员:{prop.UnderlyingName}");
+
+                //用DisableAuditing代替JsonIgnore
+                if (attrs.Any(p => p is DisableAuditingLogAttribute)) prop.Ignored = true;
+
+                //实现SimplifyAuditing
+                var sim = attrs.OfType<SimplifyAuditingLogAttribute>().SingleOrDefault();
+                if (null != sim) prop.Converter = new JsonSimplifyAuditingConverter(sim.MaxLengthAllowed);
+            }
+
+            return properties;
+        }
+    }
+}

+ 21 - 0
VCommon.VApplication/Auditing/Json/JsonAuditLogSerializer.cs

@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using VCommon.Json;
+
+namespace VCommon.VApplication.Auditing.Json
+{
+    internal class JsonAuditLogSerializer
+    {
+        private readonly IVJsonSerializer _jsonSerializer = new VJsonSerializer(new JsonSerializerSettings
+        {
+            NullValueHandling = NullValueHandling.Ignore,
+            Converters = new JsonConverter[]
+            {
+                new StringEnumConverter()
+            },
+            ContractResolver = new JsonAuditLogContractResolver(),
+        });
+
+        public string SerializeObject(object obj) => _jsonSerializer.SerializeObject(obj);
+    }
+}

+ 47 - 0
VCommon.VApplication/Auditing/Json/JsonSimplifyAuditingConverter.cs

@@ -0,0 +1,47 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections;
+using System.Linq;
+
+namespace VCommon.VApplication.Auditing.Json
+{
+    internal class JsonSimplifyAuditingConverter : JsonConverter
+    {
+        private readonly int _maxLengthAllowed;
+
+        public JsonSimplifyAuditingConverter(int maxLengthAllowed) => _maxLengthAllowed = maxLengthAllowed;
+
+        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+        {
+            var jToken = JToken.FromObject(value, serializer);
+            var json = jToken.ToString(Formatting.None);
+
+            var length = json.Length;
+
+            if (length <= _maxLengthAllowed)
+            {
+                writer.WriteRawValue(json);
+            }
+            else
+            {
+                //TODO: 进一步调整简化后的内容
+
+                var type = value.GetType().FullName;
+                int? count = null;
+
+                //集合
+                if (value is IEnumerable collection) count = collection.Cast<object>().Count();
+
+                //字符串
+                //一般对象
+
+                JToken.FromObject(new { Count = count, Type = type, Length = length, }).WriteTo(writer);
+            }
+        }
+
+        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
+
+        public override bool CanConvert(Type objectType) => true;
+    }
+}

+ 11 - 0
VCommon.VApplication/Auditing/ServiceInvokeTiming.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace VCommon.VApplication.Auditing
+{
+    public class ServiceInvokeTiming
+    {
+        public DateTime BeginInvokeTime { get; private set; } = DateTime.Now;
+
+        public TimeSpan Elapsed => DateTime.Now - BeginInvokeTime;
+    }
+}

+ 20 - 4
VCommon.VApplication/EntityFramework/VAppDbContextBase.cs

@@ -1,18 +1,26 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Microsoft.EntityFrameworkCore;
 using Unity;
 using VCommon.Ioc;
 using VCommon.VEntity;
 using VCommon.VEntityFrameworkCore;
 
-namespace VCommon.VApplication.EntityFramework
+namespace VCommon.VApplication.EntityFrameworkCore
 {
     public abstract class VAppDbContextBase : VEfDbContextBase, ITransientIocClass
     {
+
+        protected VAppDbContextBase()
+        {
+
+        }
+
+        protected VAppDbContextBase(DbContextOptions options) : base(options)
+        {
+
+        }
+
         [Dependency]
         public IVSession VSession { get; set; }
 
@@ -26,6 +34,14 @@ namespace VCommon.VApplication.EntityFramework
                 if (track.Entity is IHaveLastModificationUserId lmu) lmu.LastModificationUserId = VSession.UserId;
             }
 
+            if (VSession.TenantId.HasValue)
+            {
+                foreach (var track in ChangeTracker.Entries<IMustHaveTenant>().Where(p => p.State == EntityState.Added))
+                {
+                    track.Entity.TenantId = VSession.TenantId.Value;
+                }
+            }
+
             return base.SaveChanges();
         }
     }

+ 1 - 1
VCommon.VApplication/EntityFramework/VAppEfRepository.cs

@@ -4,7 +4,7 @@ using VCommon.Ioc;
 using VCommon.VEntity;
 using VCommon.VEntityFrameworkCore;
 
-namespace VCommon.VApplication.EntityFramework
+namespace VCommon.VApplication.EntityFrameworkCore
 {
     public class VAppEfRepository<TDbContext, TEntity> : VEfRepositoryBase<TDbContext, TEntity> where TEntity : VEntityBase where TDbContext : VAppDbContextBase
     {

+ 0 - 17
VCommon.VApplication/VCommon - Backup.VApplication.csproj

@@ -1,17 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <Folder Include="Dto\" />
-    <Folder Include="DataAnnotations\" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\VCommon.VEntity\VCommon.VEntity.csproj" />
-    <ProjectReference Include="..\VCommon\VCommon.csproj" />
-  </ItemGroup>
-
-</Project>

+ 1 - 0
VCommon.VApplication/VCommon.VApplication.csproj

@@ -6,6 +6,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\VCommon.Ioc\VCommon.Ioc.csproj" />
+    <ProjectReference Include="..\VCommon.Json\VCommon.Json.csproj" />
     <ProjectReference Include="..\VCommon.VAutoMapper\VCommon.VAutoMapper.csproj" />
     <ProjectReference Include="..\VCommon.VEntityFrameworkCore\VCommon.VEntityFrameworkCore.csproj" />
     <ProjectReference Include="..\VCommon.VEntity\VCommon.VEntity.csproj" />

+ 11 - 2
VCommon.VEntityFrameworkCore/VEfDbContextBase.cs

@@ -8,6 +8,16 @@ namespace VCommon.VEntityFrameworkCore
 {
     public abstract class VEfDbContextBase : DbContext
     {
+        protected VEfDbContextBase()
+        {
+            
+        }
+
+        protected VEfDbContextBase(DbContextOptions options):base(options)
+        {
+            
+        }
+
         public override int SaveChanges()
         {
             foreach (var track in ChangeTracker.Entries<IHaveCreationTime>().Where(p => p.State == EntityState.Added)) track.Entity.CreationTime = DateTime.Now;
@@ -21,8 +31,7 @@ namespace VCommon.VEntityFrameworkCore
                 if (track.Entity is IHaveLastModificationTime lmd) lmd.LastModificationTime = DateTime.Now;
             }
 
-
-            //TODO: Tenant/Org/UserId in VApplication
+            
 
             return base.SaveChanges();
         }

+ 9 - 9
VCommon.VOpenApi.VAspNetCore/ApiMiddleware.cs

@@ -8,6 +8,7 @@ using VCommon.Json;
 using VCommon.Logging;
 using VCommon.Reflection;
 using VCommon.VApplication;
+using VCommon.VApplication.Auditing;
 using VCommon.VOpenApi.Docgen;
 
 namespace VCommon.VOpenApi.VAspNetCore
@@ -21,7 +22,7 @@ namespace VCommon.VOpenApi.VAspNetCore
 
         private static IIocManager _iocManager;
 
-        public static void Init(IReadOnlyDictionary<string, Type> services, string basePath="/api", IIocManager iocManager = null, bool docGen = false, string title = "Api Set", string version = "1.0", bool isDebuggingEnabled = false)
+        public static void Init(IReadOnlyDictionary<string, Type> services, string basePath = "/api", IIocManager iocManager = null, bool docGen = false, string title = "Api Set", string version = "1.0", bool isDebuggingEnabled = false)
         {
             _basePath = basePath;
             _isDebuggingEnabled = isDebuggingEnabled;
@@ -103,16 +104,15 @@ namespace VCommon.VOpenApi.VAspNetCore
             {
                 context.Response.ContentType = "application/json";
 
+                //create child container
+                using var child = _iocManager.CreateChildren();
+                child.RegisterInstanceToContainer(new ServiceInvokeTiming());
+                //put session dependency
+                child.RegisterInstanceToContainer(context);
+
                 try
                 {
-                    //create child container and put session
-
-                    await invokeBind.Invoke(it =>
-                    {
-                        var child = _iocManager.CreateChildren();
-                        child.RegisterInstanceToContainer<IVSession>(new VSession(context));
-                        return child.Resolve(it);
-                    }, context.Request.Body, context.Response.Body);
+                    await invokeBind.Invoke(it => child.Resolve(it), context.Request.Body, context.Response.Body);
                 }
                 catch (Exception exception)
                 {

+ 0 - 35
VCommon.VOpenApi.VAspNetCore/VSession.cs

@@ -1,35 +0,0 @@
-using System;
-using Microsoft.AspNetCore.Http;
-using VCommon.VApplication;
-
-namespace VCommon.VOpenApi.VAspNetCore
-{
-    public class VSession : IVSession
-    {
-        public VSession(HttpContext ctx)
-        {
-            //extract token
-            //and hold in context items?
-
-
-        }
-
-        public Guid GetTenantId()
-        {
-            throw new NotImplementedException();
-        }
-
-        public Guid GetUserId()
-        {
-            throw new NotImplementedException();
-        }
-
-        public string Token { get; set; }
-        public Guid? UserId { get; }
-        public Guid? TenantId { get; }
-        public void DemandAuth()
-        {
-            throw new NotImplementedException();
-        }
-    }
-}

+ 63 - 5
VCommon.VOpenApi/ApiBind.cs

@@ -1,6 +1,7 @@
 using Newtonsoft.Json;
 using System;
 using System.IO;
+using System.Linq.Expressions;
 using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
@@ -16,6 +17,8 @@ namespace VCommon.VOpenApi
 
         private readonly Type _interfaceType;
 
+        private readonly Func<object, object, object> _preCompiledInvoke;
+
         public string RawRoute { get; }
 
         public Type ApiResourceType { get; }
@@ -41,6 +44,65 @@ namespace VCommon.VOpenApi
             }
 
             RawRoute = rawRoute;
+
+            //Compile method invoke reduce time
+            {
+                var hasParam = InputParamType != null;
+                var hasReturn = apiMethod.ReturnType != typeof(void);
+
+                var pInstance = Expression.Parameter(typeof(object), "instance");
+                var unboxInstance = Expression.Convert(pInstance, interfaceType);
+                var pInput = Expression.Parameter(typeof(object), "input");
+
+                MethodCallExpression invoke;
+                if (hasParam)
+                {
+                    var unboxInput = Expression.Convert(pInput, InputParamType);
+                    invoke = Expression.Call(unboxInstance, apiMethod, unboxInput);
+                }
+                else
+                {
+                    invoke = Expression.Call(unboxInstance, apiMethod);
+                }
+
+                if (hasParam)
+                {
+                    if (hasReturn)
+                    {
+                        var lam = Expression.Lambda<Func<object, object, object>>(invoke, pInstance, pInput);
+                        _preCompiledInvoke = lam.Compile();
+                    }
+                    else
+                    {
+                        var lam = Expression.Lambda<Action<object, object>>(invoke, pInstance, pInput);
+                        var del = lam.Compile();
+                        _preCompiledInvoke = (instance, input) =>
+                        {
+                            del(instance, input);
+                            return null;
+                        };
+                    }
+                }
+                else
+                {
+                    if (hasReturn)
+                    {
+                        var lam = Expression.Lambda<Func<object, object>>(invoke, pInstance);
+                        var del = lam.Compile();
+                        _preCompiledInvoke = (instance, _) => del(instance);
+                    }
+                    else
+                    {
+                        var lam = Expression.Lambda<Action<object>>(invoke, pInstance);
+                        var del = lam.Compile();
+                        _preCompiledInvoke = (instance, _) =>
+                        {
+                            del(instance);
+                            return null;
+                        };
+                    }
+                }
+            }
         }
 
         public async Task Invoke(Func<Type, object> resolveFunc, Stream inputStream, Stream outputStream)
@@ -76,11 +138,7 @@ namespace VCommon.VOpenApi
 
             var inst = resolveFunc(_interfaceType);
 
-            //TODO: 使用更高性能的动态调用方式 Expression Tree Compile?
-            var returnValue = ApiResourceType.InvokeMember(
-                ApiMethod.Name, BindingFlags.InvokeMethod, null, inst,
-                inputParam == null ? null : new[] { inputParam }
-            );
+            var returnValue = _preCompiledInvoke(inst, inputParam);
 
             var output = ReturnValueSerializer.SerializeObject(returnValue);
             var bufOutput = Encoding.UTF8.GetBytes(output);

+ 21 - 11
VCommon/Diagnostics/StackTraceExtensionMethod.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
 using System.Text;
 
 namespace VCommon.Diagnostics
@@ -8,32 +9,41 @@ namespace VCommon.Diagnostics
     public static class StackTraceExtensionMethod
     {
         /// <summary> 将堆栈跟踪信息格式化成每一帧都带程序集的详情文本, 可指定一个type来忽略之上的调用堆栈帧 </summary>
-        public static string GetFormattedDetailStackTraceText(this StackTrace me, Type skipUpper = null)
+        public static string GetFormattedDetailStackTraceText(this StackTrace me, Type skipUpper = null, bool hasSourceStackFrameOnly = false)
         {
-            var frames = me.GetFrames();
-            if (null == frames) return null;
+            var rawFrames = me.GetFrames();
+            if (null == rawFrames) return null;
+
+            var upperFrames = rawFrames;
 
             if (null != skipUpper)
             {
-                var lst = new List<StackFrame>(frames.Length);
-                for (var i = frames.Length - 1; i >= 0; i--)
+                var lst = new List<StackFrame>(rawFrames.Length);
+                for (var i = rawFrames.Length - 1; i >= 0; i--)
                 {
-                    if (frames[i].GetMethod().DeclaringType == skipUpper) break;
-                    lst.Insert(0, frames[i]);
+                    if (rawFrames[i].GetMethod().DeclaringType == skipUpper) break;
+                    lst.Insert(0, rawFrames[i]);
                 }
-                frames = lst.ToArray();
+                upperFrames = lst.ToArray();
             }
 
             var sb = new StringBuilder();
 
-            foreach (var frame in frames)
+            var filteredFrames = upperFrames;
+
+            if (hasSourceStackFrameOnly)
+            {
+                filteredFrames = upperFrames.Where(p => p.HasSource()).ToArray();
+            }
+
+            foreach (var frame in filteredFrames)
             {
                 var method = frame.GetMethod();
                 var type = method.DeclaringType;
-                var exeasm = type?.Assembly;
+                var asm = type?.Assembly;
                 var filename = frame.GetFileName();
 
-                if (null != exeasm) sb.Append($"[{exeasm.GetName().Name}] ");
+                if (null != asm) sb.Append($"[{asm.GetName().Name}] ");
                 if (null != type) sb.Append($"{type.FullName}::");
                 sb.Append($"{method}");
                 if (null != filename) sb.Append($" @ {filename}:{frame.GetFileLineNumber()},{frame.GetFileColumnNumber()}");