# 资源备份恢复前端对接文档 ## 1. 对接目标 本文说明当前 `tms-framework` 资源备份和资源恢复接口的前端调用方式、接口顺序、页面判断规则和错误处理建议。 当前前端只需要支持两个主流程: - 创建资源备份包并下载 `.tmsbak` - 上传 `.tmsbak`,预检通过后执行停机恢复,并在 TMS 重启后展示最近恢复结果 恢复侧已经收敛为轻量流程,不再提供恢复任务详情、恢复步骤和恢复步骤日志接口。恢复完成后的页面展示只依赖 `GET /api/v1/resource-restores/latest`。 ## 2. 通用约定 ### 2.1 基础路径 | 模块 | 基础路径 | | --- | --- | | 文件上传 | `/api/v1/files` | | 资源备份 | `/api/v1/resource-backups` | | 资源恢复 | `/api/v1/resource-restores` | ### 2.2 统一响应 除备份包下载接口外,其它接口返回 `ApiResponse`: ```json { "success": true, "code": 200, "msg": "成功", "data": {}, "timestamp": 1777459200000, "traceId": "optional-trace-id" } ``` 前端统一处理规则: - HTTP 非 2xx:请求失败,展示 HTTP 错误或后端 `msg` - `success=false`:业务失败,展示 `msg` - `success=true`:请求成功,继续按 `data` 中的业务字段判断流程 ### 2.3 权限和防重放 资源备份/恢复接口要求内部登录态,角色为 `OPS_ADMIN`,认证等级为 `FULL`。 需要防重放头的接口: - `POST /api/v1/resource-backups` - `POST /api/v1/resource-restores` - `POST /api/v1/resource-restores/{taskId}/apply` 请求头示例: ```http Authorization: Bearer X-Request-Timestamp: X-Request-Nonce: <唯一随机串> ``` `GET` 查询类接口和 `POST /api/v1/resource-restores/precheck` 当前不需要防重放头,但仍需要登录态。 ### 2.4 时间字段 时间字段前端按字符串展示即可: - Java 任务时间:`2026-04-29T16:00:00` - shell 状态时间:`2026-04-29T16:00:00+0800` ## 3. 页面流程总览 ### 3.1 备份流程 1. 用户点击“创建资源备份”。 2. 前端调用 `POST /api/v1/resource-backups`。 3. 根据返回的 `data.status` 判断: - `SUCCESS`:展示“下载备份包” - `FAILED`:展示 `errorMessage`,可查看备份步骤/日志 - 其它状态:调用 `GET /api/v1/resource-backups/{taskId}` 轮询 4. 用户点击“下载”,调用 `GET /api/v1/resource-backups/{taskId}/download`。 5. 浏览器保存 `.tmsbak` 文件。 当前后端创建备份通常同步完成,`POST /api/v1/resource-backups` 可能直接返回 `SUCCESS` 或 `FAILED`。前端保留轮询逻辑即可。 ### 3.2 恢复流程 1. 用户选择 `.tmsbak` 文件。 2. 前端调用 `POST /api/v1/files/upload` 上传文件,拿到 `fileId`。 3. 前端调用 `POST /api/v1/resource-restores/precheck`,传入 `fileId`。 4. 前端展示预检信息,并按 `data.precheckPassed` 判断是否允许下一步。 5. `precheckPassed=true` 时,前端展示一次“确认恢复”弹窗,弹窗中明确提示会进入停机恢复。 6. 用户确认后,前端调用 `POST /api/v1/resource-restores` 创建恢复准备任务。 7. 创建成功且 `data.status=READY_TO_APPLY` 时,前端不要再次弹确认框,直接调用 `POST /api/v1/resource-restores/{taskId}/apply`。 8. `apply` 接口返回后,TMS 可能很快停机、恢复、重启。前端应提示页面/API 短暂不可用。 9. TMS 重启后,恢复页面调用 `GET /api/v1/resource-restores/latest` 展示最近恢复结果。 ### 3.3 恢复流程判断点 | 阶段 | 判断字段 | 前端动作 | | --- | --- | --- | | 上传备份包 | `data.fileId` 非空 | 保存 `fileId`,进入预检 | | 预检 | `data.precheckPassed === true` | 允许点击“确认恢复” | | 预检 | `data.precheckPassed !== true` | 禁止下一步,展示 `blockingIssues` / `warnings` | | 创建恢复任务 | `data.status === "READY_TO_APPLY"` | 立即调用 apply,不再弹确认框 | | 执行恢复任务 | `data.status === "APPLYING"` | 展示“恢复中,服务可能重启” | | 重启后查询 latest | `data.status === "SUCCESS"` | 展示恢复成功 | | 重启后查询 latest | `data.status === "FAILED"` | 展示恢复失败、`phase`、`message` | | 重启后查询 latest | `data.status === "NONE"` | 展示暂无恢复记录 | ## 4. 接口明细 ### 4.1 上传备份包 `POST /api/v1/files/upload` 请求类型:`multipart/form-data` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `file` | `File` | 是 | 用户选择的 `.tmsbak` 文件 | 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "fileId": "FILE-20260429-000001", "filename": "RBKP-20260429-160000-ABCDEF.tmsbak", "size": 1048576 } } ``` 前端只需要保存 `fileId`,不要依赖服务端文件路径。 ### 4.2 创建资源备份 `POST /api/v1/resource-backups` 需要防重放头。 请求体可为空,也可以传备注: ```json { "remark": "硬件更换前备份" } ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `remark` | `string` | 否 | 备份备注 | 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "taskId": "RBKP-TASK-20260429-000001", "backupId": "RBKP-20260429-160000-ABCDEF", "status": "SUCCESS", "createdAt": "2026-04-29T16:00:00" } } ``` 前端判断: - `status=SUCCESS`:允许下载 - `status=FAILED`:展示失败原因,可查询任务详情或步骤日志 - 其它状态:使用 `taskId` 查询任务详情 ### 4.3 查询备份任务详情 `GET /api/v1/resource-backups/{taskId}` 常用响应字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `taskId` | `string` | 备份任务号 | | `backupId` | `string` | 备份包身份号,恢复预检后会显示给用户确认 | | `status` | `ResourceTaskStatus` | 任务状态 | | `productType` | `ProductType` | 产品类型 | | `mqType` | `MqType` | MQ 类型 | | `orgCode` | `string` | 机构号 | | `packageName` | `string` | 备份包文件名 | | `packageSize` | `number` | 包大小,字节 | | `packageHash` | `string` | 包摘要 | | `remark` | `string` | 创建备份时传入的备注 | | `errorMessage` | `string` | 失败原因 | | `createdAt` | `string` | 创建时间 | | `finishedAt` | `string` | 完成时间 | ### 4.4 下载备份包 `GET /api/v1/resource-backups/{taskId}/download` 该接口返回文件流,不是 `ApiResponse`。 响应头示例: ```http Content-Type: application/octet-stream Content-Disposition: attachment; filename="RBKP-20260429-160000-ABCDEF.tmsbak" ``` 前端建议: - 仅当备份任务 `status=SUCCESS` 时展示下载按钮 - 使用浏览器下载能力或 `blob` 保存文件 ### 4.5 查询备份步骤 `GET /api/v1/resource-backups/{taskId}/steps` 用途:备份失败时展示执行上下文。 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": [ { "stepNo": 1, "stepCode": "PACKAGE_CREATE", "status": "SUCCESS", "message": "generated backup package RBKP-20260429-160000-ABCDEF.tmsbak", "startedAt": "2026-04-29T16:00:00", "finishedAt": "2026-04-29T16:00:03", "logAvailable": false } ] } ``` ### 4.6 查询备份步骤日志 `GET /api/v1/resource-backups/{taskId}/steps/{stepNo}/log` 用途:备份失败或排障时展示详细日志。 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "taskId": "RBKP-TASK-20260429-000001", "stepNo": 1, "stepCode": "PACKAGE_CREATE", "content": "{\"packageName\":\"RBKP-20260429-160000-ABCDEF.tmsbak\"}", "truncated": false } } ``` ### 4.7 恢复预检 `POST /api/v1/resource-restores/precheck` 请求体: ```json { "fileId": "FILE-20260429-000001" } ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `fileId` | `string` | 是 | 上传接口返回的文件 ID | 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "precheckId": "RPRE-20260429-000001", "backupId": "RBKP-20260429-160000-ABCDEF", "sourceProductType": "ENTERPRISE", "sourceMqType": "RABBITMQ", "sourceOrgCode": "AAAABBBBXXX", "sourceBackupTime": "2026-04-29T16:00:00", "targetProductType": "ENTERPRISE", "keysetMatched": true, "signatureValid": true, "compatible": true, "precheckPassed": true, "payloadDecrypted": false, "warnings": [], "blockingIssues": [], "restorePlan": ["JAVA_EXTRACT", "SHELL_APPLY"], "restoreScopeSummary": { "resourceCount": 6, "productType": "ENTERPRISE", "mqType": "RABBITMQ", "orgCode": "AAAABBBBXXX" }, "expiresAt": "2026-04-29T16:30:00" } } ``` 前端判断: - 下一步只看 `precheckPassed` - `precheckPassed=true`:允许创建恢复任务 - `precheckPassed=false`:禁止下一步,展示 `blockingIssues`;如只有 `warnings`,按页面需要提示用户 说明: - `precheckPassed` 是后端聚合结果,当前等价于 `keysetMatched=true`、`signatureValid=true`、`compatible=true` 且 `blockingIssues` 为空 - `payloadDecrypted=false` 是正常值,预检阶段不解密完整 payload - 真正解密、展开 payload、路径白名单校验发生在创建恢复任务阶段 - `payload.enc` 的实际加解密由后端通过 PCIe 密码卡 SDF 会话密钥流程完成,前端不需要传递 `keyIndex/keyBits/algId` ### 4.8 创建恢复任务 `POST /api/v1/resource-restores` 需要防重放头。 请求体: ```json { "precheckId": "RPRE-20260429-000001", "confirmBackupId": "RBKP-20260429-160000-ABCDEF", "deployMode": "DUAL", "nodes": { "node01Ip": "192.168.1.11", "node02Ip": "192.168.1.12" } } ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `precheckId` | `string` | 是 | 预检接口返回的预检号 | | `confirmBackupId` | `string` | 是 | 用户确认的备份号,必须等于预检响应 `backupId` | | `deployMode` | `DeployMode` | 是 | `SINGLE`、`DUAL`、`QUAD` | | `nodes.node01Ip` | `string` | 是 | 节点 1 IPv4 | | `nodes.node02Ip` | `string` | `DUAL` / `QUAD` 必填 | 节点 2 IPv4 | | `nodes.node03Ip` | `string` | `QUAD` 必填 | 节点 3 IPv4 | | `nodes.node04Ip` | `string` | `QUAD` 必填 | 节点 4 IPv4 | 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "taskId": "RRST-TASK-20260429-000001", "status": "READY_TO_APPLY", "createdAt": "2026-04-29T16:05:00" } } ``` 前端判断: - `status=READY_TO_APPLY`:立即调用 apply,不再弹确认框 - 其它状态:展示异常提示,不调用 apply 后端在该阶段会完成验签、PCIe/SDF 解密、payload 展开、用户密钥恢复、路径白名单校验和恢复计划文件生成,但不会停止 TMS。 ### 4.9 执行恢复任务 `POST /api/v1/resource-restores/{taskId}/apply` 需要防重放头。 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "taskId": "RRST-TASK-20260429-000001", "status": "APPLYING", "createdAt": "2026-04-29T16:05:00" } } ``` 前端判断: - `status=APPLYING`:提示用户恢复执行中,页面和接口可能短暂不可用 - 之后不要继续轮询恢复步骤,等待 TMS 重启后查 latest 后端在该阶段会启动后台脚本,执行停服务、文件覆盖、数据库导入、MQ replay、启动服务和健康检查。 ### 4.10 查询最近恢复结果 `GET /api/v1/resource-restores/latest` 用途:TMS 重启后,恢复页面展示最近一次恢复结果。 响应示例: ```json { "success": true, "code": 200, "msg": "成功", "data": { "taskId": "RRST-TASK-20260429-000001", "backupId": "RBKP-20260429-160000-ABCDEF", "status": "FAILED", "phase": "DATABASE", "message": "数据库恢复失败", "createdAt": "2026-04-29T16:05:00", "finishedAt": "2026-04-29T16:12:00+0800" } } ``` 前端判断: - `status=SUCCESS`:展示最近恢复成功和 `finishedAt` - `status=FAILED`:展示恢复失败、`phase`、`message` - `status=APPLYING` 或 `RUNNING`:展示恢复仍在执行或等待刷新 - `status=NONE`:展示暂无恢复记录 ## 5. 状态和枚举 ### 5.1 ProductType | 值 | 说明 | | --- | --- | | `ENTERPRISE` | 企参模式 | | `INDIRECT` | 间参模式 | | `DIRECT` | 直参模式 | | `UNKNOWN` | 后端无法识别或未配置 | ### 5.2 MqType | 值 | 说明 | | --- | --- | | `RABBITMQ` | 标准 RabbitMQ | | `TLQ` | TLQ | | `RABBITMQ_TLQ` | RabbitMQ + TLQ | | `RABBITMQ_CFMQ` | RabbitMQ + CFMQ | ### 5.3 DeployMode | 值 | 说明 | 节点要求 | | --- | --- | --- | | `SINGLE` | 单节点部署 | `node01Ip` | | `DUAL` | 双节点部署 | `node01Ip`、`node02Ip` | | `QUAD` | 四节点部署 | `node01Ip`、`node02Ip`、`node03Ip`、`node04Ip` | 第一版中,节点参数只写入恢复上下文用于审计和排障,不自动改写恢复后的配置文件。 ### 5.4 ResourceTaskStatus | 值 | 前端文案建议 | 说明 | | --- | --- | --- | | `RUNNING` | 执行中 | 备份或 shell 恢复执行中 | | `SUCCESS` | 成功 | 任务成功 | | `FAILED` | 失败 | 任务失败 | | `READY_TO_APPLY` | 待停机恢复 | 恢复任务已准备好,前端可继续调用 apply | | `APPLYING` | 恢复执行中 | 恢复脚本已启动或即将启动 | | `NONE` | 暂无记录 | 仅 latest 接口可能返回 | 其它预留状态前端可以兜底展示原值。 ### 5.5 RestorePhase | 值 | 说明 | | --- | --- | | `APPLY_DELAY` | 后台脚本启动后短暂等待 | | `SERVICE_STOP` | 停止服务 | | `FILE` | 恢复文件 | | `DATABASE` | 导入数据库 | | `MQ_REPLAY` | 重放 MQ 配置 | | `SERVICE_START` | 启动服务 | | `HEALTH_CHECK` | 健康检查 | | `DONE` | 完成 | ### 5.6 资源范围说明 当前备份/恢复资源包括: - `TMS_CONFIG` - `TMS_DB` - `CMEP_DB` - `ORG_DB` - `CP_CONFIG` - `RECEIVER_LICENSE` - `STANDARD_CMSP_APP_CONFIG` - `STANDARD_CMTP_APP_CONFIG` - `USER_KEY` - `MQ_REPLAY` `TMS_DB` 默认排除 `tms.backup.tms-db-excluded-tables` 中声明的表。默认排除角色、授权、会话和资源任务表,避免恢复时覆盖新机器安全登录状态或带回旧的资源任务状态;`tms_operation_audit_log` 默认随 TMS 库一起备份。 ## 6. TypeScript 类型建议 ```ts export interface ApiResponse { success: boolean; code: number; msg: string; data: T; timestamp?: number; traceId?: string; } export type ProductType = 'ENTERPRISE' | 'INDIRECT' | 'DIRECT' | 'UNKNOWN'; export type MqType = 'RABBITMQ' | 'TLQ' | 'RABBITMQ_TLQ' | 'RABBITMQ_CFMQ'; export type DeployMode = 'SINGLE' | 'DUAL' | 'QUAD'; export type ResourceTaskStatus = | 'RUNNING' | 'SUCCESS' | 'FAILED' | 'READY_TO_APPLY' | 'APPLYING' | 'NONE' | string; export interface CreateResourceBackupRequest { remark?: string; } export interface CreateResourceBackupResponse { taskId: string; backupId?: string; status: ResourceTaskStatus; createdAt?: string; } export interface ResourceBackupTaskDetailResponse { taskId: string; backupId?: string; status: ResourceTaskStatus; productType?: ProductType; mqType?: MqType; orgCode?: string; packageName?: string; packageSize?: number; packageHash?: string; remark?: string; errorMessage?: string; createdAt?: string; finishedAt?: string; } export interface ResourceRestorePrecheckRequest { fileId: string; } export interface ResourceRestorePrecheckResponse { precheckId: string; backupId: string; sourceProductType?: ProductType; sourceMqType?: MqType; sourceOrgCode?: string; sourceBackupTime?: string; targetProductType?: ProductType; keysetMatched: boolean; signatureValid: boolean; compatible: boolean; precheckPassed: boolean; payloadDecrypted: boolean; warnings: string[]; blockingIssues: string[]; restorePlan: string[]; restoreScopeSummary?: unknown; expiresAt: string; } export interface CreateResourceRestoreRequest { precheckId: string; confirmBackupId: string; deployMode: DeployMode; nodes: { node01Ip: string; node02Ip?: string; node03Ip?: string; node04Ip?: string; }; } export interface CreateResourceRestoreResponse { taskId: string; status: ResourceTaskStatus; createdAt?: string; } export interface ResourceRestoreLatestResponse { taskId?: string; backupId?: string; status: ResourceTaskStatus; phase?: string; message?: string; createdAt?: string; finishedAt?: string; } ``` ## 7. Vue 3 对接示例 以下示例按 Vue 3 Composition API 编写。`request`、`message`、`confirmRestore`、`downloadByUrl` 可替换为项目内已有的 axios 封装、弹窗组件和下载工具。 ### 7.1 防重放头 ```ts function replayHeaders() { return { 'X-Request-Timestamp': String(Math.floor(Date.now() / 1000)), 'X-Request-Nonce': crypto.randomUUID() }; } ``` ### 7.2 备份 composable ```ts import { computed, ref } from 'vue'; export function useResourceBackup() { const backupTask = ref(); const backupLoading = ref(false); const canDownload = computed(() => backupTask.value?.status === 'SUCCESS'); const backupFailed = computed(() => backupTask.value?.status === 'FAILED'); async function createBackup(remark?: string) { backupLoading.value = true; try { const res = await request.post>( '/api/v1/resource-backups', remark ? { remark } : {}, { headers: replayHeaders() } ); if (!res.data.success) throw new Error(res.data.msg); backupTask.value = res.data.data; if (!['SUCCESS', 'FAILED'].includes(res.data.data.status)) { await refreshBackup(res.data.data.taskId); } } finally { backupLoading.value = false; } } async function refreshBackup(taskId: string) { const res = await request.get>( `/api/v1/resource-backups/${taskId}` ); if (!res.data.success) throw new Error(res.data.msg); backupTask.value = res.data.data; } function downloadBackup() { if (!backupTask.value || backupTask.value.status !== 'SUCCESS') return; downloadByUrl(`/api/v1/resource-backups/${backupTask.value.taskId}/download`); } return { backupTask, backupLoading, canDownload, backupFailed, createBackup, refreshBackup, downloadBackup }; } ``` 页面判断: - `canDownload=true` 时展示“下载备份包”。 - `backupFailed=true` 时展示 `backupTask.errorMessage`,并允许进入备份步骤/日志排查。 - 备份侧可以继续保留任务详情、步骤、日志查询。 ### 7.3 恢复 composable ```ts import { computed, ref } from 'vue'; export function useResourceRestore() { const selectedFile = ref(); const precheckResult = ref(); const latestRestore = ref(); const restoreLoading = ref(false); const canCreateRestore = computed(() => precheckResult.value?.precheckPassed === true); const precheckBlocked = computed(() => precheckResult.value?.precheckPassed === false); async function uploadAndPrecheck(file: File) { selectedFile.value = file; restoreLoading.value = true; try { const form = new FormData(); form.append('file', file); const upload = await request.post>( '/api/v1/files/upload', form ); if (!upload.data.success) throw new Error(upload.data.msg); const precheck = await request.post>( '/api/v1/resource-restores/precheck', { fileId: upload.data.data.fileId } ); if (!precheck.data.success) throw new Error(precheck.data.msg); precheckResult.value = precheck.data.data; } finally { restoreLoading.value = false; } } async function confirmAndRestore(deployMode: DeployMode, nodes: CreateResourceRestoreRequest['nodes']) { if (!precheckResult.value || !canCreateRestore.value) return; await confirmRestore({ title: '确认恢复资源', message: `确认使用备份包 ${precheckResult.value.backupId} 进行恢复?恢复过程中服务会停启,页面可能短暂不可访问。` }); restoreLoading.value = true; try { const created = await request.post>( '/api/v1/resource-restores', { precheckId: precheckResult.value.precheckId, confirmBackupId: precheckResult.value.backupId, deployMode, nodes }, { headers: replayHeaders() } ); if (!created.data.success) throw new Error(created.data.msg); if (created.data.data.status !== 'READY_TO_APPLY') { throw new Error('恢复任务状态异常'); } const applied = await request.post>( `/api/v1/resource-restores/${created.data.data.taskId}/apply`, {}, { headers: replayHeaders() } ); if (!applied.data.success) throw new Error(applied.data.msg); message.info('恢复已开始,TMS 将停机并重启。重启后请返回恢复页面查看最近恢复结果。'); } finally { restoreLoading.value = false; } } async function loadLatestRestore() { const res = await request.get>( '/api/v1/resource-restores/latest' ); if (!res.data.success) throw new Error(res.data.msg); latestRestore.value = res.data.data; } return { selectedFile, precheckResult, latestRestore, restoreLoading, canCreateRestore, precheckBlocked, uploadAndPrecheck, confirmAndRestore, loadLatestRestore }; } ``` 页面判断: - 文件选择后调用 `uploadAndPrecheck(file)`。 - “确认恢复”按钮只在 `canCreateRestore=true` 时启用。 - `precheckBlocked=true` 时展示 `blockingIssues`,不要允许用户继续。 - `confirmAndRestore` 内部只弹一次确认;用户确认后按顺序调用创建恢复任务和 apply。 - apply 返回后不要轮询恢复任务详情、恢复步骤或恢复日志。 ### 7.4 重启后最近恢复结果展示 ```vue ``` ## 8. 交互和展示建议 - 备份创建按钮点击后立即禁用,避免重复提交。 - 恢复预检通过后,必须展示备份号 `backupId`,让用户确认正在恢复哪个包。 - `precheckPassed=false` 时不要允许创建恢复任务。 - 恢复流程只做一次用户确认;确认后前端按顺序调用创建恢复任务和 apply。 - apply 返回后不要继续请求恢复步骤接口;恢复侧没有步骤接口。 - TMS 重启后只调用 latest 展示最近恢复结果。 - `payloadDecrypted=false` 不表示失败,它表示预检阶段没有解密完整 payload。 - `TMS_DB` 默认不包含角色、授权、会话和资源任务表,因此恢复不会覆盖新机器已有登录授权状态;操作审计日志默认随 TMS 库备份。 - 服务端路径类字段不展示给普通用户,只用于运维排查。