发布日期: 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服务。