feat: 初始化后台管理模块与基础业务框架
1. 新增依赖注入生命周期标记接口、基础仓储与管理员仓储实现 2. 新增管理员认证与用户服务接口,补充认证相关DTO 3. 重构实体审计字段命名,统一Created/UpdatedAt规范 4. 新增大量业务实体类与API版本枚举配置 5. 集成Autofac依赖注入、JWT自动刷新与跨域配置 6. 替换原有微信小程序与旧认证服务为后台管理系统架构 7. 完善Swagger文档配置与项目基础部署配置
This commit is contained in:
@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨域扩展
|
||||
/// </summary>
|
||||
public static class CorsExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 跨域配置
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configuration"></param>
|
||||
public static void AddCorsRegister(this WebApplicationBuilder builder)
|
||||
{
|
||||
bool isCorsAll = builder.Configuration["corsUrls"] == "*";
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("defaultCors", policy =>
|
||||
{
|
||||
if (isCorsAll)
|
||||
{
|
||||
policy.SetIsOriginAllowed(origin => true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var corsUrls = builder.Configuration.GetSection("corsUrls").Get<string[]>();
|
||||
policy.WithOrigins(corsUrls ?? Array.Empty<string>());
|
||||
}
|
||||
policy.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials()
|
||||
.WithExposedHeaders("X-New-Token");
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Cache;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
|
||||
public class JwtAutoRefreshMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IConfiguration _configuration;
|
||||
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||
private const int RefreshThresholdMinutes = 10;
|
||||
|
||||
public JwtAutoRefreshMiddleware(RequestDelegate next, IConfiguration configuration)
|
||||
{
|
||||
_next = next;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
var token = authHeader.Substring("Bearer ".Length).Trim();
|
||||
await TryAutoRefreshTokenAsync(context, token);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private async Task TryAutoRefreshTokenAsync(HttpContext context, string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var expiryTime = jwtToken.ValidTo;
|
||||
var remainingTime = expiryTime - DateTime.Now;
|
||||
|
||||
if (remainingTime <= TimeSpan.FromMinutes(RefreshThresholdMinutes) && remainingTime > TimeSpan.Zero)
|
||||
{
|
||||
var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
var userName = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(userName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
||||
if (jwtSettings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newToken = QYZH.InteractiveMagazine.Infrastructure.Auth.JwtHelper.GenerateToken(
|
||||
long.Parse(userId), userName, jwtSettings);
|
||||
|
||||
await RedisHelper.StringSetAsync(
|
||||
$"{TokenKeyPrefix}:{userId}",
|
||||
newToken,
|
||||
TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
|
||||
context.Response.Headers["X-New-Token"] = newToken;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略自动刷新异常,由后续认证中间件处理
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user