docs(bug): 诸葛亮分析报告 Bug #758
This commit is contained in:
122
MD/bugs/BUG_758_ANALYSIS.md
Normal file
122
MD/bugs/BUG_758_ANALYSIS.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Bug #758 诸葛亮分析报告
|
||||
|
||||
> **文档类型**: Bug分析
|
||||
> **分析时间**: 2026-06-12 15:34:50
|
||||
> **分析模型**: mimo-v2.5 (LLM深度分析)
|
||||
|
||||
---
|
||||
|
||||
## 基本信息
|
||||
- **Bug #**: 758
|
||||
- **标题**: 【门诊医生工作站-检验】检验信息下的新增的字段按钮点击会出现报错Cannot deserialize value of type `com.core.common.core.domain.entity.SysDictData` from Array value
|
||||
- **模块**: 门诊医生工作站
|
||||
- **提出人**: 王栩坤
|
||||
|
||||
---
|
||||
|
||||
I now have a complete understanding of the root cause. Let me provide the analysis.
|
||||
|
||||
---
|
||||
|
||||
### 一、Bug 理解
|
||||
|
||||
用户在门诊医生工作站的"检验信息"区域点击"新增"按钮时,页面弹出 Jackson 反序列化错误 `Cannot deserialize value of type SysDictData from Array value`,无法正常新增检验信息。期望点击新增后能正常清空表单、准备录入新检验申请单。
|
||||
|
||||
### 二、根因分析
|
||||
|
||||
**错误触发链路:**
|
||||
|
||||
```
|
||||
用户点击"新增"
|
||||
→ handleNewApplication()
|
||||
→ resetForm()
|
||||
→ getEncounterDiagnosis(encounterId) [API 调用]
|
||||
→ 后端返回 R<List<DiagnosisQueryDto>>
|
||||
→ DictAspect 拦截 @GetMapping 响应
|
||||
→ processDict() 发现 DiagnosisQueryDto.medTypeCode 有 @Dict(dictCode = "med_type")
|
||||
→ DictUtils.getDictLabel("med_type", value)
|
||||
→ DictUtils.getDictCache("med_type")
|
||||
→ mapper.convertValue(cached, TypeReference<List<SysDictData>>) ← 💥 这里抛异常
|
||||
→ DictAspect 无 try-catch,异常直接传播到前端
|
||||
```
|
||||
|
||||
**根因:`DictUtils.getDictCache()` 缺少异常处理**
|
||||
|
||||
- 文件:`core-common/src/main/java/com/core/common/utils/DictUtils.java:38-62`
|
||||
- Redis 中的字典缓存数据结构异常(可能是旧 Fastjson 序列化格式遗留、嵌套数组等)
|
||||
- Jackson `ObjectMapper.convertValue()` 无法将异常结构转为 `List<SysDictData>`,抛出 `JsonMappingException`
|
||||
- 该异常未被 `DictUtils` 或 `DictAspect` 捕获,直接传播为 HTTP 500 错误
|
||||
|
||||
**涉及的关键文件:**
|
||||
1. `core-common/src/main/java/com/core/common/utils/DictUtils.java` — `getDictCache()` 方法(核心问题)
|
||||
2. `healthlink-his-common/src/main/java/com/healthlink/his/common/aspectj/DictAspect.java` — `processDict()` 方法(缺少异常保护)
|
||||
3. `healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/controller/DoctorStationDiagnosisController.java` — 被 DictAspect 拦截的控制器
|
||||
4. `healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/dto/DiagnosisQueryDto.java` — 含 `@Dict` 注解的 DTO
|
||||
|
||||
**与历史 Bug 的关联:** 此前 commit `babd8d0c0` 修复了类似问题(Jackson 配置从 ObjectMapper bean 改回 Jackson2ObjectMapperBuilderCustomizer),但 `DictUtils.getDictCache()` 的 `convertValue` 仍缺少防御性异常处理。
|
||||
|
||||
### 三、修复方案
|
||||
|
||||
**修改 1:`DictUtils.getDictCache()` 添加 try-catch 防御**(核心修复)
|
||||
|
||||
文件:`core-common/src/main/java/com/core/common/utils/DictUtils.java`
|
||||
|
||||
在 `getDictCache()` 方法中,为 `mapper.convertValue()` 添加 try-catch,失败时清理损坏缓存并返回 null:
|
||||
|
||||
```java
|
||||
public static List<SysDictData> getDictCache(String key) {
|
||||
Object cached = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
|
||||
if (StringUtils.isNull(cached)) {
|
||||
return null;
|
||||
}
|
||||
if (cached instanceof List && ((List<?>) cached).stream().allMatch(e -> e instanceof SysDictData)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<SysDictData> result = (List<SysDictData>) cached;
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>> typeRef =
|
||||
new com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>>() {};
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
if (cached instanceof JsonNode jsonNode) {
|
||||
return mapper.convertValue(jsonNode, typeRef);
|
||||
}
|
||||
return mapper.convertValue(cached, typeRef);
|
||||
} catch (Exception e) {
|
||||
// 缓存数据格式异常,清理损坏缓存,下次重新加载
|
||||
org.slf4j.LoggerFactory.getLogger(DictUtils.class)
|
||||
.warn("字典缓存转换失败(key={}),已清理: {}", key, e.getMessage());
|
||||
removeDictCache(key);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**修改 2:`DictAspect.processDict()` 添加 try-catch 防御**(防止字典翻译失败影响 API 响应)
|
||||
|
||||
文件:`healthlink-his-common/src/main/java/com/healthlink/his/common/aspectj/DictAspect.java`
|
||||
|
||||
在 `processDict()` 方法中,对字典查询部分包裹 try-catch:
|
||||
|
||||
```java
|
||||
} else if (field.isAnnotationPresent(Dict.class)) {
|
||||
try {
|
||||
// ... 原有字典翻译逻辑 ...
|
||||
} catch (Exception e) {
|
||||
log.debug("字段 {} 字典翻译失败,跳过: {}", field.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 四、路由决策
|
||||
|
||||
**FIXER: guanyu(后端开发)**
|
||||
|
||||
**REASON:** 修复涉及 `DictUtils`(core-common 模块)和 `DictAspect`(healthlink-his-common 模块)两个后端 Java 文件的异常处理逻辑,纯后端修复,无需前端改动。
|
||||
|
||||
---
|
||||
|
||||
## 路由决策
|
||||
- **修复 Agent**: guanyu
|
||||
- **原因**: ** 修复涉及 `DictUtils`(core-common 模块)和 `DictAspect`(healthlink-his-common 模块)两个后端 Java 文件的异常处理逻辑,纯后端修复,无需前端改动。
|
||||
Reference in New Issue
Block a user