Certbot Lexicon DNS 插件测试基类全解析:certbot.plugins.dns_test_common_lexicon 使用指南
网络安全CLI后端【免费下载链接】certbotCertbot is EFFs tool to obtain certs from Lets Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.项目地址https://gitcode.com/gh_mirrors/ce/certbot点击查看免费下载本篇技术指南围绕 Certbot 仓库中的 API 文档模块 certbot.plugins.dns_test_common_lexicon 展开系统讲解该模块为基于 Lexicon 构建的 DNS-01 认证插件所提供的测试基类体系。读者将掌握BaseLexiconDNSAuthenticatorTest、BaseLexiconAuthenticatorTest与BaseLexiconClientTest三类测试基类的职责边界、内置测试方法清单以及如何在certbot-dns-ovh、certbot-dns-linode、certbot-dns-dnsimple等真实插件中通过寥寥数十行代码复用它完成完整的 perform/cleanup 行为验证。一、模块定位为什么 Lexicon 系 DNS 插件需要专用测试基类Certbot 支持通过 DNS-01 挑战完成域名验证即由插件在 DNS 服务商处创建_acme-challenge.domainTXT 记录。对于大量使用 dns-lexicon 库作为底层通信后端的 DNS 插件如 OVH、Linode、DNSimple、Luadns、NS1、Sakura Cloud 等它们共享几乎完全相同的解析托管区域 → 鉴权 → 增删 TXT 记录 → 错误归类流程。为了让这些插件避免各自重复编写测试样板代码Certbot 在certbot.plugins.dns_test_common_lexicon模块中集中提供了一套测试基类。该模块文档通过 Sphinx 的automodule指令自动生成 API 页面所有成员、未文档化成员与继承关系都会被收录.. automodule:: certbot.plugins.dns_test_common_lexicon :members: :undoc-members: :show-inheritance:其实际实现位于 certbot/src/certbot/plugins/dns_test_common_lexicon.py与测试基类的通用版 certbot/src/certbot/plugins/dns_test_common.py 及运行时基类 certbot/src/certbot/plugins/dns_common_lexicon.py 紧密配合。二、三类测试基类职责、演进与选型模块导出的三个测试基类对应两条不同的测试思路并且存在明确的版本演进关系。2.1 BaseLexiconDNSAuthenticatorTest当前推荐方案class BaseLexiconDNSAuthenticatorTest(dns_test_common.BaseAuthenticatorTest):这是 Certbot 2.7.0 起推荐的测试基类见 CHANGELOG.md 2.7.0 条目。它假设被测对象是继承dns_common_lexicon.LexiconDNSAuthenticator的认证器实例测试时通过_patch_lexicon_client()将certbot.plugins.dns_common_lexicon.Client整体替换为MagicMock从而在不发起任何真实 DNS API 请求的前提下验证认证器的完整行为链路。2.2 BaseLexiconAuthenticatorTest已弃用class BaseLexiconAuthenticatorTest(dns_test_common.BaseAuthenticatorTest): # pragma: no cover它面向旧的LexiconClient封装self.mock_client仅包含两个核心方法test_perform验证auth.perform([self.achall])最终调用add_txt_record(DOMAIN, _acme-challenge.DOMAIN, mock.ANY)test_cleanup先设置auth._attempt_cleanup True再验证cleanup([self.achall])调用del_txt_record。2.3 BaseLexiconClientTest已弃用class BaseLexiconClientTest: # pragma: no cover与前两者不同它不继承 unittest.TestCase而是面向真实 LexiconClient 实例 mock 的 provider 层self.client与self.provider_mock用于直接测试旧版LexiconClient.add_txt_record/del_txt_record的异常处理逻辑覆盖十余种鉴权失败、域名未找到、记录增删失败场景。2.4 演进关系与弃用警告机制模块顶部通过with warnings.catch_warnings()抑制DeprecationWarning后导入dns_common_lexicon模块末尾则用_DeprecationModule替换sys.modules[__name__]使得访问已弃用的BaseLexiconAuthenticatorTest、BaseLexiconClientTest属性时自动发出弃用警告提示用户迁移到BaseLexiconDNSAuthenticatorTest。这也是 CHANGELOG 2.7.0 中明确记录的变更新基类随LexiconDNSAuthenticator一并引入旧的LexiconClient、build_lexicon_config及其测试基类进入弃用通道。三、模块级常量的语义插件测试的错误字典模块在导入时即定义了一组供所有子类共享的测试故障注入常量位于 dns_test_common_lexicon.py常量类型语义对应真实场景DOMAINstrexample.com默认测试域名与dns_test_common.DOMAIN一致KEYjose.JWKRSA从rsa512_key.pem加载的测试账户密钥生成 DNS-01 校验值DOMAIN_NOT_FOUNDExceptionException(No domain found)Lexicon provider 报告托管区域不存在GENERIC_ERRORRequestException通用网络/API 请求异常增删记录时的任意请求失败LOGIN_ERRORHTTPError带 400 响应的 HTTP 错误凭据无效等可预期的鉴权失败UNKNOWN_LOGIN_ERRORHTTPError带 500 响应的 HTTP 错误服务端意外错误各插件的测试类可以按需覆写这些常量以贴近自身 provider 的真实错误消息详见第五节示例。模块同时复用了 dns_test_common.py 中同名的DOMAIN与KEY并通过test_util.load_vector(rsa512_key.pem)加载测试密钥。四、BaseLexiconDNSAuthenticatorTest 内置测试方法清单继承该基类后插件测试类自动获得以下 14 个测试方法来自 dns_test_common_lexicon.py覆盖 perform 与 cleanup 两条主路径及其全部异常分支4.1 perform 成功路径test_perform_succeed执行self.auth.perform([self.achall])断言mock_client被调用、其配置中lexicon:domain解析为DOMAIN且最终通过create_record(rtypeTXT, name_acme-challenge.example.com, contentmock.ANY)创建记录test_perform_with_one_domain_resolution_failure_succeed__enter__第一次抛DOMAIN_NOT_FOUND域名解析首次失败、第二次起返回 mock 操作对象验证重试后成功的降级路径。4.2 perform 失败路径均断言抛出 errors.PluginError测试方法注入的故障test_perform_with_two_domain_resolution_failures_raise__enter__持续抛DOMAIN_NOT_FOUNDtest_perform_with_domain_resolution_general_failure_raise__enter__抛GENERIC_ERRORtest_perform_with_auth_failure_raisemock_client自身抛LOGIN_ERRORtest_perform_with_unknown_auth_failure_raisemock_client抛UNKNOWN_LOGIN_ERRORtest_perform_with_create_record_failure_raisecreate_record抛GENERIC_ERROR4.3 cleanup 路径失败一律静默忽略test_cleanup_success先设置auth._attempt_cleanup True再执行cleanup断言delete_record(rtypeTXT, name_acme-challenge.example.com, contentmock.ANY)被调用。其余 6 个test_cleanup_with_*_ignore方法分别注入鉴权失败、未知鉴权失败、域名解析失败、解析通用失败与删除记录失败验证 cleanup 在各类异常下都不会向 Certbot 抛出错误——这与运行时LexiconDNSAuthenticator._cleanup中解析区域失败直接 return、删除失败仅记录日志的实现见 dns_common_lexicon.py严格对应。4.4 继承自 BaseAuthenticatorTest 的通用断言由于BaseLexiconDNSAuthenticatorTest继承 dns_test_common.py 的BaseAuthenticatorTest子类还会自动获得test_more_infomore_info()返回字符串test_get_chall_prefget_chall_pref(example.org)返回[challenges.DNS01]test_parser_argumentsadd_parser_arguments注册了propagation-secondstypeint其默认值与 dns_common.py 中的default_propagation_seconds10对应。五、实战示例在真实插件测试中复用该基类仓库中 9 个 Lexicon 系 DNS 插件OVH、DNSimple、DNSMadeEasy、Gehirn、Linode、Luadns、NS1、Sakura Cloud 等的测试均已迁移到BaseLexiconDNSAuthenticatorTest。以 OVH 为例certbot-dns-ovh/src/certbot_dns_ovh/_internal/tests/dns_ovh_test.py 的全部测试只有约 40 行from unittest import mock import pytest from requests import Response from requests.exceptions import HTTPError from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util ENDPOINT ovh-eu APPLICATION_KEY foo APPLICATION_SECRET bar CONSUMER_KEY spam class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconDNSAuthenticatorTest): DOMAIN_NOT_FOUND Exception(Domain example.com not found) LOGIN_ERROR HTTPError(403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/..., responseResponse()) def setUp(self): super().setUp() from certbot_dns_ovh._internal.dns_ovh import Authenticator path os.path.join(self.tempdir, file.ini) credentials { ovh_endpoint: ENDPOINT, ovh_application_key: APPLICATION_KEY, ovh_application_secret: APPLICATION_SECRET, ovh_consumer_key: CONSUMER_KEY, } dns_test_common.write(credentials, path) self.config mock.MagicMock(ovh_credentialspath, ovh_propagation_seconds0) # dont wait during tests self.auth Authenticator(self.config, ovh)要点拆解多重继承同时继承test_util.TempDirTestCase提供self.tempdir临时目录并在 tearDown 时清理见 certbot/src/certbot/tests/util.py与BaseLexiconDNSAuthenticatorTest覆写错误常量DOMAIN_NOT_FOUND与LOGIN_ERROR按 OVH 真实错误文案定制UNKNOWN_LOGIN_ERROR、GENERIC_ERROR沿用模块默认值凭据文件通过dns_test_common.write(values, path)将凭据字典写入临时 INI 文件——该函数内部用configobj写出并执行filesystem.chmod(path, 0o600)见 dns_test_common.py构造被测对象self.config用MagicMock注入ovh_credentials与ovh_propagation_seconds0测试期间跳过传播等待再实例化Authenticator(self.config, ovh)存入self.auth基类全部测试即通过self.auth运行。Linode 的测试 certbot-dns-linode/src/certbot_dns_linode/_internal/tests/dns_linode_test.py 采用完全相同的骨架仅将凭据改为{linode_key: TOKEN}并额外添加了一个test_api_version_4_detection验证密钥格式对_provider_name选择的影响DNSimple 的测试 certbot-dns-dnsimple/src/certbot_dns_dnsimple/_internal/tests/dns_dnsimple_test.py 同样只覆写了LOGIN_ERROR。可见该基类的设计目标正是插件测试类只声明差异行为断言全部复用。六、底层原理mock 注入与 LexiconDNSAuthenticator 的真实调用链6.1 _patch_lexicon_client 上下文管理器BaseLexiconDNSAuthenticatorTest的 perform/cleanup 测试统一通过模块私有的_patch_lexicon_client()dns_test_common_lexicon.py完成依赖替换contextlib.contextmanager def _patch_lexicon_client() - Generator[tuple[MagicMock, MagicMock, None], None, None]: with mock.patch(certbot.plugins.dns_common_lexicon.Client) as mock_client: mock_operations MagicMock() mock_client.return_value.__enter__.return_value mock_operations yield mock_client, mock_operations它把certbot.plugins.dns_common_lexicon.Client替换为MagicMock并让Client(...)的上下文管理器__enter__返回另一个MagicMockmock_operations。由此测试既可以向mock_client.side_effect注入Client 构造失败/鉴权失败类错误也可以向mock_operations.create_record/delete_record.side_effect注入记录操作失败类错误——这正是第四节各测试方法故障注入的实现基础。6.2 被测运行时行为LexiconDNSAuthenticator 的 perform/cleanup 语义测试所验证的行为来自运行时基类 dns_common_lexicon.py_perform(domain, validation_name, validation)先_resolve_domain(domain)逐级猜测托管区域dns_common.base_domain_name_guesses随后with Client(self._build_lexicon_config(resolved_domain)) as operations: operations.create_record(...)任何RequestException都会被包装为errors.PluginError(Error adding TXT record: ...)_cleanup(...)同样先解析区域若解析失败PluginError则仅记日志并静默返回delete_record失败同样只记日志不向调用方抛出——这正是测试基类中 cleanup 系列失败一律忽略断言的依据_build_lexicon_config(domain)构造ConfigResolver字典配置固定写入domain、delegated与domain相同绕过 Lexicon 的子域解析、provider_name、ttl默认 60以及 provider 专属凭据映射。6.3 上游 perform 流程最终被测试的perform由 dns_common.py 的DNSAuthenticator基类实现调用_setup_credentials()→ 置_attempt_cleanup True→ 对每个achall计算校验域名与校验值后调用self._perform(...)→ 通过display_util.notify(...)提示Waiting %d seconds for DNS changes to propagate并sleep(propagation-seconds)。BaseAuthenticatorTest提供的test_parser_arguments正是对该类在add_parser_arguments中注册propagation-seconds参数的回归保护而 perform/cleanup 测试中对test_util.patch_display_util()的调用见 certbot/src/certbot/tests/util.py则屏蔽了certbot._internal.display.obj.get_display避免测试过程中触发真实交互式 UI。七、最佳实践与迁移建议新插件一律使用BaseLexiconDNSAuthenticatorTest它是唯一面向LexiconDNSAuthenticator新架构的基类旧的两个基类已进入弃用通道并会触发DeprecationWarning只覆写差异点优先覆写DOMAIN_NOT_FOUND、LOGIN_ERROR、UNKNOWN_LOGIN_ERROR以匹配真实 provider 的错误文案如插件有特殊逻辑如 Linode 的 API 版本探测以追加独立测试方法的方式扩展不要改动基类行为断言凭据统一走dns_test_common.write它负责以0o600权限写出 INI 文件模拟插件真实的凭据文件加载路径运行方式各插件测试文件末尾均带有if __name__ __main__: sys.exit(pytest.main(sys.argv[1:] [__file__]))既可pytest直接运行也可按文件执行测试通过mock完全离线运行不需要真实的 DNS 服务商账号。综上certbot.plugins.dns_test_common_lexicon是 Certbot 生态中 Lexicon 系 DNS 插件测试的标准化底座它以一套精心设计的 mock 注入机制把增删 TXT 记录、区域解析重试、鉴权失败分类、cleanup 静默容错等最容易出错的逻辑固化为可继承的断言集合让每个插件只需声明自己的凭据与错误文案即可获得与核心插件同等质量的测试覆盖。结合 certbot.plugins.dns_test_common 与 certbot.plugins.dns_common_lexicon 模块文档开发者可以完整掌握 Certbot DNS 插件从实现到验证的整个技术栈。赞分享网络安全CLI后端【免费下载链接】certbotCertbot is EFFs tool to obtain certs from Lets Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.项目地址https://gitcode.com/gh_mirrors/ce/certbot点击查看免费下载相关推荐Certbot LuaDNS 插件实战基于 Lexicon 的 dns-01 域名验证与自动签发证书指南Certbot LuaDNS 插件实战基于 Lexicon 的 dns 01 域名验证与自动签发证书指南 导读 本文讲解 Certbot 生态中的 certb网络安全CLI后端使用阿里云DNS自动获取SSL证书Certbot-DNS-Aliyun插件指南使用阿里云DNS自动获取SSL证书Certbot DNS Aliyun插件指南 项目介绍 Certbot DNS Aliyun 是一个专为阿里云设计的CertCertbot 插件公共基类模块 certbot.plugins.common 全解析从命名空间到安装器基类的插件开发指南Certbot 插件公共基类模块 certbot.plugins.common 全解析从命名空间到安装器基类的插件开发指南 certbot.plugins.c网络安全CLI后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

原生AI应用短期记忆优化:7个工程技巧守住多轮对话关键信息

原生AI应用短期记忆优化:7个工程技巧守住多轮对话关键信息

做原生AI应用最容易被低估的坑,恰恰是短期记忆。前阵子有个朋友找我复盘,他做的AI助手用的是一线大模型,功能做得挺全,可用户一聊超过十轮就开始"犯迷糊":用户上半段刚说过"我对坚果过敏"&#xf…

2026/9/20 2:40:01 阅读更多 →
从CCA到CDP:Cloudera认证备考与Hadoop生态实战全攻略

从CCA到CDP:Cloudera认证备考与Hadoop生态实战全攻略

1. 2026年认证体系到底变了什么:从CCA时代到CDP时代1.1 很多人的认知还停在CCA/CCP,但Cloudera早就换牌了先聊一个我最近常碰到的现象:还不断有人问我"考CCA Administrator还有没有必要""CCP Data Engineer是不是含金量最高的…

2026/9/20 2:40:01 阅读更多 →
Pandoc 管道表格(Pipe Table)列宽计算机制解析:以 `--columns=72` 与多行单元格为例

Pandoc 管道表格(Pipe Table)列宽计算机制解析:以 `--columns=72` 与多行单元格为例

文档开发工具CLI 【免费下载链接】pandoc Universal markup converter 项目地址: https://gitcode.com/gh_mirrors/pa/pandoc 点击查看 免费下载 导读 本文基于 Pandoc 官方测试用例 test/command/3348.md,深入剖析 Markdown 管道表格(pipe…

2026/9/20 2:40:01 阅读更多 →

最新新闻

Vector工具链经典三件套:CANoe、Davinci与Driver Setup实战解析

Vector工具链经典三件套:CANoe、Davinci与Driver Setup实战解析

看到这个标题点进来的,我猜你大概率和我是同行:在汽车电子、嵌入式软件开发这条线上泡着,天天跟总线、AUTOSAR、测试台架打交道。先确认一下,咱们今天聊的不是C标准库里的那个vector容器,虽然std::vector也是很多人刚入…

2026/9/20 3:15:15 阅读更多 →
DeepSeek Harness glob 工具超限结果采样机制:sampleOverCapGlobResults 的设计、实现与测试全解

DeepSeek Harness glob 工具超限结果采样机制:sampleOverCapGlobResults 的设计、实现与测试全解

DeepSeek Harness glob 工具超限结果采样机制:sampleOverCapGlobResults 的设计、实现与测试全解 【免费下载链接】deepseek-harness DeepSeek Harness: Everything is a Plugin. 项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness 本文基于仓库…

2026/9/20 3:15:15 阅读更多 →
IEC 60601标准落地指南:从体系解读到检测认证避坑

IEC 60601标准落地指南:从体系解读到检测认证避坑

做医疗电气设备这些年,我最大的感受是:标准不是拿来背的,是拿来“对话”的。你设计一台监护仪、一张电动病床、一套超声主机,哪怕只是一个带电源适配器的家用指夹血氧仪,要进全球市场,绕不开的核心门槛就是…

2026/9/20 3:15:15 阅读更多 →
Chrome DevTools MCP vs Playwright MCP:浏览器AI自动化选型对比

Chrome DevTools MCP vs Playwright MCP:浏览器AI自动化选型对比

别急着去搜索“对比”,这两个项目我建议你直接都装一遍,花一个下午分别跑几个典型任务,比看十篇评测都有用。但既然你诚心要选型依据,我就把实际体验和底层逻辑掰开揉碎讲清楚。Chrome DevTools MCP和Playwright MCP,本…

2026/9/20 3:15:15 阅读更多 →
nvm安装Node.js报错not yet released:6种原因与排查方法

nvm安装Node.js报错not yet released:6种原因与排查方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 3:15:15 阅读更多 →
2026年AI编程工具版图:从补全代码到数字员工的五大阵营解析

2026年AI编程工具版图:从补全代码到数字员工的五大阵营解析

1. 2026年AI编程工具的版图:从“补全代码”到“数字员工”的演变年初整理自己电脑上装的一堆AI编程插件时,我发现一个很有意思的现象:三年前大家口中所谓的“AI编程工具”,默认指的就是GitHub Copilot那种在你敲代码时自动补全下半…

2026/9/20 3:14:15 阅读更多 →

日新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →