docs(bug): 诸葛亮分析报告 Bug #732

This commit is contained in:
2026-06-13 00:30:55 +08:00
parent 0db4dc726f
commit e21cc32634

195
MD/bugs/BUG_732_ANALYSIS.md Normal file
View File

@@ -0,0 +1,195 @@
# Bug #732 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-13 00:30:55
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 732
- **标题**: 【医嘱闭环-闭环统计】科室的闭环和未闭环医嘱预警加载卡死
- **模块**: 系统管理
- **提出人**: 王栩坤
---
Based on my analysis, I now have a clear picture of the root cause. Let me output the structured analysis.
---
### 一、Bug 理解
**禅道原文引用:**
- **标题**:【医嘱闭环-闭环统计】科室的闭环和未闭环医嘱预警加载卡死
- **重现步骤**登录内科医生1的账号doctor1 密码123456进入医嘱闭环打开闭环统计查看界面
- **结果**:出现报错:`main.js:84 TypeError: Cannot convert object to primitive value`
- **期望**:能够正常显示报卡的信息和操作界面
**附图分析关键信息:**
- 页面标题"科室/医生闭环统计",右侧有"按科室"下拉和"刷新"按钮
- 两个数据区域(科室/医生闭环统计表格、未闭环医嘱预警表格均为空白仅有蓝色加载动画圆圈loading spinner
- 右上角红色标签"X 条待处理"但数字被遮挡
- **核心异常**:控制台 `TypeError: Cannot convert object to primitive value`,导致两个表格数据加载卡死
**综合总结**:用户登录 doctor1 账号进入医嘱闭环统计页面,页面框架正常渲染但两个核心数据表格(科室/医生闭环统计、未闭环医嘱预警)均卡在 loading 状态无法加载数据。控制台报 `TypeError: Cannot convert object to primitive value`,表明前端在渲染过程中遇到了一个对象值被当作原始值使用的场景。
---
### 二、根因分析
经过全链路追踪(前端 Vue 组件 → API → Controller → AppService → Mapper SQL根因是**多层问题叠加**
#### 根因 1直接触发 TypeErrorPostgreSQL 列别名大小写折叠 + 前端 prop 不匹配
`OrderExecuteRecordMapper.java` 中的 4 个 SQL 查询使用了 camelCase 列别名:
```sql
-- selectGroupByDepartment
SELECT COALESCE(m.department_name, '未知') AS department, COUNT(*) AS totalOrders, ...
-- selectGroupByDoctor
SELECT COALESCE(m.doctor_name, '未知') AS doctorName, COUNT(*) AS totalOrders, ...
-- selectUnclosedWarnings
SELECT e.order_no AS orderNo, e.patient_name AS patientName, ...
```
**PostgreSQL 会将未加引号的标识符折叠为小写**。因此 JDBC ResultSet 返回的 Map key 全部是小写:
- `totalOrders``totalorders`
- `closedCount``closedcount`
- `orderNo``orderno`
- `patientName``patientname`
- `orderType``ordertype`
- `currentStep``currentstep`
- `orderTime``ordertime`
Java 的 `getLong()`/`getString()` helper 有 `key.toLowerCase()` fallback所以 Java 层计算正确。但 **`getGroupStats()` 返回的 Map 中同时存在**
- 小写 key来自 SQL`totalorders`, `closedcount`
- camelCase keyJava 代码 put`unclosedCount`, `closedRate`
前端模板使用 `prop="totalOrders"` 无法匹配 `totalorders`,导致表格列值为 `undefined`
#### 根因 2直接触发 TypeError`el-progress` 接收到非 number 类型的 `percentage`
`<el-progress :percentage="scope.row.closedRate || 0">` 中:
- `scope.row.closedRate` 是 Java 计算后 put 进 map 的 `double`,序列化为 JSON number → 正常
-`scope.row.closedRate` 如果因上游数据异常为非数字对象,`||` 不会拦截(对象是 truthy`el-progress` 内部尝试数值转换时抛出 `TypeError: Cannot convert object to primitive value`
#### 根因 3数据问题`getUnclosedWarnings()` 未用 helper 处理 `orderTime`
```java
// 第 143 行
Object orderTimeObj = row.get("orderTime"); // ← PostgreSQL 返回的是 "ordertime",永远 null
```
导致所有未闭环医嘱的 `overdueDuration` 都显示"未知"`orderTime` 都显示空字符串。
#### 根因 4缺失数据源`order_execute_record` 表与 `order_main` 表无关联数据
`selectGroupByDepartment` 等 SQL 做了 `LEFT JOIN order_main m ON e.order_no = m.order_no`,但 `order_execute_record` 是闭环追踪表,其中的 `order_no` 可能与 `order_main`(门诊挂号单表)中的 `order_no` **语义不同**——`order_main` 是门诊挂号/预约表,不是医嘱表。即使 JOIN 成功,`department_name``doctor_name` 也可能全为 NULL。
**涉及的关键文件:**
| 层 | 文件 | 行号 |
|---|---|---|
| Mapper SQL | `OrderExecuteRecordMapper.java` | 全部 4 个 `@Select` 方法 |
| AppService | `OrderClosedLoopAppServiceImpl.java` | `getGroupStats()` L131-143, `getUnclosedWarnings()` L146-168 |
| 前端组件 | `statistics/index.vue` | L74 `el-progress`, L84-88 `el-table-column`, L99-102 `el-progress` |
| 前端 API | `orderclosedloop.js` | `getClosedLoopStatistics()` |
---
### 三、修复方案
#### 修复 1SQL 列别名加双引号保留大小写(根本修复)
修改 `OrderExecuteRecordMapper.java`,所有 SQL 别名加 PostgreSQL 双引号:
```sql
-- selectOverviewByType
SELECT e.order_type AS "orderType",
COUNT(*) AS "totalOrders",
COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount"
-- selectGroupByDepartment
SELECT COALESCE(m.department_name, '未知') AS "department",
COUNT(*) AS "totalOrders",
COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount"
-- selectGroupByDoctor
SELECT COALESCE(m.doctor_name, '未知') AS "doctorName",
COUNT(*) AS "totalOrders",
COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount"
-- selectUnclosedWarnings
SELECT e.order_no AS "orderNo",
e.patient_name AS "patientName",
e.order_type AS "orderType",
COALESCE(m.department_name, '未知') AS "department",
COALESCE(m.doctor_name, '未知') AS "doctorName",
e.current_step AS "currentStep",
e.create_time AS "orderTime"
```
这样 JDBC 返回的 Map key 就是精确的 camelCase前端 `prop` 能正确匹配。
#### 修复 2`getUnclosedWarnings()` 使用 helper 处理 `orderTime`
`row.get("orderTime")` 改为使用 `getString()` helper 或直接用小写 key
```java
// 修改前
Object orderTimeObj = row.get("orderTime");
// 修改后(兼容大小写)
Object orderTimeObj = row.get("orderTime");
if (orderTimeObj == null) {
orderTimeObj = row.get("ordertime");
}
```
或者更彻底地,在修复 1SQL 加引号)之后,`row.get("orderTime")` 就能正确获取值。
#### 修复 3前端 `el-progress` 添加防御性数值转换
`statistics/index.vue` 中,确保传给 `el-progress``percentage` 始终为 number
```html
<!-- 修复前 -->
<el-progress :percentage="statistics.drugClosedRate || 0" :stroke-width="8" />
<!-- 修复后 -->
<el-progress :percentage="Number(statistics.drugClosedRate) || 0" :stroke-width="8" />
```
同理修复 `closedRate` 的绑定:
```html
<el-progress :percentage="Number(scope.row.closedRate) || 0" :stroke-width="12" />
```
#### 修复 4`getGroupStats()` 确保 key 一致性
`OrderClosedLoopAppServiceImpl.getGroupStats()` 中,显式 put camelCase key即使修复 1 后 SQL 已正确,这是防御性编码):
```java
// 在 item.put("unclosedCount", ...) 之前加:
item.put("totalOrders", totalOrders);
item.put("closedCount", closedCount);
```
---
### 四、路由决策
**FIXER: zhaoyun赵云 — 前端开发)**
**REASON**: 此 Bug 的直接表现是前端 TypeError 导致页面卡死,核心修复在前端模板的防御性编码(修复 3和后端 Mapper SQL 的列别名引号处理(修复 1/2/4。赵云负责前端修复`statistics/index.vue``el-progress` 的数值转换),同时需要协调荀彧协助后端 Mapper SQL 修改(`OrderExecuteRecordMapper.java`)。前端权重更高(页面卡死的直接原因是前端渲染),由赵云主导。
---
## 路由决策
- **FIXER_ID**: zhaoyun
- **修复 Agent**: zhaoyun前端
- **原因**: LLM 分析决策
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。