8.4 KiB
8.4 KiB
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行
- 问题代码:
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行
- 问题代码:
# 第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行:全量查询后循环处理
- 问题代码:
# 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行
- 问题代码:
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行
- 问题代码:
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+ |
优先修复建议(按优先级排序)
- [P0] 前端:为所有分页表格添加
:reserve-selection="true"和唯一row-key - [P0] 前端:修复 stocktake <20><> 10000 条限制,改用分页或虚拟滚动
- [P0] 后端:修复 @audit_log 的 DetachedInstanceError(分两次 commit 或刷新对象)
- [P1] 后端:为库存表添加 version 字段实现乐观锁
- [P1] 后端:为盘点实盘更新添加 with_for_update()
- [P1] 后端:添加批量操作最大限制(如 500 条/请求)
- [P2] 前端:为所有表单弹窗添加 destroy-on-close 或手动重置
- [P2] 后端:优化 N+1 查询,添加 joinedload
✅ 状态良好的核心链路
- ✅ 出库单创建使用了悲观锁 (with_for_update)
- ✅ 物料基础信息管理使用 visibilityLevel 控制
- ✅ 登录 Token 验证与 Redis 单设备登录互踢
- ✅ 文件上传使用 UUID 生成唯一文件名
- ✅ 出库选单唯一键已修复为
${type}_${id}格式 - ✅ 库位路径 natural sorting 已实现
本报告由 Qwen Code 全局静态扫描生成,仅供参考。实际修复请结合业务场景进行测试验证。