[Abp 源码分析]自动审计记录

点击上方蓝字关注我们

0.简介

Abp 框架为我们自带了审计日志功能,审计日志可以方便地查看每次请求接口所耗的时间,能够帮助我们快速定位到某些性能有问题的接口。除此之外,审计日志信息还包含有每次调用接口时客户端请求的参数信息,客户端的 IP 与客户端使用的浏览器。有了这些数据之后,我们就可以很方便地复现接口产生 BUG 时的一些环境信息。

当然如果你脑洞更大的话,可以根据这些数据来开发一个可视化的图形界面,方便开发与测试人员来快速定位问题。

PS:

如果使用了 Abp.Zero 模块则自带的审计记录实现是存储到数据库当中的,但是在使用 EF Core + MySQL(EF Provider 为 Pomelo.EntityFrameworkCore.MySql) 在高并发的情况下会有数据库连接超时的问题,这块推荐是重写实现,自己采用 Redis 或者其他存储方式。

如果需要禁用审计日志功能,则需要在任意模块的预加载方法(PreInitialize()) 当中增加如下代码关闭审计日志功能。

public class XXXStartupModule
{public override PreInitialize(){// 禁用审计日志Configuration.Auditing.IsEnabled = false;}
}

1.启动流程

审计组件与参数校验组件一样,都是通过 MVC 过滤器与 Castle 拦截器来实现记录的。也就是说,在每次调用接口/方法时都会进入 过滤器/拦截器 并将其写入到数据库表 AbpAuditLogs 当中。

其核心思想十分简单,就是在执行具体接口方法的时候,先使用 StopWatch 对象来记录执行完一个方法所需要的时间,并且还能够通过 HttpContext 来获取到一些客户端的关键信息。

2.1 过滤器注入

同上一篇文章所讲的一样,过滤器是在 AddAbp() 方法内部的 ConfigureAspNetCore() 方法注入的。

private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
{// ... 其他代码//Configure MVCservices.Configure<MvcOptions>(mvcOptions =>{mvcOptions.AddAbp(services);});// ... 其他代码
}

而下面就是过滤器的注入方法:

internal static class AbpMvcOptionsExtensions
{public static void AddAbp(this MvcOptions options, IServiceCollection services){// ... 其他代码AddFilters(options);// ... 其他代码}// ... 其他代码private static void AddFilters(MvcOptions options){// ... 其他过滤器注入// 注入审计日志过滤器options.Filters.AddService(typeof(AbpAuditActionFilter));// ... 其他过滤器注入}// ... 其他代码
}

2.2 拦截器注入

注入拦截器的地方与 DTO 自动验证的拦截器的位置一样,都是在 AbpBootstrapper 对象被构造的时候进行注册。

public class AbpBootstrapper : IDisposable
{private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null){// ... 其他代码if (!options.DisableAllInterceptors){AddInterceptorRegistrars();}}// ... 其他代码// 添加各种拦截器private void AddInterceptorRegistrars(){ValidationInterceptorRegistrar.Initialize(IocManager);AuditingInterceptorRegistrar.Initialize(IocManager);EntityHistoryInterceptorRegistrar.Initialize(IocManager);UnitOfWorkRegistrar.Initialize(IocManager);AuthorizationInterceptorRegistrar.Initialize(IocManager);}// ... 其他代码
}

转到 AuditingInterceptorRegistrar 的具体实现可以发现,他在内部针对于审计日志拦截器的注入是区分了类型的。

internal static class AuditingInterceptorRegistrar
{public static void Initialize(IIocManager iocManager){iocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>{// 如果审计日志配置类没有被注入,则直接跳过if (!iocManager.IsRegistered<IAuditingConfiguration>()){return;}var auditingConfiguration = iocManager.Resolve<IAuditingConfiguration>();// 判断当前 DI 所注入的类型是否应该为其绑定审计日志拦截器if (ShouldIntercept(auditingConfiguration, handler.ComponentModel.Implementation)){handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AuditingInterceptor)));}};}// 本方法主要用于判断当前类型是否符合绑定拦截器的条件private static bool ShouldIntercept(IAuditingConfiguration auditingConfiguration, Type type){// 首先判断当前类型是否在配置类的注册类型之中,如果是,则进行拦截器绑定if (auditingConfiguration.Selectors.Any(selector => selector.Predicate(type))){return true;}// 当前类型如果拥有 Audited 特性,则进行拦截器绑定if (type.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true)){return true;}// 如果当前类型内部的所有方法当中有一个方法拥有 Audited 特性,则进行拦截器绑定if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true))){return true;}// 都不满足则返回 false,不对当前类型进行绑定return false;}
}

可以看到在判断是否绑定拦截器的时候,Abp 使用了 auditingConfiguration.Selectors 的属性来进行判断,那么默认 Abp 为我们添加了哪些类型是必定有审计日志的呢?

通过代码追踪,我们来到了 AbpKernalModule 类的内部,在其预加载方法里面有一个 AddAuditingSelectors() 的方法,该方法的作用就是添加了一个针对于应用服务类型的一个选择器对象。

public sealed class AbpKernelModule : AbpModule
{public override void PreInitialize(){// ... 其他代码AddAuditingSelectors();// ... 其他代码}// ... 其他代码private void AddAuditingSelectors(){Configuration.Auditing.Selectors.Add(new NamedTypeSelector("Abp.ApplicationServices",type => typeof(IApplicationService).IsAssignableFrom(type)));}// ... 其他代码
}

我们先看一下 NamedTypeSelector 的一个作用是什么,其基本类型定义由一个 string 和 Func<Type, bool> 组成,十分简单,重点就出在这个断言委托上面。

public class NamedTypeSelector
{// 选择器名称public string Name { get; set; }// 断言委托public Func<Type, bool> Predicate { get; set; }public NamedTypeSelector(string name, Func<Type, bool> predicate){Name = name;Predicate = predicate;}
}

回到最开始的地方,当 Abp 为 Selectors 添加了一个名字为 "Abp.ApplicationServices" 的类型选择器。其断言委托的大体意思就是传入的 **type ** 参数是继承自 IApplicationService 接口的话,则返回 true,否则返回 false

这样在程序启动的时候,首先注入类型的时候,会首先进入上文所述的拦截器绑定类当中,这个时候会使用 Selectors 内部的类型选择器来调用这个集合内部的断言委托,只要这些选择器对象有一个返回 true,那么就直接与当前注入的 type 绑定拦截器。

2.代码分析

2.1 过滤器代码分析

首先查看这个过滤器的整体类型结构,一个标准的过滤器,肯定要实现 IAsyncActionFilter 接口。从下面的代码我们可以看到其注入了 IAbpAspNetCoreConfiguration 和一个 IAuditingHelper 对象。这两个对象的作用分别是判断是否记录日志,另一个则是用来真正写入日志所使用的。

public class AbpAuditActionFilter : IAsyncActionFilter, ITransientDependency
{// 审计日志组件配置对象private readonly IAbpAspNetCoreConfiguration _configuration;// 真正用来写入审计日志的工具类private readonly IAuditingHelper _auditingHelper;public AbpAuditActionFilter(IAbpAspNetCoreConfiguration configuration, IAuditingHelper auditingHelper){_configuration = configuration;_auditingHelper = auditingHelper;}public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next){// ... 代码实现}// ... 其他代码
}

接着看 AbpAuditActionFilter() 方法内部的实现,进入这个过滤器的时候,通过 ShouldSaveAudit() 方法来判断是否要写审计日志。

之后呢与 DTO 自动验证的过滤器一样,通过 AbpCrossCuttingConcerns.Applying() 方法为当前的对象增加了一个标识,用来告诉拦截器说我已经处理过了,你就不要再重复处理了。

再往下就是创建审计信息,执行具体接口方法,并且如果产生了异常的话,也会存放到审计信息当中。

最后接口无论是否执行成功,还是说出现了异常信息,都会将其性能计数信息同审计信息一起,通过 IAuditingHelper 存储起来。

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{// 判断是否写日志if (!ShouldSaveAudit(context)){await next();return;}// 为当前类型打上标识using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Auditing)){// 构造审计信息(AuditInfo)var auditInfo = _auditingHelper.CreateAuditInfo(context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(),context.ActionDescriptor.AsControllerActionDescriptor().MethodInfo,context.ActionArguments);// 开始性能计数var stopwatch = Stopwatch.StartNew();try{// 尝试调用接口方法var result = await next();// 产生异常之后,将其异常信息存放在审计信息之中if (result.Exception != null && !result.ExceptionHandled){auditInfo.Exception = result.Exception;}}catch (Exception ex){// 产生异常之后,将其异常信息存放在审计信息之中auditInfo.Exception = ex;throw;}finally{// 停止计数,并且存储审计信息stopwatch.Stop();auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);await _auditingHelper.SaveAsync(auditInfo);}}
}

2.2 拦截器代码分析

拦截器处理时的总体思路与过滤器类似,其核心都是通过 IAuditingHelper 来创建审计信息和持久化审计信息的。只不过呢由于拦截器不仅仅是处理 MVC 接口,也会处理内部的一些类型的方法,所以针对同步方法与异步方法的处理肯定会复杂一点。

拦截器呢,我们关心一下他的核心方法 Intercept() 就行了。

public void Intercept(IInvocation invocation)
{// 判断过滤器是否已经处理了过了if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Auditing)){invocation.Proceed();return;}// 通过 IAuditingHelper 来判断当前方法是否需要记录审计日志信息if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget)){invocation.Proceed();return;}// 构造审计信息var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetType, invocation.MethodInvocationTarget, invocation.Arguments);// 判断方法的类型,同步方法与异步方法的处理逻辑不一样if (invocation.Method.IsAsync()){PerformAsyncAuditing(invocation, auditInfo);}else{PerformSyncAuditing(invocation, auditInfo);}
}// 同步方法的处理逻辑与 MVC 过滤器逻辑相似
private void PerformSyncAuditing(IInvocation invocation, AuditInfo auditInfo)
{var stopwatch = Stopwatch.StartNew();try{invocation.Proceed();}catch (Exception ex){auditInfo.Exception = ex;throw;}finally{stopwatch.Stop();auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);_auditingHelper.Save(auditInfo);}
}// 异步方法处理
private void PerformAsyncAuditing(IInvocation invocation, AuditInfo auditInfo)
{var stopwatch = Stopwatch.StartNew();invocation.Proceed();if (invocation.Method.ReturnType == typeof(Task)){invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally((Task) invocation.ReturnValue,exception => SaveAuditInfo(auditInfo, stopwatch, exception));}else //Task<TResult>{invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult(invocation.Method.ReturnType.GenericTypeArguments[0],invocation.ReturnValue,exception => SaveAuditInfo(auditInfo, stopwatch, exception));}
}private void SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception exception)
{stopwatch.Stop();auditInfo.Exception = exception;auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds);_auditingHelper.Save(auditInfo);
}

这里异步方法的处理在很早之前的工作单元拦截器就有过讲述,这里就不再重复说明了。

2.3 核心的 IAuditingHelper

从代码上我们就可以看到,不论是拦截器还是过滤器都是最终都是通过 IAuditingHelper 对象来储存审计日志的。Abp 依旧为我们实现了一个默认的 AuditingHelper ,实现了其接口的所有方法。我们先查看一下这个接口的定义:

public interface IAuditingHelper
{// 判断当前方法是否需要存储审计日志信息bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false);// 根据参数集合创建一个审计信息,一般用于拦截器AuditInfo CreateAuditInfo(Type type, MethodInfo method, object[] arguments);// 根据一个参数字典类来创建一个审计信息,一般用于 MVC 过滤器AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary<string, object> arguments);// 同步保存审计信息void Save(AuditInfo auditInfo);// 异步保存审计信息Task SaveAsync(AuditInfo auditInfo);
}

我们来到其默认实现 AuditingHelper 类型,先看一下其内部注入了哪些接口。

public class AuditingHelper : IAuditingHelper, ITransientDependency
{// 日志记录器,用于记录日志public ILogger Logger { get; set; }// 用于获取当前登录用户的信息public IAbpSession AbpSession { get; set; }// 用于持久话审计日志信息public IAuditingStore AuditingStore { get; set; }// 主要作用是填充审计信息的客户端调用信息private readonly IAuditInfoProvider _auditInfoProvider;// 审计日志组件的配置相关private readonly IAuditingConfiguration _configuration;// 在调用 AuditingStore 进行持久化的时候使用,创建一个工作单元private readonly IUnitOfWorkManager _unitOfWorkManager;// 用于序列化参数信息为 JSON 字符串private readonly IAuditSerializer _auditSerializer;public AuditingHelper(IAuditInfoProvider auditInfoProvider,IAuditingConfiguration configuration,IUnitOfWorkManager unitOfWorkManager,IAuditSerializer auditSerializer){_auditInfoProvider = auditInfoProvider;_configuration = configuration;_unitOfWorkManager = unitOfWorkManager;_auditSerializer = auditSerializer;AbpSession = NullAbpSession.Instance;Logger = NullLogger.Instance;AuditingStore = SimpleLogAuditingStore.Instance;}// ... 其他实现的接口
}

2.3.1 判断是否创建审计信息

首先分析一下其内部的 ShouldSaveAudit() 方法,整个方法的核心作用就是根据传入的方法类型来判定是否为其创建审计信息。

其实在这一串 if 当中,你可以发现有一句代码对方法是否标注了 DisableAuditingAttribute 特性进行了判断,如果标注了该特性,则不为该方法创建审计信息。所以我们就可以通过该特性来控制自己应用服务类,控制里面的的接口是否要创建审计信息。同理,我们也可以通过显式标注 AuditedAttribute 特性来让拦截器为这个方法创建审计信息。

public bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false)
{if (!_configuration.IsEnabled){return false;}if (!_configuration.IsEnabledForAnonymousUsers && (AbpSession?.UserId == null)){return false;}if (methodInfo == null){return false;}if (!methodInfo.IsPublic){return false;}if (methodInfo.IsDefined(typeof(AuditedAttribute), true)){return true;}if (methodInfo.IsDefined(typeof(DisableAuditingAttribute), true)){return false;}var classType = methodInfo.DeclaringType;if (classType != null){if (classType.GetTypeInfo().IsDefined(typeof(AuditedAttribute), true)){return true;}if (classType.GetTypeInfo().IsDefined(typeof(DisableAuditingAttribute), true)){return false;}if (_configuration.Selectors.Any(selector => selector.Predicate(classType))){return true;}}return defaultValue;
}

2.3.2 创建审计信息

审计信息在创建的时候,就为我们将当前调用接口时的用户信息存放在了审计信息当中,之后通过 IAuditInfoProvider 的 Fill() 方法填充了客户端 IP 与浏览器信息。

public AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary<string, object> arguments)
{// 构建一个审计信息对象var auditInfo = new AuditInfo{TenantId = AbpSession.TenantId,UserId = AbpSession.UserId,ImpersonatorUserId = AbpSession.ImpersonatorUserId,ImpersonatorTenantId = AbpSession.ImpersonatorTenantId,ServiceName = type != null? type.FullName: "",MethodName = method.Name,// 将参数转换为 JSON 字符串Parameters = ConvertArgumentsToJson(arguments),ExecutionTime = Clock.Now};try{// 填充客户 IP 与浏览器信息等_auditInfoProvider.Fill(auditInfo);}catch (Exception ex){Logger.Warn(ex.ToString(), ex);}return auditInfo;
}

2.4 审计信息持久化

通过上一小节我们知道了在调用审计信息保存接口的时候,实际上是调用的 IAuditingStore 所提供的 SaveAsync(AuditInfo auditInfo) 方法来持久化这些审计日志信息的。

如果你没有集成 Abp.Zero 项目的话,则使用的是默认的实现,就是简单通过 ILogger 输出审计信息到日志当中。

默认有这两种实现,至于第一种是 Abp 的单元测试项目所使用的。

这里我们就简单将一下 AuditingStore 这个实现吧,其实很简单的,就是注入了一个仓储,在保存的时候往审计日志表插入一条数据即可。

这里使用了 AuditLog.CreateFromAuditInfo() 方法将 AuditInfo 类型的审计信息转换为数据库实体,用于仓储进行插入操作。

public class AuditingStore : IAuditingStore, ITransientDependency
{private readonly IRepository<AuditLog, long> _auditLogRepository;public AuditingStore(IRepository<AuditLog, long> auditLogRepository){_auditLogRepository = auditLogRepository;}public virtual Task SaveAsync(AuditInfo auditInfo){// 向表中插入数据return _auditLogRepository.InsertAsync(AuditLog.CreateFromAuditInfo(auditInfo));}
}

同样,这里建议重新实现一个 AuditingStore,存储在 Redis 或者其他地方。

3. 后记

前几天发现 Abp 的团队有开了一个新坑,叫做 Abp vNext 框架,该框架全部基于 .NET Core 进行开发,而且会针对微服务项目进行专门的设计,有兴趣的朋友可以持续关注。

其 GitHub 地址为:https://github.com/abpframework/abp/

官方地址为:https://abp.io/

作者:myzony

出处:https://www.cnblogs.com/myzony/p/9723531.html

公众号“码侠江湖”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

扫描二维码

获取更多精彩

码侠江湖


喜欢就点个在看再走吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我的老公是枚码农

前两天看到一篇写程序员的爆文&#xff0c;虽然略显夸张&#xff0c;但也着实有趣。忽然想到身边人也是一枚码农&#xff0c;浑身上下也是浓厚的码农气息&#xff0c;遂也胡乱写了几笔&#xff0c;博君一笑&#xff0c;为了方便起见&#xff0c;就称其为“码农哥”。 1 码农哥还…

harmonyos con,鸿蒙HarmonyOS系统中的JS开发框架

HarmonyOS开源至今已经一个多月&#xff0c;源码托管在国内知名开源平台码云上&#xff0c;https://gitee.com/openharmony 。我最感兴趣的就是JS 框架 ace_lite_jsfwk&#xff0c;从名字中可以看出来这是一个非常轻量级的框架&#xff0c;官方介绍说是“轻量级 JS 核心开发框架…

.NET工资低?那肯定是你打开的方式不正确

点击上方蓝字关注我们因为工作的关系&#xff0c;本人总是会接触到一些刚踏入社会没多久的.NET开发小伙伴。尤其是年关将近&#xff0c;这时候想要跳槽的人特别多&#xff0c;所以收到一些小伙伴的迷茫求解。今天就拿其中一个来说&#xff0c;我们暂且称他为A同学吧。A同学是一…

10分钟读懂人工智能、机器学习到底有什么关系

文末彩蛋&#xff0c;错过哭一年。。。。 人工智能的浪潮正在席卷全球&#xff0c;诸多词汇时刻萦绕在我们耳边&#xff1a;人工智能&#xff08;Artificial Intelligence&#xff09;、机器学习&#xff08;Machine Learning&#xff09;。不少人对这些高频词汇的含义及其背后…

苏泊尔搭载华为鸿蒙系统,华为鸿蒙打算在一年内跨过生死线,拿下16%的市场份额...

原标题&#xff1a;华为鸿蒙打算在一年内跨过生死线&#xff0c;拿下16%的市场份额华为鸿蒙操作系统发布已经有一段时间了&#xff0c;这个操作系统直到上个月月底才开启了公测&#xff0c;很多用户都已经使用上了华为的这个鸿蒙操作系统。根据不少用户的反馈情况来看&#xff…

python获取系统时间函数_简单记录python的时间函数操作

1. time和datetime模块 import datetime,time 2. 获得当前时间 time.time() #获得当前时间&#xff0c;返回float型 time.localtime([float time]) #获得本地当前时间&#xff0c;返回time.struct_time类型 说明&#xff1a;struct_time是一个只读的9元组&#xff0c;其中参数命…

10个最佳的大数据处理编程语言

大数据的浪潮仍在继续。它渗透到了几乎所有的行业&#xff0c;信息像洪水一样地席卷企业&#xff0c;使得软件越发庞然大物&#xff0c;比如Excel看上去就变得越来越笨拙。数据处理不再无足轻重&#xff0c;并且对精密分析和强大又实时处理的需要变得前所未有的巨大。 那么&…

再记一次 应用服务器 CPU 暴高事故分析

一&#xff1a;背景 1. 前言大概有2个月没写博客了&#xff0c;不是不想写哈????&#xff0c;关注公号的朋友应该知道我这两个月一直都在翻译文章&#xff0c;前前后后大概100篇左右吧&#xff0c;前几天看公号的 常读用户 降了好几十&#xff0c;心疼哈&#xff0c;还得回…

”残酷“人生第一步

仔仔终于要面对人生中的第一次巨大困难&#xff1a;断奶了。恰逢夫人要出差几日&#xff0c;刚刚开始&#xff0c;我们都很犹豫。确实很怕他整夜哭闹。但是思考再三还是决定&#xff0c;让小家伙独自面对吧。于是&#xff0c;他的“残酷”人生第一步&#xff0c;就这样准备开始…

IdentityServer4之Authorization Code(授权码)相对更安全

前言接着授权模式聊&#xff0c;这次说说Authorization Code(授权码)模式&#xff0c;熟悉的微博接入、微信接入、QQ接入都是这种方式(这里说的是oauth2.0的授权码模式)&#xff0c;从用户体验上来看&#xff0c;交互方式和Implicit没啥改变&#xff0c;随便找个网站瞅瞅&#…

后端根据百度地图真实路径距离_导航软件哪家强?实测百度地图and高德地图哪个更靠谱...

随着社会的不断发展&#xff0c;人们的生活越来越离不开地图导航&#xff0c;无论是开车出行还是到去到陌生的地方&#xff0c;我们都会用到手机地图。然而在众多导航软件中&#xff0c;使用最为广泛的就要属百度地图和高德地图了。但别看都是导航软件&#xff0c;其中差别还真…

一文读懂 KMP 算法

字符串匹配是计算机的基本任务之一。举例来说&#xff0c;有一个字符串"BBC ABCDAB ABCDABCDABDE"&#xff0c;我想知道&#xff0c;里面是否包含另一个字符串"ABCDABD"&#xff1f; 许多算法可以完成这个任务&#xff0c;Knuth-Morris-Pratt算法&#xff…

128位加密SSL证书

SGC超真SSL(SGC ZhenSSL)属于 SGC Enabled High Assurance SSL&#xff0c; 是 WoSign 的增强型 SSL证书产品&#xff0c;支持 SGC 强制128位加密技术&#xff0c;即使用户的浏览器只支持 40 位( 如 IE4.X) 或 56 位 ( 如 IE5.X) 也能自动强制实现至少 128 位的高强度加密&…

eclipse连接mysql_专题一、flask构建mysql数据库正确姿势

每周壹总结&#xff0c;一起共同充电第121篇应用程序最核心的就是数据&#xff0c;每天我们写程序其实也是在处理数据的过程&#xff0c;那么很有必要系统性的讲讲和梳理python的flask框架是如何进行数据交互操作的。趁这3天假期&#xff0c;分4篇内容来系统的讲讲&#xff0c;…

C#多线程和异步(二)——Task和async/await详解

一、什么是异步同步和异步主要用于修饰方法。当一个方法被调用时&#xff0c;调用者需要等待该方法执行完毕并返回才能继续执行&#xff0c;我们称这个方法是同步方法&#xff1b;当一个方法被调用时立即返回&#xff0c;并获取一个线程执行该方法内部的业务&#xff0c;调用者…

从头到尾彻底理解傅里叶变换算法(上)

从头到尾彻底理解傅里叶变换算法&#xff08;上&#xff09; 前言 第一部分、 DFT 第一章、傅立叶变换的由来 第二章、实数形式离散傅立叶变换&#xff08;Real DFT&#xff09; 从头到尾彻底理解傅里叶变换算法、下 第三章、复数 第四章、复数形式离散傅立叶变换 前言&#x…

使用ADO.NET的参数集合来有效防止SQL注入漏洞

SQL注入漏洞是个老话题了&#xff0c;在以前做ASP做开发时&#xff0c;就经常需要用字符串的过虑等方式来解决这个问题&#xff0c;但有时候确做的不够彻底&#xff0c;往往让***钻了空子。那么目前在我们.NET中&#xff0c;不管是用WINFORM开发还是用WEBFORM&#xff0c;连接数…

[Abp 源码分析]ASP.NET Core 集成

点击上方蓝字关注我们0. 简介整个 Abp 框架最为核心的除了 Abp 库之外&#xff0c;其次就是 Abp.AspNetCore 库了。虽然 Abp 本身是可以用于控制台程序的&#xff0c;不过那样的话 Abp 就基本没什么用&#xff0c;还是需要集合 ASP.NET Core 才能发挥它真正的作用。在 Abp.AspN…

从头到尾彻底理解傅里叶变换算法(下)

从头到尾彻底理解傅里叶变换算法&#xff08;上&#xff09;&#xff0c;请看今天第一条。 以下继续&#xff1a; 第三章、复数 复数扩展了我们一般所能理解的数的概念&#xff0c;复数包含了实数和虚数两部分&#xff0c;利用复数的形式可以把由两个变量表示的表达式变成由一个…

树莓派安装python3.5_梦见树_周公解梦梦到树是什么意思_做梦梦见树好不好_周公解梦官网...

梦见树是什么意思&#xff1f;做梦梦见树好不好&#xff1f;梦见树有现实的影响和反应&#xff0c;也有梦者的主观想象&#xff0c;请看下面由(周公解梦官网www.zgjm.org)小编帮你整理的梦见树的详细解说吧。树主健康&#xff0c;树笔直挺拔&#xff0c;象征着人的健康。 在梦中…