Files
his/MD/bugs/BUG_732_ANALYSIS.md

196 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 分析决策
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。