Skip to content

Commit

Permalink
FEAT: 增加ValidateAttribute校验特性及相关校验处理器
Browse files Browse the repository at this point in the history
  • Loading branch information
褚知霖 committed Jan 8, 2020
1 parent 0d4b85a commit e301756
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
using Surging.Core.CPlatform.Transport.Codec;
using Surging.Core.CPlatform.Transport.Codec.Implementation;
using Surging.Core.CPlatform.Utilities;
using Surging.Core.CPlatform.Validation;
using Surging.Core.CPlatform.Validation.Implementation;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -445,6 +447,8 @@ public static IServiceBuilder AddCoreService(this ContainerBuilder services)
services.RegisterType(typeof(AuthorizationAttribute)).As(typeof(IAuthorizationFilter)).SingleInstance();
//注册基本过滤
services.RegisterType(typeof(AuthorizationAttribute)).As(typeof(IFilter)).SingleInstance();
//注册默认校验处理器
services.RegisterType(typeof(DefaultValidationProcessor)).As(typeof(IValidationProcessor)).SingleInstance();
//注册服务器路由接口
services.RegisterType(typeof(DefaultServiceRouteProvider)).As(typeof(IServiceRouteProvider)).SingleInstance();
//注册服务路由工厂
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Surging.Core.CPlatform.Exceptions
{
/// <summary>
/// Model、DTO等对象校验异常
/// </summary>
public class ValidateException : CPlatformException
{
/// <summary>
/// 初始构造函数
/// </summary>
/// <param name="message">异常信息</param>
/// <param name="innerException">内部异常</param>
public ValidateException(string message, Exception innerException = null) : base(message, innerException)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Surging.Core.CPlatform.Validation;
using static Surging.Core.CPlatform.Utilities.FastInvoke;

namespace Surging.Core.CPlatform.Runtime.Server.Implementation.ServiceDiscovery.Implementation
Expand All @@ -23,14 +25,17 @@ public class ClrServiceEntryFactory : IClrServiceEntryFactory
private readonly CPlatformContainer _serviceProvider;
private readonly IServiceIdGenerator _serviceIdGenerator;
private readonly ITypeConvertibleService _typeConvertibleService;
private readonly IValidationProcessor _validationProcessor;

#endregion Field

#region Constructor
public ClrServiceEntryFactory(CPlatformContainer serviceProvider, IServiceIdGenerator serviceIdGenerator, ITypeConvertibleService typeConvertibleService)
public ClrServiceEntryFactory(CPlatformContainer serviceProvider, IServiceIdGenerator serviceIdGenerator, ITypeConvertibleService typeConvertibleService, IValidationProcessor validationProcessor)
{
_serviceProvider = serviceProvider;
_serviceIdGenerator = serviceIdGenerator;
_typeConvertibleService = typeConvertibleService;
_validationProcessor = validationProcessor;
}

#endregion Constructor
Expand Down Expand Up @@ -75,6 +80,20 @@ private ServiceEntry Create(MethodInfo method, string serviceName, string routeT
{
descriptorAttribute.Apply(serviceDescriptor);
}
var httpMethodAttributes = attributes.Where(p => p is HttpMethodAttribute).Select(p => p as HttpMethodAttribute).ToList();
var httpMethods = new List<string>();
StringBuilder httpMethod = new StringBuilder();
foreach (var attribute in httpMethodAttributes)
{
httpMethods.AddRange(attribute.HttpMethods);
if (attribute.IsRegisterMetadata)
httpMethod.AppendJoin(',',attribute.HttpMethods).Append(",");
}
if (httpMethod.Length > 0)
{
httpMethod.Length = httpMethod.Length - 1;
serviceDescriptor.HttpMethod(httpMethod.ToString());
}
var authorization = attributes.Where(p => p is AuthorizationFilterAttribute).FirstOrDefault();
if (authorization != null)
serviceDescriptor.EnableAuthorization(true);
Expand All @@ -84,10 +103,16 @@ private ServiceEntry Create(MethodInfo method, string serviceName, string routeT
?? AuthorizationType.AppSecret);
}
var fastInvoker = GetHandler(serviceId, method);

var methodValidateAttribute = method.GetCustomAttributes(typeof(ValidateAttribute))
.Cast<ValidateAttribute>()
.FirstOrDefault();

return new ServiceEntry
{
Descriptor = serviceDescriptor,
RoutePath = serviceDescriptor.RoutePath,
Methods=httpMethods,
MethodName = method.Name,
Type = method.DeclaringType,
Attributes = attributes,
Expand All @@ -109,6 +134,9 @@ private ServiceEntry Create(MethodInfo method, string serviceName, string routeT
continue;
}
var value = parameters[parameterInfo.Name];

_validationProcessor.Validate(parameterInfo, value, methodValidateAttribute);

var parameterType = parameterInfo.ParameterType;
var parameter = _typeConvertibleService.Convert(value, parameterType);
list.Add(parameter);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Reflection;

namespace Surging.Core.CPlatform.Validation
{
public interface IValidationProcessor
{
void Validate(ParameterInfo parameterInfo, object value, ValidateAttribute methodValidateAttribute = null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Surging.Core.CPlatform.Convertibles;
using Surging.Core.CPlatform.Exceptions;
using Surging.Core.CPlatform.Utilities;

namespace Surging.Core.CPlatform.Validation.Implementation
{
public class DefaultValidationProcessor : IValidationProcessor
{
private readonly ITypeConvertibleService _typeConvertibleService;

public DefaultValidationProcessor(ITypeConvertibleService typeConvertibleService)
{
_typeConvertibleService = typeConvertibleService;
}

public void Validate(ParameterInfo parameterInfo, object value, ValidateAttribute methodValidateAttribute = null)
{
Check.NotNull(parameterInfo, nameof(parameterInfo));

var parameterType = parameterInfo.ParameterType;
if (value != null)
{
var parameter = _typeConvertibleService.Convert(value, parameterType);
var customAttributes = parameterInfo.GetCustomAttributes(true);
if (customAttributes.Any(at => at is ValidateAttribute) || methodValidateAttribute != null)
{
var validateAttr =
(ValidateAttribute)customAttributes.FirstOrDefault(at => at is ValidateAttribute) ?? methodValidateAttribute;

var customValidAttributes = customAttributes
.Where(ca => ca.GetType() != typeof(ValidateAttribute))
.OfType<ValidationAttribute>()
.ToList();

var validationContext = new ValidationContext(parameter);
var validationResults = new List<ValidationResult>();
var isObjValid = Validator.TryValidateObject(parameter, validationContext,
validationResults,
true);

var isValueValid = Validator.TryValidateValue(parameter, validationContext,
validationResults, customValidAttributes);

if (isObjValid && isValueValid) return;

throw new ValidateException(validationResults.Select(p => p.ErrorMessage).First());
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Surging.Core.CPlatform.Validation
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter)]
public class ValidateAttribute : Attribute
{
public ValidateAttribute()
{

}
}
}
107 changes: 59 additions & 48 deletions src/Surging.Core/Surging.Core.KestrelHttpServer/HttpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
using System.Text;
using System.Threading.Tasks;
using static Surging.Core.CPlatform.Utilities.FastInvoke;
using System.Diagnostics;
using Surging.Core.CPlatform.Diagnostics;
using Surging.Core.CPlatform.Exceptions;
using Surging.Core.CPlatform.Transport.Implementation;

namespace Surging.Core.KestrelHttpServer
{
Expand All @@ -29,6 +33,7 @@ public class HttpExecutor : IServiceExecutor
private readonly IAuthorizationFilter _authorizationFilter;
private readonly CPlatformContainer _serviceProvider;
private readonly ITypeConvertibleService _typeConvertibleService;
private readonly IServiceProxyProvider _serviceProxyProvider;
private readonly ConcurrentDictionary<string, ValueTuple<FastInvokeHandler, object, MethodInfo>> _concurrent =
new ConcurrentDictionary<string, ValueTuple<FastInvokeHandler, object, MethodInfo>>();
#endregion Field
Expand All @@ -37,14 +42,15 @@ public class HttpExecutor : IServiceExecutor

public HttpExecutor(IServiceEntryLocate serviceEntryLocate, IServiceRouteProvider serviceRouteProvider,
IAuthorizationFilter authorizationFilter,
ILogger<HttpExecutor> logger, CPlatformContainer serviceProvider, ITypeConvertibleService typeConvertibleService)
ILogger<HttpExecutor> logger, CPlatformContainer serviceProvider, IServiceProxyProvider serviceProxyProvider, ITypeConvertibleService typeConvertibleService)
{
_serviceEntryLocate = serviceEntryLocate;
_logger = logger;
_serviceProvider = serviceProvider;
_typeConvertibleService = typeConvertibleService;
_serviceRouteProvider = serviceRouteProvider;
_authorizationFilter = authorizationFilter;
_serviceProxyProvider = serviceProxyProvider;
}
#endregion Constructor

Expand All @@ -67,70 +73,39 @@ public async Task ExecuteAsync(IMessageSender sender, TransportMessage message)
_logger.LogError(exception, "将接收到的消息反序列化成 TransportMessage<httpMessage> 时发送了错误。");
return;
}
var entry = _serviceEntryLocate.Locate(httpMessage);
if (entry == null)
if (httpMessage.Attachments != null)
{
if (_logger.IsEnabled(LogLevel.Error))
_logger.LogError($"根据服务routePath:{httpMessage.RoutePath},找不到服务条目。");
return;
foreach (var attachment in httpMessage.Attachments)
RpcContext.GetContext().SetAttachment(attachment.Key, attachment.Value);
}
if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug("准备执行本地逻辑。");
WirteDiagnosticBefore(message);
var entry = _serviceEntryLocate.Locate(httpMessage);

HttpResultMessage<object> httpResultMessage = new HttpResultMessage<object>() { };

if (_serviceProvider.IsRegisteredWithKey(httpMessage.ServiceKey, entry.Type))
if (entry!=null && _serviceProvider.IsRegisteredWithKey(httpMessage.ServiceKey, entry.Type))
{
//执行本地代码。
httpResultMessage = await LocalExecuteAsync(entry, httpMessage);
}
else
{
httpResultMessage = await RemoteExecuteAsync(entry, httpMessage);
httpResultMessage = await RemoteExecuteAsync(httpMessage);
}
await SendRemoteInvokeResult(sender, httpResultMessage);
await SendRemoteInvokeResult(sender,message.Id, httpResultMessage);
}


#endregion Implementation of IServiceExecutor

#region Private Method

private async Task<HttpResultMessage<object>> RemoteExecuteAsync(ServiceEntry entry, HttpMessage httpMessage)
private async Task<HttpResultMessage<object>> RemoteExecuteAsync(HttpMessage httpMessage)
{
HttpResultMessage<object> resultMessage = new HttpResultMessage<object>();
var provider = _concurrent.GetValueOrDefault(httpMessage.RoutePath);
var list = new List<object>();
if (provider.Item1 == null)
{
provider.Item2 = ServiceLocator.GetService<IServiceProxyFactory>().CreateProxy(httpMessage.ServiceKey, entry.Type);
provider.Item3 = provider.Item2.GetType().GetTypeInfo().DeclaredMethods.Where(p => p.Name == entry.MethodName).FirstOrDefault();
provider.Item1 = FastInvoke.GetMethodInvoker(provider.Item3);
_concurrent.GetOrAdd(httpMessage.RoutePath, ValueTuple.Create<FastInvokeHandler, object, MethodInfo>(provider.Item1, provider.Item2, provider.Item3));
}
foreach (var parameterInfo in provider.Item3.GetParameters())
{
var value = httpMessage.Parameters[parameterInfo.Name];
var parameterType = parameterInfo.ParameterType;
var parameter = _typeConvertibleService.Convert(value, parameterType);
list.Add(parameter);
}
try
{
var methodResult = provider.Item1(provider.Item2, list.ToArray());

var task = methodResult as Task;
if (task == null)
{
resultMessage.Entity = methodResult;
}
else
{
await task;
var taskType = task.GetType().GetTypeInfo();
if (taskType.IsGenericType)
resultMessage.Entity = taskType.GetProperty("Result").GetValue(task);
}
resultMessage.IsSucceed = resultMessage.Entity != null;
try {
resultMessage.Entity=await _serviceProxyProvider.Invoke<object>(httpMessage.Parameters, httpMessage.RoutePath, httpMessage.ServiceKey);
resultMessage.IsSucceed = resultMessage.Entity != default;
resultMessage.StatusCode = resultMessage.IsSucceed ? (int)StatusCode.Success : (int)StatusCode.RequestError;
}
catch (Exception ex)
Expand Down Expand Up @@ -161,8 +136,18 @@ private async Task<HttpResultMessage<object>> LocalExecuteAsync(ServiceEntry ent
if (taskType.IsGenericType)
resultMessage.Entity = taskType.GetProperty("Result").GetValue(task);
}

resultMessage.IsSucceed = resultMessage.Entity != null;
resultMessage.StatusCode = resultMessage.IsSucceed ? (int)StatusCode.Success : (int)StatusCode.RequestError;
resultMessage.StatusCode =
resultMessage.IsSucceed ? (int) StatusCode.Success : (int) StatusCode.RequestError;
}
catch (ValidateException validateException)
{
if (_logger.IsEnabled(LogLevel.Error))
_logger.LogError(validateException, "执行本地逻辑时候发生了错误。", validateException);

resultMessage.Message = validateException.Message;
resultMessage.StatusCode = validateException.HResult;
}
catch (Exception exception)
{
Expand All @@ -174,14 +159,14 @@ private async Task<HttpResultMessage<object>> LocalExecuteAsync(ServiceEntry ent
return resultMessage;
}

private async Task SendRemoteInvokeResult(IMessageSender sender, HttpResultMessage resultMessage)
private async Task SendRemoteInvokeResult(IMessageSender sender,string messageId, HttpResultMessage resultMessage)
{
try
{
if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug("准备发送响应消息。");

await sender.SendAndFlushAsync(new TransportMessage(resultMessage));
await sender.SendAndFlushAsync(new TransportMessage(messageId,resultMessage));
if (_logger.IsEnabled(LogLevel.Debug))
_logger.LogDebug("响应消息发送成功。");
}
Expand All @@ -205,6 +190,32 @@ private static string GetExceptionMessage(Exception exception)
return message;
}

private void WirteDiagnosticBefore(TransportMessage message)
{
if (!AppConfig.ServerOptions.DisableDiagnostic)
{
RpcContext.GetContext().SetAttachment("TraceId", message.Id);
var diagnosticListener = new DiagnosticListener(DiagnosticListenerExtensions.DiagnosticListenerName);
var remoteInvokeMessage = message.GetContent<HttpMessage>();
diagnosticListener.WriteTransportBefore(TransportType.Rest, new TransportEventData(new DiagnosticMessage
{
Content = message.Content,
ContentType = message.ContentType,
Id = message.Id,
MessageName = remoteInvokeMessage.RoutePath
}, TransportType.Rest.ToString(),
message.Id,
RpcContext.GetContext().GetAttachment("RemoteIpAddress")?.ToString()));
}
else
{
var parameters = RpcContext.GetContext().GetContextParameters();
parameters.TryRemove("RemoteIpAddress", out object value);
RpcContext.GetContext().SetContextParameters(parameters);
}

}

#endregion Private Method

}
Expand Down
Loading

0 comments on commit e301756

Please sign in to comment.