本文目录导读:

在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}");
}
建议的日志系统架构:
- 数据库审计表 - 核心记录,支持查询和回滚
- 文件日志 - 详细操作日志,便于排查
- 监控告警 - 异常操作实时通知
- 定期清理 - 设置日志保留期限
- 权限控制 - 只有授权用户可查看日志
这样的设计既能满足追溯需求,又能保证系统的安全和可维护性。