35 changed files with 1284 additions and 3 deletions
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net5.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore" Version="4.4.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\common\LINGYUN.Abp.Wrapper\LINGYUN.Abp.Wrapper.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.OpenApi\LINGYUN.Abp.OpenApi.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,16 @@ |
|||||
|
using LINGYUN.Abp.Wrapper; |
||||
|
using Volo.Abp.AspNetCore; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.Authorization |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpWrapperModule), |
||||
|
typeof(AbpTimingModule), |
||||
|
typeof(AbpOpenApiModule), |
||||
|
typeof(AbpAspNetCoreModule))] |
||||
|
public class AbpOpenApiAuthorizationModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.Authorization |
||||
|
{ |
||||
|
public interface IOpenApiAuthorizationService |
||||
|
{ |
||||
|
Task<bool> AuthorizeAsync(HttpContext httpContext); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.Authorization |
||||
|
{ |
||||
|
public class OpenApiAuthorizationMiddleware : IMiddleware, ITransientDependency |
||||
|
{ |
||||
|
private readonly IOpenApiAuthorizationService _authorizationService; |
||||
|
public OpenApiAuthorizationMiddleware( |
||||
|
IOpenApiAuthorizationService authorizationService) |
||||
|
{ |
||||
|
_authorizationService = authorizationService; |
||||
|
} |
||||
|
|
||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
||||
|
{ |
||||
|
if (await _authorizationService.AuthorizeAsync(context)) |
||||
|
{ |
||||
|
await next(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,234 @@ |
|||||
|
using LINGYUN.Abp.Wrapper; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Microsoft.Extensions.Primitives; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Net; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.AspNetCore.ExceptionHandling; |
||||
|
using Volo.Abp.AspNetCore.WebClientInfo; |
||||
|
using Volo.Abp.Clients; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Http; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.Authorization |
||||
|
{ |
||||
|
public class OpenApiAuthorizationService : IOpenApiAuthorizationService, ITransientDependency |
||||
|
{ |
||||
|
private readonly IAppKeyStore _appKeyStore; |
||||
|
private readonly AbpOpenApiOptions _openApiOptions; |
||||
|
private readonly ICurrentClient _currentClient; |
||||
|
private readonly IWebClientInfoProvider _clientInfoProvider; |
||||
|
|
||||
|
public OpenApiAuthorizationService( |
||||
|
IAppKeyStore appKeyStore, |
||||
|
ICurrentClient currentClient, |
||||
|
IWebClientInfoProvider clientInfoProvider, |
||||
|
IOptionsMonitor<AbpOpenApiOptions> options) |
||||
|
{ |
||||
|
_appKeyStore = appKeyStore; |
||||
|
_currentClient = currentClient; |
||||
|
_clientInfoProvider = clientInfoProvider; |
||||
|
_openApiOptions = options.CurrentValue; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> AuthorizeAsync(HttpContext httpContext) |
||||
|
{ |
||||
|
if (!_openApiOptions.IsEnabled) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (_currentClient.IsAuthenticated && |
||||
|
_openApiOptions.HasWhiteClient(_currentClient.Id)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (!string.IsNullOrWhiteSpace(_clientInfoProvider.ClientIpAddress) && |
||||
|
_openApiOptions.HasWhiteIpAddress(_clientInfoProvider.ClientIpAddress)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
BusinessException exception; |
||||
|
if (!httpContext.Request.QueryString.HasValue) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound, |
||||
|
$"{AbpOpenApiConsts.AppKeyFieldName} Not Found", |
||||
|
$"{AbpOpenApiConsts.AppKeyFieldName} Not Found"); |
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.AppKeyFieldName, out var appKey); |
||||
|
httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.SignatureFieldName, out var sign); |
||||
|
httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.TimeStampFieldName, out var timeStampString); |
||||
|
|
||||
|
if (StringValues.IsNullOrEmpty(appKey)) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound, |
||||
|
$"{AbpOpenApiConsts.AppKeyFieldName} Not Found", |
||||
|
$"{AbpOpenApiConsts.AppKeyFieldName} Not Found"); |
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (StringValues.IsNullOrEmpty(sign)) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithSignNotFound, |
||||
|
$"{AbpOpenApiConsts.SignatureFieldName} Not Found", |
||||
|
$"{AbpOpenApiConsts.SignatureFieldName} Not Found"); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (StringValues.IsNullOrEmpty(timeStampString)) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithTimestampNotFound, |
||||
|
$"{AbpOpenApiConsts.TimeStampFieldName} Not Found", |
||||
|
$"{AbpOpenApiConsts.TimeStampFieldName} Not Found"); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!long.TryParse(timeStampString.ToString(), out long timeStamp)) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithTimestamp, |
||||
|
"invalid timestamp", |
||||
|
"invalid timestamp"); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var appDescriptor = await _appKeyStore.FindAsync(appKey.ToString()); |
||||
|
if (appDescriptor == null) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithAppKey, |
||||
|
"invalid appKey", |
||||
|
"invalid appKey") |
||||
|
.WithData("AppKey", appKey.ToString()); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var queryDictionary = new Dictionary<string, string>(); |
||||
|
var queryStringCollection = httpContext.Request.Query.OrderBy(q => q.Key); |
||||
|
foreach (var queryString in queryStringCollection) |
||||
|
{ |
||||
|
if (queryString.Key.Equals(AbpOpenApiConsts.SignatureFieldName)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
queryDictionary.Add(queryString.Key, queryString.Value.ToString()); |
||||
|
} |
||||
|
|
||||
|
var requiredSign = CalculationSignature(httpContext.Request.Path.Value, appDescriptor.AppSecret, queryDictionary); |
||||
|
if (!string.Equals(requiredSign, sign.ToString())) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithSign, |
||||
|
"invalid signature", |
||||
|
"invalid signature"); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (appDescriptor.SignLifetime.HasValue && appDescriptor.SignLifetime > 0) |
||||
|
{ |
||||
|
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); |
||||
|
|
||||
|
if ((now - timeStamp) / 1000 > appDescriptor.SignLifetime.Value) |
||||
|
{ |
||||
|
exception = new BusinessException( |
||||
|
AbpOpenApiConsts.InvalidAccessWithTimestamp, |
||||
|
"session timed out or expired", |
||||
|
"session timed out or expired"); |
||||
|
|
||||
|
await Unauthorized(httpContext, exception); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task Unauthorized(HttpContext context, BusinessException exception) |
||||
|
{ |
||||
|
var errorInfoConverter = context.RequestServices.GetRequiredService<IExceptionToErrorInfoConverter>(); |
||||
|
var errorInfo = errorInfoConverter.Convert(exception, false); |
||||
|
|
||||
|
var exceptionWrapHandlerFactory = context.RequestServices.GetRequiredService<IExceptionWrapHandlerFactory>(); |
||||
|
var exceptionWrapContext = new ExceptionWrapContext( |
||||
|
exception, |
||||
|
errorInfo, |
||||
|
context.RequestServices); |
||||
|
exceptionWrapHandlerFactory.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext); |
||||
|
|
||||
|
if (context.Request.CanAccept(MimeTypes.Application.Json) || |
||||
|
context.Request.IsAjax()) |
||||
|
{ |
||||
|
var wrapResult = new WrapResult( |
||||
|
exceptionWrapContext.ErrorInfo.Code, |
||||
|
exceptionWrapContext.ErrorInfo.Message, |
||||
|
exceptionWrapContext.ErrorInfo.Details); |
||||
|
|
||||
|
var jsonSerializer = context.RequestServices.GetRequiredService<IJsonSerializer>(); |
||||
|
|
||||
|
context.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); |
||||
|
context.Response.StatusCode = (int)HttpStatusCode.OK; |
||||
|
|
||||
|
await context.Response.WriteAsync(jsonSerializer.Serialize(wrapResult)); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
context.Response.StatusCode = (int)HttpStatusCode.BadRequest; |
||||
|
await context.Response.WriteAsync(errorInfo.Message); |
||||
|
} |
||||
|
|
||||
|
private static string CalculationSignature(string url, string appKey, IDictionary<string, string> queryDictionary) |
||||
|
{ |
||||
|
var queryString = BuildQuery(queryDictionary); |
||||
|
var encodeUrl = UrlEncode(string.Concat(url, "?", queryString, appKey)); |
||||
|
|
||||
|
return encodeUrl.ToMd5(); |
||||
|
} |
||||
|
|
||||
|
private static string BuildQuery(IDictionary<string, string> queryStringDictionary) |
||||
|
{ |
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
foreach (var queryString in queryStringDictionary) |
||||
|
{ |
||||
|
sb.Append(queryString.Key) |
||||
|
.Append('=') |
||||
|
.Append(queryString.Value) |
||||
|
.Append('&'); |
||||
|
} |
||||
|
sb.Remove(sb.Length - 1, 1); |
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
|
||||
|
private static string UrlEncode(string str) |
||||
|
{ |
||||
|
return HttpUtility.UrlEncode(str); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.OpenApi.Authorization; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Builder |
||||
|
{ |
||||
|
public static class OpenApiAuthorizationApplicationBuilderExtensions |
||||
|
{ |
||||
|
public static IApplicationBuilder UseOpenApiAuthorization(this IApplicationBuilder app) |
||||
|
{ |
||||
|
return app.UseMiddleware<OpenApiAuthorizationMiddleware>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Remove="LINGYUN\Abp\OpenApi\Localization\Resources\en.json" /> |
||||
|
<None Remove="LINGYUN\Abp\OpenApi\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\OpenApi\Localization\Resources\en.json" /> |
||||
|
<EmbeddedResource Include="LINGYUN\Abp\OpenApi\Localization\Resources\zh-Hans.json" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Security" Version="4.4.0" /> |
||||
|
<PackageReference Include="Volo.Abp.Localization" Version="4.4.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,22 @@ |
|||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public static class AbpOpenApiConsts |
||||
|
{ |
||||
|
public const string SecurityChecking = "_AbpOpenApiSecurityChecking"; |
||||
|
|
||||
|
public const string AppKeyFieldName = "appKey"; |
||||
|
public const string SignatureFieldName = "sign"; |
||||
|
public const string TimeStampFieldName = "t"; |
||||
|
|
||||
|
public const string KeyPrefix = "AbpOpenApi"; |
||||
|
|
||||
|
public const string InvalidAccessWithAppKey = KeyPrefix + ":9100"; |
||||
|
public const string InvalidAccessWithAppKeyNotFound = KeyPrefix + ":9101"; |
||||
|
|
||||
|
public const string InvalidAccessWithSign = KeyPrefix + ":9110"; |
||||
|
public const string InvalidAccessWithSignNotFound = KeyPrefix + ":9111"; |
||||
|
|
||||
|
public const string InvalidAccessWithTimestamp = KeyPrefix + ":9210"; |
||||
|
public const string InvalidAccessWithTimestampNotFound = KeyPrefix + ":9211"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using LINGYUN.Abp.OpenApi.Localization; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Localization.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Security; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpSecurityModule), |
||||
|
typeof(AbpLocalizationModule))] |
||||
|
public class AbpOpenApiModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
|
||||
|
Configure<AbpOpenApiOptions>(configuration.GetSection("OpenApi")); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpOpenApiModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Add<OpenApiResource>() |
||||
|
.AddVirtualJson("/LINGYUN/Abp/OpenApi/Localization/Resources"); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpExceptionLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.MapCodeNamespace(AbpOpenApiConsts.KeyPrefix, typeof(OpenApiResource)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public class AbpOpenApiOptions |
||||
|
{ |
||||
|
public bool IsEnabled { get; set; } |
||||
|
public string[] WhiteIpAddress { get; set; } |
||||
|
public string[] WhiteClient { get; set; } |
||||
|
public AbpOpenApiOptions() |
||||
|
{ |
||||
|
IsEnabled = true; |
||||
|
WhiteIpAddress = new string[0]; |
||||
|
WhiteClient = new string[0]; |
||||
|
} |
||||
|
|
||||
|
public bool HasWhiteIpAddress(string ipAddress) |
||||
|
{ |
||||
|
return WhiteIpAddress?.Contains(ipAddress) == true; |
||||
|
} |
||||
|
|
||||
|
public bool HasWhiteClient(string clientId) |
||||
|
{ |
||||
|
if (clientId.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
return WhiteClient?.Contains(clientId) == true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public class AppDescriptor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 应用名称
|
||||
|
/// </summary>
|
||||
|
public string AppName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 应用标识
|
||||
|
/// </summary>
|
||||
|
public string AppKey { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 应用密钥
|
||||
|
/// </summary>
|
||||
|
public string AppSecret { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 应用token
|
||||
|
/// </summary>
|
||||
|
public string AppToken { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 签名有效时间
|
||||
|
/// 单位: s
|
||||
|
/// </summary>
|
||||
|
public int? SignLifetime { get; set; } |
||||
|
|
||||
|
public AppDescriptor() { } |
||||
|
public AppDescriptor( |
||||
|
string appName, |
||||
|
string appKey, |
||||
|
string appSecret, |
||||
|
string appToken = null, |
||||
|
int? signLifeTime = null) |
||||
|
{ |
||||
|
AppName = appName; |
||||
|
AppKey = appKey; |
||||
|
AppSecret = appSecret; |
||||
|
AppToken = appToken; |
||||
|
SignLifetime = signLifeTime; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace LINGYUN.Abp.OpenApi.ConfigurationStore |
||||
|
{ |
||||
|
public class AbpDefaultAppKeyStoreOptions |
||||
|
{ |
||||
|
public AppDescriptor[] Apps { get; set; } |
||||
|
|
||||
|
public AbpDefaultAppKeyStoreOptions() |
||||
|
{ |
||||
|
Apps = new AppDescriptor[0]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.ConfigurationStore |
||||
|
{ |
||||
|
[Dependency(TryRegister = true)] |
||||
|
public class DefaultAppKeyStore : IAppKeyStore, ITransientDependency |
||||
|
{ |
||||
|
private readonly AbpDefaultAppKeyStoreOptions _options; |
||||
|
|
||||
|
public DefaultAppKeyStore(IOptionsMonitor<AbpDefaultAppKeyStoreOptions> options) |
||||
|
{ |
||||
|
_options = options.CurrentValue; |
||||
|
} |
||||
|
|
||||
|
public Task<AppDescriptor> FindAsync(string appKey) |
||||
|
{ |
||||
|
return Task.FromResult(Find(appKey)); |
||||
|
} |
||||
|
|
||||
|
public AppDescriptor Find(string appKey) |
||||
|
{ |
||||
|
return _options.Apps?.FirstOrDefault(t => t.AppKey == appKey); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public interface IAppKeyStore |
||||
|
{ |
||||
|
Task<AppDescriptor> FindAsync(string appKey); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Localization; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi.Localization |
||||
|
{ |
||||
|
[LocalizationResourceName("OpenApi")] |
||||
|
public class OpenApiResource |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"culture": "en", |
||||
|
"texts": { |
||||
|
"AbpOpenApi:9100": "Invalid appKey {AppKey}.", |
||||
|
"AbpOpenApi:9101": "appKey not found.", |
||||
|
"AbpOpenApi:9110": "Invalid sign.", |
||||
|
"AbpOpenApi:9111": "sign not found.", |
||||
|
"AbpOpenApi:9210": "Request timed out or the session expired.", |
||||
|
"AbpOpenApi:9211": "timestamp not found." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"culture": "zh-Hans", |
||||
|
"texts": { |
||||
|
"AbpOpenApi:9100": "无效的应用标识 {AppKey}.", |
||||
|
"AbpOpenApi:9101": "未携带应用标识(appKey).", |
||||
|
"AbpOpenApi:9110": "无效的签名 sign.", |
||||
|
"AbpOpenApi:9111": "未携带签名(sign).", |
||||
|
"AbpOpenApi:9210": "请求超时或会话已过期.", |
||||
|
"AbpOpenApi:9211": "未携带时间戳标识." |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using OpenApi; |
||||
|
using System; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection |
||||
|
{ |
||||
|
public static class ClientProxyServiceCollectionExtensions |
||||
|
{ |
||||
|
public static IServiceCollection AddClientProxy(this IServiceCollection services, string serverUrl) |
||||
|
{ |
||||
|
services.AddHttpClient("opensdk", options => |
||||
|
{ |
||||
|
options.BaseAddress = new Uri(serverUrl); |
||||
|
}); |
||||
|
services.AddSingleton<IClientProxy, ClientProxy>(); |
||||
|
|
||||
|
return services; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> |
||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace OpenApi |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ApiResponse : ApiResponse<object> |
||||
|
{ |
||||
|
public ApiResponse() { } |
||||
|
public ApiResponse( |
||||
|
string code, |
||||
|
string message, |
||||
|
string details = null) |
||||
|
: base(code, message, details) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public ApiResponse( |
||||
|
string code, |
||||
|
object result, |
||||
|
string message = "OK") |
||||
|
: base(code, result, message) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace OpenApi |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class ApiResponse<TResult> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 错误代码
|
||||
|
/// </summary>
|
||||
|
public string Code { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误提示消息
|
||||
|
/// </summary>
|
||||
|
public string Message { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 补充消息
|
||||
|
/// </summary>
|
||||
|
public string Details { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回值
|
||||
|
/// </summary>
|
||||
|
public TResult Result { get; set; } |
||||
|
public ApiResponse() { } |
||||
|
public ApiResponse( |
||||
|
string code, |
||||
|
string message, |
||||
|
string details = null) |
||||
|
{ |
||||
|
Code = code; |
||||
|
Message = message; |
||||
|
Details = details; |
||||
|
} |
||||
|
|
||||
|
public ApiResponse( |
||||
|
string code, |
||||
|
TResult result, |
||||
|
string message = "OK") |
||||
|
{ |
||||
|
Code = code; |
||||
|
Result = result; |
||||
|
Message = message; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
using Newtonsoft.Json; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Web; |
||||
|
|
||||
|
namespace OpenApi |
||||
|
{ |
||||
|
public class ClientProxy : IClientProxy |
||||
|
{ |
||||
|
private readonly IHttpClientFactory _httpClientFactory; |
||||
|
|
||||
|
public ClientProxy(IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
_httpClientFactory = httpClientFactory; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> DeleteAsync<TResult>(string url, string appKey, string appSecret) |
||||
|
{ |
||||
|
return await RequestAsync<TResult>(url, appKey, appSecret, HttpMethod.Delete); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> GetAsync<TResult>(string url, string appKey, string appSecret) |
||||
|
{ |
||||
|
return await RequestAsync<TResult>(url, appKey, appSecret, HttpMethod.Get); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> PostAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request) |
||||
|
{ |
||||
|
return await RequestAsync<TRequest, TResult>(url, appKey, appSecret, request, HttpMethod.Post); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> PutAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request) |
||||
|
{ |
||||
|
return await RequestAsync<TRequest, TResult>(url, appKey, appSecret, request, HttpMethod.Put); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> RequestAsync<TResult>(string url, string appKey, string appSecret, HttpMethod httpMethod) |
||||
|
{ |
||||
|
return await RequestAsync<object, TResult>(url, appKey, appSecret, null, httpMethod); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> RequestAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod) |
||||
|
{ |
||||
|
// 构建请求客户端
|
||||
|
var client = _httpClientFactory.CreateClient("opensdk"); |
||||
|
|
||||
|
return await RequestAsync<TRequest, TResult>(client, url, appKey, appSecret, request, httpMethod); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<ApiResponse<TResult>> RequestAsync<TRequest, TResult>(HttpClient client, string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod) |
||||
|
{ |
||||
|
// UTC时间戳
|
||||
|
var timeStamp = GetUtcTimeStampString(); |
||||
|
// 取出api地址
|
||||
|
var baseUrl = url.Split('?')[0]; |
||||
|
// 组装请求参数
|
||||
|
var requestUrl = string.Concat( |
||||
|
url, |
||||
|
url.Contains('?') ? "&" : "?", |
||||
|
"appKey=", |
||||
|
appKey, |
||||
|
"&t=", |
||||
|
timeStamp); |
||||
|
var quertString = ReverseQueryString(requestUrl); |
||||
|
// 对请求参数签名
|
||||
|
var sign = CalculationSignature(baseUrl, appSecret, quertString); |
||||
|
// 签名随请求传递
|
||||
|
quertString.Add("sign", sign); |
||||
|
// 重新拼接请求参数
|
||||
|
requestUrl = string.Concat(baseUrl, "?", BuildQuery(quertString)); |
||||
|
// 构建请求体
|
||||
|
var requestMessage = new HttpRequestMessage(httpMethod, requestUrl); |
||||
|
|
||||
|
if (request != null) |
||||
|
{ |
||||
|
// Request Payload
|
||||
|
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request)); |
||||
|
} |
||||
|
|
||||
|
// 返回中文错误提示
|
||||
|
requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(CultureInfo.CurrentUICulture.Name)); |
||||
|
// 返回错误消息可序列化
|
||||
|
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); |
||||
|
// 序列化响应
|
||||
|
var response = await client.SendAsync(requestMessage); |
||||
|
var stringContent = await response.Content.ReadAsStringAsync(); |
||||
|
|
||||
|
return JsonConvert.DeserializeObject<ApiResponse<TResult>>(stringContent); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetUtcTimeStampString() |
||||
|
{ |
||||
|
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); |
||||
|
} |
||||
|
|
||||
|
private static IDictionary<string, string> ReverseQueryString(string requestUri) |
||||
|
{ |
||||
|
if (!requestUri.Contains("?")) |
||||
|
{ |
||||
|
throw new Exception("查询路径格式不合法"); |
||||
|
} |
||||
|
|
||||
|
var queryString = requestUri.Split('?')[1]; |
||||
|
// 按照首字母排序
|
||||
|
var paramters = queryString.Split('&').OrderBy(p => p); |
||||
|
|
||||
|
var queryDic = new Dictionary<string, string>(); |
||||
|
foreach (var parm in paramters) |
||||
|
{ |
||||
|
var thisParams = parm.Split('='); |
||||
|
if (thisParams.Length == 0) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
queryDic.Add(thisParams[0], thisParams.Length > 0 ? thisParams[1] : ""); |
||||
|
} |
||||
|
|
||||
|
// 返回参数列表
|
||||
|
return queryDic; |
||||
|
} |
||||
|
|
||||
|
private static string CalculationSignature(string url, string appSecret, IDictionary<string, string> queryDictionary) |
||||
|
{ |
||||
|
var queryString = BuildQuery(queryDictionary); |
||||
|
var requestUrl = string.Concat( |
||||
|
url, |
||||
|
url.Contains('?') ? "" : "?", |
||||
|
queryString, |
||||
|
appSecret); |
||||
|
var encodeUrl = UrlEncode(requestUrl); |
||||
|
return encodeUrl.ToMd5(); |
||||
|
} |
||||
|
|
||||
|
private static string BuildQuery(IDictionary<string, string> queryStringDictionary) |
||||
|
{ |
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
foreach (var queryString in queryStringDictionary) |
||||
|
{ |
||||
|
sb.Append(queryString.Key) |
||||
|
.Append('=') |
||||
|
.Append(queryString.Value) |
||||
|
.Append('&'); |
||||
|
} |
||||
|
sb.Remove(sb.Length - 1, 1); |
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
|
||||
|
private static string UrlEncode(string str) |
||||
|
{ |
||||
|
return HttpUtility.UrlEncode(str); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace OpenApi |
||||
|
{ |
||||
|
public interface IClientProxy |
||||
|
{ |
||||
|
Task<ApiResponse<TResult>> GetAsync<TResult>(string url, string appKey, string appSecret); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> DeleteAsync<TResult>(string url, string appKey, string appSecret); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> PutAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> PostAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> RequestAsync<TResult>(string url, string appKey, string appSecret, HttpMethod httpMethod); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> RequestAsync<TRequest, TResult>(string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod); |
||||
|
|
||||
|
Task<ApiResponse<TResult>> RequestAsync<TRequest, TResult>(HttpClient client, string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace OpenApi |
||||
|
{ |
||||
|
public static class IClientProxyExtensions |
||||
|
{ |
||||
|
public static async Task<ApiResponse<TResult>> RequestAsync<TResult>( |
||||
|
this IClientProxy proxy, |
||||
|
HttpClient client, |
||||
|
string url, |
||||
|
string appKey, |
||||
|
string appSecret, |
||||
|
HttpMethod httpMethod) |
||||
|
{ |
||||
|
return await proxy.RequestAsync<object, TResult>(client, url, appKey, appSecret, null, httpMethod); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace System |
||||
|
{ |
||||
|
internal static class StringMd5Extensions |
||||
|
{ |
||||
|
public static string ToMd5(this string str) |
||||
|
{ |
||||
|
using (MD5 mD = MD5.Create()) |
||||
|
{ |
||||
|
byte[] bytes = Encoding.UTF8.GetBytes(str); |
||||
|
byte[] array = mD.ComputeHash(bytes); |
||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||
|
byte[] array2 = array; |
||||
|
foreach (byte b in array2) |
||||
|
{ |
||||
|
stringBuilder.Append(b.ToString("X2")); |
||||
|
} |
||||
|
|
||||
|
return stringBuilder.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk.Web"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net5.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Compile Remove="LINGYUN\Abp\AspNetCore\**" /> |
||||
|
<Content Remove="LINGYUN\Abp\AspNetCore\**" /> |
||||
|
<EmbeddedResource Remove="LINGYUN\Abp\AspNetCore\**" /> |
||||
|
<None Remove="LINGYUN\Abp\AspNetCore\**" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> |
||||
|
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="4.4.0" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="coverlet.collector" Version="3.0.2"> |
||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
||||
|
<PrivateAssets>all</PrivateAssets> |
||||
|
</PackageReference> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.*" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\mvc\LINGYUN.Abp.AspNetCore.Mvc.Wrapper\LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj" /> |
||||
|
<ProjectReference Include="..\..\modules\open-api\LINGYUN.Abp.OpenApi.Authorization\LINGYUN.Abp.OpenApi.Authorization.csproj" /> |
||||
|
<ProjectReference Include="..\..\modules\open-api\OpenApi.Sdk\OpenApi.Sdk.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.AspNetCore.Tests\LINGYUN.Abp.AspNetCore.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,33 @@ |
|||||
|
using LINGYUN.Abp.AspNetCore; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public abstract class AbpOpenApiTestBase : AbpAspNetCoreTestBase<Startup> |
||||
|
{ |
||||
|
protected override IHostBuilder CreateHostBuilder() |
||||
|
{ |
||||
|
return base.CreateHostBuilder(); |
||||
|
} |
||||
|
|
||||
|
private static string CalculateContentRootPath(string projectFileName, string contentPath) |
||||
|
{ |
||||
|
var currentDirectory = Directory.GetCurrentDirectory(); |
||||
|
while (!ContainsFile(currentDirectory, projectFileName)) |
||||
|
{ |
||||
|
currentDirectory = new DirectoryInfo(currentDirectory).Parent.FullName; |
||||
|
} |
||||
|
|
||||
|
return Path.Combine(currentDirectory, contentPath); |
||||
|
} |
||||
|
|
||||
|
private static bool ContainsFile(string currentDirectory, string projectFileName) |
||||
|
{ |
||||
|
return Directory |
||||
|
.GetFiles(currentDirectory, "*.*", SearchOption.TopDirectoryOnly) |
||||
|
.Any(f => Path.GetFileName(f) == projectFileName); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,114 @@ |
|||||
|
using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; |
||||
|
using LINGYUN.Abp.OpenApi.Authorization; |
||||
|
using LINGYUN.Abp.OpenApi.Localization; |
||||
|
using Localization.Resources.AbpUi; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using OpenApi; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.Localization; |
||||
|
using Volo.Abp.AspNetCore.Security.Claims; |
||||
|
using Volo.Abp.AspNetCore.TestBase; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.Localization.ExceptionHandling; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Validation.Localization; |
||||
|
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpOpenApiAuthorizationModule), |
||||
|
typeof(AbpAspNetCoreTestBaseModule), |
||||
|
typeof(AbpAspNetCoreMvcWrapperModule), |
||||
|
typeof(AbpAutofacModule) |
||||
|
)] |
||||
|
public class AbpOpenApiTestModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.AddAssemblyResource( |
||||
|
typeof(OpenApiResource), |
||||
|
typeof(AbpOpenApiTestModule).Assembly |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// 测试环境不需要主机地址
|
||||
|
context.Services.AddHttpClient("opensdk"); |
||||
|
context.Services.AddSingleton<IClientProxy, ClientProxy>(); |
||||
|
|
||||
|
context.Services.AddAuthentication(options => |
||||
|
{ |
||||
|
options.DefaultChallengeScheme = "Bearer"; |
||||
|
options.DefaultForbidScheme = "Cookie"; |
||||
|
}).AddCookie("Cookie").AddJwtBearer("Bearer", _ => { }); |
||||
|
|
||||
|
context.Services.AddAuthorization(options => |
||||
|
{ |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpAspNetCoreMvcOptions>(options => |
||||
|
{ |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpVirtualFileSystemOptions>(options => |
||||
|
{ |
||||
|
options.FileSets.AddEmbedded<AbpOpenApiTestModule>(); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.Resources |
||||
|
.Get<OpenApiResource>() |
||||
|
.AddBaseTypes( |
||||
|
typeof(AbpUiResource), |
||||
|
typeof(AbpValidationResource) |
||||
|
).AddVirtualJson("/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources"); |
||||
|
|
||||
|
options.Languages.Add(new LanguageInfo("en", "en", "English")); |
||||
|
options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); |
||||
|
}); |
||||
|
|
||||
|
Configure<RazorPagesOptions>(options => |
||||
|
{ |
||||
|
options.RootDirectory = "/LINGYUN/Abp/AspNetCore/Mvc"; |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpClaimsMapOptions>(options => |
||||
|
{ |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
Configure<AbpExceptionLocalizationOptions>(options => |
||||
|
{ |
||||
|
options.ErrorCodeNamespaceMappings.Add("Test", typeof(OpenApiResource)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var app = context.GetApplicationBuilder(); |
||||
|
|
||||
|
app.UseCorrelationId(); |
||||
|
app.UseStaticFiles(); |
||||
|
app.UseAbpRequestLocalization(); |
||||
|
app.UseAbpSecurityHeaders(); |
||||
|
app.UseRouting(); |
||||
|
app.UseAbpClaimsMap(); |
||||
|
app.UseAuthentication(); |
||||
|
app.UseOpenApiAuthorization(); |
||||
|
app.UseAuthorization(); |
||||
|
app.UseAuditing(); |
||||
|
app.UseUnitOfWork(); |
||||
|
app.UseConfiguredEndpoints(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class FakeAppKeyStore : IAppKeyStore, ISingletonDependency |
||||
|
{ |
||||
|
public readonly static AppDescriptor AppDescriptor = |
||||
|
new AppDescriptor( |
||||
|
"TEST", |
||||
|
Guid.NewGuid().ToString("N"), |
||||
|
Guid.NewGuid().ToString("N"), |
||||
|
signLifeTime: 5); |
||||
|
|
||||
|
public virtual Task<AppDescriptor> FindAsync(string appKey) |
||||
|
{ |
||||
|
AppDescriptor app = null; |
||||
|
if (AppDescriptor.AppKey.Equals(appKey)) |
||||
|
{ |
||||
|
app = AppDescriptor; |
||||
|
} |
||||
|
|
||||
|
return Task.FromResult(app); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
[Route("/api/invoke")] |
||||
|
public class FakeInvokeController : AbpController |
||||
|
{ |
||||
|
[HttpGet] |
||||
|
public Task<string> Index() |
||||
|
{ |
||||
|
return Task.FromResult("Hello"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using OpenApi; |
||||
|
using Shouldly; |
||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public class FakeInvokeController_Tests : AbpOpenApiTestBase |
||||
|
{ |
||||
|
private readonly IClientProxy _clientProxy; |
||||
|
|
||||
|
public FakeInvokeController_Tests() |
||||
|
{ |
||||
|
_clientProxy = GetRequiredService<IClientProxy>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Invoke_Controller_And_Result_With_Hello() |
||||
|
{ |
||||
|
var result = await _clientProxy.RequestAsync<string>( |
||||
|
Client, |
||||
|
"/api/invoke", |
||||
|
FakeAppKeyStore.AppDescriptor.AppKey, |
||||
|
FakeAppKeyStore.AppDescriptor.AppSecret, |
||||
|
HttpMethod.Get); |
||||
|
|
||||
|
result.Code.ShouldBe("0"); |
||||
|
result.Message.ShouldBe("OK"); |
||||
|
result.Result.ShouldBe("Hello"); |
||||
|
result.Details.ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Invoke_Controller_With_Invalid_App_Key() |
||||
|
{ |
||||
|
using (CultureHelper.Use("en")) |
||||
|
{ |
||||
|
var appKey = Guid.NewGuid().ToString("N"); |
||||
|
var result = await _clientProxy.RequestAsync<string>( |
||||
|
Client, |
||||
|
"/api/invoke", |
||||
|
appKey, |
||||
|
Guid.NewGuid().ToString("N"), |
||||
|
HttpMethod.Get); |
||||
|
|
||||
|
result.Code.ShouldBe("9100"); |
||||
|
result.Message.ShouldBe($"Invalid appKey {appKey}."); |
||||
|
result.Result.ShouldBeNull(); |
||||
|
result.Details.ShouldBeNull(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Invoke_Controller_With_Invalid_Signature() |
||||
|
{ |
||||
|
using (CultureHelper.Use("en")) |
||||
|
{ |
||||
|
var result = await _clientProxy.RequestAsync<string>( |
||||
|
Client, |
||||
|
"/api/invoke", |
||||
|
FakeAppKeyStore.AppDescriptor.AppKey, |
||||
|
Guid.NewGuid().ToString("N"), |
||||
|
HttpMethod.Get); |
||||
|
|
||||
|
result.Code.ShouldBe("9110"); |
||||
|
result.Message.ShouldBe("Invalid sign."); |
||||
|
result.Result.ShouldBeNull(); |
||||
|
result.Details.ShouldBeNull(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
|
||||
|
namespace LINGYUN.Abp.OpenApi |
||||
|
{ |
||||
|
public class Startup |
||||
|
{ |
||||
|
public void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.AddApplication<AbpOpenApiTestModule>(); |
||||
|
} |
||||
|
|
||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) |
||||
|
{ |
||||
|
app.InitializeApplication(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"iisSettings": { |
||||
|
"windowsAuthentication": false, |
||||
|
"anonymousAuthentication": true, |
||||
|
"iisExpress": { |
||||
|
"applicationUrl": "http://localhost:49160/", |
||||
|
"sslPort": 44396 |
||||
|
} |
||||
|
}, |
||||
|
"profiles": { |
||||
|
"IIS Express": { |
||||
|
"commandName": "IISExpress", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
}, |
||||
|
"LINGYUN.Abp.OpenApi.Tests": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
}, |
||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue