PHP项目数据订正如何记录日志追溯原因

wen PHP项目 29

本文目录导读:

PHP项目数据订正如何记录日志追溯原因

  1. 核心日志记录策略
  2. 数据库审计表设计
  3. 批量订正与文件日志
  4. 追踪查询与回溯
  5. 最佳实践
  6. 实际使用示例

在PHP项目中进行数据订正时,良好的日志记录至关重要,它能帮助你追溯问题、审计操作和排查错误,以下是一个系统化的方案:

核心日志记录策略

基础日志记录

class DataCorrectionLogger {
    private $logFile;
    private $correctionId;
    public function __construct($logFile = 'corrections.log') {
        $this->logFile = $logFile;
        $this->correctionId = uniqid('CORR_', true);
    }
    public function logCorrection($table, $recordId, $oldData, $newData, $operator, $reason) {
        $logEntry = [
            'correction_id' => $this->correctionId,
            'timestamp' => date('Y-m-d H:i:s'),
            'table' => $table,
            'record_id' => $recordId,
            'operator' => $operator,
            'reason' => $reason,
            'before' => $oldData,
            'after' => $newData,
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'CLI',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'CLI'
        ];
        $this->writeLog($logEntry);
    }
    private function writeLog($entry) {
        $line = json_encode($entry) . PHP_EOL;
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

数据库审计表设计

创建专门的审计表

CREATE TABLE data_correction_audit (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    correction_id VARCHAR(64) NOT NULL,
    table_name VARCHAR(100) NOT NULL,
    record_id BIGINT NOT NULL,
    operation_type ENUM('UPDATE', 'DELETE', 'INSERT') NOT NULL,
    old_data JSON,
    new_data JSON,
    operator_id INT,
    operator_name VARCHAR(100),
    operator_ip VARCHAR(45),
    reason TEXT,
    status ENUM('PENDING', 'COMPLETED', 'ROLLED_BACK') DEFAULT 'COMPLETED',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    rollback_at TIMESTAMP NULL,
    INDEX idx_correction_id (correction_id),
    INDEX idx_table_record (table_name, record_id),
    INDEX idx_operator (operator_id),
    INDEX idx_created_at (created_at)
);

统一的数据订正类

class DataCorrectionManager {
    private $db;
    private $logger;
    private $auditTable = 'data_correction_audit';
    public function __construct($dbConnection) {
        $this->db = $dbConnection;
        $this->logger = new DataCorrectionLogger();
    }
    /**
     * 执行数据订正
     */
    public function correctData($table, $recordId, array $changes, $operatorId, $operatorName, $reason) {
        try {
            // 1. 获取原始数据
            $oldData = $this->getRecord($table, $recordId);
            if (!$oldData) {
                throw new Exception("Record not found: {$table}.{$recordId}");
            }
            // 2. 开启事务
            $this->db->beginTransaction();
            // 3. 更新数据
            $updateResult = $this->applyCorrection($table, $recordId, $changes);
            // 4. 获取更新后数据
            $newData = $this->getRecord($table, $recordId);
            // 5. 写入审计日志
            $auditId = $this->saveAuditLog(
                $table, $recordId, 'UPDATE',
                $oldData, $newData,
                $operatorId, $operatorName,
                $reason
            );
            // 6. 提交事务
            $this->db->commit();
            // 7. 记录业务日志
            $this->logger->logCorrection(
                $table, $recordId, $oldData, $newData, 
                $operatorName, $reason
            );
            return [
                'success' => true,
                'audit_id' => $auditId,
                'correction_id' => $this->logger->correctionId
            ];
        } catch (Exception $e) {
            $this->db->rollBack();
            $this->logError($e, $table, $recordId, $operatorName);
            throw $e;
        }
    }
    private function saveAuditLog($table, $recordId, $operation, $oldData, $newData, 
                                   $operatorId, $operatorName, $reason) {
        $sql = "INSERT INTO {$this->auditTable} 
                (correction_id, table_name, record_id, operation_type, 
                 old_data, new_data, operator_id, operator_name, 
                 operator_ip, reason) 
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            $this->logger->correctionId,
            $table,
            $recordId,
            $operation,
            json_encode($oldData),
            json_encode($newData),
            $operatorId,
            $operatorName,
            $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1',
            $reason
        ]);
        return $this->db->lastInsertId();
    }
}

批量订正与文件日志

批量操作日志

class BatchCorrectionLogger {
    private $batchId;
    private $csvLog;
    public function __construct() {
        $this->batchId = date('Ymd_His') . '_' . uniqid();
        $this->csvLog = fopen("corrections/{$this->batchId}.csv", 'w');
        // CSV头
        fputcsv($this->csvLog, [
            '时间', '操作类型', '表名', '记录ID', 
            '操作前', '操作后', '操作人', '原因', '状态'
        ]);
    }
    public function logSingle($type, $table, $id, $before, $after, $operator, $reason, $status) {
        fputcsv($this->csvLog, [
            date('Y-m-d H:i:s'),
            $type,
            $table,
            $id,
            json_encode($before),
            json_encode($after),
            $operator,
            $reason,
            $status
        ]);
    }
    public function close() {
        fclose($this->csvLog);
    }
}

追踪查询与回溯

查询日志系统

class CorrectionQuery {
    private $db;
    public function getCorrectionsByDate($startDate, $endDate) {
        $sql = "SELECT * FROM data_correction_audit 
                WHERE created_at BETWEEN ? AND ?
                ORDER BY created_at DESC";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$startDate, $endDate]);
        return $stmt->fetchAll();
    }
    public function getRecordHistory($table, $recordId) {
        $sql = "SELECT * FROM data_correction_audit 
                WHERE table_name = ? AND record_id = ?
                ORDER BY created_at ASC";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$table, $recordId]);
        return $this->buildTimeline($stmt->fetchAll());
    }
    private function buildTimeline($records) {
        $timeline = [];
        foreach ($records as $record) {
            $timeline[] = [
                'time' => $record['created_at'],
                'operator' => $record['operator_name'],
                'reason' => $record['reason'],
                'changes' => $this->calculateDiff(
                    json_decode($record['old_data'], true),
                    json_decode($record['new_data'], true)
                )
            ];
        }
        return $timeline;
    }
    private function calculateDiff($old, $new) {
        $diff = [];
        foreach ($new as $key => $value) {
            if ($old[$key] !== $value) {
                $diff[$key] = [
                    'from' => $old[$key],
                    'to' => $value
                ];
            }
        }
        return $diff;
    }
}

最佳实践

日志级别与内容

/**
 * 使用多种日志级别
 */
class CorrectionLogger {
    const LEVEL_INFO = 'INFO';
    const LEVEL_WARNING = 'WARNING';
    const LEVEL_ERROR = 'ERROR';
    public function log($level, $message, array $context = []) {
        $logEntry = [
            'level' => $level,
            'message' => $message,
            'context' => $context,
            'stack_trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),
            'memory_usage' => memory_get_usage(true)
        ];
        // 根据级别决定存储位置
        switch ($level) {
            case self::LEVEL_ERROR:
                $this->storeToDatabase($logEntry);
                $this->sendAlert($logEntry);
                break;
            default:
                $this->storeToFile($logEntry);
        }
    }
}

数据回滚支持

class RollbackManager {
    public function rollbackCorrection($correctionId) {
        // 1. 获取审计记录
        $record = $this->getAuditRecord($correctionId);
        // 2. 验证是否可回滚
        if ($record['status'] !== 'COMPLETED') {
            throw new Exception('Cannot rollback: status is ' . $record['status']);
        }
        // 3. 执行回滚
        $this->db->beginTransaction();
        try {
            $oldData = json_decode($record['old_data'], true);
            $this->updateRecord($record['table_name'], $record['record_id'], $oldData);
            // 4. 更新审计状态
            $this->updateAuditStatus($correctionId, 'ROLLED_BACK');
            $this->db->commit();
            // 5. 记录回滚日志
            $this->logRollback($correctionId, $oldData);
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }
}

实际使用示例

// 在业务代码中使用
try {
    $correction = new DataCorrectionManager($db);
    $result = $correction->correctData(
        'users', 
        123,
        ['email' => 'new@example.com', 'phone' => '13800138000'],
        1,
        'admin',
        '用户请求更新联系方式'
    );
    if ($result['success']) {
        echo "订正成功,审计ID: " . $result['audit_id'];
    }
} catch (Exception $e) {
    // 记录错误日志
    error_log("数据订正失败: " . $e->getMessage());
    // 发送告警
    $alertService->send("数据订正失败: {$table}.{$recordId}");
}

建议的日志系统架构:

  1. 数据库审计表 - 核心记录,支持查询和回滚
  2. 文件日志 - 详细操作日志,便于排查
  3. 监控告警 - 异常操作实时通知
  4. 定期清理 - 设置日志保留期限
  5. 权限控制 - 只有授权用户可查看日志

这样的设计既能满足追溯需求,又能保证系统的安全和可维护性。

抱歉,评论功能暂时关闭!