添加项目文件。
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using CommonService;
|
||||
using MongoRepository;
|
||||
using RedisService;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Autofac
|
||||
{
|
||||
public abstract class ApplicationModule : global::Autofac.Module
|
||||
{
|
||||
protected abstract Assembly CurrentAssembly { get; }
|
||||
|
||||
protected virtual Assembly[] GetServiceAssemblies()
|
||||
{
|
||||
return new[] { CurrentAssembly };
|
||||
}
|
||||
|
||||
protected virtual bool RegisterMongoBaseService => true;
|
||||
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterGeneric(typeof(MongoBaseRepository<>))
|
||||
.As(typeof(IMongoBaseRepository<>))
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
if (RegisterMongoBaseService)
|
||||
{
|
||||
builder.RegisterGeneric(typeof(MongoBaseService<>))
|
||||
.As(typeof(IMongoBaseService<>))
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
builder.RegisterType<RedisService.RedisService>()
|
||||
.As<RedisService.IRedisService>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
var serviceAssemblies = GetServiceAssemblies();
|
||||
foreach (var assembly in serviceAssemblies)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(assembly)
|
||||
.Where(t => t.Name.EndsWith("Service") && !t.IsAbstract)
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
foreach (var assembly in serviceAssemblies)
|
||||
{
|
||||
builder.RegisterAssemblyTypes(assembly)
|
||||
.Where(t => t.Name.EndsWith("Repository") && !t.IsAbstract)
|
||||
.AsImplementedInterfaces()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace SharpAbp.Infrastructure.Api.Configuration
|
||||
{
|
||||
public class JwtConfig
|
||||
{
|
||||
public string TokenKey { get; set; } = "token";
|
||||
public string Issuer { get; set; } = "admin";
|
||||
public string Audience { get; set; } = "";
|
||||
public string SecretKey { get; set; } = "";
|
||||
public bool ValidateIssuer { get; set; } = false;
|
||||
public bool ValidateAudience { get; set; } = false;
|
||||
public bool ValidateIssuerSigningKey { get; set; } = false;
|
||||
public bool ValidateLifetime { get; set; } = false;
|
||||
}
|
||||
|
||||
public class CapConfig
|
||||
{
|
||||
public string Host { get; set; } = "localhost";
|
||||
public string UserName { get; set; } = "admin";
|
||||
public string Password { get; set; } = "";
|
||||
public string VirtualHost { get; set; } = "/";
|
||||
public string PGLogHost { get; set; } = "";
|
||||
}
|
||||
|
||||
public class RedisConfig
|
||||
{
|
||||
public string Host { get; set; } = "localhost";
|
||||
public int Port { get; set; } = 6379;
|
||||
public string UserName { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
public int Database { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class WXConfig
|
||||
{
|
||||
public string AppId { get; set; } = "";
|
||||
public string Secret { get; set; } = "";
|
||||
public string Url { get; set; } = "";
|
||||
}
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
public JwtConfig JwtConfig { get; set; } = new();
|
||||
public string PgSqlConnection { get; set; } = "";
|
||||
public CapConfig CapConfig { get; set; } = new();
|
||||
public RedisConfig RedisConfig { get; set; } = new();
|
||||
public MongoDbSettings MongoDbSettings { get; set; } = new();
|
||||
public WXConfig WXConfig { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MongoDbSettings
|
||||
{
|
||||
public string Username { get; set; } = "";
|
||||
public string Password { get; set; } = "";
|
||||
public string ServerUrl { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
namespace SharpAbp.Infrastructure.Api.Configuration
|
||||
{
|
||||
public static class ConfigurationHelper
|
||||
{
|
||||
public static string GetString(string key, string defaultValue = null)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (!string.IsNullOrEmpty(envValue))
|
||||
{
|
||||
return envValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int GetInt(string key, int defaultValue = 0)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (int.TryParse(envValue, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool GetBool(string key, bool defaultValue = false)
|
||||
{
|
||||
var envValue = Environment.GetEnvironmentVariable(key);
|
||||
if (bool.TryParse(envValue, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static string GetConnectionString(string type)
|
||||
{
|
||||
return type.ToLower() switch
|
||||
{
|
||||
"mongodb" => GetMongoDBConnectionString(),
|
||||
"redis" => GetRedisConnectionString(),
|
||||
"rabbitmq" => GetRabbitMQConnectionString(),
|
||||
"mysql" => GetMySQLConnectionString(),
|
||||
_ => throw new ArgumentException($"Unknown connection type: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetMongoDBConnectionString()
|
||||
{
|
||||
var host = GetString("MONGODB_HOST", "localhost");
|
||||
var port = GetString("MONGODB_PORT", "27017");
|
||||
var user = GetString("MONGODB_USER", "");
|
||||
var password = GetString("MONGODB_PASSWORD", "");
|
||||
var database = GetString("MONGODB_DATABASE", "data_0524");
|
||||
|
||||
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(password))
|
||||
{
|
||||
return $"mongodb://{user}:{password}@{host}:{port}/{database}";
|
||||
}
|
||||
return $"mongodb://{host}:{port}/{database}";
|
||||
}
|
||||
|
||||
private static string GetRedisConnectionString()
|
||||
{
|
||||
var host = GetString("REDIS_HOST", "localhost");
|
||||
var port = GetString("REDIS_PORT", "6379");
|
||||
var password = GetString("REDIS_PASSWORD", "");
|
||||
var database = GetString("REDIS_DB", "0");
|
||||
|
||||
var connectionString = $"{host}:{port}";
|
||||
if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
connectionString += $",password={password}";
|
||||
}
|
||||
connectionString += $",defaultDatabase={database}";
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
private static string GetRabbitMQConnectionString()
|
||||
{
|
||||
var host = GetString("RABBITMQ_HOST", "localhost");
|
||||
var port = GetString("RABBITMQ_PORT", "5672");
|
||||
var user = GetString("RABBITMQ_USER", "guest");
|
||||
var password = GetString("RABBITMQ_PASSWORD", "guest");
|
||||
|
||||
return $"amqp://{user}:{password}@{host}:{port}";
|
||||
}
|
||||
|
||||
private static string GetMySQLConnectionString()
|
||||
{
|
||||
var host = GetString("MYSQL_HOST", "localhost");
|
||||
var port = GetString("MYSQL_PORT", "3306");
|
||||
var user = GetString("MYSQL_USER", "root");
|
||||
var password = GetString("MYSQL_PASSWORD", "");
|
||||
var database = GetString("MYSQL_DATABASE", "data_0524");
|
||||
|
||||
return $"Server={host};Port={port};Database={database};User={user};Password={password};";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
public abstract class BaseController : Controller
|
||||
{
|
||||
public LoginUser loginUser
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetLoginUser();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual LoginUser GetLoginUser()
|
||||
{
|
||||
var claims = User?.Claims?.ToList();
|
||||
if (claims == null || claims.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
var userName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
var role = claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
return new LoginUser
|
||||
{
|
||||
userId = userId,
|
||||
userName = userName,
|
||||
role = role
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual string GetClientIp()
|
||||
{
|
||||
var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
|
||||
if (string.IsNullOrEmpty(ip))
|
||||
{
|
||||
ip = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
}
|
||||
return ip ?? "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginUser
|
||||
{
|
||||
public string userId { get; set; }
|
||||
|
||||
public string userName { get; set; }
|
||||
|
||||
public string role { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Extensions
|
||||
{
|
||||
public static class ControllerExtensions
|
||||
{
|
||||
public static BaseResponse<T> ToFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new BaseResponse<T>() { Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static ListResponse<T> ToListFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new ListResponse<T>()
|
||||
{ Info = info, Code = CommonResponseCodeEnum.ResultFail };
|
||||
|
||||
public static PageResponse<T> ToPageFailResult<T>(
|
||||
this Controller controller,
|
||||
string info) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.ResultFail,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToResult<T>(
|
||||
this Controller controller,
|
||||
T obj, string info = null) => new BaseResponse<T>()
|
||||
{
|
||||
Data = obj,
|
||||
Info = info,
|
||||
Code = CommonResponseCodeEnum.Success
|
||||
};
|
||||
|
||||
public static ListResponse<T> ToListResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, string info = null) => new ListResponse<T>()
|
||||
{
|
||||
Data = list,
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info
|
||||
};
|
||||
|
||||
public static PageResponse<T> ToPageResult<T>(
|
||||
this Controller controller,
|
||||
IEnumerable<T> list, int count = 0, string info = null) => new PageResponse<T>()
|
||||
{
|
||||
Code = CommonResponseCodeEnum.Success,
|
||||
Info = info,
|
||||
Count = count,
|
||||
Data = list
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToNoLoginResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>()
|
||||
{
|
||||
Info = "未登录",
|
||||
Code = CommonResponseCodeEnum.NoLogin
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToTokenInvalidResult<T>(
|
||||
this Controller controller) => new BaseResponse<T>()
|
||||
{
|
||||
Info = "token失效",
|
||||
Code = CommonResponseCodeEnum.TokenInvalid
|
||||
};
|
||||
|
||||
public static BaseResponse<T> ToParamErrorResult<T>(
|
||||
this Controller controller,
|
||||
string info = "请求参数错误") => new BaseResponse<T>()
|
||||
{
|
||||
Info = info,
|
||||
Code = CommonResponseCodeEnum.ParamError
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Middleware
|
||||
{
|
||||
public class GlobalExceptionMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<GlobalExceptionMiddleware> _logger;
|
||||
|
||||
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleExceptionAsync(HttpContext context, Exception ex)
|
||||
{
|
||||
if (ex is Common.BusinessException bizEx)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Error, bizEx.Message);
|
||||
}
|
||||
|
||||
if (ex is UnauthorizedAccessException)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.NoAuth, "未授权访问");
|
||||
}
|
||||
|
||||
if (ex is ArgumentException argEx)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.ParamError, argEx.Message);
|
||||
}
|
||||
|
||||
if (ex is TimeoutException || ex is OperationCanceledException)
|
||||
{
|
||||
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Error, "操作超时,请稍后重试: " + ex.Message);
|
||||
}
|
||||
|
||||
_logger.LogError(ex, "系统异常: {Message}", ex.Message);
|
||||
return WriteJsonResponse(context, Common.CommonResponseCodeEnum.Exception, ex.Message);
|
||||
}
|
||||
|
||||
private static Task WriteJsonResponse(HttpContext context, Common.CommonResponseCodeEnum code, string message)
|
||||
{
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.ContentType = "application/json";
|
||||
return context.Response.WriteAsync(JsonConvert.SerializeObject(new
|
||||
{
|
||||
code = code,
|
||||
info = message,
|
||||
data = (object?)null
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GlobalExceptionMiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SharpAbp.Infrastructure.Api.Resilience
|
||||
{
|
||||
public enum CircuitState
|
||||
{
|
||||
Closed,
|
||||
|
||||
Open,
|
||||
|
||||
HalfOpen
|
||||
}
|
||||
|
||||
public class CircuitBreakerOptions
|
||||
{
|
||||
public int FailureThreshold { get; set; } = 5;
|
||||
|
||||
public int BreakDurationSeconds { get; set; } = 30;
|
||||
|
||||
public int HalfOpenAttempts { get; set; } = 3;
|
||||
|
||||
public int SuccessThreshold { get; set; } = 2;
|
||||
}
|
||||
|
||||
public class CircuitBreaker
|
||||
{
|
||||
private readonly CircuitBreakerOptions _options;
|
||||
private readonly ILogger _logger;
|
||||
private int _failureCount;
|
||||
private int _successCount;
|
||||
private int _halfOpenAttempts;
|
||||
private CircuitState _state = CircuitState.Closed;
|
||||
private DateTime _lastFailureTime;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public CircuitBreaker(string name, CircuitBreakerOptions options, ILogger logger)
|
||||
{
|
||||
Name = name;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public CircuitState State => _state;
|
||||
|
||||
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
|
||||
{
|
||||
if (!CanExecute())
|
||||
{
|
||||
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await action();
|
||||
OnSuccess();
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnFailure(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(Func<Task> action)
|
||||
{
|
||||
if (!CanExecute())
|
||||
{
|
||||
throw new CircuitBreakerOpenException($"Circuit breaker '{Name}' is open");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await action();
|
||||
OnSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnFailure(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanExecute()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
return true;
|
||||
|
||||
case CircuitState.Open:
|
||||
var elapsed = (DateTime.UtcNow - _lastFailureTime).TotalSeconds;
|
||||
if (elapsed >= _options.BreakDurationSeconds)
|
||||
{
|
||||
_state = CircuitState.HalfOpen;
|
||||
_halfOpenAttempts = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
return _halfOpenAttempts < _options.HalfOpenAttempts;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSuccess()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
_failureCount = 0;
|
||||
break;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
_successCount++;
|
||||
_halfOpenAttempts++;
|
||||
if (_successCount >= _options.SuccessThreshold)
|
||||
{
|
||||
_state = CircuitState.Closed;
|
||||
_failureCount = 0;
|
||||
_successCount = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFailure(Exception ex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_failureCount++;
|
||||
_lastFailureTime = DateTime.UtcNow;
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
case CircuitState.Closed:
|
||||
if (_failureCount >= _options.FailureThreshold)
|
||||
{
|
||||
_state = CircuitState.Open;
|
||||
}
|
||||
break;
|
||||
|
||||
case CircuitState.HalfOpen:
|
||||
_state = CircuitState.Open;
|
||||
_halfOpenAttempts = _options.HalfOpenAttempts;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CircuitBreakerOpenException : Exception
|
||||
{
|
||||
public CircuitBreakerOpenException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
public class CircuitBreakerFactory
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CircuitBreaker> _breakers = new();
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public CircuitBreakerFactory(ILoggerFactory loggerFactory)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
public CircuitBreaker GetOrCreate(string name, CircuitBreakerOptions options = null)
|
||||
{
|
||||
return _breakers.GetOrAdd(name, n =>
|
||||
{
|
||||
var opts = options ?? new CircuitBreakerOptions();
|
||||
var logger = _loggerFactory.CreateLogger<CircuitBreaker>();
|
||||
return new CircuitBreaker(n, opts, logger);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>SharpAbp.Infrastructure.Api</RootNamespace>
|
||||
<AssemblyName>SharpAbp.Infrastructure.Api</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Autofac" Version="8.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\CommonService\CommonService.csproj" />
|
||||
<ProjectReference Include="..\..\RedisService\RedisService.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user