sunpengfei
2025-08-05 356c47fb0ea1a642784e85c82833141d065bc4bc
FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
@@ -1,7 +1,11 @@
using Furion;
using FlexJobApi.User.Application;
using Furion;
using Furion.DatabaseAccessor;
using Furion.DistributedIDGenerator;
using Furion.DynamicApiController;
using Furion.FriendlyException;
using Mapster;
using MediatR;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
@@ -10,7 +14,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -22,63 +28,224 @@
    public static class ResourceUtils
    {
        /// <summary>
        /// 生成资源
        /// 生成动态控制器
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public static async Task BuildWebApis(CancellationToken cancellationToken = default)
        public static async Task BuildDynamicControllersAsync()
        {
            var traceId = App.GetTraceId() ?? IDGen.NextID().ToString();
            var scopeFactory = App.GetService<IServiceScopeFactory>();
            var serviceScope = scopeFactory.CreateScope();
            var rep = serviceScope.ServiceProvider.GetRequiredService<IRepository<Resource>>();
            var endpointDataSource = App.GetService<EndpointDataSource>();
            var routeEndpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>();
            var provider = serviceScope.ServiceProvider.GetRequiredService<IDynamicApiRuntimeChangeProvider>();
            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 => typeof(IBaseRequest).IsAssignableFrom(it)).ToList();
            var resources = await rep.AsQueryable()
                .Where(it => !it.IsExpired)
                .ToListAsync();
            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}分配服务特性Resource");
                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);
                }
                else
                {
                    model.Adapt(webApi);
                    await rep.UpdateAsync(webApi);
                    var resourceController = controller.GetType().GetMember(controller.ToString())[0].GetCustomAttribute<ResourceControllerAttribute>();
                    var resourceService = resourceController.Service.GetType().GetMember(resourceController.Service.ToString())[0].GetCustomAttribute<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.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.Name = requestXmlDoc?.Summary;
                    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;
                    }
                    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 expiredWebApis = webApis.Where(it => it.TraceId != traceId).ToList();
            foreach (var webApi in expiredWebApis)
            var expiredResources = resources.Where(it => it.TraceId != traceId).ToList();
            foreach (var expiredResource in expiredResources)
            {
                webApi.IsExpired = true;
                await rep.UpdateAsync(webApi);
                expiredResource.IsExpired = true;
                await rep.UpdateAsync(expiredResource);
            }
            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 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;
namespace {controller.Key.ApplicationName}
{{
    /// <summary>
    /// {controller.Key.ControllerSummary}
    /// </summary>
    [Route(""api/{controller.Key.RouteArea}/[controller]"")]
    public class {controller.Key.Controller}AppService(IMediator mediator) : IDynamicApiController
    {{
        private readonly IMediator mediator = mediator;";
                foreach (var action in controller.Actions)
                {
                    code += $@"
        /// <summary>
        /// {action.Name}
        /// </summary>
        /// <param name=""request""></param>
        /// <returns></returns>";
                    if (action.AllowAnonymous)
                    {
                        code += $@"
        [AllowAnonymous]";
                    }
                    code += $@"
        [Http{action.Method}]
        public Task<{action.ResponseTypeName}> {action.ActionName}({action.RequestTypeName} request)
        {{
            return mediator.Send(request);
        }}
";
                }
                code += $@"
    }}
}}
";
                var dynamicAssembly = App.CompileCSharpClassCode(code);
                provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
            }
            await rep.SaveNowAsync();
        }
        /// <summary>
        /// 获取C#友好名称
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public 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)}>";
        }
    }
}