From 0de2dc108b9bb1b6ff3692741362a3da5cb7a068 Mon Sep 17 00:00:00 2001
From: sunpengfei <i@angelzzz.com>
Date: 星期四, 07 八月 2025 10:29:19 +0800
Subject: [PATCH] fix:bug

---
 FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs |  372 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 333 insertions(+), 39 deletions(-)

diff --git a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
index c31228c..9b51203 100644
--- a/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
+++ b/FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
@@ -1,7 +1,14 @@
-锘縰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;
@@ -9,10 +16,18 @@
 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 +37,342 @@
     public static class ResourceUtils
     {
         /// <summary>
-        /// 鐢熸垚璧勬簮
+        /// 鍙戦�丠TTP璇锋眰
         /// </summary>
-        /// <param name="cancellationToken"></param>
+        /// <typeparam name="TRequest"></typeparam>
+        /// <typeparam name="TResponse"></typeparam>
+        /// <param name="request"></param>
         /// <returns></returns>
-        public static async Task BuildWebApis(CancellationToken cancellationToken = default)
+        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()
         {
             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);
+                }
+            }
+
+            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]";
+                }
+                code += $@"
+        [Http{resource.Method}]
+        public Task<{resource.ResponseTypeName}> {resource.ActionName}({resource.RequestTypeName} request)
+        {{
+            return mediator.Send(request);
+        }}
+
+    }}
+}}
+";
+                var dynamicAssembly = App.CompileCSharpClassCode(code);
+                provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
+                var dynamicAssemblyName = dynamicAssembly.GetName().Name;
+                resource.DynamicAssemblyName = dynamicAssemblyName;
+            }
+
+        }
+
+        /// <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