using 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; using Microsoft.EntityFrameworkCore; 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 { /// /// 资源工具 /// public static class ResourceUtils { /// /// 发送HTTP请求 /// /// /// /// /// public static async Task SendHttpAsync(TRequest request) where TRequest : class, new() { var requestTypeFullName = typeof(TRequest).FullName; var resource = await Db.GetRepository().AsQueryable().AsNoTracking() .Where(it => !it.IsExpired && it.RequestTypeFullName == requestTypeFullName) .FirstOrDefaultAsync(); var domain = await GetHealthyServiceDomain(resource); var httpRemoteService = App.GetRequiredService(); 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( $"{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( $"{domain}{resource.Route}", it => it.SetJsonContent(request) .WithHeader("Authorization", token) .WithHeader("X-Authorization", refreshToken)); break; case EnumResourceMethod.Put: response = await httpRemoteService.PutAsAsync( $"{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( $"{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; } /// /// 获取健康服务域名 /// /// /// public static async Task GetHealthyServiceDomain(Resource resource) { var client = App.GetRequiredService(); 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]; } /// /// 生成动态控制器 /// public static async Task BuildDynamicControllersAsync() { var traceId = App.GetTraceId() ?? IDGen.NextID().ToString(); var scopeFactory = App.GetService(); var serviceScope = scopeFactory.CreateScope(); var provider = serviceScope.ServiceProvider.GetRequiredService(); var rep = serviceScope.ServiceProvider.GetRequiredService>(); var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); var enumWebApiMethods = await EnumUtils.GetModel(); var resourceControllers = await EnumUtils.GetModel(); var requests = App.Assemblies .SelectMany(it => it.GetTypes()) .Where(it => !it.IsAbstract && typeof(IBaseRequest).IsAssignableFrom(it)) .ToList(); var models = new List(); foreach (var request in requests) { var resourceAttribute = request.GetCustomAttribute(); if (resourceAttribute == null) throw Oops.Oh(EnumErrorCodeType.s404, $"请给资源{request.Name}分配服务特性Resource"); foreach (var controller in resourceAttribute.Controllers) { var requestXmlDoc = await request.GetXmlDocMemberAsync(xmlDoc); var resourceController = controller.GetCustomAttribute(); var resourceService = resourceController.Service.GetCustomAttribute(); 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 { "Post", "Add", "Create", "Insert", "Submit" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) ? EnumResourceMethod.Post : new List { "GetAll", "GetList", "Get", "Find", "Fetch", "Query" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) ? EnumResourceMethod.Get : new List { "Put", "Update ", "Set" }.Any(it => request.Name.StartsWith(it, StringComparison.OrdinalIgnoreCase)) ? EnumResourceMethod.Put : new List { "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(); } /// /// 保存资源 /// /// /// /// /// private static async Task> SaveResourcesAsync(List models, IRepository rep = null) { rep = rep ?? Db.GetRepository(); 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 { 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 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(); } /// /// 动态控制器热插 /// /// /// public static void DynamicControllersHotPlug(List resources, IDynamicApiRuntimeChangeProvider provider = null) { provider = provider ?? App.GetRequiredService(); 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 {{ /// /// {resource.ControllerSummary} /// [Route(""api/{resource.RouteArea}/[controller]"")] public class {resource.Controller}AppService(IMediator mediator) : IDynamicApiController {{ private readonly IMediator mediator = mediator; /// /// {resource.ActionSummary} /// /// /// "; 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; } } /// /// 动态控制器热拔 /// /// /// public static void DynamicControllerHotPluck(Resource resource, IDynamicApiRuntimeChangeProvider provider = null) { provider = provider ?? App.GetRequiredService(); provider.RemoveAssembliesWithNotifyChanges(resource.DynamicAssemblyName); } /// /// 获取C#友好名称 /// /// /// 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 { { "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)}>"; } } }