修改User模块,同步原始代码

This commit is contained in:
chfei committed 2026-10-23 15:42:37 +08:00
1 parent 4a5fada435
commit 78b0c2bb2e
47 files changed
+2939 -193

No files matched your search

@@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace MAX.Framework.Conventions;
/// <summary>为 API 控制器的属性路由统一添加 api 前缀,已含前缀的路由保持不变。</summary>
public sealed class ApiRoutePrefixConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
if (!controller.Attributes.OfType<IApiBehaviorMetadata>().Any()
&& !controller.ControllerType.Assembly.IsDefined(typeof(ApiControllerAttribute), true))
{
continue;
}
var hasControllerRoute = controller.Selectors.Any(selector => selector.AttributeRouteModel != null);
foreach (var selector in controller.Selectors)
{
if (selector.AttributeRouteModel is { } route)
{
AddPrefix(route);
}
}
foreach (var action in controller.Actions)
foreach (var selector in action.Selectors)
{
// 绝对动作路由会覆盖控制器路由,因此需要单独添加前缀。
if (selector.AttributeRouteModel is { } route
&& (!hasControllerRoute || route.IsAbsoluteTemplate))
{
AddPrefix(route);
}
}
}
}
private static void AddPrefix(AttributeRouteModel route)
{
var template = route.Template ?? "";
var absolute = route.IsAbsoluteTemplate;
template = template.StartsWith("~/", StringComparison.Ordinal) ? template[2..] : template.TrimStart('/');
if (!template.Equals("api", StringComparison.OrdinalIgnoreCase)
&& !template.StartsWith("api/", StringComparison.OrdinalIgnoreCase))
{
template = string.IsNullOrEmpty(template) ? "api" : $"api/{template}";
}
route.Template = absolute ? $"~/{template}" : template;
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Routing;
namespace MAX.Framework.Conventions;
/// <summary>
/// 控制器内未声明 HTTP 方法的操作默认只接受 GET,供各模块复用。
/// 显式 HTTP 方法声明优先;同时补充路由约束和端点元数据,保证接口文档一致。
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public sealed class DefaultHttpGetAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
foreach (var action in controller.Actions)
foreach (var selector in action.Selectors)
{
if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any()
|| selector.EndpointMetadata.OfType<IHttpMethodMetadata>().Any())
continue;
selector.ActionConstraints.Add(new HttpMethodActionConstraint([HttpMethods.Get]));
selector.EndpointMetadata.Add(new HttpMethodMetadata([HttpMethods.Get]));
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Routing;
namespace MAX.Framework.Conventions;
/// <summary>
/// 控制器内未声明 HTTP 方法的操作默认只接受 POST。
/// 同时补充动作约束和端点元数据,使路由匹配与 OpenAPI 文档保持一致;显式方法声明优先。
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public sealed class DefaultHttpPostAttribute : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
foreach (var action in controller.Actions)
foreach (var selector in action.Selectors)
{
if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any()
|| selector.EndpointMetadata.OfType<IHttpMethodMetadata>().Any())
continue;
selector.ActionConstraints.Add(new HttpMethodActionConstraint([HttpMethods.Post]));
selector.EndpointMetadata.Add(new HttpMethodMetadata([HttpMethods.Post]));
}
}
}
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace MAX.Framework.Validation;
/// <summary>通用请求校验入口,供各模块复用,不依赖 HTTP、数据库或具体业务。</summary>
public static class RequestValidator
{
/// <summary>
/// 检查对象非空,并验证全部属性上的数据注解及 IValidatableObject 规则。
/// 不递归验证子对象;对象为空抛出 ArgumentNullException,规则不满足抛出 ValidationException。
/// </summary>
public static void Validate(object? request)
{
ArgumentNullException.ThrowIfNull(request);
Validator.ValidateObject(request, new ValidationContext(request), validateAllProperties: true);
}
/// <summary>不抛出规则验证异常,适用于登录等需要自行处理校验结果的入口;对象为空仍视为调用错误。</summary>
public static bool TryValidate(object? request, out IReadOnlyList<ValidationResult> errors)
{
ArgumentNullException.ThrowIfNull(request);
var results = new List<ValidationResult>();
bool valid = Validator.TryValidateObject(request, new ValidationContext(request), results, validateAllProperties: true);
errors = results.AsReadOnly();
return valid;
}
}