本文目录导读:

我来详细介绍如何在PHP项目中实现用户自定义审计功能。
系统架构设计
数据库表结构
-- 审计事件定义表
CREATE TABLE audit_events (
id INT PRIMARY KEY AUTO_INCREMENT,
event_code VARCHAR(50) UNIQUE NOT NULL,
event_name VARCHAR(200) NOT NULL,
event_category VARCHAR(50),
description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 审计日志表
CREATE TABLE audit_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
event_id INT,
user_id INT,
user_name VARCHAR(100),
ip_address VARCHAR(45),
user_agent TEXT,
request_method VARCHAR(10),
request_url TEXT,
request_data JSON,
response_data JSON,
status VARCHAR(20),
execution_time DECIMAL(10,4),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_event_id (event_id),
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at)
);
-- 用户自定义审计规则表
CREATE TABLE audit_rules (
id INT PRIMARY KEY AUTO_INCREMENT,
rule_name VARCHAR(100) NOT NULL,
event_id INT,
user_id INT,
conditions JSON,
actions JSON,
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 审计报告模板表
CREATE TABLE audit_reports (
id INT PRIMARY KEY AUTO_INCREMENT,
report_name VARCHAR(200) NOT NULL,
user_id INT,
filters JSON,
columns JSON,
schedule VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
核心PHP实现
审计管理器类
<?php
namespace App\Audit;
use App\Audit\Events\AuditEvent;
use App\Audit\Storage\AuditStorage;
use App\Audit\Rules\AuditRuleEngine;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class AuditManager
{
private $storage;
private $ruleEngine;
private $logger;
private $config;
public function __construct(array $config = [])
{
$this->config = $config;
$this->storage = new AuditStorage();
$this->ruleEngine = new AuditRuleEngine();
$this->initializeLogger();
}
/**
* 记录审计事件
*/
public function log(AuditEvent $event): bool
{
try {
// 1. 检查事件是否启用
if (!$this->isEventActive($event->getEventCode())) {
return false;
}
// 2. 应用自定义规则
$event = $this->ruleEngine->applyRules($event);
// 3. 记录日志
$logId = $this->storage->save($event);
// 4. 触发事件监听器
$this->dispatchListeners($event);
// 5. 记录到日志文件
$this->logger->info('Audit event logged', [
'event_code' => $event->getEventCode(),
'user_id' => $event->getUserId(),
'log_id' => $logId
]);
return true;
} catch (\Exception $e) {
$this->logger->error('Failed to log audit event: ' . $e->getMessage());
return false;
}
}
/**
* 自定义审计事件
*/
public function registerCustomEvent(string $code, string $name, array $options = []): bool
{
$event = [
'event_code' => $code,
'event_name' => $name,
'event_category' => $options['category'] ?? 'custom',
'description' => $options['description'] ?? '',
'is_active' => $options['is_active'] ?? true
];
return $this->storage->saveEvent($event);
}
/**
* 创建审计规则
*/
public function createRule(string $ruleName, array $conditions, array $actions, int $userId = null): int
{
$rule = [
'rule_name' => $ruleName,
'conditions' => json_encode($conditions),
'actions' => json_encode($actions),
'user_id' => $userId
];
return $this->storage->saveRule($rule);
}
/**
* 查询审计日志
*/
public function query(array $filters = [], int $page = 1, int $perPage = 20): array
{
return $this->storage->query($filters, $page, $perPage);
}
/**
* 生成审计报告
*/
public function generateReport(array $filters, array $columns, string $format = 'csv'): string
{
$data = $this->storage->query($filters, 1, 10000);
switch ($format) {
case 'csv':
return $this->generateCsvReport($data['items'], $columns);
case 'pdf':
return $this->generatePdfReport($data['items'], $columns);
case 'excel':
return $this->generateExcelReport($data['items'], $columns);
default:
throw new \InvalidArgumentException("Unsupported format: $format");
}
}
private function initializeLogger()
{
$this->logger = new Logger('audit');
$logPath = $this->config['log_path'] ?? __DIR__ . '/../logs/audit.log';
$this->logger->pushHandler(
new StreamHandler($logPath, Logger::INFO)
);
}
private function isEventActive(string $eventCode): bool
{
// 检查事件是否启用
$event = $this->storage->getEventByCode($eventCode);
return $event && $event['is_active'];
}
private function dispatchListeners(AuditEvent $event)
{
// 触发事件监听器
$listeners = $this->config['listeners'] ?? [];
foreach ($listeners as $listener) {
if (class_exists($listener)) {
$instance = new $listener();
if (method_exists($instance, 'handle')) {
$instance->handle($event);
}
}
}
}
private function generateCsvReport(array $data, array $columns): string
{
$output = fopen('php://temp', 'r+');
// 添加表头
fputcsv($output, array_column($columns, 'label'));
// 添加数据
foreach ($data as $row) {
$rowData = [];
foreach ($columns as $column) {
$key = $column['field'];
$rowData[] = $row[$key] ?? '';
}
fputcsv($output, $rowData);
}
rewind($output);
$content = stream_get_contents($output);
fclose($output);
return $content;
}
private function generatePdfReport(array $data, array $columns): string
{
// 使用TCPDF或其他PDF库
$pdf = new \TCPDF();
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 12);
// 添加表头
$html = '<table border="1"><tr>';
foreach ($columns as $column) {
$html .= '<th>' . htmlspecialchars($column['label']) . '</th>';
}
$html .= '</tr>';
// 添加数据
foreach ($data as $row) {
$html .= '<tr>';
foreach ($columns as $column) {
$key = $column['field'];
$html .= '<td>' . htmlspecialchars($row[$key] ?? '') . '</td>';
}
$html .= '</tr>';
}
$html .= '</table>';
$pdf->writeHTML($html);
return $pdf->Output('report.pdf', 'S');
}
private function generateExcelReport(array $data, array $columns): string
{
// 使用PhpSpreadsheet库
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 添加表头
$col = 1;
foreach ($columns as $column) {
$sheet->setCellValueByColumnAndRow($col, 1, $column['label']);
$col++;
}
// 添加数据
$row = 2;
foreach ($data as $dataRow) {
$col = 1;
foreach ($columns as $column) {
$key = $column['field'];
$sheet->setCellValueByColumnAndRow($col, $row, $dataRow[$key] ?? '');
$col++;
}
$row++;
}
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
ob_start();
$writer->save('php://output');
return ob_get_clean();
}
}
审计事件类
<?php
namespace App\Audit\Events;
class AuditEvent
{
private $eventCode;
private $userId;
private $userName;
private $ipAddress;
private $userAgent;
private $requestMethod;
private $requestUrl;
private $requestData;
private $responseData;
private $status;
private $executionTime;
private $metadata = [];
public function __construct(string $eventCode)
{
$this->eventCode = $eventCode;
$this->requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'CLI';
$this->requestUrl = $_SERVER['REQUEST_URI'] ?? '';
$this->ipAddress = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$this->userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
}
// Getters and Setters
public function getEventCode(): string
{
return $this->eventCode;
}
public function setUserId(int $userId): self
{
$this->userId = $userId;
return $this;
}
public function getUserId(): ?int
{
return $this->userId;
}
public function setUserName(string $userName): self
{
$this->userName = $userName;
return $this;
}
public function setRequestData($data): self
{
$this->requestData = $data;
return $this;
}
public function setResponseData($data): self
{
$this->responseData = $data;
return $this;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function setExecutionTime(float $time): self
{
$this->executionTime = $time;
return $this;
}
public function addMetadata(string $key, $value): self
{
$this->metadata[$key] = $value;
return $this;
}
public function toArray(): array
{
return [
'event_code' => $this->eventCode,
'user_id' => $this->userId,
'user_name' => $this->userName,
'ip_address' => $this->ipAddress,
'user_agent' => $this->userAgent,
'request_method' => $this->requestMethod,
'request_url' => $this->requestUrl,
'request_data' => json_encode($this->requestData),
'response_data' => json_encode($this->responseData),
'status' => $this->status,
'execution_time' => $this->executionTime,
'metadata' => json_encode($this->metadata)
];
}
}
审计规则引擎
<?php
namespace App\Audit\Rules;
class AuditRuleEngine
{
private $rules = [];
/**
* 加载用户自定义规则
*/
public function loadRules(int $userId = null): void
{
// 从数据库加载规则
global $db;
$query = "SELECT * FROM audit_rules WHERE is_enabled = 1";
if ($userId) {
$query .= " AND (user_id IS NULL OR user_id = $userId)";
}
$result = $db->query($query);
while ($row = $result->fetch_assoc()) {
$this->rules[] = $row;
}
}
/**
* 应用规则到事件
*/
public function applyRules($event): $event
{
foreach ($this->rules as $rule) {
$conditions = json_decode($rule['conditions'], true);
$actions = json_decode($rule['actions'], true);
if ($this->evaluateConditions($conditions, $event)) {
$event = $this->executeActions($actions, $event);
}
}
return $event;
}
/**
* 评估规则条件
*/
private function evaluateConditions(array $conditions, $event): bool
{
foreach ($conditions as $condition) {
$field = $condition['field'];
$operator = $condition['operator'];
$value = $condition['value'];
$fieldValue = $this->getEventFieldValue($event, $field);
if (!$this->compareValues($fieldValue, $operator, $value)) {
return false;
}
}
return true;
}
/**
* 获取事件字段值
*/
private function getEventFieldValue($event, string $field)
{
$getter = 'get' . ucfirst($field);
if (method_exists($event, $getter)) {
return $event->$getter();
}
// 检查metadata
$metadata = $event->getMetadata();
if (isset($metadata[$field])) {
return $metadata[$field];
}
return null;
}
/**
* 比较值
*/
private function compareValues($fieldValue, string $operator, $value): bool
{
switch ($operator) {
case 'equals':
return $fieldValue === $value;
case 'not_equals':
return $fieldValue !== $value;
case 'contains':
return strpos($fieldValue, $value) !== false;
case 'greater_than':
return $fieldValue > $value;
case 'less_than':
return $fieldValue < $value;
case 'in':
return in_array($fieldValue, explode(',', $value));
case 'not_in':
return !in_array($fieldValue, explode(',', $value));
case 'regex':
return preg_match($value, $fieldValue);
default:
return true;
}
}
/**
* 执行规则动作
*/
private function executeActions(array $actions, $event): $event
{
foreach ($actions as $action) {
switch ($action['type']) {
case 'ignore':
// 忽略该事件
$event->setIgnored(true);
break;
case 'add_tag':
$event->addMetadata('tags', $action['value']);
break;
case 'set_priority':
$event->setPriority($action['value']);
break;
case 'notify':
// 发送通知
$this->sendNotification($action['value'], $event);
break;
case 'transform':
// 转换数据
$transformer = $action['transformer'];
if (class_exists($transformer)) {
$instance = new $transformer();
$event = $instance->transform($event);
}
break;
}
}
return $event;
}
private function sendNotification(array $notificationConfig, $event)
{
$type = $notificationConfig['type'] ?? 'email';
$recipients = $notificationConfig['recipients'] ?? [];
$notificationService = new \App\Notification\NotificationService();
switch ($type) {
case 'email':
$notificationService->sendEmail($recipients, $event);
break;
case 'slack':
$notificationService->sendSlack($recipients, $event);
break;
case 'webhook':
$notificationService->sendWebhook($notificationConfig['url'], $event);
break;
}
}
}
使用示例
基础使用
<?php
// 初始化审计管理器
$auditManager = new AuditManager([
'log_path' => '/var/log/audit.log',
'listeners' => [
'App\Listeners\AuditEventListener'
]
]);
// 创建审计事件
$event = new AuditEvent('user.login');
$event->setUserId(123)
->setUserName('john.doe')
->setRequestData(['username' => 'john.doe'])
->setResponseData(['success' => true])
->setStatus('success')
->setExecutionTime(0.125)
->addMetadata('browser', 'Chrome 90');
// 记录审计事件
$auditManager->log($event);
自定义事件和规则
<?php
// 注册自定义事件
$auditManager->registerCustomEvent(
'order.refund',
'Order Refund',
[
'category' => 'financial',
'description' => 'Track order refund operations'
]
);
// 创建自定义规则
$auditManager->createRule(
'High Value Refund Alert',
[
[
'field' => 'eventCode',
'operator' => 'equals',
'value' => 'order.refund'
],
[
'field' => 'refund_amount',
'operator' => 'greater_than',
'value' => 1000
]
],
[
[
'type' => 'set_priority',
'value' => 'high'
],
[
'type' => 'notify',
'value' => [
'type' => 'email',
'recipients' => ['admin@example.com']
]
]
],
1 // user_id
);
查询和报告
<?php
// 查询审计日志
$results = $auditManager->query(
[
'event_code' => 'user.login',
'user_id' => 123,
'date_from' => '2024-01-01',
'date_to' => '2024-12-31',
'status' => 'failed'
],
1, // page
20 // per page
);
// 生成报告
$report = $auditManager->generateReport(
[
'event_code' => 'order.*',
'date_from' => date('Y-m-d', strtotime('-30 days'))
],
[
['field' => 'id', 'label' => 'ID'],
['field' => 'event_code', 'label' => 'Event'],
['field' => 'user_name', 'label' => 'User'],
['field' => 'created_at', 'label' => 'Timestamp'],
['field' => 'status', 'label' => 'Status']
],
'csv'
);
// 输出文件
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="audit_report.csv"');
echo $report;
中间件集成(Laravel示例)
<?php
namespace App\Http\Middleware;
use Closure;
use App\Audit\AuditManager;
use App\Audit\Events\AuditEvent;
class AuditMiddleware
{
private $auditManager;
public function __construct(AuditManager $auditManager)
{
$this->auditManager = $auditManager;
}
public function handle($request, Closure $next)
{
$startTime = microtime(true);
$response = $next($request);
$executionTime = microtime(true) - $startTime;
// 记录请求审计
if ($this->shouldAudit($request)) {
$event = new AuditEvent('http.request');
$event->setUserId(auth()->id())
->setUserName(auth()->user()?->name)
->setRequestData([
'params' => $request->all(),
'headers' => $request->headers->all()
])
->setResponseData([
'status_code' => $response->getStatusCode(),
'content' => substr($response->getContent(), 0, 1000)
])
->setStatus($response->isSuccessful() ? 'success' : 'failed')
->setExecutionTime($executionTime);
$this->auditManager->log($event);
}
return $response;
}
private function shouldAudit($request): bool
{
// 排除某些路径
$excludedPaths = ['_debugbar', 'telescope', 'api/health'];
foreach ($excludedPaths as $path) {
if (strpos($request->path(), $path) !== false) {
return false;
}
}
return true;
}
}
前端管理界面
简单的审计规则配置页面
<!-- templates/audit/rule_form.html -->
<form id="auditRuleForm">
<div class="form-group">
<label>Rule Name</label>
<input type="text" name="rule_name" required>
</div>
<div class="form-group">
<label>Event</label>
<select name="event_id">
<option value="">Select Event</option>
<!-- Populated from backend -->
</select>
</div>
<div class="form-group">
<label>Conditions</label>
<div id="conditionsContainer">
<div class="condition-row">
<select name="conditions[0][field]">
<option value="user_id">User ID</option>
<option value="event_code">Event Code</option>
<option value="status">Status</option>
</select>
<select name="conditions[0][operator]">
<option value="equals">Equals</option>
<option value="not_equals">Not Equals</option>
<option value="contains">Contains</option>
<option value="greater_than">Greater Than</option>
<option value="less_than">Less Than</option>
</select>
<input type="text" name="conditions[0][value]" placeholder="Value">
<button type="button" onclick="removeCondition(this)">Remove</button>
</div>
</div>
<button type="button" onclick="addCondition()">Add Condition</button>
</div>
<div class="form-group">
<label>Actions</label>
<div id="actionsContainer">
<div class="action-row">
<select name="actions[0][type]">
<option value="ignore">Ignore Event</option>
<option value="set_priority">Set Priority</option>
<option value="add_tag">Add Tag</option>
<option value="notify">Send Notification</option>
</select>
<input type="text" name="actions[0][value]" placeholder="Action value">
<button type="button" onclick="removeAction(this)">Remove</button>
</div>
</div>
<button type="button" onclick="addAction()">Add Action</button>
</div>
<button type="submit">Save Rule</button>
</form>
<script>
function addCondition() {
const container = document.getElementById('conditionsContainer');
const index = container.children.length;
const html = `
<div class="condition-row">
<select name="conditions[${index}][field]">
<option value="user_id">User ID</option>
<option value="event_code">Event Code</option>
<option value="status">Status</option>
</select>
<select name="conditions[${index}][operator]">
<option value="equals">Equals</option>
<option value="not_equals">Not Equals</option>
<option value="contains">Contains</option>
</select>
<input type="text" name="conditions[${index}][value]" placeholder="Value">
<button type="button" onclick="removeCondition(this)">Remove</button>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function removeCondition(btn) {
btn.parentElement.remove();
}
function addAction() {
const container = document.getElementById('actionsContainer');
const index = container.children.length;
const html = `
<div class="action-row">
<select name="actions[${index}][type]">
<option value="ignore">Ignore Event</option>
<option value="set_priority">Set Priority</option>
<option value="add_tag">Add Tag</option>
<option value="notify">Send Notification</option>
</select>
<input type="text" name="actions[${index}][value]" placeholder="Action value">
<button type="button" onclick="removeAction(this)">Remove</button>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function removeAction(btn) {
btn.parentElement.remove();
}
document.getElementById('auditRuleForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
// 提交到后端
const response = await fetch('/api/audit/rules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(data)
});
if (response.ok) {
alert('Rule created successfully');
location.reload();
}
});
</script>
这个实现提供了完整的用户自定义审计功能,包括:
- 灵活的事件系统 - 支持预定义和自定义审计事件
- 规则引擎 - 用户可以创建自己的审计规则
- 多种存储方式 - 支持数据库、文件、外部服务
- 报告生成 - 支持CSV、PDF、Excel格式
- 通知集成 - 支持邮件、Slack、Webhook等
- 完整的API - 便于集成到现有系统
- 前端管理界面 - 方便用户配置和管理