dy-dashboard/services/audit_governance.py
2026-10-28 11:14:17 +08:00

50 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""操作日志 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]}