"""结构化操作日志公共模型。""" import re from services import db as _db _READY=False HIGH_ACTIONS={'权限变更','产品彻底删除','删除订单','下单回退','回退订单','修改成本价','价格审核','批量删除','数据修复','批量数据修复'} MEDIUM_ACTIONS={'产品批量编辑','产品导入','修改下单数量','订单字段编辑','到货批次及数量','品牌分类','新增供应商'} PAGE_PATHS=[ ('/api/planning','计划预测'),('/api/purchase','采购管理'),('/api/domestic','国内订单管理'),('/api/overseas','海外订单管理'),('/api/packaging','包材订单管理'),('/api/products','产品信息管理'),('/api/product','产品信息管理'),('/api/bom','物料清单管理'),('/api/supplier','供应商管理'),('/api/permissions','权限配置'),('/api/users','用户管理'),('/api/config','参数配置'),('/api/warehouse','仓库管理'),('/api/inventory','库存管理'),('/api/feedback','问题反馈'),('/api/knowledge','知识库')] def ensure_audit_schema(): global _READY if _READY:return column_rows=_db.query("SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='dashboard_logs'") if not isinstance(column_rows, (list, tuple)): _READY=True return cols={r['COLUMN_NAME'] for r in column_rows} defs={ 'event_type':"VARCHAR(40) NOT NULL DEFAULT 'data_change'",'result_status':"VARCHAR(20) NOT NULL DEFAULT 'success'",'risk_level':"VARCHAR(20) NOT NULL DEFAULT 'normal'",'object_type':"VARCHAR(100) NULL",'object_id':"VARCHAR(200) NULL",'old_values':"LONGTEXT NULL",'new_values':"LONGTEXT NULL",'request_method':"VARCHAR(10) NULL",'request_path':"VARCHAR(255) NULL",'request_id':"VARCHAR(64) NULL",'user_agent':"VARCHAR(500) NULL",'role_snapshot':"VARCHAR(50) NULL",'position_snapshot':"VARCHAR(100) NULL",'http_status':"INT NULL"} for name,definition in defs.items(): if name not in cols: _db.query(f"ALTER TABLE dashboard_logs ADD COLUMN `{name}` {definition}") indexes={r['INDEX_NAME'] for r in _db.query("SELECT INDEX_NAME FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='dashboard_logs'")} for name,sql in {'idx_logs_created':'created_at','idx_logs_user_created':'user_id,created_at','idx_logs_page_created':'page,created_at','idx_logs_action_created':'action,created_at','idx_logs_risk_created':'risk_level,created_at','idx_logs_result_created':'result_status,created_at','idx_logs_request':'request_id'}.items(): if name not in indexes: _db.query(f"CREATE INDEX {name} ON dashboard_logs ({sql})") high = ','.join(['%s'] * len(HIGH_ACTIONS)); medium = ','.join(['%s'] * len(MEDIUM_ACTIONS)) _db.query(f"UPDATE dashboard_logs SET risk_level='high' WHERE action IN ({high}) OR COALESCE(field,'') REGEXP '成本价|单价|权限|删除|回退'", tuple(HIGH_ACTIONS)) _db.query(f"UPDATE dashboard_logs SET risk_level='medium' WHERE risk_level<>'high' AND action IN ({medium})", tuple(MEDIUM_ACTIONS)) _READY=True def infer_page(path,action=''): for prefix,name in PAGE_PATHS: if str(path or '').startswith(prefix):return name return action or '' ACTION_PATHS = [ ('/api/domestic/orders/update','编辑国内订单'),('/api/packaging/orders/update','编辑包材订单'),('/api/overseas/orders/update','编辑海外订单'),('/api/purchase/add_pending','加入待下单'),('/api/purchase/confirm_order','确认下单'),('/api/new_products/bom/save','保存新品BOM'),('/api/bom/create_with_materials','保存物料清单'),('/api/bom/delete','删除物料清单'),('/api/users/update','编辑用户'),('/api/users/create','新增用户'),('/api/product/remark/save','保存产品批注'),('/api/planning/scheme/save','保存计划预测方案'),('/api/planning/scheme/delete','删除计划预测方案'),('/api/supplier/update','编辑供应商'),('/api/supplier/delete','删除供应商'),('/api/feedback/submit','提交问题反馈')] def infer_action(path, method=''): for prefix,name in ACTION_PATHS: if str(path or '').startswith(prefix):return name page=infer_page(path,'系统操作') return ('删除' if method=='DELETE' else '更新') + page def classify_event(action,path='',method=''): text=str(action or '') if '登录' in text:return 'login' if '权限' in text:return 'security' if '导入' in text:return 'import' if '导出' in text:return 'export' if '删除' in text or method=='DELETE':return 'delete' if any(x in text for x in ('新增','创建')):return 'create' if method in ('POST','PUT','PATCH'):return 'update' return 'data_change' def classify_risk(action='',field='',path=''): if action in HIGH_ACTIONS or re.search(r'成本价|单价|权限|删除|回退',str(field or '')) or re.search(r'permission|delete|rollback|update_price',str(path or ''),re.I):return 'high' if action in MEDIUM_ACTIONS:return 'medium' return 'normal' def infer_object(data,path=''): if not isinstance(data,dict):return ('','') for key in ('product_code','material_code','supplier_code','record_id','order_id','subject_id','target_id','id','user_id'): value=data.get(key) if value not in (None,''): return (key.replace('_code','').replace('_id',''),str(value)) return ('','')