项目

微服务解决方案:添加新的 API 网关

你必须拥有ABP商业版或更高级别的许可证才能创建微服务解决方案。

API网关是你的微服务系统的入口点。它们负责将传入的请求路由到正确的微服务。在一个微服务系统中,你可以拥有多个API网关来服务于不同的目的。例如,你可以拥有一个面向客户的公共API网关和一个面向内部服务的私有API网关。在解决方案模板中,有一个名为gateways的文件夹,其中包含API网关项目。你可以向此文件夹添加新的API网关。

此外,在根目录中有一个名为_templates的文件夹。此文件夹包含可用于创建新微服务、API网关和应用程序的模板。这些模板可以根据你的需求进行定制。

添加新的API网关

要向解决方案添加新的网关应用程序,你可以使用gateway模板。此模板会创建一个带有必要配置和依赖项的新ASP.NET Core应用程序。按照以下步骤添加新的Web应用程序:

在ABP Studio的解决方案资源管理器中,右键单击gateways文件夹,选择添加 -> 新模块 -> 网关

新网关应用程序

这将打开创建新模块对话框。输入新网关应用程序的名称,如果需要则指定输出目录,然后单击创建按钮。有一个命名约定:模块名称应包含解决方案名称作为前缀,并且模块名称中不允许使用点(.)字符。

创建新网关应用

新网关已创建并添加到解决方案中。你可以在gateways文件夹中看到新的应用程序。

网关应用

配置 appsettings.json

新网关应用程序使用YARP作为反向代理。你可以配置appsettings.json文件来定义路由和端点。appsettings.json文件中的ReverseProxy部分包含了反向代理的配置。你可以向此部分添加新的路由和端点。

{
  "ReverseProxy": {
    "Routes": {
      "AbpApi": {
        "ClusterId": "Administration",
        "Match": {
          "Path": "/api/abp/{**catch-all}"
        }
      },
      "AdministrationSwagger": {
        "ClusterId": "Administration",
        "Match": {
          "Path": "/swagger-json/Administration/swagger/v1/swagger.json"
        },
        "Transforms": [
          { "PathRemovePrefix": "/swagger-json/Administration" }
        ]
      },
      "ProductService": {
        "ClusterId": "ProductService",
        "Match": {
          "Path": "/api/productservice/{**catch-all}"
        }
      },
      "ProductServiceSwagger": {
        "ClusterId": "ProductService",
        "Match": {
          "Path": "/swagger-json/ProductService/swagger/v1/swagger.json"
        },
        "Transforms": [
          { "PathRemovePrefix": "/swagger-json/ProductService" }
        ]
      }
    },
    "Clusters": {
      "Administration": {
        "Destinations": {
          "Administration": {
            "Address": "http://localhost:44393/"
          }
        }
      },
      "ProductService": {
        "Destinations": {
          "ProductService": {
            "Address": "http://localhost:44350/"
          }
        }
      }
    }
  }
}

配置 OpenId 选项

我们应该通过修改Identity服务中的OpenIddictDataSeeder来配置OpenId选项。以下是PublicGateway应用程序的OpenIddictDataSeeder选项示例。

将创建的网关URL添加到OpenIddictDataSeeder类的CreateSwaggerClientAsync方法中的redirectUris参数中。

private async Task CreateSwaggerClientAsync(string clientId, string[] scopes)
{
    var webGatewaySwaggerRootUrl = _configuration["OpenIddict:Applications:WebGateway:RootUrl"]!.TrimEnd('/'); 
    //PublicGateway 的 Url
    var publicGatewaySwaggerRootUrl = _configuration["OpenIddict:Applications:PublicGateway:RootUrl"]!.TrimEnd('/');
    ...

    await CreateOrUpdateApplicationAsync(
        name: clientId,
        type:  OpenIddictConstants.ClientTypes.Public,
        consentType: OpenIddictConstants.ConsentTypes.Implicit,
        displayName: "Swagger测试客户端",
        secret: null,
        grantTypes: new List<string>
        {
            OpenIddictConstants.GrantTypes.AuthorizationCode,
        },
        scopes: commonScopes.Union(scopes).ToList(),
        redirectUris: new List<string> {
            $"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html",
            $"{publicGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // PublicGateway 的重定向 URI
            ...
        },
        clientUri: webGatewaySwaggerRootUrl,
        logoUri: "/images/clients/swagger.svg"
    );
}

将新的网关URL添加到Identity服务的appsettings.json文件中。

{
  "OpenIddict": {
    "Applications": {
      ...
      "PublicGateway": {
        "RootUrl": "http://localhost:44382"
      }
    }
  }
}

配置 AuthServer

我们应该为**CORS(跨域资源共享)RedirectAllowedUrls(允许的重定向URL)**配置AuthServer。

"App": {
  "SelfUrl": "http://localhost:***",
  "CorsOrigins": "...... ,http://localhost:44382",
  "EnablePII": false,
  "RedirectAllowedUrls": "...... ,http://localhost:44382"
}

为新网关创建 Helm 图表

如果你希望将新网关部署到Kubernetes,你应该为该新应用程序创建一个Helm图表。

首先,将新网关添加到etc/helm文件夹中的build-all-images.ps1脚本中。你可以复制现有应用程序的配置,并根据新应用程序进行修改。以下是PublicGateway应用程序的build-all-images.ps1脚本示例。

./build-image.ps1 -ProjectPath "../../gateways/public/Acme.Bookstore.PublicGateway/Acme.Bookstore.PublicGateway.csproj" -ImageName bookstore/publicgateway

由于我们希望在集群外部公开我们的网关,我们应该将主机URL添加到etc/helm/projectname文件夹中的values.projectname-local.yaml文件中。以下是PublicGateway应用程序的values.bookstore-local.yaml文件示例。

global:
  ...
  hosts:
    ...
    publicgateway: "[RELEASE_NAME]-publicgateway"

出于开发目的,我们还应该为新网关创建TLS证书。你可以编辑etc/helm文件夹中的create-tls-certificate.ps1脚本,为新网关生成TLS证书。以下是PublicGateway应用程序的create-tls-certificate.ps1脚本示例。

mkcert --cert-file bookstore-local.pem --key-file bookstore-local-key.pem "bookstore-local" ... "bookstore-local-publicgateway"
kubectl create namespace bookstore-local
kubectl create secret tls -n bookstore-local bookstore-local-tls --cert=./bookstore-local.pem --key=./bookstore-local-key.pem

最后,我们应该在etc/helm/projectname/templates文件夹中的*_helpers.tpl文件中定义新应用程序。你可以复制现有应用程序的配置,并根据新应用程序进行修改。以下是PublicGateway应用程序的_helpers.tpl*文件示例。

{{- define "bookstore.hosts.publicgateway" -}}
{{- print "https://" (.Values.global.hosts.publicgateway | replace "[RELEASE_NAME]" .Release.Name) -}}
{{- end -}}

之后,我们需要为新网关创建一个新的Helm图表。你可以复制现有应用程序的配置,并根据新网关进行修改。以下是PublicGateway应用程序的publicgateway Helm图表示例。

# values.yaml
image:
  repository: "bookstore/publicgateway"
  tag: "latest"
  pullPolicy: "IfNotPresent"
swagger:
  isEnabled: "true"

# Chart.yaml
apiVersion: v2
name: publicgateway
appVersion: "1.0"
description: Bookstore 公共 API 网关
version: 1.0.0
type: application

# publicapigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
  selector:
    matchLabels:
      app: "{{ .Release.Name }}-{{ .Chart.Name }}"
  template:
    metadata:
      labels:
        app: "{{ .Release.Name }}-{{ .Chart.Name }}"
    spec:
      containers:
      - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: "{{ .Values.image.pullPolicy }}"
        name: "{{ .Release.Name }}-{{ .Chart.Name }}"
        ports:
        - name: "http"
          containerPort: 80
        env:
        - name: "DOTNET_ENVIRONMENT"
          value: "{{ .Values.global.dotnetEnvironment }}"
        - name: "ElasticSearch__IsLoggingEnabled"
          value: "{{ .Values.global.elasticSearch.isLoggingEnabled }}"
        - name: "ElasticSearch__Url"
          value: "http://{{ .Release.Name }}-elasticsearch:{{ .Values.global.elasticSearch.port }}"
        - name: "Swagger__IsEnabled"
          value: "{{ .Values.swagger.isEnabled }}"
        - name: "AbpStudioClient__StudioUrl"
          value: "{{ .Values.global.abpStudioClient.studioUrl }}"
        - name: "AbpStudioClient__IsLinkEnabled"
          value: "{{ .Values.global.abpStudioClient.isLinkEnabled }}"
        - name: "ReverseProxy__Clusters__Administration__Destinations__Administration__Address"
          value: "http://{{ .Release.Name }}-administration"
        - name: "ReverseProxy__Clusters__ProductService__Destinations__ProductService__Address"
          value: "http://{{ .Release.Name }}-productservice"

# publicapigateway-service.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: "{{ .Release.Name }}-{{ .Chart.Name }}"
  name: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
  ports:
    - name: "80"
      port: 80
  selector:
    app: "{{ .Release.Name }}-{{ .Chart.Name }}"

# publicapigateway-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: "{{ .Release.Name }}-{{ .Chart.Name }}"
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: "/"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  ingressClassName: "nginx"
  tls:
  - hosts:
      - "{{ (include "bookstore.hosts.publicgateway" .) | trimPrefix "https://" }}"
    secretName: "{{ .Values.global.tlsSecret }}"
  rules:
  - host: "{{ (include "bookstore.hosts.publicgateway" .) | trimPrefix "https://" }}"
    http:
      paths:
      - path: /
        pathType: "Prefix"
        backend:
          service:
            name: "{{ .Release.Name }}-{{ .Chart.Name }}"
            port:
              number: 80

创建Helm图表后,你可以在ABP Studio中刷新子图表

kubernetes-刷新-子图表

然后,更新元数据信息,右键单击网关子图表,选择属性,这将打开图表属性窗口。你可以在元数据选项卡中进行编辑。

网关图表属性

图表属性 -> Kubernetes服务选项卡中添加服务名称的正则表达式模式。

网关图表属性-kubernetes-服务

最后但同样重要的是,我们需要为身份微服务和认证服务器应用程序配置Helm图表环境变量。

# identity.yaml
# 在 "env:" 部分添加这一行
- name: "OpenIddict__Applications__PublicGateway__RootUrl"
  value: "{{ include "bookstore.hosts.publicgateway" . }}"

# authserver.yaml
# 为 "App__CorsOrigins" 部分拼接以下行
- name: "App__CorsOrigins"
  value: "...,http://{{ .Release.Name }}-administration,{{ include "bookstore.hosts.publicgateway" . }}"

在本文档中