从零开始学 PHP 系列(六):MySQL 数据库与 PHP 交互——让数据真正“住”进服务器

摘要 :前面几篇,我们的数据都存储在变量、数组甚至文件中,但真正的网站需要持久化、可查询、高性能的数据存储。数据库就是为此而生。本篇将带你走进关系型数据库 MySQL 的世界,从安装和基本概念开始,学会用 phpMyAdmin 可视化管理数据库,掌握 SQL 的"增删改查"(CRUD)操作。然后,用 PHP 的 PDO 扩展连接数据库,执行查询,并用预处理语句彻底杜绝 SQL 注入攻击。学完本篇,你将能够为博客、商城等应用构建坚实的数据后端,真正打通"前端表单 → PHP 处理 → 数据库存储"的全链路。


一、引言:为什么需要数据库?

回忆我们上一篇文章的注册系统,用户数据被保存在一个 users.json 文件中。这种方式问题很多:查询困难(想找到某个用户必须读取整个文件)、并发冲突(多个用户同时写入可能互相覆盖)、无法高效排序和统计、数据量大了性能会急剧下降。

数据库就像一个专业化的仓库:它把数据按规则分类存放(表),提供标准化的语言进行存取(SQL),并且有锁机制保证多人同时操作不冲突。MySQL 是使用最广泛的开源关系型数据库之一,与 PHP 搭配组成了经典的 LAMP/LEMP 技术栈。


二、MySQL 简介与安装确认

2.1 什么是 MySQL?

MySQL 是一个关系型数据库管理系统(RDBMS),把数据组织成一张张(类似 Excel 工作表),表之间有联系(通过键)。它使用结构化查询语言(SQL)来操作数据。

核心概念

  • 数据库(Database):一个项目或应用的数据集合。

  • 表(Table):数据库里的具体存储单元,由行和列组成。

  • 行(Row):一条记录,比如一个用户、一篇文章。

  • 列(Column):记录的属性,比如用户名、密码、邮箱。

  • 主键(Primary Key):唯一标识一行记录的字段,通常用自增整数。

  • 外键(Foreign Key):关联另一张表的字段。

2.2 确认 MySQL 是否安装

之前我们使用了 XAMPP 集成环境,它已经包含了 MySQL/MariaDB(后者是 MySQL 的分支,完全兼容)。打开 XAMPP 控制面板,点击 MySQL 一行的 Start,看到端口 3306 变为绿色,表示数据库已启动。

也可以打开命令行(Windows 下点击 XAMPP 的 Shell),输入:

复制代码
mysql -u root

直接回车(默认无密码),进入 MySQL 命令行界面,输入 SELECT VERSION(); 查看版本。


三、SQL 入门:数据库的"普通话"

3.1 创建数据库和表

假设我们要做一个博客系统,先建数据库 blog,并创建第一张表 users

sql 复制代码
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用该数据库
USE blog;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

解释

  • id:自增主键,每插入一行自动 +1。

  • username:最长 50 字符,不能为空,值唯一。

  • password:最长 255(为了存储哈希后的密码)。

  • email:最长 100。

  • created_at:时间戳,默认插入时间。

  • ENGINE=InnoDB:支持事务和外键。

  • CHARSET=utf8mb4:支持 emoji 等 4 字节 Unicode。

3.2 插入数据(INSERT)

sql 复制代码
INSERT INTO users (username, password, email) 
VALUES ('zhangsan', 'hashed_password_here', 'zhangsan@example.com');

批量插入:

sql 复制代码
INSERT INTO users (username, password, email) VALUES
('lisi', 'pass2', 'lisi@test.com'),
('wangwu', 'pass3', 'wangwu@test.com');

3.3 查询数据(SELECT)

查所有用户

sql 复制代码
SELECT * FROM users;

查指定列

sql 复制代码
SELECT username, email FROM users;

带条件

sql 复制代码
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE username LIKE '%zhang%';  -- 模糊搜索

排序与限制

sql 复制代码
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;  -- 最新10条

3.4 更新数据(UPDATE)

sql 复制代码
UPDATE users SET email = 'newemail@test.com' WHERE id = 2;

务必加 WHERE! 否则整表数据都会修改。

3.5 删除数据(DELETE)

sql 复制代码
DELETE FROM users WHERE id = 3;

同样,忘记 WHERE 会清空整张表,要极其小心。

3.6 更多实用 SQL

  • COUNT():计数,如 SELECT COUNT(*) FROM users;

  • SUM()AVG()MAX()MIN():聚合函数。

  • GROUP BY:分组统计,如 SELECT city, COUNT(*) FROM users GROUP BY city;

  • JOIN:连接多表查询(下一篇会深入)。

  • ALTER TABLE:修改表结构,如添加列。


四、使用 phpMyAdmin 可视化管理

虽然命令行很强大,但对初学者来说,phpMyAdmin 更加直观。XAMPP 已内置它。

打开浏览器,访问 http://localhost/phpmyadmin

左侧点击 新建 ,输入数据库名 blog,选择 utf8mb4_unicode_ci,点击创建。

在新建的 blog 数据库中,创建表,输入名称 users,字段数 5,点击执行。

填写各字段的名称、类型、长度/值、索引等,参考上面的 SQL 定义。

点击保存,表创建成功。

点击顶部的 SQL 标签,可以直接输入 SQL 语句执行;也可以使用 插入浏览 等功能。

虽然图形化很方便,但一定要结合 SQL 学习,因为代码中操作数据库仍然需要写 SQL。


五、PHP 连接 MySQL 的方式:从 mysql_ 到 PDO

5.1 历史回顾与现状

  • mysql 扩展 :PHP 5.5.0 起已废弃,PHP 7.0 移除。绝不使用

  • mysqli 扩展:改进版,支持面向对象和过程化接口,只支持 MySQL。可以用,但推荐 PDO。

  • PDO(PHP Data Objects) :数据库抽象层,支持 12 种数据库(MySQL, SQLite, PostgreSQL 等),提供统一的接口。支持预处理语句,安全性高。本文只讲 PDO

5.2 PDO 连接数据库

php 复制代码
<?php
$host = '127.0.0.1';
$dbname = 'blog';
$username = 'root';
$password = '';
$charset = 'utf8mb4';
​
try {
    $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,      // 抛出异常便于调试
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 默认返回关联数组
        PDO::ATTR_EMULATE_PREPARES => false,              // 使用真正的预处理
    ]);
    echo "数据库连接成功!";
} catch (PDOException $e) {
    die("数据库连接失败:" . $e->getMessage());
}
?>

关键配置解释

  • PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION:出错时抛异常,比手动检查 errorInfo 方便。

  • PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC:fetch 时默认返回以列名为键的数组。

  • PDO::ATTR_EMULATE_PREPARES => false:禁用模拟预处理,让数据库原生支持预处理,更安全。

5.3 测试连接并执行简单查询

php 复制代码
<?php
// 数据库连接配置
$host     = '127.0.0.1';
$dbname   = 'blog';
$dbUser   = 'root';
$dbPass   = '';
$charset  = 'utf8mb4';

$pdo = null;
try {
    // 构造DSN连接字符串
    $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

    // PDO连接配置项
    $options = [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,      // 异常模式报错
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,            // 默认返回关联数组
        PDO::ATTR_EMULATE_PREPARES   => false,                       // 关闭模拟预处理,防SQL注入
    ];

    // 实例化PDO对象建立连接
    $pdo = new PDO($dsn, $dbUser, $dbPass, $options);
    echo "<p style='color:green'>数据库连接成功!</p>";
} catch (PDOException $e) {
    // 捕获连接异常,终止后续数据库操作
    die("<p style='color:red'>数据库连接失败:" . htmlspecialchars($e->getMessage()) . "</p>");
}

// 查询用户表数据
try {
    $stmt = $pdo->query("SELECT * FROM users");
    $users = $stmt->fetchAll();

    if (empty($users)) {
        echo "<p>暂无用户数据</p>";
    } else {
        foreach ($users as $user) {
            $username = htmlspecialchars($user['username'] ?? '');
            $email = htmlspecialchars($user['email'] ?? '');
            echo "用户名:{$username},邮箱:{$email}<br>";
        }
    }
} catch (PDOException $e) {
    echo "<p style='color:red'>查询失败:" . htmlspecialchars($e->getMessage()) . "</p>";
}
?>

query() 适合无变量的查询。但涉及用户输入,必须使用预处理语句


六、预处理语句:安全与高效的基石

6.1 为什么需要预处理?

如果你直接拼接 SQL:

php 复制代码
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";

黑客可以输入 ' OR '1'='1,导致 SQL 变成 SELECT * FROM users WHERE username = '' OR '1'='1',返回所有用户。这就是 SQL 注入

预处理语句将 SQL 结构和数据分开发送,数据不会破坏 SQL 语法,从根本上杜绝注入。

6.2 基本预处理步骤

php 复制代码
<?php
// 准备 SQL 模板,用 ? 或命名占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
​
// 绑定参数
$username = $_POST['username'];
$stmt->bindParam(':username', $username);
​
// 执行
$stmt->execute();
​
// 获取结果
$user = $stmt->fetch();  // 获取一条记录
?>

6.3 使用问号占位符

php 复制代码
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);  // 按顺序绑定
$user = $stmt->fetch();

6.4 插入数据(INSERT)预处理

php 复制代码
<?php
$username = $_POST['username'];
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$email = $_POST['email'];
​
$sql = "INSERT INTO users (username, password, email) VALUES (:username, :password, :email)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    ':username' => $username,
    ':password' => $password,
    ':email' => $email,
]);
​
echo "新用户 ID:" . $pdo->lastInsertId();
?>

execute 接受一个关联数组,键对应命名占位符。lastInsertId() 获取自增主键值。

6.5 更新和删除

php 复制代码
// 更新
$sql = "UPDATE users SET email = :email WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':email' => $newEmail, ':id' => $userId]);
​
// 删除
$sql = "DELETE FROM users WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':id' => $userId]);

返回受影响的行数:$stmt->rowCount()


七、综合实战一:将注册登录系统升级为数据库版

blog 数据库中执行:

sql 复制代码
CREATE DATABASE IF NOT EXISTS blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE blog;

CREATE TABLE IF NOT EXISTS users (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键ID',
    username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
    password VARCHAR(255) NOT NULL COMMENT '加密密码',
    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
    avatar VARCHAR(255) DEFAULT NULL COMMENT '头像文件相对路径',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

7.1 数据库配置与公共连接文件

新建 db.php

php 复制代码
<?php
/**
 * 数据库公共连接文件
 * 单例模式获取PDO连接,全局复用,避免重复创建连接
 * @return PDO
 */
function getPdo(): PDO
{
    // 静态变量仅首次调用实例化
    static $pdo = null;

    if ($pdo === null) {
        // 数据库配置项
        $host    = '127.0.0.1';
        $dbname  = 'blog';
        $dbUser  = 'root';
        $dbPass  = '';
        $charset = 'utf8mb4';

        // DSN连接字符串
        $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

        // PDO安全配置
        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ];

        $pdo = new PDO($dsn, $dbUser, $dbPass, $options);
    }

    return $pdo;
}

后续所有页面引入 require 'db.php';,调用 getPdo() 获取连接。

7.2 注册页面(register_db.php)

php 复制代码
<?php
require 'db.php';
session_start();
// 安全响应头
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

// 生成CSRF令牌,防止跨站提交
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

$errors = [];
$inputFill = [
    'username' => '',
    'email'    => ''
];

// 上传目录自动创建
$uploadDir = __DIR__ . '/uploads/avatar/';
if (!file_exists($uploadDir)) {
    mkdir($uploadDir, 0755, true);
}
$avatarPath = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // CSRF校验
    $postToken = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'], $postToken)) {
        $errors['global'] = '非法请求,禁止跨站重复提交';
    }

    // 获取并清洗输入
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $password2 = $_POST['password2'] ?? '';
    $email = trim($_POST['email'] ?? '');

    // 回填输入框
    $inputFill['username'] = $username;
    $inputFill['email'] = $email;

    // 基础表单校验
    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'] = '邮箱格式不合法';
    }

    // 头像上传处理
    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;
                $destFile = $uploadDir . $fileName;
                if (move_uploaded_file($file['tmp_name'], $destFile)) {
                    $avatarPath = 'uploads/avatar/' . $fileName;
                } else {
                    $errors['avatar'] = '头像保存失败,检查目录权限';
                }
            }
        }
    }

    // 无前端错误再操作数据库
    if (empty($errors)) {
        try {
            $pdo = getPdo();
            // 查询用户名是否已注册
            $checkSql = "SELECT id FROM users WHERE username = :username";
            $checkStmt = $pdo->prepare($checkSql);
            $checkStmt->execute([':username' => $username]);

            if ($checkStmt->fetch()) {
                $errors['username'] = '该用户名已被占用,请更换';
            } else {
                // 密码哈希加密存储
                $hashPwd = password_hash($password, PASSWORD_DEFAULT);
                // 插入用户数据(新增avatar字段)
                $insertSql = "INSERT INTO users (username, password, email, avatar) VALUES (:u, :p, :e, :avatar)";
                $insertStmt = $pdo->prepare($insertSql);
                $insertStmt->execute([
                    ':u' => $username,
                    ':p' => $hashPwd,
                    ':e' => $email,
                    ':avatar' => $avatarPath
                ]);

                // 获取新增用户ID,写入会话自动登录
                $uid = $pdo->lastInsertId();
                $_SESSION['logged_in'] = true;
                $_SESSION['user_id'] = $uid;
                $_SESSION['username'] = $username;
                $_SESSION['avatar'] = $avatarPath;

                // 跳转欢迎页
                header('Location: welcome_db.php', true, 302);
                exit;
            }
        } catch (PDOException $e) {
            // 生产环境隐藏原始数据库报错
            $errors['global'] = '注册失败,服务器繁忙,请稍后重试';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户注册</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }
        body {
            background-color: #f4f6f9;
            padding: 60px 20px;
        }
        .card {
            width: 420px;
            margin: 0 auto;
            background: #fff;
            padding: 32px;
            border-radius: 10px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.07);
        }
        h2 {
            text-align: center;
            color: #2d3748;
            margin-bottom: 24px;
        }
        .global-error {
            background: #fee;
            color: #dc2626;
            padding: 10px;
            border-radius: 6px;
            margin-bottom: 16px;
            text-align: center;
        }
        .item {
            margin-bottom: 16px;
        }
        label {
            display: block;
            margin-bottom: 6px;
            color: #4a5568;
        }
        input {
            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: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
        }
        button:hover {
            background: #1d4ed8;
        }
        .link {
            text-align: center;
            margin-top: 16px;
            font-size: 14px;
        }
        .link a {
            color: #2563eb;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="card">
        <h2>新用户注册</h2>
        <?php if (!empty($errors['global'])): ?>
            <div class="global-error">
                <?= htmlspecialchars($errors['global'], ENT_QUOTES) ?>
            </div>
        <?php endif; ?>

        <!-- 上传文件必须加 enctype="multipart/form-data" -->
        <form method="post" enctype="multipart/form-data">
            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES) ?>">

            <div class="item">
                <label>用户名</label>
                <input type="text" name="username" value="<?= htmlspecialchars($inputFill['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="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="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="item">
                <label>邮箱地址</label>
                <input type="email" name="email" value="<?= htmlspecialchars($inputFill['email'], ENT_QUOTES) ?>" placeholder="example@xxx.com">
                <?php if (!empty($errors['email'])): ?>
                    <span class="err-text"><?= htmlspecialchars($errors['email'], ENT_QUOTES) ?></span>
                <?php endif; ?>
            </div>

            <div class="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 class="link">已有账号?<a href="login_db.php">前往登录</a></div>
    </div>
</body>
</html>

7.3 登录页面(login_db.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

$error = '';
$fillUser = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';
    $fillUser = $username;

    // 非空校验
    if (empty($username) || empty($password)) {
        $error = '用户名和密码均不能为空';
    } else {
        try {
            $pdo = getPdo();
            // 预处理查询用户
            $sql = "SELECT id, username, password, avatar FROM users WHERE username = :u";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([':u' => $username]);
            $user = $stmt->fetch();

            // 校验账号与密码哈希
            if ($user && password_verify($password, $user['password'])) {
                $_SESSION['logged_in'] = true;
                $_SESSION['user_id'] = $user['id'];
                $_SESSION['username'] = $user['username'];
                $_SESSION['avatar'] = $user['avatar']; 
                header('Location: welcome_db.php', true, 302);
                exit;
            } else {
                $error = '用户名或密码不正确';
            }
        } catch (PDOException $e) {
            $error = '登录异常,请稍后重试';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>账号登录</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }
        body {
            background-color: #f4f6f9;
            padding: 60px 20px;
        }
        .login-card {
            width: 380px;
            margin: 0 auto;
            background: #fff;
            padding: 32px;
            border-radius: 10px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.07);
        }
        h2 {
            text-align: center;
            color: #2d3748;
            margin-bottom: 24px;
        }
        .tip-error {
            background: #fee;
            color: #dc2626;
            padding: 10px;
            border-radius: 6px;
            margin-bottom: 16px;
            text-align: center;
        }
        .form-row {
            margin-bottom: 16px;
        }
        label {
            display: block;
            margin-bottom: 6px;
            color: #4a5568;
        }
        input {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid #cbd5e0;
            border-radius: 6px;
            font-size: 15px;
        }
        button {
            width: 100%;
            padding: 11px;
            background: #2563eb;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            cursor: pointer;
        }
        button:hover {
            background: #1d4ed8;
        }
        .reg-link {
            text-align: center;
            margin-top: 16px;
            font-size: 14px;
        }
        .reg-link a {
            color: #2563eb;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="login-card">
        <h2>账号登录</h2>
        <?php if (!empty($error)): ?>
            <div class="tip-error">
                <?= htmlspecialchars($error, ENT_QUOTES) ?>
            </div>
        <?php endif; ?>

        <form method="post">
            <div class="form-row">
                <label>用户名</label>
                <input type="text" name="username" value="<?= htmlspecialchars($fillUser, ENT_QUOTES) ?>" placeholder="请输入注册用户名">
            </div>
            <div class="form-row">
                <label>密码</label>
                <input type="password" name="password" placeholder="输入登录密码">
            </div>
            <button type="submit">登录</button>
        </form>
        <div class="reg-link">没有账号?<a href="register_db.php">立即注册</a></div>
    </div>
</body>
</html>

7.4 欢迎页面与退出

welcome_db.php

php 复制代码
<?php
session_start();
// 未登录拦截跳转
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header('Location: login_db.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>
        * {
            margin: 0;
            padding: 0;
            font-family: "Microsoft YaHei";
        }
        body {
            background: #f4f6f9;
            padding-top: 100px;
            text-align: center;
        }
        .box {
            width: 420px;
            margin: 0 auto;
            background: #fff;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.07);
        }
        h2 {
            color: #2d3748;
            margin-bottom: 20px;
        }
        .avatar-wrap {
            margin: 24px 0;
        }
        .avatar-img {
            width: 140px;
            height: 140px;
            border-radius: 50%;
            object-fit: cover;
            border: 3px solid #2563eb;
        }
        .avatar-empty {
            width: 140px;
            height: 140px;
            border-radius: 50%;
            background: #e2e8f0;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            color: #666;
            font-size:14px;
        }
        .logout-btn {
            display: inline-block;
            margin-top: 24px;
            padding: 10px 24px;
            background: #6c757d;
            color: #fff;
            text-decoration: none;
            border-radius: 6px;
        }
        .logout-btn:hover {
            background: #5a6268;
        }
    </style>
</head>
<body>
    <div class="box">
        <h2>欢迎你,<?= $userName ?>!</h2>
        <p>当前页面已登录保护,未登录用户无法访问</p>

        <div class="avatar-wrap">
            <?php if (!empty($avatar) && file_exists($avatar)): ?>
                <img class="avatar-img" src="<?= htmlspecialchars($avatar, ENT_QUOTES) ?>" alt="用户头像">
            <?php else: ?>
                <div class="avatar-empty">暂无头像</div>
            <?php endif; ?>
        </div>

        <a href="logout_db.php" class="logout-btn">安全退出登录</a>
    </div>
</body>
</html>

logout_db.php

php 复制代码
<?php
session_start();

// 清空会话数据
$_SESSION = [];

// 销毁浏览器Session Cookie
if (ini_get("session.use_cookies")) {
    $cookieParam = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 86400,
        $cookieParam["path"],
        $cookieParam["domain"],
        $cookieParam["secure"],
        $cookieParam["httponly"]
    );
}

// 销毁服务器端会话文件
session_destroy();

// 跳转登录页
header('Location: login_db.php', true, 302);
exit;
?>

八、综合实战二:文章发布与列表(CRUD 完整示例)

继续完善博客系统,实现文章的增、查、改、删

8.1 创建文章表

blog 数据库中执行:

sql 复制代码
USE blog;
CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id INT UNSIGNED NOT NULL,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

外键约束确保文章必须有对应的用户,用户删除时其文章也删除。

8.2 发布文章(create_post.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

// 未登录拦截
if (empty($_SESSION['logged_in'])) {
    header('Location: login_db.php', true, 302);
    exit;
}

// CSRF令牌
if (empty($_SESSION['csrf_post'])) {
    $_SESSION['csrf_post'] = bin2hex(random_bytes(32));
}

$errors = [];
$titleFill = '';
$contentFill = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // CSRF校验
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_post'], $token)) {
        $errors[] = '非法提交请求';
    }

    $titleFill = trim($_POST['title'] ?? '');
    $contentFill = trim($_POST['content'] ?? '');

    // 表单校验
    if (empty($titleFill)) $errors[] = '文章标题不能为空';
    if (mb_strlen($titleFill) > 200) $errors[] = '标题不能超过200个字符';
    if (empty($contentFill)) $errors[] = '文章内容不能为空';

    if (empty($errors)) {
        try {
            $pdo = getPdo();
            $stmt = $pdo->prepare("INSERT INTO posts (user_id, title, content) VALUES (:uid, :title, :content)");
            $stmt->execute([
                ':uid' => $_SESSION['user_id'],
                ':title' => $titleFill,
                ':content' => $contentFill
            ]);
            header('Location: list_posts.php', true, 302);
            exit;
        } catch (PDOException $e) {
            $errors[] = '文章发布失败,请稍后重试';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>发布文章</title>
    <style>
        * {margin:0;padding:0;box-sizing:border-box;font-family:"Microsoft YaHei";}
        body {background:#f5f7fa;padding:60px 20px;}
        .card {width:620px;margin:0 auto;background:#fff;padding:32px;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,0.08);}
        h2 {text-align:center;margin-bottom:24px;color:#2d3748;}
        .err-box {background:#fee;color:#dc2626;padding:10px;border-radius:6px;margin-bottom:16px;}
        .item {margin-bottom:16px;}
        label {display:block;margin-bottom:6px;color:#444;}
        input, textarea {width:100%;padding:12px;border:1px solid #cbd5e0;border-radius:6px;font-size:15px;}
        textarea {height:240px;resize:none;}
        button {width:100%;padding:12px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:16px;cursor:pointer;}
        button:hover {background:#1d4ed8;}
        .link-back {display:block;text-align:center;margin-top:16px;color:#666;text-decoration:none;}
    </style>
</head>
<body>
    <div class="card">
        <h2>发布新文章</h2>
        <?php if (!empty($errors)): ?>
            <div class="err-box"><?= implode('<br>', array_map(fn($v)=>htmlspecialchars($v,ENT_QUOTES), $errors)) ?></div>
        <?php endif; ?>
        <form method="post">
            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_post'], ENT_QUOTES) ?>">
            <div class="item">
                <label>文章标题</label>
                <input type="text" name="title" value="<?= htmlspecialchars($titleFill, ENT_QUOTES) ?>" placeholder="请输入文章标题(最多200字)">
            </div>
            <div class="item">
                <label>文章内容</label>
                <textarea name="content" placeholder="请输入正文内容"><?= htmlspecialchars($contentFill, ENT_QUOTES) ?></textarea>
            </div>
            <button type="submit">立即发布</button>
        </form>
        <a class="link-back" href="list_posts.php">返回文章列表</a>
    </div>
</body>
</html>

8.3 文章列表(list_posts.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

$pdo = getPdo();
// 联表查询文章+作者
$sql = "SELECT p.id, p.title, p.created_at, u.username
FROM posts p
JOIN users u ON p.user_id = u.id
ORDER BY p.created_at DESC";
$stmt = $pdo->query($sql);
$postList = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>全部文章</title>
    <style>
        * {margin:0;padding:0;box-sizing:border-box;font-family:"Microsoft YaHei";}
        body {background:#f5f7fa;padding:40px 20px;}
        .wrap {max-width:720px;margin:0 auto;}
        .top-bar {display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;}
        h1 {color:#2d3748;font-size:24px;}
        .btn-pub {padding:8px 16px;background:#2563eb;color:#fff;text-decoration:none;border-radius:6px;}
        .post-item {background:#fff;padding:20px;border-radius:10px;box-shadow:0 1px 8px rgba(0,0,0,0.06);margin-bottom:16px;}
        .post-title {font-size:18px;margin-bottom:8px;}
        .post-title a {color:#2563eb;text-decoration:none;}
        .post-meta {color:#666;font-size:14px;}
        .empty-tip {text-align:center;padding:40px;color:#888;background:#fff;border-radius:10px;}
    </style>
</head>
<body>
    <div class="wrap">
        <div class="top-bar">
            <h1>文章列表</h1>
            <a href="create_post.php" class="btn-pub">发布新文章</a>
        </div>
        <?php if (empty($postList)): ?>
            <div class="empty-tip">暂无任何文章,快去发布第一篇吧</div>
        <?php else: ?>
            <?php foreach ($postList as $post): ?>
                <div class="post-item">
                    <div class="post-title">
                        <a href="view_post.php?id=<?= htmlspecialchars($post['id'], ENT_QUOTES) ?>">
                            <?= htmlspecialchars($post['title']) ?>
                        </a>
                    </div>
                    <div class="post-meta">
                        作者:<?= htmlspecialchars($post['username']) ?> · 发布时间:<?= htmlspecialchars($post['created_at']) ?>
                    </div>
                </div>
            <?php endforeach; ?>
        <?php endif; ?>
    </div>
</body>
</html>

8.4 查看文章(view_post.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

$id = trim($_GET['id'] ?? '');
$pdo = getPdo();
$sql = "SELECT p.*, u.username, u.id as author_uid
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.id = :pid";
$stmt = $pdo->prepare($sql);
$stmt->execute([':pid' => $id]);
$post = $stmt->fetch();

// 不存在拦截
if (!$post) {
    header('Location: list_posts.php', true, 302);
    exit;
}
$isAuthor = (!empty($_SESSION['logged_in']) && $_SESSION['user_id'] == $post['author_uid']);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title><?= htmlspecialchars($post['title']) ?></title>
    <style>
        * {margin:0;padding:0;box-sizing:border-box;font-family:"Microsoft YaHei";}
        body {background:#f5f7fa;padding:40px 20px;}
        .box {max-width:700px;margin:0 auto;background:#fff;padding:36px;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,0.08);}
        h1 {text-align:center;margin-bottom:16px;color:#222;}
        .meta {text-align:center;color:#666;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid #eee;}
        .content {line-height:1.8;font-size:16px;color:#333;white-space:pre-wrap;}
        .btn-group {margin-top:30px;text-align:center;}
        .btn {display:inline-block;padding:8px 16px;margin:0 6px;text-decoration:none;border-radius:6px;}
        .btn-edit {background:#059669;color:#fff;}
        .btn-del {background:#dc2626;color:#fff;}
        .btn-back {background:#666;color:#fff;}
    </style>
</head>
<body>
    <div class="box">
        <h1><?= htmlspecialchars($post['title']) ?></h1>
        <div class="meta">
            作者:<?= htmlspecialchars($post['username']) ?> | 发布时间:<?= htmlspecialchars($post['created_at']) ?>
        </div>
        <div class="content">
            <?= nl2br(htmlspecialchars($post['content'])) ?>
        </div>
        <div class="btn-group">
            <?php if ($isAuthor): ?>
                <a href="edit_post.php?id=<?= htmlspecialchars($post['id']) ?>" class="btn btn-edit">编辑文章</a>
                <a href="delete_post.php?id=<?= htmlspecialchars($post['id']) ?>" class="btn btn-del">删除文章</a>
            <?php endif; ?>
            <a href="list_posts.php" class="btn btn-back">返回列表</a>
        </div>
    </div>
</body>
</html>

8.5 编辑文章(edit_post.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

// 未登录拦截
if (empty($_SESSION['logged_in'])) {
    header('Location: login_db.php', true, 302);
    exit;
}

$id = trim($_GET['id'] ?? '');
$pdo = getPdo();
// 查询文章,校验归属
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id = :pid");
$stmt->execute([':pid' => $id]);
$post = $stmt->fetch();

// 无文章/不是作者拦截
if (!$post || $post['user_id'] != $_SESSION['user_id']) {
    header('Location: list_posts.php', true, 302);
    exit;
}

// CSRF
if (empty($_SESSION['csrf_edit'])) {
    $_SESSION['csrf_edit'] = bin2hex(random_bytes(32));
}

$errors = [];
$titleFill = $post['title'];
$contentFill = $post['content'];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_edit'], $token)) {
        $errors[] = '非法请求';
    }

    $titleFill = trim($_POST['title'] ?? '');
    $contentFill = trim($_POST['content'] ?? '');

    if (empty($titleFill)) $errors[] = '标题不能为空';
    if (mb_strlen($titleFill) > 200) $errors[] = '标题最多200字符';
    if (empty($contentFill)) $errors[] = '内容不能为空';

    if (empty($errors)) {
        try {
            $upd = $pdo->prepare("UPDATE posts SET title=:t, content=:c WHERE id=:id");
            $upd->execute([
                ':t' => $titleFill,
                ':c' => $contentFill,
                ':id' => $id
            ]);
            header("Location: view_post.php?id=$id", true, 302);
            exit;
        } catch (PDOException $e) {
            $errors[] = '保存修改失败';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>编辑文章</title>
    <style>
        * {margin:0;padding:0;box-sizing:border-box;font-family:"Microsoft YaHei";}
        body {background:#f5f7fa;padding:60px 20px;}
        .card {width:620px;margin:0 auto;background:#fff;padding:32px;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,0.08);}
        h2 {text-align:center;margin-bottom:24px;color:#2d3748;}
        .err-box {background:#fee;color:#dc2626;padding:10px;border-radius:6px;margin-bottom:16px;}
        .item {margin-bottom:16px;}
        label {display:block;margin-bottom:6px;color:#444;}
        input, textarea {width:100%;padding:12px;border:1px solid #cbd5e0;border-radius:6px;font-size:15px;}
        textarea {height:240px;resize:none;}
        button {width:100%;padding:12px;background:#059669;color:#fff;border:none;border-radius:6px;font-size:16px;cursor:pointer;}
        button:hover {background:#047857;}
        .link-back {display:block;text-align:center;margin-top:16px;color:#666;text-decoration:none;}
    </style>
</head>
<body>
    <div class="card">
        <h2>编辑文章</h2>
        <?php if (!empty($errors)): ?>
            <div class="err-box"><?= implode('<br>', array_map(fn($v)=>htmlspecialchars($v,ENT_QUOTES), $errors)) ?></div>
        <?php endif; ?>
        <form method="post">
            <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_edit'], ENT_QUOTES) ?>">
            <div class="item">
                <label>文章标题</label>
                <input type="text" name="title" value="<?= htmlspecialchars($titleFill, ENT_QUOTES) ?>">
            </div>
            <div class="item">
                <label>文章内容</label>
                <textarea name="content"><?= htmlspecialchars($contentFill, ENT_QUOTES) ?></textarea>
            </div>
            <button type="submit">保存修改</button>
        </form>
        <a class="link-back" href="view_post.php?id=<?= htmlspecialchars($id, ENT_QUOTES) ?>">取消返回文章</a>
    </div>
</body>
</html>

8.6 删除文章(delete_post.php)

php 复制代码
<?php
require 'db.php';
session_start();
header("X-XSS-Protection: 1; mode=block");
header("X-Frame-Options: DENY");

if (empty($_SESSION['logged_in'])) {
    header('Location: login_db.php', true, 302);
    exit;
}

$id = trim($_GET['id'] ?? '');
$pdo = getPdo();
$stmt = $pdo->prepare("SELECT id, title, user_id FROM posts WHERE id = :pid");
$stmt->execute([':pid' => $id]);
$post = $stmt->fetch();

// 拦截无权限/不存在
if (!$post || $post['user_id'] != $_SESSION['user_id']) {
    header('Location: list_posts.php', true, 302);
    exit;
}

// CSRF
if (empty($_SESSION['csrf_del'])) {
    $_SESSION['csrf_del'] = bin2hex(random_bytes(32));
}
$err = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_del'], $token)) {
        $err = '非法操作';
    } else {
        try {
            $del = $pdo->prepare("DELETE FROM posts WHERE id=:id");
            $del->execute([':id' => $id]);
            header('Location: list_posts.php', true, 302);
            exit;
        } catch (PDOException $e) {
            $err = '删除失败,请重试';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>确认删除文章</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei";
        }
        body {
            background: #f5f7fa;
            padding: 80px 20px;
        }
        .card {
            width: 460px;
            margin: 0 auto;
            background: #fff;
            padding: 36px;
            border-radius: 10px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.08);
            text-align: center;
        }
        h2 {
            color: #dc2626;
            margin-bottom: 16px;
            font-size: 20px;
        }
        .tip {
            margin-bottom: 24px;
            color: #444;
            line-height: 1.7;
        }
        .err {
            color: #dc2626;
            margin-bottom: 16px;
        }
        /* 弹性容器,两个子容器平分宽度 */
        .btn-group {
            display: flex;
            gap: 12px;
        }
        /* form 和 a 容器各占一半宽度 */
        .btn-group form,
        .btn-group .cancel-wrap {
            flex: 1;
        }
        /* 按钮与链接统一尺寸,填满父容器 */
        .btn-group button,
        .btn-group a {
            display: block;
            width: 100%;
            height: 44px;
            line-height: 44px;
            padding: 0;
            border-radius: 6px;
            font-size: 16px;
            text-decoration: none;
            border: none;
            cursor: pointer;
            text-align: center;
        }
        .btn-confirm {
            background: #dc2626;
            color: #fff;
        }
        .btn-cancel {
            background: #666;
            color: #fff;
        }
    </style>
</head>
<body>
    <div class="card">
        <h2>删除确认</h2>
        <?php if ($err): ?>
            <div class="err"><?= htmlspecialchars($err, ENT_QUOTES) ?></div>
        <?php endif; ?>
        <div class="tip">
            确定要永久删除文章:<br>
            <strong><?= htmlspecialchars($post['title']) ?></strong>?<br>
            删除后数据无法恢复!
        </div>
        <div class="btn-group">
            <!-- 左侧确认按钮容器 -->
            <form method="post">
                <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_del'], ENT_QUOTES) ?>">
                <button class="btn-confirm" type="submit">确认删除</button>
            </form>
            <!-- 右侧取消按钮容器 -->
            <div class="cancel-wrap">
                <a class="btn-cancel" href="view_post.php?id=<?= htmlspecialchars($id, ENT_QUOTES) ?>">取消</a>
            </div>
        </div>
    </div>
</body>
</html>

访问:http://localhost/list_posts.php


九、高级但实用的知识点

9.1 事务(Transaction)

当一系列数据库操作必须全部成功或全部失败(如转账:A 扣钱,B 加钱),要用事务。

php 复制代码
<?php
$pdo->beginTransaction();
try {
    $stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
    $stmt1->execute();
    $stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
    $stmt2->execute();
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    throw $e;
}
?>

9.2 分页查询

利用 LIMITOFFSET 实现。

php 复制代码
<?php
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 10;
$offset = ($page - 1) * $perPage;
​
$stmt = $pdo->prepare("SELECT * FROM posts ORDER BY id DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$posts = $stmt->fetchAll();
​
// 获取总数计算总页数
$totalStmt = $pdo->query("SELECT COUNT(*) FROM posts");
$total = $totalStmt->fetchColumn();
$totalPages = ceil($total / $perPage);
?>

9.3 防止 SQL 注入的高级注意事项

  • 即使是 ORDER BYLIMIT,参数也应使用白名单验证,因占位符不能用于这些地方。

  • 对于 IN (...) 查询,需要动态生成占位符并绑定。

  • 仍然要使用 htmlspecialchars 输出数据库内容,防止 XSS。


十、总结

  • MySQL 基础:理解了数据库、表、行、列、主键、外键的概念,学会了使用 SQL 语句(CREATE、INSERT、SELECT、UPDATE、DELETE)。

  • phpMyAdmin:可视化操作数据库的便捷工具,适合初学者。

  • PDO 连接:掌握了安全稳定的连接方式,配置属性让错误处理更友好。

  • 预处理语句:彻底解决 SQL 注入,使用命名占位符或问号绑定参数,并应用于增删改查。

  • 综合实战:将注册登录系统升级为数据库版,并完整实现了博客文章的 CRUD,包括权限验证。

  • 进阶技巧:事务、分页、更细致的注入防御。


如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。动手练习是掌握编程最快的方法,请务必亲手敲一遍本文的所有示例代码,并截图保存你的成果。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。

相关推荐
fofantasy1 小时前
NSK LH25FL 升级至 NH25EM 技术规格指南
服务器·网络·数据库·经验分享·规格说明书
炘爚1 小时前
Linux——Redis
数据库·redis·缓存
Oo_行者_oO2 小时前
删库先别跑路,万一修复呢?MySQL 误删数据恢复可落地运维文档
数据库·面试
曾阿伦2 小时前
深入了解MongoDB 两地三中心架构
数据库·mongodb·架构
代码雕刻家2 小时前
1.24.MySQL-idea中连接MySQL的基本操作
数据库·mysql·intellij-idea
qq_452396232 小时前
第十四篇:《K8s 网络模型与 CNI 插件(Calico、Flannel、Cilium)》
网络·kubernetes·php
炘爚2 小时前
MySQL——事务和隔离级别
数据库·mysql
DeboPXK2 小时前
NSK VH25EM 高防尘法兰型导轨技术手册
服务器·网络·数据库·经验分享·规格说明书
小挪号底迪滴2 小时前
Redis 和 MySQL 数据不一致怎么办?缓存更新策略实战
redis·mysql·缓存