Cookie的使用

什么是Cookie?

Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器发起请求时被携带并发送到服务器上。Cookie是Web开发中实现状态管理的重要机制之一。

Cookie的基本概念

Cookie的组成

一个完整的Cookie包含以下几个部分:

Set-Cookie: name=value; Domain=example.com; Path=/; Expires=Wed, 09 Jun 2025 10:18:14 GMT; Secure; HttpOnly; SameSite=Lax
  • 名称-值对name=value - Cookie的核心数据
  • 域(Domain):指定Cookie发送到哪个域名
  • 路径(Path):指定Cookie在哪个路径下发送
  • 过期时间(Expires/Max-Age):Cookie的有效期
  • 安全标志(Secure):只在HTTPS连接下发送
  • HttpOnly标志:防止JavaScript访问Cookie
  • SameSite属性:防止跨站请求伪造(CSRF)攻击

Cookie的特点

  1. 存储在客户端:数据保存在用户的浏览器中
  2. 大小限制:通常限制为4KB
  3. 自动发送:每次请求相关域名时会自动携带
  4. 可设置过期时间:可以是会话Cookie或持久Cookie
  5. 相对不安全:用户可以查看和修改Cookie

Cookie的基本操作

1. 设置Cookie

在PHP中,使用setcookie()函数创建Cookie:

<?php
// 基本语法
setcookie(name, value, expire, path, domain, secure, httponly);

// 示例1:创建一个简单的Cookie
setcookie("username", "张三", time() + 3600); // 1小时后过期

// 示例2:创建带有详细参数的Cookie
$cookie_name = "user_preferences";
$cookie_value = "theme=dark&lang=zh";
$expiration_time = time() + (86400 * 30); // 30天后过期
$cookie_path = "/"; // 整个网站都可用
$cookie_domain = ""; // 当前域名
$secure_only = false; // HTTP和HTTPS都可用
$httponly = true; // 只能通过HTTP访问,防止JavaScript攻击

setcookie($cookie_name, $cookie_value, $expiration_time, $cookie_path, $cookie_domain, $secure_only, $httponly);

echo "Cookie已设置!";
?>

2. 读取Cookie

读取Cookie使用$_COOKIE超全局数组:

<?php
// 检查Cookie是否存在
if (isset($_COOKIE['username'])) {
    $username = $_COOKIE['username'];
    echo "欢迎回来," . htmlspecialchars($username) . "!";
} else {
    echo "欢迎新访客!";
}

// 读取所有Cookie
echo "<h3>所有Cookie:</h3>";
foreach ($_COOKIE as $name => $value) {
    echo "$name = " . htmlspecialchars($value) . "<br>";
}

// 读取用户偏好设置并解析
if (isset($_COOKIE['user_preferences'])) {
    $preferences = $_COOKIE['user_preferences'];
    parse_str($preferences, $prefs);

    echo "<h3>用户偏好设置:</h3>";
    echo "主题:" . ($prefs['theme'] ?? '默认') . "<br>";
    echo "语言:" . ($prefs['lang'] ?? '默认') . "<br>";
}
?>

3. 修改Cookie

修改Cookie实际上是设置一个同名的新Cookie:

<?php
// 修改现有Cookie
if (isset($_COOKIE['username'])) {
    // 更新用户名
    $new_username = "张三_更新版";
    setcookie("username", $new_username, time() + 3600);
    echo "用户名已更新为:" . htmlspecialchars($new_username);
}

// 更新Cookie过期时间
if (isset($_COOKIE['last_visit'])) {
    // 延长过期时间
    setcookie("last_visit", date('Y-m-d H:i:s'), time() + (86400 * 7));
    echo "访问时间Cookie已延长7天";
}
?>

4. 删除Cookie

删除Cookie通过将过期时间设置为过去的时间来实现:

<?php
// 删除单个Cookie
if (isset($_COOKIE['username'])) {
    setcookie("username", "", time() - 3600); // 设置为过去的时间
    echo "username Cookie已删除";
}

// 删除所有Cookie(示例)
$cookies_to_delete = ['username', 'user_preferences', 'last_visit'];
$deleted_count = 0;

foreach ($cookies_to_delete as $cookie_name) {
    if (isset($_COOKIE[$cookie_name])) {
        setcookie($cookie_name, "", time() - 3600, "/");
        $deleted_count++;
    }
}

echo "已删除 $deleted_count 个Cookie";
?>

Cookie的实际应用示例

示例1:用户偏好设置系统

<?php
// 用户偏好设置管理系统
class UserPreferences {
    private $preferences = [];
    private $cookie_name = "user_prefs";
    private $cookie_duration = 86400 * 30; // 30天

    public function __construct() {
        $this->loadPreferences();
    }

    // 从Cookie加载用户偏好
    private function loadPreferences() {
        if (isset($_COOKIE[$this->cookie_name])) {
            $cookie_data = $_COOKIE[$this->cookie_name];
            $decoded_data = json_decode($cookie_data, true);
            if ($decoded_data) {
                $this->preferences = $decoded_data;
            }
        }
    }

    // 设置用户偏好
    public function setPreference($key, $value) {
        $this->preferences[$key] = $value;
        $this->savePreferences();
    }

    // 获取用户偏好
    public function getPreference($key, $default = null) {
        return $this->preferences[$key] ?? $default;
    }

    // 保存偏好到Cookie
    private function savePreferences() {
        $encoded_data = json_encode($this->preferences);
        setcookie($this->cookie_name, $encoded_data, time() + $this->cookie_duration, "/", "", false, true);
    }

    // 获取所有偏好
    public function getAllPreferences() {
        return $this->preferences;
    }

    // 清除所有偏好
    public function clearPreferences() {
        $this->preferences = [];
        setcookie($this->cookie_name, "", time() - 3600, "/");
    }
}

// 使用示例
$preferences = new UserPreferences();

// 设置用户偏好
$preferences->setPreference('theme', 'dark');
$preferences->setPreference('language', 'zh');
$preferences->setPreference('page_size', 20);
$preferences->setPreference('notifications', 'enabled');

// 获取并应用用户偏好
$theme = $preferences->getPreference('theme', 'light');
$language = $preferences->getPreference('language', 'en');
$page_size = $preferences->getPreference('page_size', 10);

echo "<div style='background-color: " . ($theme === 'dark' ? '#333' : '#fff') . "; color: " . ($theme === 'dark' ? '#fff' : '#333') . "; padding: 20px;'>";
echo "<h2>用户偏好设置已应用</h2>";
echo "主题:$theme<br>";
echo "语言:$language<br>";
echo "页面大小:$page_size 条记录<br>";
echo "</div>";
?>

示例2:访问计数器

<?php
// 网站访问计数器
class VisitCounter {
    private $cookie_name = "visit_counter";
    private $cookie_duration = 86400 * 365; // 1年

    public function __construct() {
        $this->trackVisit();
    }

    // 记录访问
    private function trackVisit() {
        if (!isset($_COOKIE[$this->cookie_name])) {
            // 首次访问
            $visit_data = [
                'total_visits' => 1,
                'first_visit' => date('Y-m-d H:i:s'),
                'last_visit' => date('Y-m-d H:i:s'),
                'visit_dates' => [date('Y-m-d')]
            ];
        } else {
            // 重复访问
            $visit_data = json_decode($_COOKIE[$this->cookie_name], true);
            $visit_data['total_visits']++;
            $visit_data['last_visit'] = date('Y-m-d H:i:s');

            // 添加今天的日期(如果不重复)
            $today = date('Y-m-d');
            if (!in_array($today, $visit_data['visit_dates'])) {
                $visit_data['visit_dates'][] = $today;
                // 保持最近30天的访问记录
                if (count($visit_data['visit_dates']) > 30) {
                    array_shift($visit_data['visit_dates']);
                }
            }
        }

        // 保存到Cookie
        setcookie($this->cookie_name, json_encode($visit_data), time() + $this->cookie_duration, "/", "", false, true);
    }

    // 获取访问统计
    public function getVisitStats() {
        if (isset($_COOKIE[$this->cookie_name])) {
            return json_decode($_COOKIE[$this->cookie_name], true);
        }
        return null;
    }

    // 获取总访问次数
    public function getTotalVisits() {
        $stats = $this->getVisitStats();
        return $stats['total_visits'] ?? 0;
    }

    // 获取访问天数
    public function getVisitDays() {
        $stats = $this->getVisitStats();
        return $stats ? count($stats['visit_dates']) : 0;
    }

    // 显示欢迎信息
    public function showWelcomeMessage() {
        $stats = $this->getVisitStats();

        if ($stats) {
            if ($stats['total_visits'] === 1) {
                $message = "欢迎您首次访问本站!";
            } else {
                $days_between = $this->calculateDaysBetween($stats['first_visit'], $stats['last_visit']);
                $message = "欢迎回来!您已经访问了 {$stats['total_visits']} 次,";
                $message .= "跨越了 {$days_between} 天,";
                $message .= "其中有 {$this->getVisitDays()} 天有访问记录。";
            }
        } else {
            $message = "欢迎访问本站!";
        }

        return $message;
    }

    // 计算两个日期之间的天数
    private function calculateDaysBetween($start_date, $end_date) {
        $start = new DateTime($start_date);
        $end = new DateTime($end_date);
        return $start->diff($end)->days + 1;
    }
}

// 使用访问计数器
$counter = new VisitCounter();

echo "<div style='border: 1px solid #ddd; padding: 20px; margin: 20px; border-radius: 5px;'>";
echo "<h2>访问统计</h2>";
echo "<p><strong>" . $counter->showWelcomeMessage() . "</strong></p>";

$stats = $counter->getVisitStats();
if ($stats) {
    echo "<ul>";
    echo "<li>总访问次数:<strong>" . $stats['total_visits'] . "</strong> 次</li>";
    echo "<li>首次访问:<strong>" . $stats['first_visit'] . "</strong></li>";
    echo "<li>最近访问:<strong>" . $stats['last_visit'] . "</strong></li>";
    echo "<li>访问天数:<strong>" . $counter->getVisitDays() . "</strong> 天</li>";
    echo "</ul>";
}
echo "</div>";
?>

示例3:购物车系统

<?php
// 简单的购物车Cookie实现
class ShoppingCart {
    private $cookie_name = "shopping_cart";
    private $cookie_duration = 86400 * 7; // 7天

    public function __construct() {
        $this->initializeCart();
    }

    // 初始化购物车
    private function initializeCart() {
        if (!isset($_COOKIE[$this->cookie_name])) {
            // 创建空的购物车
            $cart_data = [
                'items' => [],
                'total_price' => 0,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            $this->saveCart($cart_data);
        }
    }

    // 添加商品到购物车
    public function addItem($product_id, $product_name, $price, $quantity = 1) {
        $cart = $this->getCart();

        // 检查商品是否已在购物车中
        if (isset($cart['items'][$product_id])) {
            $cart['items'][$product_id]['quantity'] += $quantity;
        } else {
            $cart['items'][$product_id] = [
                'name' => $product_name,
                'price' => $price,
                'quantity' => $quantity,
                'added_at' => date('Y-m-d H:i:s')
            ];
        }

        // 更新总价和修改时间
        $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
        $cart['updated_at'] = date('Y-m-d H:i:s');

        $this->saveCart($cart);
        return true;
    }

    // 从购物车移除商品
    public function removeItem($product_id) {
        $cart = $this->getCart();

        if (isset($cart['items'][$product_id])) {
            unset($cart['items'][$product_id]);
            $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
            $cart['updated_at'] = date('Y-m-d H:i:s');
            $this->saveCart($cart);
            return true;
        }
        return false;
    }

    // 更新商品数量
    public function updateQuantity($product_id, $quantity) {
        if ($quantity <= 0) {
            return $this->removeItem($product_id);
        }

        $cart = $this->getCart();

        if (isset($cart['items'][$product_id])) {
            $cart['items'][$product_id]['quantity'] = $quantity;
            $cart['total_price'] = $this->calculateTotalPrice($cart['items']);
            $cart['updated_at'] = date('Y-m-d H:i:s');
            $this->saveCart($cart);
            return true;
        }
        return false;
    }

    // 获取购物车数据
    public function getCart() {
        if (isset($_COOKIE[$this->cookie_name])) {
            return json_decode($_COOKIE[$this->cookie_name], true);
        }
        return null;
    }

    // 获取购物车中的商品数量
    public function getItemCount() {
        $cart = $this->getCart();
        if ($cart && isset($cart['items'])) {
            return count($cart['items']);
        }
        return 0;
    }

    // 获取商品总数量
    public function getTotalQuantity() {
        $cart = $this->getCart();
        $total = 0;

        if ($cart && isset($cart['items'])) {
            foreach ($cart['items'] as $item) {
                $total += $item['quantity'];
            }
        }
        return $total;
    }

    // 清空购物车
    public function clearCart() {
        setcookie($this->cookie_name, "", time() - 3600, "/");
        return true;
    }

    // 保存购物车到Cookie
    private function saveCart($cart_data) {
        $encoded_data = json_encode($cart_data);
        setcookie($this->cookie_name, $encoded_data, time() + $this->cookie_duration, "/", "", false, true);
    }

    // 计算总价
    private function calculateTotalPrice($items) {
        $total = 0;
        foreach ($items as $item) {
            $total += $item['price'] * $item['quantity'];
        }
        return $total;
    }

    // 显示购物车
    public function displayCart() {
        $cart = $this->getCart();

        if (!$cart || empty($cart['items'])) {
            echo "<p>购物车是空的</p>";
            return;
        }

        echo "<div style='border: 1px solid #ddd; padding: 20px; margin: 20px;'>";
        echo "<h2>购物车</h2>";
        echo "<table style='width: 100%; border-collapse: collapse;'>";
        echo "<tr style='background-color: #f2f2f2;'>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>商品</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>单价</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>数量</th>";
        echo "<th style='padding: 10px; border: 1px solid #ddd;'>小计</th>";
        echo "</tr>";

        foreach ($cart['items'] as $product_id => $item) {
            $subtotal = $item['price'] * $item['quantity'];
            echo "<tr>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>" . htmlspecialchars($item['name']) . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($item['price'], 2) . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>" . $item['quantity'] . "</td>";
            echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($subtotal, 2) . "</td>";
            echo "</tr>";
        }

        echo "<tr style='font-weight: bold;'>";
        echo "<td colspan='3' style='padding: 10px; border: 1px solid #ddd; text-align: right;'>总计:</td>";
        echo "<td style='padding: 10px; border: 1px solid #ddd;'>¥" . number_format($cart['total_price'], 2) . "</td>";
        echo "</tr>";
        echo "</table>";
        echo "</div>";
    }
}

// 使用购物车示例
$cart = new ShoppingCart();

// 模拟添加商品
$cart->addItem(1, "iPhone 14", 5999.00, 1);
$cart->addItem(2, "MacBook Pro", 12999.00, 1);
$cart->addItem(3, "AirPods", 1299.00, 2);

// 显示购物车信息
echo "<div style='margin: 20px;'>";
echo "<h3>购物车统计</h3>";
echo "<p>商品种类:" . $cart->getItemCount() . " 种</p>";
echo "<p>商品总数:" . $cart->getTotalQuantity() . " 件</p>";
echo "</div>";

// 显示购物车详情
$cart->displayCart();
?>

Cookie的安全性问题

1. Cookie的安全威胁

<?php
// ❌ 不安全的Cookie使用方式
setcookie("user_id", "123", time() + 3600); // 直接存储敏感信息
setcookie("password", "secret123", time() + 3600); // 绝对不要存储密码

// ✅ 安全的Cookie使用方式
$user_session_token = bin2hex(random_bytes(32)); // 生成安全的随机令牌
setcookie("session_token", $user_session_token, [
    'expires' => time() + 3600,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,      // 只在HTTPS下传输
    'httponly' => true,    // 防止JavaScript访问
    'samesite' => 'Strict' // 防止CSRF攻击
]);
?>

2. Cookie安全最佳实践

<?php
// 安全Cookie设置类
class SecureCookieManager {
    private $domain;
    private $use_https;

    public function __construct($domain = '', $use_https = false) {
        $this->domain = $domain;
        $this->use_https = $use_https;
    }

    // 设置安全的Cookie
    public function setSecureCookie($name, $value, $expiration = 0, $path = '/') {
        $options = [
            'expires' => $expiration,
            'path' => $path,
            'domain' => $this->domain,
            'secure' => $this->use_https,
            'httponly' => true,
            'samesite' => 'Strict'
        ];

        // 对Cookie值进行编码(可选)
        $encoded_value = base64_encode($value);

        return setcookie($name, $encoded_value, $options);
    }

    // 获取并验证Cookie
    public function getSecureCookie($name) {
        if (isset($_COOKIE[$name])) {
            $value = base64_decode($_COOKIE[$name]);
            // 这里可以添加额外的验证逻辑
            return $value;
        }
        return null;
    }

    // 删除Cookie
    public function deleteCookie($name, $path = '/') {
        $options = [
            'expires' => time() - 3600,
            'path' => $path,
            'domain' => $this->domain,
            'secure' => $this->use_https,
            'httponly' => true,
            'samesite' => 'Strict'
        ];

        return setcookie($name, '', $options);
    }
}

// 使用示例
$cookie_manager = new SecureCookieManager('example.com', true);

// 安全地设置用户Cookie
$user_data = ['id' => 123, 'role' => 'user'];
$cookie_manager->setSecureCookie('user_session', json_encode($user_data), time() + 3600);

// 安全地获取用户Cookie
$user_session = $cookie_manager->getSecureCookie('user_session');
if ($user_session) {
    $user_data = json_decode($user_session, true);
    echo "用户ID:" . $user_data['id'];
}
?>

Cookie的限制和注意事项

1. Cookie大小限制

<?php
// 检查Cookie大小
function checkCookieSize($name, $value) {
    $max_size = 4096; // 4KB
    $cookie_size = strlen($name . '=' . $value);

    if ($cookie_size > $max_size) {
        throw new Exception("Cookie大小超过限制:{$cookie_size} 字节 > {$max_size} 字节");
    }

    return true;
}

// 安全设置大Cookie
try {
    $large_data = "这是一个很长的数据...";
    checkCookieSize("large_cookie", $large_data);
    setcookie("large_cookie", $large_data, time() + 3600);
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
    // 考虑使用Session或其他存储方式
}
?>

2. Cookie数量限制

<?php
// Cookie数量管理类
class CookieLimitManager {
    private $max_cookies = 50; // 每个域名最多50个Cookie
    private $cookie_prefix = "app_";

    // 检查Cookie数量
    public function checkCookieLimit() {
        $app_cookies = 0;
        foreach ($_COOKIE as $name => $value) {
            if (strpos($name, $this->cookie_prefix) === 0) {
                $app_cookies++;
            }
        }

        if ($app_cookies >= $this->max_cookies) {
            return false;
        }
        return true;
    }

    // 智能设置Cookie
    public function setSmartCookie($name, $value, $expiration = 0) {
        $full_name = $this->cookie_prefix . $name;

        if (!$this->checkCookieLimit()) {
            // 清理最旧的Cookie
            $this->cleanupOldCookies();
        }

        return setcookie($full_name, $value, $expiration, "/");
    }

    // 清理最旧的Cookie
    private function cleanupOldCookies() {
        // 这里可以实现清理逻辑
        // 例如删除临时Cookie或不重要的Cookie
    }
}
?>

总结

Cookie是Web开发中实现状态管理的重要工具,掌握其正确使用方法对于开发高质量的Web应用至关重要。

关键要点:

  1. Cookie存储在客户端,适合存储非敏感的用户偏好信息
  2. 正确设置安全选项:Secure、HttpOnly、SameSite
  3. 注意大小限制:单个Cookie不超过4KB
  4. 考虑隐私保护:遵守GDPR等隐私法规
  5. 合理使用过期时间:平衡用户体验和服务器资源

最佳实践:

  • 使用Session存储敏感数据,Cookie存储非敏感数据
  • 始终对Cookie值进行验证和过滤
  • 定期清理不必要的Cookie
  • 在HTTPS网站下使用安全Cookie选项
  • 遵循最小权限原则

通过合理使用Cookie,你可以创建更加用户友好和功能丰富的Web应用程序。在下一章中,我们将学习Session的使用,了解如何在服务器端管理用户状态。