11

ABP微服务系列学习-搭建自己的微服务结构(三)

 1 year ago
source link: https://www.cnblogs.com/fanshaoO/p/17166952.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

上一篇我们基础服务初步搭建完毕,接下来我们整一下认证和网关。

搭建认证服务#

认证服务的话,ABP CLI生成的所有模板都包括了一个AuthServer。我们直接生成模板然后微调一下就可以直接用了。

abp new FunShow -t app --tiered

使用命令创建模板后,我们可以找到一个AuthServer。把项目移动到Apps目录下,然后我们开始改造一下这个项目。
首先修改项目文件的引用配置
修改EFCore项目引用为AdministrationService.EntityFrameworkCore和IdentityService.EntityFrameworkCore,
然后添加Shared.Localization和Shared.Hosting.AspNetCore项目引用,别的基本不用怎么修改,完整项目配置为:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <Import Project="..\..\..\..\common.props" />

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <UserSecretsId>b83bc18b-a6ca-4e2d-a827-26ffaff35dce</UserSecretsId>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
    <DockerfileContext>..\..\..\..</DockerfileContext>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.5" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="7.0.0" />
    <PackageReference Include="Volo.Abp.EventBus.RabbitMQ" Version="7.0.0" />
    <PackageReference Include="Volo.Abp.BackgroundJobs.RabbitMQ" Version="7.0.0" />
    <PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="7.0.0" />
    <PackageReference Include="Volo.Abp.Account.Application" Version="7.0.0" />
    <PackageReference Include="Volo.Abp.Account.HttpApi" Version="7.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\..\services\administration\src\FunShow.AdministrationService.EntityFrameworkCore\FunShow.AdministrationService.EntityFrameworkCore.csproj" />
    <ProjectReference Include="..\..\..\..\services\identity\src\FunShow.IdentityService.EntityFrameworkCore\FunShow.IdentityService.EntityFrameworkCore.csproj" />
    <ProjectReference Include="..\..\..\..\shared\FunShow.Shared.Hosting.AspNetCore\FunShow.Shared.Hosting.AspNetCore.csproj" />
    <ProjectReference Include="..\..\..\..\shared\FunShow.Shared.Localization\FunShow.Shared.Localization.csproj" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="2.0.0-*" />
  </ItemGroup>

  <ItemGroup>
    <Compile Remove="Logs\**" />
    <Content Remove="Logs\**" />
    <EmbeddedResource Remove="Logs\**" />
    <None Remove="Logs\**" />
  </ItemGroup>

</Project>

然后修改Program文件,主要是日志配置修改一下,别的不用改动

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using FunShow.Shared.Hosting.AspNetCore;
using Serilog;

namespace FunShow.AuthServer;

public class Program
{
    public async static Task<int> Main(string[] args)
    {
        var assemblyName = typeof(Program).Assembly.GetName().Name;

        SerilogConfigurationHelper.Configure(assemblyName);

        try
        {
            Log.Information($"Starting {assemblyName}.");
            var builder = WebApplication.CreateBuilder(args);
            builder.Host
                .AddAppSettingsSecretsJson()
                .UseAutofac()
                .UseSerilog();
            await builder.AddApplicationAsync<FunShowAuthServerModule>();
            var app = builder.Build();
            await app.InitializeApplicationAsync();
            await app.RunAsync();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

修改module.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using FunShow.AdministrationService.EntityFrameworkCore;
using FunShow.IdentityService.EntityFrameworkCore;
using FunShow.Shared.Hosting.AspNetCore;
using Prometheus;
using StackExchange.Redis;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Web;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
using Volo.Abp.Auditing;
using Volo.Abp.BackgroundJobs.RabbitMQ;
using Volo.Abp.Caching;
using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Abp.Emailing;
using Volo.Abp.EventBus.RabbitMq;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
using Microsoft.AspNetCore.HttpOverrides;
using FunShow.Shared.Localization;

namespace FunShow.AuthServer;

[DependsOn(
    typeof(AbpCachingStackExchangeRedisModule),
    typeof(AbpEventBusRabbitMqModule),
    typeof(AbpBackgroundJobsRabbitMqModule),
    typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule),
    typeof(AbpAccountWebOpenIddictModule),
    typeof(AbpAccountApplicationModule),
    typeof(AbpAccountHttpApiModule),
    typeof(AdministrationServiceEntityFrameworkCoreModule),
    typeof(IdentityServiceEntityFrameworkCoreModule),
    typeof(FunShowSharedHostingAspNetCoreModule),
    typeof(FunShowSharedLocalizationModule)
)]
public class FunShowAuthServerModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
    
    PreConfigure<OpenIddictBuilder>(builder =>
    {
    	builder.AddValidation(options =>
    {
    options.AddAudiences("AccountService");
    options.UseLocalServer();
    options.UseAspNetCore();
    });
    });
    if (!hostingEnvironment.IsDevelopment())
    {
    	PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
    	{
    	options.AddDevelopmentEncryptionAndSigningCertificate = false;
    	});
    
        PreConfigure<OpenIddictServerBuilder>(builder =>
        {
            builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration));
            builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration));
            });
        }
    }
    
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
    //You can disable this setting in production to avoid any potential security risks.
        Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
        
        var hostingEnvironment = context.Services.GetHostingEnvironment();
        var configuration = context.Services.GetConfiguration();
        
        ConfigureBundles();
        ConfigureSwagger(context, configuration);
        ConfigureSameSiteCookiePolicy(context);
        ConfigureExternalProviders(context);
        
        Configure<AbpMultiTenancyOptions>(options =>
        {
        	options.IsEnabled = true;
        });
        
        Configure<AbpAuditingOptions>(options =>
        {
            options.ApplicationName = "AuthServer";
        });
        
        Configure<AppUrlOptions>(options =>
        {
            options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
            options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));
        });
        
        Configure<AbpDistributedCacheOptions>(options =>
        {
        	options.KeyPrefix = "FunShow:";
        });
        
        var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("FunShow");
        var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
        dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "FunShow-Protection-Keys");
        
        context.Services.AddCors(options =>
        {
        	options.AddDefaultPolicy(builder =>
            {
            builder
            .WithOrigins(
            configuration["App:CorsOrigins"]
            .Split(",", StringSplitOptions.RemoveEmptyEntries)
            .Select(o => o.Trim().RemovePostFix("/"))
            .ToArray()
            )
            .WithAbpExposedHeaders()
            .SetIsOriginAllowedToAllowWildcardSubdomains()
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials();
            });
        });
        
        #if DEBUG
        context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>());
        #endif
        
        if (hostingEnvironment.IsDevelopment())
        {
            Configure<AbpVirtualFileSystemOptions>(options =>
            {
            options.FileSets.ReplaceEmbeddedByPhysical<FunShowSharedLocalizationModule>(Path.Combine(
            hostingEnvironment.ContentRootPath,
            $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}shared{Path.DirectorySeparatorChar}FunShow.Shared.Localization"));
            });
        }
    }
    
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        
        var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
        
        if (env.IsDevelopment())
        {
        	app.UseDeveloperExceptionPage();
        }
    
        app.UseAbpRequestLocalization();
        
        if (!env.IsDevelopment())
        {
        	app.UseErrorPage();
        }
        var forwardOptions = new ForwardedHeadersOptions
        {
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
            RequireHeaderSymmetry = false
        };
    
        forwardOptions.KnownNetworks.Clear();
        forwardOptions.KnownProxies.Clear();
        
    // ref: https://github.com/aspnet/Docs/issues/2384
        app.UseForwardedHeaders(forwardOptions);
        
        app.UseCorrelationId();
        app.UseAbpSecurityHeaders();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseCors();
        app.UseCookiePolicy();
        app.UseHttpMetrics();
        app.UseAuthentication();
        app.UseAbpOpenIddictValidation();
        app.UseAbpSerilogEnrichers();
        app.UseUnitOfWork();
        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Account Service API");
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        });
        	app.UseAuditing();
        	app.UseConfiguredEndpoints(endpoints =>
            {
            	endpoints.MapMetrics();
            });
    }
    
    private void ConfigureBundles()
    {
        Configure<AbpBundlingOptions>(options =>
        {
            options.StyleBundles.Configure(
            LeptonXLiteThemeBundles.Styles.Global,
            bundle =>
            {
            	bundle.AddFiles("/global-styles.css");
            }
        );
        });
    }
    
    private void ConfigureExternalProviders(ServiceConfigurationContext context)
    {
    	context.Services.AddAuthentication();
    }
    
    private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration)
    {
        var fileName = "authserver.pfx";
        var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED";
        var file = Path.Combine(hostingEnv.ContentRootPath, fileName);
        
        if (!File.Exists(file))
        {
        	throw new FileNotFoundException($"Signing Certificate couldn't found: {file}");
        }
    
    	return new X509Certificate2(file, passPhrase);
    }
    
    private void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration)
    {
        SwaggerConfigurationHelper.ConfigureWithAuth(
        context: context,
        authority: configuration["AuthServer:Authority"],
        scopes: new Dictionary<string, string> {
        /* Requested scopes for authorization code request and descriptions for swagger UI only */
        { "AccountService", "Account Service API" }
        },
        apiTitle: "Account Service API"
        );
    }
    
    private void ConfigureSameSiteCookiePolicy(ServiceConfigurationContext context)
    {
    	context.Services.AddSameSiteCookiePolicy();
    }
}

最后修改配置文件

{
  "App": {
    "SelfUrl": "https://localhost:44322",
    "CorsOrigins": "http://localhost:4200,http://localhost:9527,https://localhost:44307,https://localhost:44325,https://localhost:44353,https://localhost:44367,https://localhost:44388,https://localhost:44381,https://localhost:44361",
    "RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307,https://localhost:44321,http://localhost:9527"
  },
  "AuthServer": {
    "Authority": "https://localhost:44322",
    "RequireHttpsMetadata": "true",
    "SwaggerClientId": "WebGateway_Swagger"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "AdministrationService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Administration;",
    "IdentityService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Identity;"
  },
  "StringEncryption": {
    "DefaultPassPhrase": "fCrJICTG3WoyissG"
  },
  "Redis": {
    "Configuration": "localhost:6379"
  },
  "RabbitMQ": {
    "Connections": {
      "Default": {
        "HostName": "localhost"
      }
    },
    "EventBus": {
      "ClientName": "FunShow_AuthServer",
      "ExchangeName": "FunShow"
    }
  },
  "ElasticSearch": {
    "Url": "http://localhost:9200"
  }
}

这样我们认证服务即修改完成。

搭建网关服务#

网关服务我们直接新建一个空asp.net core项目。
然后只需要添加一个我们的Shared.Hosting.Gateways项目引用即可。

<Project Sdk="Microsoft.NET.Sdk.Web">

  <Import Project="..\..\..\..\common.props" />

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\..\shared\FunShow.Shared.Hosting.Gateways\FunShow.Shared.Hosting.Gateways.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Compile Remove="Logs\**" />
    <Content Remove="Logs\**" />
    <EmbeddedResource Remove="Logs\**" />
    <None Remove="Logs\**" />
  </ItemGroup>

</Project>

修改Program.cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using FunShow.Shared.Hosting.AspNetCore;
using Serilog;

namespace FunShow.WebGateway;

public class Program
{
    public async static Task<int> Main(string[] args)
    {
        var assemblyName = typeof(Program).Assembly.GetName().Name;

        SerilogConfigurationHelper.Configure(assemblyName);

        try
        {
            Log.Information($"Starting {assemblyName}.");
            var builder = WebApplication.CreateBuilder(args);
            builder.Host
                .AddAppSettingsSecretsJson()
                .AddYarpJson()
                .UseAutofac()
                .UseSerilog();
            await builder.AddApplicationAsync<FunShowWebGatewayModule>();
            var app = builder.Build();
            await app.InitializeApplicationAsync();
            await app.RunAsync();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

这里和认证服务基本一致,就是多了一个AddYarpJson()来添加我们的yarp的配置文件。
在目录下新建yarp.json文件,添加我们的yarp配置内容。配置集群和路由如下:

{
  "ReverseProxy": {
    "Routes": {
      "Account Service": {
        "ClusterId": "accountCluster",
        "Match": {
          "Path": "/api/account/{**everything}"
        }
      },
      "Identity Service": {
        "ClusterId": "identityCluster",
        "Match": {
          "Path": "/api/identity/{**everything}"
        }
      },
      "Administration Service": {
        "ClusterId": "administrationCluster",
        "Match": {
          "Path": "/api/abp/{**everything}"
        }
      },
      "Logging Service": {
        "ClusterId": "loggingCluster",
        "Match": {
          "Path": "/api/LoggingService/{**everything}"
        }
      },
      "feature-management-route": {
        "ClusterId": "feature-management-cluster",
        "Match": {
          "Path": "/api/feature-management/{**everything}"
        }
      },
      "permission-management-route": {
        "ClusterId": "permission-management-cluster",
        "Match": {
          "Path": "/api/permission-management/{**everything}"
        }
      },
      "setting-management-route": {
        "ClusterId": "setting-management-cluster",
        "Match": {
          "Path": "/api/setting-management/{**everything}"
        }
      }
    },
    "Clusters": {
      "accountCluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44322"
          }
        }
      },
      "identityCluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44388"
          }
        }
      },
      "administrationCluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44367"
          }
        }
      },
      "loggingCluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:45124"
          }
        }
      },
      "feature-management-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44367"
          }
        }
      },
      "permission-management-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44367"
          }
        }
      },
      "setting-management-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "https://localhost:44367"
          }
        }
      }
    }
  }
}

在appsettings.json文件添加我们认证服务的地址

{
  "App": {
    "SelfUrl": "https://localhost:44325",
    "CorsOrigins": "http://localhost:4200,https://localhost:44307,http://localhost:9527"
  },
  "AuthServer": {
    "Authority": "https://localhost:44322",
    "RequireHttpsMetadata": "true",
    "SwaggerClientId": "WebGateway_Swagger"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Redis": {
    "Configuration": "localhost:6379"
  },
  "ElasticSearch": {
    "Url": "http://localhost:9200"
  }
}

最后我们添加FunShowWebGatewayModule文件。配置我们yarp的服务。

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 FunShow.Shared.Hosting.AspNetCore;
using FunShow.Shared.Hosting.Gateways;
using Volo.Abp;
using Volo.Abp.Modularity;

namespace FunShow.WebGateway;

[DependsOn(
    typeof(FunShowSharedHostingGatewaysModule)
)]
public class FunShowWebGatewayModule : 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();
        
        SwaggerConfigurationHelper.ConfigureWithAuth(
            context: context,
            authority: configuration["AuthServer:Authority"],
            scopes: new
            Dictionary<string, string> /* Requested scopes for authorization code request and descriptions for swagger UI only */ {
            { "AccountService", "Account Service API" },
            { "IdentityService", "Identity Service API" },
            { "AdministrationService", "Administration Service API" },
            { "LoggingService", "Logging Service API" }
            },
            apiTitle: "Web Gateway API"
        );
        
        context.Services.AddCors(options =>
        {
        	options.AddDefaultPolicy(builder =>
        {
        builder
            .WithOrigins(
                configuration["App:CorsOrigins"]
                .Split(",", StringSplitOptions.RemoveEmptyEntries)
                .Select(o => o.Trim().RemovePostFix("/"))
                .ToArray()
            )
            .WithAbpExposedHeaders()
            .SetIsOriginAllowedToAllowWildcardSubdomains()
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials();
            });
        });
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        
        if (env.IsDevelopment())
        {
        	app.UseDeveloperExceptionPage();
        }
        app.UseCorrelationId();
        app.UseAbpSerilogEnrichers();
        app.UseCors();
        app.UseSwaggerUIWithYarp(context);
        
        app.UseRewriter(new RewriteOptions()
            // Regex for "", "/" and "" (whitespace)
            .AddRedirect("^(|\\|\\s+)$", "/swagger"));
            
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
        	endpoints.MapReverseProxy();
        });
    }
}

UseSwaggerUIWithYarp是从我们Yarp配置文件中读取服务信息去构造swagger路由配置。
好了,到这我们认证服务和网关服务也搭建完毕,下一篇我们开始迁移数据库。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK