admin管理员组文章数量:1029102
ThinkPHP6 API开放平台:调用日志与请求频率限制的实现
在构建API开放平台时,调用日志记录和请求频率限制是两个至关重要的功能。调用日志帮助我们追踪API使用情况、排查问题,而频率限制则保护系统免受滥用和过载。本文将详细介绍如何在ThinkPHP6中实现这两大功能。
一、调用日志的实现
1.1 数据库设计
首先我们需要设计一个日志表来存储API调用记录:
代码语言:javascript代码运行次数:0运行复制CREATE TABLE `api_call_logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`app_id` varchar(32) NOT NULL COMMENT '应用ID',
`api_path` varchar(255) NOT NULL COMMENT 'API路径',
`request_method` varchar(10) NOT NULL COMMENT '请求方法',
`request_params` text COMMENT '请求参数',
`response_code` int(11) NOT NULL COMMENT '响应状态码',
`response_data` text COMMENT '响应数据',
`ip_address` varchar(45) NOT NULL COMMENT 'IP地址',
`user_agent` varchar(255) DEFAULT NULL COMMENT '用户代理',
`request_time` datetime NOT NULL COMMENT '请求时间',
`response_time` datetime NOT NULL COMMENT '响应时间',
`execution_time` int(11) NOT NULL COMMENT '执行时间(ms)',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_app_id` (`app_id`),
KEY `idx_api_path` (`api_path`),
KEY `idx_request_time` (`request_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API调用日志表';
1.2 创建日志模型
代码语言:javascript代码运行次数:0运行复制namespace app\model;
use think\Model;
class ApiCallLog extends Model
{
protected $table = 'api_call_logs';
protected $autoWriteTimestamp = 'datetime';
protected $type = [
'request_params' => 'json',
'response_data' => 'json',
];
}
1.3 中间件实现日志记录
代码语言:javascript代码运行次数:0运行复制namespace app\middleware;
use app\model\ApiCallLog;
use think\Request;
use think\Response;
class ApiLogger
{
public function handle(Request $request, \Closure $next)
{
// 记录请求开始时间
$startTime = microtime(true);
// 继续执行请求
$response = $next($request);
// 记录日志
$this->logRequest($request, $response, $startTime);
return $response;
}
protected function logRequest(Request $request, Response $response, float $startTime)
{
try {
$appId = $request->header('app-id', '');
$executionTime = round((microtime(true) - $startTime) * 1000;
ApiCallLog::create([
'app_id' => $appId,
'api_path' => $request->pathinfo(),
'request_method' => $request->method(),
'request_params' => $request->param(),
'response_code' => $response->getCode(),
'response_data' => $response->getData(),
'ip_address' => $request->ip(),
'user_agent' => $request->header('user-agent'),
'request_time' => date('Y-m-d H:i:s', $startTime),
'response_time' => date('Y-m-d H:i:s'),
'execution_time' => $executionTime,
]);
} catch (\Exception $e) {
// 记录日志失败不应影响主流程
\think\facade\Log::error('API日志记录失败: ' . $e->getMessage());
}
}
}
1.4 注册中间件
在app/middleware.php
中注册中间件:
return [
// 其他中间件...
\app\middleware\ApiLogger::class,
];
二、请求频率限制的实现
2.1 使用Redis实现计数器
ThinkPHP6内置了缓存和Redis支持,我们可以利用Redis的高性能特性来实现频率限制。
2.1.1 频率限制配置
在config/cache.php
中配置Redis:
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'expire' => 0,
'persistent' => false,
'prefix' => 'api_limit:',
'tag_prefix' => 'tag:',
],
2.2 频率限制中间件
代码语言:javascript代码运行次数:0运行复制namespace app\middleware;
use think\Request;
use think\Response;
use think\facade\Cache;
class RateLimiter
{
// 默认限制配置
protected $defaultLimit = [
'limit' => 100, // 请求次数
'window' => 60, // 时间窗口(秒)
];
public function handle(Request $request, \Closure $next, $config = null)
{
$appId = $request->header('app-id', 'anonymous');
$ip = $request->ip();
$apiPath = $request->pathinfo();
// 获取限流配置
$limitConfig = $this->getLimitConfig($appId, $apiPath, $config);
// 生成唯一键
$key = $this->generateKey($appId, $ip, $apiPath);
// 检查是否超过限制
if ($this->isRateLimited($key, $limitConfig)) {
return $this->responseLimitExceeded($limitConfig);
}
return $next($request);
}
protected function getLimitConfig($appId, $apiPath, $config)
{
// 这里可以从数据库或配置文件中获取特定appId和apiPath的限流配置
// 简化示例返回默认配置
return $this->defaultLimit;
}
protected function generateKey($appId, $ip, $apiPath)
{
return md5("{$appId}_{$ip}_{$apiPath}");
}
protected function isRateLimited($key, $config)
{
$cache = Cache::store('redis');
$now = time();
// 使用Redis的有序集合实现滑动窗口计数
$windowStart = $now - $config['window'];
// 移除时间窗口外的记录
$cache->zRemRangeByScore($key, 0, $windowStart);
// 获取当前窗口内的请求数
$requestCount = $cache->zCard($key);
if ($requestCount >= $config['limit']) {
return true;
}
// 添加当前请求时间戳
$cache->zAdd($key, $now, $now);
// 设置键的过期时间
$cache->expire($key, $config['window']);
return false;
}
protected function responseLimitExceeded($config)
{
$response = Response::create([
'code' => 429,
'message' => '请求过于频繁',
'data' => [
'limit' => $config['limit'],
'window' => $config['window'],
]
], 'json', 429);
$response->header([
'X-RateLimit-Limit' => $config['limit'],
'X-RateLimit-Remaining' => 0,
'X-RateLimit-Reset' => time() + $config['window'],
]);
return $response;
}
}
2.3 应用频率限制中间件
2.3.1 全局中间件
在app/middleware.php
中注册全局中间件:
return [
// 其他中间件...
\app\middleware\RateLimiter::class,
];
2.3.2 路由中间件
也可以在特定路由上应用不同的限制:
代码语言:javascript代码运行次数:0运行复制Route::group('api', function() {
Route::get('user/info', 'user/info')
->middleware(\app\middleware\RateLimiter::class, ['limit' => 50, 'window' => 60]);
Route::post('user/update', 'user/update')
->middleware(\app\middleware\RateLimiter::class, ['limit' => 10, 'window' => 60]);
});
三、进阶优化
3.1 动态限流配置
将限流配置存储在数据库中,实现动态调整:
代码语言:javascript代码运行次数:0运行复制protected function getLimitConfig($appId, $apiPath, $config)
{
// 从缓存获取配置
$cacheKey = "rate_limit_config:{$appId}:{$apiPath}";
$config = Cache::get($cacheKey);
if ($config) {
return $config;
}
// 从数据库查询
$config = \app\model\RateLimitConfig::where('app_id', $appId)
->where('api_path', $apiPath)
->find();
if (!$config) {
// 使用默认配置
$config = $this->defaultLimit;
}
// 缓存配置
Cache::set($cacheKey, $config, 3600);
return $config;
}
3.2 分布式限流
对于分布式系统,可以使用Redis+Lua脚本实现原子操作:
代码语言:javascript代码运行次数:0运行复制-- ratelimit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local windowStart = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
local current = redis.call('ZCARD', key)
if current >= limit then
return 0
end
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1
然后在PHP中调用:
代码语言:javascript代码运行次数:0运行复制protected function isRateLimited($key, $config)
{
$lua = file_get_contents(app_path().'lua/ratelimit.lua');
$now = time();
$result = Cache::store('redis')->eval($lua, [$key, $config['limit'], $config['window'], $now], 1);
return $result == 0;
}
3.3 日志性能优化
对于高并发场景,日志记录可以改为异步处理:
代码语言:javascript代码运行次数:0运行复制protected function logRequest(Request $request, Response $response, float $startTime)
{
// 将日志数据放入队列
\think\facade\Queue::push('app\job\ApiLogJob', [
'app_id' => $request->header('app-id', ''),
'api_path' => $request->pathinfo(),
'request_method' => $request->method(),
'request_params' => $request->param(),
'response_code' => $response->getCode(),
'response_data' => $response->getData(),
'ip_address' => $request->ip(),
'user_agent' => $request->header('user-agent'),
'request_time' => date('Y-m-d H:i:s', $startTime),
'response_time' => date('Y-m-d H:i:s'),
'execution_time' => round((microtime(true) - $startTime) * 1000),
]);
}
四、总结
在ThinkPHP6中实现API调用日志和请求频率限制,我们可以:
- 通过中间件机制无侵入式地实现功能
- 使用Redis高效实现滑动窗口限流算法
- 采用异步处理提高日志记录性能
- 支持动态配置满足不同API和应用的限流需求
这些功能的实现不仅保护了API服务器的稳定性,还为后续的监控分析和计费提供了数据基础。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-04-24,如有侵权请联系 cloudcommunity@tencent 删除日志中间件apithinkphp6配置ThinkPHP6 API开放平台:调用日志与请求频率限制的实现
在构建API开放平台时,调用日志记录和请求频率限制是两个至关重要的功能。调用日志帮助我们追踪API使用情况、排查问题,而频率限制则保护系统免受滥用和过载。本文将详细介绍如何在ThinkPHP6中实现这两大功能。
一、调用日志的实现
1.1 数据库设计
首先我们需要设计一个日志表来存储API调用记录:
代码语言:javascript代码运行次数:0运行复制CREATE TABLE `api_call_logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`app_id` varchar(32) NOT NULL COMMENT '应用ID',
`api_path` varchar(255) NOT NULL COMMENT 'API路径',
`request_method` varchar(10) NOT NULL COMMENT '请求方法',
`request_params` text COMMENT '请求参数',
`response_code` int(11) NOT NULL COMMENT '响应状态码',
`response_data` text COMMENT '响应数据',
`ip_address` varchar(45) NOT NULL COMMENT 'IP地址',
`user_agent` varchar(255) DEFAULT NULL COMMENT '用户代理',
`request_time` datetime NOT NULL COMMENT '请求时间',
`response_time` datetime NOT NULL COMMENT '响应时间',
`execution_time` int(11) NOT NULL COMMENT '执行时间(ms)',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_app_id` (`app_id`),
KEY `idx_api_path` (`api_path`),
KEY `idx_request_time` (`request_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API调用日志表';
1.2 创建日志模型
代码语言:javascript代码运行次数:0运行复制namespace app\model;
use think\Model;
class ApiCallLog extends Model
{
protected $table = 'api_call_logs';
protected $autoWriteTimestamp = 'datetime';
protected $type = [
'request_params' => 'json',
'response_data' => 'json',
];
}
1.3 中间件实现日志记录
代码语言:javascript代码运行次数:0运行复制namespace app\middleware;
use app\model\ApiCallLog;
use think\Request;
use think\Response;
class ApiLogger
{
public function handle(Request $request, \Closure $next)
{
// 记录请求开始时间
$startTime = microtime(true);
// 继续执行请求
$response = $next($request);
// 记录日志
$this->logRequest($request, $response, $startTime);
return $response;
}
protected function logRequest(Request $request, Response $response, float $startTime)
{
try {
$appId = $request->header('app-id', '');
$executionTime = round((microtime(true) - $startTime) * 1000;
ApiCallLog::create([
'app_id' => $appId,
'api_path' => $request->pathinfo(),
'request_method' => $request->method(),
'request_params' => $request->param(),
'response_code' => $response->getCode(),
'response_data' => $response->getData(),
'ip_address' => $request->ip(),
'user_agent' => $request->header('user-agent'),
'request_time' => date('Y-m-d H:i:s', $startTime),
'response_time' => date('Y-m-d H:i:s'),
'execution_time' => $executionTime,
]);
} catch (\Exception $e) {
// 记录日志失败不应影响主流程
\think\facade\Log::error('API日志记录失败: ' . $e->getMessage());
}
}
}
1.4 注册中间件
在app/middleware.php
中注册中间件:
return [
// 其他中间件...
\app\middleware\ApiLogger::class,
];
二、请求频率限制的实现
2.1 使用Redis实现计数器
ThinkPHP6内置了缓存和Redis支持,我们可以利用Redis的高性能特性来实现频率限制。
2.1.1 频率限制配置
在config/cache.php
中配置Redis:
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'expire' => 0,
'persistent' => false,
'prefix' => 'api_limit:',
'tag_prefix' => 'tag:',
],
2.2 频率限制中间件
代码语言:javascript代码运行次数:0运行复制namespace app\middleware;
use think\Request;
use think\Response;
use think\facade\Cache;
class RateLimiter
{
// 默认限制配置
protected $defaultLimit = [
'limit' => 100, // 请求次数
'window' => 60, // 时间窗口(秒)
];
public function handle(Request $request, \Closure $next, $config = null)
{
$appId = $request->header('app-id', 'anonymous');
$ip = $request->ip();
$apiPath = $request->pathinfo();
// 获取限流配置
$limitConfig = $this->getLimitConfig($appId, $apiPath, $config);
// 生成唯一键
$key = $this->generateKey($appId, $ip, $apiPath);
// 检查是否超过限制
if ($this->isRateLimited($key, $limitConfig)) {
return $this->responseLimitExceeded($limitConfig);
}
return $next($request);
}
protected function getLimitConfig($appId, $apiPath, $config)
{
// 这里可以从数据库或配置文件中获取特定appId和apiPath的限流配置
// 简化示例返回默认配置
return $this->defaultLimit;
}
protected function generateKey($appId, $ip, $apiPath)
{
return md5("{$appId}_{$ip}_{$apiPath}");
}
protected function isRateLimited($key, $config)
{
$cache = Cache::store('redis');
$now = time();
// 使用Redis的有序集合实现滑动窗口计数
$windowStart = $now - $config['window'];
// 移除时间窗口外的记录
$cache->zRemRangeByScore($key, 0, $windowStart);
// 获取当前窗口内的请求数
$requestCount = $cache->zCard($key);
if ($requestCount >= $config['limit']) {
return true;
}
// 添加当前请求时间戳
$cache->zAdd($key, $now, $now);
// 设置键的过期时间
$cache->expire($key, $config['window']);
return false;
}
protected function responseLimitExceeded($config)
{
$response = Response::create([
'code' => 429,
'message' => '请求过于频繁',
'data' => [
'limit' => $config['limit'],
'window' => $config['window'],
]
], 'json', 429);
$response->header([
'X-RateLimit-Limit' => $config['limit'],
'X-RateLimit-Remaining' => 0,
'X-RateLimit-Reset' => time() + $config['window'],
]);
return $response;
}
}
2.3 应用频率限制中间件
2.3.1 全局中间件
在app/middleware.php
中注册全局中间件:
return [
// 其他中间件...
\app\middleware\RateLimiter::class,
];
2.3.2 路由中间件
也可以在特定路由上应用不同的限制:
代码语言:javascript代码运行次数:0运行复制Route::group('api', function() {
Route::get('user/info', 'user/info')
->middleware(\app\middleware\RateLimiter::class, ['limit' => 50, 'window' => 60]);
Route::post('user/update', 'user/update')
->middleware(\app\middleware\RateLimiter::class, ['limit' => 10, 'window' => 60]);
});
三、进阶优化
3.1 动态限流配置
将限流配置存储在数据库中,实现动态调整:
代码语言:javascript代码运行次数:0运行复制protected function getLimitConfig($appId, $apiPath, $config)
{
// 从缓存获取配置
$cacheKey = "rate_limit_config:{$appId}:{$apiPath}";
$config = Cache::get($cacheKey);
if ($config) {
return $config;
}
// 从数据库查询
$config = \app\model\RateLimitConfig::where('app_id', $appId)
->where('api_path', $apiPath)
->find();
if (!$config) {
// 使用默认配置
$config = $this->defaultLimit;
}
// 缓存配置
Cache::set($cacheKey, $config, 3600);
return $config;
}
3.2 分布式限流
对于分布式系统,可以使用Redis+Lua脚本实现原子操作:
代码语言:javascript代码运行次数:0运行复制-- ratelimit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local windowStart = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
local current = redis.call('ZCARD', key)
if current >= limit then
return 0
end
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1
然后在PHP中调用:
代码语言:javascript代码运行次数:0运行复制protected function isRateLimited($key, $config)
{
$lua = file_get_contents(app_path().'lua/ratelimit.lua');
$now = time();
$result = Cache::store('redis')->eval($lua, [$key, $config['limit'], $config['window'], $now], 1);
return $result == 0;
}
3.3 日志性能优化
对于高并发场景,日志记录可以改为异步处理:
代码语言:javascript代码运行次数:0运行复制protected function logRequest(Request $request, Response $response, float $startTime)
{
// 将日志数据放入队列
\think\facade\Queue::push('app\job\ApiLogJob', [
'app_id' => $request->header('app-id', ''),
'api_path' => $request->pathinfo(),
'request_method' => $request->method(),
'request_params' => $request->param(),
'response_code' => $response->getCode(),
'response_data' => $response->getData(),
'ip_address' => $request->ip(),
'user_agent' => $request->header('user-agent'),
'request_time' => date('Y-m-d H:i:s', $startTime),
'response_time' => date('Y-m-d H:i:s'),
'execution_time' => round((microtime(true) - $startTime) * 1000),
]);
}
四、总结
在ThinkPHP6中实现API调用日志和请求频率限制,我们可以:
- 通过中间件机制无侵入式地实现功能
- 使用Redis高效实现滑动窗口限流算法
- 采用异步处理提高日志记录性能
- 支持动态配置满足不同API和应用的限流需求
这些功能的实现不仅保护了API服务器的稳定性,还为后续的监控分析和计费提供了数据基础。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-04-24,如有侵权请联系 cloudcommunity@tencent 删除日志中间件apithinkphp6配置本文标签: ThinkPHP6 API开放平台调用日志与请求频率限制的实现
版权声明:本文标题:ThinkPHP6 API开放平台:调用日志与请求频率限制的实现 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1747533098a2171529.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论