摘要 :前几篇我们掌握了 PHP 的语法、流程控制、函数和数组,但一直是在"自言自语"。真正的 Web 应用需要与用户互动:用户填写注册信息、提交评论、上传头像,服务器接收并处理这些数据,再返回个性化页面。这一切的核心就是 表单 和 请求数据。本篇将带你系统地学习 HTML 表单与 PHP 的交互方式,深入理解 GET 与 POST 的区别,手把手教你如何安全地接收、验证和过滤用户输入,抵御 XSS 和 CSRF 攻击。接着,你会学到文件上传的完整流程------从前端表单到后端校验,再到安全存储。最后,我们揭开 Cookie 和 Session 的面纱,实现用户登录状态的保持。学完本篇,你将能够独立开发带有用户交互功能的动态网站,安全意识和技能都会大幅提升。
一、引言:从"我写你看"到"你来我往"
回顾之前的例子,我们的 PHP 脚本只是被动地输出内容,像一块固定的电子公告板。但真实的网站------比如微博、淘宝、知乎------核心是 交互:你输入内容,点击按钮,服务器处理并反馈结果。
这个交互的起点就是 HTML 表单。当你在搜索框里输入关键词点击"搜索",或者在登录框填写用户名密码点击"登录",背后都有一套数据传递的机制。而 PHP 作为服务器端的语言,正是负责接收、处理这些数据的主角。
二、HTTP 请求方法:GET 与 POST 的本质区别
2.1 HTTP 协议简览
浏览器与服务器通信使用 HTTP 协议。一个 HTTP 请求包含:请求方法 (如 GET、POST)、URL 、头信息 (Headers),以及可选的 请求体(Body)。服务器返回状态码、头和响应体(HTML 等)。
表单提交数据的核心方法是 GET 和 POST。
2.2 GET 方法:数据附在 URL 上
GET 请求将表单数据经过 URL 编码后附加在 URL 后面,以 ? 开头,多个参数用 & 分隔。
示例 URL:
http://localhost/search.php?keyword=php&page=2
特点:
-
数据可见于 URL,可被收藏、分享、缓存。
-
长度受限(通常 2048 字符以内)。
-
用于获取数据、查询、导航等不改变服务器状态的操作。
-
不要用于提交敏感信息(密码、身份证等)。
2.3 POST 方法:数据放在请求体中
POST 请求将数据放在请求体中发送,URL 上不可见。
特点:
-
数据不可见于 URL(但未加密,抓包仍可见,HTTPS 才是加密关键)。
-
长度无限制(服务器可配置限制)。
-
用于创建、修改数据,提交表单(注册、登录、订单等)。
-
不会像 GET 那样被缓存或保存到历史记录。
2.4 在 PHP 中接收 GET 和 POST 数据
php
<?php
// 对于 GET 请求,数据存放在 $_GET 超全局数组
// 对于 POST 请求,数据存放在 $_POST 超全局数组
$keyword = $_GET['keyword'] ?? ''; // 获取 GET 参数 keyword
$username = $_POST['username'] ?? ''; // 获取 POST 参数 username
?>
$_REQUEST 包含了 $_GET、$_POST、$_COOKIE 的数据,不推荐使用,因为来源不明易导致安全问题。
三、表单数据处理实战
3.1 创建第一个表单(GET 方式)
search.html:
html
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>搜索</title></head>
<body>
<form action="search.php" method="get">
<input type="text" name="keyword" placeholder="请输入关键词">
<button type="submit">搜索</button>
</form>
</body>
</html>
search.php:
php
<?php
// 获取GET传参,无值则为空字符串
$keyword = $_GET['keyword'] ?? '';
// 去除首尾空白
$keyword = trim($keyword);
if ($keyword !== '') {
// htmlspecialchars 防止XSS输出转义
echo "你搜索了:" . htmlspecialchars($keyword, ENT_QUOTES);
} else {
echo "请输入关键词";
}
?>
访问 search.html,输入内容提交,URL 会变成类似 search.php?keyword=hello,页面显示搜索内容。


3.2 POST 方式表单(更常用)
login.php(表单和处理合在一起,推荐方式):
php
<?php
$error = '';
$username = '';
// 判断是否POST提交表单
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 获取并清理输入
$username = trim($_POST['username'] ?? '');
$password = trim($_POST['password'] ?? '');
// 简单非空校验
if (empty($username)) {
$error = '用户名不能为空';
} elseif (empty($password)) {
$error = '密码不能为空';
} elseif ($username === 'admin' && $password === '123456') {
// 登录成功
echo '<div style="color:#009944;font-size:18px;">登录成功!欢迎回来,' . htmlspecialchars($username, ENT_QUOTES) . '</div>';
exit;
} else {
$error = '用户名或密码错误';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
body {
font-family: "Microsoft YaHei", sans-serif;
background: #f5f7fa;
display: flex;
justify-content: center;
padding-top: 80px;
margin: 0;
}
.login-box {
background: #fff;
padding: 30px 40px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
width: 320px;
}
h2 {
text-align: center;
color: #333;
margin-top: 0;
}
.err-tip {
color: #e53e3e;
text-align: center;
margin: 8px 0 16px;
}
label {
display: block;
margin: 12px 0 4px;
color: #444;
}
input {
width: 100%;
box-sizing: border-box;
padding: 9px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 15px;
}
button {
width: 100%;
margin-top: 20px;
padding: 10px;
background: #3490dc;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
button:hover {
background: #2779bd;
}
</style>
</head>
<body>
<div class="login-box">
<h2>账号登录</h2>
<?php if (!empty($error)): ?>
<p class="err-tip"><?= htmlspecialchars($error, ENT_QUOTES) ?></p>
<?php endif; ?>
<form method="post">
<label>用户名</label>
<input type="text" name="username" value="<?= htmlspecialchars($username, ENT_QUOTES) ?>" placeholder="请输入用户名">
<label>密码</label>
<input type="password" name="password" placeholder="请输入密码">
<button type="submit">立即登录</button>
</form>
</div>
</body>
</html>
说明:
-
$_SERVER['REQUEST_METHOD']判断请求方法,区分"首次展示页面"和"提交后处理"。 -
表单 action 留空则提交到当前页面,处理逻辑写在顶部。
-
使用
htmlspecialchars输出防止 XSS。


3.3 多值字段的处理(复选框、下拉多选)
复选框和多个选择框的名字以 [] 结尾,PHP 会将其组织为数组。
php
<!-- hobby.html -->
<form action="hobby.php" method="post">
<label><input type="checkbox" name="hobby[]" value="reading"> 阅读</label>
<label><input type="checkbox" name="hobby[]" value="sports"> 运动</label>
<label><input type="checkbox" name="hobby[]" value="music"> 音乐</label>
<button type="submit">提交</button>
</form>
// hobby.php
$hobbies = $_POST['hobby'] ?? [];
if ($hobbies) {
echo "你的爱好:" . implode(", ", array_map('htmlspecialchars', $hobbies));
} else {
echo "你没有选择任何爱好。";
}
同样适用于 <select multiple>。
3.4 隐藏域与按钮值
php
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="123">
<button type="submit" name="submit" value="save">保存</button>
隐藏域用于传递页面状态而不让用户看到。按钮可以有 name 和 value,点击后也会出现在 $_POST 中,用于判断哪个按钮被点击。
四、数据验证与过滤:永远不要信任用户输入
4.1 为什么要验证?
黑客可以通过构造恶意输入攻击你的网站(SQL 注入、XSS、文件包含等)。前端验证仅仅是用户体验,安全验证必须在后端。
4.2 常用验证方法
php
<?php
// 1. 必填项检查
if (empty($_POST['username'])) {
die("用户名不能为空");
}
// 2. 长度限制
if (strlen($username) < 3 || strlen($username) > 20) {
die("用户名长度应在 3-20 字符之间");
}
// 3. 格式验证(正则)
$email = $_POST['email'];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die("邮箱格式不正确");
}
// 4. 数字范围
$age = (int)$_POST['age'];
if ($age < 1 || $age > 150) {
die("年龄不合法");
}
// 5. 白名单验证(枚举值)
$allowed_roles = ['admin', 'user', 'guest'];
if (!in_array($_POST['role'], $allowed_roles)) {
die("角色非法");
}
?>
4.3 使用 filter_var 过滤与验证
PHP 内置的 filter_* 函数非常强大,既能验证也能清理。
常用过滤器:
-
FILTER_VALIDATE_EMAIL:验证邮箱 -
FILTER_VALIDATE_URL:验证 URL -
FILTER_VALIDATE_INT:验证整数(可带范围选项) -
FILTER_VALIDATE_FLOAT:浮点数 -
FILTER_VALIDATE_BOOLEAN:布尔 -
FILTER_SANITIZE_EMAIL:清理邮箱(移除非法字符) -
FILTER_SANITIZE_STRING(已弃用,但可用htmlspecialchars或strip_tags) -
FILTER_SANITIZE_NUMBER_INT:只保留数字和加减号
php
<?php
$email = "bad@email<script>";
$clean = filter_var($email, FILTER_SANITIZE_EMAIL);
echo $clean; // bad@email (移除了非法字符)
?>
4.4 综合验证示例
php
<?php
/**
* 注册表单数据校验函数
* @param array $data 表单提交数据数组
* @return array 错误信息数组,键为字段名,值为错误提示
*/
function validateRegistration(array $data): array
{
$errors = [];
// 统一提取并清洗字段,不存在则设为空字符串
$username = trim($data['username'] ?? '');
$password = $data['password'] ?? '';
$password2 = $data['password2'] ?? '';
$email = trim($data['email'] ?? '');
// 用户名校验:非空且长度≥3(兼容中文)
if (empty($username) || mb_strlen($username) < 3) {
$errors['username'] = '用户名至少3个字符';
}
// 密码校验
if (empty($password) || strlen($password) < 6) {
$errors['password'] = '密码至少6位';
} else {
// 仅当密码合法时,校验两次密码是否一致
if ($password !== $password2) {
$errors['password2'] = '两次密码不一致';
}
}
// 邮箱校验
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = '邮箱格式不正确';
}
return $errors;
}
?>
若有错误,则返回给用户并提示。
五、安全防线:XSS 与 CSRF 攻防
5.1 XSS(跨站脚本攻击)
攻击者提交包含恶意 JavaScript 的输入,当该内容被展示给其他用户时,脚本执行,可窃取 Cookie、会话、重定向等。
示例 :用户在评论框输入 <script>alert('XSS')</script>,若不处理直接输出,其他用户打开评论页即弹窗。
防护:
-
输出时使用
htmlspecialchars($str, ENT_QUOTES, 'UTF-8')编码特殊字符。 -
永远不要直接
echo $_GET['...']。 -
设置 Content-Security-Policy 头。
5.2 CSRF(跨站请求伪造)
攻击者诱导用户点击一个链接或加载一个图片,该请求携带用户已登录的 Cookie,冒充用户执行操作(如删除文章、转账)。
防护:
-
关键操作使用 POST 而非 GET。
-
添加 CSRF Token:生成随机令牌存储在 Session,表单中放入隐藏域,提交时比对。
-
使用 SameSite Cookie 属性。
-
检查 Referer/Origin 头(不绝对可靠)。
CSRF Token 简单实现:
php
<?php
session_start();
// 生成 Token
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $token)) {
die('CSRF 验证失败');
}
// 处理表单...
}
?>
<!-- 表单中 -->
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
hash_equals 防止时序攻击,比较字符串用。
六、文件上传:让用户可以上传头像
6.1 文件上传的前提条件
-
表单
enctype必须为multipart/form-data。 -
上传方法用 POST。
-
PHP 配置
php.ini中file_uploads = On,调整upload_max_filesize和post_max_size。
6.2 前端表单
php
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="avatar">
<button type="submit">上传头像</button>
</form>
6.3 后端接收与处理
上传的文件信息保存在 $_FILES['avatar'] 中,是一个数组:
-
name:原始文件名 -
type:MIME 类型(浏览器提供,不可信) -
size:文件大小(字节) -
tmp_name:临时文件路径 -
error:错误代码(0 表示成功)
php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
$file = $_FILES['avatar'];
// 检查错误
if ($file['error'] !== UPLOAD_ERR_OK) {
die("上传失败,错误代码: " . $file['error']);
}
// 验证文件大小(例如最大 2MB)
$maxSize = 2 * 1024 * 1024;
if ($file['size'] > $maxSize) {
die("文件太大,不能超过 2MB");
}
// 验证 MIME 类型(实际检查文件内容更安全,这里用简易方式)
$allowedMime = ['image/jpeg', 'image/png', 'image/gif'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, $allowedMime)) {
die("只允许上传 JPG/PNG/GIF 图片");
}
// 生成唯一文件名,防止冲突和路径穿越
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = md5(uniqid() . time()) . '.' . $ext;
$destination = __DIR__ . '/uploads/' . $newName;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $destination)) {
echo "上传成功!文件路径:uploads/$newName";
} else {
die("移动文件失败,请检查目录权限");
}
}
?>
关键安全要点:
-
永远不信任
$_FILES['file']['type'],用 Fileinfo 库检查真实 MIME。 -
检查文件扩展名,但不要仅依赖扩展名。
-
上传目录不要有执行权限,最好放在文档根目录之外。
-
用随机文件名存储,避免覆盖和路径遍历攻击(如
../../etc/passwd)。 -
检查文件大小,服务器配置限制外再加应用层限制。
6.4 多文件上传
在表单中使用 name="photos[]" 并添加 multiple 属性,$_FILES['photos'] 结构会变化(name、type 等均为数组),需要循环处理。使用 array() 方式组织文件表单数据更易遍历。
php
// 遍历
foreach ($_FILES['photos']['error'] as $key => $error) {
if ($error === UPLOAD_ERR_OK) {
// 使用对应 key 的 tmp_name, name, size 等
}
}
七、Cookie 与 Session:记住用户的状态
HTTP 是无状态协议,每次请求都是独立的。如何让网站"记住"你已经登录?这就需要 Cookie 和 Session。
7.1 Cookie:客户端的小纸条
Cookie 是服务器让浏览器保存在用户电脑上的小文本数据(通常 4KB 以内),下次请求同域名时浏览器自动发送。
设置 Cookie:
php
<?php
setcookie('username', '张三', time() + 3600, '/'); // 有效期 1 小时
// 参数:名称, 值, 过期时间, 路径, 域, 安全, HttpOnly
?>
读取 Cookie:
php
<?php
echo $_COOKIE['username'] ?? '未设置';
?>
删除 Cookie:将过期时间设为过去。
php
<?php
setcookie('username', '', time() - 3600);
?>
安全实践:
-
通过
HttpOnly标志禁止 JavaScript 读取,防止 XSS 窃取。 -
通过
Secure标志仅允许 HTTPS 传输。 -
设置
SameSite属性防御 CSRF(Lax/Strict)。
7.2 Session:服务器端的状态保存
Session 将数据保存在服务器端,仅给浏览器一个唯一的 Session ID(存在 Cookie 中)。用户每次请求带上这个 ID,服务器查找对应的数据,实现状态保持。
启动 Session:
php
<?php
session_start(); // 必须放在输出任何内容之前
?>
存取数据:
php
<?php
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'admin';
echo "欢迎 " . $_SESSION['username'];
?>
销毁 Session(退出登录):
php
<?php
session_start();
$_SESSION = []; // 清空数组
// 清除 Cookie 中的 Session ID
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"],
$params["secure"], $params["httponly"]);
}
session_destroy();
?>
配置 Session(php.ini):
-
session.gc_maxlifetime:垃圾回收最大生命周期(默认 1440 秒)。 -
session.save_path:存放位置。 -
session.cookie_httponly= On -
session.cookie_secure= On(HTTPS 时) -
session.use_strict_mode= 1(防止 Session 固定攻击)
7.3 实战:简易登录与页面保护
login_session.php 登录页:
php
<?php
// 必须放在最顶部,前面不能有任何输出
session_start();
$error = '';
$username = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['user'] ?? '');
$pass = trim($_POST['pass'] ?? '');
// 简单非空校验
if ($username === '' || $pass === '') {
$error = '用户名和密码不能为空';
} elseif ($username === 'admin' && $pass === '123') {
// 登录成功,写入会话
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['login_time'] = time();
// 跳转后台,302重定向
header('Location: dashboard.php', true, 302);
exit;
} else {
$error = '用户名或密码错误';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录验证</title>
<style>
body {
font-family: system-ui;
max-width: 320px;
margin: 60px auto;
}
.err {
color: #dc3545;
background: #ffebee;
padding: 8px;
border-radius: 4px;
}
div {
margin: 12px 0;
}
input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 10px;
background: #0d6efd;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>系统登录</h2>
<?php if ($error !== ''): ?>
<p class="err"><?= htmlspecialchars($error, ENT_QUOTES) ?></p>
<?php endif; ?>
<form method="post">
<div>
<label>用户名</label>
<input type="text" name="user" value="<?= htmlspecialchars($username, ENT_QUOTES) ?>" placeholder="admin">
</div>
<div>
<label>密码</label>
<input type="password" name="pass" placeholder="123">
</div>
<div>
<button type="submit">登录</button>
</div>
</form>
</body>
</html>
dashboard.php 受保护后台页面:
php
<?php
session_start();
// 校验登录状态,未登录强制跳转登录页
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
header('Location: login_session.php', true, 302);
exit;
}
// 安全取出用户名
$user = htmlspecialchars($_SESSION['username'] ?? '', ENT_QUOTES);
$loginTime = date('Y-m-d H:i:s', $_SESSION['login_time']);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>管理后台</title>
<style>
body { font-family: system-ui; padding: 40px; }
.box {
border: 1px solid #eee;
padding: 30px;
border-radius: 8px;
max-width: 400px;
}
a {
display: inline-block;
margin-top: 20px;
padding: 8px 16px;
background: #6c757d;
color: white;
text-decoration: none;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="box">
<h2>欢迎,<?= $user ?>!</h2>
<p>登录时间:<?= $loginTime ?></p>
<p>当前页面受会话保护,未登录无法访问</p>
<a href="logout.php">安全退出登录</a>
</div>
</body>
</html>
logout.php 完整安全登出:
php
<?php
session_start();
// 1. 清空当前会话数据
$_SESSION = [];
// 2. 清除客户端session cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 3600,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
}
// 3. 销毁服务器端会话文件
session_destroy();
// 跳转登录页
header('Location: login_session.php', true, 302);
exit;
?>


八、综合实战:带验证的注册页
让我们将本篇所有知识整合,创建一个较为完整的注册系统:包含表单验证、头像上传、密码哈希(安全性)、Session 登录持久化、CSRF 保护。
register.php:
php
<?php
// 页面最顶部,无任何前置输出
session_start();
// 安全响应头,防止会话劫持、XSS
header("X-Frame-Options: DENY");
header("X-XSS-Protection: 1; mode=block");
// 自动生成CSRF令牌
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$errors = [];
$input = [
'username' => '',
'email' => '',
];
// 上传目录自动创建
$uploadDir = __DIR__ . '/uploads/';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
// 用户存储文件不存在则初始化空数组
$userFile = __DIR__ . '/users.json';
if (!file_exists($userFile)) {
file_put_contents($userFile, json_encode([], JSON_PRETTY_PRINT));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. CSRF 防护校验
$postCsrf = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $postCsrf)) {
$errors['global'] = '非法提交请求,禁止重复提交或跨站攻击';
}
// 2. 获取并清洗所有输入
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$password2 = $_POST['password2'] ?? '';
$email = trim($_POST['email'] ?? '');
// 回填输入框
$input['username'] = $username;
$input['email'] = $email;
// 3. 表单基础校验
if (empty($username)) {
$errors['username'] = '用户名不能为空';
} elseif (mb_strlen($username) < 3) {
$errors['username'] = '用户名至少3个字符';
}
if (empty($password)) {
$errors['password'] = '密码不能为空';
} elseif (strlen($password) < 6) {
$errors['password'] = '密码长度至少6位';
}
if ($password !== $password2) {
$errors['password2'] = '两次输入的密码不一致';
}
if (empty($email)) {
$errors['email'] = '邮箱不能为空';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = '邮箱格式不合法';
}
// 4. 校验用户名/邮箱是否已被注册
$userList = json_decode(file_get_contents($userFile), true) ?? [];
foreach ($userList as $user) {
if ($user['username'] === $username) {
$errors['username'] = '该用户名已被占用';
}
if ($user['email'] === $email) {
$errors['email'] = '该邮箱已注册账号';
}
}
// 5. 头像上传处理
$avatarPath = '';
if (isset($_FILES['avatar']) && $_FILES['avatar']['error'] !== UPLOAD_ERR_NO_FILE) {
$file = $_FILES['avatar'];
// 上传错误判断
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors['avatar'] = '文件上传失败,请重新选择';
} else {
// MIME类型校验
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowMime = ['image/jpeg', 'image/png'];
if (!in_array($mime, $allowMime)) {
$errors['avatar'] = '仅支持 JPG / PNG 图片头像';
} elseif ($file['size'] > 1048576) {
$errors['avatar'] = '头像文件不能超过 1MB';
} else {
// 生成唯一文件名,防止覆盖
$ext = $mime === 'image/png' ? 'png' : 'jpg';
$fileName = md5(uniqid(microtime(true), true) . $username) . '.' . $ext;
$destPath = $uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $destPath)) {
$avatarPath = 'uploads/' . $fileName;
} else {
$errors['avatar'] = '文件写入失败,目录权限不足';
}
}
}
}
// 6. 无错误则写入用户数据并自动登录
if (empty($errors)) {
$newUser = [
'username' => $username,
'password' => password_hash($password, PASSWORD_DEFAULT),
'email' => $email,
'avatar' => $avatarPath,
'reg_time' => time()
];
$userList[] = $newUser;
file_put_contents($userFile, json_encode($userList, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
// 写入会话,自动登录
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['avatar'] = $avatarPath;
// 跳转后台,终止脚本
header('Location: dashboard.php', true, 302);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: "Microsoft YaHei", system-ui;
}
body {
background: #f5f7fa;
padding: 60px 20px;
}
.register-card {
max-width: 420px;
margin: 0 auto;
background: #fff;
padding: 32px;
border-radius: 10px;
box-shadow: 0 2px 14px rgba(0,0,0,0.08);
}
h1 {
text-align: center;
color: #2d3748;
margin-bottom: 24px;
font-size: 22px;
}
.global-err {
background: #fee;
color: #dc2626;
padding: 10px;
border-radius: 6px;
margin-bottom: 16px;
text-align: center;
}
.form-item {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 6px;
color: #4a5568;
}
input[type="text"],
input[type="password"],
input[type="email"],
input[type="file"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #cbd5e0;
border-radius: 6px;
font-size: 15px;
}
.err-text {
color: #dc2626;
font-size: 13px;
margin-top: 4px;
display: block;
}
button {
width: 100%;
padding: 11px;
background: #2563eb;
color: #fff;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s;
}
button:hover {
background: #1d4ed8;
}
</style>
</head>
<body>
<div class="register-card">
<h1>新用户注册</h1>
<?php if (!empty($errors['global'])): ?>
<div class="global-err">
<?= htmlspecialchars($errors['global'], ENT_QUOTES) ?>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data">
<!-- CSRF 隐藏令牌 -->
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES) ?>">
<div class="form-item">
<label>用户名</label>
<input type="text" name="username" value="<?= htmlspecialchars($input['username'], ENT_QUOTES) ?>" placeholder="至少3个字符">
<?php if (!empty($errors['username'])): ?>
<span class="err-text"><?= htmlspecialchars($errors['username'], ENT_QUOTES) ?></span>
<?php endif; ?>
</div>
<div class="form-item">
<label>登录密码</label>
<input type="password" name="password" placeholder="最少6位">
<?php if (!empty($errors['password'])): ?>
<span class="err-text"><?= htmlspecialchars($errors['password'], ENT_QUOTES) ?></span>
<?php endif; ?>
</div>
<div class="form-item">
<label>确认密码</label>
<input type="password" name="password2" placeholder="再次输入密码">
<?php if (!empty($errors['password2'])): ?>
<span class="err-text"><?= htmlspecialchars($errors['password2'], ENT_QUOTES) ?></span>
<?php endif; ?>
</div>
<div class="form-item">
<label>邮箱地址</label>
<input type="email" name="email" value="<?= htmlspecialchars($input['email'], ENT_QUOTES) ?>" placeholder="example@shturl.">
<?php if (!empty($errors['email'])): ?>
<span class="err-text"><?= htmlspecialchars($errors['email'], ENT_QUOTES) ?></span>
<?php endif; ?>
</div>
<div class="form-item">
<label>上传头像(选填,1MB内 JPG/PNG)</label>
<input type="file" name="avatar" accept="image/jpeg,image/png">
<?php if (!empty($errors['avatar'])): ?>
<span class="err-text"><?= htmlspecialchars($errors['avatar'], ENT_QUOTES) ?></span>
<?php endif; ?>
</div>
<button type="submit">立即注册</button>
</form>
</div>
</body>
</html>
核心亮点:
-
使用了
password_hash安全存储密码。 -
CSRF Token 验证。
-
文件真实 MIME 检测。
-
htmlspecialchars输出防止 XSS。 -
自动登录并跳转到 dashboard(需自己创建 dashboard.php 展示欢迎信息和头像)。
dashboard.php:
php
<?php
session_start();
// 未登录跳转注册页
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
header('Location: register.php', true, 302);
exit;
}
$userName = htmlspecialchars($_SESSION['username'], ENT_QUOTES);
$avatar = $_SESSION['avatar'] ?? '';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>个人中心</title>
<style>
body { padding: 50px; text-align: center; }
.avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; border: 2px solid #2563eb; }
.no-avatar { width: 120px; height: 120px; background: #eee; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size:14px; color:#666; }
a { display: inline-block; margin-top:20px; padding:8px 16px; background:#666; color:#fff; text-decoration:none; border-radius:4px; }
</style>
</head>
<body>
<h2>欢迎你,<?= $userName ?></h2>
<div style="margin:20px 0;">
<?php if (!empty($avatar) && file_exists($avatar)): ?>
<img class="avatar" src="<?= htmlspecialchars($avatar, ENT_QUOTES) ?>">
<?php else: ?>
<div class="no-avatar">暂无头像</div>
<?php endif; ?>
</div>
<a href="logout.php">退出登录</a>
</body>
</html>
logout.php:
php
<?php
session_start();
// 清空会话
$_SESSION = [];
// 销毁Cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 86400,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
header('Location: register.php', true, 302);
exit;
?>


九、总结
-
GET 与 POST :掌握了它们的区别和适用场景,通过
$_GET、$_POST接收数据。 -
表单处理:学会了创建表单、多值字段、隐藏域,以及如何在同一页面完成展示与处理。
-
数据验证与过滤 :掌握了
filter_var和自定义验证函数,知道后端验证才是安全的根基。 -
安全攻防 :理解了 XSS 的原理与
htmlspecialchars防御,了解了 CSRF 及 Token 防御机制。 -
文件上传:完整实现了从前端到后端的文件上传,包括错误检查、类型校验、唯一命名和安全存储。
-
Cookie 与 Session:理解了 HTTP 无状态以及如何通过它们维持用户状态,实现了登录、退出及页面保护。
-
综合案例:注册系统融合了以上全部知识,让你看到一个真实开发流程的缩影。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。动手练习是掌握编程最快的方法,请务必亲手敲一遍本文的所有示例代码,并截图保存你的成果。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。