From 356c47fb0ea1a642784e85c82833141d065bc4bc Mon Sep 17 00:00:00 2001 From: sunpengfei <i@angelzzz.com> Date: 星期二, 05 八月 2025 09:22:25 +0800 Subject: [PATCH] feat:动态控制器开发 --- FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs | 251 +++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 209 insertions(+), 42 deletions(-) diff --git a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs index c31228c..fb77aa3 100644 --- a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs +++ b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs @@ -1,7 +1,11 @@ -锘縰sing Furion; +锘縰sing FlexJobApi.User.Application; +using Furion; using Furion.DatabaseAccessor; using Furion.DistributedIDGenerator; +using Furion.DynamicApiController; +using Furion.FriendlyException; using Mapster; +using MediatR; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; @@ -10,7 +14,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -22,63 +28,224 @@ public static class ResourceUtils { /// <summary> - /// 鐢熸垚璧勬簮 + /// 鐢熸垚鍔ㄦ�佹帶鍒跺櫒 /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - public static async Task BuildWebApis(CancellationToken cancellationToken = default) + public static async Task BuildDynamicControllersAsync() { var traceId = App.GetTraceId() ?? IDGen.NextID().ToString(); var scopeFactory = App.GetService<IServiceScopeFactory>(); var serviceScope = scopeFactory.CreateScope(); var rep = serviceScope.ServiceProvider.GetRequiredService<IRepository<Resource>>(); - var endpointDataSource = App.GetService<EndpointDataSource>(); - var routeEndpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>(); + var provider = serviceScope.ServiceProvider.GetRequiredService<IDynamicApiRuntimeChangeProvider>(); var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); - var enumWebApiMethods = await EnumUtils.GetModel<EnumWebApiMethod>(); - var webApis = await rep.AsQueryable().ToListAsync(); - foreach (var routeEndpoint in routeEndpoints) + var enumWebApiMethods = await EnumUtils.GetModel<EnumResourceMethod>(); + var resourceControllers = await EnumUtils.GetModel<EnumResourceController>(); + var requests = App.Assemblies.SelectMany(it => it.GetTypes()).Where(it => typeof(IBaseRequest).IsAssignableFrom(it)).ToList(); + var resources = await rep.AsQueryable() + .Where(it => !it.IsExpired) + .ToListAsync(); + var models = new List<ResourceModel>(); + foreach (var request in requests) { - var model = new ResourceModel(); - model.TraceId = traceId; - model.Route = "/" + routeEndpoint.RoutePattern.RawText; - var controllerActionDescriptor = routeEndpoint.Metadata.GetMetadata<ControllerActionDescriptor>(); - var methodInfo = controllerActionDescriptor.MethodInfo; - var methodInfoXmlDoc = await controllerActionDescriptor.MethodInfo.GetXmlDocMemberAsync(xmlDoc); - var controllerTypeXmlDoc = await methodInfo.DeclaringType?.GetXmlDocMemberAsync(); - model.Code = methodInfoXmlDoc.Name; - model.Service = methodInfo.Module.Name.Split(".")[1]; - model.Method = enumWebApiMethods.GetEnum((controllerActionDescriptor.ActionConstraints[0] as HttpMethodActionConstraint).HttpMethods.FirstOrDefault()); - model.Name = $"{controllerTypeXmlDoc?.Summary ?? "娌″啓娉ㄩ噴"}-{methodInfoXmlDoc?.Summary ?? "娌″啓娉ㄩ噴"}"; - model.RequestTypeName = methodInfo.GetParameters().FirstOrDefault().ParameterType.FullName; - var returnType = methodInfo.ReturnType; - if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>)) - returnType = returnType.GetGenericArguments()[0]; - if (returnType != null && returnType != typeof(Task)) - model.ResponseTypeName = methodInfo.ReturnType.FullName; + var resourceAttribute = request.GetCustomAttribute<ResourceAttribute>(); + if (resourceAttribute == null) throw Oops.Oh(EnumErrorCodeType.s404, $"璇风粰璧勬簮{request.Name}鍒嗛厤鏈嶅姟鐗规�esource"); - var webApi = webApis.FirstOrDefault(it => it.Route == model.Route && it.Method == model.Method); - if (webApi == null) + foreach (var controller in resourceAttribute.Controllers) { - webApi = new Resource(); - model.Adapt(webApi); - await rep.InsertAsync(webApi); - webApis.Add(webApi); - } - else - { - model.Adapt(webApi); - await rep.UpdateAsync(webApi); + var resourceController = controller.GetType().GetMember(controller.ToString())[0].GetCustomAttribute<ResourceControllerAttribute>(); + var resourceService = resourceController.Service.GetType().GetMember(resourceController.Service.ToString())[0].GetCustomAttribute<ResourceServiceAttribute>(); + + var model = new ResourceModel(); + model.TraceId = traceId; + model.ApplicationName = resourceService.ApplicationName; + model.RouteArea = resourceService.RouteArea; + var name = request.Name; + name = Regex.Replace(name, @"(Command|Query)$", "", RegexOptions.None); + model.ActionName = name; + model.Route = $"/api/{resourceService.RouteArea ?? "main"}/{controller}/{name}"; + var requestXmlDoc = await request.GetXmlDocMemberAsync(xmlDoc); + model.Code = requestXmlDoc.Name; + model.Controller = controller; + model.ControllerSummary = resourceControllers.GetDescription(controller); + if (controller == EnumResourceController.Role) + { + Console.WriteLine(); + } + model.Method = + request.BaseType?.IsGenericType == true && request.BaseType.GetGenericTypeDefinition() == typeof(PagedListQuery<,>) + ? EnumResourceMethod.Post + : new List<string> { "Post", "Add", "Create", "Insert", "Submit" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) + ? EnumResourceMethod.Post + : new List<string> { "GetAll", "GetList", "Get", "Find", "Fetch", "Query" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) + ? EnumResourceMethod.Get + : new List<string> { "Put", "Update ", "Set" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) + ? EnumResourceMethod.Put + : new List<string> { "Delete", "Remove ", "Clear" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) + ? EnumResourceMethod.Delete + : EnumResourceMethod.Post; + model.Name = requestXmlDoc?.Summary; + model.AllowAnonymous = resourceAttribute.AllowAnonymous; + model.RequestTypeName = request.Name; + model.RequestTypeFullName = request.FullName; + var iRequestType = request.GetInterface("IRequest`1"); + if (iRequestType != null && iRequestType.IsGenericType) + { + var responseType = iRequestType.GenericTypeArguments[0]; + model.ResponseTypeName = responseType.GetCSharpFriendlyName(); + model.ResponseTypeFullName = responseType.FullName; + } + + var resource = resources.FirstOrDefault(it => it.Route == model.Route && it.Method == model.Method); + if (resource == null) + { + resource = new Resource(); + model.Adapt(resource); + await rep.InsertAsync(resource); + resources.Add(resource); + } + else + { + model.Adapt(resource); + await rep.UpdateAsync(resource); + } + + models.Add(model); } } - var expiredWebApis = webApis.Where(it => it.TraceId != traceId).ToList(); - foreach (var webApi in expiredWebApis) + + var expiredResources = resources.Where(it => it.TraceId != traceId).ToList(); + foreach (var expiredResource in expiredResources) { - webApi.IsExpired = true; - await rep.UpdateAsync(webApi); + expiredResource.IsExpired = true; + await rep.UpdateAsync(expiredResource); + } + + var controllers = models + .GroupBy(it => new + { + it.Controller, + it.ControllerSummary, + it.ApplicationName, + it.RouteArea + }) + .Select(it => new + { + it.Key, + Actions = it.ToList() + }) + .ToList(); + foreach (var controller in controllers) + { + var code = $@" +using FlexJobApi.Core; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace {controller.Key.ApplicationName} +{{ + /// <summary> + /// {controller.Key.ControllerSummary} + /// </summary> + [Route(""api/{controller.Key.RouteArea}/[controller]"")] + public class {controller.Key.Controller}AppService(IMediator mediator) : IDynamicApiController + {{ + private readonly IMediator mediator = mediator;"; + foreach (var action in controller.Actions) + { + code += $@" + + /// <summary> + /// {action.Name} + /// </summary> + /// <param name=""request""></param> + /// <returns></returns>"; + if (action.AllowAnonymous) + { + code += $@" + [AllowAnonymous]"; + } + code += $@" + [Http{action.Method}] + public Task<{action.ResponseTypeName}> {action.ActionName}({action.RequestTypeName} request) + {{ + return mediator.Send(request); + }} +"; + } + code += $@" + }} +}} +"; + var dynamicAssembly = App.CompileCSharpClassCode(code); + provider.AddAssembliesWithNotifyChanges(dynamicAssembly); } await rep.SaveNowAsync(); } + + /// <summary> + /// 鑾峰彇C#鍙嬪ソ鍚嶇О + /// </summary> + /// <param name="type"></param> + /// <returns></returns> + public static string GetCSharpFriendlyName(this Type type) + { + // 澶勭悊鍙┖绫诲瀷 + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + Type underlyingType = Nullable.GetUnderlyingType(type)!; + return $"{GetCSharpFriendlyName(underlyingType)}?"; + } + + // 澶勭悊鍩虹绫诲瀷 + if (type.FullName.IsNotNull()) + { + var baseTypes = new Dictionary<string, string> + { + { "System.Byte", "byte" }, + { "System.SByte", "sbyte" }, + { "System.Int16", "short" }, + { "System.UInt16", "ushort" }, + { "System.Int32", "int" }, + { "System.UInt32", "uint" }, + { "System.Int64", "long" }, + { "System.UInt64", "ulong" }, + { "System.Single", "float" }, + { "System.Double", "double" }, + { "System.Decimal", "decimal" }, + { "System.Char", "char" }, + { "System.Boolean", "bool" }, + { "System.String", "string" }, + { "System.Object", "object" } + }; + if (baseTypes.TryGetValue(type.FullName, out string? friendlyName) && friendlyName.IsNotNull()) + { + return friendlyName; + } + } + + // 澶勭悊闈炴硾鍨嬬被鍨� + if (!type.IsGenericType) + { + return type.Name; + } + + // 澶勭悊娉涘瀷绫诲瀷 + string genericTypeName = type.GetGenericTypeDefinition().Name; + if (genericTypeName.Contains('`')) + genericTypeName = genericTypeName.Substring(0, genericTypeName.IndexOf('`')); + + string[] genericArgs = type.GetGenericArguments() + .Select(GetCSharpFriendlyName) + .ToArray(); + + return $"{genericTypeName}<{string.Join(", ", genericArgs)}>"; + } } } -- Gitblit v1.9.1