如何用PHP项目实现用户自定义审计?

wen java案例 2

本文目录导读:

如何用PHP项目实现用户自定义审计?

  1. 系统架构设计
  2. 核心PHP实现
  3. 使用示例
  4. 中间件集成(Laravel示例)
  5. 前端管理界面

我来详细介绍如何在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>

这个实现提供了完整的用户自定义审计功能,包括:

  1. 灵活的事件系统 - 支持预定义和自定义审计事件
  2. 规则引擎 - 用户可以创建自己的审计规则
  3. 多种存储方式 - 支持数据库、文件、外部服务
  4. 报告生成 - 支持CSV、PDF、Excel格式
  5. 通知集成 - 支持邮件、Slack、Webhook等
  6. 完整的API - 便于集成到现有系统
  7. 前端管理界面 - 方便用户配置和管理

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