sunpengfei
2025-08-08 6c0058d15ab61956b806f62a2b576ffd5d723ed9
fix:bug
3个文件已添加
7个文件已修改
265 ■■■■ 已修改文件
FlexJobApi.Core/FlexJobApi.Core.xml 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/FlexJobApiCoreStartup.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/Models/FlexJobServer/Tasks/Repositories/TaskInfoRepository.cs 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/Utils/ResourceUtils/ResourceHttpUtils.cs 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/Utils/ResourceUtils/ResourceModel.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.FlexJobServer.Test/FlexJobApi.FlexJobServer.Test.csproj 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.FlexJobServer.Test/Program.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.FlexJobServer.Test/Startup.cs 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.FlexJobServer.Test/Tasks/TaskUnitTest.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FlexJobApi.Core/FlexJobApi.Core.xml
@@ -5640,6 +5640,23 @@
            </summary>
            <returns></returns>
        </member>
        <member name="M:FlexJobApi.Core.ResourceHttpUtils.SendHttpAsync``2(``0,FlexJobApi.Core.IResourceHttpProvider)">
            <summary>
            发送HTTP请求
            </summary>
            <typeparam name="TRequest"></typeparam>
            <typeparam name="TResponse"></typeparam>
            <param name="request"></param>
            <param name="provider"></param>
            <returns></returns>
        </member>
        <member name="M:FlexJobApi.Core.ResourceHttpUtils.GetHealthyServiceDomain(FlexJobApi.Core.ResourceModel)">
            <summary>
            获取健康服务域名
            </summary>
            <param name="resource"></param>
            <returns></returns>
        </member>
        <member name="P:FlexJobApi.Core.ResourceModel.TraceId">
            <summary>
            跟踪Id
@@ -5798,13 +5815,6 @@
            <typeparam name="TResponse"></typeparam>
            <param name="request"></param>
            <param name="provider"></param>
            <returns></returns>
        </member>
        <member name="M:FlexJobApi.Core.ResourceUtils.GetHealthyServiceDomain(FlexJobApi.Core.Resource)">
            <summary>
            获取健康服务域名
            </summary>
            <param name="resource"></param>
            <returns></returns>
        </member>
        <member name="M:FlexJobApi.Core.ResourceUtils.BuildDynamicControllersAsync">
FlexJobApi.Core/FlexJobApiCoreStartup.cs
@@ -43,14 +43,15 @@
            services.AddConfigurableOptions<AliyunOptions>();
            services.AddHttpRemote();
            services.AddComponent<ConsulServiceComponent>();
            services.AddComponent<EventBusServiceComponent>();
            services.AddComponent<DistributedCacheServiceComponent>();
            services.AddHttpRemote();
            services.AddSingleton<ResourceHttpUtils>();
            services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(App.Assemblies.ToArray()));
            services.AddHostedService<XmlDocBuildHostedService>();
FlexJobApi.Core/Models/FlexJobServer/Tasks/Repositories/TaskInfoRepository.cs
@@ -25,7 +25,10 @@
            logier = logier ?? JwtUtils.GetCurrentLogier();
            return rep.AsQueryable().AsNoTracking()
                 .OrderBy(it => it.BeginTime)
                 .Where(it => it.EnterpriseId == logier.EnterpriseId);
                 .Where(it =>
                    logier.Type == EnumUserType.Enterprise
                    ? it.EnterpriseId == logier.EnterpriseId
                    : true);
        }
    }
}
FlexJobApi.Core/Utils/ResourceUtils/ResourceHttpUtils.cs
New file
@@ -0,0 +1,81 @@
using Consul;
using Furion;
using Furion.FriendlyException;
using Furion.HttpRemote;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FlexJobApi.Core
{
    public class ResourceHttpUtils
    {
        private readonly IDistributedCache distributedCache;
        private readonly IHttpRemoteService httpRemoteService;
        private readonly IConsulClient consulClient;
        public ResourceHttpUtils(
            IDistributedCache distributedCache,
            IHttpRemoteService httpRemoteService,
            IConsulClient consulClient)
        {
            this.distributedCache = distributedCache;
            this.httpRemoteService = httpRemoteService;
            this.consulClient = consulClient;
        }
        /// <summary>
        /// 发送HTTP请求
        /// </summary>
        /// <typeparam name="TRequest"></typeparam>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="request"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        public async Task<TResponse> SendHttpAsync<TRequest, TResponse>(
            TRequest request,
            IResourceHttpProvider provider = null)
            where TRequest : class, new()
        {
            var requestTypeFullName = typeof(TRequest).FullName;
            var jsonResourceModels = await distributedCache.GetStringAsync($"ResourceModel|{requestTypeFullName}");
            var resource = jsonResourceModels.JsonTo<ResourceModel>();
            var domain = await GetHealthyServiceDomain(resource);
            var builder = HttpRequestBuilder.Create(resource.GetHttpMethod(), $"{domain}{resource.Route}");
            if (resource.Method == EnumResourceMethod.Get)
                builder = builder.WithQueryParameters(request);
            else
                builder = builder.SetJsonContent(request);
            provider = provider ?? new DefaultResourceHttpProvider();
            builder = provider.AddAuthentication(builder);
            var response = await provider.SendAsAsync<TResponse>(httpRemoteService, builder);
            return response;
        }
        /// <summary>
        /// 获取健康服务域名
        /// </summary>
        /// <param name="resource"></param>
        /// <returns></returns>
        public async Task<string> GetHealthyServiceDomain(ResourceModel resource)
        {
            var queryResult = await consulClient.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];
        }
    }
}
FlexJobApi.Core/Utils/ResourceUtils/ResourceModel.cs
@@ -1,7 +1,9 @@
using System;
using Furion.FriendlyException;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
@@ -103,5 +105,22 @@
        /// 响应类型全名
        /// </summary>
        public string ResponseTypeFullName { get; set; }
        public HttpMethod GetHttpMethod()
        {
            switch (Method)
            {
                case EnumResourceMethod.Get:
                    return HttpMethod.Get;
                case EnumResourceMethod.Post:
                    return HttpMethod.Post;
                case EnumResourceMethod.Put:
                    return HttpMethod.Put;
                case EnumResourceMethod.Delete:
                    return HttpMethod.Delete;
                default:
                    throw Oops.Oh(EnumErrorCodeType.s400, "不支持的请求类型");
            }
        }
    }
}
FlexJobApi.Core/Utils/ResourceUtils/ResourceUtils.cs
@@ -13,6 +13,7 @@
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;
@@ -44,48 +45,12 @@
        /// <param name="request"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        public static async Task<TResponse> SendHttpAsync<TRequest, TResponse>(
        public static Task<TResponse> SendHttpAsync<TRequest, TResponse>(
            TRequest request,
            IResourceHttpProvider provider = null)
            where TRequest : class, new()
        {
            var requestTypeFullName = typeof(TRequest).FullName;
            var resource = await Db.GetRepository<Resource>().AsQueryable().AsNoTracking()
                .Where(it => !it.IsExpired && it.RequestTypeFullName == requestTypeFullName)
                .FirstOrDefaultAsync();
            var domain = await GetHealthyServiceDomain(resource);
            var httpRemoteService = App.GetRequiredService<IHttpRemoteService>();
            var builder = HttpRequestBuilder.Create(resource.GetHttpMethod(), $"{domain}{resource.Route}");
            if (resource.Method == EnumResourceMethod.Get)
                builder = builder.WithQueryParameters(request);
            else
                builder = builder.SetJsonContent(request);
            provider = provider ?? new DefaultResourceHttpProvider();
            builder = provider.AddAuthentication(builder);
            var response = await provider.SendAsAsync<TResponse>(httpRemoteService, builder);
            return response;
        }
        /// <summary>
        /// 获取健康服务域名
        /// </summary>
        /// <param name="resource"></param>
        /// <returns></returns>
        public static async Task<string> GetHealthyServiceDomain(Resource resource)
        {
            var client = App.GetRequiredService<IConsulClient>();
            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];
            return App.GetRequiredService<ResourceHttpUtils>().SendHttpAsync<TRequest, TResponse>(request, provider);
        }
        /// <summary>
@@ -152,9 +117,9 @@
                        model.ResponseTypeName = responseType.GetCSharpFriendlyName();
                        model.ResponseTypeFullName = responseType.FullName;
                    }
                    models.Add(model);
                    await App.GetRequiredService<IDistributedCache>().SetStringAsync($"ResourceModel|{model.RequestTypeFullName}", model.ToJson());
                }
            }
FlexJobApi.FlexJobServer.Test/FlexJobApi.FlexJobServer.Test.csproj
@@ -17,8 +17,9 @@
  <ItemGroup>
    <PackageReference Include="coverlet.collector" Version="6.0.2" />
    <PackageReference Include="Furion.Xunit" Version="4.9.7.108" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
    <PackageReference Include="xunit" Version="2.9.2" />
    <PackageReference Include="xunit" Version="2.9.3" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
  </ItemGroup>
FlexJobApi.FlexJobServer.Test/Program.cs
New file
@@ -0,0 +1 @@
Serve.Run(RunOptions.Default.WithArgs(args));
FlexJobApi.FlexJobServer.Test/Startup.cs
New file
@@ -0,0 +1,71 @@
using Consul;
using FlexJobApi.Core;
using Furion;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FlexJobApi.FlexJobServer.Test
{
    public class Startup : AppStartup
    {
        public void ConfigureServices(IServiceCollection services, IConfiguration config)
        {
            services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(options =>
            {
                var address = config["Consul:Address"] ?? "http://localhost:8500";
                options.Address = new Uri(address);
            }));
            services.AddComponent<DistributedCacheServiceComponent>();
            services.AddHttpRemote();
            services.AddSingleton<ResourceHttpUtils>();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration config)
        {
            var consulClient = app.ApplicationServices.GetRequiredService<IConsulClient>();
            var lifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
            // 服务配置(从appsettings.json读取)
            var serviceName = config["Consul:ServiceName"] ?? "UnknownService";
            var serviceHost = config["Consul:ServiceIP"] ?? "localhost";
            var servicePort = int.Parse(config["Consul:ServicePort"]); // 或直接用启动端口
            // 服务唯一ID(避免同一服务多实例冲突)
            var serviceId = $"{serviceName}-{serviceHost}-{servicePort}";
            // 服务注册信息
            var registration = new AgentServiceRegistration
            {
                ID = serviceId,
                Name = serviceName,
                Address = serviceHost,
                Port = servicePort,
                // 健康检查配置
                Check = new AgentServiceCheck
                {
                    HTTP = $"http://{serviceHost}:{servicePort}{config["Consul:ServiceHealthCheck"]}",
                    Interval = TimeSpan.FromSeconds(10),
                    Timeout = TimeSpan.FromSeconds(5),
                    DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30)
                }
            };
            // 注册服务
            consulClient.Agent.ServiceRegister(registration).Wait();
            // 应用停止时注销服务
            lifetime.ApplicationStopping.Register(() =>
            {
                consulClient.Agent.ServiceDeregister(serviceId).Wait();
            });
        }
    }
}
FlexJobApi.FlexJobServer.Test/Tasks/TaskUnitTest.cs
@@ -5,10 +5,17 @@
{
    public class TaskUnitTest
    {
        private readonly ResourceHttpUtils http;
        public TaskUnitTest(ResourceHttpUtils http)
        {
            this.http = http;
        }
        [Fact]
        public async Task Test1()
        {
            var categories = await ResourceUtils.SendHttpAsync<
            var categories = await http.SendHttpAsync<
                GetDictionaryCategorySelectQuery,
                FriendlyResult<SelectOption<Guid, GetDictionaryCategorySelectQueryOption>>>(
                new GetDictionaryCategorySelectQuery());