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