Fix Bug #470: 住院医生工作站-手术申请单加载手术项目添加Redis缓存+修复loading状态
根因:getSurgeryPage接口缺少Redis缓存层,每次弹窗打开都直接查数据库。
修复:1. 后端getSurgeryPage添加Redis缓存(24h过期),与getAdviceBaseInfo保持一致
2. 前端getList()命中内存缓存时显式清除loading状态,防止加载动画卡住
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
72
BUG_470_ANALYSIS.md
Normal file
72
BUG_470_ANALYSIS.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Bug #470 分析报告
|
||||
|
||||
## 根因分析
|
||||
|
||||
### 症状
|
||||
住院医生工作站-手术申请单加载手术项目耗时过长,影响医生开单效率。
|
||||
|
||||
### 根本原因
|
||||
|
||||
**后端 `getSurgeryPage` 接口缺少 Redis 缓存层。**
|
||||
|
||||
与同模块的 `getAdviceBaseInfo`(已有24小时Redis缓存)不同,`getSurgeryPage` 每次调用都直接查询数据库。
|
||||
|
||||
**代码对比:**
|
||||
|
||||
- `getAdviceBaseInfo`(DoctorStationAdviceAppServiceImpl.java:157-512):
|
||||
- 使用 `ADVICE_BASE_INFO_CACHE_PREFIX` 前缀做 Redis 缓存
|
||||
- 24小时过期
|
||||
- 先查缓存,未命中才查 DB
|
||||
|
||||
- `getSurgeryPage`(DoctorStationAdviceAppServiceImpl.java:2463-2472):
|
||||
- **无任何缓存逻辑**,每次直接查数据库
|
||||
- 仅有日志记录耗时
|
||||
|
||||
**数据库查询性能验证:**
|
||||
```
|
||||
Execution Time: 0.400 ms (10102条手术项目,已有 idx_wor_activity_def_surgery 索引)
|
||||
Planning Time: 4.349 ms
|
||||
```
|
||||
数据库查询本身很快(<1ms),但每次弹窗打开都重复执行查询 + 序列化 + 网络传输,累积延迟明显。
|
||||
|
||||
**辅助因素:**
|
||||
1. `applicationFormBottomBtn.vue` 的对话框设置了 `destroy-on-close`,每次关闭都会销毁 Surgery 组件
|
||||
2. 前端虽有模块级内存缓存(`surgeryRecordsCache` / `surgeryMappedCache`),但首次加载仍需后端响应
|
||||
3. 前端 `getList()` 命中缓存时未清除 `loading.value`,导致 loading 动画可能卡住
|
||||
|
||||
### 影响范围
|
||||
|
||||
**涉及文件:**
|
||||
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java` — 后端手术分页查询实现(需加缓存)
|
||||
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/surgery.vue` — 前端手术申请单组件(需修复 loading 状态)
|
||||
|
||||
**涉及数据表:**
|
||||
- `wor_activity_definition` — 活动定义表(手术项目源表),10,102条手术记录
|
||||
- `adm_charge_item_definition` — 收费项定义表(定价关联)
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 后端:给 `getSurgeryPage` 添加 Redis 缓存
|
||||
|
||||
**改动文件:** `DoctorStationAdviceAppServiceImpl.java`
|
||||
|
||||
1. 新增缓存键常量:`SURGERY_PAGE_CACHE_PREFIX = "surgery:page:"`
|
||||
2. 在无搜索关键字时,尝试从 Redis 读取缓存
|
||||
3. 缓存未命中时,查询数据库后写入 Redis(24小时过期)
|
||||
4. 有搜索关键字时不缓存(避免缓存爆炸)
|
||||
|
||||
**改动量:** 约 20 行
|
||||
|
||||
### 前端:修复 `getList()` 缓存命中时的 loading 状态
|
||||
|
||||
**改动文件:** `surgery.vue`
|
||||
|
||||
1. 在 `getList()` 方法中,当命中内存缓存时,显式设置 `loading.value = false`
|
||||
|
||||
**改动量:** 1 行
|
||||
|
||||
## 验证计划
|
||||
|
||||
1. 编译验证 Java 代码
|
||||
2. 语法验证 Vue 文件:`node --check surgery.vue`
|
||||
3. 手动验证:登录医生工作站,打开手术申请单,观察加载速度(首次应有loading,二次打开应秒开)
|
||||
@@ -2463,12 +2463,34 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
public IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey) {
|
||||
log.info("getSurgeryPage 开始: orgId={}, page={}/{}, searchKey={}", organizationId, pageNo, pageSize, searchKey);
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// 无搜索时尝试从 Redis 缓存读取(手术项目变更频率低,适合缓存)
|
||||
String safeOrgId = organizationId != null ? organizationId.toString() : "";
|
||||
String cacheKey = "surgery:page:" + safeOrgId + ":" + pageNo + ":" + pageSize;
|
||||
boolean useCache = (searchKey == null || searchKey.trim().isEmpty());
|
||||
|
||||
if (useCache) {
|
||||
Object cachedObj = redisCache.getCacheObject(cacheKey);
|
||||
if (cachedObj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
|
||||
log.info("从 Redis 缓存获取手术项目, key: {}, records: {}", cacheKey,
|
||||
((IPage<?>) cachedObj).getRecords().size());
|
||||
return (IPage<SurgeryItemDto>) cachedObj;
|
||||
}
|
||||
}
|
||||
|
||||
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getSurgeryPage(
|
||||
new Page<>(pageNo, pageSize),
|
||||
PublicationStatus.ACTIVE.getValue(),
|
||||
organizationId,
|
||||
searchKey);
|
||||
log.info("getSurgeryPage 完成: {}ms, total={}, records={}", System.currentTimeMillis() - start, result.getTotal(), result.getRecords().size());
|
||||
|
||||
// 无搜索时将结果写入缓存
|
||||
if (useCache && result instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
|
||||
redisCache.setCacheObject(cacheKey, result, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
|
||||
log.info("缓存手术项目, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,7 @@ const getList = () => {
|
||||
if (surgeryMappedCache && surgeryMappedCache.length > 0) {
|
||||
applicationList.value = surgeryMappedCache;
|
||||
applicationListAll.value = surgeryRecordsCache;
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
loadPage('');
|
||||
|
||||
Reference in New Issue
Block a user