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
{
///
/// 资源工具
///
public static class ResourceUtils
{
///
/// 生成动态控制器
///
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);
await App.GetRequiredService().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson());
}
}
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)
{
resources.Remove(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]";
}
var result = resource.ResponseTypeName.IsNull() ? "Task" : $"Task<{resource.ResponseTypeName}>";
code += $@"
[Http{resource.Method}]
public {result} {resource.ActionName}({resource.RequestTypeName} request)
{{
return mediator.Send(request);
}}
}}
}}
";
try
{
var dynamicAssembly = App.CompileCSharpClassCode(code);
provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
var dynamicAssemblyName = dynamicAssembly.GetName().Name;
resource.DynamicAssemblyName = dynamicAssemblyName;
}
catch (Exception ex)
{
Console.WriteLine(code);
throw;
}
}
}
///
/// 动态控制器热拔
///
///
///
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)}>";
}
}
}