This commit is contained in:
2026-03-25 14:55:34 +08:00
commit 2c44b3a4b2
131 changed files with 7453 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using StopShopping.Services.Extensions;
namespace StopShopping.Api.Middlewares;
public class DevelopmentCookieMiddleware
{
public DevelopmentCookieMiddleware(
RequestDelegate requestDelegate,
IOptions<AppOptions> options)
{
_next = requestDelegate;
_appOptions = options.Value;
}
private readonly RequestDelegate _next;
private readonly AppOptions _appOptions;
public async Task InvokeAsync(HttpContext httpContext)
{
int? port = null;
var origin = httpContext.Request.Headers[HeaderNames.Origin];
if (origin.Count > 0 && null != origin[0])
{
Uri uri = new(origin[0]!);
port = uri.Port;
}
else
{
var referer = httpContext.Request.Headers[HeaderNames.Referer];
if (referer.Count > 0 && null != referer[0])
{
Uri uri = new(referer[0]!);
port = uri.Port;
}
}
if (port.HasValue)
{
var modified = ModifyCookie(httpContext.Request.Headers[HeaderNames.Cookie], port.Value);
httpContext.Request.Headers[HeaderNames.Cookie] = modified;
}
httpContext.Response.OnStarting(() =>
{
if (port.HasValue)
{
var cookieHeader = httpContext.Response.Headers[HeaderNames.SetCookie];
ModifyResponseCookie(cookieHeader, httpContext, port.Value);
}
return Task.CompletedTask;
});
await _next(httpContext);
}
private void ModifyResponseCookie(StringValues cookieHeader, HttpContext httpContext, int port)
{
foreach (var cookieItem in cookieHeader)
{
if (cookieItem != null)
{
var cookies = cookieItem.Split(';');
foreach (var item in cookies)
{
if (null != item)
{
var pairs = item.Split('=');
if (2 == pairs.Length)
{
if (pairs[0].Trim() == _appOptions.CSRFCookieName)
{
var val = pairs[1];
httpContext.Response.Cookies.Delete(pairs[0]);
httpContext.Response.Cookies.Append($"{_appOptions.CSRFCookieName}_{port}", val);
}
else if (pairs[0].Trim() == HttpExtensions.REFRESH_TOKEN_COOKIE_KEY)
{
var val = pairs[1];
httpContext.Response.Cookies.Delete(pairs[0]);
httpContext.Response.Cookies.Append($"{HttpExtensions.REFRESH_TOKEN_COOKIE_KEY}_{port}", val);
}
}
}
}
}
}
}
private StringValues ModifyCookie(StringValues cookie, int port)
{
List<string> result = [];
var csrfCookieName = $"{_appOptions.CSRFCookieName}_{port}";
var refreshTokenCookieName = $"{HttpExtensions.REFRESH_TOKEN_COOKIE_KEY}_{port}";
foreach (var cookieItem in cookie)
{
if (null != cookieItem)
{
StringBuilder itemBuilder = new();
var cookies = cookieItem.Split(';');
foreach (var item in cookies)
{
if (null != item)
{
var pairs = item.Split('=');
if (pairs.Length == 2)
{
if (0 != itemBuilder.Length)
itemBuilder.Append(';');
if (pairs[0].Trim() == csrfCookieName)
{
itemBuilder.Append(_appOptions.CSRFCookieName);
itemBuilder.Append('=');
itemBuilder.Append(pairs[1].Trim());
}
else if (pairs[0].Trim() == refreshTokenCookieName)
{
itemBuilder.Append(HttpExtensions.REFRESH_TOKEN_COOKIE_KEY);
itemBuilder.Append('=');
itemBuilder.Append(pairs[1].Trim());
}
else
{
itemBuilder.Append(item);
}
}
else
{
itemBuilder.Append(item);
}
}
}
result.Add(itemBuilder.ToString());
}
}
return new StringValues(result.ToArray());
}
}

View File

@@ -0,0 +1,100 @@
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using StopShopping.Services.Models.Resp;
namespace StopShopping.Api.Middlewares;
public class GlobalExceptionHandlerMiddleware
{
public GlobalExceptionHandlerMiddleware(RequestDelegate requestDelegate,
ILogger<GlobalExceptionHandlerMiddleware> logger,
IProblemDetailsService problemDetailsService)
{
_next = requestDelegate;
_logger = logger;
_problemDetailsService = problemDetailsService;
}
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionHandlerMiddleware> _logger;
private readonly IProblemDetailsService _problemDetailsService;
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (BadHttpRequestException ex) when (ex.InnerException is AntiforgeryValidationException)
{
var problemDetails = new ProblemDetails
{
Detail = ex.InnerException.Message,
Instance = httpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Title = "CSRF 错误",
};
problemDetails.AddErrorCode(ProblemDetailsCodes.CsrfValidationFailed);
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
httpContext.Response.ContentType = "application/problem+json";
await _problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = httpContext,
ProblemDetails = problemDetails
});
}
catch (BadHttpRequestException ex)
{
_logger.LogError(ex, "参数错误");
var problemDetails = new ProblemDetails
{
Detail = ex.Message,
Instance = httpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Title = "参数错误",
};
problemDetails.AddErrorCode(ProblemDetailsCodes.BadParameters);
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
httpContext.Response.ContentType = "application/problem+json";
await _problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = httpContext,
ProblemDetails = problemDetails
});
}
catch (ServiceException ex)//业务层抛出提示
{
await httpContext.Response.WriteAsJsonAsync(ApiResponse.Failed(ex.Message));
}
catch (Exception ex)
{
_logger.LogCritical(ex, "意外的异常");
var problemDetails = new ProblemDetails
{
Detail = ex.Message,
Instance = httpContext.Request.Path,
Status = StatusCodes.Status500InternalServerError,
Title = "服务器错误",
};
problemDetails.AddErrorCode(ProblemDetailsCodes.ServerError);
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
httpContext.Response.ContentType = "application/problem+json";
await _problemDetailsService.WriteAsync(new ProblemDetailsContext
{
HttpContext = httpContext,
ProblemDetails = problemDetails,
});
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc;
namespace StopShopping.Api.Middlewares;
public static class ProblemDetailsExtensions
{
private const string CODE_FIELD = "code";
public static ProblemDetails AddErrorCode(this ProblemDetails problemDetails, ProblemDetailsCodes code)
{
problemDetails.Extensions ??= new Dictionary<string, object?>();
problemDetails.Extensions.Add(CODE_FIELD, (int)code);
return problemDetails;
}
}
public enum ProblemDetailsCodes
{
CsrfValidationFailed = 1000,
ParametersValidationFailed = 1001,
BadParameters = 1002,
ServerError = 1003,
}