$route], $params); return '/index.php?' . http_build_query($params); } function redirect(string $route, array $params = [], string $anchor = ''): void { $location = url($route, $params); if ($anchor !== '') { $location .= '#' . ltrim($anchor, '#'); } header('Location: ' . $location); exit; } function view(string $template, array $data = []): void { extract($data, EXTR_SKIP); $basePath = dirname(__DIR__); $content = $basePath . '/templates/' . $template . '.php'; if (!file_exists($content)) { throw new \RuntimeException("模板不存在: $template"); } require $basePath . '/templates/layout.php'; } function flash(string $key, ?string $value = null): ?string { if ($value !== null) { $_SESSION['_flash'][$key] = $value; return null; } $v = $_SESSION['_flash'][$key] ?? null; unset($_SESSION['_flash'][$key]); return $v; } function csrf_token(): string { if (empty($_SESSION['_csrf'])) { $_SESSION['_csrf'] = bin2hex(random_bytes(16)); } return $_SESSION['_csrf']; } function csrf_check(): void { $token = $_POST['_csrf'] ?? ''; if (!hash_equals($_SESSION['_csrf'] ?? '', $token)) { http_response_code(419); exit('CSRF token 校验失败'); } } function json_decode_field($json, $default = []) { if (empty($json)) return $default; if (is_array($json)) return $json; $decoded = json_decode($json, true); return $decoded ?? $default; }