Skip to content

Commit 431d529

Browse files
committed
first commit
0 parents  commit 431d529

File tree

9 files changed

+327
-0
lines changed

9 files changed

+327
-0
lines changed

.editorconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = false
10+
11+
[*.{vue,js,scss}]
12+
charset = utf-8
13+
indent_style = space
14+
indent_size = 2
15+
end_of_line = lf
16+
insert_final_newline = true
17+
trim_trailing_whitespace = true
18+
19+
[*.md]
20+
trim_trailing_whitespace = false

.gitattributes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
* text=auto
2+
3+
/tests export-ignore
4+
.gitattributes export-ignore
5+
.gitignore export-ignore
6+
.scrutinizer.yml export-ignore
7+
.travis.yml export-ignore
8+
phpunit.php export-ignore
9+
phpunit.xml.dist export-ignore
10+
phpunit.xml export-ignore
11+
.php_cs export-ignore

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.idea
2+
*.DS_Store
3+
/vendor
4+
/coverage
5+
sftp-config.json
6+
composer.lock
7+
.subsplit
8+
.php_cs.cache

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<h1 align="left"> Idempotent </h1>
2+
3+
<p align="left"> laravel 幂等中间件,防止同一时间多次请求。</p>
4+
5+
## Requirement
6+
7+
1. PHP >= 7.0 | PHP >= 8.0
8+
2. laravel >= 6
9+
10+
## Installation
11+
12+
```shell
13+
$ composer require chenpkg/idempotent
14+
```
15+
16+
## Usage
17+
18+
发布配置文件
19+
20+
```
21+
$ php artisan vendor:publish --tag="laravel-idempotent"
22+
```
23+
24+
中间件为 `Chenpkg\Idempotent\IdempotentMiddleware`,别名 `idempotent`
25+
26+
```php
27+
Route::group(['middleware' => 'idempotent'], function () {
28+
//...
29+
});
30+
```
31+
32+
或者你可以将它加入到指定路由中间件组中
33+
34+
```php
35+
protected $middlewareGroups = [
36+
// ...
37+
'api' => [
38+
'idempotent',
39+
'throttle:api',
40+
\Illuminate\Routing\Middleware\SubstituteBindings::class,
41+
],
42+
];
43+
```
44+
45+
重复的请求将会抛出 `Chenpkg\Idempotent\Exceptions\RepeatRequestException` `Http` 异常
46+
47+
## Configure
48+
49+
50+
```php
51+
// config/idempotent.php
52+
53+
return [
54+
// true 自动获取唯一key, false 前端提供
55+
'forcible' => true,
56+
57+
// 需要过滤重复请求的请求类型
58+
'methods' => ['POST', 'PUT', 'PATCH'],
59+
60+
// 缓存有效时间/秒,防止死锁
61+
'seconds' => 10,
62+
63+
// 获取当前用户
64+
'resolve_user' => function (\Illuminate\Http\Request $request) {
65+
return auth()->user();
66+
},
67+
68+
// 前端提供 key 请求头名称.
69+
'header_name' => 'Idempotent-Key',
70+
];
71+
```
72+
73+
74+
## License
75+
76+
MIT

composer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "chenpkg\/idempotent",
3+
"description": "laravel idempotent middleware",
4+
"license": "MIT",
5+
"authors": [
6+
{
7+
"name": "cestbon",
8+
"email": "[email protected]"
9+
}
10+
],
11+
"require": {
12+
"php": "^7.0|^8.0",
13+
"laravel/framework": "^6.0|^7.0|^8.0"
14+
},
15+
"autoload": {
16+
"psr-4": {
17+
"Chenpkg\\Idempotent\\": "src"
18+
}
19+
},
20+
"extra": {
21+
"laravel": {
22+
"providers": [
23+
"Chenpkg\\Idempotent\\IdempotentServiceProvider"
24+
]
25+
}
26+
}
27+
}

config/idempotent.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
return [
4+
// true 自动获取唯一key, false 前端提供
5+
'forcible' => true,
6+
7+
// 需要过滤重复请求的请求类型
8+
'methods' => ['POST', 'PUT', 'PATCH'],
9+
10+
// 缓存有效时间/秒,防止死锁
11+
'seconds' => 10,
12+
13+
// 获取当前用户
14+
'resolve_user' => function (\Illuminate\Http\Request $request) {
15+
return auth()->user();
16+
},
17+
18+
// 前端提供 key 请求头名称.
19+
'header_name' => 'Idempotent-Key',
20+
];
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
/**
3+
* Created by Cestbon.
4+
* Author Cestbon <[email protected]>
5+
* Date 2021-08-23 11:43
6+
*/
7+
8+
namespace Chenpkg\Idempotent\Exceptions;
9+
10+
use Symfony\Component\HttpKernel\Exception\HttpException;
11+
12+
class RepeatRequestException extends HttpException
13+
{
14+
public function __construct($message = null)
15+
{
16+
parent::__construct(423, $message);
17+
}
18+
}

src/IdempotentMiddleware.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
/**
3+
* Created by Cestbon.
4+
* Author Cestbon <[email protected]>
5+
* Date 2021-08-20 15:45
6+
*/
7+
8+
namespace Chenpkg\Idempotent;
9+
10+
use Chenpkg\Idempotent\Exceptions\RepeatRequestException;
11+
use Closure;
12+
use Illuminate\Http\Request;
13+
use Illuminate\Support\Facades\Cache;
14+
15+
class IdempotentMiddleware
16+
{
17+
protected $config;
18+
19+
const PLACE_HOLDER = 'idempotent_place_holder';
20+
21+
public function handle(Request $request, Closure $next)
22+
{
23+
$this->config = config('idempotent');
24+
25+
if (! in_array($request->method(), $this->config['methods'])) {
26+
return $next($request);
27+
}
28+
29+
$idempotentKey = $this->getIdempotentKey();
30+
31+
if (! $idempotentKey) {
32+
return $next($request);
33+
}
34+
35+
$this->repeated($idempotentKey);
36+
37+
return $next($request);
38+
}
39+
40+
/**
41+
* @param $request
42+
* @param $response
43+
*/
44+
public function terminate($request, $response)
45+
{
46+
Cache::forget($this->getCacheKey($request->header($this->config['header_name'])));
47+
}
48+
49+
/**
50+
* @param $key
51+
* @return string
52+
*/
53+
protected function getCacheKey($key)
54+
{
55+
return 'idempotent_key:'.$key;
56+
}
57+
58+
/**
59+
* @param $idempotentKey
60+
* @return true
61+
* @throws RepeatRequestException
62+
*/
63+
protected function repeated($idempotentKey)
64+
{
65+
$value = Cache::get($this->getCacheKey($idempotentKey));
66+
67+
if ($value == static::PLACE_HOLDER) {
68+
throw new RepeatRequestException('Your request is still being processed.');
69+
}
70+
71+
$seconds = (int)$this->config['seconds'];
72+
$seconds = $seconds > 0 ? $seconds : null;
73+
74+
Cache::put($this->getCacheKey($idempotentKey), static::PLACE_HOLDER, $seconds);
75+
76+
return true;
77+
}
78+
79+
/**
80+
* @return array|string|null
81+
*/
82+
protected function getIdempotentKey()
83+
{
84+
return $this->config['forcible'] ? $this->generateIdempotentKey() : request()->header($this->config['header_name']);
85+
}
86+
87+
/**
88+
* @return string
89+
*/
90+
protected function generateIdempotentKey()
91+
{
92+
$user = $this->resolveUser();
93+
94+
$idempotentKey = $user ? $user->getAuthIdentifier().request() : request()->ip().request();
95+
$idempotentKey = sha1($idempotentKey);
96+
97+
request()->headers->set(config('idempotent.header_name'), $idempotentKey);
98+
99+
return $idempotentKey;
100+
}
101+
102+
/**
103+
* @return mixed|null
104+
*/
105+
protected function resolveUser()
106+
{
107+
$user = null;
108+
109+
$resolveUser = $this->config['resolve_user'];
110+
111+
if ($resolveUser instanceof Closure && $result = app()->call($resolveUser)) {
112+
$user = $result;
113+
}
114+
115+
return $user;
116+
}
117+
}

src/IdempotentServiceProvider.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Created by Cestbon.
4+
* Author Cestbon <[email protected]>
5+
* Date 2021-08-20 15:14
6+
*/
7+
8+
namespace Chenpkg\Idempotent;
9+
10+
use Illuminate\Support\ServiceProvider;
11+
12+
class IdempotentServiceProvider extends ServiceProvider
13+
{
14+
public function register()
15+
{
16+
$this->mergeConfigFrom(__DIR__.'/../config/idempotent.php', 'idempotent');
17+
18+
$this->app->singleton(IdempotentMiddleware::class);
19+
$this->app['router']->aliasMiddleware('idempotent', IdempotentMiddleware::class);
20+
}
21+
22+
public function boot()
23+
{
24+
if ($this->app->runningInConsole()) {
25+
$this->publishes([
26+
__DIR__.'/../config/idempotent.php' => config_path('idempotent.php')
27+
], 'laravel-idempotent');
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)