bug-annotation-cross-environment-rendering-fix.md 8.2 KB

Bug 修复文档:注释跨环境渲染问题

Bug ID: annotation-cross-environment-rendering 修复日期: 2025-12-16 版本: 1.10.2 提交哈希: 8b4fe68


📋 Bug 描述

问题现象

浏览器端保存的图像注释数据,在桌面端(Electron)环境下无法正确渲染显示。

复现步骤

  1. 在浏览器端(Web 版本)打开医学影像
  2. 使用注释工具(如线段测量、角度测量等)在图像上创建注释
  3. 保存注释数据到后端
  4. 关闭浏览器,打开桌面端(Electron)应用
  5. 打开同一张图像
  6. 问题:之前创建的注释无法显示

影响范围

  • 影响所有跨环境使用的注释数据
  • 影响浏览器端 ↔ 桌面端的注释共享
  • 影响不同网络环境下的注释重现

🔍 根本原因分析

1. 数据结构问题

注释数据在保存时,会记录图像的引用 URL(referencedImageIdreferencedImageURI),这些 URL 包含了完整的服务器地址信息。

示例

{
  metadata: {
    referencedImageId: "dicomweb:http://192.168.110.245:6001/dicomweb/studies/...",
    referencedImageURI: "http://192.168.110.245:6001/dicomweb/studies/..."
  }
}

2. 环境切换导致地址不匹配

当用户在不同环境下访问应用时:

  • 浏览器端:可能通过 http://localhost:6001 或其他代理地址访问
  • 桌面端:可能通过 http://192.168.110.245:6001 或配置的其他地址访问

注释数据中硬编码的服务器地址与当前环境不匹配,导致:

  1. Cornerstone3D 无法根据 referencedImageId 找到对应的图像
  2. 无法定位到正确的 viewport element
  3. 注释渲染失败

3. 技术层面原因

// 渲染注释时的逻辑
const imageId = annotation.metadata?.referencedImageId;
// 如果 imageId 中的服务器地址与当前环境不匹配
// 则无法找到对应的 viewport,注释渲染失败
const elements = this.findElementsByFOR(imageId);
if (elements.length === 0) {
  console.warn('未找到对应的 viewport');
  return; // 渲染失败
}

💡 解决方案

设计思路

在渲染注释之前,自动将注释对象中的服务器地址替换为当前环境的服务器地址,确保注释数据能够正确匹配当前环境下的图像引用。

核心实现

1. 新增 URL 更新方法

AnnotationManager 类中新增 updateAnnotationUrls 私有方法:

/**
 * 更新注释对象中的 URL,替换为当前服务器地址
 * @param annotation 注释对象
 * @returns 更新后的注释对象
 */
private updateAnnotationUrls(annotation: any): any {
  try {
    const currentIpPort = getIpPort();
    if (!currentIpPort) {
      console.warn('【annotationmanager】无法获取当前服务器地址,跳过 URL 更新');
      return annotation;
    }

    // 正则表达式:匹配 URL 中的协议+域名/IP+端口部分
    // 例如:dicomweb:http://192.168.110.245:6001/path -> 提取 http://192.168.110.245:6001
    const urlRegex = /(https?:\/\/[^\/]+)/;

    // 更新 metadata.referencedImageId
    if (annotation.metadata?.referencedImageId) {
      const originalUrl = annotation.metadata.referencedImageId;
      const updatedUrl = originalUrl.replace(urlRegex, currentIpPort);
      annotation.metadata.referencedImageId = updatedUrl;
      console.log(`【annotationmanager】已更新 referencedImageId: ${originalUrl} -> ${updatedUrl}`);
    }

    // 更新 metadata.referencedImageURI(如果存在)
    if (annotation.metadata?.referencedImageURI) {
      const originalUrl = annotation.metadata.referencedImageURI;
      const updatedUrl = originalUrl.replace(urlRegex, currentIpPort);
      annotation.metadata.referencedImageURI = updatedUrl;
      console.log(`【annotationmanager】已更新 referencedImageURI: ${originalUrl} -> ${updatedUrl}`);
    }

    return annotation;
  } catch (error) {
    console.error('【annotationmanager】更新注释 URL 失败:', error);
    return annotation;
  }
}

2. 集成到渲染流程

renderAnnotations 方法中,反序列化后立即调用 URL 更新:

private async renderAnnotations(annotationStrings: string[]): Promise<void> {
  console.log(`【annotationmanager】🎨 渲染 ${annotationStrings.length} 个注释`);

  for (const annotationString of annotationStrings) {
    try {
      // 1. 反序列化为Cornerstone格式
      let deserializedAnnotation = this.serializer.deserialize(annotationString);

      // 2. 更新注释中的 URL,替换为当前服务器地址
      deserializedAnnotation = this.updateAnnotationUrls(deserializedAnnotation);

      // 3. 继续后续的渲染逻辑...
      const imageId = deserializedAnnotation.metadata?.referencedImageId;
      // ...
    } catch (error) {
      console.error('【annotationmanager】渲染注释失败:', error);
    }
  }
}

URL 匹配逻辑

使用正则表达式 /(https?:\/\/[^\/]+)/ 精确匹配 URL 的服务器部分:

匹配示例

// 输入
"dicomweb:http://192.168.110.245:6001/dicomweb/studies/1.2.3/series/4.5.6"

// 匹配到
"http://192.168.110.245:6001"

// 替换为当前服务器地址
"dicomweb:http://localhost:6001/dicomweb/studies/1.2.3/series/4.5.6"

✅ 验证方案

测试场景 1:浏览器 → 桌面端

  1. 在浏览器端创建注释并保存
  2. 打开桌面端应用
  3. 打开同一张图像
  4. 预期:注释正常显示

测试场景 2:桌面端 → 浏览器

  1. 在桌面端创建注释并保存
  2. 打开浏览器端应用
  3. 打开同一张图像
  4. 预期:注释正常显示

测试场景 3:服务器地址变更

  1. 在服务器 A 创建注释
  2. 修改服务器配置为服务器 B
  3. 打开同一张图像
  4. 预期:注释正常显示(URL 自动更新)

📊 技术细节

依赖的工具函数

import { getIpPort } from '../../../API/config';
  • getIpPort(): 获取当前环境的服务器地址(如 http://localhost:6001

容错处理

  1. 服务器地址获取失败:跳过 URL 更新,但不影响渲染流程
  2. URL 格式异常:捕获异常,返回原始注释对象
  3. 字段不存在:使用可选链操作符 ?. 避免空指针异常

日志输出

【annotationmanager】已更新 referencedImageId: 
  dicomweb:http://192.168.110.245:6001/dicomweb/... 
  -> dicomweb:http://localhost:6001/dicomweb/...

🎯 核心改进

1. 跨环境兼容性

✅ 注释数据可以在不同服务器环境之间无缝切换 ✅ 支持浏览器端和桌面端的数据共享

2. 自动化处理

✅ 无需手动修改注释数据 ✅ 自动检测并替换服务器地址

3. 容错能力

✅ 服务器地址获取失败时不影响渲染 ✅ 完整的错误处理和日志记录

4. 维护性

✅ 代码结构清晰,易于理解 ✅ 日志输出详细,便于调试


📁 相关文件

修改的文件

  • src/features/imageAnnotation/services/AnnotationManager.ts

新增的导入

import { getIpPort } from '../../../API/config';

版本更新

  • package.json: 1.10.1 → 1.10.2
  • CHANGELOG.md: 添加版本 1.10.2 的变更记录

🔗 相关提交

Commit: 8b4fe68 Message:

fix: 修复注释跨环境渲染问题

- 新增 updateAnnotationUrls 方法,自动更新注释对象中的服务器地址
- 修复浏览器端保存的注释数据在桌面端(Electron)无法渲染的问题
- 在渲染注释时自动替换 referencedImageId 和 referencedImageURI 中的服务器地址
- 使用正则表达式匹配并替换 URL 中的协议+域名/IP+端口部分
- 新增服务器地址获取失败的容错处理和日志记录

改动文件:
- src/features/imageAnnotation/services/AnnotationManager.ts
- CHANGELOG.md
- package.json (版本更新: 1.10.1 -> 1.10.2)

📝 总结

这是一个典型的跨环境数据同步问题。通过在渲染时动态更新服务器地址,而不是在保存时硬编码,成功解决了注释数据在不同环境下的兼容性问题。

核心思想:将环境相关的信息(服务器地址)延迟到使用时再确定,而不是在存储时固化,从而提高系统的灵活性和可移植性。