前言
在现代Web开发中,API接口已成为前后端分离架构的核心。PHP作为最流行的服务器端脚本语言之一,以其高效、灵活的特性在API开发中占据重要地位。本教程将带你从零开始,使用PHP构建一个高性能的RESTful API,并集成JWT身份验证机制。
一、环境准备与项目结构
1.1 开发环境配置
首先确保你的系统已安装以下组件:
- PHP 7.4或更高版本(需开启openssl扩展)
- Composer(PHP依赖管理工具)
- Apache/Nginx Web服务器
- MySQL数据库
1.2 创建项目并初始化
通过Composer初始化项目并安装必要依赖:
mkdir php-api-project cd php-api-project composer init composer require firebase/php-jwt composer require vlucas/phpdotenv
1.3 项目目录结构
php-api-project/ ├── config/ │ └── database.php # 数据库配置 ├── controllers/ # 控制器目录 ├── models/ # 模型目录 ├── utils/ # 工具类目录 ├── vendor/ # Composer依赖 ├── .env # 环境变量 ├── .htaccess # Apache重写规则 └── index.php # 入口文件
二、数据库设计与连接
2.1 创建数据库表
我们创建一个简单的用户系统和文章管理系统:
-- 用户表 CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 文章表 CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );
2.2 数据库配置文件
创建数据库配置文件,使用环境变量管理敏感信息:
// config/database.php <?php class Database { private $host; private $db_name; private $username; private $password; public $conn; public function __construct() { $this->host = getenv('DB_HOST'); $this->db_name = getenv('DB_NAME'); $this->username = getenv('DB_USER'); $this->password = getenv('DB_PASS'); } public function getConnection() { $this->conn = null; try { $this->conn = new PDO( "mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password ); $this->conn->exec("set names utf8"); } catch(PDOException $exception) { echo "Connection error: " . $exception->getMessage(); } return $this->conn; } } ?>
2.3 环境变量配置
// .env文件 DB_HOST=localhost DB_NAME=api_database DB_USER=root DB_PASS=password JWT_SECRET=your_super_secret_key_here
三、实现JWT身份验证
3.1 JWT工具类
创建JWT处理工具类,用于生成和验证Token:
// utils/JWTHandler.php <?php require_once 'vendor/autoload.php'; use FirebaseJWTJWT; use FirebaseJWTKey; class JWTHandler { private $secretKey; public function __construct() { $this->secretKey = getenv('JWT_SECRET'); } public function generateToken($userId, $username) { $issuedAt = time(); $expire = $issuedAt + (60 * 60); // 1小时有效期 $payload = array( "iat" => $issuedAt, "exp" => $expire, "iss" => "php-api-server", "data" => array( "id" => $userId, "username" => $username ) ); return JWT::encode($payload, $this->secretKey, 'HS256'); } public function validateToken($token) { try { $decoded = JWT::decode($token, new Key($this->secretKey, 'HS256')); return (array) $decoded->data; } catch (Exception $e) { return false; } } } ?>
3.2 用户认证控制器
实现用户注册和登录接口:
// controllers/AuthController.php <?php require_once 'config/database.php'; require_once 'utils/JWTHandler.php'; class AuthController { private $db; private $jwtHandler; public function __construct() { $database = new Database(); $this->db = $database->getConnection(); $this->jwtHandler = new JWTHandler(); } public function register($data) { // 验证输入数据 if (!isset($data->username) || !isset($data->email) || !isset($data->password)) { return array("message" => "缺少必要字段", "status" => 400); } // 检查用户是否已存在 $query = "SELECT id FROM users WHERE username = :username OR email = :email"; $stmt = $this->db->prepare($query); $stmt->bindParam(":username", $data->username); $stmt->bindParam(":email", $data->email); $stmt->execute(); if ($stmt->rowCount() > 0) { return array("message" => "用户名或邮箱已存在", "status" => 409); } // 创建用户 $query = "INSERT INTO users SET username=:username, email=:email, password=:password"; $stmt = $this->db->prepare($query); $stmt->bindParam(":username", $data->username); $stmt->bindParam(":email", $data->email); // 哈希密码 $password_hash = password_hash($data->password, PASSWORD_BCRYPT); $stmt->bindParam(":password", $password_hash); if ($stmt->execute()) { return array( "message" => "用户注册成功", "status" => 201, "user_id" => $this->db->lastInsertId() ); } else { return array("message" => "注册失败", "status" => 500); } } public function login($data) { // 验证输入 if (!isset($data->username) || !isset($data->password)) { return array("message" => "请输入用户名和密码", "status" => 400); } // 查询用户 $query = "SELECT id, username, password FROM users WHERE username = :username"; $stmt = $this->db->prepare($query); $stmt->bindParam(":username", $data->username); $stmt->execute(); if ($stmt->rowCount() == 1) { $row = $stmt->fetch(PDO::FETCH_ASSOC); $id = $row['id']; $username = $row['username']; $password = $row['password']; // 验证密码 if (password_verify($data->password, $password)) { // 生成JWT令牌 $token = $this->jwtHandler->generateToken($id, $username); return array( "message" => "登录成功", "status" => 200, "token" => $token ); } } return array("message" => "登录失败,用户名或密码错误", "status" => 401); } } ?>
四、实现RESTful API接口
4.1 文章控制器
实现文章的增删改查接口:
// controllers/PostController.php <?php require_once 'config/database.php'; require_once 'utils/JWTHandler.php'; class PostController { private $db; private $table_name = "posts"; public function __construct() { $database = new Database(); $this->db = $database->getConnection(); } // 获取所有文章 public function read() { $query = "SELECT p.id, p.title, p.content, p.created_at, u.username FROM " . $this->table_name . " p LEFT JOIN users u ON p.user_id = u.id ORDER BY p.created_at DESC"; $stmt = $this->db->prepare($query); $stmt->execute(); $posts = array(); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { array_push($posts, $row); } return array("data" => $posts, "status" => 200); } // 创建文章 public function create($data, $user_id) { if (!isset($data->title) || !isset($data->content)) { return array("message" => "标题和内容不能为空", "status" => 400); } $query = "INSERT INTO " . $this->table_name . " SET title=:title, content=:content, user_id=:user_id"; $stmt = $this->db->prepare($query); $stmt->bindParam(":title", $data->title); $stmt->bindParam(":content", $data->content); $stmt->bindParam(":user_id", $user_id); if ($stmt->execute()) { return array( "message" => "文章创建成功", "status" => 201, "post_id" => $this->db->lastInsertId() ); } else { return array("message" => "创建文章失败", "status" => 500); } } // 更新文章 public function update($id, $data, $user_id) { // 先检查文章是否存在且属于当前用户 $query = "SELECT id FROM " . $this->table_name . " WHERE id = :id AND user_id = :user_id"; $stmt = $this->db->prepare($query); $stmt->bindParam(":id", $id); $stmt->bindParam(":user_id", $user_id); $stmt->execute(); if ($stmt->rowCount() == 0) { return array("message" => "文章不存在或无权操作", "status" => 404); } $query = "UPDATE " . $this->table_name . " SET title=:title, content=:content WHERE id=:id"; $stmt = $this->db->prepare($query); $stmt->bindParam(":title", $data->title); $stmt->bindParam(":content", $data->content); $stmt->bindParam(":id", $id); if ($stmt->execute()) { return array("message" => "文章更新成功", "status" => 200); } else { return array("message" => "更新文章失败", "status" => 500); } } // 删除文章 public function delete($id, $user_id) { // 先检查文章是否存在且属于当前用户 $query = "SELECT id FROM " . $this->table_name . " WHERE id = :id AND user_id = :user_id"; $stmt = $this->db->prepare($query); $stmt->bindParam(":id", $id); $stmt->bindParam(":user_id", $user_id); $stmt->execute(); if ($stmt->rowCount() == 0) { return array("message" => "文章不存在或无权操作", "status" => 404); } $query = "DELETE FROM " . $this->table_name . " WHERE id = :id"; $stmt = $this->db->prepare($query); $stmt->bindParam(":id", $id); if ($stmt->execute()) { return array("message" => "文章删除成功", "status" => 200); } else { return array("message" => "删除文章失败", "status" => 500); } } } ?>
4.2 API路由与入口文件
创建统一的入口文件处理所有API请求:
// index.php <?php // 允许跨域请求 header("Access-Control-Allow-Origin: *"); header("Content-Type: application/json; charset=UTF-8"); header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS"); header("Access-Control-Max-Age: 3600"); header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); // 处理预检请求 if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { exit(0); } // 加载环境变量和自动加载 require_once 'vendor/autoload.php'; $dotenv = DotenvDotenv::createImmutable(__DIR__); $dotenv->load(); // 获取请求方法和路径 $requestMethod = $_SERVER["REQUEST_METHOD"]; $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $pathSegments = explode('/', trim($path, '/')); // 路由分发 switch ($pathSegments[0]) { case 'auth': require_once 'controllers/AuthController.php'; $auth = new AuthController(); $data = json_decode(file_get_contents("php://input")); if ($requestMethod == 'POST' && $pathSegments[1] == 'register') { $result = $auth->register($data); http_response_code($result['status']); echo json_encode($result); } elseif ($requestMethod == 'POST' && $pathSegments[1] == 'login') { $result = $auth->login($data); http_response_code($result['status']); echo json_encode($result); } break; case 'posts': require_once 'controllers/PostController.php'; require_once 'utils/JWTHandler.php'; $post = new PostController(); // JWT验证 $headers = apache_request_headers(); $token = isset($headers['Authorization']) ? str_replace('Bearer ', '', $headers['Authorization']) : ''; $jwt = new JWTHandler(); $userData = $jwt->validateToken($token); if (!$userData && $requestMethod != 'GET') { http_response_code(401); echo json_encode(array("message" => "访问被拒绝,需要有效令牌")); exit; } if ($requestMethod == 'GET') { $result = $post->read(); http_response_code($result['status']); echo json_encode($result); } elseif ($requestMethod == 'POST') { $data = json_decode(file_get_contents("php://input")); $result = $post->create($data, $userData['id']); http_response_code($result['status']); echo json_encode($result); } elseif ($requestMethod == 'PUT' && isset($pathSegments[1])) { $data = json_decode(file_get_contents("php://input")); $result = $post->update($pathSegments[1], $data, $userData['id']); http_response_code($result['status']); echo json_encode($result); } elseif ($requestMethod == 'DELETE' && isset($pathSegments[1])) { $result = $post->delete($pathSegments[1], $userData['id']); http_response_code($result['status']); echo json_encode($result); } break; default: http_response_code(404); echo json_encode(array("message" => "接口不存在")); break; } ?>
五、API测试与部署
5.1 使用Postman测试API
使用Postman或其他API测试工具测试我们的接口:
- 注册用户:POST /auth/register
{"username":"testuser","email":"test@example.com","password":"mypassword"}
- 用户登录:POST /auth/login
{"username":"testuser","password":"mypassword"}
- 获取文章列表:GET /posts
- 创建文章:POST /posts (需在Header中添加Authorization: Bearer [token])
{"title":"我的第一篇文章","content":"这是文章内容..."}
5.2 部署到生产环境
部署注意事项:
- 确保生产环境的PHP版本符合要求
- 设置正确的文件权限
- 配置Web服务器重写规则(.htaccess用于Apache)
- 使用环境变量管理敏感信息
- 启用HTTPS加密传输
5.3 .htaccess配置
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [QSA,L]
结语
通过本教程,我们完成了一个完整的PHP API开发项目,包含了用户认证、JWT令牌管理、RESTful接口设计等核心功能。这个项目可以作为开发更复杂API系统的基础框架。
在实际项目中,你还可以进一步扩展功能,如添加API速率限制、数据验证、更复杂的权限管理系统、API文档生成等。PHP虽然是一门历史悠久的语言,但在现代Web开发中仍然具有强大的生命力和广泛的应用场景。