"""操作日志 P2:完整性校验、高风险预警和保留策略。""" import hashlib, json from services import db as _db from services.audit_schema import ensure_audit_schema _READY=False def _payload(row): keys=('id','user_id','action','detail','ip_address','created_at','page','field','event_type','result_status','risk_level','object_type','object_id','request_id') return json.dumps({k:str(row.get(k) or '') for k in keys},ensure_ascii=False,sort_keys=True,separators=(',',':')) def calculate_hash(row): return hashlib.sha256(_payload(row).encode('utf-8')).hexdigest() def ensure_governance_schema(): global _READY if _READY:return ensure_audit_schema() cols={r['COLUMN_NAME'] for r in _db.query("SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='dashboard_logs'")} if 'integrity_hash' not in cols:_db.query("ALTER TABLE dashboard_logs ADD COLUMN integrity_hash CHAR(64) NULL") if 'integrity_checked_at' not in cols:_db.query("ALTER TABLE dashboard_logs ADD COLUMN integrity_checked_at DATETIME NULL") _db.query("""CREATE TABLE IF NOT EXISTS audit_alerts(id BIGINT AUTO_INCREMENT PRIMARY KEY,log_id BIGINT NOT NULL,user_id INT NULL,alert_type VARCHAR(50) NOT NULL,risk_level VARCHAR(20) NOT NULL,title VARCHAR(200) NOT NULL,detail TEXT,status VARCHAR(20) NOT NULL DEFAULT 'open',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,resolved_at DATETIME NULL,resolved_by INT NULL,UNIQUE KEY uq_alert_log_type(log_id,alert_type),KEY idx_alert_status(status,created_at))""") _db.query("""CREATE TABLE IF NOT EXISTS audit_retention_config(id INT PRIMARY KEY DEFAULT 1,online_days INT NOT NULL DEFAULT 180,archive_days INT NOT NULL DEFAULT 730,auto_archive TINYINT NOT NULL DEFAULT 0,updated_by INT NULL,updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)""") _db.query("INSERT IGNORE INTO audit_retention_config(id) VALUES(1)") rows=_db.query("SELECT * FROM dashboard_logs WHERE integrity_hash IS NULL OR integrity_hash='' ORDER BY id") _db.query("""INSERT IGNORE INTO audit_alerts(log_id,user_id,alert_type,risk_level,title,detail) SELECT id,user_id,CASE WHEN risk_level='high' THEN 'high_risk' ELSE 'failed' END, risk_level,CASE WHEN risk_level='high' THEN '高风险操作' ELSE '失败或拒绝操作' END,detail FROM dashboard_logs WHERE risk_level='high' OR result_status IN ('failed','denied')""") if rows: conn=_db.get_db() try: with conn.cursor() as cur: cur.executemany("UPDATE dashboard_logs SET integrity_hash=%s WHERE id=%s",[(calculate_hash(r),r['id']) for r in rows]) conn.commit() finally:conn.close() _READY=True def finalize_log(log_id): if not log_id:return ensure_governance_schema(); rows=_db.query("SELECT * FROM dashboard_logs WHERE id=%s",(log_id,)) if not rows:return row=rows[0]; digest=calculate_hash(row); _db.query("UPDATE dashboard_logs SET integrity_hash=%s WHERE id=%s",(digest,log_id)) if row.get('risk_level')=='high' or row.get('result_status') in ('failed','denied'): title='高风险操作' if row.get('risk_level')=='high' else '失败或拒绝操作' _db.query("INSERT IGNORE INTO audit_alerts(log_id,user_id,alert_type,risk_level,title,detail) VALUES(%s,%s,%s,%s,%s,%s)",(log_id,row.get('user_id'),'high_risk' if row.get('risk_level')=='high' else 'failed',row.get('risk_level') or 'medium',title,row.get('detail') or '')) def verify_logs(limit=10000): ensure_governance_schema(); rows=_db.query("SELECT * FROM dashboard_logs ORDER BY id DESC LIMIT %s",(limit,)); bad=[] for row in rows: if row.get('integrity_hash')!=calculate_hash(row):bad.append(row['id']) return {'checked':len(rows),'invalid':len(bad),'invalid_ids':bad[:100]}