从今天这一节起就要深入到ABP中的每一个重要的知识点来一步步进行分析,在进行介绍ABP中的拦截器之前我们先要有个概念,到底什么是拦截器,在介绍这些之前,我们必须要了解AOP编程思想,这个一般翻译是面向切面编程,这个是和OOP相对的一个概念,在此之前应该先读一读其概念的介绍。这篇文章的重点是介绍ABP框架中的各种拦截器,并介绍其具体在代码中是如何起作用的。

  整个ABP框架中的拦截器开始于AbpBootstrapper的构造函数中,在这里我们创建了一个AbpBootstrapperOptions,在这里定义了一个DisableAllInterceptors属性,这个用来禁用添加到ABP系统中所有的所有的拦截器,默认值为false,即默认会开启所有的拦截器。当然这个参数可以在AddAbp方法中通过回调函数来进行修改。我们来看看代码是怎样添加拦截器的,这里从AddInterceptorRegistrars方法来时说起,我们来看看这个方法中都添加了哪些ABP拦截器。

 private void AddInterceptorRegistrars()
{
ValidationInterceptorRegistrar.Initialize(IocManager);
AuditingInterceptorRegistrar.Initialize(IocManager);
EntityHistoryInterceptorRegistrar.Initialize(IocManager);
UnitOfWorkRegistrar.Initialize(IocManager);
AuthorizationInterceptorRegistrar.Initialize(IocManager);
}

  在这个方法中进行了总共进行了5中类型的拦截器的初始化,后面我们来就每一种拦截器进行说明和分析。  

  这个顾名思义是用作验证的,那么到底验证些什么东西呢?我们来一步步进行分析。

internal static class ValidationInterceptorRegistrar
{
public static void Initialize(IIocManager iocManager)
{
iocManager.IocContainer.Kernel.ComponentRegistered += Kernel_ComponentRegistered;
} private static void Kernel_ComponentRegistered(string key, IHandler handler)
{
if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(handler.ComponentModel.Implementation))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(ValidationInterceptor)));
}
}
}

  首先在执行静态Initialize方法,在这个方法内部,首先将整个ABP中唯一的IoCManager作为参数传递到里面,然后订阅依赖注入容器的ComponentRegister事件,这里订阅的函数有两个参数,一个是key,另外一个是IHandle的接口,这段代码意思是说当Ioc中有组件被注册的时候(也就是往Ioc添加某个类型的时候), 检测该对象是否是IApplicationService(也就是只验证ApplicationService层), 是的话做Validation的拦截,可以做到拦截之后对ApplicationService层的方法参数做检测, Interceptors是一个拦截器集合, 可以加入更多的拦截器,在这里我们添加了一个ValidationInterceptor。所以当一个请求进入ApplicationService层之后,第一个做的事情就是 Validation,那么这个Validation到底做些什么呢?接下来我们来一步步进行分析。

 public class ValidationInterceptor : IInterceptor
{
private readonly IIocResolver _iocResolver; public ValidationInterceptor(IIocResolver iocResolver)
{
_iocResolver = iocResolver;
} public void Intercept(IInvocation invocation)
{
if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Validation))
{
invocation.Proceed();
return;
} using (var validator = _iocResolver.ResolveAsDisposable<MethodInvocationValidator>())
{
validator.Object.Initialize(invocation.MethodInvocationTarget, invocation.Arguments);
validator.Object.Validate();
} invocation.Proceed();
}
}

  当我们添加了特定类型的拦截器后,当我们请求从IApplicationService类中继承的方法的时候,首先调用的是IInterceptor接口中定义的Intercept拦截方法,在这个方法中首先调用一个静态的IsApplied方法,我们来看看这个方法中到底是如何进行定义的,我们来看看这个方法内如做了什么?这个方法会传递两个参数,一个是invocation.InvocationTarget,这个指的是什么呢?这个就是当前当前继承自IApplicationService接口的自定义应用层service,第二个参数是一个静态常量,这个定义在internal static class AbpCrossCuttingConcerns这个静态类中,那么具体的形式是怎么样的呢?public const string Validation = "AbpValidation",其实就是一个字符串形式。

 public static bool IsApplied([NotNull] object obj, [NotNull] string concern)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
} if (concern == null)
{
throw new ArgumentNullException(nameof(concern));
} return (obj as IAvoidDuplicateCrossCuttingConcerns)?.AppliedCrossCuttingConcerns.Contains(concern) ?? false;
}

  在这里有一个IAvoidDuplicateCrossCuttingConcerns 接口,通过查阅相关的代码我们发现 ApplicationService : AbpServiceBase, IApplicationService, IAvoidDuplicateCrossCuttingConcerns ,ApplicationService刚好是继承自当前接口的,这个接口内部定义了一个AppiedCrossCuttingConcerns的List<string>的对象,那么通过上面的一番解释你应该了解了,我们自定义的应用服务层一般都是继承自ApplicationService的,当我们调用应用层的某个方法时,首先就会判断当前应用服务层中的List<string>对象中是否包含了这个常量AbpValidation,如果已经包含了那么就不执行拦截过程,就让请求的方法继续执行,如果没有包含该字符串,那么就会执行后面的拦截过程,拦截的过程就是创建一个MethodInvocationValidator对象,然后调用这个对象中的Initialize和Validate方法,这个是后面要重点讲述的过程,这里我们先跳转到另外一个话题,这里为什么需要判断这个AppiedCrossCuttingConcerns中是否包含了AbpValidation呢?难道是别的地方也会往这个集合中添加AbpValidation这个字符串吗?答案是肯定的,在我们的ABP项目中还真有另外的地方往这个集合中添加了这个字符串,那么到底是哪里呢?通过分析我们发现在ABP中还有另外一类重要的内容,他们就是Filter,这个是Asp.Net Core中的内容,那么我们的ABP中又是在何时使用到这些Filter的呢?让我们带着这些疑问来一步步去分析,首先来看看这个类。

 internal static class AbpMvcOptionsExtensions
{
public static void AddAbp(this MvcOptions options, IServiceCollection services)
{
AddConventions(options, services);
AddFilters(options);
AddModelBinders(options);
} private static void AddConventions(MvcOptions options, IServiceCollection services)
{
options.Conventions.Add(new AbpAppServiceConvention(services));
} private static void AddFilters(MvcOptions options)
{
options.Filters.AddService(typeof(AbpAuthorizationFilter));
options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpValidationActionFilter));
options.Filters.AddService(typeof(AbpUowActionFilter));
options.Filters.AddService(typeof(AbpExceptionFilter));
options.Filters.AddService(typeof(AbpResultFilter));
} private static void AddModelBinders(MvcOptions options)
{
options.ModelBinderProviders.Insert(, new AbpDateTimeModelBinderProvider());
}
}

  在这个静态类中有一个AddFilters的方法,它会MvcOptions中的Filters添加6种类型的Filter,那么这个类中的AddAbp方法是在什么时候调用的呢?这个在看过我之前系列的应该不会陌生,其实在最开始Startup类中ConfigureServices中执行AddAbp方法中就会执行配置ASPNetCore中就会调用这个方法,我们来贴出这个类中的这部分代码。

    public static class AbpServiceCollectionExtensions
{
/// <summary>
/// Integrates ABP to AspNet Core.
/// </summary>
/// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
/// <param name="services">Services.</param>
/// <param name="optionsAction">An action to get/modify options</param>
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction); ConfigureAspNetCore(services, abpBootstrapper.IocManager); return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
} private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
{
//See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>(); //Use DI to create controllers
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); //Use DI to create view components
services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>()); //Change anti forgery filters (to work proper with non-browser clients)
services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>()); //Add feature providers
var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver)); //Configure JSON serializer
services.Configure<MvcJsonOptions>(jsonOptions =>
{
jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
};
}); //Configure MVC
services.Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(services);
}); //Configure Razor
services.Insert(0,
ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
new ConfigureOptions<RazorViewEngineOptions>(
(options) =>
{
options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
}
)
)
);
} private static AbpBootstrapper AddAbpBootstrapper<TStartupModule>(IServiceCollection services, Action<AbpBootstrapperOptions> optionsAction)
where TStartupModule : AbpModule
{
var abpBootstrapper = AbpBootstrapper.Create<TStartupModule>(optionsAction);
services.AddSingleton(abpBootstrapper);
return abpBootstrapper;
}
}

  请看上面这个类中的//Configure MVC这段注释的代码,在这段代码中会调用AbpMvcOptionsExtensions中的静态AddAbp方法,从而为整个ABP项目配置Filter,这个我们在后面的文章也会写一个系列来介绍ABP中的Filter,今天这篇文章的重点不是这个内容,这里介绍这部分内容只是为了和ABP中的Interceptor来进行对比,从而说明ABP中的拦截器的作用。

  在了解了这些Filter是如何添加到ABP系统后,那么就让我们来重点关注AbpValidationActionFilter这个类型的Filter。

public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
{
private readonly IIocResolver _iocResolver;
private readonly IAbpAspNetCoreConfiguration _configuration; public AbpValidationActionFilter(IIocResolver iocResolver, IAbpAspNetCoreConfiguration configuration)
{
_iocResolver = iocResolver;
_configuration = configuration;
} public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!_configuration.IsValidationEnabledForControllers || !context.ActionDescriptor.IsControllerAction())
{
await next();
return;
} using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Validation))
{
using (var validator = _iocResolver.ResolveAsDisposable<MvcActionInvocationValidator>())
{
validator.Object.Initialize(context);
validator.Object.Validate();
} await next();
}
}
}

  熟悉Asp.Net Core的都应该了解AspNet Core中的管道的执行概念,如果对ABP中的Filter还不是很了解的情况下请先读一下下面的这篇文章,从而让自己对整个概念有一个更加清晰的认识。总之ABP中我们添加的AbpValidationActionFilter 会先于定义的拦截器中的方法,当执行一个继承自ApplicationService中的方法的时候,首先会被上面定义的Filter拦截,然后调用OnActionExecutionAsync这个方法,我们来看看这个方法中到底做了些什么,在这个方法中首先会判断IsValidationEnabledForControllers这个ABP配置项中的配置参数,这个参数默认配置为ture,第二部分的判断当前执行方法是否是控制器Action,如果是的话就继续验证后面的过程,所以到了这里我们会发现AbpValidationActionFilter和ValidationInterceptor作用的范围是不同的,前者主要作用于Controller层而后者主要是作用于ABP中的应用层,所以两者的同时存在是有必要的,接下来OnActionExecutionAsync方法会执行下面的Using包含的方法AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Validation)在这个方法中会执行静态类AbpCrossCuttingConcerns中的静态Applying方法,如果当前执行的方法所属的Controller继承自IAvoidDuplicateCrossCuttingConcerns接口,那么这个方法会将字符串常量AbpValidation添加到当前继承自ApplicationService中的应用服务类的List<string>类型的AppiedCrossCuttingConcerns变量中,如果满足上面的这些条件,那么在ABP中如果执行了ValidateFilter后,后面的ValidationInterceptor中的方法是不会执行的,但是在我们的项目中几乎没有任何Controller会继承自这个接口(IAvoidDuplicateCrossCuttingConcerns),所以两者在ABP系统中是彼此独立存在的。所以这里我们重点讲述一下,这个Filter后面执行的方法,var validator = _iocResolver.ResolveAsDisposable<MvcActionInvocationValidator>(),这个方法利用依赖注入的iocResolver对象来创建一个MvcActionInvocationValidator的实例,我们会发现这个类最终是继承自MethodInvocationValidator,所以后面执行Intialize方法和Validate方法,执行的实际上就是MethodInvocationValidator中的方法,这个最终和AbpValidationInterceptor中执行是一致的,通过这些分析你应该对整个ABP中的验证Validation有一个清晰的认识了,当然想要完全了解这个过程,最好的方式还是去下载ABP的源代码然后一步步去分析。

  在后面我们的重点是分析这个MethodInvocationValidator这个方法,然后看看这里面到底做了些什么工作,我们通过代码来分析这两个过程。首先便是Initialize方法,我们来看看这个里面都执行了哪些操作。

/// <param name="method">Method to be validated</param>
/// <param name="parameterValues">List of arguments those are used to call the <paramref name="method"/>.</param>
public virtual void Initialize(MethodInfo method, object[] parameterValues)
{
Check.NotNull(method, nameof(method));
Check.NotNull(parameterValues, nameof(parameterValues));
Method = method;
ParameterValues = parameterValues;
Parameters = method.GetParameters();
}

  这个其实一看就知道,那就是获取当前继承自ApplicationService的类中的 请求方法,并获取相应的参数,并将这些方法放到当前类中的私有全局变量中去,在做好了这些准备工作后就只进行方法的验证工作了,我们来看看究竟验证了哪些东西,有哪些验证的规则。

/// <summary>
/// Validates the method invocation.
/// </summary>
public void Validate()
{
CheckInitialized(); if (Parameters.IsNullOrEmpty())
{
return;
} if (!Method.IsPublic)
{
return;
} if (IsValidationDisabled())
{
return;
} if (Parameters.Length != ParameterValues.Length)
{
throw new Exception("Method parameter count does not match with argument count!");
} for (var i = 0; i < Parameters.Length; i++)
{
ValidateMethodParameter(Parameters[i], ParameterValues[i]);
} if (ValidationErrors.Any())
{
ThrowValidationError();
} foreach (var objectToBeNormalized in ObjectsToBeNormalized)
{
objectToBeNormalized.Normalize();
}
}

  在这个方法中首先会进行一些排除工作,首先该方法必须是公共方法、参数不能为null、而且没有ValidationDisabled,并且方法的参数要和通过反射获取到的保持一致,在做完这些基础的工作后就会对每一个方法的参数进行验证,这里有一个ValidateMethodParameter的方法来验证每一个参数,我们来看看这个方法中做了哪些工作,也是和上面一样的,我们来看看代码。

/// <summary>
/// Validates given parameter for given value.
/// </summary>
/// <param name="parameterInfo">Parameter of the method to validate</param>
/// <param name="parameterValue">Value to validate</param>
protected virtual void ValidateMethodParameter(ParameterInfo parameterInfo, object parameterValue)
{
if (parameterValue == null)
{
if (!parameterInfo.IsOptional &&
!parameterInfo.IsOut &&
!TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true))
{
ValidationErrors.Add(new ValidationResult(parameterInfo.Name + " is null!", new[] { parameterInfo.Name }));
} return;
} ValidateObjectRecursively(parameterValue, 1);
}

  这里面主要是判断当前的参数是否包含一些特殊的情况,比如是否定义了Out等,这里便不再赘述,重点来看看其中调用的子函数ValidateObjectRecursively,我们也来看看这个到底做了些什么?

 protected virtual void ValidateObjectRecursively(object validatingObject, int currentDepth)
{
if (currentDepth > MaxRecursiveParameterValidationDepth)
{
return;
} if (validatingObject == null)
{
return;
} if (_configuration.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
{
return;
} if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType()))
{
return;
} SetValidationErrors(validatingObject); // Validate items of enumerable
if (IsEnumerable(validatingObject))
{
foreach (var item in (IEnumerable) validatingObject)
{
ValidateObjectRecursively(item, currentDepth + 1);
}
} // Add list to be normalized later
if (validatingObject is IShouldNormalize)
{
ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize);
} if (ShouldMakeDeepValidation(validatingObject))
{
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)
{
if (property.Attributes.OfType<DisableValidationAttribute>().Any())
{
continue;
} ValidateObjectRecursively(property.GetValue(validatingObject), currentDepth + 1);
}
}
}

  在这个方法中首先为一个对象定义了一个最大的嵌套验证层级为8,如果当前验证层级超过了8就直接返回,后面紧接着判断当前验证的方法的参数是否为null,如果为null那么也直接返回,紧接着判断当前参数类型是否是ABP中默认配置的忽略的类型,如果是的话也直接返回,接下来判断该参数是否是C#中定义的基础类型Primitive Types,The primitive types are BooleanByteSByteInt16UInt16Int32UInt32Int64UInt64IntPtrUIntPtrCharDouble, and Single.这些类型,另外还讲忽略掉一些常见string、Guido、TimeSpan、DateTime这些类型将直接被忽略掉,在排除掉这些类型以后,就是我们真正需要进行验证的类型了,后面紧接着进入了这个函数SetValidationErrors了

protected virtual void SetValidationErrors(object validatingObject)
{
foreach (var validatorType in _configuration.Validators)
{
if (ShouldValidateUsingValidator(validatingObject, validatorType))
{
using (var validator = _iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType))
{
var validationResults = validator.Object.Validate(validatingObject);
ValidationErrors.AddRange(validationResults);
}
}
}
}

  在这个方法中首先会调用ABP中的配置文件中有哪些Validator,这个在AbpKenelModule中默认增加了集中继承自IMethodParameterValidator的几个类型,让我们来看一看。

 private void AddMethodParameterValidators()
{
Configuration.Validation.Validators.Add<DataAnnotationsValidator>();
Configuration.Validation.Validators.Add<ValidatableObjectValidator>();
Configuration.Validation.Validators.Add<CustomValidator>();
}

  关于每一种验证器,这个会在后面的文章中来逐步进行分析。这里只是做一个概述性的说明。在执行完毕ABP中的默认添加的Validatory以后,后面还有一系类的操作,再在后面会验证当前的validatingObject是否是继承自IEnumerable接口,比如当前传入的参数是List类型的数据,那么就会进行For循环再次验证里面的每一个参数,这里是通过迭代进行验证的,再在后面会判断validatingObject是否继承自IShouldNormalize接口,如果继承自该接口那么就会调用接口中的Normalize方法来对当前对象中的一些参数进行一些标准化的操作,另外最后进行的操作进行判断是否需要对当前对象进行深度验证的操作,判断的依据就是当前对象不是继承自IEnumerable接口并且也不是基础类型,这里过滤掉不是继承自IEnumerable类型是有道理的,因为在执行深度验证之前已经验证过了这个情况避免进行了重复的验证过程,为什么要再做深度验证当前的validatingObject这个过程呢?因为当前对象中可以包含常规的属性,但是也可以包含另外的对象,对于这种对象中又嵌套验证对象就必须通过迭代进行深度验证,但是验证的时候也不可能无限地进行验证下去,所以才有了在每一次在进行迭代之前判断当前的currentDepth > MaxRecursiveParameterValidationDepth的时候就直接退出迭代了,否则真的就会没完没了了,在ABP中MaxRecursiveParameterValidationDepth默认为8,也就是最多进行8次迭代操作过程,这个希望读者能够理解这个,在进行深度验证这个validatingObject的时候,会获取这个对象的所有属性,然后再迭代验证这些属性,然后又会重复执行上面的整个过程,这个就是整个验证的全过程,另外这篇文章没有涉及到具体的ABP中中几种Validitor,这个将会在后面一篇中具体介绍,另外在下篇中还会重点介绍这些验证器到底是怎么用的,这篇文章是按照ABP源码中的顺序进行一步步分析的,理解的时候最好是参照源代码进行一步步分析,文中可能有很多不是十分准确的地方,但是也是经过自己一点点去分析得出的结论,希望能够给新接触ABP系列的有一个指导性的参考,写得不好的望海涵。

  最后,点击这里返回整个ABP系列的主目录。

ABP中的拦截器之ValidationInterceptor(上)的更多相关文章

  1. ABP中的拦截器之ValidationInterceptor(下)

    在上篇我分析了整个ABP中ValitationInterceptor的整个过程,就其中涉及到的Validator过程没有详细的论述,这篇文章就这个过程进行详细的论述,另外任何一个重要的特性如何应用是最 ...

  2. ABP中的拦截器之AuditingInterceptor

    在上面两篇介绍了ABP中的ValidationInterceptor之后,我们今天来看看ABP中定义的另外一种Interceptor即为AuditingInterceptor,顾名思义就是一种审计相关 ...

  3. ABP中的拦截器之EntityHistoryInterceptor

    今天我们接着之前的系列接着来写另外一种拦截器EntityHistoryInterceptor,这个拦截器到底是做什么的呢?这个从字面上理解是实体历史?这个到底是什么意思?带着这个问题我们来一步步去分析 ...

  4. ABP中的本地化处理(上)

    今天这篇文章主要来总结一下ABP中的多语言是怎么实现的,在后面我们将结合ABP中的源码和相关的实例来一步步进行说明,在介绍这个之前我们先来看看ABP的官方文档,通过这个文档我们就知道怎样在我们的系统中 ...

  5. 6. abp中的拦截器

    abp拦截器基本定义 拦截器接口定义: public interface IAbpInterceptor { void Intercept(IAbpMethodInvocation invocatio ...

  6. ABP拦截器之UnitOfWorkRegistrar(一)

    ABP中UnitOfWorkRegistrar拦截器是整个ABP中非常关键的一个部分,这个部分在整个业务系统中也是用的最多的一个部分,这篇文章的主要思路并不是写如何使用ABP中的UnitOfWork, ...

  7. ABP中的Filter(上)

    这个部分我打算用上下两个部分来将整个结构来讲完,在我们读ABP中的代码之后我们一直有一个疑问?在ABP中为什么要定义Interceptor和Filter,甚至这两者之间我们都能找到一些对应关系,比如: ...

  8. ABP拦截器之AuthorizationInterceptor

    在整体介绍这个部分之前,如果对ABP中的权限控制还没有一个很明确的认知,请先阅读这篇文章,然后在读下面的内容. AuthorizationInterceptor看这个名字我们就知道这个拦截器拦截用户一 ...

  9. ABP拦截器之UnitOfWorkRegistrar(二)

    在上面一篇中我们主要是了解了在ABP系统中是如何使用UnitOfWork以及整个ABP系统中如何执行这些过程的,那么这一篇就让我们来看看UnitOfWorkManager中在执行Begin和Compl ...

随机推荐

  1. 配置javaJDK环境

    1.官网下载JDK包 2.解压包 3.打开vi /etc/profile文件添加一下内容 export JAVA_HOME=/usr/jdk1.8.0_121 #你的jdk所在的目录 export C ...

  2. 学习前端笔记1(HTML)

    (注:此文是在看过许多学习资料和视频之后,加上自身理解拼凑而成,仅作学习之用.若有版权问题,麻烦及时联系) 标准页面结构: HTML发展历史:  注:每一种HTML需要有对应的doctype声明. H ...

  3. jq筛选方法

    jq筛选方法(参照手册) 过滤: 1) eq(index|-index):获取第N个元素 负值表示从末尾开始匹配 <!-- 获取匹配的第二个元素 --> <p> This is ...

  4. vue中使用Element主题自定义肤色

    一.搭建好项目的环境. 二.根据ElementUI官网的自定义主题(http://element.eleme.io/#/zh-CN/component/custom-theme)来安装[主题生成工具] ...

  5. 在 Apex 中得到 sObject 的信息

    Salesforce 的数据模型是基于 sObject 的.在 Apex 中,所有的标准对象.自定义对象都是继承自 sObject 的. 关于在 Apex 中得到 sObject 的信息,我们要基于两 ...

  6. Jquery 使用和Jquery选择器

    jQuery中的顶级对象($) jQuery 中最常用的对象即 $ 对象,要想使用 jQuery 的方法必须通过 $ 对象.只有将普通的 Dom 对象封装成 jQuery 对象,然后才能调用 jQue ...

  7. Go 语言笔记

    Go 语言笔记 基本概念 综述 Go 语言将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡. 设计者通过 goroutine 这种轻量级线程的概念来实现这个目标,然后通过 ch ...

  8. Django 简介

    一  MVC  与 MTV 模型 (1)MVC C: controller 控制器(url分发和视图函数) V: 存放html文件 M: model:数据库操作 Web服务器开发领域里著名的MVC模式 ...

  9. Linux运维基础

    一.服务器硬件 二.Linux的发展史 三.Linux的系统安装和配置 四.Xshell的安装和优化 五.远程连接排错 六.Linux命令初识 七.Linux系统初识与优化 八.Linux目录结构 九 ...

  10. day5-python的文件操作-坚持就好

    目录摘要 文件处理 1.文件初识 2.文件的读操作 3.文件的写操作 4.文件的追加操作 5.文件的其他操作 6.文件的修改 正式开始 文件处理:写了这么多代码了,有的时候我们执行完成的结果想永久保存 ...