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.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
|
{
|
/// <summary>
|
/// 资源工具
|
/// </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 Task<TResponse> SendHttpAsync<TRequest, TResponse>(
|
TRequest request,
|
IResourceHttpProvider provider = null)
|
where TRequest : class, new()
|
{
|
return App.GetRequiredService<ResourceHttpUtils>().SendHttpAsync<TRequest, TResponse>(request, provider);
|
}
|
|
/// <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 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 => !it.IsAbstract && typeof(IBaseRequest).IsAssignableFrom(it))
|
.ToList();
|
var models = new List<ResourceModel>();
|
foreach (var request in requests)
|
{
|
var resourceAttribute = request.GetCustomAttribute<ResourceAttribute>();
|
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<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);
|
|
await App.GetRequiredService<IDistributedCache>().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson());
|
}
|
}
|
|
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
|
{
|
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();
|
}
|
|
/// <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)}>";
|
}
|
}
|
}
|