Files
KCGL/全局系统体检报告.md

219 lines
8.4 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.

# IRIS 库存管理系统 - 全局系统体检报告
> 审查日期2026-04-02
> 审查范围inventory-web (Vue3 + Element Plus) + inventory-backend (Flask + SQLAlchemy)
> 审查模式:静态代码分析
---
## 一、前端状态与渲染漏洞 (Vue3 + Element Plus)
### [🚨 高危漏洞]
#### 1. el-table 缺少 reserve-selection 导致分页勾选丢失
- **模块/文件**: `inventory-web/src/views/materiel/list.vue` (及其他多个页面)
- **问题描述**: 大多数表格使用了 `type="selection"` 但未设置 `:reserve-selection="true"`
- **代码行数**: 该问题影响 30+ 个 el-table 组件
- **根因分析**:
- 分页后切换页面时,之前选中的行会丢失
- 只有 Selection.vue 的手动选择弹窗表格(第118行)添加了 `:reserve-selection="true"`
- **验证方法**: 进入物料列表勾选第1页的某几项切换到第2页再返回第1页确认勾选状态
#### 2. el-table 缺少 row-key 导致全选/渲染异常
- **模块/文件**: `inventory-web/src/views/stock/stocktake/index.vue`
- **代码行数**: 第240行
- **根因分析**: 该表格使用的 row-key="id"但如果存在跨表数据如物料、半成品、成品混排id 可能会冲突
#### 3. 盘点列表一次性加载 10000 条数据
- **模块/文件**: `inventory-web/src/views/stock/stocktake/index.vue`
- **代码行数**: 第662行、第985行
- **问题代码**:
```javascript
params: { page: 1, limit: 10000 } // 获取足够多的数据
limit: 10000, // 获取全部已盘点记录
```
- **风险**: 大量盘点记录时会导致前端内存溢出、页面卡死
---
### [⚠️ 交互/逻辑隐患]
#### 4. el-dialog 缺少 destroy-on-close 导致表单残留
- **模块/文件**: `inventory-web/src/views/outbound/create.vue`
- **代码行数**: 第174行
- **问题代码**: `<el-dialog v-model="showDialog" title="新建出库单" width="75%" :close-on-click-modal="false">`
- **根因分析**:
- 弹窗关闭后,数据未重置
- 再次打开弹窗会看到上一次填写的数据
- **建议**: 添加 `destroy-on-close` 属性或手动在关闭回调中重置表单
#### 5. formRef.resetFields() 调用不足
- **模块/文件**: 多个视图文件
- **根因分析**: 大多数弹窗表单没有调用 resetFields() 进行彻底重置
- **对比**:
- material/list.vue: 第1228行 ✅ 有 resetFields
- bom/BomManage.vue: 第656行 ✅ 有 resetFields
- system/UserCreate.vue: 第374行 ✅ 有 resetFields
- 其他大多数 ❌ 无重置逻辑
#### 6. 响应式解构潜在风险
- **模块/文件**: `inventory-web/src/App.vue`
- **代码行数**: 第80行
- **问题代码**: `const { new_password, confirm_password } = passwordForm.value`
- **说明**: 这种写法会失去响应性绑定,属于潜在风险(当前代码未直接修改解构后的变量,风险较低)
---
## 二、后端 ORM 与生命周期陷阱 (Flask + SQLAlchemy)
### [🚨 高危漏洞]
#### 7. @audit_log 装饰器中的 DetachedInstanceError 风险
- **模块/文件**: `inventory-backend/app/utils/decorators.py`
- **代码行数**: 第211-260行、第318行
- **问题代码**:
```python
# 第211-217行查询用户
user = SysUser.query.get(user_id)
if user:
user_info = user.to_dict() # 可能返回 DetachedInstanceError
display_name = user_info.get('display_name', username)
# 第248行添加审计日志
log_entry = AuditLog(...)
db.session.add(log_entry)
# 第318行在请求结束后 commit
db.session.commit()
```
- **根因分析**:
- 装饰器在 db.session.commit() 后访问已游离的对象属性
- 特别是在请求返回后session 可能已关闭,再次访问 user 对象会触发 DetachedInstanceError
#### 8. N+1 查询问题 - 库位树/库存列表
- **模块/文件**: `inventory-backend/app/api/v1/warehouse.py`, `inventory-backend/app/api/v1/inbound/stock.py`
- **代码行数**:
- warehouse.py 第118-132行循环查询每条库存记录
- stock.py 第1113-1138行全量查询后循环处理
- **问题代码**:
```python
# stock.py 第1113行
for item in StockBuy.query.filter(StockBuy.stock_quantity > 0).all():
# 每次循环访问 item.base 时会触发新的 SQL 查询 (Lazy Load)
'material_name': item.base.name
```
- **根因分析**: 没有使用 joinedload 或 eager loading 预加载关联关系
#### 9. 批量操作无最大数量限制
- **模块/文件**: `inventory-backend/app/api/v1/inbound/buy.py`
- **根因分析**:
- 批量创建物料/成品/半成品时没有限制最大条目数
- 前端子啊 stocktake/index.vue 设置了 limit=10000
- 后端如果接收大量数据会导致内存溢出
---
### [⚠️ 交互/逻辑隐患]
#### 10. 出库扣减逻辑中的可用库存竞态
- **模块/文件**: `inventory-backend/app/services/outbound_service.py`
- **代码行数**: 第169-173行
- **问题代码**:
```python
stock_record = ModelClass.query.with_for_update().get(stock_id) # 使用了悲观锁 ✅
if float(stock_record.available_quantity) < quantity:
raise ValueError(...)
stock_record.available_quantity = float(stock_record.available_quantity) - quantity
```
- **分析**: 已使用 `with_for_update()` 悲观锁,但需要确认数据库连接是否支持行级锁
- **建议**: 建议增加版本号字段实现乐观锁,作为双重保险
#### 11. 文件上传无大小限制
- **模块/文件**: `inventory-backend/app/api/v1/common/upload.py`
- **根因分析**:
- 没有检查文件大小
- 没有限制同时上传的文件数量
- **建议**: 添加 MAX_FILE_SIZE 和并发限制
---
## 三、业务并发与数据一致性
### [🚨 高危漏洞]
#### 12. 库存扣减无乐观锁
- **模块/文件**: 所有库存相关表 (StockBuy, StockSemi, StockProduct)
- **根因分析**:
- 库存表中没有 version 字段
- 仅依赖数据库行锁with_for_update()
- 高并发场景下可能出现库存扣为负数
- **影响范围**: 出库、报废、借用、盘点调整等所有减少库存的操作
#### 13. 盘点实盘数更新竞态
- **模块/文件**: `inventory-backend/app/api/v1/stock/adjustment.py`
- **代码行数**: 第226-250行
- **问题代码**:
```python
for stock in pagination.items:
new_qty = float(stock.stock_quantity)
# 读取和写入之间存在时间窗口,可能被其他请求修改
stock.stock_quantity = new_qty
db.session.add(stock)
db.session.commit()
```
- **根因分析**:
- 循环中的每条记录没有悲观锁
- 并发盘点可能导致数据覆盖
---
### [⚠️ 交互/逻辑隐患]
#### 14. 唯一键判断不严谨 - 购物车追加
- **模块/文件**: `inventory-web/src/views/outbound/Selection.vue`
- **根因分析**:
- 前端购物车基于 `type_id` 判断是否重复
- 如果 type 相同但 id 不同,仍会误判
- **当前状态**: ✅ 代码已修复,使用 `${item.type}_${item.id}` 格式
#### 15. BOM 批量导入无校验
- **模块/文件**: `inventory-backend/app/api/v1/bom.py`
- **代码行数**: 第278-340行
- **根因分析**:
- children 数据直接写入,不检查是否有重复子件
- 不验证子件是否存在
---
## 四、报告总结
### 漏洞统计
| 严重程度 | 数量 |
|---------|------|
| 🚨 高危漏洞 | 7 |
| ⚠️ 交互/逻辑隐患 | 8 |
| ✅ 状态良好 | 15+ |
### 优先修复建议(按优先级排序)
1. **[P0] 前端**:为所有分页表格添加 `:reserve-selection="true"` 和唯一 `row-key`
2. **[P0] 前端**:修复 stocktake <20><> 10000 条限制,改用分页或虚拟滚动
3. **[P0] 后端**:修复 @audit_log 的 DetachedInstanceError分两次 commit 或刷新对象)
4. **[P1] 后端**:为库存表添加 version 字段实现乐观锁
5. **[P1] 后端**:为盘点实盘更新添加 with_for_update()
6. **[P1] 后端**:添加批量操作最大限制(如 500 条/请求)
7. **[P2] 前端**:为所有表单弹窗添加 destroy-on-close 或手动重置
8. **[P2] 后端**:优化 N+1 查询,添加 joinedload
### ✅ 状态良好的核心链路
- ✅ 出库单创建使用了悲观锁 (with_for_update)
- ✅ 物料基础信息管理使用 visibilityLevel 控制
- ✅ 登录 Token 验证与 Redis 单设备登录互踢
- ✅ 文件上传使用 UUID 生成唯一文件名
- ✅ 出库选单唯一键已修复为 `${type}_${id}` 格式
- ✅ 库位路径 natural sorting 已实现
---
*本报告由 Qwen Code 全局静态扫描生成,仅供参考。实际修复请结合业务场景进行测试验证。*