Activities of "dhaoo"

ABP Framework version: v8.1.3

UI Type: MVC

Database System: EF Core (Only for Oracle)

Tiered (for MVC) or Auth Server Separated (for Angular): yes (Micro Service)

Exception message and full stack trace:

Steps to reproduce the issue:

I am integrating Quartz with ABP and then I create a tenant, How do I migrate Quartz tables data (e.g. QRTZ_JOB_DETAILS ,etc.) into the new tenant database? Can ABP automatically migrate Quartz table data when I create a tenant?

Thanks for your reply.

Answer

hi

You said: I find that yarp will automatically update swagger when the configuration file is changed?

Does the abp template project not update swagger automatically?

If so. Can you share a sample code of yarp that works? I will compare the code with the abp template project.

I checked the document https://microsoft.github.io/reverse-proxy/articles/config-providers.html

abp will always set reloadOnChange to ture for appsettings.json.

Thanks.

The code I shared above is the code in my current project, is reloadOnChange only for appsettings.json? I changed the yarp configuration file from yarp.json to memory in the code, I put in my middleware yarp configuration through the httpContext. RequestServices. GetRequiredService < InMemoryConfigProvider > (), Update (routes, clusters); Method was assigned and I checked that IProxyConfig had a value, but swagger didn't refresh. Do you know why

Answer

I am in configProvider.Update(routes, clusters); Then check that the value of ProxyConfig in IProxyConfigProvider has been attached

Answer
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using LCFC.MOM.Shared.Hosting.AspNetCore;
using LCFC.MOM.Shared.Hosting.Gateways;
using Swashbuckle.AspNetCore.SwaggerUI;
using Volo.Abp;
using Volo.Abp.Modularity;
using Yarp.ReverseProxy.Configuration;
using Consul;
using Autofac.Core;

namespace LCFC.MOM.WebGateway;

[DependsOn(
    typeof(MOMSharedHostingGatewaysModule)
)]
public class MOMWebGatewayModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // Enable if you need hosting environment
        // var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
        var hostingEnvironment = context.Services.GetHostingEnvironment();

        //context.Services.AddSingleton<ISwaggerConfigHelper, SwaggerConfigHelper>();
       
        context.Services.AddSingleton<IConsulClient>(provider =>
        {
            var consulAddress = configuration["Consul:Address"] ?? "http://localhost:8500"; // 默认 Consul 地址
            return new ConsulClient(config => config.Address = new Uri(consulAddress));
        });
        UpdateRoutesFromConsul("http://localhost:8500", context);
        var consulClient = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>();
        SwaggerConfigurationHelper
            .ConfigureWithOidc(
                context: context,
                authority: configuration["AuthServer:Authority"]!,
                scopes: consulClient.Agent.Services().Result.Response.Select(i=>i.Value.Service+"Service").ToArray(),
                apiTitle: "Web Gateway API",
                discoveryEndpoint: configuration["AuthServer:MetadataAddress"]
            );
        //context.Services.AddSingleton<IProxyConfigProvider, ProxyConfigProvider>();
        // context.Services.AddSingleton<ProxyConfigProvider>();
        // context.Services.AddSingleton<IProxyConfigProvider>(provider => provider.GetRequiredService<ProxyConfigProvider>());
        context.Services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]?
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.Trim().RemovePostFix("/"))
                            .ToArray() ?? Array.Empty<string>()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
        //context.Services.AddHostedService<ConsulServiceWatcher>();
    }
    private List<AgentService> GetServicesFromConsul(string serviceName,ServiceConfigurationContext context)
    {
        var services = new List<AgentService>();
        var result = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>()
            .Agent.Services().Result.Response;

        foreach (var service in result)
        {
            services.Add(service.Value);
        }

        return services;
    }
    public void UpdateRoutesFromConsul(string serviceName,ServiceConfigurationContext context)
    {
        var services = GetServicesFromConsul(serviceName,context);

        var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
        var clusters = new List<ClusterConfig>();

        foreach (var service in services)
        {
            // 更新 YARP 路由配置
            routes.Add(new Yarp.ReverseProxy.Configuration. RouteConfig
            {
                RouteId = service.Service,
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
            });
            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service+ "Swagger",
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                Transforms=new List<IReadOnlyDictionary<string, string>>()
                {
                    new Dictionary<string, string>
                    {
                        { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                    }
                }
            });
            // 更新 YARP 集群配置
            clusters.Add(new ClusterConfig
            {
                ClusterId = service.Service,
                Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                {
                    { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                }
            });
        }
        context.Services.AddReverseProxy()
             .LoadFromMemory(routes,clusters);
        // 更新代理配置
        //_proxyConfig.UpdateConfig(routes, clusters);
    }
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        var configuration = context.GetConfiguration();
        app.UseMiddleware<ConsulServiceWatcher>(); // 注册自定义中间件
        var proxyConfig = app.ApplicationServices.GetRequiredService<IProxyConfigProvider>().GetConfig();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseCorrelationId();
        app.UseStaticFiles();
        app.UseCors();
        app.UseRouting();
        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options => { ConfigureSwaggerUI(proxyConfig, options, configuration); });
        app.UseRewriter(CreateSwaggerRewriteOptions());
        app.UseAbpSerilogEnrichers();
        app.UseEndpoints(endpoints => endpoints.MapReverseProxy());

    }

    private static void ConfigureSwaggerUI(
        IProxyConfig proxyConfig,
        SwaggerUIOptions options,
        IConfiguration configuration)
    {
        foreach (var cluster in proxyConfig.Clusters)
        {
            options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json",
                $"{cluster.ClusterId} API");
        }

        options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        options.OAuthScopes(proxyConfig.Clusters.Select(i=>i.ClusterId+"Service").ToArray()
        );
    }

    private static RewriteOptions CreateSwaggerRewriteOptions()
    {
        var rewriteOptions = new RewriteOptions();
        rewriteOptions.AddRedirect("^(|\\|\\s+)$", "/swagger"); // Regex for "/" and "" (whitespace)
        return rewriteOptions;
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Consul;
using LCFC.MOM.Shared.Hosting.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
using Volo.Abp.Modularity;
using Yarp.ReverseProxy.Configuration;

namespace LCFC.MOM.WebGateway;

public class ConsulServiceWatcher 
{
    private readonly IConsulClient _consulClient;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private List<AgentService> _lastServices = new List<AgentService>();
    private Timer _timer;
    private IConfiguration _configuration;
    private readonly RequestDelegate _next;
    private readonly InMemoryConfigProvider _inMemoryConfigProvider;
    public ConsulServiceWatcher(RequestDelegate next,IConsulClient consulClient, 
        IConfiguration configuration,IHttpContextAccessor httpContextAccessor)
    {
        _consulClient = consulClient;
        _httpContextAccessor = httpContextAccessor;
        _next = next;
        _configuration = configuration;
    }
    private void UpdateServices(object state)
    {
        // 获取 Consul 中的服务列表
        var services = GetServicesFromConsul();

        // 更新代理配置
        if (!services.Select(i=>i.Service).SequenceEqual(_lastServices.Select(i=>i.Service)))
        {
            // 如果服务有变化,更新路由
            _lastServices = services;
            UpdateRoutesFromConsul(services);
        }
    }
    public async Task InvokeAsync(HttpContext context)
    {
        // 初始化服务监控
        // 每隔 10 秒更新一次服务配置
        //_timer = new Timer(UpdateServices, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
        UpdateServices(null);
        await _next(context);
    }

    private void UpdateRoutesFromConsul(List<AgentService> services)
    {
        var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
        var clusters = new List<ClusterConfig>();

        foreach (var service in services)
        {
            // 更新 YARP 路由配置
            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service,
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
            });

            routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
            {
                RouteId = service.Service + "Swagger",
                ClusterId = service.Service,
                Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                Transforms = new List<IReadOnlyDictionary<string, string>>()
                {
                    new Dictionary<string, string>
                    {
                        { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                    }
                }
            });

            clusters.Add(new ClusterConfig
            {
                ClusterId = service.Service,
                Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                {
                    { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                }
            });
        }
        
        var configProvider = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<InMemoryConfigProvider>();
        configProvider.Update(routes, clusters);
        //configProvider.GetConfig()
        // 手动触发 ChangeToken

        var updatedConfig =_httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
        //UpdateOAuthScopes(services.Select(i=>i.Service+"Service").ToArray());
        //UpdateSwaggerUI(updatedConfig);
    }
    private List<AgentService> GetServicesFromConsul()
    {
        var services = new List<AgentService>();
        var result = _consulClient.Agent.Services().Result.Response;

        foreach (var service in result)
        {
            services.Add(service.Value);
        }

        return services;
    }
    private void UpdateSwaggerUI(IProxyConfig proxyConfig)
    {
        if (proxyConfig.Clusters.Count == 0)
        {
            return;
        }
        // 动态更新 Swagger UI
        var app = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IApplicationBuilder>();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            foreach (var cluster in proxyConfig.Clusters)
            {
                options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API");
            }
        });
    }


}
  • ABP Framework version: v8.1.3

  • UI Type: MVC

  • Database System: EF Core (Only for Oracle)

  • Tiered (for MVC) or Auth Server Separated (for Angular): yes (Micro Service)

  • Exception message and full stack trace:

  • Steps to reproduce the issue:

    I am using Oracle database. I have filled two connection string in the Database connection strings Modal as the following figure. One for Tenant and the other for Product Service. They both use Oracle. and then click "Save" button, but no two databases(table spaces) specified in Database connection strings are created.

    • Can the ABP Framework support creating oracle database(table spaces) if they don't exists?
    • How do I do if I want to migrate(Clone) data in a Job table into a tenant job table during the above Tenant data migration process?
    • What is the Best Practice for Mult-Tenancy Schema & Data Migration in Oracle ?

Answer

hi

This is not abp scope. Microservice uses the yarp, you can check its document.

Thanks.

hi, About yarp, abp uses the default to read the configuration file, but when I debug it, I find that yarp will automatically update swagger when the configuration file is changed. Is abp framework processed or yarp processed automatically? Now that I have changed yarp to use memory, I see when debugging that yarp's cluster has been updated, but swagger is not automatically updated

Question
  • ABP Framework version: v8.1.3
  • UI Type: MVC
  • Database System: EF Core (Oracle,etc..)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no
  • Exception message and full stack trace:
  • Steps to reproduce the issue: hi, I want to use gateway in conjunction with consul to automatically register services. Does ABP have any corresponding examples?

ok,thanks!

I see, let's name the new view TenantSwitchModal, thank you! Now I want to display the user tenant information in the red-framed picture after the user logs in. I need to rewrite which pages?

Showing 1 to 10 of 31 entries
Made with ❤️ on ABP v9.1.0-preview. Updated on December 13, 2024, 06:09