19 KiB
证书异步任务接口接线说明
更新日期:2026年10月2日。依据当前源码;本批尚未部署,不代表生产验收通过。
业务范围
证书申请、当前证书部署、证书导入、本地证书校验及申请/部署手动重试先持久化任务,再交给后台工作线程执行。浏览器断线不取消已接收工作,前端通过任务接口确认最终结果。提交成功、证书保存成功、本地校验成功、逐服务器推送成功、整体任务成功是不同层级,不能混用。
接口与权限
所有接口要求完成初始化、有效登录会话及来源校验;前端沿用现有带会话的 fetchAdmin,不在 URL 中携带凭据。任务与域名接口返回 private, no-store 和 Vary: Cookie,不使用共享缓存。
| 方法与路径 | 行为 | 权限及返回 |
|---|---|---|
| POST /api/v1/domains/:id/issue | 提交申请及关联部署,无请求体 | 可见域名、证书管理、部署执行、DNS 凭据使用权限;202 |
| POST /api/v1/domains/:id/deploy | 提交已有证书部署,无请求体 | 可见域名、部署执行权限;202 |
| POST /api/v1/domains/:id/certificate/import | 上传证书后提交导入及部署任务 | 可见域名、证书管理、部署执行权限;不要求 DNS 凭据权限;202 |
| POST /api/v1/domains/:id/certificate/validate | 提交当前本地证书校验,无请求体 | 可见域名、私钥读取权限;不额外要求证书管理、DNS 或部署执行权限;202 |
| POST /api/v1/renewal/run | 提交管理证书及业务域名的全局续期检查,无请求体 | 当前有效超级管理员;202,不再同步返回 report |
| POST /api/v1/tasks/:id/retry | 同一任务新增一次尝试,无请求体 | 重试、证书管理、部署执行权限;申请类还检查 DNS 凭据;202 |
| DELETE /api/v1/tasks/:id | 请求取消 | 任务可见及取消权限;后台任务返回 202,非异步历史任务可能返回 200 |
| GET /api/v1/tasks | 当前身份可见任务列表 | 支持 domain_id 和 limit;返回 200 与 items 数组 |
| GET /api/v1/tasks/:id | 单个任务最新状态 | 返回 200 与任务对象本身,不包 task 字段 |
| GET /api/v1/tasks/:id/attempts | 任务尝试历史 | 返回 200 与 items 数组 |
| GET /api/v1/tasks/:id/deployment-results | 逐尝试的逐服务器结果与完整报告 | 返回 200、items 和 reports 数组;按任务、最近尝试编号及尝试 ID 精确匹配 |
| GET /api/v1/tasks/:id/validation-results | 读取本地校验公开元数据 | 任务读取权限;返回 200 与 items 数组,仓储查询实时复核可见范围 |
列表 limit 默认 50,有效范围为 1~200,越界由仓储回退至 50。不存在和无权访问的任务都返回 403,不能根据接口泄露其他项目资源是否存在。
异步提交响应
执行槽取得后还会重新检查当前账户、域名项目归属及操作权限。排队后撤权、停用账户、删除/迁移域名或权限依赖异常时,不执行证书工作流;已接收任务最终为 failed、stage 为 authorize,消息为“任务执行前权限或资源归属校验未通过,未执行证书操作”。任务及尝试、失败审计统一收尾,不附带虚构的部署报告。
上述终态以未发生取消且事务收尾成功为前提;同时收到取消时沿用取消语义,数据库收尾失败则保留中断状态供启动恢复,不伪造失败持久化成功。
该失败通过后续任务查询显示,不改变已经返回的 202,也不要求前端自动重新提交。若操作者已失去读取权限,查询仍可能返回 403;不得据此显示成功。重试按本次重试者重新授权,不沿用首次提交者身份。此复核不等同于操作开始后撤权自动中止,正在执行的任务仍使用取消入口。
返回 202 和 Location: /api/v1/tasks/任务编号。以下仅为字段示例,编号不对应真实环境:
{
"accepted": true,
"task": {
"id": "example-task-id",
"domain_id": "example-domain-id",
"scope_type": "project",
"project_id": "example-project-id",
"task_type": "certificate_deploy",
"status": "queued",
"stage": "queued",
"message": "等待执行证书任务",
"created_at": "2026-10-02T08:00:00Z",
"updated_at": "2026-10-02T08:00:00Z"
}
}
202 的 task 是提交时快照,不保证仍处于 queued。重试响应可能已经是 running。前端必须使用后续 GET 状态作为依据,不凭提交响应显示成功。
幂等与错误
- 首次申请、部署、导入和校验支持 Idempotency-Key:长度 8~128,只允许英文字母、数字、连字符、下划线及英文句点。校验前端使用 certificate-validate 前缀,不将接口路径中的斜线写入请求头。前端当前为每次明确操作生成新键,网络重发若需要去重必须复用原键。
- 同一域名、同一键命中已结束任务返回 200、task 和 replayed: true;命中活动任务返回 409、task 和 replayed: true,不再次执行。跨操作复用同一键返回 409。
- 同类活动任务、仍在收尾、成功任务重试或不支持的任务类型返回 409。部署的 partial_success 允许重试;申请类只允许 failed 或 cancelled。
- 取消 202 只说明接收取消意图,状态为 cancelling;不能提前显示已取消或允许重试。已经完成的外部操作仍可最终成功,并提示取消未撤销该操作。
- 400:幂等键等输入无效;401:登录失效;403:无权访问或来源错误;409:初始化未完成或状态冲突;500:持久化等内部错误;501:工作流未配置;503:权限服务未就绪、服务停止或队列已满。
- 错误响应一般含 error,可能带 request_id 或 task;网关也可能返回非 JSON 页面。解析失败只能提示暂时无法确认,不能推断任务已失败或自动再次提交。
全局手动续期检查
本入口继续调用原续期调度器,保留管理证书、业务域名、调度互斥锁、续期子任务、紧急重试等待与取消传播;不缩减为只检查业务域名。自动调度行为不因 HTTP 后台化改变。
- 系统任务固定为 task_type=renewal_check、domain_id=global-renewal-check、scope_type=system、project_id 为空;它既不是业务域名,也不是单次管理证书签发任务。
- 提交与取得执行槽后均重新核验当前超级管理员。普通管理员及普通用户不能读取该任务、尝试或完成审计,也不能取消;排队期间停用或降级则不执行续期。
- 支持合法 Idempotency-Key。同键命中原续期任务返回 200、task、replayed=true;另一键遇到活动全局检查返回 409、原 task、replayed=true;新任务返回 202、task、accepted=true 和 Location。前端必须确认任务类型,不能把任意 409 当作已受理。
- 同步旧版的 200 加 report 不是新版受理响应,前端必须提示结果无法确认,不能显示检查完成。响应丢失时保留原幂等键;确认受理后,下一次明确操作才生成新键。
- DELETE /api/v1/tasks/:id 接收取消意图后,继续轮询终态。已发生的续期或部署不会因父任务取消自动撤销;取消和超时摘要保留已返回的执行统计。
- 终态摘要保存检查、临期、选中、紧急、成功、失败和异常数量,与任务尝试及完成审计同事务提交;通过现有 task.message 和 attempt.message 展示。没有新增结构化续期 report 接口,也不伪造部署报告或保存底层敏感错误。
- 有失败或异常时为 failed,已有成功项时可为 partial_success;取消为 cancelled、超时为 failed,不因下游返回空错误而显示整体成功。异常中断提示核查已生成的子任务,不自动重放。
- 不支持通过通用 retry 重试 renewal_check;完成后如需再次检查,由超级管理员明确重新提交。逐域名部署、校验与恢复结果仍以对应子任务为准。
- 400 表示幂等键无效,403 表示超级管理员权限校验未通过,501 表示续期工作流未配置,503 表示任务依赖未就绪、服务停止或队列已满,500 表示持久化等错误。队列拒绝时可能已保存失败任务,不能假定从未创建任务。
前端使用独立 RenewalAction.js 管理提交状态和幂等键,复用任务详情跟踪结果;禁止重复点击时并发提交,不能仅凭受理响应显示续期完成。
证书历史版本恢复
GET /api/v1/domains/:id/certificate/versions返回当前证书及可恢复的本地历史版本;前端使用history_id作为稳定恢复标识,不使用列表序号或展示版本号。POST /api/v1/domains/:id/certificate/versions/:history_id/restore不接收证书内容,使用Idempotency-Key提交恢复任务;成功返回202 Accepted、accepted=true、task和Location: /api/v1/tasks/任务编号。- 恢复任务必须通过域名可见性、证书管理、部署执行和私钥读取权限复核;当前证书、过期版本、未生效版本、域名不匹配或证书类型不匹配不得恢复。
- 同域名存在活动恢复任务时返回冲突及原任务信息;相同幂等键重放时返回原任务,不重复执行。前端必须继续查询任务详情,不能凭 202 或 200 直接显示恢复完成。
- 恢复范围仅为 Scert 本地托管证书历史;任务完成不代表远端服务器已经部署新证书,远端部署必须使用独立部署任务并单独确认。
- 响应及错误信息不得暴露私钥内容、私钥路径、证书存储本地路径或服务器路径。
数据模型
| 模型 | 字段 |
|---|---|
| Task | id、domain_id、task_type、status、stage、message、scope_type、project_id、created_at、updated_at;可选 operator_id、certificate_version_id、idempotency_key、started_at、finished_at |
| TaskAttempt | id、task_id、attempt_number、stage、status、message、scope_type、project_id、created_at;可选 started_at、finished_at |
| TaskDeploymentResult | task_id、attempt_id、attempt_number、result_index、server_id、server、status、message、created_at;可选 backup_id |
| TaskDeploymentReport | task_id、attempt_id、attempt_number、report、created_at;report 保留汇总、逐目标结果、verification_status 与 recovery_status |
| TaskValidationResult | task_id、attempt_id、attempt_number、validation、created_at;validation 包含 domains、serial_number、fingerprint_sha256、not_before、not_after、chain_length |
编号和时间为字符串,时间使用带时区的 RFC3339 表示;attempt_number 与 result_index 为整数。Task 与 TaskAttempt 的状态含义如下:
| 状态 | 中文含义 | 是否终态 |
|---|---|---|
| queued | 排队中 | 否 |
| running | 执行中 | 否 |
| retry_wait | 等待重试 | 否 |
| cancelling | 正在取消并等待收尾 | 否 |
| success | 已完成 | 是 |
| failed | 执行失败或结果未可靠确认 | 是 |
| cancelled | 已停止;不保证外部副作用全部撤销 | 是 |
| partial_success | 部分完成 | 是 |
未知状态不得默认视为成功。逐服务器结果不包含完整的站点验证及恢复报告,不能据此推导 HTTPS 校验成功。
完整报告读取
deployment-results 响应保留旧 items,新增 reports;每次尝试独立保存,重试不覆盖历史。report.results 即使零目标也返回数组。示例省略逐目标明细:
{
"items": [],
"reports": [{
"task_id": "task-example",
"attempt_id": "attempt-example",
"attempt_number": 1,
"created_at": "2026-10-02T09:00:00Z",
"report": {
"total": 0,
"success": 0,
"failed": 0,
"status": "failed",
"verification_status": "failed",
"recovery_status": "success",
"results": []
}
}]
}
- 完整报告写入和读取均按公开诊断规则处理;逐目标 failure_code 保留,原始远端错误不公开。
- 前端只在任务终态与最近尝试一致后展示报告,以任务终态覆盖报告展示状态,不能由外部操作成功推断收尾成功。
- 旧服务未提供 reports 或旧历史无完整报告时兼容逐目标汇总,但不补造验证/恢复状态。reports 存在而不是数组时视为读取异常。
- 报告读取在仓储查询中实时检查任务项目权限;只读成员可读,撤权后不得凭旧会话读取。
执行及前端交互边界
本地证书校验
- 提交返回 202,task_type 为 certificate_validate。提交与取得执行槽之后均复核私钥读取权限和域名归属;响应断开不取消已接收工作,不返回证书或私钥路径。
- 只有成功校验才保存 validation;失败、排队取消、关停、执行异常不生成通过结果。校验结果、任务、尝试和完成审计共用一个事务,不生成空的部署报告。
- validation 中 domains 为字符串数组,serial_number 和 fingerprint_sha256 为字符串,not_before 和 not_after 为带时区时间,chain_length 为整数。结果读取形式为 items 数组,每条记录关联原 task_id、attempt_id 和 attempt_number。
- 前端仅为校验任务额外读取 validation-results;任务与最近一次尝试均为 success,且三个关联字段全部一致时展示结果。切换、关闭详情和撤权后清除缓存;历史成功任务没有详情时明确说明未留存,不推断当前证书仍然有效。
- 结果只证明执行时的本地证书、私钥匹配及信任链校验,不代表远端部署、HTTPS 或生产验收通过。
- 校验不接入通用手动重试入口,POST /tasks/:id/retry 返回 409。用户需要重新校验时,应从域名校验入口明确创建新任务。
公共执行约束
- 当前后台最多接收 32 个在途任务,4 个执行槽,总执行预算 30 分钟;这些是实现限制,不是保证耗时。
- 队列满或关闭时拒绝执行,并尝试保存失败终态;持久化失败保留中断状态,不伪造已完成。
- 服务关闭停止接收、发送取消信号并等待工作线程收尾。启动恢复将中断记录标为失败,不自动重放外部签发、部署。
- HTTP 异步收尾路径将任务、本次尝试、完整报告、逐目标结果及完成审计放入同一事务;任一写入失败整体回滚并保留中断状态,不伪造终态。其他后台工作流仍需独立核查,不能据此声明全项目所有写入原子化。
- 后台证书共用执行器现同样采用事务收尾,覆盖续期、当前证书部署、导入、恢复及校验入口。内部失败重试会保存本次失败尝试及完整报告,父任务保持 retry_wait,不生成完成审计;最终失败审计统一使用 error,与 HTTP 路径一致。
- 后台重试等待期间取消,保留此前尝试及报告,只关闭父任务并保存取消审计;尚有活动尝试时拒绝绕过尝试单独收尾。报告、结果或审计落库失败不继续重试外部操作,保留活动状态供恢复。此约束不等于所有后台模块均已完成事务化。
- 前端每三秒查询一次,单次周期十秒超时且不重叠;卸载后停止轮询、取消请求。
- 切换任务时丢弃旧选择的迟到响应;撤权清除详情及部署结果。一般查询故障保留最后有效状态并提示未知,不改变任务终态。
- 三个详情 GET 不是一次原子快照;任务终态与最大 attempt_number 的状态不一致时暂不合并,下一周期重新查询。
- 任务从活动变终态后刷新域名、证书、运行状态和审计,但不覆盖正在编辑的设置表单。
回归与未完成项
证书导入接线补充
- 上传使用 multipart/form-data:certificate、private_key 必填,issuer_bundle 可选;整个请求最多 20 MiB,包含表单开销,不是每个文件分别获得 20 MiB 配额。错误上传参数返回 400。
- 兼容 JSON 路径字段 certificate_path、private_key_path、issuer_bundle_path;前两项必填,第三项可省略。路径在实际导入时仍受原有读取校验约束,异步接收不代表路径有效,不放宽任意本地文件读取权限。
- 返回与申请相同的 202、accepted、task 和 Location;task_type 为 certificate_import。不返回同步证书路径,不允许收到 202 就显示“证书已导入”。缺少 task.id 应提示结果未知,不能自动重新提交。
- 前端每次打开导入表单生成一个 Idempotency-Key,响应断开后继续使用原键。409 且 replayed=true 表示原任务仍活动,应跟踪原 task.id;已完成任务重放返回 200。相同键不比较上传内容,不用于更换文件或启动新的导入。
- 在途同域名任务冲突、幂等重放、无任务仓储、队列满或服务正在关闭时,本次上传副本由提交路径回收;成功入队后所有权交给工作线程,正常结束、授权拒绝、取消、关停和可恢复异常均回收副本。客户端断开不影响已接收任务。JSON 指定的源文件不在回收范围内。
- 进程强制终止后的上传残留清理尚未完成实机验证,不将正常关停回收扩大为崩溃清理验收通过。
- 已执行导入的最终完整报告新增 certificate_imported: true,表示证书已经保存,不表示部署成功;false 时字段可省略。该标志与任务终态及报告同事务持久化,不含源文件路径和私钥材料。
- 部署失败或部分成功且证书已保存时,任务消息明确提示检查结果后重试部署,无需重新导入。导入任务不支持 POST /tasks/:id/retry,返回 409,不能重放已回收的上传文件;修复目标后使用已有证书部署入口。
- 前端刷新时读取最近尝试的完整报告恢复保存标志;任务查询失败只显示无法确认,不将上传失败、部署失败、取消和保存状态混为一谈。
导入验证场景:正常上传及请求结束后继续执行、排队取消、服务关停、队列拒绝、异常、无效幂等键、重复上传回收、保存后部署失败、部分成功、禁止重试丢失的上传文件,以及排队后的账户/权限/资源变化。
已加入自动用例:断线后继续执行、排队取消、取消收尾禁止重试、队列满、异常脱敏、读取和保存失败、跨任务尝试拒绝、终态事务回滚、已完成与部分成功不被取消意图覆盖、前端迟到响应和网络恢复。
- [~] 当前 Linux 自动回归持续执行;竞争检测尚无可靠通过结果。
- 完整验证及恢复报告持久化与页面读取已实现,关联回归通过;导入接线后最新前端 260 项及生产构建通过,未部署。
- HTTP 异步完成审计、逐服务器结果与任务终态同事务,已覆盖三类保存失败整体回滚;其他工作流及三库实机复审仍待完成。
- 后台证书共用执行器新增五处故障回滚、每次重试报告、等待取消及无活动尝试收尾回归;应用、仓储、HTTP 三包原生 Linux 测试、全后端测试及 go vet 均通过,未正式部署。
- 生产导入入口及前端任务跟踪已接入异步执行;专项回归通过,未正式部署。
- 校验入口、独立结果持久化和详情展示已接入异步任务;前端幂等键契约缺陷已先复现后修复,最新 266 项测试及生产构建通过,尚未正式部署。
- 版本恢复及手动续期等其他长操作尚未全部异步化;当前历史版本恢复的真实实现仍拒绝恢复,不能用模拟工作流测试替代功能验收。
- 正式构建部署后验证超过反代等待窗口的真实操作、断线查询、取消回滚和服务重启;本说明不替代生产验收。