C#/.NET 微服务架构:从入门到精通(五):配置Swagger文档+接口版本控制

前一篇我们完成了 EF Core + MySQL 的数据持久化落地,为微服务提供了数据层支撑。而在微服务开发中,接口文档管理与接口版本控制是提升协作效率、保证系统兼容性的关键 —— 微服务接口数量多、迭代快,清晰的文档能让前后端 / 跨团队协作更顺畅,版本控制则能避免接口升级对旧客户端的影响。

本篇将聚焦Swagger 文档配置与接口版本控制的实战集成,从基础搭建、扩展优化到多版本管理,手把手实现微服务接口的规范化管理。

一、微服务架构下的 Swagger 与接口版本控制

1. 为什么微服务需要 Swagger?

微服务由多个独立服务组成,每个服务暴露大量 API 接口,Swagger 的价值在于:

  • 自动生成文档:无需手动维护接口文档,代码即文档;
  • 在线调试接口:内置 UI 界面,直接测试接口请求 / 响应;
  • 团队协作友好:前后端、跨服务团队通过统一文档对接,减少沟通成本;
  • 支持认证集成:可配置 JWT/Token 等认证方式,直接测试需授权的接口。

2. 为什么微服务需要接口版本控制?

微服务接口迭代频繁(如新增字段、调整逻辑),版本控制能:

  • 保证兼容性:旧版本客户端不受新版本接口影响;
  • 支持并行迭代:多个版本接口可同时运行,平滑过渡;
  • 规范接口管理:明确版本生命周期,避免接口混乱。

二、实战步骤 1:Swagger 文档基础配置

我们以商品Api(ProductApi) 为例,演示 Swagger 的完整配置。

1. 安装必需的 NuGet 包

在ProductApi项目中安装 Swashbuckle(.NET 官方推荐的 Swagger 工具):

dotnet add package Swashbuckle.AspNetCore --version 6.4.0

image

 2. 基础配置:Program.cs 中注册 Swagger

修改Program.cs,在.NET 8 的顶级语句中,添加 Swagger 服务与中间件:

using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using ProductApi.DbContexts;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
//注册Swagger生成器
builder.Services.AddSwaggerGen(c =>
{
    // 配置Swagger文档基本信息
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "商品 API",
        Version = "v1",
        Description = "基于.NET 8的微服务商品接口文档",
        Contact = new OpenApiContact
        {
            Name = "挺秃然的i",
            Url = new Uri("https://www.cnblogs.com/lantingxu")
        }
    });
});

//注册EF Core DbContext(连接MySQL)
var connectionString = builder.Configuration.GetConnectionString("ProductDb");
builder.Services.AddDbContext<ProductDbContext>(options =>
{
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString),
        mysqlOpt => mysqlOpt.MigrationsAssembly(typeof(ProductDbContext).Assembly.FullName));
});


var app = builder.Build();

//配置Swagger中间件(仅在开发环境启用,生产环境可关闭)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger(); // 生成Swagger JSON端点(/swagger/v1/swagger.json)
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "商品 API v1");
        c.RoutePrefix = "swagger"; // 将Swagger UI设为swagger路径(访问http://localhost:端口/swagger即可打开)
    });
}

app.UseAuthorization();

app.MapControllers();

app.Run();

3. 验证 Swagger

运行ProductApi,访问http://localhost:你的端口/swagger,即可看到 Swagger UI 界面,显示所有 API 接口。

image

 三、实战步骤 2:Swagger 扩展优化(XML 注释 + JWT 认证)

基础 Swagger 仅显示接口结构,我们需要添加XML 注释让文档更详细,以及JWT 认证配置支持测试需授权的接口。

1. 配置 XML 注释

步骤 1:启用项目 XML 文档生成

右键用户服务项目 → 选择 “属性” → “生成” 选项卡 → 勾选 “XML 文档文件”,路径默认即可。

步骤 2:在 Swagger 中引入 XML 注释

修改AddSwaggerGen配置:

//注册Swagger生成器
builder.Services.AddSwaggerGen(c =>
{
    // 配置Swagger文档基本信息
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "商品 API",
        Version = "v1",
        Description = "基于.NET 8的微服务商品接口文档",
        Contact = new OpenApiContact
        {
            Name = "挺秃然的i",
            Url = new Uri("https://www.cnblogs.com/lantingxu")
        }
    });

    // 引入控制器XML注释
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath, true); // true表示包含控制器注释
});

步骤 3:给接口添加 XML 注释示例

ProductController中添加注释:

image

步骤 4:验证Swagger

运行ProductApi,访问http://localhost:你的端口/swagger,即可看到 Swagger UI 界面,显示所有 API 接口。

image

 2. 配置 JWT 认证支持(微服务常用)

如果微服务接口需要 JWT 授权,需在 Swagger 中添加 “小锁” 图标,支持输入 Token 测试,修改AddSwaggerGen配置:

//注册Swagger生成器
builder.Services.AddSwaggerGen(c =>
{
    // 配置Swagger文档基本信息
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "商品 API",
        Version = "v1",
        Description = "基于.NET 8的微服务商品接口文档",
        Contact = new OpenApiContact
        {
            Name = "挺秃然的i",
            Url = new Uri("https://www.cnblogs.com/lantingxu")
        }
    });

    // 引入控制器XML注释
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath, true); // true表示包含控制器注释

    // 启用JWT授权
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "请输入JWT Token(格式:Bearer {Token})",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    // 全局应用JWT认证要求
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
});

验证swagger

image

 四、实战步骤 3:接口版本控制配置

微服务接口迭代时,需通过版本控制保证兼容性,我们使用微软官方最新的Asp.Versioning.Mvc包实现。

1. 安装必需的 NuGet 包

# API版本控制核心包
dotnet add package Asp.Versioning.Mvc --version 8.1.0
# 版本探索器
dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 8.1.0

image

 2. 配置版本控制服务

Program.cs中注册版本控制:

/ 注册接口版本控制
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0); // 默认版本v1.0
    options.AssumeDefaultVersionWhenUnspecified = true; // 未指定版本时使用默认版本
    options.ReportApiVersions = true; // 在响应头中返回支持的版本
    // 配置版本读取方式(支持多种方式,这里演示URL路径方式,最直观)
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader() // URL路径版本:/api/v1/User
                                         // 也可添加查询字符串方式:/api/User?api-version=1.0
                                         // new QueryStringApiVersionReader("api-version"),
                                         // 或请求头方式:Header中添加Api-Version: 1.0
                                         // new HeaderApiVersionReader("Api-Version")
    );
})
//注册版本探索器(与Swagger集成必需)
.AddMvc()
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV"; // 版本组名格式:v1、v1.1
    options.SubstituteApiVersionInUrl = true; // 替换URL中的版本占位符
});

image

 3. 创建多版本控制器示例

调整一下Controllers文件夹,创建两个版本的产品控制器,演示版本隔离:

V1版本的ProductController:

using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ProductApi.DbContexts;
using ProductApi.Entities;

namespace ProductApi.Controllers.V1;

/// <summary>
/// 商品控制器v1版本
/// </summary>
[ApiVersion(1.0)] // 标记版本v1.0
[Route("api/v{version:apiVersion}/[controller]")] // URL路径包含版本
[ApiController]
public class ProductController : ControllerBase
{
    private readonly ProductDbContext _context;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="context"></param>
    public ProductController(ProductDbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// 获取所有商品
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        // AsNoTracking:查询优化,不跟踪实体状态(微服务读多写少场景推荐)
        return await _context.Products.AsNoTracking().ToListAsync();
    }

    /// <summary>
    /// 获取单个商品
    /// </summary>
    /// <param name="id">主键Id</param>
    /// <returns></returns>
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(long id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null) return NotFound();
        return product;
    }
}

V2版本的ProductController:

using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ProductApi.DbContexts;
using ProductApi.Entities;

namespace ProductApi.Controllers.V2;

/// <summary>
/// 商品控制器v2版本
/// </summary>
[ApiVersion(2.0)] // 标记版本v2.0
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly ProductDbContext _context;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="context"></param>
    public ProductController(ProductDbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// 获取所有商品
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        // AsNoTracking:查询优化,不跟踪实体状态(微服务读多写少场景推荐)
        return await _context.Products.AsNoTracking().ToListAsync();
    }

    /// <summary>
    /// 获取单个商品
    /// </summary>
    /// <param name="id">主键Id</param>
    /// <returns></returns>
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(long id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null) return NotFound();
        return product;
    }

    /// <summary>
    /// 新增商品
    /// </summary>
    /// <param name="product">商品</param>
    /// <returns></returns>
    [HttpPost]
    public async Task<ActionResult<Product>> PostProduct(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    /// <summary>
    /// 更新商品
    /// </summary>
    /// <param name="id">主键Id</param>
    /// <param name="product">商品</param>
    /// <returns></returns>
    [HttpPut("{id}")]
    public async Task<IActionResult> PutProduct(long id, Product product)
    {
        if (id != product.Id) return BadRequest();
        _context.Entry(product).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!ProductExists(id)) return NotFound();
            else throw;
        }
        return NoContent();
    }

    /// <summary>
    /// 删除商品
    /// </summary>
    /// <param name="id">主键Id</param>
    /// <returns></returns>
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(long id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null) return NotFound();

        // 软删除:仅标记IsDeleted,不物理删除
        product.IsDeleted = true;
        await _context.SaveChangesAsync();
        return NoContent();
    }

    private bool ProductExists(long id)
    {
        return _context.Products.Any(e => e.Id == id);
    }
}

最终拆分:

image

 五、实战步骤 4:Swagger 与多版本控制集成

让 Swagger 自动识别并显示不同版本的接口,修改 Swagger 配置:

1. 修改 Swagger 生成器配置

Program.cs中,通过IApiVersionDescriptionProvider动态生成多版本 Swagger 文档:

// 动态获取所有版本信息,生成对应的SwaggerDoc
using var scope = builder.Services.BuildServiceProvider().CreateScope();
var provider = scope.ServiceProvider.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
    c.SwaggerDoc(description.GroupName, new OpenApiInfo
    {
        Title = $"商品 API {description.GroupName}",
        Version = description.GroupName,
        Description = description.IsDeprecated ? "该版本已废弃" : "当前稳定版本",
        Contact = new OpenApiContact
        {
            Name = "挺秃然的i",
            Url = new Uri("https://www.cnblogs.com/lantingxu")
        }
    });
}

image

 2. 修改 Swagger UI 配置

让 Swagger UI 显示多版本切换下拉框:

//配置Swagger中间件(仅在开发环境启用,生产环境可关闭)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger(); // 生成Swagger JSON端点(/swagger/v1/swagger.json)
    app.UseSwaggerUI(c =>
    {
        //c.SwaggerEndpoint("/swagger/v1/swagger.json", "商品 API v1");

        // 动态获取所有版本,配置Swagger端点
        var provider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
        foreach (var description in provider.ApiVersionDescriptions)
        {
            c.SwaggerEndpoint(
                $"/swagger/{description.GroupName}/swagger.json",
                $"商品 API {description.GroupName}"
            );
        }
        c.RoutePrefix = "swagger"; // 将Swagger UI设为swagger路径(访问http://localhost:端口/swagger即可打开)
    });
}

image

 3. 验证多版本 Swagger

运行服务,访问 Swagger UI,右上角会出现版本下拉框,可切换 v1、v2 版本查看对应接口。

image

 

image

 六、最佳实践

1. XML 注释规范

  • 所有控制器、接口、参数、返回值必须添加注释;
  • 复杂实体也需在属性上添加注释,Swagger 会自动显示在模型说明中

2. 接口版本管理

  • 版本号规则:使用语义化版本(MAJOR.MINOR),如 v1、v1.1,仅在不兼容变更时升级 MAJOR 版本;
  • 废弃策略:旧版本接口用[ApiVersion(1.0, Deprecated = true)]标记,Swagger 会显示 “已废弃” 提示;
  • 过渡周期:保留旧版本至少 1-2 个迭代周期,给客户端足够时间升级。

3. 生产环境 Swagger 配置

  • 限制访问:生产环境建议关闭 Swagger,或通过身份认证(如添加中间件验证请求头)限制仅内部网络访问;
  • 环境隔离:通过app.Environment.IsDevelopment()判断,仅在开发 / 测试环境启用 Swagger UI。

七、常见问题解决

1. XML 注释不显示

  • 检查项目属性是否勾选 “XML 文档文件”;
  • 确认IncludeXmlComments的路径正确(使用AppContext.BaseDirectory获取运行时路径);
  • 实体类的 XML 注释需在IncludeXmlComments中设置第二个参数为true

2. Swagger 找不到多版本接口

  • 检查控制器的[ApiVersion][Route]特性是否正确(Route 中需包含v{version:apiVersion});
  • 确认AddApiExplorerSubstituteApiVersionInUrl设为true
  • 检查 Swagger 生成器中是否通过IApiVersionDescriptionProvider动态配置了SwaggerDoc

3. 版本控制冲突

  • 避免同时使用多种版本读取方式(如 URL 路径 + 查询字符串),优先用 URL 路径方式(最直观);
  • 确保所有控制器都标记了[ApiVersion],未标记的会使用默认版本。

八、总结

本篇我们完成了Swagger 文档配置与接口版本控制的全流程集成:

  1. 搭建 Swagger 基础环境,实现接口文档自动生成与在线调试;
  2. 扩展 Swagger 配置,添加 XML 注释让文档更详细,集成 JWT 认证支持测试授权接口;
  3. 配置接口版本控制,通过 URL 路径方式实现多版本隔离;
  4. 实现 Swagger 与多版本控制的集成,支持在 UI 中切换查看不同版本的接口;
  5. 梳理了 XML 注释规范、版本管理策略、生产环境配置等最佳实践。
规范化的接口文档与版本控制,是微服务团队协作与系统稳定迭代的重要保障。
posted @ 2026-04-28 16:30  挺秃然的i  阅读(73)  评论(0)    收藏  举报