API 版本控制:使用 ABP vNext 实现版本化 API 系统

🚀API 版本控制:使用 ABP vNext 实现版本化 API 系统


📚 目录

  • 🚀API 版本控制:使用 ABP vNext 实现版本化 API 系统
    • 一、背景切入 🧭
    • 二、核心配置规则 📋
      • 2.1 前置准备:NuGet 包与 `using` 📦
      • 2.2 启用版本化服务 🔧
        • 2.1.1 版本解析流程图 🗺️
        • 2.1.2 `RemoveVersionFromParameter` 实现 🛠️
        • 2.1.3 `ReplaceVersionWithExactValueInPath` 实现 🔄
      • 2.2 声明支持的版本 📝
      • 2.3 客户端代理与 Swagger 分组生成 ⚙️
        • 2.3.1 手动执行命令
        • 2.3.2 自动生成配置
        • 2.3.3 Swagger 文档生成流程图 📊
    • 三、实战演示 🎬
      • 3.1 URL Segment 模式(示例一:同类分支)🔀
      • 3.2 URL Segment 模式(示例二:拆分控制器)✂️
      • 3.3 QueryString 模式 🔍
      • 3.4 Header 模式 📬
      • 3.5 三种模式对比表 📋


一、背景切入 🧭

在需求快速迭代的时代,API 不再是一次性设计后一劳永逸的产物。为了保证旧版本客户端继续运行,同时平滑引入新功能,API 版本化(API Versioning) 就成为了一项必不可少的技术手段。🔧

ABP vNext 依托 ASP.NET Core 的版本化机制,提供了高强度可配置、完善体系化的版本控制解决方案。其底层实质是对 Microsoft.AspNetCore.Mvc.Versioning 的一层封装,在 ABP 框架下可以一行代码启用版本化,同时兼容 ABP 模块化、依赖注入等特性。本文将结合实际场景,详细讲解如何配置 ABP vNext 的 API 版本化支持,并通过实战代码展示各种版本读取方式与 Swagger 分组生成。✨


二、核心配置规则 📋

2.1 前置准备:NuGet 包与 using 📦

在开始配置之前,请先确保项目已经添加了以下 NuGet 包(示例版本号可根据实际情况调整)——直接在 .csproj 文件中加入下面的 <PackageReference>,或通过 dotnet add package 命令安装:

<PackageReference Include="Volo.Abp.AspNetCore.Mvc.Versioning" Version="4.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />

在代码文件顶部,需要引用以下命名空间,确保示例能够正常编译:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Versioning;
using Volo.Abp.Modularity;
using System.Linq;

2.2 启用版本化服务 🔧

YourProject.HttpApi.Host 项目的 YourProjectHttpApiHostModule 中,覆盖 ConfigureServices 方法,调用 AddAbpApiVersioning 配置版本化选项。同时配置 Swagger 分组以生成多版本文档。示例如下:

namespace YourProject.HttpApi.Host
{[DependsOn(typeof(AbpAspNetCoreMvcModule),typeof(AbpAspNetCoreMvcVersioningModule)  // 自动引入 Microsoft.AspNetCore.Mvc.Versioning)]public class YourProjectHttpApiHostModule : AbpModule{public override void ConfigureServices(ServiceConfigurationContext context){// 注册 API 版本化服务context.Services.AddAbpApiVersioning(options =>{// 默认使用 1.0 版本options.DefaultApiVersion = new ApiVersion(1, 0);// 如果未指定版本,采用默认版本options.AssumeDefaultVersionWhenUnspecified = true;// 返回当前支持的版本信息到响应头 (api-supported-versions)options.ReportApiVersions = true;// 支持 URL Segment、QueryString、Header 三种方式读取版本// 顺序决定优先级:先按 URL Segment,再按 QueryString,最后按 Headeroptions.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),           // /api/v1.0/...new QueryStringApiVersionReader("v"),       // /api/... ?v=1.0new HeaderApiVersionReader("x-api-version") // Header: x-api-version: 1.0);});// 注册 Swagger 分组支持context.Services.AddSwaggerGen(options =>{// 为每个版本定义一个 Swagger 文档options.SwaggerDoc("v1.0", new OpenApiInfo{Title = "Your API V1.0",Version = "v1.0"});options.SwaggerDoc("v2.0", new OpenApiInfo{Title = "Your API V2.0",Version = "v2.0"});// 按 GroupName 过滤 APIoptions.DocInclusionPredicate((docName, apiDesc) =>{// 如果没有 ApiVersionAttribute,也不是中立版本,则不包含var hasVersionAttribute = apiDesc.CustomAttributes().OfType<ApiVersionAttribute>().Any();var isNeutral = apiDesc.CustomAttributes().OfType<ApiVersionNeutralAttribute>().Any();if (isNeutral){// 将中立版本也展示在所有文档中return true;}if (!hasVersionAttribute){return false;}// 取得所有标注的版本号,例如 "1.0", "2.0"var versions = apiDesc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).Select(v => $"v{v.ToString()}");// 只包含与当前 docName(如 "v1.0")匹配的 APIreturn versions.Contains(docName);});// 移除版本参数(避免在 Swagger UI 中显示 {version} 占位符)options.OperationFilter<RemoveVersionFromParameter>();// 将路径中的 {version:apiVersion} 占位符替换为具体版本号options.DocumentFilter<ReplaceVersionWithExactValueInPath>();});}}
}

说明

  1. AbpAspNetCoreMvcVersioningModule 模块会自动引入 Microsoft.AspNetCore.Mvc.Versioning,无需额外手动添加。
  2. 若只想使用单一的版本读取方式(如仅用 QueryString),可在 ApiVersionReader.Combine(...) 中删除多余的 Reader。
  3. RemoveVersionFromParameterReplaceVersionWithExactValueInPath 的实现可参考下文示例或 官方文档。

2.1.1 版本解析流程图 🗺️
有版本号
无版本号
有参数 ?v=
无参数
有 Header
无 Header
客户端请求
检查 URL Segment?
使用 URL Segment 版本
检查 QueryString?
使用 QueryString 版本
检查 Header?
使用 Header 版本
使用默认版本 (v1.0)
进入对应版本逻辑

2.1.2 RemoveVersionFromParameter 实现 🛠️
// 移除 Swagger 中的 version 参数
public class RemoveVersionFromParameter : IOperationFilter
{public void Apply(OpenApiOperation operation, OperationFilterContext context){if (operation.Parameters == null){return;}var versionParameter = operation.Parameters.FirstOrDefault(p => p.Name.Equals("version", StringComparison.InvariantCultureIgnoreCase));if (versionParameter != null){operation.Parameters.Remove(versionParameter);}}
}

2.1.3 ReplaceVersionWithExactValueInPath 实现 🔄
// 将路径中的 {version:apiVersion} 占位符替换为具体版本号
public class ReplaceVersionWithExactValueInPath : IDocumentFilter
{public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context){var paths = new OpenApiPaths();foreach (var (key, value) in swaggerDoc.Paths){// 将路径中的占位符 {version} 替换为 swaggerDoc.Info.Version(例如 "v1.0")var updatedKey = key.Replace("{version}", swaggerDoc.Info.Version);paths.Add(updatedKey, value);}swaggerDoc.Paths = paths;}
}

2.2 声明支持的版本 📝

当某个 Controller 需要同时响应多个版本时,可在类上使用 [ApiVersion] 标注,并在方法上使用 MapToApiVersion 指定具体版本。示例如下:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// 同时支持 v1.0 和 v2.0[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductController : ControllerBase{// 仅在 v1.0 时暴露该方法[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1(){return Ok("Product from v1.0");}// 仅在 v2.0 时暴露该方法[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2(){return Ok("Product from v2.0");}}
}
  • 说明
    • 路由路径为 /api/v{version}/products,版本号通过 URL Segment 方式传递(如 /api/v1.0/products/api/v2.0/products)。
    • 如果在同一个 Controller 内逻辑分支较多,可使用 MapToApiVersion 在同一个类中实现多版本映射,避免类数量过多。
    • 若想做“版本中立”(即对所有版本通用),在类上使用 [ApiVersionNeutral] 即可:
    [ApiVersionNeutral][Route("api/health")]public class HealthController : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Health OK");}

2.3 客户端代理与 Swagger 分组生成 ⚙️

当启用版本化后,ABP 会自动在 Swagger UI 上按版本分组暴露文档。开发者可以通过以下几种方式生成客户端代理:

2.3.1 手动执行命令
   abp suite generate-proxy

该命令会根据当前已发布的 Swagger 文档(包括多个版本)在 *.HttpApi.Client 项目中生成对应的 TypeScript/C# 代理文件,并自动分文件夹存放。📁


2.3.2 自动生成配置

如果希望每次项目启动时自动生成,可在 YourProjectHttpApiHostModule 中添加以下配置,并确保已启用 Swagger 生成配置(见 2.1 中的 AddSwaggerGen):

   using Volo.Abp.AspNetCore.Mvc.ApiExplorer;public override void ConfigureServices(ServiceConfigurationContext context){// ... 上述版本化和 Swagger 配置 ...Configure<AbpApiDescriptionModelOptions>(options =>{options.IsControllerModelEnabled = true;});}

注意

  • 仅在你想要每次运行项目时自动生成 TypeScript/C# 代理时配置 IsControllerModelEnabled = true。若只想手动触发生成,可忽略此配置。
  • 为了让 Swagger UI 正常显示分组,需要在 AddSwaggerGen 中编写 DocInclusionPredicateOperationFilterDocumentFilter 等逻辑,详见 2.1。

2.3.3 Swagger 文档生成流程图 📊
不是
扫描 Controller 特性
是否 ApiVersionAttribute?
收集版本列表
是否 ApiVersionNeutral?
包含在所有文档
忽略该 API
根据版本分组生成 SwaggerDoc
应用 RemoveVersionFromParameter
应用 ReplaceVersionWithExactValueInPath
输出多版本 Swagger JSON

三、实战演示 🎬

下面分别示范 URL SegmentQueryStringHeader 三种模式下的版本化实现方式,并给出调用示例与注意事项。💡


3.1 URL Segment 模式(示例一:同类分支)🔀

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// 同时支持 v1.0 和 v2.0[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductController : ControllerBase{// 仅在 v1.0 时暴露该方法[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1(){return Ok("Product from v1.0");}// 仅在 v2.0 时暴露该方法[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2(){return Ok("Product from v2.0");}}
}
  • 启动项目后测试
  GET /api/v1.0/products   → 返回 "Product from v1.0" 🥇GET /api/v2.0/products   → 返回 "Product from v2.0" 🥈

注意:如果同一个 Controller 内既使用 URL Segment 又使用 QueryString,可在请求中同时带两种版本号,例如 /api/v1.0/products?v=2.0,框架会优先按照 UrlSegmentApiVersionReader(v1.0)解析;若想优先使用 QueryString,需将 QueryStringApiVersionReader("v") 放在 Combine 方法第一个参数位置。


3.2 URL Segment 模式(示例二:拆分控制器)✂️

当两个版本逻辑差异较大,或者想将不同版本拆分到独立类时,可编写如下示例:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{// v1.0 Controller[ApiVersion("1.0")][Route("api/v{version:apiVersion}/products")]public class ProductV1Controller : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Product from v1.0");}// v2.0 Controller[ApiVersion("2.0")][Route("api/v{version:apiVersion}/products")]public class ProductV2Controller : ControllerBase{[HttpGet]public IActionResult Get() => Ok("Product from v2.0");}
}
  • 测试示例
  GET /api/v1.0/products   → 返回 "Product from v1.0" 🥇GET /api/v2.0/products   → 返回 "Product from v2.0" 🥈

对比说明

  • 同类分支(见 3.1):同一个 Controller 内通过 MapToApiVersion 在方法层面区分版本,代码复用率高,但类文件大小可能增加。
  • 拆分控制器:将各版本逻辑完全隔离到不同类,类名更能明确版本含义,便于后续维护;但若大部分逻辑相同,会导致重复代码。🔍

3.3 QueryString 模式 🔍

如果想将版本号放在 QueryString 中,而不在路由路径里,则需在 2.1 中对 ApiVersionReader.Combine(...) 只保留 QueryStringApiVersionReader("v") 或者把它放到第一个位置。下面示例演示只使用 QueryString:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/products")]public class ProductQueryController : ControllerBase{// 默认 v1.0[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1() => Ok("Product from v1.0");// v2.0[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2() => Ok("Product from v2.0");}
}
  • 请求示例
  GET /api/products?v=1.0   → 返回 "Product from v1.0" 🎯GET /api/products?v=2.0   → 返回 "Product from v2.0" 🎯
  • 如果客户端既不带 v 参数(因配置了 AssumeDefaultVersionWhenUnspecified = true),也不放在 URL 中,则会默认调用 v1.0。

注意:确保在 AddAbpApiVersioning 中的 ApiVersionReader.Combine(...) 顺序中,将 new QueryStringApiVersionReader("v") 放在首位,否则若同时存在 URL、QueryString,会优先按照 URL Segment 解析。⚠️


3.4 Header 模式 📬

Header 模式适用于不想在 URL 中显式暴露版本号的场景。示例如下:

using Microsoft.AspNetCore.Mvc;namespace YourProject.HttpApi.Controllers
{[ApiVersion("1.0")][ApiVersion("2.0")][Route("api/products")]public class ProductHeaderController : ControllerBase{[HttpGet, MapToApiVersion("1.0")]public IActionResult GetV1() => Ok("Product from v1.0");[HttpGet, MapToApiVersion("2.0")]public IActionResult GetV2() => Ok("Product from v2.0");}
}
  • 请求示例
  GET /api/productsHeader: x-api-version: 1.0   → 返回 "Product from v1.0" 📬GET /api/productsHeader: x-api-version: 2.0   → 返回 "Product from v2.0" 📬
  • 如果既不在 URL,也不在 Header 中指定版本,则会使用默认版本(1.0)。

3.5 三种模式对比表 📋

版本传递方式传递形式优点缺点
URL Segment/api/v{version}/resource路由清晰、便于缓存、SEO 友好路径变化需兼容旧客户端
QueryString/api/resource?v={version}简洁易用、易于测试URL 参数可丢失、不美观
HeaderHeader: x-api-version: {version}版本号不暴露在 URL,更灵活;与 URL 解耦客户端需额外设置 Header,调试不直观

参考链接

  • ABP vNext 官方文档
  • Swagger 分组示例代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android高级开发第四篇 - JNI性能优化技巧和高级调试方法

文章目录 Android高级开发第四篇 - JNI性能优化技巧和高级调试方法引言为什么JNI性能优化如此重要&#xff1f;第一部分&#xff1a;JNI性能基础知识JNI调用的性能开销何时使用JNI才有意义&#xff1f; 第二部分&#xff1a;核心性能优化技巧1. 减少JNI调用频率2. 高效的数组操…

小白的进阶之路系列之十----人工智能从初步到精通pytorch综合运用的讲解第三部分

本文将介绍Autograd基础。 PyTorch的Autograd特性是PyTorch灵活和快速构建机器学习项目的一部分。它允许在一个复杂的计算中快速而简单地计算多个偏导数(也称为梯度)。这个操作是基于反向传播的神经网络学习的核心。 autograd的强大之处在于它在运行时动态地跟踪你的计算,…

43. 远程分布式测试实现

43. 远程分布式测试实现详解 一、远程测试环境配置 1.1 远程WebDriver服务定义 # Chrome浏览器远程服务地址 chrome_url rhttp://localhost:5143# Edge浏览器远程服务地址 edge_url rhttp://localhost:9438关键概念&#xff1a;每个URL对应一个独立的WebDriver服务典型配置…

Python爬虫(40)基于Selenium与ScrapyRT构建高并发动态网页爬虫架构:原理、实现与性能优化

目录 一、引言二、技术背景1. 动态页面处理痛点2. 架构设计目标 三、核心组件详解1. Selenium Grid集群部署2. ScrapyRT服务化改造3. 智能等待策略 四、系统架构图五、性能优化实践1. 资源隔离策略2. 并发控制算法3. 监控体系 六、总结与展望&#x1f308;Python爬虫相关文章&a…

【存储基础】SAN存储基础知识

文章目录 1. 什么是SAN存储&#xff1f;2. SAN存储组网架构3. SAN存储的主要协议SCSI光纤通道&#xff08;FC&#xff09;协议iSCSIFCoENVMe-oFIB 4. SAN存储的关键技术Thin Provision&#xff1a;LUN空间按需分配Tier&#xff1a;分级存储Cache&#xff1a;缓存机制QoS&#x…

TDengine 运维——巡检工具(定期检查)

背景 TDengine 在运行一段时间后需要针对运行环境和 TDengine 本身的运行状态进行定期巡检&#xff0c;本文档旨在说明如何使用巡检工具对 TDengine 的运行环境进行自动化检查。 安装工具使用方法 工具支持通过 help 参数查看支持的语法 Usage: taosinspect [OPTIONS]Check…

DHCP应用

一、DHCP介绍 在LAN(局域网)中我们常会遇到以下的情况&#xff1a; 1.不知道如何配置IP地址及相关信息的员工&#xff0c;无法上网&#xff1b;2.IP地址配置冲突&#xff0c;无法上网&#xff1b;3.来访用户因不熟悉公司网络情况无法上网&#xff1b; 以上这些情况都是日常最…

LabVIEW多按键自动化检测系统

LabVIEW开发一套高精度按键力与行程自动化检测系统&#xff0c;针对传统检测设备自动化程度低、定位误差大等痛点&#xff0c;实现多按键产品的全流程自动化测试。系统集成 6 轴工业机器人、高精度传感器及实时数据处理模块&#xff0c;满足汽车电子、消费电子等领域对按键手感…

嵌入式硬件篇---蜂鸣器

蜂鸣器是一种常用的电子发声元件&#xff0c;主要分为有源蜂鸣器和无源蜂鸣器两类。它们在结构、工作原理、驱动方式、应用场景等方面存在显著差异。以下是详细介绍&#xff1a; 一、核心定义与结构差异 1. 有源蜂鸣器 定义&#xff1a; “有源” 指内部自带振荡电路&#x…

600+纯CSS加载动画一键获取指南

CSS-Loaders.com 完整使用指南&#xff1a;600纯CSS加载动画库 &#x1f3af; 什么是 CSS-Loaders.com&#xff1f; CSS-Loaders.com 是一个专门提供纯CSS加载动画的资源网站&#xff0c;拥有超过600个精美的单元素加载器。这个网站的最大特色是所有动画都只需要一个HTML元素…

国内高频混压PCB厂家有哪些?

一、技术领先型厂商&#xff08;聚焦材料与工艺突破&#xff09; 猎板PCB 技术亮点&#xff1a;真空层压工艺实现FR-4与罗杰斯高频材料&#xff08;RO4350B/RO3003&#xff09;混压&#xff0c;阻抗公差3%&#xff0c;支持64单元/板的5G天线模块&#xff0c;插损降低15%。 应用…

volatile,synchronized,原子操作实现原理,缓存一致性协议

文章目录 缓存一致性协议&#xff08;MESI&#xff09;volatile1. volatile 的作用2.volatile的底层实现3,volatile 实现单例模式的双重锁&#xff08;面手写&#xff09; synchronized1,基本用法2,可重入性3,Java对象头4,实现原理&#xff08;1&#xff09;代码块同步的实现&a…

webfuture:如何屏蔽后台发文界面的保存为新文章按钮?

问题描述&#xff1a; 如何屏蔽后台发文界面的保存为新文章按钮&#xff1f; 问题解决&#xff1a;修改这个文件 /Admin/Content/Base/css/base.css 定义这个的id saveAsNewItemSubmit #saveAsNewItemSubmit{display: none;}

SpringBoot集成第三方jar的完整指南

原文地址&#xff1a;https://blog.csdn.net/weixin_43826336/article/details/141640152?ops_request_misc%257B%2522request%255Fid%2522%253A%25227d4118ef2d572ba4428caf83f1d2bb28%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id7d4118…

题目 3293: 蓝桥杯2024年第十五届决赛真题-数位翻转

题目 3293: 蓝桥杯2024年第十五届决赛真题-数位翻转 时间限制: 2s 内存限制: 192MB 提交: 1046 解决: 318 题目描述 小明创造了一个函数 f(x) 用来翻转 x 的二进制的数位&#xff08;无前导 0&#xff09;。比如f(11) 13&#xff0c;因为 11 (1011)2&#xff0c;将其左右翻转…

word为跨页表格新加表头和表名

问题&#xff1a; 当表格过长需要跨页时&#xff08;如下图所示&#xff09;&#xff0c;某些格式要求需要转页接排加续表。 方法一&#xff1a; 1、选中表格&#xff0c;在“表布局”区域点开“自动调整”&#xff0c;选择“固定列宽”&#xff08;防止后续拆分表格后表格变…

Ubuntu上进行VS Code的配置

1. 安装VS code sudo snap install code --classic 2. 安装GCC sudo apt install build-essential 3. 安装VS Code中文包 打开 VS Code 点击左侧活动栏中的扩展图标(或按Ctrl+Shift+X) 在搜索框中输入:Chinese (Simplified) 选择由 Microsoft 提供的 中文(简体)语言包…

vr中风--数据处理模型搭建与训练2

位置http://localhost:8888/notebooks/Untitled1-Copy1.ipynb # -*- coding: utf-8 -*- """ MUSED-I康复评估系统&#xff08;增强版&#xff09; 包含&#xff1a;多通道sEMG数据增强、混合模型架构、标准化处理 """ import numpy as np impor…

【LLM vs Agent】从语言模型到智能体,人工智能迈出的关键一步

目录 一、什么是 LLM&#xff1f;语言的天才&#xff0c;思维的起点 ✅ 特点小结&#xff1a; 二、什么是 Agent&#xff1f;智能的执行者&#xff0c;自主的决策者 ✅ 特点小结&#xff1a; 三、LLM 与 Agent 的关系&#xff1a;是工具&#xff0c;更是大脑 四、案例实战…

安装DockerDocker-Compose

Docker 1、换掉关键文件 vim /etc/yum.repos.d/CentOS-Base.repo ▽ [base] nameCentOS-$releasever - Base - Mirrors Aliyun baseurlhttp://mirrors.aliyun.com/centos/$releasever/os/$basearch/ gpgcheck1 enabled1 gpgkeyhttp://mirrors.aliyun.com/centos/RPM-GPG-KEY-C…