sunpengfei
2025-09-02 44c6adb7620d3e7ddec49a59035f094499113a6a
FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
@@ -1,8 +1,13 @@
using Furion;
using Consul;
using Furion;
using Furion.DatabaseAccessor;
using Furion.DataEncryption;
using Furion.DistributedIDGenerator;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using Furion.HttpRemote;
using Furion.Logging;
using Furion.Logging.Extensions;
using Mapster;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@@ -10,16 +15,22 @@
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
{
@@ -36,15 +47,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)
            {
@@ -53,27 +64,24 @@
                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.ControllerName = resourceController.ControllerName;
                    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.ServiceName = resourceService.ServiceName;
                    model.RouteArea = resourceService.RouteArea;
                    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
@@ -84,7 +92,17 @@
                        : new List<string> { "Delete", "Remove ", "Clear" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase))
                        ? EnumResourceMethod.Delete
                        : EnumResourceMethod.Post;
                    model.Name = requestXmlDoc?.Summary;
                    model.FileUpload = resourceAttribute.FileUpload;
                    if (model.FileUpload)
                    {
                        model.IsFromForm = true;
                    }
                    else
                    {
                        model.IsFromForm = resourceAttribute.IsFromForm;
                    }
                    model.Code = requestXmlDoc?.Name;
                    model.Name = $"{model.ControllerSummary}-{model.ActionSummary}";
                    model.AllowAnonymous = resourceAttribute.AllowAnonymous;
                    model.RequestTypeName = request.Name;
                    model.RequestTypeFullName = request.FullName;
@@ -95,38 +113,83 @@
                        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);
                    await App.GetRequiredService<IDistributedCache>().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson());
                }
            }
            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
            var expiredResources = resources.Where(it => !models.Any(m => m.Route == it.Route && m.Method == it.Method)).ToList();
            foreach (var expiredResource in expiredResources)
            {
                resources.Remove(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>();
            var controllers = resources
                .GroupBy(it => new
                {
                    it.Controller,
                    it.ControllerSummary,
                    it.ApplicationName,
                    it.ControllerName,
                    it.ControllerSummary,
                    it.RouteArea
                })
                .Select(it => new
@@ -149,21 +212,24 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace {controller.Key.ApplicationName}
namespace {controller.Key.ApplicationName}.{controller.Key.ControllerName}
{{
    /// <summary>
    /// {controller.Key.ControllerSummary}
    /// </summary>
    [Route(""api/{controller.Key.RouteArea}/[controller]"")]
    public class {controller.Key.Controller}AppService(IMediator mediator) : IDynamicApiController
    public class {controller.Key.ControllerName}AppService(IMediator mediator) : IDynamicApiController
    {{
        private readonly IMediator mediator = mediator;";
                foreach (var action in controller.Actions)
                {
                    code += $@"
                    var result = action.ResponseTypeName.IsNull() ? "Task" : $"Task<{action.ResponseTypeName}>";
                    code += @$"
        /// <summary>
        /// {action.Name}
        /// {action.ActionSummary}
        /// </summary>
        /// <param name=""request""></param>
        /// <returns></returns>";
@@ -173,22 +239,56 @@
        [AllowAnonymous]";
                    }
                    code += $@"
        [Http{action.Method}]
        public Task<{action.ResponseTypeName}> {action.ActionName}({action.RequestTypeName} request)
        [Http{action.Method}]";
                    if (action.FileUpload)
                    {
                        code += @"
        [Consumes(""multipart/form-data"")]";
                    }
                    code += @$"
        public {result} {action.ActionName}(";
                    if (action.FileUpload || action.IsFromForm)
                    {
                        code += "[FromForm] ";
                    }
                    code += $@"{action.RequestTypeName} request)
        {{
            return mediator.Send(request);
        }}
";
        }}";
                }
                code += $@"
    }}
}}
                code += @"
    }
}
";
                var dynamicAssembly = App.CompileCSharpClassCode(code);
                provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
                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;
                }
            }
            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>
@@ -196,7 +296,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<>))