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;
|
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.Linq;
|
using System.Reflection;
|
using System.Text;
|
using System.Text.RegularExpressions;
|
using System.Threading;
|
using System.Threading.Tasks;
|
|
namespace FlexJobApi.Core
|
{
|
/// <summary>
|
/// 资源工具
|
/// </summary>
|
public static class ResourceUtils
|
{
|
/// <summary>
|
/// 生成动态控制器
|
/// </summary>
|
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 provider = serviceScope.ServiceProvider.GetRequiredService<IDynamicApiRuntimeChangeProvider>();
|
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 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 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 expiredResources = resources.Where(it => it.TraceId != traceId).ToList();
|
foreach (var expiredResource in expiredResources)
|
{
|
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;
|
using System.ComponentModel;
|
|
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)}>";
|
}
|
}
|
}
|