WPS文档版本控制方案.md 9.5 KB

WPS 文档版本控制方案

需求

在 fileId 不变的情况下,让 WPS 重新从服务器获取文件。

解决方案

通过后端的文件信息接口返回不同的版本号,让 WPS 判断文件已更新,从而重新下载。

工作原理

WPS 缓存判断机制

WPS 通过以下字段判断是否需要重新下载文件:

  1. version(版本号)
  2. modify_time(修改时间)
  3. size(文件大小)

如果这些字段发生变化,WPS 会重新下载文件。

流程

用户打开文档:
    ↓
WPS 调用:GET /v1/3rd/file/info?_w_fileid=28
    ↓
后端返回:
{
  "file": {
    "id": "28",
    "name": "文档.pdf",
    "version": 1,  // ← 版本号
    "modify_time": 1735545600000,
    "size": 1024000
  }
}
    ↓
WPS 检查缓存:
  - 缓存中的版本号是 1
  - 服务器返回的版本号是 1
  - 版本号相同,使用缓存 ✅

用户点击"结束编辑":
    ↓
前端调用:PUT /wps/callback/v3/3rd/file/28/version
    ↓
后端更新版本号:version = 2
    ↓
销毁 WPS 实例

用户重新打开文档:
    ↓
WPS 调用:GET /v1/3rd/file/info?_w_fileid=28
    ↓
后端返回:
{
  "file": {
    "id": "28",
    "name": "文档.pdf",
    "version": 2,  // ← 版本号已更新
    "modify_time": 1735545700000,
    "size": 1024000
  }
}
    ↓
WPS 检查缓存:
  - 缓存中的版本号是 1
  - 服务器返回的版本号是 2
  - 版本号不同,重新下载 ✅

前端实现

1. 新增 API 接口

文件src/api/system/signature.ts

/**
 * 通知后端更新文档版本(用于强制 WPS 刷新)
 * @param documentId 文档 ID
 */
export function updateDocumentVersion(documentId: number | string): AxiosPromise<any> {
    return request({
        url: `/wps/callback/v3/3rd/file/${documentId}/version`,
        method: 'put'
    });
}

2. 修改"结束编辑"功能

文件src/components/DocumentAuditDialog/index.vue

const handleEndEdit = async () => {
  // ... 确认对话框 ...
  
  try {
    // 1. 调用 WPS SDK 的销毁方法
    if (wpsInstance.destroy) {
      await wpsInstance.destroy();
    }
    
    // 2. 通知后端更新文档版本
    try {
      await updateDocumentVersion(props.document.id);
      console.log('[WPS] 文档版本已更新');
    } catch (versionErr) {
      console.warn('[WPS] 更新文档版本失败(不影响主流程):', versionErr);
    }
    
    // 3. 清空实例
    wpsInstance = null;
    
    // 4. 显示成功提示并关闭对话框
    ElMessage.success('编辑已结束,文档已销毁');
    setTimeout(() => {
      dialogVisible.value = false;
    }, 1000);
    
  } catch (err) {
    // 错误处理...
  }
};

后端实现

1. 数据库表结构

需要在文档表中添加版本号字段:

ALTER TABLE document ADD COLUMN version INT DEFAULT 1;

或者使用修改时间:

ALTER TABLE document ADD COLUMN modify_time BIGINT;

2. 更新版本接口

接口地址PUT /wps/callback/v3/3rd/file/{documentId}/version

功能:更新文档版本号

实现示例

@PutMapping("/wps/callback/v3/3rd/file/{documentId}/version")
public R<Void> updateDocumentVersion(@PathVariable Long documentId) {
    // 1. 获取文档
    Document doc = documentService.getById(documentId);
    if (doc == null) {
        return R.fail("文档不存在");
    }
    
    // 2. 更新版本号
    doc.setVersion(doc.getVersion() + 1);
    doc.setModifyTime(System.currentTimeMillis());
    documentService.updateById(doc);
    
    log.info("文档版本已更新: id={}, version={}", documentId, doc.getVersion());
    
    return R.ok();
}

3. 文件信息接口

接口地址GET /v1/3rd/file/info

修改:返回版本号和修改时间

实现示例

@GetMapping("/v1/3rd/file/info")
public R<FileInfo> getFileInfo(@RequestParam("_w_fileid") String fileId) {
    // 1. 获取文档
    Document doc = documentService.getById(fileId);
    if (doc == null) {
        return R.fail("文档不存在");
    }
    
    // 2. 构建文件信息
    FileInfo fileInfo = new FileInfo();
    fileInfo.setId(doc.getId().toString());
    fileInfo.setName(doc.getFileName());
    fileInfo.setVersion(doc.getVersion());  // ← 版本号
    fileInfo.setModifyTime(doc.getModifyTime());  // ← 修改时间
    fileInfo.setSize(doc.getFileSize());
    fileInfo.setDownloadUrl(buildDownloadUrl(doc.getOssId()));
    
    return R.ok(fileInfo);
}

返回格式

{
  "code": 200,
  "msg": "success",
  "data": {
    "file": {
      "id": "28",
      "name": "文档.pdf",
      "version": 2,
      "modify_time": 1735545700000,
      "size": 1024000,
      "download_url": "http://your-server.com/resource/oss/downloadWithoutPermission/123"
    }
  }
}

完整流程

第一次打开文档

1. 用户打开文档
    ↓
2. WPS 调用:GET /v1/3rd/file/info?_w_fileid=28
    ↓
3. 后端返回:version=1, modify_time=1735545600000
    ↓
4. WPS 缓存中没有该文档
    ↓
5. WPS 下载文件:GET /resource/oss/downloadWithoutPermission/123
    ↓
6. WPS 缓存文件(version=1)
    ↓
7. 显示文档

结束编辑

1. 用户点击"结束编辑"
    ↓
2. 前端调用:wpsInstance.destroy()
    ↓
3. 前端调用:PUT /wps/callback/v3/3rd/file/28/version
    ↓
4. 后端更新:version=2, modify_time=1735545700000
    ↓
5. 前端清空实例:wpsInstance = null
    ↓
6. 关闭对话框

重新打开文档

1. 用户重新打开文档
    ↓
2. WPS 调用:GET /v1/3rd/file/info?_w_fileid=28
    ↓
3. 后端返回:version=2, modify_time=1735545700000
    ↓
4. WPS 检查缓存:
   - 缓存版本:version=1
   - 服务器版本:version=2
   - 版本不同!
    ↓
5. WPS 重新下载文件:GET /resource/oss/downloadWithoutPermission/123
    ↓
6. WPS 更新缓存(version=2)
    ↓
7. 显示最新文档 ✅

版本号策略

方案 1:递增版本号(推荐)

// 每次更新时递增
doc.setVersion(doc.getVersion() + 1);

优势

  • ✅ 简单明了
  • ✅ 易于理解
  • ✅ 便于调试

示例

初始:version = 1
第一次更新:version = 2
第二次更新:version = 3

方案 2:使用时间戳

// 使用当前时间戳作为版本号
doc.setVersion(System.currentTimeMillis());

优势

  • ✅ 自动递增
  • ✅ 包含时间信息
  • ✅ 不会重复

示例

初始:version = 1735545600000
第一次更新:version = 1735545700000
第二次更新:version = 1735545800000

方案 3:使用修改时间

// 只更新 modify_time,不使用 version
doc.setModifyTime(System.currentTimeMillis());

优势

  • ✅ 不需要额外字段
  • ✅ 符合语义
  • ✅ WPS 也会检查此字段

注意

  • WPS 会同时检查 versionmodify_time
  • 任一字段变化都会触发重新下载

调试日志

前端日志

[WPS] 开始结束编辑,文档 ID: 28
[WPS] 调用 destroy 方法
[WPS] destroy 方法调用成功
[WPS] 通知后端更新文档版本
[WPS] 文档版本已更新
[WPS] 结束编辑成功

后端日志

[INFO] 收到更新版本请求: documentId=28
[INFO] 当前版本: 1
[INFO] 更新后版本: 2
[INFO] 文档版本已更新: id=28, version=2

WPS 日志

[WPS] 获取文件信息: fileId=28
[WPS] 服务器版本: 2
[WPS] 缓存版本: 1
[WPS] 版本不同,重新下载文件
[WPS] 下载完成,更新缓存

测试场景

场景 1:正常流程

1. 打开文档(version=1)
2. 编辑文档
3. 点击"结束编辑"(version 更新为 2)
4. 重新打开文档
5. 预期:WPS 重新下载文件

场景 2:多次编辑

1. 打开文档(version=1)
2. 结束编辑(version=2)
3. 重新打开(version=2)
4. 结束编辑(version=3)
5. 重新打开(version=3)
6. 预期:每次都重新下载

场景 3:版本更新失败

1. 打开文档(version=1)
2. 点击"结束编辑"
3. 版本更新接口失败(version 仍为 1)
4. 重新打开文档
5. 预期:WPS 使用缓存(因为版本号未变)

常见问题

Q1: 为什么要更新版本号?

:让 WPS 知道文件已更新,需要重新下载。

Q2: 版本号必须递增吗?

:不必须,只要每次不同即可。但递增更易于理解和调试。

Q3: 如果版本更新失败怎么办?

:不影响主流程,只是下次打开时 WPS 会使用缓存。

Q4: 可以手动触发刷新吗?

:可以,调用版本更新接口即可。

Q5: 版本号会一直增长吗?

:是的,但这不是问题。可以定期重置或使用时间戳。

Q6: 需要修改数据库吗?

:是的,需要添加 version 字段或使用 modify_time 字段。

总结

核心思路

  • ✅ fileId 保持不变
  • ✅ 通过版本号控制缓存
  • ✅ 结束编辑时更新版本号
  • ✅ 重新打开时 WPS 检测到版本变化
  • ✅ WPS 重新下载文件

前端改动

  1. 新增 updateDocumentVersion API
  2. 在"结束编辑"时调用该 API

后端改动

  1. 添加 version 字段到数据库
  2. 实现版本更新接口
  3. 在文件信息接口中返回版本号

优势

  • ✅ fileId 不变,后端逻辑简单
  • ✅ 符合 WPS 的缓存机制
  • ✅ 可控性强
  • ✅ 易于调试

现在点击"结束编辑"后,重新打开文档时 WPS 会从你的服务器重新获取最新文件!