Appearance
10.5 实操:用户登录系统
本实操将创建一个完整的用户登录系统,包含注册、登录、退出功能,以及会话管理和权限控制。
功能需求
- 用户注册:用户名、邮箱、密码
- 用户登录:用户名/邮箱、密码
- 退出登录
- 会话管理:登录状态保持、会话过期
- 权限控制:受保护页面只能登录用户访问
实现代码
1. 数据库准备
sql
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);2. 配置文件
php
<?php
// config.php
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'test');
// 会话配置
define('SESSION_EXPIRY', 30 * 60); // 30分钟
// 网站配置
define('SITE_URL', 'http://localhost');
?>3. 数据库连接
php
<?php
// db.php
require_once 'config.php';
function getDbConnection() {
$conn = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if (!$conn) {
die('数据库连接失败: ' . mysqli_connect_error());
}
mysqli_set_charset($conn, 'utf8');
return $conn;
}
?>4. 认证函数
php
<?php
// auth.php
require_once 'config.php';
/**
* 启动会话
*/
function startSession() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
/**
* 检查用户是否已登录
* @return bool
*/
function isLoggedIn() {
startSession();
return isset($_SESSION['is_logged_in']) && $_SESSION['is_logged_in'];
}
/**
* 获取当前登录用户信息
* @return array|null
*/
function getCurrentUser() {
if (!isLoggedIn()) {
return null;
}
return [
'id' => $_SESSION['user_id'] ?? null,
'username' => $_SESSION['username'] ?? null,
'email' => $_SESSION['email'] ?? null,
'login_time' => $_SESSION['login_time'] ?? null
];
}
/**
* 要求用户必须登录
*/
function requireLogin() {
if (!isLoggedIn()) {
// 存储当前页面,登录后重定向回来
startSession();
$_SESSION['redirect_url'] = $_SERVER['REQUEST_URI'];
header('Location: login.php');
exit;
}
}
/**
* 检查会话是否过期
* @return bool
*/
function isSessionExpired() {
startSession();
if (!isset($_SESSION['last_activity'])) {
return true;
}
return time() - $_SESSION['last_activity'] > SESSION_EXPIRY;
}
/**
* 检查并处理会话
*/
function checkSession() {
startSession();
if (isLoggedIn() && isSessionExpired()) {
// 会话过期,销毁会话
logout();
// 存储当前页面,登录后重定向回来
$_SESSION['redirect_url'] = $_SERVER['REQUEST_URI'];
header('Location: login.php');
exit;
} else if (isLoggedIn()) {
// 更新最后活动时间
$_SESSION['last_activity'] = time();
}
}
/**
* 登录用户
* @param array $user 用户信息
*/
function login($user) {
startSession();
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['is_logged_in'] = true;
$_SESSION['login_time'] = time();
$_SESSION['last_activity'] = time();
}
/**
* 退出登录
*/
function logout() {
startSession();
session_unset();
session_destroy();
}
?>5. 注册页面
php
<?php
// register.php
require_once 'auth.php';
require_once 'db.php';
// 检查是否已登录
if (isLoggedIn()) {
header('Location: dashboard.php');
exit;
}
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 表单验证
if (empty($_POST['username'])) {
$errors[] = '用户名不能为空';
}
if (empty($_POST['email'])) {
$errors[] = '邮箱不能为空';
} else if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = '邮箱格式不正确';
}
if (empty($_POST['password'])) {
$errors[] = '密码不能为空';
} else if (strlen($_POST['password']) < 6) {
$errors[] = '密码长度不能少于6位';
}
if (empty($errors)) {
$username = htmlspecialchars($_POST['username']);
$email = htmlspecialchars($_POST['email']);
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 检查用户名和邮箱是否已存在
$conn = getDbConnection();
$stmt = $conn->prepare('SELECT id FROM users WHERE username = ? OR email = ?');
$stmt->bind_param('ss', $username, $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows > 0) {
$errors[] = '用户名或邮箱已存在';
} else {
// 插入新用户
$stmt = $conn->prepare('INSERT INTO users (username, email, password) VALUES (?, ?, ?)');
$stmt->bind_param('sss', $username, $email, $password);
if ($stmt->execute()) {
$success = true;
} else {
$errors[] = '注册失败,请重试';
}
}
$stmt->close();
$conn->close();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
.form-container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; }
.error { color: red; margin: 10px 0; }
.success { color: green; margin: 10px 0; }
input { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
input[type="submit"] { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
.login-link { margin-top: 15px; text-align: center; }
</style>
</head>
<body>
<div class="form-container">
<h1>用户注册</h1>
<?php if (!empty($errors)): ?>
<div class="error">
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="success">
注册成功!<a href="login.php">立即登录</a>
</div>
<?php else: ?>
<form action="register.php" method="post">
<label>用户名: <input type="text" name="username" value="<?php echo isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; ?>"></label><br>
<label>邮箱: <input type="email" name="email" value="<?php echo isset($_POST['email']) ? htmlspecialchars($_POST['email']) : ''; ?>"></label><br>
<label>密码: <input type="password" name="password"></label><br>
<input type="submit" value="注册">
</form>
<div class="login-link">
已有账号?<a href="login.php">登录</a>
</div>
<?php endif; ?>
</div>
</body>
</html>6. 登录页面
php
<?php
// login.php
require_once 'auth.php';
require_once 'db.php';
// 检查是否已登录
if (isLoggedIn()) {
header('Location: dashboard.php');
exit;
}
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 表单验证
if (empty($_POST['login'])) {
$errors[] = '用户名/邮箱不能为空';
}
if (empty($_POST['password'])) {
$errors[] = '密码不能为空';
}
if (empty($errors)) {
$login = htmlspecialchars($_POST['login']);
$password = $_POST['password'];
// 查找用户
$conn = getDbConnection();
$stmt = $conn->prepare('SELECT id, username, email, password FROM users WHERE username = ? OR email = ?');
$stmt->bind_param('ss', $login, $login);
$stmt->execute();
$result = $stmt->get_result();
if ($user = $result->fetch_assoc()) {
// 验证密码
if (password_verify($password, $user['password'])) {
// 登录成功
login($user);
// 重定向到之前的页面或仪表板
$redirectUrl = $_SESSION['redirect_url'] ?? 'dashboard.php';
unset($_SESSION['redirect_url']);
header('Location: ' . $redirectUrl);
exit;
} else {
$errors[] = '密码错误';
}
} else {
$errors[] = '用户不存在';
}
$stmt->close();
$conn->close();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>用户登录</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; }
.form-container { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; }
.error { color: red; margin: 10px 0; }
input { width: 100%; padding: 10px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
input[type="submit"] { background-color: #4CAF50; color: white; border: none; cursor: pointer; }
.register-link { margin-top: 15px; text-align: center; }
</style>
</head>
<body>
<div class="form-container">
<h1>用户登录</h1>
<?php if (!empty($errors)): ?>
<div class="error">
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form action="login.php" method="post">
<label>用户名/邮箱: <input type="text" name="login" value="<?php echo isset($_POST['login']) ? htmlspecialchars($_POST['login']) : ''; ?>"></label><br>
<label>密码: <input type="password" name="password"></label><br>
<input type="submit" value="登录">
</form>
<div class="register-link">
还没有账号?<a href="register.php">注册</a>
</div>
</div>
</body>
</html>7. 仪表板页面
php
<?php
// dashboard.php
require_once 'auth.php';
// 检查会话
checkSession();
// 要求登录
requireLogin();
// 获取当前用户
$user = getCurrentUser();
?>
<!DOCTYPE html>
<html>
<head>
<title>仪表板</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5; }
.header { display: flex; justify-content: space-between; align-items: center; background-color: white; padding: 10px 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
.welcome { font-size: 18px; }
.logout { background-color: #f44336; color: white; padding: 10px; text-decoration: none; border-radius: 4px; }
.content { background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div class="header">
<div class="welcome">
欢迎,<?php echo $user['username']; ?>!<br>
登录时间: <?php echo date('Y-m-d H:i:s', $user['login_time']); ?>
</div>
<a href="logout.php" class="logout">退出登录</a>
</div>
<div class="content">
<h2>仪表板</h2>
<p>这是登录后的页面,只有登录用户才能访问。</p>
<p>用户信息:</p>
<ul>
<li>ID: <?php echo $user['id']; ?></li>
<li>用户名: <?php echo $user['username']; ?></li>
<li>邮箱: <?php echo $user['email']; ?></li>
</ul>
</div>
</body>
</html>8. 退出登录页面
php
<?php
// logout.php
require_once 'auth.php';
// 退出登录
logout();
// 重定向到登录页面
header('Location: login.php');
exit;
?>代码解析
- 配置文件:定义数据库连接信息和会话配置
- 数据库连接:提供数据库连接功能
- 认证函数:封装登录、退出、会话检查等功能
- 注册页面:处理用户注册,包括表单验证和数据库操作
- 登录页面:处理用户登录,包括身份验证和会话设置
- 仪表板页面:显示用户信息,只有登录用户才能访问
- 退出登录页面:销毁会话,重定向到登录页面
注意事项
安全性:
- 使用
password_hash()存储密码 - 验证用户输入,防止 SQL 注入和 XSS 攻击
- 定期检查会话过期
- 使用
用户体验:
- 提供清晰的错误提示
- 登录后重定向到之前的页面
- 会话过期后自动跳转到登录页面
性能:
- 使用预处理语句防止 SQL 注入
- 避免在会话中存储大量数据
练习
- 实现密码重置功能
- 添加用户资料编辑功能
- 实现基于角色的权限控制
- 添加登录尝试限制,防止暴力破解
