一、回顾与本篇目标
上一篇你学会了 PHP 表单处理——接收 GET 和 POST 数据、验证用户输入、防御 XSS 和 CSRF 攻击、处理文件上传。你的应用现在可以和用户交互了。
但有一个根本问题还没解决:HTTP 是无状态协议。这意味着服务器默认把每个请求都当作独立的——同一个用户访问首页和访问个人中心,服务器不知道这是同一个人。你登录之后刷新页面,服务器”忘记”你已经登录了。每次请求都要重新验证身份?那用户体验会非常糟糕。
解决这个问题的两种核心技术就是 Cookie 和 Session。Cookie 在浏览器端存储数据,Session 在服务器端存储数据。两者配合使用,就能让服务器”记住”用户——登录状态、购物车内容、浏览历史、语言偏好——这些都需要跨请求保持状态。
本篇的目标:
- 理解 Cookie 的原理和用法:创建、读取、删除
- 理解 Session 的原理和用法:启动、存取数据、销毁
- 学会用 Session 实现用户登录状态保持
- 理解 Cookie 的安全属性:HttpOnly、Secure、SameSite
- 掌握 Session 安全:固定攻击防御、过期时间配置
- 实现一个完整的登录/注销系统
二、为什么需要状态管理
HTTP 协议的设计哲学是每个请求都是独立的。服务器处理完一个请求就”忘记”了——不记得上一个请求是谁发的、上一个请求做了什么。这种设计让 HTTP 协议非常简单、可扩展,但也带来一个问题:怎么让服务器知道两次请求来自同一个用户?
比如一个在线购物流程:用户浏览商品 → 加入购物车 → 结算 → 付款。这涉及多次 HTTP 请求。如果不做状态管理,每次请求服务器都以为是一个新用户,购物车永远是空的。
解决方案:
- Cookie:服务器在响应中给浏览器发一个”通行证”,浏览器把它存起来。以后每次请求同一个网站,浏览器自动把这个通行证带回去。这样服务器就能认出”哦,是你啊”。
- Session:通行证上只写一个编号(Session ID),真正的数据(用户信息、购物车内容)存在服务器端。浏览器只负责携带编号,服务器根据编号找到对应的数据。
类比:Cookie 就像超市的会员卡——卡片本身可以存储一些信息(积分、等级)。Session 就像超市的存包柜——你手里只有一把钥匙(Session ID),你的物品(用户数据)存在超市的柜子里(服务器端)。
三、Cookie:浏览器端的存储
3.1 Cookie 是什么
Cookie 是服务器通过 HTTP 响应头发给浏览器的一小段文本数据(通常不超过 4KB)。浏览器收到后会把它存起来。之后每次向同一个域名发送请求时,浏览器会自动把对应的 Cookie 附带在请求头中发送给服务器。
Cookie 的核心机制可以概括为:
- 服务器发送 Cookie:通过
Set-Cookie响应头。 - 浏览器存储 Cookie:按域名分类存储,每个域名下的 Cookie 互不干扰。
- 浏览器回传 Cookie:每次请求自动把该域名下的所有 Cookie 通过
Cookie请求头发送给服务器。
3.2 用 PHP 创建 Cookie
使用 setcookie() 函数。这个函数必须在任何 HTML 输出之前调用——因为它本质上是设置 HTTP 响应头,响应头必须在响应体之前发送。
<?php
// 创建一个 Cookie:名为 username,值为"张三",有效期 7 天
setcookie('username', '张三', time() + 7 * 24 * 60 * 60, '/');
echo "Cookie 已设置";
?>
setcookie() 的参数:
| 参数 | 含义 | 示例 |
|---|---|---|
| name | Cookie 的名称(必填) | 'username' |
| value | Cookie 的值 | '张三' |
| expires | 过期时间(Unix 时间戳) | time() + 86400(24 小时后) |
| path | Cookie 在哪个路径下生效 | '/'(全站生效) |
| domain | Cookie 在哪个域名下生效 | 'example.com' |
| secure | 是否只在 HTTPS 下传输 | true |
| httponly | 是否禁止 JavaScript 访问 | true |
过期时间的计算:
time() + 3600:1 小时后过期。time() + 86400:24 小时后过期(86400 = 24 × 60 × 60)。time() + 7 * 86400:7 天后过期。- 不设置 expires:Cookie 在浏览器关闭时自动删除(会话 Cookie)。
3.3 读取 Cookie
Cookie 的值通过 $_COOKIE 超全局变量获取:
<?php
// 读取名为 username 的 Cookie
$username = $_COOKIE['username'] ?? '未设置';
echo "Cookie 中的 username:{$username}";
?>
重要:通过 setcookie() 设置的 Cookie,在当前请求中无法通过 $_COOKIE 立即获取。因为 Cookie 是服务器通过响应头发给浏览器的,浏览器收到响应后才会存储它。只有下一次请求,浏览器才会把它带回来,这时候 $_COOKIE 里才有值。
<?php
// 第一次访问:设置 Cookie
setcookie('test', 'hello', time() + 3600, '/');
echo isset($_COOKIE['test']) ? $_COOKIE['test'] : 'Cookie 还没生效,刷新页面试试';
// 输出:Cookie 还没生效,刷新页面试试
// 刷新页面后:Cookie 已经生效
echo $_COOKIE['test'] ?? '未设置';
// 输出:hello
?>
3.4 删除 Cookie
删除 Cookie 的方法是:把过期时间设置为过去的时间。浏览器发现 Cookie 已经过期,就会自动删除它。
<?php
// 把过期时间设为 1 小时前
setcookie('username', '', time() - 3600, '/');
echo "Cookie 已删除";
?>
删除时,path 参数必须和创建时一致,否则浏览器可能删不掉。
四、Session:服务器端的存储
4.1 Session 是什么
Session 是服务器端的存储机制。服务器为每个用户创建一个唯一标识(Session ID),通过 Cookie 传给浏览器。浏览器每次请求时带上这个 Session ID,服务器根据它找到对应的数据。
Session 的工作流程:
- 用户第一次访问网站,服务器创建一个 Session,生成一个唯一的 Session ID。
- 服务器把 Session ID 通过 Cookie(名为
PHPSESSID)发给浏览器。 - 浏览器存储这个 Cookie。
- 用户再次访问,浏览器自动带上
PHPSESSIDCookie。 - 服务器根据 Session ID 找到对应的 Session 数据。
Session 和 Cookie 的关系:Session 依赖 Cookie 来传递 Session ID。如果浏览器禁用了 Cookie,Session 就无法正常工作(可以通过 URL 参数传递 Session ID,但极不推荐)。
4.2 启动 Session
在使用 Session 之前,必须调用 session_start()。这个函数必须在任何 HTML 输出之前调用——和 setcookie() 一样,它需要设置响应头。
<?php
// 启动 Session(必须在任何输出之前)
session_start();
// 现在可以使用 $_SESSION 了
$_SESSION['user_id'] = 1;
$_SESSION['username'] = '张三';
$_SESSION['logged_in'] = true;
echo "Session 已启动,数据已存储";
?>
session_start() 做了什么:
- 检查请求中是否带有
PHPSESSIDCookie。 - 如果有,根据这个 ID 从服务器存储中加载对应的 Session 数据,填充到
$_SESSION数组中。 - 如果没有,创建一个新的 Session ID,生成一个新的空 Session,并通过
Set-Cookie响应头发给浏览器。
最佳实践:把 session_start() 放在配置文件中,确保每个需要 Session 的页面都调用它。可以使用条件判断避免重复启动:
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
?>
4.3 存取 Session 数据
Session 数据存储在 $_SESSION 超全局数组中。这是一个关联数组,你可以在里面存任何 PHP 数据类型(字符串、数字、数组、对象——只要对象是可序列化的)。
<?php
session_start();
// 存储数据
$_SESSION['user'] = [
'id' => 1,
'name' => '张三',
'email' => 'zhangsan@example.com',
'role' => 'admin'
];
$_SESSION['login_time'] = time();
$_SESSION['cart'] = ['item1', 'item2'];
// 读取数据
echo "欢迎,{$_SESSION['user']['name']}!<br>";
echo "登录时间:" . date('Y-m-d H:i:s', $_SESSION['login_time']) . "<br>";
echo "购物车有 " . count($_SESSION['cart']) . " 件商品<br>";
// 删除单个 Session 变量
unset($_SESSION['cart']);
// 修改 Session 变量
$_SESSION['user']['role'] = 'editor';
?>
4.4 销毁 Session
用户登出时,需要彻底清除 Session:
<?php
session_start();
// 方式一:清空所有 Session 数据(保留 Session ID)
$_SESSION = [];
// 方式二:彻底销毁 Session(推荐用于登出)
session_unset(); // 清空 $_SESSION 数组
session_destroy(); // 删除服务器端的 Session 文件
// 同时删除客户端的 Session Cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
echo "Session 已销毁,用户已登出";
?>
为什么需要同时删除 Cookie? session_destroy() 只删除服务器端的 Session 数据,浏览器端的 PHPSESSID Cookie 还在。如果不删除 Cookie,下次请求浏览器仍然会发送旧的 Session ID,服务器会创建一个新的空 Session。虽然数据没了,但 Cookie 残留是不规范的做法。
五、Session 的存储与配置
PHP 默认把 Session 数据以文件形式存储在服务器的临时目录中(通常是 /tmp)。每个 Session 对应一个文件,文件名是 sess_ + Session ID。
5.1 Session 的过期时间
PHP 默认的 Session 过期机制有两个层面:
- Session 文件本身:由 PHP 的垃圾回收机制清理。默认在 24 分钟后可能被清理(取决于
session.gc_maxlifetime配置,通常是 1440 秒 = 24 分钟)。 - Session Cookie 的过期:默认是浏览器关闭时删除(会话 Cookie)。如果你希望用户关闭浏览器后仍然保持登录,需要手动设置 Session Cookie 的过期时间。
要延长登录状态的有效期,在 session_start() 之前设置:
<?php
// 设置 Session 的有效期为 7 天
ini_set('session.gc_maxlifetime', 7 * 24 * 60 * 60); // 服务器端
session_set_cookie_params(7 * 24 * 60 * 60); // 浏览器端 Cookie
session_start();
?>
或者更简单的方式——在 php.ini 配置文件中全局设置(推荐,但不总是可操作)。
5.2 Session 存储的其他方式
文件存储在小规模应用中可以接受,但存在两个问题:
- 性能:高并发时,大量 Session 文件的读写会成为瓶颈。
- 分布式部署:如果有多台服务器,一台服务器上的 Session 文件另一台无法访问——用户的请求被负载均衡分配到不同服务器时,Session 就丢了。
解决方案是把 Session 存储到共享的中央存储中——通常是 Redis 或数据库。PHP 允许你自定义 Session 处理器。最常用的是 Redis:
<?php
// 使用 Redis 存储 Session(需要安装 phpredis 扩展)
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
session_start();
?>
这样所有服务器的 Session 都存到同一个 Redis 中,实现了共享。这个暂时不需要深入,知道有这种方案就行。
六、Cookie 的安全属性
Cookie 默认通过 HTTP 明文传输(如果不用 HTTPS)。它有一些重要的安全属性,每个都应该了解:
| 属性 | 含义 | 建议值 |
|---|---|---|
| HttpOnly | 禁止 JavaScript 通过 document.cookie 访问此 Cookie |
始终开启(特别是 Session Cookie) |
| Secure | 只在 HTTPS 连接中传输此 Cookie | 生产环境必须开启 |
| SameSite | 控制跨站请求是否携带此 Cookie | 设为 Lax(默认)或 Strict |
<?php
// 安全的 Cookie 设置示例
setcookie('session_token', $token, [
'expires' => time() + 86400, // 24 小时后过期
'path' => '/', // 全站生效
'domain' => '', // 当前域名
'secure' => true, // 仅 HTTPS
'httponly' => true, // 禁止 JS 访问
'samesite' => 'Lax' // 跨站请求控制
]);
?>
HttpOnly 为什么重要:如果 Session Cookie 没有 HttpOnly 属性,攻击者通过 XSS 漏洞注入的 JavaScript 代码可以直接读取 document.cookie,把 Session ID 发送给攻击者的服务器。攻击者拿到 Session ID 后,就可以冒充你的身份访问网站。开启 HttpOnly 之后,JavaScript 完全无法访问这个 Cookie,XSS 攻击的破坏范围被大大限制。
SameSite 的三个值:
- Strict:完全禁止跨站携带 Cookie。用户从外部链接点击进来时,Cookie 不会被发送——用户总是未登录状态。最安全但用户体验差。
- Lax(默认):大部分跨站请求不携带 Cookie,但导航到目标网站的 GET 请求会携带。用户在搜索引擎点链接进入你的网站时,能保持登录状态。这是平衡安全和体验的最佳选择。
- None:所有跨站请求都携带 Cookie。必须同时设置
Secure。不推荐,除非你明确需要(比如嵌入在 iframe 中的第三方应用)。
七、Session 安全
7.1 会话固定攻击
会话固定攻击的基本思路:
- 攻击者访问你的网站,获得一个 Session ID(比如
abc123)。 - 攻击者诱导用户点击一个链接,URL 中带了这个 Session ID(比如
http://yoursite.com/?PHPSESSID=abc123)。 - 用户点击链接访问网站,PHP 使用了这个已知的 Session ID。
- 用户登录后,Session 中存储了用户的身份信息。
- 攻击者用同样的 Session ID 访问网站,直接获得用户的登录状态。
防御:登录后重新生成 Session ID
<?php
session_start();
// 验证用户名密码...
if ($login_successful) {
// 登录成功后重新生成 Session ID
session_regenerate_id(true);
// true 参数表示删除旧的 Session 文件
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['name'];
}
?>
session_regenerate_id(true) 生成一个新的 Session ID,攻击者之前拿到的旧 ID 立即失效。用户登录后的身份信息绑定在新的、只有用户自己知道的 Session ID 上。
7.2 Session 的其他安全配置
<?php
// 在 session_start() 之前设置
ini_set('session.use_strict_mode', 1); // 严格模式:拒绝未初始化的 Session ID
ini_set('session.cookie_httponly', 1); // Cookie 设为 HttpOnly
ini_set('session.cookie_secure', 1); // Cookie 仅在 HTTPS 下传输(生产环境)
ini_set('session.cookie_samesite', 'Lax'); // SameSite 属性
ini_set('session.use_only_cookies', 1); // 仅通过 Cookie 传递 Session ID(禁止 URL 传递)
session_start();
?>
严格模式的作用:如果浏览器传来的 Session ID 在服务器端不存在(比如攻击者伪造的),PHP 会拒绝使用它,创建一个新的 Session ID。这可以防止攻击者随意指定 Session ID。
八、综合演示:完整的登录/注销系统
下面这个完整示例综合了 Cookie、Session、数据库操作、表单验证、安全防护:
<?php
// config.php —— 公共配置
session_start();
// 安全配置
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);
require_once 'db.php';
// 检查用户是否已登录
function isLoggedIn(): bool {
return isset($_SESSION['user_id']);
}
// 获取当前登录用户的信息
function getCurrentUser(): ?array {
if (!isLoggedIn()) {
return null;
}
try {
$pdo = getDBConnection();
$stmt = $pdo->prepare("SELECT id, name, email, age, created_at FROM users WHERE id = :id");
$stmt->execute(['id' => $_SESSION['user_id']]);
return $stmt->fetch() ?: null;
} catch (PDOException $e) {
return null;
}
}
// 获取提示消息
$message = $_SESSION['message'] ?? '';
$messageType = $_SESSION['message_type'] ?? '';
unset($_SESSION['message'], $_SESSION['message_type']);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title><?= isLoggedIn() ? '个人中心' : '登录' ?></title>
<style>
body { font-family: sans-serif; padding: 40px; background: #f0f4f8; display: flex; justify-content: center; }
.container { background: white; padding: 40px; border-radius: 12px; width: 400px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h2 { margin-top: 0; color: #333; text-align: center; }
.form-group { margin-bottom: 16px; }
label { display: block; margin-bottom: 4px; color: #555; font-size: 14px; }
input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
.error { color: #e74c3c; font-size: 13px; margin-top: 4px; }
.message { padding: 12px; border-radius: 6px; margin-bottom: 20px; text-align: center; }
.success { background: #e8f5e9; color: #2e7d32; }
.error-msg { background: #fbe9e7; color: #c62828; }
button { width: 100%; padding: 12px; background: #4a90d9; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
button:hover { background: #3a7bc8; }
.btn-logout { background: #e74c3c; margin-top: 20px; }
.btn-logout:hover { background: #c0392b; }
.user-info { background: #f8f9fa; padding: 16px; border-radius: 8px; margin: 16px 0; }
.user-info p { margin: 8px 0; }
.links { text-align: center; margin-top: 16px; font-size: 14px; }
.links a { color: #4a90d9; text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<?php if ($message): ?>
<div class="message <?= $messageType ?>"><?= htmlspecialchars($message, ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
<?php if (isLoggedIn()): ?>
<!-- 已登录:显示用户信息 -->
<?php $user = getCurrentUser(); ?>
<h2>👤 个人中心</h2>
<?php if ($user): ?>
<div class="user-info">
<p><strong>姓名:</strong><?= htmlspecialchars($user['name'], ENT_QUOTES, 'UTF-8') ?></p>
<p><strong>邮箱:</strong><?= htmlspecialchars($user['email'], ENT_QUOTES, 'UTF-8') ?></p>
<p><strong>年龄:</strong><?= $user['age'] ?> 岁</p>
<p><strong>注册时间:</strong><?= $user['created_at'] ?></p>
</div>
<?php endif; ?>
<form method="POST" action="logout.php">
<button type="submit" class="btn-logout">退出登录</button>
</form>
<?php else: ?>
<!-- 未登录:显示登录表单 -->
<h2>🔐 用户登录</h2>
<form method="POST" action="login.php">
<div class="form-group">
<label>邮箱</label>
<input type="email" name="email" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" required>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="remember"> 记住我(7 天)
</label>
</div>
<button type="submit">登录</button>
</form>
<div class="links">
还没有账号?<a href="register.php">立即注册</a>
</div>
<?php endif; ?>
</div>
</body>
</html>
login.php —— 处理登录逻辑:
<?php
session_start();
require_once 'db.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.php');
exit;
}
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
// 验证
if ($email === '' || $password === '') {
$_SESSION['message'] = '请填写邮箱和密码';
$_SESSION['message_type'] = 'error-msg';
header('Location: index.php');
exit;
}
try {
$pdo = getDBConnection();
$stmt = $pdo->prepare("SELECT id, name, password FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password'])) {
$_SESSION['message'] = '邮箱或密码错误';
$_SESSION['message_type'] = 'error-msg';
header('Location: index.php');
exit;
}
// 登录成功:重新生成 Session ID
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['name'];
$_SESSION['login_time'] = time();
// 如果勾选了"记住我",延长 Session Cookie 有效期
if ($remember) {
setcookie(session_name(), session_id(), time() + 7 * 24 * 60 * 60, '/');
}
$_SESSION['message'] = '登录成功!欢迎回来,' . $user['name'];
$_SESSION['message_type'] = 'success';
header('Location: index.php');
exit;
} catch (PDOException $e) {
$_SESSION['message'] = '系统错误,请稍后重试';
$_SESSION['message_type'] = 'error-msg';
header('Location: index.php');
exit;
}
?>
logout.php —— 处理登出逻辑:
<?php
session_start();
// 清空 Session 数组
$_SESSION = [];
// 删除服务器端的 Session 文件
session_destroy();
// 删除客户端的 Session Cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// 重定向到首页
header('Location: index.php');
exit;
?>
代码设计要点:
- 会话固定防御:登录成功后调用
session_regenerate_id(true),防止攻击者使用预先准备的 Session ID。 - “记住我”功能:勾选后,通过
setcookie()延长 Session Cookie 的有效期到 7 天。不勾选时,Cookie 是会话级别的(浏览器关闭即失效)。 - 密码验证:使用
password_verify()验证用户输入的密码和数据库中存储的哈希值是否匹配。 - 登出完整性:清空
$_SESSION、调用session_destroy()、删除浏览器端的 Session Cookie——三步缺一不可。 - 重定向后显示消息:处理完登录/登出后,把提示消息存入 Session,然后重定向到首页。首页读取并显示消息后立即清除——这是 Flash Message(一次性消息)的标准实现方式。
九、本篇动手练习
练习 1:实现用户偏好设置
新建 practice8-1.php,用 Cookie 存储用户的主题偏好(亮色/暗色)和字体大小。用户通过表单选择偏好后,页面使用相应的样式。Cookie 有效期设为 30 天。
练习 2:简单的购物车
新建 practice8-2.php,用 Session 实现一个简单的购物车。用户可以”添加商品到购物车”(商品列表可以是硬编码的数组),购物车页面显示所有已添加的商品、数量和总价。用户关闭浏览器再打开,购物车内容还在(默认 Session Cookie 在浏览器关闭时失效,可以不处理这个)。
练习 3:登录次数限制
新建 practice8-3.php,在登录功能中增加失败次数限制。同一个 IP 地址在 15 分钟内连续登录失败 5 次,锁定该 IP 15 分钟。用 Session 或数据库记录失败次数和时间。
练习 4:完整的注册登录系统
新建一个完整的项目文件夹,包含注册页面(参考第七篇的综合演示)、登录页面(参考本篇的综合演示)、个人中心页面、退出登录功能。确保:CSRF Token 防护、密码哈希存储、会话固定防御、XSS 输出转义、SQL 注入防护——安全措施全部到位。
十、本篇小结
这一篇你学会了 PHP 中状态管理的两个核心机制:
- Cookie:浏览器端的存储。通过
setcookie()创建,通过$_COOKIE读取,把过期时间设为过去来删除。有大小限制(4KB),每次请求都会自动发送给服务器。 - Session:服务器端的存储。通过
session_start()启动,通过$_SESSION存取数据。依赖 Cookie 传递 Session ID(PHPSESSID)。 - Cookie 安全属性:
HttpOnly(禁止 JS 访问)、Secure(仅 HTTPS)、SameSite(控制跨站携带)。Session Cookie 必须开启 HttpOnly。 - Session 安全:登录后重新生成 Session ID(
session_regenerate_id(true))防止会话固定。严格模式拒绝未初始化的 Session ID。只通过 Cookie 传递 Session ID,禁止 URL 传递。 - 登录系统:完整的登录/登出流程——验证身份、存储 Session、Flash Message 提示、”记住我”功能。
Session 和 Cookie 是 Web 开发中最基础的状态管理机制。掌握了它们,你就能实现登录状态保持、购物车、用户偏好、表单多步骤提交等任何需要”跨请求记住信息”的功能。下一篇,我们学习 PHP 面向对象编程——类、对象、继承、封装、命名空间。
下一篇预告
下一篇——《PHP 面向对象编程——类、对象与继承》:理解类和对象的概念、构造函数和析构函数、访问修饰符(public/private/protected)、继承和多态、静态属性和方法、命名空间的使用。面向对象是现代 PHP 框架的基础。
PHP 零基础入门,每周更新。













暂无评论内容