fix: Day2就绪 — 删除硬化+索引补全+全局异常处理

## base_service.py — MaterialBase删除硬化
- 新增 BomTable 引用检查(bom_usage_count)
- 物料被BOM引用时禁止删除,提示「请先解除BOM绑定」

## models/base.py + bom.py — 缺失索引补全
- material_base: company_name + is_enabled 添加 index=True
- bom_table: is_enabled 添加 index=True

## __init__.py — 全局异常处理
- @app.errorhandler(ValueError) → 400 + 自定义消息
- @app.errorhandler(404/405) → 标准错误
- @app.errorhandler(Exception) → 500 + 通用消息
  堆栈仅记录服务器日志,不泄露到前端
This commit is contained in:
yueli
2026-07-16 14:14:43 +08:00
parent 382b461b68
commit 23fc10a25f
4 changed files with 40 additions and 6 deletions

View File

@ -1,6 +1,6 @@
# 文件路径: inventory-backend/app/__init__.py # 文件路径: inventory-backend/app/__init__.py
from flask import Flask from flask import Flask, jsonify
from config import Config from config import Config
from app.extensions import db, migrate, cors, jwt from app.extensions import db, migrate, cors, jwt
from app.api.v1.scan import scan_bp from app.api.v1.scan import scan_bp
@ -295,4 +295,33 @@ def create_app():
except Exception as e: except Exception as e:
print(f"⚠️ 模型预加载发生未知错误: {e}") print(f"⚠️ 模型预加载发生未知错误: {e}")
# =========================================================
# 4. 全局异常处理(面向生产:不泄露堆栈)
# =========================================================
import logging
import traceback
@app.errorhandler(ValueError)
def handle_value_error(e):
"""业务校验异常 → 400 + 自定义消息"""
return jsonify({'code': 400, 'msg': str(e)}), 400
@app.errorhandler(404)
def handle_not_found(e):
return jsonify({'code': 404, 'msg': '资源不存在'}), 404
@app.errorhandler(405)
def handle_method_not_allowed(e):
return jsonify({'code': 405, 'msg': '请求方法不允许'}), 405
@app.errorhandler(Exception)
def handle_unhandled_exception(e):
"""兜底:任何未捕获异常 → 500 + 通用消息,堆栈仅记录日志"""
app.logger.error(f"Unhandled exception: {type(e).__name__}: {e}\n{traceback.format_exc()}")
return jsonify({
'code': 500,
'msg': '系统繁忙,请稍后再试',
'error': 'Internal Server Error'
}), 500
return app return app

View File

@ -13,7 +13,7 @@ class MaterialBase(db.Model):
# 1. 基础字段 # 1. 基础字段
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
company_name = db.Column(db.String(255), comment='所属公司') company_name = db.Column(db.String(255), index=True, comment='所属公司') # ★ 多租户过滤高频列
name = db.Column(db.String(255), nullable=False, index=True, comment='名称') # ★ 模糊搜索/精确定位高频列 name = db.Column(db.String(255), nullable=False, index=True, comment='名称') # ★ 模糊搜索/精确定位高频列
common_name = db.Column(db.String(255), comment='俗名') common_name = db.Column(db.String(255), comment='俗名')
@ -34,7 +34,7 @@ class MaterialBase(db.Model):
manual_link_remark = db.Column(db.Text, default='', comment='说明书备注') manual_link_remark = db.Column(db.Text, default='', comment='说明书备注')
# 启用状态 # 启用状态
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用') is_enabled = db.Column(db.Boolean, default=True, index=True, comment='是否启用') # ★ 状态过滤高频列
# 强制质检标记(采购入库时必须上传检测报告) # 强制质检标记(采购入库时必须上传检测报告)
is_inspection_required = db.Column(db.Boolean, default=False, comment='是否强制要求质检') is_inspection_required = db.Column(db.Boolean, default=False, comment='是否强制要求质检')

View File

@ -26,7 +26,7 @@ class BomTable(db.Model):
remark = db.Column(db.Text, comment='备注') remark = db.Column(db.Text, comment='备注')
# ★ 新增:启用状态 # ★ 新增:启用状态
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用') is_enabled = db.Column(db.Boolean, default=True, index=True, comment='是否启用') # ★ 状态过滤高频列
# 约束: 保证同一版本下的父子对唯一,允许不同版本存在 # 约束: 保证同一版本下的父子对唯一,允许不同版本存在
__table_args__ = ( __table_args__ = (

View File

@ -7,6 +7,7 @@ from app.models.inbound.buy import StockBuy
from app.models.inbound.semi import StockSemi from app.models.inbound.semi import StockSemi
from app.models.inbound.product import StockProduct from app.models.inbound.product import StockProduct
# from app.models.inbound.service import StockService # from app.models.inbound.service import StockService
from app.models.bom import BomTable
from sqlalchemy import or_, and_, func, case, desc, asc, cast, Numeric, text from sqlalchemy import or_, and_, func, case, desc, asc, cast, Numeric, text
import traceback import traceback
import json import json
@ -746,8 +747,11 @@ class MaterialBaseService:
buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count() buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count()
semi_usage_count = StockSemi.query.filter_by(base_id=m_id).count() semi_usage_count = StockSemi.query.filter_by(base_id=m_id).count()
prod_usage_count = StockProduct.query.filter_by(base_id=m_id).count() prod_usage_count = StockProduct.query.filter_by(base_id=m_id).count()
bom_usage_count = BomTable.query.filter(
or_(BomTable.parent_id == m_id, BomTable.child_id == m_id)
).count()
total_usage = buy_usage_count + semi_usage_count + prod_usage_count total_usage = buy_usage_count + semi_usage_count + prod_usage_count + bom_usage_count
if total_usage > 0: if total_usage > 0:
raise ValueError( raise ValueError(
@ -755,7 +759,8 @@ class MaterialBaseService:
f"- 采购库存记录: {buy_usage_count}\n" f"- 采购库存记录: {buy_usage_count}\n"
f"- 半成品库存记录: {semi_usage_count}\n" f"- 半成品库存记录: {semi_usage_count}\n"
f"- 成品库存记录: {prod_usage_count}\n" f"- 成品库存记录: {prod_usage_count}\n"
f"请先清理相关库存或仅‘禁用’此条目。" f"- BOM配方引用: {bom_usage_count}\n"
f"请先清理相关库存/BOM绑定或仅「禁用」此条目。"
) )
# 删除时同步清理向量记录 # 删除时同步清理向量记录