发布日期: 2023年11月15日
引言
RESTful API是现代Web开发的核心,它提供了一种标准化、可扩展的方式来构建Web服务。本教程将指导您使用PHP和MySQL创建一个功能完整的博客系统API,包含用户认证、文章管理、评论系统等核心功能。
我们将使用纯PHP实现,不依赖任何框架,以便更好地理解底层原理。最终实现的API将支持JSON格式的请求和响应,并包含适当的安全措施和错误处理机制。
API设计与规划
在开始编码前,我们需要设计API端点和数据结构:
API端点设计
GET /api/posts # 获取文章列表
POST /api/posts # 创建新文章
GET /api/posts/{id} # 获取特定文章
PUT /api/posts/{id} # 更新文章
DELETE /api/posts/{id} # 删除文章
POST /api/auth/login # 用户登录
POST /api/auth/register # 用户注册
GET /api/auth/profile # 获取用户资料
GET /api/comments # 获取评论
POST /api/comments # 创建评论
数据结构设计
用户 (users)
- id (主键)
- username (用户名)
- email (邮箱)
- password (密码哈希)
- created_at (创建时间)
文章 (posts)
- id (主键)
- title (标题)
- content (内容)
- author_id (作者ID)
- created_at (创建时间)
- updated_at (更新时间)
评论 (comments)
- id (主键)
- post_id (文章ID)
- user_id (用户ID)
- content (评论内容)
- created_at (创建时间)
环境设置与项目结构
首先设置项目结构和必要的配置文件:
项目结构
blog-api/
├── api/
│ ├── auth/
│ │ ├── login.php
│ │ └── register.php
│ ├── posts/
│ │ ├── index.php
│ │ └── [id]/
│ │ └── index.php
│ └── comments/
│ ├── index.php
│ └── [id]/
│ └── index.php
├── config/
│ ├── database.php
│ └── constants.php
├── models/
│ ├── User.php
│ ├── Post.php
│ └── Comment.php
├── utils/
│ ├── Database.php
│ ├── Auth.php
│ └── Response.php
└── .htaccess
数据库配置文件 (config/database.php)
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'blog_api');
define('DB_USER', 'root');
define('DB_PASS', 'password');
// 创建数据库连接
function getDBConnection() {
try {
$conn = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME,
DB_USER,
DB_PASS
);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $conn;
} catch(PDOException $e) {
// 记录错误日志
error_log("Database connection failed: " . $e->getMessage());
// 返回统一的错误响应
sendResponse(500, 'Database connection error');
exit;
}
}
?>
数据库设计与实现
创建数据库表和初始数据:
SQL表结构
<?php
// install.php - 数据库安装脚本
require_once 'config/database.php';
try {
$conn = getDBConnection();
// 创建用户表
$conn->exec("
CREATE TABLE IF NOT EXISTS 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
)
");
// 创建文章表
$conn->exec("
CREATE TABLE IF NOT EXISTS posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
author_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
)
");
// 创建评论表
$conn->exec("
CREATE TABLE IF NOT EXISTS comments (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT NOT NULL,
user_id INT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
");
echo "Database tables created successfully!";
} catch(PDOException $e) {
echo "Error creating tables: " . $e->getMessage();
}
?>
核心API实现
实现文章管理的核心API:
文章模型 (models/Post.php)
<?php
class Post {
private $conn;
private $table = 'posts';
// 文章属性
public $id;
public $title;
public $content;
public $author_id;
public $created_at;
public $updated_at;
public function __construct($db) {
$this->conn = $db;
}
// 获取所有文章
public function read() {
$query = "SELECT
p.id,
p.title,
p.content,
p.author_id,
u.username as author_name,
p.created_at,
p.updated_at
FROM " . $this->table . " p
LEFT JOIN users u ON p.author_id = u.id
ORDER BY p.created_at DESC";
$stmt = $this->conn->prepare($query);
$stmt->execute();
return $stmt;
}
// 获取单篇文章
public function read_single() {
$query = "SELECT
p.id,
p.title,
p.content,
p.author_id,
u.username as author_name,
p.created_at,
p.updated_at
FROM " . $this->table . " p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.id = ?
LIMIT 0,1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(1, $this->id);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if($row) {
$this->title = $row['title'];
$this->content = $row['content'];
$this->author_id = $row['author_id'];
$this->author_name = $row['author_name'];
$this->created_at = $row['created_at'];
$this->updated_at = $row['updated_at'];
return true;
}
return false;
}
// 创建文章
public function create() {
$query = "INSERT INTO " . $this->table . "
SET title = :title,
content = :content,
author_id = :author_id";
$stmt = $this->conn->prepare($query);
// 清理数据
$this->title = htmlspecialchars(strip_tags($this->title));
$this->content = htmlspecialchars(strip_tags($this->content));
$this->author_id = htmlspecialchars(strip_tags($this->author_id));
// 绑定参数
$stmt->bindParam(':title', $this->title);
$stmt->bindParam(':content', $this->content);
$stmt->bindParam(':author_id', $this->author_id);
if($stmt->execute()) {
return true;
}
return false;
}
// 更新文章
public function update() {
$query = "UPDATE " . $this->table . "
SET title = :title,
content = :content
WHERE id = :id";
$stmt = $this->conn->prepare($query);
// 清理数据
$this->title = htmlspecialchars(strip_tags($this->title));
$this->content = htmlspecialchars(strip_tags($this->content));
$this->id = htmlspecialchars(strip_tags($this->id));
// 绑定参数
$stmt->bindParam(':title', $this->title);
$stmt->bindParam(':content', $this->content);
$stmt->bindParam(':id', $this->id);
if($stmt->execute()) {
return true;
}
return false;
}
// 删除文章
public function delete() {
$query = "DELETE FROM " . $this->table . " WHERE id = :id";
$stmt = $this->conn->prepare($query);
// 清理数据
$this->id = htmlspecialchars(strip_tags($this->id));
// 绑定参数
$stmt->bindParam(':id', $this->id);
if($stmt->execute()) {
return true;
}
return false;
}
}
?>
文章API端点 (api/posts/index.php)
<?php
// 设置头部信息
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Access-Control-Allow-Headers,Content-Type,Access-Control-Allow-Methods, Authorization, X-Requested-With');
// 引入必要文件
require_once '../../config/database.php';
require_once '../../models/Post.php';
require_once '../../utils/Response.php';
// 获取请求方法
$method = $_SERVER['REQUEST_METHOD'];
// 处理预检请求
if ($method == 'OPTIONS') {
header('HTTP/1.1 200 OK');
exit;
}
// 实例化数据库连接和Post对象
$database = new Database();
$db = $database->getConnection();
$post = new Post($db);
// 根据请求方法处理
switch($method) {
case 'GET':
// 获取文章列表或单篇文章
if(isset($_GET['id'])) {
$post->id = $_GET['id'];
if($post->read_single()) {
$post_arr = array(
'id' => $post->id,
'title' => $post->title,
'content' => $post->content,
'author_id' => $post->author_id,
'author_name' => $post->author_name,
'created_at' => $post->created_at,
'updated_at' => $post->updated_at
);
Response::send(200, $post_arr);
} else {
Response::send(404, array('message' => 'Post not found'));
}
} else {
$stmt = $post->read();
$num = $stmt->rowCount();
if($num > 0) {
$posts_arr = array();
$posts_arr['data'] = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
extract($row);
$post_item = array(
'id' => $id,
'title' => $title,
'content' => $content,
'author_id' => $author_id,
'author_name' => $author_name,
'created_at' => $created_at,
'updated_at' => $updated_at
);
array_push($posts_arr['data'], $post_item);
}
Response::send(200, $posts_arr);
} else {
Response::send(404, array('message' => 'No posts found'));
}
}
break;
case 'POST':
// 创建新文章
$data = json_decode(file_get_contents("php://input"));
if(!empty($data->title) && !empty($data->content) && !empty($data->author_id)) {
$post->title = $data->title;
$post->content = $data->content;
$post->author_id = $data->author_id;
if($post->create()) {
Response::send(201, array('message' => 'Post created'));
} else {
Response::send(503, array('message' => 'Unable to create post'));
}
} else {
Response::send(400, array('message' => 'Missing required fields'));
}
break;
case 'PUT':
// 更新文章
$data = json_decode(file_get_contents("php://input"));
$post->id = $data->id;
if($post->read_single()) {
$post->title = $data->title ?? $post->title;
$post->content = $data->content ?? $post->content;
if($post->update()) {
Response::send(200, array('message' => 'Post updated'));
} else {
Response::send(503, array('message' => 'Unable to update post'));
}
} else {
Response::send(404, array('message' => 'Post not found'));
}
break;
case 'DELETE':
// 删除文章
$data = json_decode(file_get_contents("php://input"));
$post->id = $data->id;
if($post->read_single()) {
if($post->delete()) {
Response::send(200, array('message' => 'Post deleted'));
} else {
Response::send(503, array('message' => 'Unable to delete post'));
}
} else {
Response::send(404, array('message' => 'Post not found'));
}
break;
default:
Response::send(405, array('message' => 'Method not allowed'));
break;
}
?>
安全性与错误处理
实现API的安全措施和统一的错误处理:
响应处理工具 (utils/Response.php)
<?php
class Response {
public static function send($status_code, $data) {
http_response_code($status_code);
echo json_encode($data);
exit;
}
public static function error($status_code, $message) {
self::send($status_code, array(
'error' => array(
'status' => $status_code,
'message' => $message
)
));
}
}
?>
认证中间件 (utils/Auth.php)
<?php
class Auth {
private $conn;
private $table = 'users';
public $id;
public $username;
public $email;
public $password;
public function __construct($db) {
$this->conn = $db;
}
// 用户注册
public function register() {
$query = "INSERT INTO " . $this->table . "
SET username = :username,
email = :email,
password = :password";
$stmt = $this->conn->prepare($query);
// 清理数据
$this->username = htmlspecialchars(strip_tags($this->username));
$this->email = htmlspecialchars(strip_tags($this->email));
// 哈希密码
$this->password = password_hash($this->password, PASSWORD_DEFAULT);
// 绑定参数
$stmt->bindParam(':username', $this->username);
$stmt->bindParam(':email', $this->email);
$stmt->bindParam(':password', $this->password);
if($stmt->execute()) {
return true;
}
return false;
}
// 用户登录
public function login() {
$query = "SELECT id, username, password
FROM " . $this->table . "
WHERE username = :username
LIMIT 0,1";
$stmt = $this->conn->prepare($query);
// 清理数据
$this->username = htmlspecialchars(strip_tags($this->username));
// 绑定参数
$stmt->bindParam(':username', $this->username);
$stmt->execute();
if($stmt->rowCount() > 0) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// 验证密码
if(password_verify($this->password, $row['password'])) {
$this->id = $row['id'];
$this->username = $row['username'];
return true;
}
}
return false;
}
// 验证JWT令牌(简化版)
public static function validateToken($token) {
// 在实际应用中,这里应该验证JWT令牌
// 这里使用简化版本进行演示
return !empty($token);
}
}
?>
总结与最佳实践
通过本教程,我们创建了一个功能完整的PHP RESTful API博客系统。关键要点包括:
- 使用PDO进行安全的数据库操作,防止SQL注入
- 实现统一的响应格式和错误处理机制
- 设计合理的RESTful API端点结构
- 使用密码哈希保护用户凭证
- 实现适当的数据验证和清理
进一步改进建议
- 实现完整的JWT认证系统
- 添加API速率限制防止滥用
- 实现数据分页提高性能
- 添加API文档(OpenAPI/Swagger)
- 实现单元测试和集成测试
- 添加缓存机制(Redis/Memcached)
这个API可以作为任何前端应用(如React、Vue或移动应用)的后端,提供完整的内容管理功能。通过遵循RESTful原则和安全性最佳实践,您可以构建出健壮、可扩展的Web服务。

