基于PHP后端搭建的简单博客系统介绍 第一部分:项目概述 1. 项目名称
2. 项目简介
这是一个采用经典WAMP (Windows/Apache/MySQL/PHP)架构的轻量级博客系统。系统部署在高度集成的PHPStudy环境中,实现了博客文章的动态发布、管理和展示等核心功能。该系统结构清晰、代码简单,非常适合作为Web开发的入门学习项目。
3. 项目目标
主要通过搭建简单的博客系统,来理解掌握PHP原生语法、MySQL数据库操作以及前后端数据交互的基本原理。
第二部分:技术框架
技术选型
版本
说明
开发环境
PHPStudy
v8
核心工具。集成了Apache、MySQL、PHP等必要组件,极大简化了环境配置流程,实现一键启动服务。
服务器
Apache
2.4.39
Web服务器,负责处理HTTP请求和响应。
后端语言
PHP
7.3.4
服务器端脚本语言,负责处理业务逻辑,如连接数据库、处理表单提交、生成动态页面。
数据库
MySQL
5.7.26
关系型数据库,用于存储博客的核心数据,如文章、评论、用户信息等。
前端技术
HTML, CSS
构建用户界面和交互。HTML负责页面结构,CSS负责样式美化。
数据库操作方式
PDO
PHP连接和操作MySQL数据库的扩展。
第三部分:项目功能
此项目包含的功能有基本的用户注册、登录、登出,以及文章写入、编辑、删除和评论功能,部分前端写出来的功能没有在后端实现。
1. 注册功能
2. 登录功能
3. 按发布顺序在首页展示用户文章
在首页我们可以看到我们发布过的文章,包括文章的作者,发布时间等信息。
4. 写文章、编辑文章
在控制台里我们可以编辑之前写过的文章,或者写新文章,又或者删除文章,同时可以看到文章的发布状态。
5. 评论功能
在写好的文章下用户可以看到评论功能,并对文章做出评价。
第四部分:数据库设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY , username VARCHAR (50 ) UNIQUE NOT NULL , email VARCHAR (100 ) UNIQUE NOT NULL , password VARCHAR (255 ) NOT NULL , created_at DATETIME DEFAULT CURRENT_TIMESTAMP , reset_token VARCHAR (255 ), reset_token_expiry DATETIME ); CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY , title VARCHAR (255 ) NOT NULL , content TEXT NOT NULL , author_id INT NOT NULL , created_at DATETIME DEFAULT CURRENT_TIMESTAMP , updated_at DATETIME DEFAULT CURRENT_TIMESTAMP , FOREIGN KEY (author_id) REFERENCES users(id) ); CREATE TABLE comments ( id INT AUTO_INCREMENT PRIMARY KEY , post_id INT NOT NULL , user_id INT NOT NULL , content TEXT NOT NULL , created_at DATETIME DEFAULT CURRENT_TIMESTAMP , parent_id INT , FOREIGN KEY (post_id) REFERENCES posts(id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (parent_id) REFERENCES comments(id) );
1. 用户表(users) 1 2 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY ,
1 username VARCHAR (50 ) UNIQUE NOT NULL ,
username VARCHAR(50):用户名,限制长度为50个字符。
UNIQUE:数据库中的UNIQUE约束 ,确保一列或多列中所有的值都是唯一的,所以在约束应用的列中不能有重复的值。
NOT NULL:在UNIQUE约束 中允许列中的值为空,所以这里用NOT NULL来规定列中的值不能为空值。
1 2 3 email VARCHAR (100 ) UNIQUE NOT NULL , email VARCHAR (100 ) UNIQUE NOT NULL , password VARCHAR (255 ) NOT NULL ,
与上面的写法基本类似,不再解释。
这里的密码表没有使用UNIQUE约束 是因为密码本就是只有用户自己知道,所以一致没有影响,而且VARCHAR(255)是因为我们需要存储HASH加密后的密码,所以长度限定为255个字符。
1 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ,
当插入新的值时MySQL会自动将created_at字段的值设置成当前的日期和时间。
1 2 3 reset_token VARCHAR (255 ), reset_token_expiry DATETIME );
reset_token:密码重置令牌(用于“忘记密码”功能)。
reset_token_expiry:令牌过期时间。
2. 文章表(posts) 1 2 CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY ,
1 title VARCHAR (255 ) NOT NULL ,
存放文章内容,TEXT类型适合存储长文本,不能为空。
1 2 created_at DATETIME DEFAULT CURRENT_TIMESTAMP , updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ,
created_at:文章创建时间,由MySQL自动设置。
updated_at:文章最后修改时间,由MySQL自动设置。
1 2 FOREIGN KEY (author_id) REFERENCES users(id));
外键约束: 确保author_id必须存在于users表的id中。
1 2 CREATE TABLE comments ( id INT AUTO_INCREMENT PRIMARY KEY ,
1 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
parent_id:实现回复功能,指向被回复的评论ID。
1 2 3 4 FOREIGN KEY (post_id) REFERENCES posts(id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (parent_id) REFERENCES comments(id) );
第五部分:后端实现
整个博客系统的树形图展示如下:(其中CSS部分不会在本次汇报中展示)。
1. includes文件夹
此文件夹下包含了两个文件auth.php,db.php。
1.1 db.php(数据库连接模块)
这个文件负责建立应用程序与数据库之间的连接。它被其他需要操作数据库的页面(如调用上述 auth.php中函数的页面)所包含。
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $host = 'localhost:3316' ;$dbname = 'blog_sql' ;$username = 'blog' ;$password = 'blog123' ;try { $pdo = new PDO ("mysql:host=$host ;dbname=$dbname ;charset=utf8" , $username , $password ); $pdo ->setAttribute (PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION ); } catch (PDOException $e ) { die ("数据库连接失败: " . $e ->getMessage ()); } ?>
我们来看看这段代码怎样实现了应用程序与数据库之间的连接。
1 2 3 4 5 6 <?php $host = 'localhost:3316' ; $dbname = 'blog_sql' ; $username = 'blog' ; $password = 'blog123' ;
定义了连接数据库所需的配置信息。这样做的优点是修改配置时只需改动这一个文件。
1 2 3 4 try { $pdo = new PDO ("mysql:host=$host ;dbname=$dbname ;charset=utf8" , $username , $password );
使用PDO扩展连接MySQL数据库。
new PDO(...):实例化一个PDO对象,传入数据源名称(DSN)、用户名和密码。
"mysql:host=$host;dbname=$dbname;charset=utf8":
mysql::指定数据库驱动程序为MySQL。
charset=utf8:设置连接字符集为UTF-8,这是防止中文乱码的关键 。
1 2 $pdo ->setAttribute (PDO::ATTR_ERRMODE , PDO::ERRMODE_EXCEPTION );
配置PDO的错误处理模式。
PDO::ATTR_ERRMODE:这是要设置的属性,这个属性决定了 PDO 在遇到错误时的行为方式。他有三种错误模式也就是第二个参数的选择有三个:
PDO::ERRMODE_SILENT:静默模式,发生错误时不抛出异常,需要手动检查错误。
PDO::ERRMODE_WARNING:警告模式,发生错误时产生E_WARNING,脚本继续执行。
PDO::ERRMODE_EXCEPTION:异常模式,发生错误时抛出 PDOException。
这里使用第三种模式是因为它能强制开发者处理错误,避免将敏感信息暴露给用户。
1 2 3 4 5 } catch (PDOException $e ) { die ("数据库连接失败: " . $e ->getMessage ()); } ?>
作用 :异常处理,优雅地处理连接失败的情况。
catch(PDOException $e):捕获在 try块中抛出的 PDOException类型的异常。
die("数据库连接失败: " . $e->getMessage()):如果连接失败,立即终止脚本运行,并显示一个友好的错误信息以及从异常对象中获取的具体错误原因 $e->getMessage()。
1.2 auth.php(用户认证模块)
这个文件是博客系统的核心安全与身份验证模块,负责处理用户登陆状态、登录、注册和密码重置等关键功能,具体代码展示如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php session_start ();function isLoggedIn ( ) { return isset ($_SESSION ['user_id' ]); } function login ($username , $password , $pdo ) { $stmt = $pdo ->prepare ("SELECT * FROM users WHERE username = ? OR email = ?" ); $stmt ->execute ([$username , $username ]); $user = $stmt ->fetch (); if ($user && password_verify ($password , $user ['password' ])) { $_SESSION ['user_id' ] = $user ['id' ]; $_SESSION ['username' ] = $user ['username' ]; return true ; } return false ; } function register ($userData , $pdo ) { $stmt = $pdo ->prepare ("SELECT id FROM users WHERE username = ? OR email = ?" ); $stmt ->execute ([$userData ['username' ], $userData ['email' ]]); if ($stmt ->fetch ()) { return "用户名或邮箱已存在" ; } $hashedPassword = password_hash ($userData ['password' ], PASSWORD_DEFAULT); $stmt = $pdo ->prepare ("INSERT INTO users (username, email, password) VALUES (?, ?, ?)" ); if ($stmt ->execute ([$userData ['username' ], $userData ['email' ], $hashedPassword ])) { return true ; } return "注册失败,请重试" ; } ?>
session_start()函数:
用于在PHP中启动一个新的会话或者重用现有的会话。
会话是一种在多个页面之间存储信息的方式,通常用于存储用户信息,他必须在任何输出到浏览器之前调用。
会话机制是允许服务器在多个页面请求之间存储用户的基本信息,是实现用户登陆状态保持的基础。
1 2 3 4 function isLoggedIn ( ) { return isset ($_SESSION ['user_id' ]); }
这是一个工具函数,用于快速检查当前访问者是否已经登录。
isset()函数用来检查一个变量是否已经被声明,并且不是一个空值。
所以isset($_SESSION['user_id']);:用于检查变量$_SESSION中是否存在一个名为user_id的键。如果用户成功登录,这个值会被设置。
return:如果 user_id存在,返回 true,表示用户已登录;否则返回 false。
1.2.1 用户登录函数 1 2 3 4 5 6 function login ($username , $password , $pdo ) { $stmt = $pdo ->prepare ("SELECT * FROM users WHERE username = ? OR email = ?" ); $stmt ->execute ([$username , $username ]); $user = $stmt ->fetch ();
这个函数尝试使用用户名密码登录。
$pdo->prepare(...):创建一个预处理语句。使用占位符 ?可以有效防止SQL注入攻击,这是至关重要的安全措施。
$stmt->execute([$username, $username]):执行预处理语句,将两个 $username分别替换到SQL中的两个 ?位置。
$user = $stmt->fetch():从查询结果中获取一行数据(即用户记录),存储在 $user变量中。
1 2 3 4 5 6 7 if ($user && password_verify ($password , $user ['password' ])) { $_SESSION ['user_id' ] = $user ['id' ]; $_SESSION ['username' ] = $user ['username' ]; return true ; } return false ;
if ($user && ...):首先检查是否找到了对应的用户($user不为空),然后进行密码验证。
password_verify($password, $user['password']):函数将用户输入的明文密码$password与数据库中存储的经过哈希加密的密码$user['password']进行比对,如果使用MD5或者==进行比较可能会出现问题。
$_SESSION['user_id'] = ...:密码验证成功后,将用户的唯一标识 id和 username存入会话变量。此后,在同一会话的任何页面中,都可以通过检查这些会话变量来判断用户身份,与开局信用的session_start()形成呼应。
1.2.2 用户注册函数 1 2 3 4 5 6 7 8 9 function register ($userData , $pdo ) { $stmt = $pdo ->prepare ("SELECT id FROM users WHERE username = ? OR email = ?" ); $stmt ->execute ([$userData ['username' ], $userData ['email' ]]); if ($stmt ->fetch ()) { return "用户名或邮箱已存在" ; }
注册用户时我们首先检查用户名或者邮箱是不是已经被使用过。
PDO的使用流程可以说和登录一样,首先是通过$pdo->prepare创建一个预处理SQL语句,通过?占位,之后就是通过execute()来替换?,并执行语句。
SELECT id ...:查询是否存在相同的用户名或邮箱。只需查询 id字段就足够了,效率高。
if ($stmt->fetch()):如果查询到任何结果,说明用户名或邮箱已被占用,函数立即返回一个错误消息字符串。
1 2 $hashedPassword = password_hash ($userData ['password' ], PASSWORD_DEFAULT);
password_hash(..., PASSWORD_DEFAULT):使用当前PHP的默认算法对明文密码进行哈希加密,一个系统基本的安全要求就是不要将明文密码存入数据库中。
1 2 3 4 5 6 7 $stmt = $pdo ->prepare ("INSERT INTO users (username, email, password) VALUES (?, ?, ?)" ); if ($stmt ->execute ([$userData ['username' ], $userData ['email' ], $hashedPassword ])) { return true ; } return "注册失败,请重试" ; }
INSERT INTO ...:准备插入数据的SQL语句。
$stmt->execute(...):执行插入操作。如果执行成功(通常返回 true),则返回 true表示注册成功;如果因未知原因失败(如数据库连接问题),则返回错误信息。
2. 博客根目录下文件 2.1 register.php(用户注册)
这部分主要用来实现用户注册功能,包括前端和后端实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;$error = '' ;if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $userData = [ 'username' => $_POST ['username' ], 'email' => $_POST ['email' ], 'password' => $_POST ['password' ] ]; $result = register ($userData , $pdo ); if ($result === true ) { header ('Location: login.php?registered=1' ); exit ; } else { $error = $result ; } } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>注册 - 我的博客</title> <link rel="stylesheet" href="css/style.css" > </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <li ><a href ="login .php ">登录</a ></li > </ul > </nav > </header > <main class ="container auth -container "> <h1 >注册</h1 > <?php if ($error ): ?> <div class ="error "><?= $error ?></div > <?php endif ; ?> <form method ="POST " class ="auth -form "> <div class ="form -group "> <label for ="username ">用户名:</label > <input type ="text " id ="username " name ="username " required > </div > <div class ="form -group "> <label for ="email ">邮箱:</label > <input type ="email " id ="email " name ="email " required > </div > <div class ="form -group "> <label for ="password ">密码:</label > <input type ="password " id ="password " name ="password " required > </div > <button type ="submit " class ="btn ">注册</button > </form > </main > </body > </html >
1 2 3 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;
require_once是 PHP 中用于引入文件的语句,确保同一个文件只会被加载一次。
require_once 'includes/db.php';引入数据库连接文件。
require_once 'includes/auth.php'; 引入认证功能文件。
1 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) {
1 2 3 4 5 $userData = [ 'username' => $_POST ['username' ], 'email' => $_POST ['email' ], 'password' => $_POST ['password' ] ];
1 $result = register ($userData , $pdo );
调用注册函数(我们在auth.php中定义的函数),传入用户数据和数据库连接。
1 2 3 4 5 6 7 8 9 if ($result === true ) { header ('Location: login.php?registered=1' ); exit ; } else { $error = $result ; } }
2.2 login.php(用户登录)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;$error = '' ;if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { if (login ($_POST ['username' ], $_POST ['password' ], $pdo )) { header ('Location: index.php' ); exit ; } else { $error = '用户名或密码错误' ; } } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>登录 - 我的博客</title> <link rel="stylesheet" href="css/style.css" > </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <li ><a href ="register .php ">注册</a ></li > </ul > </nav > </header > <main class ="container auth -container "> <h1 >登录</h1 > <?php if ($error ): ?> <div class ="error "><?= $error ?></div > <?php endif ; ?> <form method ="POST " class ="auth -form "> <div class ="form -group "> <label for ="username ">用户名或邮箱:</label > <input type ="text " id ="username " name ="username " required > </div > <div class ="form -group "> <label for ="password ">密码:</label > <input type ="password " id ="password " name ="password " required > </div > <button type ="submit " class ="btn ">登录</button > </form > <p ><a href ="forgot -password .php ">忘记密码?</a ></p > </main > </body > </html >
1 2 3 4 5 6 if (login ($_POST ['username' ], $_POST ['password' ], $pdo )) { header ('Location: index.php' ); exit ; } else { $error = '用户名或密码错误' ; }
调用auth.php中的login()函数,与数据库进行交互,用来判断登录信息,成功则跳转首页。
安全机制 :登录成功后立即跳转,防止重复提交。
2.3 index.php(首页文章列表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;$stmt = $pdo ->prepare (" SELECT p.*, u.username FROM posts p JOIN users u ON p.author_id = u.id ORDER BY p.created_at DESC " );$stmt ->execute ();$posts = $stmt ->fetchAll ();?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>我的博客</title> <link rel="stylesheet" href="css/style.css" > </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <?php if (isLoggedIn ()): ?> <li ><a href ="dashboard /">控制台</a ></li > <li ><a href ="logout .php ">退出</a ></li > <?php else : ?> <li ><a href ="login .php ">登录</a ></li > <li ><a href ="register .php ">注册</a ></li > <?php endif ; ?> </ul > </nav > </header > <main class ="container "> <h1 >最新文章</h1 > <?php foreach ($posts as $post ): ?> <article class ="post "> <h2 ><a href ="post .php ?id =<?= $post ['id '] ?>"><?= htmlspecialchars ($post ['title ']) ?></a ></h2 > <div class ="post -meta "> 作者: <?= htmlspecialchars ($post ['username ']) ?> | 发布时间: <?= date ('Y -m -d H :i ', strtotime ($post ['created_at '])) ?> </div > <div class ="post -content "> <?= nl2br (htmlspecialchars (substr ($post ['content '], 0, 200))) ?>... </div > <a href ="post .php ?id =<?= $post ['id '] ?>" class ="read -more ">阅读更多</a > </article > <?php endforeach ; ?> </main > <footer > <p >© ; <?= date ('Y ') ?> MosHSasA_BLOG . 保留所有权利.</p > </footer > </body > </html >
1 $stmt = $pdo ->prepare ("SELECT p.*, u.username FROM posts p JOIN users u ON p.author_id = u.id ORDER BY p.created_at DESC" );
准备SQL查询语句,获取文章和作者信息。
数据库设计 :使用JOIN关联查询,体现数据库关系设计。
1 2 $stmt ->execute ();$posts = $stmt ->fetchAll ();
执行查询并获取所有结果,这里使用了预处理语句防止SQL注入。
2.4 post.php(文章详情页)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;$postId = $_GET ['id' ] ?? 0 ;$stmt = $pdo ->prepare (" SELECT p.*, u.username FROM posts p JOIN users u ON p.author_id = u.id WHERE p.id = ? " );$stmt ->execute ([$postId ]);$post = $stmt ->fetch ();if (!$post ) { header ('HTTP/1.0 404 Not Found' ); die ('文章不存在' ); } $stmt = $pdo ->prepare (" SELECT c.*, u.username FROM comments c JOIN users u ON c.user_id = u.id WHERE c.post_id = ? ORDER BY c.created_at DESC " );$stmt ->execute ([$postId ]);$comments = $stmt ->fetchAll ();if ($_SERVER ['REQUEST_METHOD' ] === 'POST' && isLoggedIn ()) { $content = $_POST ['content' ]; $stmt = $pdo ->prepare ("INSERT INTO comments (post_id, user_id, content) VALUES (?, ?, ?)" ); $stmt ->execute ([$postId , $_SESSION ['user_id' ], $content ]); header ("Location: post.php?id=$postId " ); exit ; } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title><?= htmlspecialchars ($post ['title' ]) ?> - 我的博客</title> <link rel="stylesheet" href="css/style.css" > </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <?php if (isLoggedIn ()): ?> <li ><a href ="dashboard /">控制台</a ></li > <li ><a href ="logout .php ">退出</a ></li > <?php else : ?> <li ><a href ="login .php ">登录</a ></li > <li ><a href ="register .php ">注册</a ></li > <?php endif ; ?> </ul > </nav > </header > <main class ="container "> <article class ="post -full "> <h1 ><?= htmlspecialchars ($post ['title ']) ?></h1 > <div class ="post -meta "> 作者: <?= htmlspecialchars ($post ['username ']) ?> | 发布时间: <?= date ('Y -m -d H :i ', strtotime ($post ['created_at '])) ?> </div > <div class ="post -content "> <?= nl2br (htmlspecialchars ($post ['content '])) ?> </div > </article > <section class ="comments -section "> <h2 >评论 (<?= count ($comments ) ?>)</h2 > <?php if (isLoggedIn ()): ?> <form method ="POST " class ="comment -form "> <textarea name ="content " placeholder ="写下您的评论..." required ></textarea > <button type ="submit " class ="btn ">发表评论</button > </form > <?php else : ?> <p ><a href ="login .php ">登录</a >后即可发表评论</p > <?php endif ; ?> <div class ="comments "> <?php foreach ($comments as $comment ): ?> <div class ="comment "> <div class ="comment -header "> <strong ><?= htmlspecialchars ($comment ['username ']) ?></strong > <span ><?= date ('Y -m -d H :i ', strtotime ($comment ['created_at '])) ?></span > </div > <div class ="comment -content "> <?= nl2br (htmlspecialchars ($comment ['content '])) ?> </div > </div > <?php endforeach ; ?> </div > </section > </main > </body > </html >
1 $postId = $_GET ['id' ] ?? 0 ;
1 2 3 4 5 6 7 8 $stmt = $pdo ->prepare (" SELECT p.*, u.username FROM posts p JOIN users u ON p.author_id = u.id WHERE p.id = ? " );$stmt ->execute ([$postId ]);$post = $stmt ->fetch ();
与上面讲述过的类似,首先准备SQL查询语句,之后替换?为文章ID,最后从查询结果中获取一行数据。
1 2 3 4 if (!$post ) { header ('HTTP/1.0 404 Not Found' ); die ('文章不存在' ); }
1 2 3 4 5 6 7 8 9 $stmt = $pdo ->prepare (" SELECT c.*, u.username FROM comments c JOIN users u ON c.user_id = u.id WHERE c.post_id = ? ORDER BY c.created_at DESC " );$stmt ->execute ([$postId ]);$comments = $stmt ->fetchAll ();
获取评论,与获取文章的方式大同小异,需要注意的时$stmt->fetchAll();这里时直接获取所有的评论,不仅仅只是一行数据。
1 2 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' && isLoggedIn ()) {
条件判断 :只有当同时满足以下两个条件时才执行评论提交。
请求方法是 POST(通常是表单提交)。
用户已登录(isLoggedIn()返回 true)。
1 $content = $_POST ['content' ];
1 2 $stmt = $pdo ->prepare ("INSERT INTO comments (post_id, user_id, content) VALUES (?, ?, ?)" );$stmt ->execute ([$postId , $_SESSION ['user_id' ], $content ]);
依旧使用预处理语句防止SQL注入,与前面的过程一致。
1 2 header ("Location: post.php?id=$postId " );exit ;
页面重定向,评论提交成功后跳转回文章首页,exit确保脚本立即终止,避免后续代码执行。
2.5 forgot-password.php(密码找回功能)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <?php require_once 'includes/db.php' ;require_once 'includes/auth.php' ;$message = '' ;if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $email = $_POST ['email' ]; $token = generateResetToken ($email , $pdo ); if ($token ) { $resetLink = "http://" . $_SERVER ['HTTP_HOST' ] . "/reset-password.php?token=" . $token ; $message = "重置链接已发送到您的邮箱: " . $resetLink ; } else { $message = "邮箱不存在或发送失败" ; } } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>找回密码 - 我的博客</title> <link rel="stylesheet" href="css/style.css" > </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <li ><a href ="login .php ">登录</a ></li > <li ><a href ="register .php ">注册</a ></li > </ul > </nav > </header > <main class ="container auth -container "> <h1 >找回密码</h1 > <?php if ($message ): ?> <div class ="message "><?= $message ?></div > <?php endif ; ?> <form method ="POST " class ="auth -form "> <div class ="form -group "> <label for ="email ">请输入您的邮箱:</label > <input type ="email " id ="email " name ="email " required > </div > <button type ="submit " class ="btn ">发送重置链接</button > </form > <p ><a href ="login .php ">返回登录</a ></p > </main > </body > </html >
1 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) {
1 2 $email = $_POST ['email' ];$token = generateResetToken ($email , $pdo );
获取用户邮箱并生成密码重置令牌,令牌机制可以避免直接修改密码更加安全。
1 2 3 4 if ($token ) { $resetLink = "http://" . $_SERVER ['HTTP_HOST' ] . "/reset-password.php?token=" . $token ; $message = "重置链接已发送到您的邮箱: " . $resetLink ; }
生成充值链接并提示用户,但是这里实现的话需要更加繁琐的步骤,所以我将它直接回显在页面上,实际应用的情况下,时通过邮箱发送到用户的邮箱里。
2.6 logout.php(用户退出)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 <?php session_start ();if (isset ($_SESSION ['username' ])) { $username = $_SESSION ['username' ]; $logout_time = date ('Y-m-d H:i:s' ); $log_message = "用户 {$username} 于 {$logout_time} 注销\n" ; if (!is_dir ('logs' )) { mkdir ('logs' , 0755 , true ); } file_put_contents ('logs/logout.log' , $log_message , FILE_APPEND | LOCK_EX); } $_SESSION = array ();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 ();?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>退出成功 - 我的博客</title> <link rel="stylesheet" href="css/style.css" > <style> .logout-container { background: white; padding: 2 rem; border-radius: 5 px; box-shadow: 0 2 px 5 px rgba (0 ,0 ,0 ,0.1 ); text-align: center; max-width: 500 px; margin: 2 rem auto; } .success-icon { font-size: 4 rem; color: margin-bottom: 1 rem; } .auto-redirect { margin-top: 1.5 rem; color: } .countdown { font-weight: bold; color: } .btn-group { display: flex; gap: 1 rem; justify-content: center; margin-top: 1.5 rem; } </style> </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="index .php ">首页</a ></li > <li ><a href ="login .php ">登录</a ></li > <li ><a href ="register .php ">注册</a ></li > </ul > </nav > </header > <main class ="container "> <div class ="logout -container "> <div class ="success -icon ">✓</div > <h1 >退出成功</h1 > <p >您已成功退出登录,感谢您使用我们的博客系统</p > <div class ="btn -group "> <a href ="login .php " class ="btn ">重新登录</a > <a href ="index .php " class ="btn " style ="background : #95a5a6 ;">返回首页</a > </div > <p class ="auto -redirect ">页面将在 <span class ="countdown " id ="countdown ">5</span > 秒后自动跳转到登录页面</p > </div > </main > <footer > <p >© ; <?php echo date ('Y '); ?> 我的博客. 保留所有权利.</p > </footer > <script > // 自动跳转功能 let countdown = 5; const countdownElement = document .getElementById ('countdown '); const timer = setInterval (function () { countdown--; countdownElement.textContent = countdown; if (countdown <= 0 ) { clearInterval (timer); window.location.href = 'login.php' ; } }, 1000 ); </script> </body> </html>
1 if (isset ($_SESSION ['username' ])) {
条件判断,检查会话中是否存在'username'变量,isset()函数检查变量是否已设置且不为null。
1 2 3 $username = $_SESSION ['username' ]; $logout_time = date ('Y-m-d H:i:s' ); $log_message = "用户 {$username} 于 {$logout_time} 注销\n" ;
$username = $_SESSION['username']:从会话中获取用户名并赋值给局部变量。
$logout_time = date('Y-m-d H:i:s'):获取当前格式化的日期时间字符串。
date('Y-m-d H:i:s'):生成”年-月-日 时:分:秒”格式的时间戳。
$log_message = "用户 {$username} 于 {$logout_time} 注销\n":构造日志消息字符串。
1 2 3 if (!is_dir ('logs' )) { mkdir ('logs' , 0755 , true ); }
用于创造日志目录。
if (!is_dir('logs')):检查’logs’目录是否存在。
mkdir('logs', 0755, true):创建目录的函数调用。
参数一:'logs':要创建的目录名。
参数二:0755:目录权限设置(所有者可读写执行,组和其他用户可读执行)。
参数三:true:允许创建多级目录(递归创建)。
1 2 file_put_contents ('logs/logout.log' , $log_message , FILE_APPEND | LOCK_EX);}
写入到日志文件。
FILE_APPEND:常量,表示追加模式(不在文件开头写入)。
LOCK_EX:常量,表示独占锁,防止并发写入冲突。
|:位或运算符,组合多个选项。
清空会话变量,将会话全局变量重新赋值为空数组,会清空所有会话数据,但是会话ID仍然存在。
1 if (ini_get ("session.use_cookies" )) {
ini_get("session.use_cookies"):获取PHP配置中是否使用Cookie来管理会话。
1 $params = session_get_cookie_params ();
session_get_cookie_params():返回当前会话Cookie的详细参数数组。
1 2 3 4 setcookie (session_name (), '' , time () - 42000 , $params ["path" ], $params ["domain" ], $params ["secure" ], $params ["httponly" ] );
设置过期Cookie
setcookie():设置Cookie的函数。
session_name():获取当前会话的名称(通常是”PHPSESSID”)。
'':空值,清除Cookie内容。
time() - 42000:将过期时间设为过去(当前时间减42000秒),使浏览器立即删除Cookie。
后续参数使用原Cookie的相同设置,确保正确删除。
3. dashboard文件夹
此目录下包含博客系统控制台下的功能,包括文章的删除、编辑、新建和控制台首页
3.1 index.php(文章首页)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 <?php require_once '../includes/db.php' ;require_once '../includes/auth.php' ;if (!isLoggedIn ()) { header ('Location: ../login.php' ); exit ; } $stmt = $pdo ->prepare (" SELECT * FROM posts WHERE author_id = ? ORDER BY created_at DESC " );$stmt ->execute ([$_SESSION ['user_id' ]]);$posts = $stmt ->fetchAll ();?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>控制台 - 我的博客</title> <link rel="stylesheet" href="../css/style.css" > <style> .dashboard { display: grid; grid-template-columns: 250 px 1 fr; gap: 2 rem; min-height: 80 vh; } .sidebar { background: color: white; padding: 1.5 rem; border-radius: 5 px; } .sidebar ul { list -style: none; } .sidebar li { margin: 1 rem 0 ; } .sidebar a { color: white; text-decoration: none; display: block; padding: 0.5 rem; border-radius: 3 px; transition: background 0.3 s; } .sidebar a:hover, .sidebar a.active { background: } .main-content { padding: 0 ; } .posts-table { width: 100 %; border-collapse: collapse; background: white; border-radius: 5 px; overflow: hidden; box-shadow: 0 2 px 5 px rgba (0 ,0 ,0 ,0.1 ); } .posts-table th, .posts-table td { padding: 1 rem; text-align: left; border-bottom: 1 px solid } .posts-table th { background: font-weight: 600 ; } .btn { padding: 0.5 rem 1 rem; border: none; border-radius: 3 px; cursor: pointer; text-decoration: none; display: inline-block; font-size: 0.9 rem; } .btn-primary { background: color: white; } .btn-danger { background: color: white; } .btn-edit { background: color: white; } .post-actions { display: flex; gap: 0.5 rem; } </style> </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="../index .php ">首页</a ></li > <li ><a href ="logout .php ">退出</a ></li > </ul > </nav > </header > <main class ="container "> <h1 >控制台</h1 > <div class ="dashboard "> <aside class ="sidebar "> <ul > <li ><a href ="index .php " class ="active ">文章管理</a ></li > <li ><a href ="new -post .php ">写文章</a ></li > <li ><a href ="#">评论管理</a ></li > <li ><a href ="#">个人资料</a ></li > </ul > </aside > <div class ="main -content "> <div style ="display : flex ; justify -content : space -between ; align -items : center ; margin -bottom : 1.5rem ;"> <h2 >我的文章</h2 > <a href ="new -post .php " class ="btn btn -primary ">写新文章</a > </div > <?php if (count ($posts ) > 0): ?> <table class ="posts -table "> <thead > <tr > <th >标题</th > <th >状态</th > <th >发布时间</th > <th >操作</th > </tr > </thead > <tbody > <?php foreach ($posts as $post ): ?> <tr > <td > <a href ="../post .php ?id =<?= $post ['id '] ?>"> <?= htmlspecialchars ($post ['title ']) ?> </a > </td > <td > <span style ="color : <?= $post ['status '] == 'published ' ? '#27ae60 ' : '#f39c12 ' ?>"> <?= $post ['status '] == 'published ' ? '已发布' : '草稿' ?> </span > </td > <td ><?= date ('Y -m -d H :i ', strtotime ($post ['created_at '])) ?></td > <td > <div class ="post -actions "> <a href ="edit -post .php ?id =<?= $post ['id '] ?>" class ="btn btn -edit ">编辑</a > <a href ="delete -post .php ?id =<?= $post ['id '] ?>" class ="btn btn -danger " onclick ="return confirm ('确定要删除这篇文章吗?')">删除</a > </div > </td > </tr > <?php endforeach ; ?> </tbody > </table > <?php else : ?> <div style ="text -align : center ; padding : 3rem ; background : white ; border -radius : 5px ;"> <p >您还没有发布任何文章</p > <a href ="new -post .php " class ="btn btn -primary ">开始写第一篇文章</a > </div > <?php endif ; ?> </div > </div > </main > </body > </html >
1 2 3 4 if (!isLoggedIn ()) { header ('Location: ../login.php' ); exit ; }
验证用户是否登录,如果没有登录则重定向回登陆首页。
1 2 3 4 5 6 7 $stmt = $pdo ->prepare (" SELECT * FROM posts WHERE author_id = ? ORDER BY created_at DESC " );$stmt ->execute ([$_SESSION ['user_id' ]]);$posts = $stmt ->fetchAll ();
准备SQL语句透过用户ID获取所有文章并展示在前端页面上。
3.2 new-post.php(新建文章页面)
提供创建新文章的界面和处理逻辑,与编辑页面结构相似但功能独立。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 <?php require_once '../includes/db.php' ;require_once '../includes/auth.php' ;if (!isLoggedIn ()) { header ('Location: ../login.php' ); exit ; } $error = '' ;$success = '' ;if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $title = trim ($_POST ['title' ]); $content = trim ($_POST ['content' ]); $status = $_POST ['status' ]; if (empty ($title ) || empty ($content )) { $error = '标题和内容不能为空' ; } else { $stmt = $pdo ->prepare (" INSERT INTO posts (title, content, author_id, status) VALUES (?, ?, ?, ?) " ); if ($stmt ->execute ([$title , $content , $_SESSION ['user_id' ], $status ])) { $success = '文章发布成功!' ; $title = $content = '' ; } else { $error = '发布失败,请重试' ; } } } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>写文章 - 我的博客</title> <link rel="stylesheet" href="../css/style.css" > <style> .editor-container { background: white; padding: 2 rem; border-radius: 5 px; box-shadow: 0 2 px 5 px rgba (0 ,0 ,0 ,0.1 ); } .form-group { margin-bottom: 1.5 rem; } label { display: block; margin-bottom: 0.5 rem; font-weight: bold; } input[type="text" ], textarea { width: 100 %; padding: 0.75 rem; border: 1 px solid border-radius: 4 px; font-size: 1 rem; font-family: inherit; } textarea { min-height: 300 px; resize: vertical; } .form-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 2 rem; } .status-select { padding: 0.5 rem; border: 1 px solid border-radius: 4 px; } </style> </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="../index .php ">首页</a ></li > <li ><a href ="index .php ">控制台</a ></li > <li ><a href ="logout .php ">退出</a ></li > </ul > </nav > </header > <main class ="container "> <h1 >写文章</h1 > <?php if ($error ): ?> <div class ="error "><?= $error ?></div > <?php endif ; ?> <?php if ($success ): ?> <div class ="message "><?= $success ?></div > <?php endif ; ?> <div class ="editor -container "> <form method ="POST "> <div class ="form -group "> <label for ="title ">文章标题</label > <input type ="text " id ="title " name ="title " value ="<?= isset ($title ) ? htmlspecialchars ($title ) : '' ?>" placeholder ="请输入文章标题" required > </div > <div class ="form -group "> <label for ="content ">文章内容</label > <textarea id ="content " name ="content " placeholder ="请输入文章内容" required ><?= isset ($content ) ? htmlspecialchars ($content ) : '' ?></textarea > </div > <div class ="form -actions "> <div > <label for ="status ">状态:</label > <select id ="status " name ="status " class ="status -select "> <option value ="published ">发布</option > <option value ="draft ">草稿</option > </select > </div > <div > <button type ="submit " class ="btn btn -primary ">发布文章</button > <a href ="index .php " class ="btn ">取消</a > </div > </div > </form > </div > </main > <script > // 简单的字数统计 document .getElementById ('content ').addEventListener ('input ', function () { const charCount = this.value.length; document.getElementById ('charCount' ).textContent = charCount; }); </script> </body> </html>
1 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) {
1 2 3 $title = trim ($_POST ['title' ]);$content = trim ($_POST ['content' ]);$status = $_POST ['status' ];
从POST数据中获取标题、内容和状态。
trim()函数去除字符串两端的空白字符。
数据来源:$_POST超全局数组,包含通过POST方法提交的表单数据。
1 2 3 if (empty ($title ) || empty ($content )) { $error = '标题和内容不能为空' ; }
输入验证检查标题和内容是否为空,empty()函数检查变量是否为”空”(空字符串、0、null等),如果任一字段为空,设置错误信息。
1 2 3 4 5 6 7 8 9 10 11 $stmt = $pdo ->prepare (" INSERT INTO posts (title, content, author_id, status) VALUES (?, ?, ?, ?) " );if ($stmt ->execute ([$title , $content , $_SESSION ['user_id' ], $status ])) { $success = '文章发布成功!' ; $title = $content = '' ; } else { $error = '发布失败,请重试' ; }
数据库操作,准备SQL插入语句。execute()方法执行预处理语句,传入参数数组,$_SESSION['user_id']从会话中获取当前用户ID,如果执行成功返回成功消息并清空标题和内容变量。反之,返回失败消息。
3.3 edit-post.php(文章编辑页面)
这是一个混合型脚本 ,既处理文章更新逻辑,又显示编辑表单界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 <?php require_once '../includes/db.php' ;require_once '../includes/auth.php' ;if (!isLoggedIn ()) { header ('Location: ../login.php' ); exit ; } $error = '' ;$success = '' ;$postId = $_GET ['id' ] ?? 0 ;$stmt = $pdo ->prepare ("SELECT * FROM posts WHERE id = ? AND author_id = ?" );$stmt ->execute ([$postId , $_SESSION ['user_id' ]]);$post = $stmt ->fetch ();if (!$post ) { header ('Location: index.php' ); exit ; } if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $title = trim ($_POST ['title' ]); $content = trim ($_POST ['content' ]); $status = $_POST ['status' ]; if (empty ($title ) || empty ($content )) { $error = '标题和内容不能为空' ; } else { $stmt = $pdo ->prepare (" UPDATE posts SET title = ?, content = ?, status = ?, updated_at = NOW() WHERE id = ? AND author_id = ? " ); if ($stmt ->execute ([$title , $content , $status , $postId , $_SESSION ['user_id' ]])) { $success = '文章更新成功!' ; $stmt = $pdo ->prepare ("SELECT * FROM posts WHERE id = ?" ); $stmt ->execute ([$postId ]); $post = $stmt ->fetch (); } else { $error = '更新失败,请重试' ; } } } ?> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>编辑文章 - 我的博客</title> <link rel="stylesheet" href="../css/style.css" > <style> .editor-container { background: white; padding: 2 rem; border-radius: 5 px; box-shadow: 0 2 px 5 px rgba (0 ,0 ,0 ,0.1 ); } .form-group { margin-bottom: 1.5 rem; } label { display: block; margin-bottom: 0.5 rem; font-weight: bold; } input[type="text" ], textarea { width: 100 %; padding: 0.75 rem; border: 1 px solid border-radius: 4 px; font-size: 1 rem; font-family: inherit; } textarea { min-height: 300 px; resize: vertical; } .form-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 2 rem; } .status-select { padding: 0.5 rem; border: 1 px solid border-radius: 4 px; } </style> </head> <body> <header> <nav> <div class ="logo ">我的博客</div > <ul class ="nav -links "> <li ><a href ="../index .php ">首页</a ></li > <li ><a href ="index .php ">控制台</a ></li > <li ><a href ="logout .php ">退出</a ></li > </ul > </nav > </header > <main class ="container "> <h1 >编辑文章</h1 > <?php if ($error ): ?> <div class ="error "><?= $error ?></div > <?php endif ; ?> <?php if ($success ): ?> <div class ="message "><?= $success ?></div > <?php endif ; ?> <div class ="editor -container "> <form method ="POST "> <div class ="form -group "> <label for ="title ">文章标题</label > <input type ="text " id ="title " name ="title " value ="<?= htmlspecialchars ($post ['title ']) ?>" placeholder ="请输入文章标题" required > </div > <div class ="form -group "> <label for ="content ">文章内容</label > <textarea id ="content " name ="content " placeholder ="请输入文章内容" required ><?= htmlspecialchars ($post ['content ']) ?></textarea > </div > <div class ="form -actions "> <div > <label for ="status ">状态:</label > <select id ="status " name ="status " class ="status -select "> <option value ="published " <?= $post ['status '] == 'published ' ? 'selected ' : '' ?>>发布</option > <option value ="draft " <?= $post ['status '] == 'draft ' ? 'selected ' : '' ?>>草稿</option > </select > </div > <div > <button type ="submit " class ="btn btn -primary ">更新文章</button > <a href ="index .php " class ="btn ">取消</a > </div > </div > </form > </div > </main > </body > </html >
1 $postId = $_GET ['id' ] ?? 0 ;
1 2 3 $stmt = $pdo ->prepare ("SELECT * FROM posts WHERE id = ? AND author_id = ?" );$stmt ->execute ([$postId , $_SESSION ['user_id' ]]);$post = $stmt ->fetch ();
1 2 3 4 if (!$post ) { header ('Location: index.php' ); exit ; }
如果文章不存在或者没有访问权限,则重定向到首页页面。
1 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) {
1 2 3 $title = trim ($_POST ['title' ]);$content = trim ($_POST ['content' ]);$status = $_POST ['status' ];
1 2 3 if (empty ($title ) || empty ($content )) { $error = '标题和内容不能为空' ; } else {
1 2 3 4 5 6 7 8 $stmt = $pdo ->prepare (" UPDATE posts SET title = ?, content = ?, status = ?, updated_at = NOW() WHERE id = ? AND author_id = ? " ); if ($stmt ->execute ([$title , $content , $status , $postId , $_SESSION ['user_id' ]])) { $success = '文章更新成功!' ;
1 2 3 4 5 6 $stmt = $pdo ->prepare ("SELECT * FROM posts WHERE id = ?" ); $stmt ->execute ([$postId ]); $post = $stmt ->fetch (); } else { $error = '更新失败,请重试' ; }
3.4 delete-post.php(删除文章处理脚本)
这是一个纯处理脚本 ,用于安全地删除用户自己的文章,不包含任何HTML显示内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php require_once '../includes/db.php' ;require_once '../includes/auth.php' ;if (!isLoggedIn ()) { header ('Location: ../login.php' ); exit ; } $postId = $_GET ['id' ] ?? 0 ;if ($postId ) { $stmt = $pdo ->prepare ("DELETE FROM posts WHERE id = ? AND author_id = ?" ); $stmt ->execute ([$postId , $_SESSION ['user_id' ]]); } header ('Location: index.php' );exit ;
通过前面获取的文章ID删除文章,只有当文章ID有效时才可以删除文章,这样可以确保用户只能删除自己的文章。
1 $stmt = $pdo ->prepare ("DELETE FROM posts WHERE id = ? AND author_id = ?" );
准备SQL删除语句(安全措施:同时验证文章ID和作者ID)。
1 $stmt ->execute ([$postId , $_SESSION ['user_id' ]]);
执行删除,传入文章ID和当前用户ID(防止删除他人文章)。
1 2 header ('Location: index.php' );exit ;
删除完成后重定向回文章管理页面,并确保重定向后停止脚本。
第六部分:总结 本项目成功实现了一个功能完整的简易博客系统,达到了学习和实践的目的。通过该项目我学习了PHP动态网站的开发过程、MySQL数据库的设计思路与操作,以及前端后端之间的交互过程。
但时当前博客系统功能简单,内容也不完善仍存在部分缺陷,希望后续可以完善和改变。