作者:王后端工程师 • 发布时间:2023年11月20日
引言:现代API开发的重要性
在当今的Web开发中,API(应用程序编程接口)已成为不同系统之间数据交换的核心。无论是移动应用、前端框架还是微服务架构,都需要高效、安全的API支持。PHP作为最流行的服务器端语言之一,以其简洁的语法和强大的功能,成为构建API的理想选择。
项目概述
本文将指导您构建一个完整的任务管理API,包含以下功能:
- RESTful风格的API设计
- JWT(JSON Web Token)身份验证
- 数据库设计与优化
- 输入验证与数据过滤
- API速率限制
- 错误处理与日志记录
- API文档生成
环境准备与技术栈
我们需要以下环境和技术:
# 使用Composer安装依赖
composer require firebase/php-jwt
composer require vlucas/phpdotenv
composer require slim/slim:"4.*"
composer require slim/psr7
composer require slim/http
composer require nyholm/psr7
项目结构:
task-api/
├── config/
│ └── database.php
├── controllers/
│ ├── AuthController.php
│ └── TaskController.php
├── middlewares/
│ ├── AuthMiddleware.php
│ └── RateLimitMiddleware.php
├── models/
│ ├── User.php
│ └── Task.php
├── utils/
│ ├── Response.php
│ └── Validator.php
├── vendor/
├── .env
├── index.php
└── composer.json
数据库设计
首先创建数据库和表结构:
CREATE DATABASE task_manager;
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,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
status ENUM('pending', 'in_progress', 'completed') DEFAULT 'pending',
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
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
);
CREATE TABLE api_limits (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
endpoint VARCHAR(100) NOT NULL,
request_count INT DEFAULT 0,
last_request TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
核心代码实现
1. 环境配置与数据库连接
创建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 . ";charset=utf8",
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch(PDOException $exception) {
echo "Connection error: " . $exception->getMessage();
}
return $this->conn;
}
}
?>
2. 用户模型 (models/User.php)
<?php
class User {
private $conn;
private $table_name = "users";
public $id;
public $username;
public $email;
public $password;
public $created_at;
public $updated_at;
public function __construct($db) {
$this->conn = $db;
}
public function create() {
$query = "INSERT INTO " . $this->table_name . "
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_BCRYPT);
$stmt->bindParam(":username", $this->username);
$stmt->bindParam(":email", $this->email);
$stmt->bindParam(":password", $this->password);
if($stmt->execute()) {
return true;
}
return false;
}
public function emailExists() {
$query = "SELECT id, username, password
FROM " . $this->table_name . "
WHERE email = ?
LIMIT 0,1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(1, $this->email);
$stmt->execute();
$num = $stmt->rowCount();
if($num > 0) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$this->id = $row['id'];
$this->username = $row['username'];
$this->password = $row['password'];
return true;
}
return false;
}
public function getById($id) {
$query = "SELECT id, username, email, created_at
FROM " . $this->table_name . "
WHERE id = ?
LIMIT 0,1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(1, $id);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
?>
3. JWT工具类 (utils/JWTAuth.php)
<?php
require_once 'vendor/autoload.php';
use FirebaseJWTJWT;
use FirebaseJWTKey;
class JWTAuth {
private static $secret_key;
private static $algorithm = 'HS256';
public static function init() {
self::$secret_key = getenv('JWT_SECRET');
}
public static function generateToken($user_id, $username) {
$issued_at = time();
$expiration_time = $issued_at + (60 * 60); // 有效期为1小时
$payload = array(
"iat" => $issued_at,
"exp" => $expiration_time,
"iss" => getenv('JWT_ISSUER'),
"data" => array(
"user_id" => $user_id,
"username" => $username
)
);
return JWT::encode($payload, self::$secret_key, self::$algorithm);
}
public static function validateToken($token) {
try {
$decoded = JWT::decode($token, new Key(self::$secret_key, self::$algorithm));
return (array) $decoded->data;
} catch (Exception $e) {
return false;
}
}
public static function getBearerToken() {
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER['Authorization']);
} elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$headers = trim($_SERVER['HTTP_AUTHORIZATION']);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
$requestHeaders = array_combine(
array_map('ucwords', array_keys($requestHeaders)),
array_values($requestHeaders)
);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
if (!empty($headers) && preg_match('/Bearers(S+)/', $headers, $matches)) {
return $matches[1];
}
return null;
}
}
JWTAuth::init();
?>
4. 认证中间件 (middlewares/AuthMiddleware.php)
<?php
require_once '../utils/JWTAuth.php';
class AuthMiddleware {
public function __invoke($request, $handler) {
$token = JWTAuth::getBearerToken();
if (!$token) {
return Response::error('访问被拒绝,未提供令牌', 401);
}
$user_data = JWTAuth::validateToken($token);
if (!$user_data) {
return Response::error('无效或过期的令牌', 401);
}
// 将用户信息添加到请求属性中
$request = $request->withAttribute('user_id', $user_data['user_id']);
$request = $request->withAttribute('username', $user_data['username']);
return $handler->handle($request);
}
}
?>
5. 速率限制中间件 (middlewares/RateLimitMiddleware.php)
<?php
class RateLimitMiddleware {
private $db;
private $limit = 100; // 每小时最大请求数
private $window = 3600; // 时间窗口(秒)
public function __construct($db) {
$this->db = $db;
}
public function __invoke($request, $handler) {
$user_id = $request->getAttribute('user_id');
$path = $request->getUri()->getPath();
// 检查速率限制
$query = "SELECT request_count, last_request
FROM api_limits
WHERE user_id = :user_id AND endpoint = :endpoint";
$stmt = $this->db->prepare($query);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':endpoint', $path);
$stmt->execute();
$limit_data = $stmt->fetch();
$now = time();
$current_time = date('Y-m-d H:i:s', $now);
if ($limit_data) {
$last_request = strtotime($limit_data['last_request']);
$time_diff = $now - $last_request;
if ($time_diff > $this->window) {
// 重置计数器
$this->resetCounter($user_id, $path);
$request_count = 1;
} else {
$request_count = $limit_data['request_count'] + 1;
if ($request_count > $this->limit) {
return Response::error('速率限制 exceeded', 429);
}
$this->updateCounter($user_id, $path, $request_count, $current_time);
}
} else {
// 创建新的计数器
$this->createCounter($user_id, $path, $current_time);
$request_count = 1;
}
// 添加剩余请求数到响应头
$response = $handler->handle($request);
return $response->withHeader('X-RateLimit-Limit', $this->limit)
->withHeader('X-RateLimit-Remaining', $this->limit - $request_count);
}
private function resetCounter($user_id, $endpoint) {
$query = "UPDATE api_limits
SET request_count = 1, last_request = NOW()
WHERE user_id = :user_id AND endpoint = :endpoint";
$stmt = $this->db->prepare($query);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':endpoint', $endpoint);
$stmt->execute();
}
private function updateCounter($user_id, $endpoint, $count, $time) {
$query = "UPDATE api_limits
SET request_count = :count, last_request = :time
WHERE user_id = :user_id AND endpoint = :endpoint";
$stmt = $this->db->prepare($query);
$stmt->bindParam(':count', $count);
$stmt->bindParam(':time', $time);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':endpoint', $endpoint);
$stmt->execute();
}
private function createCounter($user_id, $endpoint, $time) {
$query = "INSERT INTO api_limits (user_id, endpoint, request_count, last_request)
VALUES (:user_id, :endpoint, 1, :time)";
$stmt = $this->db->prepare($query);
$stmt->bindParam(':user_id', $user_id);
$stmt->bindParam(':endpoint', $endpoint);
$stmt->bindParam(':time', $time);
$stmt->execute();
}
}
?>
6. 响应工具类 (utils/Response.php)
<?php
class Response {
public static function json($data, $status = 200) {
header('Content-Type: application/json');
http_response_code($status);
echo json_encode($data);
exit;
}
public static function success($message, $data = null, $status = 200) {
self::json([
'success' => true,
'message' => $message,
'data' => $data
], $status);
}
public static function error($message, $status = 400, $details = null) {
self::json([
'success' => false,
'message' => $message,
'details' => $details
], $status);
}
public static function paginate($data, $total, $page, $per_page) {
$total_pages = ceil($total / $per_page);
self::json([
'success' => true,
'data' => $data,
'pagination' => [
'total' => $total,
'per_page' => $per_page,
'current_page' => $page,
'total_pages' => $total_pages,
'has_more' => $page < $total_pages
]
]);
}
}
?>
7. 认证控制器 (controllers/AuthController.php)
<?php
require_once '../models/User.php';
require_once '../utils/JWTAuth.php';
require_once '../utils/Response.php';
require_once '../utils/Validator.php';
class AuthController {
private $db;
private $user;
public function __construct($db) {
$this->db = $db;
$this->user = new User($db);
}
public function register($request) {
$data = json_decode($request->getBody(), true);
// 验证输入
$errors = Validator::validate($data, [
'username' => 'required|min:3|max:50',
'email' => 'required|email',
'password' => 'required|min:6'
]);
if (!empty($errors)) {
return Response::error('验证失败', 422, $errors);
}
// 检查邮箱是否已存在
$this->user->email = $data['email'];
if ($this->user->emailExists()) {
return Response::error('邮箱已被注册', 409);
}
// 创建用户
$this->user->username = $data['username'];
$this->user->password = $data['password'];
if ($this->user->create()) {
return Response::success('用户注册成功', [
'user_id' => $this->user->id
], 201);
}
return Response::error('用户注册失败', 500);
}
public function login($request) {
$data = json_decode($request->getBody(), true);
$errors = Validator::validate($data, [
'email' => 'required|email',
'password' => 'required'
]);
if (!empty($errors)) {
return Response::error('验证失败', 422, $errors);
}
$this->user->email = $data['email'];
if (!$this->user->emailExists()) {
return Response::error('邮箱或密码错误', 401);
}
if (password_verify($data['password'], $this->user->password)) {
$token = JWTAuth::generateToken($this->user->id, $this->user->username);
return Response::success('登录成功', [
'token' => $token,
'user' => [
'id' => $this->user->id,
'username' => $this->user->username,
'email' => $data['email']
]
]);
}
return Response::error('邮箱或密码错误', 401);
}
public function profile($request) {
$user_id = $request->getAttribute('user_id');
$user_data = $this->user->getById($user_id);
if ($user_data) {
return Response::success('用户信息获取成功', $user_data);
}
return Response::error('用户不存在', 404);
}
}
?>
8. 主入口文件 (index.php)
<?php
require 'vendor/autoload.php';
require_once 'config/database.php';
require_once 'utils/Response.php';
use PsrHttpMessageResponseInterface as Response;
use PsrHttpMessageServerRequestInterface as Request;
use SlimFactoryAppFactory;
// 加载环境变量
$dotenv = DotenvDotenv::createImmutable(__DIR__);
$dotenv->load();
// 初始化数据库
$database = new Database();
$db = $database->getConnection();
// 创建Slim应用
$app = AppFactory::create();
// 解析JSON body
$app->addBodyParsingMiddleware();
// 错误处理中间件
$app->addErrorMiddleware(true, true, true);
// 路由:用户认证
$app->post('/api/register', function (Request $request, Response $response) use ($db) {
$authController = new AuthController($db);
return $authController->register($request);
});
$app->post('/api/login', function (Request $request, Response $response) use ($db) {
$authController = new AuthController($db);
return $authController->login($request);
});
// 需要认证的路由
$app->get('/api/profile', function (Request $request, Response $response) use ($db) {
$authController = new AuthController($db);
return $authController->profile($request);
})->add(new AuthMiddleware());
// 任务相关路由(需要认证和速率限制)
$app->group('/api/tasks', function ($group) use ($db) {
$taskController = new TaskController($db);
$group->get('', function (Request $request, Response $response) use ($taskController) {
return $taskController->getAll($request);
});
$group->post('', function (Request $request, Response $response) use ($taskController) {
return $taskController->create($request);
});
$group->get('/{id}', function (Request $request, Response $response, $args) use ($taskController) {
return $taskController->getById($request, $args['id']);
});
$group->put('/{id}', function (Request $request, Response $response, $args) use ($taskController) {
return $taskController->update($request, $args['id']);
});
$group->delete('/{id}', function (Request $request, Response $response, $args) use ($taskController) {
return $taskController->delete($request, $args['id']);
});
})->add(new AuthMiddleware())->add(new RateLimitMiddleware($db));
// 运行应用
$app->run();
?>
环境配置
创建.env文件:
DB_HOST=localhost
DB_NAME=task_manager
DB_USER=your_username
DB_PASS=your_password
JWT_SECRET=your_super_secret_jwt_key_here
JWT_ISSUER=your_api_domain
API测试与使用
使用curl或Postman测试API:
1. 用户注册
curl -X POST http://localhost/api/register
-H "Content-Type: application/json"
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "password123"
}'
2. 用户登录
curl -X POST http://localhost/api/login
-H "Content-Type: application/json"
-d '{
"email": "test@example.com",
"password": "password123"
}'
3. 获取用户信息(需要认证)
curl -X GET http://localhost/api/profile
-H "Authorization: Bearer your_jwt_token_here"
性能优化建议
- 使用OPcache缓存PHP字节码
- 数据库查询优化,添加合适的索引
- 使用Redis缓存频繁访问的数据
- 启用HTTP压缩减少响应大小
- 使用CDN分发静态资源
- 实施数据库连接池
安全最佳实践
- 始终使用HTTPS加密通信
- 验证和过滤所有用户输入
- 使用预处理语句防止SQL注入
- 设置适当的CORS策略
- 定期更新依赖库
- 实施适当的错误处理,避免信息泄露
总结
通过本教程,我们构建了一个完整的PHP RESTful API,包含了现代API开发的核心功能:JWT身份验证、速率限制、输入验证和错误处理。这个API可以作为各种应用程序的后端基础,无论是Web应用、移动应用还是其他服务。
PHP在现代Web开发中仍然具有强大的竞争力,特别是结合了合适的框架和设计模式后,能够构建出高性能、安全可靠的API服务。