2025后端面试题(拟)-03(PHP进阶篇)
如何使用 PHP 开发 RESTful API?
原生 PHP 实现
// 简单 RESTful 示例
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
$method = $_SERVER['REQUEST_METHOD'];
$request = explode('/', trim($_SERVER['PATH_INFO'], '/'));
switch ($method) {
case 'GET':
// 获取资源
echo json_encode(['data' => '获取资源']);
break;
case 'POST':
// 创建资源
$input = json_decode(file_get_contents('php://input'), true);
echo json_encode(['data' => $input]);
break;
case 'PUT':
// 更新资源
break;
case 'DELETE':
// 删除资源
break;
default:
http_response_code(405);
echo json_encode(['error' => '方法不允许']);
}
使用框架 (推荐)
Laravel 实现
// routes/api.php
Route::apiResource('products', ProductController::class);
// app/Http/Controllers/ProductController.php
class ProductController extends Controller
{
public function index()
{
return response()->json(Product::all());
}
public function store(Request $request)
{
$product = Product::create($request->all());
return response()->json($product, 201);
}
// 其他方法...
}
hyperf 实现
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use App\Model\User;
#[Controller(prefix: "/users")]
class UserController
{
#[RequestMapping(path: "", methods: "get")]
public function index(RequestInterface $request, ResponseInterface $response)
{
$users = User::all();
return $response->json($users);
}
#[RequestMapping(path: "{id:\d+}", methods: "get")]
public function show($id, ResponseInterface $response)
{
$user = User::find($id);
return $response->json($user);
}
#[RequestMapping(path: "", methods: "post")]
public function store(RequestInterface $request, ResponseInterface $response)
{
$user = User::create($request->all());
return $response->json($user)->withStatus(201);
}
#[RequestMapping(path: "{id:\d+}", methods: "put")]
public function update($id, RequestInterface $request, ResponseInterface $response)
{
$user = User::findOrFail($id);
$user->update($request->all());
return $response->json($user);
}
#[RequestMapping(path: "{id:\d+}", methods: "delete")]
public function destroy($id, ResponseInterface $response)
{
User::findOrFail($id)->delete();
return $response->json(null)->withStatus(204);
}
}
3. RESTful 最佳实践
URL 设计
- 使用名词而非动词
/products
而非/getProducts
- 复数形式
/users
而非/user
- 嵌套资源
/users/123/posts
- 使用名词而非动词
HTTP 方法
- GET - 获取资源
- POST - 创建资源
- PUT/PATCH - 更新资源
- DELETE - 删除资源
状态码
- 200 OK - 成功
- 201 Created - 资源创建成功
- 400 Bad Request - 客户端错误
- 401 Unauthorized - 未授权
- 404 Not Found - 资源不存在
- 500 Internal Server Error - 服务器错误
数据格式
- 使用 JSON 作为主要数据格式
- 统一响应结构:
{ "data": {}, "error": null }
版本控制
- URL 中
/v1/products
- 请求头
Accept: application/vnd.company.api.v1+json
- URL 中
总结
PHP 开发 RESTful API 有多种实现方式,从原生 PHP 到现代框架如 Laravel、Slim 等。选择适合项目规模和团队熟悉度的方案,遵循 REST 原则,并注意安全性和性能优化,可以构建出高质量的 API 服务。
对于新项目,我强烈推荐使用框架,它们提供了许多开箱即用的功能,能显著提高开发效率和代码质量。
请解释 php如何使用 JWT(JSON Web Tokens)进行身份验证。
JWT(JSON Web Tokens)是一种流行的无状态身份验证机制,特别适合RESTful API。下面我将详细介绍如何在PHP中实现JWT身份验证。
1. JWT基础概念
JWT由三部分组成:
- Header:包含令牌类型和签名算法
- Payload:包含声明(claims),如用户ID、过期时间等
- Signature:用于验证令牌未被篡改
2. PHP实现步骤
安装依赖库
推荐使用firebase/php-jwt
库:
composer require firebase/php-jwt
生成JWT令牌
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// 密钥,应该存储在安全的地方
$secretKey = 'your-secret-key';
// 用户登录成功后生成令牌
function generateJWT($userId) {
global $secretKey;
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // 1小时后过期
$payload = [
'iat' => $issuedAt, // 签发时间
'exp' => $expirationTime, // 过期时间
'sub' => $userId // 用户ID
];
return JWT::encode($payload, $secretKey, 'HS256');
}
验证JWT令牌
function validateJWT($jwt) {
global $secretKey;
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
return (array) $decoded;
} catch (Exception $e) {
// 处理各种异常情况
return false;
}
}
实际使用示例
登录并返回令牌:
// 假设用户认证成功
$userId = 123;
$token = generateJWT($userId);
echo json_encode(['token' => $token]);
保护API端点:
// 获取Authorization头
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$jwt = $matches[1];
$decoded = validateJWT($jwt);
if ($decoded) {
// 令牌有效,继续处理请求
$userId = $decoded['sub'];
// 你的业务逻辑...
} else {
http_response_code(401);
echo json_encode(['error' => 'Invalid or expired token']);
exit;
}
} else {
http_response_code(401);
echo json_encode(['error' => 'Token not found']);
exit;
}
3. 安全最佳实践
- 使用HTTPS:始终通过HTTPS传输JWT
- 设置合理过期时间:通常1-24小时
- 密钥安全:使用强密钥并妥善保管
- 令牌刷新:实现刷新令牌机制
- 敏感操作验证:重要操作要求重新认证
- 黑名单:实现令牌撤销机制(可选)
4. 高级用法
自定义声明
可以在payload中添加自定义声明:
$payload = [
'iat' => time(),
'exp' => time() + 3600,
'sub' => $userId,
'role' => 'admin', // 自定义声明
'custom' => 'data' // 更多自定义数据
];
使用RS256算法(非对称加密)
// 生成令牌
$privateKey = file_get_contents('/path/to/private.key');
$token = JWT::encode($payload, $privateKey, 'RS256');
// 验证令牌
$publicKey = file_get_contents('/path/to/public.key');
$decoded = JWT::decode($token, new Key($publicKey, 'RS256'));
通过以上方法,你可以在PHP应用中实现安全可靠的JWT身份验证机制。
如何处理 API 的分页和缓存?
分页处理
基本分页实现
// 获取分页参数
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 10;
// 确保最小值
$page = max($page, 1);
$perPage = max($perPage, 1);
$perPage = min($perPage, 100); // 限制每页最大数量
// 计算偏移量
$offset = ($page - 1) * $perPage;
// 数据库查询
$items = $db->query("SELECT * FROM items LIMIT $offset, $perPage");
$total = $db->query("SELECT COUNT(*) FROM items")->fetchColumn();
// 构建响应
$response = [
'data' => $items,
'pagination' => [
'total' => $total,
'per_page' => $perPage,
'current_page' => $page,
'last_page' => ceil($total / $perPage),
]
];
最佳实践
- 使用标准参数名:通常使用
page
和per_page
- 设置合理的默认值:如每页10-20条记录
- 限制最大每页数量:防止性能问题
- 提供元数据:包括总记录数、总页数等
- 使用链接头:遵循HATEOAS原则
// 添加分页链接头
header("Link: <{$baseUrl}?page=".($page+1).">; rel=\"next\"");
header("Link: <{$baseUrl}?page=".($page-1).">; rel=\"prev\"");
缓存处理
基本缓存实现
// 使用文件缓存示例
function getCachedData($key, $callback, $ttl = 3600) {
$cacheDir = __DIR__ . '/cache/';
$cacheFile = $cacheDir . md5($key) . '.cache';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
return unserialize(file_get_contents($cacheFile));
}
$data = call_user_func($callback);
file_put_contents($cacheFile, serialize($data));
return $data;
}
// 使用示例
$data = getCachedData('api_items_page_' . $page, function() use ($db, $offset, $perPage) {
return $db->query("SELECT * FROM items LIMIT $offset, $perPage")->fetchAll();
}, 300); // 5分钟缓存
高级缓存策略
- 使用Redis/Memcached
// Redis缓存示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = 'api:items:page:' . $page;
if ($redis->exists($cacheKey)) {
return json_decode($redis->get($cacheKey), true);
}
// 生成数据
$data = generateData();
// 存储到Redis,设置1小时过期
$redis->setex($cacheKey, 3600, json_encode($data));
return $data;
- HTTP缓存头
// 设置缓存头
header("Cache-Control: public, max-age=3600"); // 1小时缓存
header("Expires: " . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
header("Last-Modified: " . gmdate('D, d M Y H:i:s', filemtime($dataFile)) . ' GMT');
header("ETag: " . md5_file($dataFile));
- 条件GET请求处理
// 检查ETag或Last-Modified
$etag = md5(json_encode($data));
header("ETag: $etag");
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
header('HTTP/1.1 304 Not Modified');
exit;
}
缓存失效策略
- 基于时间失效:设置TTL(Time To Live)
- 基于事件失效:当数据变更时清除相关缓存
- 标签缓存:为相关缓存打标签,可以批量清除
综合示例
// 综合分页和缓存的API端点示例
function getItems($page = 1, $perPage = 10) {
// 生成缓存键
$cacheKey = "api_items_{$page}_{$perPage}";
// 尝试从缓存获取
$cache = new RedisCache(); // 假设有Redis缓存实现
if ($data = $cache->get($cacheKey)) {
return $data;
}
// 计算分页
$offset = ($page - 1) * $perPage;
// 数据库查询
$db = new PDO(...);
$items = $db->query("SELECT * FROM items LIMIT $offset, $perPage")->fetchAll();
$total = $db->query("SELECT COUNT(*) FROM items")->fetchColumn();
// 构建响应
$response = [
'data' => $items,
'pagination' => [
'total' => $total,
'per_page' => $perPage,
'current_page' => $page,
'last_page' => ceil($total / $perPage),
]
];
// 设置缓存 (5分钟)
$cache->set($cacheKey, $response, 300);
// 设置HTTP缓存头
header("Cache-Control: public, max-age=300");
return $response;
}
性能考虑
分页性能:
- 避免
COUNT(*)
在大表上的性能问题 - 考虑使用近似计数或缓存计数结果
- 使用索引优化分页查询
- 避免
缓存性能:
- 对小而频繁访问的数据使用内存缓存
- 对大而较少变化的数据使用文件缓存
- 考虑分层缓存策略
通过合理结合分页和缓存技术,可以显著提高API的性能和用户体验。
如何进行代码审查和团队协作?
- 代码审查流程
- 预提交检查清单:
- 静态分析工具扫描(如SonarQube)
- 单元测试覆盖率达标(建议80%+)
- 代码风格符合团队规范(使用Prettier/ESLint等)
- 审查重点:
- 架构合理性(是否符合SOLID原则)
- 边界条件处理
- 性能考量
- 安全漏洞(OWASP Top 10)
- 可测试性
- 高效协作实践
- Git工作流选择:
- 功能分支 + PR/MR模式
- 采用 Conventional Commits 规范
- 小颗粒度提交(每个PR < 300行)
- 工具链集成:
- CI/CD流水线自动触发检查
- 代码审查平台(GitHub/GitLab/Bitbucket)
- 文档即代码(Markdown + 代码注释)
- 团队文化建设
- 定期举办:
- 代码评审会(每周1-2次)
- 技术分享会(每月)
- 架构设计评审(重大变更前)
- 建立知识库:
- 决策记录(ADR)
- 模式库(Pattern Library)
- FAQ和常见陷阱文档
- 实用技巧
- 审查时使用"三明治反馈法"(肯定->建议->鼓励)
- 对复杂变更进行walkthrough演示
- 使用LGTM(Looks Good To Me)投票机制
- 设置自动化的PR模板检查项
建议从自动化工具入手逐步建立规范,同时培养团队的代码所有权意识。记住,好的代码审查应该像结对编程一样具有建设性,而非单纯的找错游戏。
请举例说明如何优化 PHP 项目的部署流程。
优化 PHP 项目的部署流程可以显著提高开发效率和系统稳定性。以下是一些关键优化策略及具体实现方式:
版本控制与自动化部署
- 使用 Git Flow 工作流管理代码
- 示例:通过 Git hooks 触发部署
# post-receive hook 示例 #!/bin/sh git --work-tree=/var/www/production --git-dir=/var/repo/site.git checkout -f
CI/CD 流水线实现
- 推荐工具:Jenkins/GitLab CI/GitHub Actions
- 示例 GitHub Actions 配置 (.github/workflows/deploy.yml):
name: Deploy on: push jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies run: composer install --no-dev - name: Deploy to production uses: appleboy/ssh-action@master with: host: ${{ secrets.PROD_HOST }} username: ${{ secrets.PROD_USER }} key: ${{ secrets.SSH_KEY }} script: | cd /var/www/project git pull origin main composer install --no-dev
依赖管理优化
- 使用 Composer 的优化命令:
composer install --no-dev --optimize-autoloader --classmap-authoritative
部署策略选择
- 蓝绿部署:保持两套生产环境切换
- 滚动更新:逐步替换实例
- 示例负载均衡配置(Nginx):
upstream backend { server 192.168.1.1:9000; server 192.168.1.2:9000 backup; }
环境一致性保障
- 使用 Docker 容器化:
FROM php:8.1-fpm RUN apt-get update && apt-get install -y \ libzip-dev \ && docker-php-ext-install zip pdo_mysql COPY . /var/www/html WORKDIR /var/www/html RUN composer install --no-dev
部署后自动化任务
- 数据库迁移:
php artisan migrate --force # Laravel 示例
- 缓存预热:
php artisan view:cache php artisan route:cache
监控与回滚机制
- 部署后检查脚本:
$healthy = checkApplicationHealth(); if (!$healthy) { exec('git reset --hard HEAD~1'); sendAlert('Deployment rolled back'); }
性能优化组合
- OPcache 配置示例 (php.ini):
opcache.enable=1 opcache.memory_consumption=256 opcache.max_accelerated_files=20000 opcache.validate_timestamps=0 ; 生产环境建议关闭
最佳实践建议:
- 实现零停机部署(通过负载均衡和优雅重启)
- 所有部署都应通过自动化流程完成
- 保持开发、测试、生产环境的一致性
- 每次部署都应有明确的版本标识
- 建立完善的部署日志和监控系统
通过以上优化,可以将传统的手动部署转变为高效可靠的自动化流程,显著降低人为错误风险,提高部署频率和系统稳定性。根据项目规模选择适合的方案,中小项目可从简单的 Git 钩子开始,大型项目建议采用完整的 CI/CD 流水线。