24 KiB
资源备份恢复前端对接文档
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<T>:
{
"success": true,
"code": 200,
"msg": "成功",
"data": {},
"timestamp": 1777459200000,
"traceId": "optional-trace-id"
}
前端统一处理规则:
- HTTP 非 2xx:请求失败,展示 HTTP 错误或后端
msg success=false:业务失败,展示msgsuccess=true:请求成功,继续按data中的业务字段判断流程
2.3 权限和防重放
资源备份/恢复接口要求内部登录态,角色为 OPS_ADMIN,认证等级为 FULL。
需要防重放头的接口:
POST /api/v1/resource-backupsPOST /api/v1/resource-restoresPOST /api/v1/resource-restores/{taskId}/apply
请求头示例:
Authorization: Bearer <token>
X-Request-Timestamp: <unix秒时间戳>
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 备份流程
- 用户点击“创建资源备份”。
- 前端调用
POST /api/v1/resource-backups。 - 根据返回的
data.status判断:SUCCESS:展示“下载备份包”FAILED:展示errorMessage,可查看备份步骤/日志- 其它状态:调用
GET /api/v1/resource-backups/{taskId}轮询
- 用户点击“下载”,调用
GET /api/v1/resource-backups/{taskId}/download。 - 浏览器保存
.tmsbak文件。
当前后端创建备份通常同步完成,POST /api/v1/resource-backups 可能直接返回 SUCCESS 或 FAILED。前端保留轮询逻辑即可。
3.2 恢复流程
- 用户选择
.tmsbak文件。 - 前端调用
POST /api/v1/files/upload上传文件,拿到fileId。 - 前端调用
POST /api/v1/resource-restores/precheck,传入fileId。 - 前端展示预检信息,并按
data.precheckPassed判断是否允许下一步。 precheckPassed=true时,前端展示一次“确认恢复”弹窗,弹窗中明确提示会进入停机恢复。- 用户确认后,前端调用
POST /api/v1/resource-restores创建恢复准备任务。 - 创建成功且
data.status=READY_TO_APPLY时,前端不要再次弹确认框,直接调用POST /api/v1/resource-restores/{taskId}/apply。 apply接口返回后,TMS 可能很快停机、恢复、重启。前端应提示页面/API 短暂不可用。- 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 文件 |
响应示例:
{
"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
需要防重放头。
请求体可为空,也可以传备注:
{
"remark": "硬件更换前备份"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
remark |
string |
否 | 备份备注 |
响应示例:
{
"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。
响应头示例:
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
用途:备份失败时展示执行上下文。
响应示例:
{
"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
用途:备份失败或排障时展示详细日志。
响应示例:
{
"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
请求体:
{
"fileId": "FILE-20260429-000001"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
fileId |
string |
是 | 上传接口返回的文件 ID |
响应示例:
{
"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
需要防重放头。
请求体:
{
"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 |
响应示例:
{
"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
需要防重放头。
响应示例:
{
"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 重启后,恢复页面展示最近一次恢复结果。
响应示例:
{
"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:展示最近恢复成功和finishedAtstatus=FAILED:展示恢复失败、phase、messagestatus=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_CONFIGTMS_DBCMEP_DBORG_DBCP_CONFIGRECEIVER_LICENSESTANDARD_CMSP_APP_CONFIGSTANDARD_CMTP_APP_CONFIGUSER_KEYMQ_REPLAY
TMS_DB 默认排除 tms.backup.tms-db-excluded-tables 中声明的表。默认排除角色、授权、会话和资源任务表,避免恢复时覆盖新机器安全登录状态或带回旧的资源任务状态;tms_operation_audit_log 默认随 TMS 库一起备份。
6. TypeScript 类型建议
export interface ApiResponse<T> {
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 防重放头
function replayHeaders() {
return {
'X-Request-Timestamp': String(Math.floor(Date.now() / 1000)),
'X-Request-Nonce': crypto.randomUUID()
};
}
7.2 备份 composable
import { computed, ref } from 'vue';
export function useResourceBackup() {
const backupTask = ref<ResourceBackupTaskDetailResponse | CreateResourceBackupResponse>();
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<ApiResponse<CreateResourceBackupResponse>>(
'/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<ApiResponse<ResourceBackupTaskDetailResponse>>(
`/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
import { computed, ref } from 'vue';
export function useResourceRestore() {
const selectedFile = ref<File>();
const precheckResult = ref<ResourceRestorePrecheckResponse>();
const latestRestore = ref<ResourceRestoreLatestResponse>();
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<ApiResponse<{ fileId: string }>>(
'/api/v1/files/upload',
form
);
if (!upload.data.success) throw new Error(upload.data.msg);
const precheck = await request.post<ApiResponse<ResourceRestorePrecheckResponse>>(
'/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<ApiResponse<CreateResourceRestoreResponse>>(
'/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<ApiResponse<CreateResourceRestoreResponse>>(
`/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<ApiResponse<ResourceRestoreLatestResponse>>(
'/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 重启后最近恢复结果展示
<script setup lang="ts">
import { computed, onMounted } from 'vue';
const { latestRestore, loadLatestRestore } = useResourceRestore();
onMounted(loadLatestRestore);
const latestText = computed(() => {
const latest = latestRestore.value;
if (!latest || latest.status === 'NONE') return '暂无资源恢复记录';
if (latest.status === 'SUCCESS') return `最近恢复成功:${latest.finishedAt ?? ''}`;
if (latest.status === 'FAILED') {
return `最近恢复失败:${latest.phase ?? ''} ${latest.message ?? ''}`;
}
return `最近恢复状态:${latest.status}`;
});
</script>
<template>
<div>{{ latestText }}</div>
</template>
8. 交互和展示建议
- 备份创建按钮点击后立即禁用,避免重复提交。
- 恢复预检通过后,必须展示备份号
backupId,让用户确认正在恢复哪个包。 precheckPassed=false时不要允许创建恢复任务。- 恢复流程只做一次用户确认;确认后前端按顺序调用创建恢复任务和 apply。
- apply 返回后不要继续请求恢复步骤接口;恢复侧没有步骤接口。
- TMS 重启后只调用 latest 展示最近恢复结果。
payloadDecrypted=false不表示失败,它表示预检阶段没有解密完整 payload。TMS_DB默认不包含角色、授权、会话和资源任务表,因此恢复不会覆盖新机器已有登录授权状态;操作审计日志默认随 TMS 库备份。- 服务端路径类字段不展示给普通用户,只用于运维排查。