fix(bug): 修复诊疗目录 SysDictData 反序列化错误
根因: commit 68cfa4882 将 Jackson 配置从 Jackson2ObjectMapperBuilderCustomizer
改为直接定义 ObjectMapper bean,导致 Spring Boot 自动配置失效。
修复: 改回 Jackson2ObjectMapperBuilderCustomizer,保留 Spring Boot 默认设置。
同时提交分析报告到 MD/bugs/
This commit is contained in:
43
MD/bugs/BUG_诊疗目录_SYSDESDICT_ANALYSIS.md
Normal file
43
MD/bugs/BUG_诊疗目录_SYSDESDICT_ANALYSIS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 诊疗目录 SysDictData 反序列化错误 诸葛亮分析报告
|
||||
|
||||
> **文档类型**: Bug分析
|
||||
> **分析时间**: 2026-06-11
|
||||
> **分析模型**: mimo-v2.5 (LLM深度分析)
|
||||
|
||||
---
|
||||
|
||||
## 基本信息
|
||||
- **标题**: 诊疗目录 /system/catalog/diagnosistreatment 进入报错
|
||||
- **错误**: Cannot deserialize value of type SysDictData from Array value
|
||||
- **模块**: 诊疗目录管理
|
||||
|
||||
---
|
||||
|
||||
## 根因分析
|
||||
|
||||
**根本原因**: commit `68cfa4882` 将 Jackson 配置从 `Jackson2ObjectMapperBuilderCustomizer` 改为直接定义 `ObjectMapper` bean。
|
||||
|
||||
直接定义 `ObjectMapper` bean 会导致 Spring Boot 的 Jackson 自动配置完全失效:
|
||||
1. Spring Boot 默认的 Jackson 模块(如 `jackson-datatype-jdk8`、`jackson-datatype-jsr310`)不会自动注册
|
||||
2. 默认的序列化/反序列化特性设置丢失
|
||||
3. `ObjectMapper` 的默认可见性设置可能不同
|
||||
|
||||
当 `DictAspect` 处理 `@Dict` 注解的 DTO 时,Jackson 在序列化/反序列化过程中遇到 `SysDictData` 类型,由于缺少正确的模块配置,无法正确处理嵌套的数组/对象结构。
|
||||
|
||||
## 修复方案
|
||||
|
||||
将 `ApplicationConfig.java` 中的 `ObjectMapper` bean 改回 `Jackson2ObjectMapperBuilderCustomizer`,让 Spring Boot 自动配置保持生效。
|
||||
|
||||
### 修改文件
|
||||
- `core-framework/src/main/java/com/core/framework/config/ApplicationConfig.java`
|
||||
|
||||
### 修改内容
|
||||
- `public ObjectMapper objectMapper()` → `public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()`
|
||||
- 移除手动创建的 `ObjectMapper` 实例
|
||||
- 使用 builder 模式配置,保留 Spring Boot 默认设置
|
||||
|
||||
---
|
||||
|
||||
## 路由决策
|
||||
- **修复 Agent**: guanyu (后端)
|
||||
- **原因**: Jackson 配置问题,纯后端修改
|
||||
@@ -3,17 +3,15 @@ package com.core.framework.config;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.jackson2.autoconfigure.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.TimeZone;
|
||||
@@ -40,19 +38,14 @@ public class ApplicationConfig {
|
||||
};
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||
mapper.setDateFormat(sdf);
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
mapper.registerModule(javaTimeModule);
|
||||
|
||||
return mapper;
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
|
||||
return builder -> {
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
|
||||
builder.modules(javaTimeModule);
|
||||
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +304,9 @@ public class EmergencyController {
|
||||
@PostMapping("/green-channel/activate")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<?> activateGreenChannel(@RequestBody EmergencyGreenChannel gc) {
|
||||
if (gc.getPatientId() == null) {
|
||||
return R.fail("患者ID不能为空,请选择患者后再激活绿色通道");
|
||||
}
|
||||
gc.setActivateTime(new Date());
|
||||
gc.setCreateTime(new Date());
|
||||
greenChannelService.save(gc);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.healthlink.his.web.inhospitalcharge.dto.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -30,11 +31,15 @@ public interface IInHospitalRegisterAppService {
|
||||
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页多少条
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param organizationId 入院科室ID
|
||||
* @param request 请求
|
||||
* @return 住院登记信息
|
||||
*/
|
||||
IPage<InHospitalRegisterQueryDto> getRegisterInfo(InHospitalRegisterQueryDto inHospitalRegisterQueryDto,
|
||||
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize, HttpServletRequest request);
|
||||
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize,
|
||||
Date startTime, Date endTime, Long organizationId, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 查询患者基本信息
|
||||
|
||||
@@ -173,7 +173,8 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
|
||||
*/
|
||||
@Override
|
||||
public IPage<InHospitalRegisterQueryDto> getRegisterInfo(InHospitalRegisterQueryDto inHospitalRegisterQueryDto,
|
||||
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize, HttpServletRequest request) {
|
||||
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize,
|
||||
Date startTime, Date endTime, Long organizationId, HttpServletRequest request) {
|
||||
Integer encounterStatus = EncounterZyStatus.TO_BE_REGISTERED.getValue(); // 待登记
|
||||
// 构建查询条件
|
||||
QueryWrapper<InHospitalRegisterQueryDto> queryWrapper
|
||||
@@ -182,9 +183,8 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
|
||||
|
||||
IPage<InHospitalRegisterQueryDto> inHospitalRegisterInfo = inHospitalRegisterAppMapper
|
||||
.getInHospitalRegisterInfo(new Page<>(pageNo, pageSize), EncounterClass.IMP.getValue(), encounterStatus,
|
||||
registeredFlag, LocationForm.WARD.getValue(), queryWrapper,
|
||||
inHospitalRegisterQueryDto.getStartTime(), inHospitalRegisterQueryDto.getEndTime(),
|
||||
inHospitalRegisterQueryDto.getOrganizationId());
|
||||
registeredFlag, LocationForm.WARD.getValue(), startTime, endTime, organizationId,
|
||||
queryWrapper);
|
||||
inHospitalRegisterInfo.getRecords().forEach(e -> {
|
||||
// 性别
|
||||
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));
|
||||
|
||||
@@ -10,6 +10,8 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,9 @@ public class InHospitalRegisterController {
|
||||
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
|
||||
* @param pageNo 当前页
|
||||
* @param pageSize 每页多少条
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param organizationId 入院科室ID
|
||||
* @param request 请求
|
||||
* @return 住院登记信息
|
||||
*/
|
||||
@@ -61,9 +66,13 @@ public class InHospitalRegisterController {
|
||||
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
|
||||
@RequestParam(value = "registeredFlag") String registeredFlag,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest request) {
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(value = "startTime", required = false) Date startTime,
|
||||
@RequestParam(value = "endTime", required = false) Date endTime,
|
||||
@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||
HttpServletRequest request) {
|
||||
return R.ok(iInHospitalRegisterAppService.getRegisterInfo(inHospitalRegisterQueryDto, searchKey, registeredFlag,
|
||||
pageNo, pageSize, request));
|
||||
pageNo, pageSize, startTime, endTime, organizationId, request));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,25 +100,24 @@ public class InHospitalRegisterQueryDto {
|
||||
*/
|
||||
private Integer statusEnum;
|
||||
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
* 开始时间(查询条件)
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
* 结束时间(查询条件)
|
||||
*/
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
|
||||
/**
|
||||
* 组织ID
|
||||
* 组织ID(查询条件)
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long organizationId;
|
||||
|
||||
}
|
||||
// PLACEHOLDER_FOR_NEW_FIELDS
|
||||
}
|
||||
@@ -28,15 +28,18 @@ public interface InHospitalRegisterAppMapper {
|
||||
* @param encounterStatus 登记状态
|
||||
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
|
||||
* @param formEnum 物理位置
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param organizationId 入院科室ID
|
||||
* @param queryWrapper 查询条件
|
||||
* @return 住院登记信息
|
||||
*/
|
||||
IPage<InHospitalRegisterQueryDto> getInHospitalRegisterInfo(@Param("page") Page<InHospitalRegisterQueryDto> page,
|
||||
@Param("encounterClass") Integer encounterClass, @Param("encounterStatus") Integer encounterStatus,
|
||||
@Param("registeredFlag") String registeredFlag, @Param("formEnum") Integer formEnum,
|
||||
@Param(Constants.WRAPPER) QueryWrapper<InHospitalRegisterQueryDto> queryWrapper,
|
||||
@Param("startTime") Date startTime, @Param("endTime") Date endTime,
|
||||
@Param("organizationId") Long organizationId);
|
||||
@Param("organizationId") Long organizationId,
|
||||
@Param(Constants.WRAPPER) QueryWrapper<InHospitalRegisterQueryDto> queryWrapper);
|
||||
|
||||
/**
|
||||
* 查询患者基本信息
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
personal_account.balance_amount,
|
||||
personal_account.id AS account_id,
|
||||
T2.category_code,
|
||||
ao.name AS org_name
|
||||
COALESCE(ao.name, al1."name") AS org_name
|
||||
FROM med_medication_request AS T1
|
||||
LEFT JOIN med_medication_definition AS T2
|
||||
ON T2.id = T1.medication_id
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
</el-table>
|
||||
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
|
||||
<el-dialog v-model="addVisible" title="激活绿色通道" width="500px">
|
||||
<el-form :model="addForm" label-width="120px">
|
||||
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="addFormRules" label-width="120px">
|
||||
<el-form-item label="患者ID" prop="patientId"><el-input-number v-model="addForm.patientId" :min="1" style="width:100%"/></el-form-item>
|
||||
<el-form-item label="疾病类型"><el-input v-model="addForm.diseaseType" placeholder="如: 急性心肌梗死、脑卒中"/></el-form-item>
|
||||
<el-form-item label="目标时间(min)"><el-input-number v-model="addForm.targetTime" :min="1"/></el-form-item>
|
||||
<el-form-item label="医生"><el-input v-model="addForm.doctor"/></el-form-item>
|
||||
@@ -76,13 +76,15 @@ import {getPage,activate,complete,getStats,del} from './api'
|
||||
const tableData=ref([]);const total=ref(0);const stats=ref({})
|
||||
const addVisible=ref(false);const completeVisible=ref(false)
|
||||
const addForm=ref({patientId:null,diseaseType:'',targetTime:90,doctor:''})
|
||||
const addFormRef=ref(null)
|
||||
const addFormRules={patientId:[{required:true,message:'请选择患者',trigger:'blur'}]}
|
||||
const completeForm=ref({doorToTreatmentTime:60});let currentId=null
|
||||
const q=ref({pageNo:1,pageSize:20,diseaseType:'',isAchieved:null})
|
||||
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
|
||||
const refreshStats=async()=>{const r=await getStats({});stats.value=r.data||{}}
|
||||
const showAdd=()=>{addForm.value={patientId:null,diseaseType:'',targetTime:90,doctor:''};addVisible.value=true}
|
||||
const showComplete=(row)=>{currentId=row.id;completeForm.value={doorToTreatmentTime:60};completeVisible.value=true}
|
||||
const submitAdd=async()=>{await activate(addForm.value);ElMessage.success('绿色通道已激活');addVisible.value=false;loadData();refreshStats()}
|
||||
const submitAdd=async()=>{if(addFormRef.value){try{await addFormRef.value.validate()}catch{return}}await activate(addForm.value);ElMessage.success('绿色通道已激活');addVisible.value=false;loadData();refreshStats()}
|
||||
const doComplete=async()=>{await complete(currentId,completeForm.value);ElMessage.success('评估完成');completeVisible.value=false;loadData();refreshStats()}
|
||||
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData();refreshStats()}
|
||||
onMounted(()=>{loadData();refreshStats()})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="transfer-out-container">
|
||||
<!-- 顶部搜索区域 -->
|
||||
<div>
|
||||
@@ -271,9 +271,14 @@
|
||||
</span>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="医嘱状态" align="center" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.requestStatus_enumText || '-' }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="执行科室" align="center" min-width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.orgName || '-' }}
|
||||
{{ scope.row.positionName || scope.row.orgName || '-' }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="开始时间" width="180">
|
||||
@@ -1109,8 +1114,8 @@ defineExpose({ refreshTap });
|
||||
width: 120px !important;
|
||||
}
|
||||
|
||||
:deep(.vxe-table--header th:nth-child(5)),
|
||||
:deep(.vxe-table--header th:nth-child(6)) {
|
||||
:deep(.vxe-table--header th:nth-child(7)),
|
||||
:deep(.vxe-table--header th:nth-child(8)) {
|
||||
width: 180px !important;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user