本文目录导读:

在SQLAlchemy中实现自动审计日志(记录数据变更历史)有几种主要方法,这里提供几种常用方案的实现代码:
使用事件监听器(推荐方案)
这是最灵活、最常用的方法,通过监听 SQLAlchemy 的事件来实现。
from sqlalchemy import create_engine, Column, Integer, String, DateTime, event
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime
import json
Base = declarative_base()
# 审计日志表
class AuditLog(Base):
__tablename__ = 'audit_logs'
id = Column(Integer, primary_key=True)
table_name = Column(String(100))
record_id = Column(Integer)
action = Column(String(20)) # INSERT, UPDATE, DELETE
old_values = Column(String(1000)) # JSON格式存储
new_values = Column(String(1000)) # JSON格式存储
changed_by = Column(String(100))
changed_at = Column(DateTime, default=datetime.utcnow)
# 示例业务表
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(100))
age = Column(Integer)
# 创建审计日志的装饰器
def audit_log(target_class, exclude_columns=None):
if exclude_columns is None:
exclude_columns = ['id', 'created_at', 'updated_at']
@event.listens_for(target_class, 'after_insert')
def after_insert(mapper, connection, target):
create_audit_log(
connection,
target_class.__tablename__,
target.id,
'INSERT',
{},
get_changed_values(target, exclude_columns),
get_current_user()
)
@event.listens_for(target_class, 'after_update')
def after_update(mapper, connection, target):
# 获取旧值(需要从session中获取)
session = Session.object_session(target)
if session:
old_values = {}
for attr in target.__table__.columns.keys():
if attr not in exclude_columns:
# 获取变更前的值
history = session.get_history(target, attr)
if history.has_changes():
old_values[attr] = history.deleted[0] if history.deleted else None
if old_values: # 只在有变化时记录
create_audit_log(
connection,
target_class.__tablename__,
target.id,
'UPDATE',
old_values,
get_changed_values(target, exclude_columns),
get_current_user()
)
@event.listens_for(target_class, 'before_delete')
def before_delete(mapper, connection, target):
old_values = get_all_values(target, exclude_columns)
create_audit_log(
connection,
target_class.__tablename__,
target.id,
'DELETE',
old_values,
{},
get_current_user()
)
def create_audit_log(connection, table_name, record_id, action, old_values, new_values, changed_by):
audit_entry = AuditLog(
table_name=table_name,
record_id=record_id,
action=action,
old_values=json.dumps(old_values, default=str),
new_values=json.dumps(new_values, default=str),
changed_by=changed_by,
changed_at=datetime.utcnow()
)
connection.execute(audit_entry.__table__.insert().values(
table_name=audit_entry.table_name,
record_id=audit_entry.record_id,
action=audit_entry.action,
old_values=audit_entry.old_values,
new_values=audit_entry.new_values,
changed_by=audit_entry.changed_by,
changed_at=audit_entry.changed_at
))
def get_changed_values(target, exclude_columns):
"""获取模型实例的所有非排除字段值"""
return {
col.name: getattr(target, col.name)
for col in target.__table__.columns
if col.name not in exclude_columns
}
def get_all_values(target, exclude_columns):
"""获取模型实例的所有值"""
return {
col.name: getattr(target, col.name)
for col in target.__table__.columns
if col.name not in exclude_columns
}
def get_current_user():
"""获取当前用户(需要根据你的认证机制实现)"""
# 示例:从Flask的request中获取
# from flask import request
# return getattr(request, 'user', 'anonymous')
return 'anonymous'
# 应用审计日志到指定模型
audit_log(User, exclude_columns=['id'])
# 数据库初始化
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
使用混合类(Mixin)方案
from sqlalchemy import Column, Integer, DateTime, event
from sqlalchemy.orm import declarative_mixin
from datetime import datetime
import json
class AuditLogMixin:
"""审计日志混合类"""
def create_audit_entry(self, action, session):
from sqlalchemy import inspect
from sqlalchemy.orm import class_mapper
# 获取表名和主键
mapper = class_mapper(self.__class__)
table_name = mapper.tables[0].name
primary_key = inspect(self).identity[0]
# 获取变更前后的值
old_values = {}
new_values = {}
for attr in mapper.attrs:
if isinstance(attr, Column):
col_name = attr.key
if col_name not in ['id', 'created_at', 'updated_at']:
history = inspect(self).attrs[col_name].history
if history.has_changes():
old_values[col_name] = history.deleted[0] if history.deleted else None
new_values[col_name] = history.added[0] if history.added else None
# 创建审计日志
audit_log = {
'table_name': table_name,
'record_id': primary_key,
'action': action,
'old_values': json.dumps(old_values, default=str),
'new_values': json.dumps(new_values, default=str),
'changed_by': 'system',
'changed_at': datetime.utcnow()
}
return audit_log
# 使用方法
class UserWithAudit(Base, AuditLogMixin):
__tablename__ = 'users_with_audit'
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(100))
age = Column(Integer)
使用数据库触发器方案
-- PostgreSQL触发器示例
CREATE TABLE audit_logs (
id SERIAL PRIMARY KEY,
table_name VARCHAR(100),
record_id INTEGER,
action VARCHAR(20),
old_values JSONB,
new_values JSONB,
changed_by VARCHAR(100),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO audit_logs(table_name, record_id, action, old_values, new_values, changed_by)
VALUES (TG_TABLE_NAME, NEW.id, 'INSERT', '{}'::jsonb, row_to_json(NEW), current_user);
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO audit_logs(table_name, record_id, action, old_values, new_values, changed_by)
VALUES (TG_TABLE_NAME, NEW.id, 'UPDATE', row_to_json(OLD), row_to_json(NEW), current_user);
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO audit_logs(table_name, record_id, action, old_values, new_values, changed_by)
VALUES (TG_TABLE_NAME, OLD.id, 'DELETE', row_to_json(OLD), '{}'::jsonb, current_user);
RETURN OLD;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 为需要审计的表添加触发器
CREATE TRIGGER users_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
高级方案:使用SQLAlchemy-Continuum
这是一个专门的审计日志库,提供了更多功能:
# 安装: pip install sqlalchemy-continuum
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy_continuum import make_versioned
from sqlalchemy_continuum.plugins import PropertyModTrackerPlugin
# 启用版本控制
make_versioned(plugins=[PropertyModTrackerPlugin()])
class User(Base):
__tablename__ = 'users'
__versioned__ = {}
id = Column(Integer, primary_key=True)
username = Column(String(50))
email = Column(String(100))
age = Column(Integer)
# 创建数据库
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
推荐使用方案
对于大多数项目,我推荐使用方案1(事件监听器),它具有以下优点:
- 完全透明:不需要修改业务代码
- 灵活控制:可以只对特定表或特定字段启用
- 易于定制:可以添加自定义字段(如用户IP、请求ID等)
- 性能可控:可以批量写入或异步处理
选择建议:
- 简单项目:使用方案1(事件监听器)
- 需要更强大的功能:使用方案4(sqlalchemy-continuum)
- 对性能要求极高:使用方案3(数据库触发器)
- 需要特定审计逻辑:使用方案2(混合类)
根据你的具体需求选择合适的方案即可。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播