|
|
@@ -0,0 +1,269 @@
|
|
|
+# 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(`referencedImageId` 和 `referencedImageURI`),这些 URL 包含了完整的服务器地址信息。
|
|
|
+
|
|
|
+**示例**:
|
|
|
+```javascript
|
|
|
+{
|
|
|
+ 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. 技术层面原因
|
|
|
+```typescript
|
|
|
+// 渲染注释时的逻辑
|
|
|
+const imageId = annotation.metadata?.referencedImageId;
|
|
|
+// 如果 imageId 中的服务器地址与当前环境不匹配
|
|
|
+// 则无法找到对应的 viewport,注释渲染失败
|
|
|
+const elements = this.findElementsByFOR(imageId);
|
|
|
+if (elements.length === 0) {
|
|
|
+ console.warn('未找到对应的 viewport');
|
|
|
+ return; // 渲染失败
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 💡 解决方案
|
|
|
+
|
|
|
+### 设计思路
|
|
|
+在渲染注释之前,自动将注释对象中的服务器地址替换为当前环境的服务器地址,确保注释数据能够正确匹配当前环境下的图像引用。
|
|
|
+
|
|
|
+### 核心实现
|
|
|
+
|
|
|
+#### 1. 新增 URL 更新方法
|
|
|
+在 `AnnotationManager` 类中新增 `updateAnnotationUrls` 私有方法:
|
|
|
+
|
|
|
+```typescript
|
|
|
+/**
|
|
|
+ * 更新注释对象中的 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 更新:
|
|
|
+
|
|
|
+```typescript
|
|
|
+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 的服务器部分:
|
|
|
+
|
|
|
+**匹配示例**:
|
|
|
+```javascript
|
|
|
+// 输入
|
|
|
+"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 自动更新)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 📊 技术细节
|
|
|
+
|
|
|
+### 依赖的工具函数
|
|
|
+```typescript
|
|
|
+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`
|
|
|
+
|
|
|
+### 新增的导入
|
|
|
+```typescript
|
|
|
+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)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 📝 总结
|
|
|
+
|
|
|
+这是一个典型的跨环境数据同步问题。通过在渲染时动态更新服务器地址,而不是在保存时硬编码,成功解决了注释数据在不同环境下的兼容性问题。
|
|
|
+
|
|
|
+**核心思想**:将环境相关的信息(服务器地址)延迟到使用时再确定,而不是在存储时固化,从而提高系统的灵活性和可移植性。
|