sunpengfei
2025-08-21 1aeaabb680b44b8ef960c4471f2f2903cbfe4805
FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
@@ -6,6 +6,8 @@
using Furion.DynamicApiController;
using Furion.FriendlyException;
using Furion.HttpRemote;
using Furion.Logging;
using Furion.Logging.Extensions;
using Mapster;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@@ -13,6 +15,7 @@
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;
@@ -36,58 +39,6 @@
    /// </summary>
    public static class ResourceUtils
    {
        /// <summary>
        /// 发送HTTP请求
        /// </summary>
        /// <typeparam name="TRequest"></typeparam>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="request"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        public static async Task<TResponse> SendHttpAsync<TRequest, TResponse>(
            TRequest request,
            IResourceHttpProvider provider = null)
            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 builder = HttpRequestBuilder.Create(resource.GetHttpMethod(), $"{domain}{resource.Route}");
            if (resource.Method == EnumResourceMethod.Get)
                builder = builder.WithQueryParameters(request);
            else
                builder = builder.SetJsonContent(request);
            provider = provider ?? new DefaultResourceHttpProvider();
            builder = provider.AddAuthentication(builder);
            var response = await provider.SendAsAsync<TResponse>(httpRemoteService, builder);
            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>
@@ -120,16 +71,17 @@
                    var model = new ResourceModel();
                    model.TraceId = traceId;
                    model.ApplicationName = resourceService.ApplicationName;
                    model.Controller = controller;
                    model.ControllerName = resourceController.ControllerName;
                    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.ServiceName = resourceController.Service.ToString();
                    model.RouteArea = resourceService.RouteArea;
                    model.Route = $"/api/{resourceService.RouteArea ?? "main"}/{controller}/{model.ActionName}";
                    model.Route = $"/api/{resourceService.RouteArea ?? "main"}/{model.ControllerName}/{model.ActionName}";
                    model.Method =
                        request.BaseType?.IsGenericType == true && request.BaseType.GetGenericTypeDefinition() == typeof(PagedListQuery<,>)
                        resourceAttribute.Method != EnumResourceMethod.None
                        ? resourceAttribute.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
@@ -140,6 +92,7 @@
                        : new List<string> { "Delete", "Remove ", "Clear" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase))
                        ? EnumResourceMethod.Delete
                        : EnumResourceMethod.Post;
                    model.FileUpload = resourceAttribute.FileUpload;
                    model.Code = requestXmlDoc?.Name;
                    model.Name = $"{model.ControllerSummary}-{model.ActionSummary}";
                    model.AllowAnonymous = resourceAttribute.AllowAnonymous;
@@ -152,9 +105,9 @@
                        model.ResponseTypeName = responseType.GetCSharpFriendlyName();
                        model.ResponseTypeFullName = responseType.FullName;
                    }
                    models.Add(model);
                    await App.GetRequiredService<IDistributedCache>().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson());
                }
            }
@@ -208,8 +161,7 @@
            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);
                resources.Remove(expiredResource);
                await rep.DeleteAsync(expiredResource);
            }
@@ -224,7 +176,21 @@
        public static void DynamicControllersHotPlug(List<Resource> resources, IDynamicApiRuntimeChangeProvider provider = null)
        {
            provider = provider ?? App.GetRequiredService<IDynamicApiRuntimeChangeProvider>();
            foreach (var resource in resources)
            var controllers = resources
                .GroupBy(it => new
                {
                    it.ApplicationName,
                    it.ControllerName,
                    it.ControllerSummary,
                    it.RouteArea
                })
                .Select(it => new
                {
                    it.Key,
                    Actions = it.ToList()
                })
                .ToList();
            foreach (var controller in controllers)
            {
                var code = $@"
using FlexJobApi.Core;
@@ -238,40 +204,70 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace {resource.ApplicationName}.{resource.Controller}.{resource.ActionName}Request
namespace {controller.Key.ApplicationName}.{controller.Key.ControllerName}
{{
    /// <summary>
    /// {resource.ControllerSummary}
    /// {controller.Key.ControllerSummary}
    /// </summary>
    [Route(""api/{resource.RouteArea}/[controller]"")]
    public class {resource.Controller}AppService(IMediator mediator) : IDynamicApiController
    [Route(""api/{controller.Key.RouteArea}/[controller]"")]
    public class {controller.Key.ControllerName}AppService(IMediator mediator) : IDynamicApiController
    {{
        private readonly IMediator mediator = mediator;
        private readonly IMediator mediator = mediator;";
                foreach (var action in controller.Actions)
                {
                    var result = action.ResponseTypeName.IsNull() ? "Task" : $"Task<{action.ResponseTypeName}>";
                    code += @$"
        /// <summary>
        /// {resource.ActionSummary}
        /// {action.ActionSummary}
        /// </summary>
        /// <param name=""request""></param>
        /// <returns></returns>";
                if (resource.AllowAnonymous)
                {
                    code += $@"
                    if (action.AllowAnonymous)
                    {
                        code += $@"
        [AllowAnonymous]";
                }
                code += $@"
        [Http{resource.Method}]
        public Task<{resource.ResponseTypeName}> {resource.ActionName}({resource.RequestTypeName} request)
                    }
                    code += $@"
        [Http{action.Method}]";
                    if (action.FileUpload)
                    {
                        code += @"
        [Consumes(""multipart/form-data"")]";
                    }
                    code += @$"
        public {result} {action.ActionName}(";
                    if (action.FileUpload)
                    {
                        code += "[FromForm] ";
                    }
                    code += $@"{action.RequestTypeName} request)
        {{
            return mediator.Send(request);
        }}
        }}";
                }
    }}
}}
                code += @"
    }
}
";
                var dynamicAssembly = App.CompileCSharpClassCode(code);
                provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
                var dynamicAssemblyName = dynamicAssembly.GetName().Name;
                resource.DynamicAssemblyName = dynamicAssemblyName;
                try
                {
                    var dynamicAssembly = App.CompileCSharpClassCode(code);
                    provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
                    var dynamicAssemblyName = dynamicAssembly.GetName().Name;
                    foreach (var action in controller.Actions)
                    {
                        action.DynamicAssemblyName = dynamicAssemblyName;
                    }
                }
                catch (Exception ex)
                {
                    code.LogError(ex);
                    throw;
                }
            }
        }