perf: 导出OOM修复(yield_per流式) + 采购模糊匹配安全防护
## base_service.py export_excel - query.all()全量→yield_per(2000)分块流式读取 - all_rows[]内存收集→write_only=True直写Excel - all_rows.sort()→ORDER BY数据库排序 - 删除~160行旧Excel样式/脱敏死代码 - 内存: 全量→2000行分块+输出流 ## purchase_service.py - 模糊匹配安全防护: len(n.strip())>=2 过滤空格/单字符 .limit(500) 防全表返回
This commit is contained in:
@ -205,29 +205,69 @@ class PurchaseService:
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
# ★ 批量预取 MaterialBase:三段式查询(base_id 精确 → name 精确 → name 模糊)
|
||||
purchase_items = list(pagination.items)
|
||||
base_ids = {p.base_id for p in purchase_items if p.base_id}
|
||||
names_no_base = [p.name for p in purchase_items if p.name and not p.base_id]
|
||||
|
||||
material_map = {} # base_id → MaterialBase
|
||||
name_map = {} # (name, spec) or (name, '__fallback__') → MaterialBase
|
||||
|
||||
# 阶段1: base_id 精确预取
|
||||
if base_ids:
|
||||
materials = MaterialBase.query.filter(MaterialBase.id.in_(base_ids)).all()
|
||||
material_map = {m.id: m for m in materials}
|
||||
|
||||
# 阶段2: name 精确预取(name.in_)
|
||||
if names_no_base:
|
||||
exact_matches = MaterialBase.query.filter(
|
||||
MaterialBase.name.in_(names_no_base),
|
||||
MaterialBase.is_enabled == True
|
||||
).all()
|
||||
for m in exact_matches:
|
||||
name_map[(m.name, m.spec_model or '')] = m
|
||||
|
||||
# 阶段3: name 模糊预取(仅>=2字符 + LIMIT 500 防爆炸)
|
||||
unmatched_names = []
|
||||
for p in purchase_items:
|
||||
if p.base_id and p.base_id in material_map:
|
||||
continue
|
||||
if p.name and (p.name, p.spec_model or '') in name_map:
|
||||
continue
|
||||
n = (p.name or '').strip()
|
||||
if len(n) >= 2: # ★ 安全阈值:至少2字符才做模糊匹配
|
||||
unmatched_names.append(n)
|
||||
|
||||
if unmatched_names:
|
||||
fuzzy_conditions = [
|
||||
MaterialBase.name.ilike(f'%{n}%') for n in unmatched_names
|
||||
]
|
||||
fuzzy_matches = MaterialBase.query.filter(
|
||||
db.or_(*fuzzy_conditions),
|
||||
MaterialBase.is_enabled == True
|
||||
).order_by(MaterialBase.id.desc()).limit(500).all() # ★ 硬上限:最多500条
|
||||
for m in fuzzy_matches:
|
||||
key = (m.name, '__fuzzy__')
|
||||
if key not in name_map:
|
||||
name_map[key] = m
|
||||
|
||||
# 内存匹配(O(1) 字典查找)
|
||||
items = []
|
||||
for p in pagination.items:
|
||||
for p in purchase_items:
|
||||
item = p.to_dict()
|
||||
material = None
|
||||
|
||||
# 附加物料基础信息
|
||||
# 1. base_id 精确
|
||||
if p.base_id:
|
||||
# 优先走 base_id 硬关联
|
||||
material = db.session.get(MaterialBase, p.base_id)
|
||||
material = material_map.get(p.base_id)
|
||||
|
||||
# 回退匹配:历史采购单没有 base_id,通过 name + spec_model 模糊匹配
|
||||
# 2. name + spec 精确
|
||||
if not material and p.name:
|
||||
material = MaterialBase.query.filter(
|
||||
MaterialBase.name == p.name,
|
||||
MaterialBase.spec_model == (p.spec_model or ''),
|
||||
MaterialBase.is_enabled == True
|
||||
).first()
|
||||
# 如果精确匹配失败,仅按 name 模糊匹配(取最新一条)
|
||||
if not material:
|
||||
material = MaterialBase.query.filter(
|
||||
MaterialBase.name.ilike(f'%{p.name}%'),
|
||||
MaterialBase.is_enabled == True
|
||||
).order_by(MaterialBase.id.desc()).first()
|
||||
material = name_map.get((p.name, p.spec_model or ''))
|
||||
|
||||
# 3. name 模糊回退
|
||||
if not material and p.name:
|
||||
material = name_map.get((p.name, '__fuzzy__'))
|
||||
|
||||
if material:
|
||||
item['material'] = {
|
||||
|
||||
Reference in New Issue
Block a user