From 32288af3e5f12bc48d8360114c872fde5d9ff4a8 Mon Sep 17 00:00:00 2001 From: sunpengfei <i@angelzzz.com> Date: 星期五, 08 八月 2025 09:09:08 +0800 Subject: [PATCH] pref:优化 --- FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs | 270 +++++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 197 insertions(+), 73 deletions(-) diff --git a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs index 5f02b3f..9b51203 100644 --- a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs +++ b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs @@ -1,9 +1,11 @@ -锘縰sing FlexJobApi.User.Application; +锘縰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; @@ -15,12 +17,17 @@ 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 { @@ -30,6 +37,87 @@ public static class ResourceUtils { /// <summary> + /// 鍙戦�丠TTP璇锋眰 + /// </summary> + /// <typeparam name="TRequest"></typeparam> + /// <typeparam name="TResponse"></typeparam> + /// <param name="request"></param> + /// <returns></returns> + public static async Task<TResponse> SendHttpAsync<TRequest, TResponse>(TRequest request) + where TRequest : class, new() + { + var requestTypeFullName = typeof(TRequest).FullName; + var resource = await Db.GetRepository<Resource>().AsQueryable().AsNoTracking() + .Where(it => !it.IsExpired && it.RequestTypeFullName == requestTypeFullName) + .FirstOrDefaultAsync(); + var domain = await GetHealthyServiceDomain(resource); + var httpRemoteService = App.GetRequiredService<IHttpRemoteService>(); + var token = App.HttpContext.Request.Headers["Authorization"].ToString(); + var refreshToken = App.HttpContext.Request.Headers["X-Authorization"].ToString(); + TResponse response; + switch (resource.Method) + { + case EnumResourceMethod.Get: + response = await httpRemoteService.GetAsAsync<TResponse>( + $"{domain}{resource.Route}", + it => + it.WithQueryParameters(request) + .AddAuthentication(new AuthenticationHeaderValue("Authorization", token)) + .AddAuthentication(new AuthenticationHeaderValue("X-Authorization", refreshToken))); + break; + case EnumResourceMethod.Post: + response = await httpRemoteService.PostAsAsync<TResponse>( + $"{domain}{resource.Route}", + it => + it.SetJsonContent(request) + .WithHeader("Authorization", token) + .WithHeader("X-Authorization", refreshToken)); + break; + case EnumResourceMethod.Put: + response = await httpRemoteService.PutAsAsync<TResponse>( + $"{domain}{resource.Route}", + it => + it.SetJsonContent(request) + .AddAuthentication(new AuthenticationHeaderValue("Authorization", token)) + .AddAuthentication(new AuthenticationHeaderValue("X-Authorization", refreshToken))); + break; + case EnumResourceMethod.Delete: + response = await httpRemoteService.DeleteAsAsync<TResponse>( + $"{domain}{resource.Route}", + it => + it.SetJsonContent(request) + .AddAuthentication(new AuthenticationHeaderValue("Authorization", token)) + .AddAuthentication(new AuthenticationHeaderValue("X-Authorization", refreshToken))); + break; + default: + throw Oops.Oh(EnumErrorCodeType.s400, $"涓嶆敮鎸佽姹傛柟寮弡resource.Method}"); + } + return response; + } + + /// <summary> + /// 鑾峰彇鍋ュ悍鏈嶅姟鍩熷悕 + /// </summary> + /// <param name="resource"></param> + /// <returns></returns> + public static async Task<string> GetHealthyServiceDomain(Resource resource) + { + var client = App.GetRequiredService<IConsulClient>(); + var queryResult = await client.Health.Service(resource.ServiceName, null, true); + if (queryResult.StatusCode != System.Net.HttpStatusCode.OK) + throw Oops.Oh(EnumErrorCodeType.s404, $"寰湇鍔resource.Service}"); + + var domains = queryResult.Response + .Select(s => $"http://{s.Service.Address}:{s.Service.Port}") + .ToList(); + if (domains.IsNull()) + throw Oops.Oh(EnumErrorCodeType.s404, $"寰湇鍔resource.Service}"); + // 杞閫夋嫨瀹炰緥 + int randomIndex = new Random().Next(domains.Count); + return domains[randomIndex]; + } + + /// <summary> /// 鐢熸垚鍔ㄦ�佹帶鍒跺櫒 /// </summary> public static async Task BuildDynamicControllersAsync() @@ -37,15 +125,15 @@ var traceId = App.GetTraceId() ?? IDGen.NextID().ToString(); var scopeFactory = App.GetService<IServiceScopeFactory>(); var serviceScope = scopeFactory.CreateScope(); - var rep = serviceScope.ServiceProvider.GetRequiredService<IRepository<Resource>>(); var provider = serviceScope.ServiceProvider.GetRequiredService<IDynamicApiRuntimeChangeProvider>(); + var rep = serviceScope.ServiceProvider.GetRequiredService<IRepository<Resource>>(); var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); 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 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) { @@ -54,25 +142,21 @@ foreach (var controller in resourceAttribute.Controllers) { - var resourceController = controller.GetType().GetMember(controller.ToString())[0].GetCustomAttribute<ResourceControllerAttribute>(); - var resourceService = resourceController.Service.GetType().GetMember(resourceController.Service.ToString())[0].GetCustomAttribute<ResourceServiceAttribute>(); + 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.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.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 @@ -85,7 +169,8 @@ : new List<string> { "Delete", "Remove ", "Clear" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) ? EnumResourceMethod.Delete : EnumResourceMethod.Post; - model.Name = requestXmlDoc?.Summary; + model.Code = requestXmlDoc?.Name; + model.Name = $"{model.ControllerSummary}-{model.ActionSummary}"; model.AllowAnonymous = resourceAttribute.AllowAnonymous; model.RequestTypeName = request.Name; model.RequestTypeFullName = request.FullName; @@ -97,46 +182,78 @@ 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 expiredResources = resources.Where(it => it.TraceId != traceId).ToList(); - foreach (var expiredResource in expiredResources) + 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) { - expiredResource.IsExpired = true; - await rep.UpdateAsync(expiredResource); + 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 + { + 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 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 expiredResources = resources.Where(it => !models.Any(m => m.Route == it.Route && m.Method == it.Method)).ToList(); + foreach (var expiredResource in expiredResources) + { + //expiredResource.IsExpired = true; + //await rep.UpdateAsync(expiredResource); + await rep.DeleteAsync(expiredResource); + } + + 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; @@ -150,46 +267,53 @@ using System.Collections.Generic; using System.ComponentModel; -namespace {controller.Key.ApplicationName} +namespace {resource.ApplicationName}.{resource.Controller}.{resource.ActionName}Request {{ /// <summary> - /// {controller.Key.ControllerSummary} + /// {resource.ControllerSummary} /// </summary> - [Route(""api/{controller.Key.RouteArea}/[controller]"")] - public class {controller.Key.Controller}AppService(IMediator mediator) : IDynamicApiController + [Route(""api/{resource.RouteArea}/[controller]"")] + public class {resource.Controller}AppService(IMediator mediator) : IDynamicApiController {{ - private readonly IMediator mediator = mediator;"; - foreach (var action in controller.Actions) - { - code += $@" + private readonly IMediator mediator = mediator; /// <summary> - /// {action.Name} + /// {resource.ActionSummary} /// </summary> /// <param name=""request""></param> /// <returns></returns>"; - if (action.AllowAnonymous) - { - code += $@" - [AllowAnonymous]"; - } + if (resource.AllowAnonymous) + { code += $@" - [Http{action.Method}] - public Task<{action.ResponseTypeName}> {action.ActionName}({action.RequestTypeName} request) + [AllowAnonymous]"; + } + code += $@" + [Http{resource.Method}] + public Task<{resource.ResponseTypeName}> {resource.ActionName}({resource.RequestTypeName} request) {{ return mediator.Send(request); }} -"; - } - code += $@" + }} }} "; var dynamicAssembly = App.CompileCSharpClassCode(code); provider.AddAssembliesWithNotifyChanges(dynamicAssembly); + var dynamicAssemblyName = dynamicAssembly.GetName().Name; + resource.DynamicAssemblyName = dynamicAssemblyName; } - await rep.SaveNowAsync(); + } + + /// <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> @@ -197,7 +321,7 @@ /// </summary> /// <param name="type"></param> /// <returns></returns> - public static string GetCSharpFriendlyName(this Type type) + private static string GetCSharpFriendlyName(this Type type) { // 澶勭悊鍙┖绫诲瀷 if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) -- Gitblit v1.9.1