From c8b6903d85e7ceef504a198b4bdfc6b72a73fe19 Mon Sep 17 00:00:00 2001 From: sunpengfei <i@angelzzz.com> Date: 星期五, 08 八月 2025 13:29:47 +0800 Subject: [PATCH] fix:bug --- FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs | 317 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 278 insertions(+), 39 deletions(-) diff --git a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs index c31228c..b9d51e0 100644 --- a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs +++ b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs @@ -1,18 +1,34 @@ -锘縰sing Furion; +锘縰sing Consul; +using Furion; using Furion.DatabaseAccessor; +using Furion.DataEncryption; using Furion.DistributedIDGenerator; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Furion.HttpRemote; using Mapster; +using MediatR; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Resources; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; namespace FlexJobApi.Core { @@ -22,63 +38,286 @@ public static class ResourceUtils { /// <summary> - /// 鐢熸垚璧勬簮 + /// 鍙戦�丠TTP璇锋眰 /// </summary> - /// <param name="cancellationToken"></param> + /// <typeparam name="TRequest"></typeparam> + /// <typeparam name="TResponse"></typeparam> + /// <param name="request"></param> + /// <param name="provider"></param> /// <returns></returns> - public static async Task BuildWebApis(CancellationToken cancellationToken = default) + public static Task<TResponse> SendHttpAsync<TRequest, TResponse>( + TRequest request, + IResourceHttpProvider provider = null) + where TRequest : class, new() + { + return App.GetRequiredService<ResourceHttpUtils>().SendHttpAsync<TRequest, TResponse>(request, provider); + } + + /// <summary> + /// 鐢熸垚鍔ㄦ�佹帶鍒跺櫒 + /// </summary> + public static async Task BuildDynamicControllersAsync() { var traceId = App.GetTraceId() ?? IDGen.NextID().ToString(); var scopeFactory = App.GetService<IServiceScopeFactory>(); var serviceScope = scopeFactory.CreateScope(); + var provider = serviceScope.ServiceProvider.GetRequiredService<IDynamicApiRuntimeChangeProvider>(); var rep = serviceScope.ServiceProvider.GetRequiredService<IRepository<Resource>>(); - var endpointDataSource = App.GetService<EndpointDataSource>(); - var routeEndpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>(); 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 => !it.IsAbstract && typeof(IBaseRequest).IsAssignableFrom(it)) + .ToList(); + 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); + var requestXmlDoc = await request.GetXmlDocMemberAsync(xmlDoc); + var resourceController = controller.GetCustomAttribute<EnumResourceController, ResourceControllerAttribute>(); + var resourceService = resourceController.Service.GetCustomAttribute<EnumResourceService, ResourceServiceAttribute>(); + + var model = new ResourceModel(); + model.TraceId = traceId; + model.ApplicationName = resourceService.ApplicationName; + model.Controller = controller; + model.ControllerSummary = resourceControllers.GetDescription(controller); + model.ActionName = Regex.Replace(request.Name, @"(Command|Query)$", "", RegexOptions.None); + model.ActionSummary = requestXmlDoc?.Summary; + model.Service = resourceController.Service; + model.ServiceName = resourceService.ServiceName; + model.RouteArea = resourceService.RouteArea; + model.Route = $"/api/{resourceService.RouteArea ?? "main"}/{controller}/{model.ActionName}"; + 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.Code = requestXmlDoc?.Name; + model.Name = $"{model.ControllerSummary}-{model.ActionSummary}"; + 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; + } + models.Add(model); + + await App.GetRequiredService<IDistributedCache>().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson()); + } + } + + var resources = await SaveResourcesAsync(models, rep); + + DynamicControllersHotPlug(resources, provider); + + await rep.SaveNowAsync(); + } + + /// <summary> + /// 淇濆瓨璧勬簮 + /// </summary> + /// <param name="models"></param> + /// <param name="traceId"></param> + /// <param name="rep"></param> + /// <returns></returns> + private static async Task<List<Resource>> SaveResourcesAsync(List<ResourceModel> models, IRepository<Resource> rep = null) + { + rep = rep ?? Db.GetRepository<Resource>(); + var resources = await rep.AsQueryable() + .Where(it => !it.IsExpired) + .ToListAsync(); + foreach (var model in models) + { + var resource = resources.FirstOrDefault(it => it.Route == model.Route && it.Method == model.Method); + if (resource == null) + { + resource = new Resource(); + resource.Id = IDGen.NextID(); + resource.CreatedTime = DateTimeOffset.Now; + model.Adapt(resource); + await rep.InsertAsync(resource); + resources.Add(resource); } else { - model.Adapt(webApi); - await rep.UpdateAsync(webApi); + var resourceBakModel = new ResourceModel(); + resource.Adapt(resourceBakModel); + resourceBakModel.TraceId = model.TraceId; + resourceBakModel.DynamicAssemblyName = model.DynamicAssemblyName; + if (resourceBakModel.ToJson() != model.ToJson()) + { + model.Adapt(resource); + resource.UpdatedTime = DateTimeOffset.Now; + await rep.UpdateAsync(resource); + } } } - var expiredWebApis = webApis.Where(it => it.TraceId != traceId).ToList(); - foreach (var webApi in expiredWebApis) + + var expiredResources = resources.Where(it => !models.Any(m => m.Route == it.Route && m.Method == it.Method)).ToList(); + foreach (var expiredResource in expiredResources) { - webApi.IsExpired = true; - await rep.UpdateAsync(webApi); + //expiredResource.IsExpired = true; + //await rep.UpdateAsync(expiredResource); + await rep.DeleteAsync(expiredResource); } - await rep.SaveNowAsync(); + return resources.Where(it => !it.IsExpired).ToList(); + } + + /// <summary> + /// 鍔ㄦ�佹帶鍒跺櫒鐑彃 + /// </summary> + /// <param name="resources"></param> + /// <param name="provider"></param> + public static void DynamicControllersHotPlug(List<Resource> resources, IDynamicApiRuntimeChangeProvider provider = null) + { + provider = provider ?? App.GetRequiredService<IDynamicApiRuntimeChangeProvider>(); + foreach (var resource in resources) + { + 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; +using System.ComponentModel; + +namespace {resource.ApplicationName}.{resource.Controller}.{resource.ActionName}Request +{{ + /// <summary> + /// {resource.ControllerSummary} + /// </summary> + [Route(""api/{resource.RouteArea}/[controller]"")] + public class {resource.Controller}AppService(IMediator mediator) : IDynamicApiController + {{ + private readonly IMediator mediator = mediator; + + /// <summary> + /// {resource.ActionSummary} + /// </summary> + /// <param name=""request""></param> + /// <returns></returns>"; + if (resource.AllowAnonymous) + { + code += $@" + [AllowAnonymous]"; + } + var result = resource.ResponseTypeName.IsNull() ? "Task" : $"Task<{resource.ResponseTypeName}>"; + code += $@" + [Http{resource.Method}] + public {result} {resource.ActionName}({resource.RequestTypeName} request) + {{ + return mediator.Send(request); + }} + + }} +}} +"; + try + { + var dynamicAssembly = App.CompileCSharpClassCode(code); + provider.AddAssembliesWithNotifyChanges(dynamicAssembly); + var dynamicAssemblyName = dynamicAssembly.GetName().Name; + resource.DynamicAssemblyName = dynamicAssemblyName; + } + catch (Exception ex) + { + Console.WriteLine(code); + throw; + } + } + + } + + /// <summary> + /// 鍔ㄦ�佹帶鍒跺櫒鐑嫈 + /// </summary> + /// <param name="resource"></param> + /// <param name="provider"></param> + public static void DynamicControllerHotPluck(Resource resource, IDynamicApiRuntimeChangeProvider provider = null) + { + provider = provider ?? App.GetRequiredService<IDynamicApiRuntimeChangeProvider>(); + provider.RemoveAssembliesWithNotifyChanges(resource.DynamicAssemblyName); + } + + /// <summary> + /// 鑾峰彇C#鍙嬪ソ鍚嶇О + /// </summary> + /// <param name="type"></param> + /// <returns></returns> + private 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