如何用PHP项目实现用户自定义回滚?

wen java案例 1

本文目录导读:

如何用PHP项目实现用户自定义回滚?

  1. 基于数据库事务的回滚
  2. 基于日志的快照回滚
  3. 更高级的版本控制回滚
  4. 前端交互实现
  5. API接口示例
  6. 最佳实践建议

在PHP项目中实现用户自定义回滚功能,通常需要结合数据库事务操作日志记录状态快照,以下是几种常见的实现方案:

基于数据库事务的回滚

核心思路

将每次用户操作包装在事务中,记录操作步骤,允许用户撤销最近的N步操作。

<?php
class TransactionRollback {
    private $db;
    private $operationStack = [];
    public function __construct($pdo) {
        $this->db = $pdo;
    }
    // 开始一个可回滚的操作
    public function beginOperation($operationName) {
        $this->db->beginTransaction();
        // 保存当前状态快照(用于回滚)
        $snapshot = $this->captureSnapshot();
        $operation = [
            'name' => $operationName,
            'snapshot' => $snapshot,
            'time' => time()
        ];
        array_push($this->operationStack, $operation);
    }
    // 提交当前操作
    public function commitOperation() {
        $this->db->commit();
    }
    // 回滚到指定步骤
    public function rollbackTo($steps = 1) {
        if (count($this->operationStack) < $steps) {
            throw new Exception("没有足够的操作可以回滚");
        }
        // 获取要回滚到的快照
        $targetOperation = $this->operationStack[count($this->operationStack) - $steps];
        $snapshot = $targetOperation['snapshot'];
        // 回滚到快照状态
        $this->restoreSnapshot($snapshot);
        // 移除已回滚的操作
        array_splice($this->operationStack, count($this->operationStack) - $steps);
    }
    // 捕获当前数据库状态快照
    private function captureSnapshot() {
        // 示例:记录受影响表的主键和关键字段
        $tables = ['users', 'orders'];
        $snapshot = [];
        foreach ($tables as $table) {
            $stmt = $this->db->query("SELECT * FROM {$table} ORDER BY id");
            $snapshot[$table] = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
        return $snapshot;
    }
    // 恢复数据库到快照状态
    private function restoreSnapshot($snapshot) {
        foreach ($snapshot as $table => $rows) {
            // 清空表
            $this->db->exec("TRUNCATE TABLE {$table}");
            // 重新插入数据
            foreach ($rows as $row) {
                $columns = implode(', ', array_keys($row));
                $placeholders = ':' . implode(', :', array_keys($row));
                $stmt = $this->db->prepare("INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})");
                $stmt->execute($row);
            }
        }
    }
}
// 使用示例
$rollback = new TransactionRollback($pdo);
try {
    $rollback->beginOperation('updateUser');
    // 执行用户操作
    $pdo->exec("UPDATE users SET balance = balance - 100 WHERE id = 1");
    $pdo->exec("UPDATE orders SET status = 'paid' WHERE user_id = 1");
    $rollback->commitOperation();
} catch (Exception $e) {
    $rollback->rollbackTo(1); // 回滚最近一步操作
}

基于日志的快照回滚

核心思路

记录每个操作的详细日志,回滚时逆向执行这些操作。

<?php
class LogBasedRollback {
    private $db;
    private $logTable = 'operation_logs';
    private $maxHistory = 50; // 最多保存50步操作历史
    public function __construct($pdo) {
        $this->db = $pdo;
        $this->initLogTable();
    }
    // 初始化日志表
    private function initLogTable() {
        $sql = "
            CREATE TABLE IF NOT EXISTS {$this->logTable} (
                id INT AUTO_INCREMENT PRIMARY KEY,
                operation_name VARCHAR(100),
                entity_type VARCHAR(50),
                entity_id INT,
                old_value JSON,
                new_value JSON,
                user_id INT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_user_time (user_id, created_at)
            )
        ";
        $this->db->exec($sql);
    }
    // 记录操作
    public function logOperation($operationName, $entityType, $entityId, $oldValue, $newValue, $userId) {
        $stmt = $this->db->prepare(
            "INSERT INTO {$this->logTable} 
             (operation_name, entity_type, entity_id, old_value, new_value, user_id) 
             VALUES (?, ?, ?, ?, ?, ?)"
        );
        $stmt->execute([
            $operationName,
            $entityType,
            $entityId,
            json_encode($oldValue),
            json_encode($newValue),
            $userId
        ]);
        // 限制历史记录数量
        $this->cleanupOldLogs($userId);
    }
    // 执行回滚
    public function rollback($userId, $steps = 1) {
        // 获取最后N条操作记录
        $stmt = $this->db->prepare(
            "SELECT * FROM {$this->logTable} 
             WHERE user_id = ? 
             ORDER BY id DESC 
             LIMIT ?"
        );
        $stmt->execute([$userId, $steps]);
        $logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 逆向执行回滚
        foreach ($logs as $log) {
            $this->performRollback($log);
            // 删除已回滚的日志
            $this->db->exec("DELETE FROM {$this->logTable} WHERE id = {$log['id']}");
        }
        return count($logs);
    }
    // 执行单个回滚操作
    private function performRollback($log) {
        $entityType = $log['entity_type'];
        $entityId = $log['entity_id'];
        $oldValue = json_decode($log['old_value'], true);
        switch ($entityType) {
            case 'user':
                $this->rollbackUser($entityId, $oldValue);
                break;
            case 'order':
                $this->rollbackOrder($entityId, $oldValue);
                break;
            // 添加更多实体类型
        }
    }
    private function rollbackUser($userId, $oldValue) {
        $setClause = [];
        foreach ($oldValue as $field => $value) {
            $setClause[] = "{$field} = :{$field}";
        }
        $sql = "UPDATE users SET " . implode(', ', $setClause) . " WHERE id = :id";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array_merge($oldValue, ['id' => $userId]));
    }
    private function rollbackOrder($orderId, $oldValue) {
        // 类似上面的逻辑
    }
    // 清理旧日志
    private function cleanupOldLogs($userId) {
        $stmt = $this->db->prepare(
            "DELETE FROM {$this->logTable} 
             WHERE user_id = ? AND id NOT IN (
                 SELECT id FROM (
                     SELECT id FROM {$this->logTable} 
                     WHERE user_id = ? 
                     ORDER BY id DESC 
                     LIMIT {$this->maxHistory}
                 ) AS temp
             )"
        );
        $stmt->execute([$userId, $userId]);
    }
}
// 使用示例
$rollback = new LogBasedRollback($pdo);
// 更新用户信息
$oldUser = $pdo->query("SELECT * FROM users WHERE id = 1")->fetch(PDO::FETCH_ASSOC);
$newUser = $oldUser;
$newUser['balance'] = 1000;
// 执行更新
$pdo->exec("UPDATE users SET balance = 1000 WHERE id = 1");
// 记录操作
$rollback->logOperation('updateUser', 'user', 1, $oldUser, $newUser, $userId);
// 用户点击"撤销"
$rollback->rollback($userId, 1);

更高级的版本控制回滚

核心思路

类似Git的版本控制,每次操作创建完整的数据快照。

<?php
class VersionControlRollback {
    private $db;
    private $versionTable = 'data_versions';
    public function __construct($pdo) {
        $this->db = $pdo;
        $this->initVersionTable();
    }
    private function initVersionTable() {
        $sql = "
            CREATE TABLE IF NOT EXISTS {$this->versionTable} (
                id INT AUTO_INCREMENT PRIMARY KEY,
                user_id INT,
                version_number INT,
                snapshot_data LONGBLOB,
                operation_name VARCHAR(100),
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_user_version (user_id, version_number)
            )
        ";
        $this->db->exec($sql);
    }
    // 创建新版本
    public function createVersion($userId, $operationName) {
        // 获取当前所有相关数据
        $snapshot = $this->captureFullSnapshot();
        // 获取当前用户的最新版本号
        $stmt = $this->db->prepare(
            "SELECT MAX(version_number) as max_version 
             FROM {$this->versionTable} 
             WHERE user_id = ?"
        );
        $stmt->execute([$userId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $newVersion = ($result['max_version'] ?? 0) + 1;
        // 保存版本
        $stmt = $this->db->prepare(
            "INSERT INTO {$this->versionTable} 
             (user_id, version_number, snapshot_data, operation_name) 
             VALUES (?, ?, ?, ?)"
        );
        $stmt->execute([
            $userId,
            $newVersion,
            gzcompress(serialize($snapshot)),
            $operationName
        ]);
        return $newVersion;
    }
    // 回滚到指定版本
    public function rollbackToVersion($userId, $targetVersion) {
        // 获取目标版本快照
        $stmt = $this->db->prepare(
            "SELECT * FROM {$this->versionTable} 
             WHERE user_id = ? AND version_number = ?"
        );
        $stmt->execute([$userId, $targetVersion]);
        $version = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$version) {
            throw new Exception("版本不存在");
        }
        // 解压并恢复快照
        $snapshot = unserialize(gzuncompress($version['snapshot_data']));
        $this->restoreFullSnapshot($snapshot);
        // 删除该版本之后的所有版本
        $stmt = $this->db->prepare(
            "DELETE FROM {$this->versionTable} 
             WHERE user_id = ? AND version_number > ?"
        );
        $stmt->execute([$userId, $targetVersion]);
        return true;
    }
    // 获取版本历史
    public function getVersionHistory($userId) {
        $stmt = $this->db->prepare(
            "SELECT id, version_number, operation_name, created_at 
             FROM {$this->versionTable} 
             WHERE user_id = ? 
             ORDER BY version_number DESC 
             LIMIT 20"
        );
        $stmt->execute([$userId]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    private function captureFullSnapshot() {
        $snapshot = [];
        // 捕获所有需要版本控制的表
        $tables = ['users', 'orders', 'products', 'categories'];
        foreach ($tables as $table) {
            $stmt = $this->db->query("SELECT * FROM {$table} ORDER BY id");
            $snapshot[$table] = $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
        return $snapshot;
    }
    private function restoreFullSnapshot($snapshot) {
        $this->db->beginTransaction();
        try {
            foreach ($snapshot as $table => $rows) {
                $this->db->exec("DELETE FROM {$table}");
                if (!empty($rows)) {
                    $stmt = $this->prepareInsertStatement($table, $rows[0]);
                    foreach ($rows as $row) {
                        $stmt->execute($row);
                    }
                }
            }
            $this->db->commit();
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }
    private function prepareInsertStatement($table, $row) {
        $columns = array_keys($row);
        $placeholders = ':' . implode(', :', $columns);
        $columns = implode(', ', $columns);
        return $this->db->prepare("INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})");
    }
}
// 前端展示版本历史示例
function renderVersionTimeline($versions) {
    echo '<div class="version-timeline">';
    foreach ($versions as $version) {
        echo '<div class="version-item">';
        echo "v{$version['version_number']}: {$version['operation_name']} ";
        echo "({$version['created_at']})";
        echo '<button onclick="rollbackTo(' . $version['version_number'] . ')">回滚到这里</button>';
        echo '</div>';
    }
    echo '</div>';
}

前端交互实现

// rollback.js
class RollbackManager {
    constructor(options) {
        this.apiEndpoint = options.apiEndpoint || '/api/rollback';
        this.userId = options.userId;
        this.maxHistory = options.maxHistory || 50;
    }
    // 获取操作历史
    async getHistory(page = 1) {
        const response = await fetch(`${this.apiEndpoint}/history?user_id=${this.userId}&page=${page}`);
        return response.json();
    }
    // 执行回滚
    async performRollback(steps = 1, type = 'step') {
        try {
            const response = await fetch(`${this.apiEndpoint}/execute`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    user_id: this.userId,
                    steps: steps,
                    type: type
                })
            });
            const result = await response.json();
            if (result.success) {
                this.showSuccess('回滚成功');
                return true;
            } else {
                this.showError(result.message);
                return false;
            }
        } catch (error) {
            this.showError('回滚失败: ' + error.message);
            return false;
        }
    }
    // 显示操作历史UI
    renderHistory(history) {
        const container = document.getElementById('rollback-history');
        container.innerHTML = '';
        history.forEach((item, index) => {
            const element = document.createElement('div');
            element.className = 'history-item';
            element.innerHTML = `
                <span class="operation-name">${item.operation_name}</span>
                <span class="operation-time">${new Date(item.created_at).toLocaleString()}</span>
                <button class="rollback-btn" data-steps="${index + 1}">
                    撤销到这一步
                </button>
            `;
            element.querySelector('.rollback-btn').addEventListener('click', () => {
                this.performRollback(index + 1);
            });
            container.appendChild(element);
        });
    }
    showSuccess(message) {
        // 显示成功提示
    }
    showError(message) {
        // 显示错误提示
    }
}
// 使用示例
const rollbackManager = new RollbackManager({
    apiEndpoint: '/api/rollback',
    userId: 123
});
// 加载历史记录
rollbackManager.getHistory().then(history => {
    rollbackManager.renderHistory(history);
});

API接口示例

<?php
// rollback_api.php
class RollbackAPI {
    private $rollbackService;
    public function __construct($pdo) {
        $this->rollbackService = new VersionControlRollback($pdo);
    }
    public function handleRequest() {
        $method = $_SERVER['REQUEST_METHOD'];
        $action = $_GET['action'] ?? '';
        switch ($method) {
            case 'GET':
                if ($action === 'history') {
                    $this->getHistory();
                }
                break;
            case 'POST':
                if ($action === 'execute') {
                    $this->executeRollback();
                }
                break;
        }
    }
    private function getHistory() {
        $userId = $_GET['user_id'] ?? 0;
        if (!$userId) {
            $this->error('User ID required');
        }
        $history = $this->rollbackService->getVersionHistory($userId);
        $this->success(['history' => $history]);
    }
    private function executeRollback() {
        $input = json_decode(file_get_contents('php://input'), true);
        $userId = $input['user_id'] ?? 0;
        $steps = $input['steps'] ?? 1;
        $type = $input['type'] ?? 'step';
        if (!$userId) {
            $this->error('User ID required');
        }
        try {
            if ($type === 'step') {
                // 基于步骤回滚
                $result = $this->rollbackService->rollbackToVersion($userId, $steps);
            } else if ($type === 'version') {
                // 基于版本回滚
                $result = $this->rollbackService->rollbackToVersion($userId, $steps);
            }
            $this->success(['message' => 'Rollback successful']);
        } catch (Exception $e) {
            $this->error($e->getMessage());
        }
    }
    private function success($data) {
        header('Content-Type: application/json');
        echo json_encode(['success' => true, 'data' => $data]);
        exit;
    }
    private function error($message) {
        header('HTTP/1.1 400 Bad Request');
        header('Content-Type: application/json');
        echo json_encode(['success' => false, 'message' => $message]);
        exit;
    }
}
// 路由示例
$api = new RollbackAPI($pdo);
$api->handleRequest();

最佳实践建议

  1. 性能优化

    • 使用增量快照而不是全量快照
    • 定期清理过期历史记录
    • 使用异步任务处理大规模回滚
  2. 安全性考虑

    • 添加用户权限验证
    • 限制可回滚的步数
    • 记录所有回滚操作
  3. 用户体验

    • 提供预览功能
    • 显示回滚影响范围
    • 支持批量选择回滚
  4. 数据完整性

    • 使用事务确保原子性
    • 提供回滚确认机制
    • 保存原始数据备份

选择哪种实现方案取决于你的具体需求:

  • 简单场景:使用事务回滚
  • 中等复杂:使用日志回滚
  • 复杂场景:使用版本控制回滚

建议在开发环境中充分测试后再部署到生产环境。

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