三、网关服务接入Nacos规范

服务注册nacos后;通过网关转发的服务都需要使用服务名称进行服务转发与请求;

1、引入的包

cs 复制代码
<PackageReference Include="MMLib.SwaggerForOcelot" Version="8.3.2" />
<PackageReference Include="nacos-sdk-csharp" Version="1.3.10" />
<PackageReference Include="nacos-sdk-csharp.AspNetCore" Version="1.3.10" />
<PackageReference Include="nacos-sdk-csharp.Extensions.Configuration" Version="1.3.10" />
<PackageReference Include="Ocelot" Version="22.0.1" />
<PackageReference Include="Ocelot.Provider.Nacos" Version="1.3.5" />

2、网关appsetting添加nacos的配置

服务名称ServiceName与DataId 必须小写

cs 复制代码
{
    "Nacos": {
        // 【配置中心监听配置】- 监听Nacos配置中心的指定配置集,项目启动后自动拉取该配置
        "Listeners": [
            {
                // 是否为可选监听:false=强制监听,配置拉取失败会影响项目启动;true=可选监听,拉取失败不影响
                "Optional": false,
                // 要监听的配置集DataId(唯一标识,和Nacos控制台配置一致)
                "DataId": "spc.gateway.ocelot",
                // 要监听的配置集分组(默认DEFAULT_GROUP,和Nacos控制台配置一致)
                "Group": "DEFAULT_GROUP"
            },
            {
                // 是否为可选监听:false=强制监听,配置拉取失败会影响项目启动;true=可选监听,拉取失败不影响
                "Optional": false,
                // 要监听的配置集DataId(唯一标识,和Nacos控制台配置一致)
                "DataId": "spc.gateway.common",
                // 要监听的配置集分组(默认DEFAULT_GROUP,和Nacos控制台配置一致)
                "Group": "DEFAULT_GROUP"
            }
        ],
        // 【Nacos服务端地址】- 配置中心+注册中心的服务端地址,多个地址用数组形式["ip1:port1", "ip2:port2"]
        "ServerAddresses": [ "http://172.16.34.40:8848/" ],
        // 【Nacos登录账号】- Nacos控制台配置的用户名(若未开启鉴权可留空,开启后必须填)
        "UserName": "nacos",
        // 【Nacos登录密码】- Nacos控制台配置的密码(若未开启鉴权可留空,开启后必须填)
        "Password": "nacos",
        // 【Nacos命名空间ID】- 隔离不同环境的配置/服务,留空=默认命名空间(public),填控制台的命名空间ID(非名称)
        "Namespace": "",
        // 【服务注册分组】- 当前服务注册到Nacos的分组,默认DEFAULT_GROUP,可用于环境隔离(如DEV/PROD)
        "GroupName": "DEFAULT_GROUP",
        // 【服务集群名称】- 当前服务所属集群,默认DEFAULT,多集群部署时填写(如SH/BEIJING)
        "ClusterName": "DEFAULT",
        // 【服务名称】- 注册到Nacos的服务唯一标识,网关/其他服务通过该名称发现当前服务,不可重复
        "ServiceName": "gatewayservice",
        // 【Nacos请求默认超时时间】- 调用Nacos服务端接口(注册/拉取配置)的默认超时时间,单位毫秒
        "DefaultTimeOut": 15000,
        // 【Nacos心跳监听间隔】- 服务注册后,Nacos客户端监听服务端心跳的间隔时间,单位毫秒
        "ListenInterval": 1000,
        // 【配置中心是否使用RPC协议】- 1.3.10版本建议false,使用HTTP协议,兼容Nacos2.x服务端
        "ConfigUseRpc": false,
        // 【注册中心是否使用RPC协议】- 1.3.10版本建议false,使用HTTP协议,兼容Nacos2.x服务端
        "NamingUseRpc": false,
        // 【是否启用配置中心缓存】- 开启后拉取的配置会缓存到本地,避免Nacos服务端不可用时项目启动失败
        "EnableRemoteConfigCache": true,
        // 【配置中心自动刷新间隔】- 客户端主动拉取Nacos配置中心最新配置的间隔时间,单位毫秒
        "RefreshInterval": 5000,
        // 【服务注册IP】- 1.3.10版本核心配置,直接根节点(源码可读取),填宿主机IP(供其他服务调用)
        "Ip": "172.16.34.40",
        // 【服务注册端口】- 1.3.10版本核心配置,直接根节点(源码可读取),填宿主机映射端口,和docker-compose的端口映射一致
        "Port": 55000,
        // 【是否启用服务注册】- true=启动时自动注册到Nacos,false=仅订阅服务,不注册自身
        "RegisterEnabled": true,
        // 【实例是否启用】- true=实例可用,接受网关/其他服务的调用;false=实例不可用,会被Nacos剔除
        "InstanceEnabled": true,
        // 【服务实例权重】- 负载均衡时的权重,值越大被调用概率越高,默认100.0,范围0-1000
        "Weight": 100.0,
        // 【是否为临时实例】- true=临时实例,服务下线后Nacos会自动剔除;false=持久化实例,需手动注销
        "Ephemeral": true,
        // 【是否为HTTPS服务】- true=当前服务是HTTPS协议,false=HTTP协议,影响其他服务调用的协议
        "Secure": false,
        // 【首选网卡IP匹配前缀】- 1.3.10版本留空即可,避免UriTool自动探测网卡IP覆盖配置的Ip,多个前缀用逗号分隔(如192.168.,172.16.)
        "PreferredNetworks": "",
        // 【服务实例元数据】- 自定义键值对,可传递服务额外信息(如健康检查地址、版本号),Nacos控制台可查看
        "Metadata": {
            // 健康检查地址 - Nacos服务端根据该地址检测实例是否健康,需和项目健康检查接口一致
            "health-check-url": "/Health",
            // 实例状态 - 自定义状态标识,供其他服务做业务判断(可删,不影响核心功能)
            "status": "UP"
        }
    }
}

3、spc.gateway.ocelot模板格式如下:

服务名称必须使用小写

复制代码
{
    "Routes": [
        // plantdataservice
        {
            "DownstreamPathTemplate": "/api/app/{everything}",
            "DownstreamScheme": "http",
            "ServiceName": "plantdataservice",
            "LoadBalancerOptions": {
                "Type": "RoundRobin"
            },
            "UseServiceDiscovery": true,
            "UpstreamPathTemplate": "/api/plantData/{everything}",
            "UpstreamHttpMethod": [],
            "SwaggerKey": "plantdataservice"
        }
    ],
    "SwaggerEndPoints": [
        {
            "Key": "plantdataservice",
            "Config": [
                {
                    "Name": "plantdataservice API",
                    "Version": "v1",
                    "Service": {
                        "Name": "plantdataservice",
                        "Path": "swagger/v1/swagger.json"
                    }
                }
            ]
        }
    ],
    "GlobalConfiguration": {
        "BaseUrl": "http://172.16.34.40:55000",
        "ServiceDiscoveryProvider": {
            "Type": "Nacos",
            "Host": "172.16.34.40",
            "Port": 8848,
            "Namespace": "",
            "UserName": "nacos",
            "Password": "nacos",
            "PollingInterval": 10000
        }
    }
}

4、spc.gateway.common配置如下:

复制代码
{
    "App": {
        "CorsOrigins": "https://*.PlanManagementService.com,http://localhost:4200,http://localhost:44307,https://localhost:44307,http://*:8181"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "SwaggerAuth": {
        "isenable": true,
        "authurl": "http://172.16.34.22:55001/connect/token",
        "client_id": "PlantDataService_App",
        "grant_type": "password",
        "scope": "offline_access",
        "response_type": "refresh_token"
    },
    "License": {
        "Path": "licenses/dev.license",
        "PublicKeyPath": "licenses/public.xml"
    }
}

5、program.cs代码如下:

复制代码
using Nacos.AspNetCore.V2;
using Newtonsoft.Json;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Nacos;
using SPC.Gateway.License.Core;
using SPC.WebGateway.Until;

#region 全局配置
var AllowSpecificOrigins = "_allowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
#endregion

#region 【新增全局变量】用于存储已校验的证书实例
RuntimeLicenseGuard? GlobalLicenseGuard = null;
LicenseValidator? GlobalLicenseValidator = null;
#endregion

#region 加载Nacos配置
builder.Configuration.AddNacosV2Configuration(builder.Configuration.GetSection("Nacos"));
#endregion

#region CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: AllowSpecificOrigins,
        policy =>
        {
            policy.AllowAnyOrigin()
             .SetIsOriginAllowedToAllowWildcardSubdomains()
             .AllowAnyHeader()
             .AllowAnyMethod();
        });
});
#endregion

builder.Services.AddHttpClient();
builder.Services.AddMemoryCache();

#region Ocelot + Nacos整合
builder.Services.AddOcelot().AddNacosDiscovery();
builder.Services.AddNacosAspNet(builder.Configuration, "Nacos");
#endregion

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerForOcelot(builder.Configuration, opt =>
{
    opt.GenerateDocsForGatewayItSelf = true;
});
builder.Services.AddSwaggerGen();

#region 构建应用实例
var app = builder.Build();
#endregion

#region
var licensePath =
    Environment.GetEnvironmentVariable("LICENSE_PATH") ??
    app.Configuration["License:Path"] ??
    "/app/license/dev.license";

var publicKeyPath =
    Environment.GetEnvironmentVariable("LICENSE_PUBLIC_KEY_PATH") ??
    app.Configuration["License:PublicKeyPath"] ??
    "/app/license/public.xml";

if (!File.Exists(publicKeyPath))
    throw new Exception("Public key file not found: " + publicKeyPath);

Console.WriteLine($"✅ 最终使用的证书路径:{licensePath}");
Console.WriteLine($"✅ 最终使用的公钥路径:{publicKeyPath}");

var publicKeyXml = File.ReadAllText(publicKeyPath);
if (string.IsNullOrWhiteSpace(publicKeyXml))
    throw new Exception("Public key content is empty.");

GlobalLicenseGuard = new RuntimeLicenseGuard(licensePath, publicKeyXml);
GlobalLicenseValidator = new LicenseValidator(licensePath, publicKeyXml);
GlobalLicenseGuard.StartupCheck(out var licenseData, out var error);
#endregion

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

app.UseRouting();
app.UseCors(AllowSpecificOrigins);
app.UseSwaggerAuthorized();
app.UseSwagger();

#region 
app.Use(async (context, next) =>
{
    var guard = GlobalLicenseGuard!;
    if (!guard.RequestCheck(out var err))
    {
        if (context.Request.Path.Value!.ToLowerInvariant()
                      .Contains("auth".ToLowerInvariant()))
        {
            await HandleExceptionAsync(context, "非法请求,未通过校验");
            return;
        }
    }
    await next(context);
});
#endregion

app.Use(async (context, next) =>
{
    if (context.Request.Path.HasValue
                && (context.Request.Path.Value.IndexOf("SignalR", StringComparison.InvariantCultureIgnoreCase) > -1
                || context.Request.Path.Value.IndexOf("Swagger", StringComparison.InvariantCultureIgnoreCase) > -1))
    {
        await next(context);
        return;
    }
    #region Real-IP
    string? ipAddress = context?.Connection?.RemoteIpAddress?.ToString();
    if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
    {
        ipAddress = context.Request.Headers["X-Forwarded-For"].ToString().Split(',')[0].Trim();
    }
    if (!string.IsNullOrEmpty(ipAddress))
    {
        context.Request.Headers["X-Real-IP"] = ipAddress;
    }
    else
    {
        context.Request.Headers.Add("X-Real-IP", "Unknown");
    }
    #endregion
    await next(context);
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSwaggerForOcelotUI();
app.UseOcelot().Wait();
app.Run();


#region 工具方法
static async Task HandleExceptionAsync(HttpContext context, string errorText)
{
    var response = context.Response;
    response.ContentType = "application/json";
    response.StatusCode = 403;
    await response.WriteAsync(JsonConvert.SerializeObject(errorText));
}
#endregion

6、服务必须添加一个控制器代码如下:

复制代码
[ApiController]
public class DefaultController : Controller
{
    [HttpGet("Health")]
    public IActionResult Health()
    {
        return Ok();
    }
}

7、网关的nacos配置文件有两个

3、spc.gateway.ocelot模板格式如下:

服务名称必须使用小写

复制代码
{
    "Routes": [
        // plantdataservice
        {
            "DownstreamPathTemplate": "/api/app/{everything}",
            "DownstreamScheme": "http",
            "ServiceName": "plantdataservice",
            "LoadBalancerOptions": {
                "Type": "RoundRobin"
            },
            "UseServiceDiscovery": true,
            "UpstreamPathTemplate": "/api/plantData/{everything}",
            "UpstreamHttpMethod": [],
            "SwaggerKey": "plantdataservice"
        }
    ],
    "SwaggerEndPoints": [
        {
            "Key": "plantdataservice",
            "Config": [
                {
                    "Name": "plantdataservice API",
                    "Version": "v1",
                    "Service": {
                        "Name": "plantdataservice",
                        "Path": "swagger/v1/swagger.json"
                    }
                }
            ]
        }
    ],
    "GlobalConfiguration": {
        "BaseUrl": "http://172.16.34.40:55000",
        "ServiceDiscoveryProvider": {
            "Type": "Nacos",
            "Host": "172.16.34.40",
            "Port": 8848,
            "Namespace": "",
            "UserName": "nacos",
            "Password": "nacos",
            "PollingInterval": 10000
        }
    }
}

4、spc.gateway.common配置如下:

复制代码
{
    "App": {
        "CorsOrigins": "https://*.PlanManagementService.com,http://localhost:4200,http://localhost:44307,https://localhost:44307,http://*:8181"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "SwaggerAuth": {
        "isenable": true,
        "authurl": "http://172.16.34.22:55001/connect/token",
        "client_id": "PlantDataService_App",
        "grant_type": "password",
        "scope": "offline_access",
        "response_type": "refresh_token"
    },
    "License": {
        "Path": "licenses/dev.license",
        "PublicKeyPath": "licenses/public.xml"
    }
}

5、program.cs代码如下:

复制代码
using Nacos.AspNetCore.V2;
using Newtonsoft.Json;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Nacos;
using SPC.Gateway.License.Core;
using SPC.WebGateway.Until;

#region 全局配置
var AllowSpecificOrigins = "_allowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
#endregion

#region 【新增全局变量】用于存储已校验的证书实例
RuntimeLicenseGuard? GlobalLicenseGuard = null;
LicenseValidator? GlobalLicenseValidator = null;
#endregion

#region 加载Nacos配置
builder.Configuration.AddNacosV2Configuration(builder.Configuration.GetSection("Nacos"));
#endregion

#region CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: AllowSpecificOrigins,
        policy =>
        {
            policy.AllowAnyOrigin()
             .SetIsOriginAllowedToAllowWildcardSubdomains()
             .AllowAnyHeader()
             .AllowAnyMethod();
        });
});
#endregion

builder.Services.AddHttpClient();
builder.Services.AddMemoryCache();

#region Ocelot + Nacos整合
builder.Services.AddOcelot().AddNacosDiscovery();
builder.Services.AddNacosAspNet(builder.Configuration, "Nacos");
#endregion

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

builder.Services.AddSwaggerForOcelot(builder.Configuration, opt =>
{
    opt.GenerateDocsForGatewayItSelf = true;
});
builder.Services.AddSwaggerGen();

#region 构建应用实例
var app = builder.Build();
#endregion

#region
var licensePath =
    Environment.GetEnvironmentVariable("LICENSE_PATH") ??
    app.Configuration["License:Path"] ??
    "/app/license/dev.license";

var publicKeyPath =
    Environment.GetEnvironmentVariable("LICENSE_PUBLIC_KEY_PATH") ??
    app.Configuration["License:PublicKeyPath"] ??
    "/app/license/public.xml";

if (!File.Exists(publicKeyPath))
    throw new Exception("Public key file not found: " + publicKeyPath);

Console.WriteLine($"✅ 最终使用的证书路径:{licensePath}");
Console.WriteLine($"✅ 最终使用的公钥路径:{publicKeyPath}");

var publicKeyXml = File.ReadAllText(publicKeyPath);
if (string.IsNullOrWhiteSpace(publicKeyXml))
    throw new Exception("Public key content is empty.");

GlobalLicenseGuard = new RuntimeLicenseGuard(licensePath, publicKeyXml);
GlobalLicenseValidator = new LicenseValidator(licensePath, publicKeyXml);
GlobalLicenseGuard.StartupCheck(out var licenseData, out var error);
#endregion

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

app.UseRouting();
app.UseCors(AllowSpecificOrigins);
app.UseSwaggerAuthorized();
app.UseSwagger();

#region 
app.Use(async (context, next) =>
{
    var guard = GlobalLicenseGuard!;
    if (!guard.RequestCheck(out var err))
    {
        if (context.Request.Path.Value!.ToLowerInvariant()
                      .Contains("auth".ToLowerInvariant()))
        {
            await HandleExceptionAsync(context, "非法请求,未通过校验");
            return;
        }
    }
    await next(context);
});
#endregion

app.Use(async (context, next) =>
{
    if (context.Request.Path.HasValue
                && (context.Request.Path.Value.IndexOf("SignalR", StringComparison.InvariantCultureIgnoreCase) > -1
                || context.Request.Path.Value.IndexOf("Swagger", StringComparison.InvariantCultureIgnoreCase) > -1))
    {
        await next(context);
        return;
    }
    #region Real-IP
    string? ipAddress = context?.Connection?.RemoteIpAddress?.ToString();
    if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
    {
        ipAddress = context.Request.Headers["X-Forwarded-For"].ToString().Split(',')[0].Trim();
    }
    if (!string.IsNullOrEmpty(ipAddress))
    {
        context.Request.Headers["X-Real-IP"] = ipAddress;
    }
    else
    {
        context.Request.Headers.Add("X-Real-IP", "Unknown");
    }
    #endregion
    await next(context);
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSwaggerForOcelotUI();
app.UseOcelot().Wait();
app.Run();


#region 工具方法
static async Task HandleExceptionAsync(HttpContext context, string errorText)
{
    var response = context.Response;
    response.ContentType = "application/json";
    response.StatusCode = 403;
    await response.WriteAsync(JsonConvert.SerializeObject(errorText));
}
#endregion

6、服务必须添加一个控制器代码如下:

复制代码
[ApiController]
public class DefaultController : Controller
{
    [HttpGet("Health")]
    public IActionResult Health()
    {
        return Ok();
    }
}

7、网关的nacos配置文件有两个

8、注入的服务截图

相关推荐
安科士andxe9 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
小白同学_C12 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖12 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
2601_9491465312 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
儒雅的晴天12 小时前
大模型幻觉问题
运维·服务器
Gofarlic_OMS13 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
通信大师13 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
dixiuapp14 小时前
智能工单系统如何选,实现自动化与预测性维护
运维·自动化
不做无法实现的梦~14 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
Elastic 中国社区官方博客14 小时前
如何防御你的 RAG 系统免受上下文投毒攻击
大数据·运维·人工智能·elasticsearch·搜索引擎·ai·全文检索