在后端开发中,记录用户对数据库的操作日志(如增、删、改)不仅是调试和监控的有效工具,还能提升系统的可维护性和安全性。本文将介绍如何在 FastAPI 中使用装饰器来记录数据库操作日志,并保留修改前和修改后的内容。

一、为什么需要操作日志?

  1. 审计:操作日志能够追踪谁在何时对数据进行了哪些修改,尤其在处理敏感数据时非常有必要。
  2. 调试:当系统出现问题时,通过日志可以快速查明问题根源,尤其是在涉及数据一致性的问题时。
  3. 安全:可以识别恶意操作或非授权修改,从而采取相应的安全措施。

通过记录操作前后的数据状态,我们能够清楚地知道数据库的变化,并且可以在系统出错或需要回滚时进行详细分析。

二、项目设置

1. 数据库设置

首先,我们创建一个日志记录表,用于保存用户操作的详细信息。我们将使用 PostgreSQL 来存储数据(当然,你可以使用任何你熟悉的数据库系统)。

CREATE TABLE operation_logs (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL, -- 操作用户的ID
    operation_type VARCHAR(50) NOT NULL, -- 操作类型,INSERT, UPDATE, DELETE
    table_name VARCHAR(100) NOT NULL, -- 操作的表名
    record_id INTEGER NOT NULL, -- 被操作记录的ID
    operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 操作时间
    before_change TEXT, -- 修改前的数据
    after_change TEXT -- 修改后的数据
);

2. 使用 SQLAlchemy 进行数据库操作

为了更好地与数据库交互,我们使用 SQLAlchemy 作为 ORM。这里是基本的 FastAPI 与 SQLAlchemy 的配置:

from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, Table, MetaData
from sqlalchemy.orm import sessionmaker

app = FastAPI()

# 数据库连接配置
DATABASE_URL = "postgresql://user:password@localhost/dbname"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 获取数据库会话
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

3. 编写日志记录装饰器

装饰器是一种非常简洁且优雅的方式,可以将日志记录与业务逻辑分离。接下来,我们将创建一个装饰器,专门用于记录数据库增删改操作时的日志信息。

from functools import wraps
from datetime import datetime
import json

def log_changes(operation_type):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            db = kwargs.get('db')  # 获取数据库会话
            user_id = kwargs.get('user_id')  # 获取用户ID
            table_name = kwargs.get('table_name')  # 操作的表名
            record_id = kwargs.get('record_id')  # 操作记录的ID
            
            # 获取修改前的数据
            before_change = func.__name__ == "delete_item" and "N/A" or db.query(Table(table_name)).filter_by(id=record_id).first()
            before_change_data = json.dumps(dict(before_change)) if before_change else "N/A"

            result = func(*args, **kwargs)  # 执行实际的增删改操作

            # 获取修改后的数据
            after_change = func.__name__ == "delete_item" and "N/A" or db.query(Table(table_name)).filter_by(id=record_id).first()
            after_change_data = json.dumps(dict(after_change)) if after_change else "N/A"
            
            # 记录操作日志
            log_operation(db, user_id, operation_type, table_name, record_id, before_change_data, after_change_data)

            return result
        return wrapper
    return decorator

4. 日志记录函数

我们还需要编写一个 log_operation 函数,用来将日志信息插入到数据库中。

def log_operation(db, user_id, operation_type, table_name, record_id, before_change, after_change):
    query = f"""
    INSERT INTO operation_logs (user_id, operation_type, table_name, record_id, operation_time, before_change, after_change)
    VALUES ({user_id}, '{operation_type}', '{table_name}', {record_id}, '{datetime.now()}', '{before_change}', '{after_change}');
    """
    db.execute(query)
    db.commit()

5. 应用装饰器

现在我们可以将装饰器应用到 FastAPI 的路由中,以便在用户执行增删改操作时自动记录操作日志。

@app.post("/items/")
@log_changes("INSERT")
def create_item(item: dict, user_id: int, db: SessionLocal = Depends(get_db)):
    # 执行插入操作
    table_name = "items"
    record_id = 123  # 插入记录后生成的ID

    # 实际插入代码 (示例)
    db.execute(f"INSERT INTO {table_name} (name, value) VALUES ('{item['name']}', '{item['value']}')")
    db.commit()

    return {"status": "success", "record_id": record_id}

@app.put("/items/{item_id}")
@log_changes("UPDATE")
def update_item(item_id: int, updated_item: dict, user_id: int, db: SessionLocal = Depends(get_db)):
    # 执行更新操作
    table_name = "items"
    
    # 实际更新代码 (示例)
    db.execute(f"UPDATE {table_name} SET name = '{updated_item['name']}', value = '{updated_item['value']}' WHERE id = {item_id}")
    db.commit()

    return {"status": "updated", "item_id": item_id}

@app.delete("/items/{item_id}")
@log_changes("DELETE")
def delete_item(item_id: int, user_id: int, db: SessionLocal = Depends(get_db)):
    # 执行删除操作
    table_name = "items"
    
    # 实际删除代码 (示例)
    db.execute(f"DELETE FROM {table_name} WHERE id = {item_id}")
    db.commit()

    return {"status": "deleted", "item_id": item_id}

三、总结

通过本文的示例,我们展示了如何在 FastAPI 中结合 SQLAlchemy 使用装饰器来记录数据库的增删改操作日志。这种方法的优势在于它与业务逻辑分离,使用简单且易于扩展。此外,日志表中保存了修改前后的数据,使得系统在调试和审计时更加高效和透明。

希望这篇文章能帮助你在项目中实现类似的功能!如果你有任何问题或建议,欢迎在评论区留言。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐