├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config ├── api.php ├── jwt.php └── resources.php └── src ├── Api.php ├── Exceptions ├── HandleException.php ├── JWTException.php ├── ResponseException.php ├── TokenExpiredException.php ├── TokenInvalidException.php ├── TokenNotBeforeException.php ├── TypeErrorException.php └── UnauthenticateException.php ├── Facades ├── ApiRoute.php ├── JWT.php └── Response.php ├── Http └── Response.php ├── JWT ├── Factories │ ├── Claims │ │ ├── Audience.php │ │ ├── Claim.php │ │ ├── Collection.php │ │ ├── Custom.php │ │ ├── Expiration.php │ │ ├── IssuedAt.php │ │ ├── Issuer.php │ │ ├── JwtId.php │ │ ├── NotBefore.php │ │ └── Subject.php │ ├── Code.php │ └── Payload.php ├── Factory.php ├── Library │ └── UrlSafeBase64.php └── Payload.php ├── Response ├── Factory.php └── Status.php ├── Routing └── Router.php ├── Serializers ├── ArraySerializer.php ├── DataArraySerializer.php └── Serializer.php └── Setting └── Set.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | /vendor 3 | .DS_Store 4 | */.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 陈泽韦 2 | 3 | e-mail: chanzewail@gmail.com 549226266@qq.com 4 | website: http://www.zewail.me/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Latest Stable Version](https://poser.pugx.org/zewail/think-api/v/stable)](https://packagist.org/packages/zewail/think-api) 3 | [![Total Downloads](https://poser.pugx.org/zewail/think-api/downloads)](https://packagist.org/packages/zewail/think-api) 4 | [![Latest Unstable Version](https://poser.pugx.org/zewail/think-api/v/unstable)](https://packagist.org/packages/zewail/think-api) 5 | [![License](https://poser.pugx.org/zewail/think-api/license)](https://packagist.org/packages/zewail/think-api) 6 | 7 | 8 | think-api 是给开发者提供的一套针对thinkphp的API扩展工具,帮助开发者方便快捷的建造自己的API应用。 9 | 10 | **该包是针对thinkphp5.1以上版本** 11 | 12 | ## 简介 13 | 14 | 这个包提供了以下等工具: 15 | - API版本管理 16 | - 响应生成器 17 | - 数据过滤器 18 | - JWT(Json Web Token)的支持 19 | 20 | `thinp-api` 是给开发者提供的一套API工具,帮助你方便快捷的建造你自己的API,有什么使用问题和反馈建议请在[issue](https://github.com/czewail/thinp-api/issues)中提出 21 | 22 | 另外,欢迎Star和Fork该项目😄😄😄😄😄 23 | 24 | 25 | ## 安装 26 | 安装该扩展包需要环境支持 27 | 28 | - thinkphp 5.1+ 29 | - php 5.6+ 30 | 31 | 修改你的 `composer.json` 文件,然后执行 `composer update` 把最后一个版本的包加入你的项目 32 | 33 | ```txt 34 | "require": { 35 | "zewail/think-api": "1.1.*" 36 | } 37 | ``` 38 | 39 | 或者你可以在命令行执行 `composer require` 命令 40 | 41 | ```txt 42 | composer require zewail/think-api:1.1.x 43 | ``` 44 | 45 | ## 路由 46 | 如果你使用了自定义路由,则可以使用路由版本管理 47 | 48 | ##### 版本组 49 | 50 | 使用`think-api`的版本管理方法来创建版本 51 | 52 | ```php 53 | $api = new \Zewail\Api\Routing\Router; 54 | 55 | $api->version('v1', function () { 56 | // TODO 可以是thinkphp自带的路由 57 | }); 58 | ``` 59 | 60 | 或者使用门面(Facede) 61 | 62 | ```php 63 | use Zewail\Api\Facades\ApiRoute; 64 | 65 | ApiRoute::version('v1', function(){ 66 | // TODO 可以是thinkphp自带的路由 67 | }); 68 | ``` 69 | 70 | 你想一个分组返回多个版本,只需要传递一个版本数组 71 | 72 | ```php 73 | ApiRoute::version(['v1', 'v2'], function () { 74 | // TODO 可以是thinkphp自带的路由 75 | }); 76 | ``` 77 | 78 | ##### 创建路由 79 | 80 | ```php 81 | ApiRoute::version('v1', function(){ 82 | Route::rule('new/:id','index/News/read'); 83 | }); 84 | ``` 85 | 86 | 因为每个版本分组了,你可以为相同 URL 上的同一个路由创建不同响应 87 | 88 | ```php 89 | ApiRoute::version('v1', function () { 90 | Route::rule('new/:id','app\index\controller\V2\News@read'); 91 | // 或者 92 | Route::rule('new/:id','index/V2.News/read'); 93 | }); 94 | 95 | ApiRoute::version('v2', function () { 96 | Route::rule('new/:id','app\index\controller\V2\News@read'); 97 | }); 98 | ``` 99 | 100 | ##### 访问特定路由版本 101 | 102 | 默认访问配置文件中的默认版本 103 | 104 | 但是,我们可以在Http的头信息中附带`Api-Version`参数,或者直接在在url或body中附带`version`参数来访问指定版本 105 | 106 | ```txt 107 | http://example.com/new/102?version=v2 108 | ``` 109 | ## 响应 110 | #### 响应生成器 111 | 112 | 响应生成器提供了一个流畅的接口去方便的建立一个定制化的响应 113 | 114 | 要利用响应生成器, 你的控制器需要使用`Zewail\Api\Api` trait, 可以建立一个通用控制器,然后你的所有的 API 控制器都继承它。 115 | 116 | ```php 117 | namespace app\index\controller; 118 | 119 | use think\Controller; 120 | use Zewail\Api\Api; 121 | 122 | class BaseController extends Controller 123 | { 124 | use Api; 125 | } 126 | ``` 127 | 128 | 然后你的控制器可以直接继承基础控制器。响应生成器可以在控制器里通过 `$response` 属性获取。 129 | 130 | 当然,也可以使用门面(Facade)来获取 131 | 132 | ```php 133 | namespace app\index\controller; 134 | 135 | use Zewail\Api\Facades\Response as ApiResponse; 136 | 137 | class IndexController 138 | { 139 | public function index() { 140 | return ApiResponse::array([]); 141 | } 142 | } 143 | ``` 144 | ##### 简单响应 145 | 146 | ```php 147 | // 简单的成功响应, 默认200状态码, 可以在第二个参数改变 148 | // 使用 trait, 其他方法都可以使用该方法,下面都使用 Facade 演示 149 | return $this->response->array($user->toArray()); 150 | // 使用 Facade 151 | return ApiResponse::success('Success', 200); 152 | ``` 153 | 154 | ##### 响应一个数组 155 | 156 | ```php 157 | $user = User::get($id); 158 | return ApiResponse::array($user->toArray()); 159 | ``` 160 | 161 | ##### 响应一个元素 162 | 163 | ```php 164 | $user = User::get($id); 165 | return ApiResponse::item($user); 166 | ``` 167 | 168 | ##### 响应一个元素集合 169 | 170 | ```php 171 | $users = User::all(); 172 | return ApiResponse::collection($users); 173 | ``` 174 | 175 | ##### 分页响应 176 | 177 | ```php 178 | $users = User::paginate(10); 179 | return ApiResponse::paginator($users); 180 | ``` 181 | #### 使用别名生成响应 182 | 183 | 捕获错误响应需要接管系统的异常处理机制,将系统`config/app.php`中的 `exception_handle`配置为`Zewail\Api\Exceptions\handleException` 184 | 185 | ```php 186 | // 异常处理handle类 留空使用 \think\exception\Handle 187 | 'exception_handle' => 'Zewail\Api\Exceptions\handleException', 188 | ``` 189 | 190 | 返回一个错误响应 191 | 192 | ```php 193 | return ApiResponse::BadRequest(); 194 | ``` 195 | 196 | 当然也可以返回成功的响应 197 | 198 | ```php 199 | return ApiResponse::OK($data); 200 | ``` 201 | 202 | | 方法名 | 状态码 | 说明 | 203 | | ----------------------------- | ------ | ------------------------------- | 204 | | Continue | 100 | Continue | 205 | | SwitchingProtocols | 101 | Switching Protocols | 206 | | Processing | 102 | Processing | 207 | | EarlyHints | 103 | Early Hints | 208 | | OK | 200 | OK | 209 | | Created | 201 | Created | 210 | | Accepted | 202 | Accepted | 211 | | NonAuthoritativeInformation | 203 | Non-Authoritative Information | 212 | | NoContent | 204 | No Content | 213 | | ResetContent | 205 | Reset Content | 214 | | PartialContent | 206 | Partial Content | 215 | | MultiStatus | 207 | Multi-Status | 216 | | AlreadyReported | 208 | Already Reported | 217 | | IMUsed | 226 | IM Used | 218 | | MultipleChoices | 300 | Multiple Choices | 219 | | MovedPermanently | 301 | Moved Permanently | 220 | | Found | 302 | Found | 221 | | SeeOther | 303 | See Other | 222 | | NotModified | 304 | Not Modified | 223 | | UseProxy | 305 | Use Proxy | 224 | | TemporaryRedirect | 307 | Temporary Redirect | 225 | | PermanentRedirect | 308 | Permanent Redirect | 226 | | BadRequest | 400 | Bad Request | 227 | | Unauthorized | 401 | Unauthorized | 228 | | PaymentRequired | 402 | Payment Required | 229 | | Forbidden | 403 | Forbidden | 230 | | NotFound | 404 | Not Found | 231 | | MethodNotAllowed | 405 | Method Not Allowed | 232 | | NotAcceptable | 406 | Not Acceptable | 233 | | ProxyAuthenticationRequired | 407 | Proxy Authentication Required | 234 | | RequestTimeou | 408 | Request Timeou | 235 | | Conflict | 409 | Conflict | 236 | | Gone | 410 | Gone | 237 | | LengthRequired | 411 | Length Required | 238 | | PreconditionFailed | 412 | Precondition Failed | 239 | | PayloadTooLarge | 413 | Payload Too Large | 240 | | URITooLong | 414 | URI Too Long | 241 | | UnsupportedMediaType | 415 | Unsupported Media Type | 242 | | RangeNotSatisfiable | 416 | Range Not Satisfiable | 243 | | ExpectationFailed | 417 | Expectation Failed | 244 | | IAmATeapot | 418 | I\'m a teapot | 245 | | MisdirectedRequest | 421 | Misdirected Request | 246 | | UnprocessableEntity | 422 | Unprocessable Entity | 247 | | Locked | 423 | Locked | 248 | | FailedDependency | 424 | Failed Dependency | 249 | | UnorderedCollection | 425 | Unordered Collection | 250 | | UpgradeRequired | 426 | Upgrade Required | 251 | | PreconditionRequired | 428 | Precondition Required | 252 | | TooManyRequests | 429 | Too Many Requests | 253 | | RequestHeaderFieldsTooLarge | 431 | Request Header Fields Too Large | 254 | | UnavailableForLegalReasons | 451 | Unavailable For Legal Reasons | 255 | | InternalServerError | 500 | Internal Server Error | 256 | | NotImplemented | 501 | Not Implemented | 257 | | BadGateway | 502 | Bad Gateway | 258 | | ServiceUnavailable | 503 | Service Unavailable | 259 | | GatewayTimeout | 504 | Gateway Timeout | 260 | | HTTPVersionNotSupported | 505 | HTTP Version Not Supported | 261 | | VariantAlsoNegotiates | 506 | Variant Also Negotiates | 262 | | InsufficientStorage | 507 | Insufficient Storage | 263 | | LoopDetected | 508 | Loop Detected | 264 | | NotExtended | 510 | Not Extended | 265 | | NetworkAuthenticationRequired | 511 | Network Authentication Required | 266 | 267 | 268 | 269 | #### 添加其他响应数据 270 | 271 | ##### 添加 Meta 数据 272 | 273 | ```php 274 | return ApiResponse::item($user)->addMeta('foo', 'bar'); 275 | ``` 276 | 277 | 或者直接设置 Meta 数据的数组 278 | 279 | ```php 280 | return ApiResponse::item($user)->setMeta($meta); 281 | ``` 282 | 283 | ##### 设置响应状态码 284 | 285 | ```php 286 | return ApiResponse::item($user)->setCode(200); 287 | ``` 288 | 289 | ##### 添加额外的头信息 290 | 291 | ```php 292 | // 提供两种设置方式 293 | return ApiResponse::item($user)->addHeader('X-Foo', 'Bar'); 294 | return ApiResponse::item($user)->addHeader(['X-Foo' => 'Bar']); 295 | ``` 296 | 297 | ##### 设置 LastModified 298 | 299 | ```php 300 | return ApiResponse::item($user)->setLastModified($time); 301 | ``` 302 | 303 | ##### 设置 ETag 304 | 305 | ```php 306 | return ApiResponse::item($user)->setETag($eTag); 307 | ``` 308 | 309 | ##### 设置 Expires 310 | 311 | ```php 312 | return ApiResponse::item($user)->setExpires($time); 313 | ``` 314 | 315 | ##### 页面缓存控制 316 | 317 | ```php 318 | return ApiResponse::item($user)->setCacheControl($cache); 319 | ``` 320 | 321 | ### 响应数据过滤 322 | 323 | 其中`item` `collection` `paginator`具有两个参数,第一个参数为模型数据,第二个参数为数据过滤列表 324 | 325 | ```php 326 | // 如查询出来的$user具有id, name, age, mobile等属性 327 | // 在设置了第二个参数为['id', 'name', 'age']后,将会过滤其他属性,只返回给接口列出的属性 328 | return ApiResponse::item($user, ['id', 'name', 'age']); 329 | return ApiResponse::collection($users, ['id', 'name', 'age']); 330 | return ApiResponse::paginator($users, ['id', 'name', 'age']); 331 | ``` 332 | 333 | 或者通过`only`与`except`方法过滤数据 334 | 335 | ```php 336 | // 只选择模型中的id、name、age属性 337 | return ApiResponse::only(['id', 'name', 'age'])->item($user); 338 | // 排除模型属性age 339 | return ApiResponse::except(['age'])->item($user); 340 | // 还可以一起使用, 选择id、name、age属性后排除age 341 | return ApiResponse::only(['id', 'name', 'age'])->except(['age'])->item($user); 342 | ``` 343 | 344 | ##### 集中管理 345 | 346 | 提供了一个配置文件用于数据过滤或者说是数据资源的集中管理 347 | 348 | 使用该功能需要在thinkphp中新建一个配置文件`resources.php` 349 | 350 | ```php 351 | return [ 352 | // 用户相关接口 353 | // 例如设置一些用户的相关接口资源 354 | 'user.age' => ['id', 'name', 'age'], 355 | 'user.mobile' => ['id', 'name', 'mobile'], 356 | ]; 357 | ``` 358 | 359 | 然后在返回接口数据的时候在`item` `collection` `paginator`第二个参数传入该标识即可 360 | 361 | ```php 362 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'age': 20}} 363 | return ApiResponse::item($user, 'user.age'); 364 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'mobile': '13777777777'}} 365 | return ApiResponse::item($user, 'user.mobile'); 366 | ``` 367 | 368 | 或者通过`only`与`except`方法 369 | 370 | ```php 371 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'age': 20}} 372 | return ApiResponse::only('user.age')->item($user); 373 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'mobile': '13777777777'}} 374 | return ApiResponse::only('user.mobile')->item($user); 375 | ``` 376 | 377 | > item、collection、paginator的第二个过滤参数属性,会覆盖only与except方法 378 | 379 | #### 设置serializer 380 | 381 | 如果默认配置`Array`,想返回`DataArray`格式的数据,可以: 382 | 383 | ```php 384 | // 返回Array格式的数据 385 | return ApiResponse::item($user)->serializer('Array'); 386 | // 返回DataArray格式的数据 387 | return ApiResponse::item($user)->serializer('DataArray'); 388 | ``` 389 | 390 | ## JWT 391 | JWT相关知识大家百度一下吧,网上很多,直接上代码 392 | 393 | ### 创建Token 394 | 395 | 使用 JWT 门面的 attempt 方法来自动验证 396 | 397 | ```php 398 | namespace app\index\controller; 399 | 400 | use app\index\model\User; 401 | use Zewail\Api\Facades\Response; 402 | use Zewail\Api\Facades\JWT; 403 | use Zewail\Api\Exceptions\JWTException; 404 | 405 | class Authenticate 406 | { 407 | public function authenticate() 408 | { 409 | // $credentials 可以从请求中获取 410 | $credentials = ['email'=>'chanzewail@gmail.com', 'password' => '123456']; 411 | $token = JWT::attempt($credentials); 412 | } 413 | } 414 | ``` 415 | 416 | 这里使用了 email 和 password 来验证用户是否合法,如果你的用户是通过 mobile或其它字段作为标识,那么可以在 app\index\model\User 模型中,添加 jwtSub 字段: 417 | 418 | ```php 419 | namespace app\index\model; 420 | 421 | use think\Model; 422 | 423 | class User extends Model 424 | { 425 | public $jwtSub = 'mobile'; 426 | } 427 | ``` 428 | 429 | 当然,如果你的密码 不是用的 password (绝大多数都用这个,不排除少数奇怪的命名….),那么你可以添加 jwtPassword 字段: 430 | 431 | ```php 432 | public $jwtPassword = 'strange_password'; 433 | ``` 434 | 435 | 这里验证psssword默认使用md5加密,绝大多数情况下这是不够安全的,很多都有自定义的加密方式,那么还有验证密码的方法,添加: 436 | 437 | ```php 438 | public function jwtEncrypt($password) 439 | { 440 | // 只要返回你加密后的结果,会自动比对数据库字段 441 | return md5(sha1($password)); 442 | } 443 | ``` 444 | 445 | 还可以直接通过用户对象实例创建token 446 | 447 | ```php 448 | $user = User::get(1); 449 | $token = JWT::fromUser($user); 450 | ``` 451 | 452 | 还可以自定义 Payload 创建任意数据 453 | 454 | ```php 455 | $customClaims = ['foo' => 'bar', 'baz' => 'bob']; 456 | $payload = JWT::makePayload($customClaims); 457 | $token = JWT::encode($payload); 458 | ``` 459 | 460 | ### 用户认证 461 | 462 | 要通过http发送一个需要认证通过的请求,需要设置Authorization头 463 | 464 | ```php 465 | Authorization: Bearer {token} 466 | ``` 467 | 468 | 或者将token信息包含到URL中 469 | 470 | ```php 471 | http://api.example.com/news?token={token} 472 | ``` 473 | 474 | ##### 解析token 475 | 476 | `resolveToken`方法可以将token还原为payload数组 477 | 478 | ```php 479 | $payload = JWT::resolveToken(); 480 | // ['foo' => 'bar', 'baz' => 'bob'] 481 | ``` 482 | 483 | 如果是从user模型创建的token,那么还可以使用authenticate方法,直接验证用户,成功后返回用户模型,失败返回false 484 | 485 | ```php 486 | if ($user = JWT::authenticate()) { 487 | // todo 488 | } 489 | ``` 490 | 491 | 当然还有更加手动的方法 492 | 493 | ```php 494 | // 从请求中获取token 495 | $token = JWT::getToken(); 496 | 497 | // todo 498 | 499 | // 对token进行解码 500 | $payload = JWT::decode($token); 501 | ``` 502 | 503 | 504 | ## 配置 505 | 506 | 该扩展包共有3个配置文件 507 | > 配置文件仅支持全局配置目录使用 508 | 509 | - `api.php`:管理接口配置 510 | - `resources.php`:过滤管理器配置 511 | - `jwt.php`:JWT相关配置 512 | 513 | 配置文件可以在`vendor/zewail/think-api/config`目录下找到,也可以手动创建它们 514 | 515 | ### api.php 516 | 517 | ```php 518 | retrun [ 519 | //配置项 520 | ]; 521 | ``` 522 | 523 | ##### version 524 | 525 | api的默认版本 526 | 527 | ##### serializer 528 | 529 | api返回的数据格式,可选: 530 | 531 | - `DataArray`:数组带data格式,默认值 532 | 533 | ```txt 534 | // item 535 | { 536 | 'data': [...], 537 | 'meta': [...], 538 | ... 539 | } 540 | 541 | // collection 542 | { 543 | 'data': [ 544 | {...}, 545 | {...}, 546 | {...}, 547 | ], 548 | 'meta': [...], 549 | ... 550 | } 551 | ``` 552 | 553 | - `Array`:数组格式 554 | 555 | ```txt 556 | // item 557 | { 558 | 'item_field1': '...', 559 | 'item_field2': '...', 560 | 'meta': [...], 561 | ... 562 | } 563 | 564 | // collection 565 | { 566 | [ 567 | {...}, 568 | {...}, 569 | ], 570 | 'meta': [...], 571 | ... 572 | } 573 | ``` 574 | 575 | ​ 576 | 577 | ### resources.php 578 | 579 | ```php 580 | retrun [ 581 | //配置项 582 | // 用户相关接口 583 | // 例如设置一些用户的相关接口资源 584 | 'user.age' => ['id', 'name', 'age'], 585 | 'user.mobile' => ['id', 'name', 'mobile'], 586 | ]; 587 | ``` 588 | 589 | 该配置文件用于数据过滤管理,在返回接口数据的时候在`item` `collection` `paginator`第二个参数传入该标识来使用 590 | 591 | ```php 592 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'age': 20}} 593 | return $this->response->item($user, 'user.age'); 594 | // 返回{'data': {'id':1, 'name': 'xiaoming', 'mobile': '13777777777'}} 595 | return $this->response->item($user, 'user.mobile'); 596 | ``` 597 | 598 | 599 | 600 | ### jwt.php 601 | 602 | ```php 603 | retrun [ 604 | //配置项 605 | ]; 606 | ``` 607 | 608 | ##### ttl 609 | 610 | token的过期时间, 默认为120分钟,单位分钟 611 | 612 | ##### deviation 613 | 614 | 允许误差时间,默认为60秒,单位秒 615 | 616 | ##### algorithm 617 | 618 | 加密算法,支持: 619 | 620 | - `HS256`: `HMAC 使用 SHA-256 算法加密` 621 | - `HS512`: `HMAC 使用 SHA-512 算法加密` 622 | - `RS256`: `RSA 使用 SHA-256 算法加密` 623 | 624 | ##### key 625 | 626 | 如果使用了HMAC加密方式,则需要配置该项,为自定义字符串 627 | 628 | ##### privateKeyPath 629 | 630 | 如果使用了RSA加密方式,则需要配置该项,为`.pem`结尾的私钥文件路径 631 | 632 | ##### publicKeyPath 633 | 634 | 如果使用了RSA加密方式,则需要配置该项,为`.pem`结尾的公钥文件路径 635 | 636 | ##### user 637 | 638 | 如果需要使用用户操作相关方法,则需定义该项,为用户模型所在路径,如 639 | 640 | ```php 641 | 'user' => app\index\model\User::class, 642 | ``` 643 | 644 | 645 | 646 | ## 授权协议 647 | 648 | [MIT license](LICENSE) 649 | 650 | 651 | ## CHANGELOG 652 | #### 1.1.0-beta1 653 | - 修复 php7 以下环境调用`response::array`报错的问题 654 | - 添加全新状态码别名方法调用 655 | - 移除动词方法别名调用生成Http异常(现在统一使用状态码别名) 656 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zewail/think-api", 3 | "type": "library", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "陈泽韦", 8 | "email": "chanzewail@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.6" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Zewail\\Api\\": "src/" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/api.php: -------------------------------------------------------------------------------- 1 | 'v1', 5 | // 可选 DataArray, Array 6 | 'serializer' => 'DataArray', 7 | ]; -------------------------------------------------------------------------------- /config/jwt.php: -------------------------------------------------------------------------------- 1 | 'HS256', 5 | // HMAC算法使用的加密字符串 6 | 'key' => 'ex-key', 7 | // RSA算法使用的私钥文件路径 8 | 'privateKeyPath' => '/home/rsa_private_key.pem', 9 | // RSA算法使用的公钥文件路径 10 | 'publicKeyPath' => '/home/rsa_public_key.pem', 11 | // 误差时间,单位秒 12 | 'deviation' => 60, 13 | // 过期时间, 单位分钟 14 | 'ttl' => 120, 15 | // 用户模型路径 16 | 'user' => app\index\model\User::class, 17 | ]; -------------------------------------------------------------------------------- /config/resources.php: -------------------------------------------------------------------------------- 1 | ['id', 'name'] 4 | ]; -------------------------------------------------------------------------------- /src/Api.php: -------------------------------------------------------------------------------- 1 | 10 | * @license https://opensource.org/licenses/MIT MIT 11 | * @link https://github.com/czewail/think-api 12 | */ 13 | trait Api 14 | { 15 | 16 | protected $response; 17 | protected $jwt; 18 | 19 | function __construct() 20 | { 21 | $this->init(); 22 | } 23 | 24 | protected function init() { 25 | $this->response = new ResponseFactory; 26 | $this->jwt = new JWTFactory; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Exceptions/HandleException.php: -------------------------------------------------------------------------------- 1 | 10 | * @license https://opensource.org/licenses/MIT MIT 11 | * @link https://github.com/czewail/think-api 12 | */ 13 | class HandleException extends Handle 14 | { 15 | /** 16 | * render 17 | */ 18 | public function render(Exception $e) 19 | { 20 | // Token 授权失败的异常 21 | if ($e instanceof TokenExpiredException || $e instanceof TokenInvalidException || $e instanceof UnauthenticateException) { 22 | return new Response(['message' => $e->getMessage(), 'status_code' => 401], 401); 23 | } 24 | 25 | if ($e instanceof JWTException) { 26 | return new Response(['message' => $e->getMessage(), 'status_code' => 500], 500); 27 | } 28 | 29 | // http状态码异常 30 | if ($e instanceof ResponseException) { 31 | return new Response(['message' => $e->getMessage(), 'status_code' => $e->getStatusCode()], $e->getStatusCode()); 32 | } 33 | // 其他错误交给系统处理 34 | return parent::render($e); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Exceptions/JWTException.php: -------------------------------------------------------------------------------- 1 | 5 | * @license https://opensource.org/licenses/MIT MIT 6 | * @link https://github.com/czewail/think-api 7 | */ 8 | class JWTException extends \Exception 9 | { 10 | // 11 | } -------------------------------------------------------------------------------- /src/Exceptions/ResponseException.php: -------------------------------------------------------------------------------- 1 | 7 | * @license https://opensource.org/licenses/MIT MIT 8 | * @link https://github.com/czewail/think-api 9 | */ 10 | class ResponseException extends HttpException 11 | { 12 | 13 | } -------------------------------------------------------------------------------- /src/Exceptions/TokenExpiredException.php: -------------------------------------------------------------------------------- 1 | 5 | * @license https://opensource.org/licenses/MIT MIT 6 | * @link https://github.com/czewail/think-api 7 | */ 8 | class TokenExpiredException extends JWTException 9 | { 10 | } -------------------------------------------------------------------------------- /src/Exceptions/TokenInvalidException.php: -------------------------------------------------------------------------------- 1 | 5 | * @license https://opensource.org/licenses/MIT MIT 6 | * @link https://github.com/czewail/think-api 7 | */ 8 | class TokenInvalidException extends JWTException 9 | { 10 | // 11 | } -------------------------------------------------------------------------------- /src/Exceptions/TokenNotBeforeException.php: -------------------------------------------------------------------------------- 1 | 5 | * @license https://opensource.org/licenses/MIT MIT 6 | * @link https://github.com/czewail/think-api 7 | */ 8 | class TokenNotBeforeException extends JWTException 9 | { 10 | // 11 | } -------------------------------------------------------------------------------- /src/Exceptions/TypeErrorException.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class TypeErrorException extends \Exception 10 | { 11 | // 12 | } -------------------------------------------------------------------------------- /src/Exceptions/UnauthenticateException.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class UnauthenticateException extends JWTException 10 | { 11 | // 12 | } -------------------------------------------------------------------------------- /src/Facades/ApiRoute.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class ApiRoute extends Facade 12 | { 13 | protected static function getFacadeClass() 14 | { 15 | return 'Zewail\Api\Routing\Router'; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Facades/JWT.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class JWT extends Facade 12 | { 13 | protected static function getFacadeClass() 14 | { 15 | return 'Zewail\Api\JWT\Factory'; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Facades/Response.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class Response extends Facade 12 | { 13 | protected static function getFacadeClass() 14 | { 15 | return 'Zewail\Api\Response\Factory'; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Http/Response.php: -------------------------------------------------------------------------------- 1 | 11 | * @license https://opensource.org/licenses/MIT MIT 12 | * @link https://github.com/czewail/think-api 13 | */ 14 | class Response extends JsonResponse 15 | { 16 | /** 17 | * 附加 meta 信息 18 | * 19 | * @var array 20 | */ 21 | private $meta = []; 22 | 23 | /** 24 | * 其他附加信息集合 25 | * 26 | * @var array 27 | */ 28 | private $adds = []; 29 | 30 | /** 31 | * 可用格式数组 32 | * @var array 33 | */ 34 | private $serializers = [ 35 | 'DataArray' => DataArraySerializer::class, 36 | 'Array' => ArraySerializer::class, 37 | ]; 38 | 39 | /** 40 | * 默认格式 41 | * @var League\Fractal\Serializer\ArraySerializer::class 42 | */ 43 | private $serializer = DataArraySerializer::class; 44 | 45 | /** 46 | * 配置 47 | * @var array 48 | */ 49 | private $config = []; 50 | 51 | /** 52 | * [__construct] 53 | * 54 | * @param string $content 55 | * @param integer $code 56 | * @param array $header 57 | */ 58 | public function __construct($content = '', $code = 200, array $header = []) 59 | { 60 | // 读取默认配置文件 61 | $configPath = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'api.php'; 62 | $config = require($configPath); 63 | 64 | // 读取tp配置文件 65 | $_config = Config::pull('api'); 66 | 67 | if ($config && is_array($config)) { 68 | $this->config = array_merge($config, $_config); 69 | $this->serializer = isset($this->serializers[$this->config['serializer']]) ? $this->serializers[$this->config['serializer']] : $this->serializers['DataArray']; 70 | } else { 71 | $this->config = $config; 72 | } 73 | 74 | parent::__construct($content, $code, $header); 75 | } 76 | 77 | /** 78 | * 处理数据 79 | * 80 | * @param $data 81 | * @return think\response\Json 82 | */ 83 | protected function output($data) 84 | { 85 | $serializer = new $this->serializer($data, $this->meta, $this->adds); 86 | return parent::output($serializer->get()); 87 | } 88 | 89 | /** 90 | * 设置 serializer 91 | * 92 | * @param [type] 93 | * @return [type] 94 | */ 95 | public function serializer($serializer = null) 96 | { 97 | if ($serializer) { 98 | $this->serializer = array_key_exists($serializer, $this->serializers) ? $this->serializers[$serializer] : $this->serializer; 99 | } 100 | return $this; 101 | } 102 | 103 | /** 104 | * 添加自定义数据 105 | * @param [type] $label [description] 106 | * @param string $value [description] 107 | */ 108 | public function add($label, $value = '') 109 | { 110 | $this->adds[$label] = $value; 111 | return $this; 112 | } 113 | 114 | /** 115 | * 添加 Meta 信息 116 | * 117 | * @param string $name 118 | * @param string $value 119 | */ 120 | public function addMeta($name, $value = '') 121 | { 122 | $this->meta[$name] = $value; 123 | return $this; 124 | } 125 | 126 | /** 127 | * 批量设置 Meta 信息 128 | * 129 | * @param array $meta 130 | */ 131 | public function setMeta(array $meta = []) 132 | { 133 | $this->meta = $meta; 134 | return $this; 135 | } 136 | 137 | /** 138 | * 设置 respone 的头部信息 139 | * 140 | * @param [string] $name 141 | * @param [mixed] $value 142 | */ 143 | public function addHeader($name, $value = null) { 144 | return $this->header($name, $value); 145 | } 146 | 147 | /** 148 | * 设置状态码 149 | * 150 | * @param [number] $code 151 | */ 152 | public function setCode($code) 153 | { 154 | return $this->code($code); 155 | } 156 | 157 | /** 158 | * LastModified 159 | * 160 | * @param [time] $time 161 | */ 162 | public function setLastModified($time) 163 | { 164 | return $this->lastModified($time); 165 | } 166 | 167 | /** 168 | * ETag 169 | * 170 | * @param [type] $eTag 171 | */ 172 | public function setETag($eTag) 173 | { 174 | return $this->eTag($eTag); 175 | } 176 | 177 | /** 178 | * expires 179 | * 180 | * @param [type] $time 181 | */ 182 | public function setExpires($time) 183 | { 184 | return $this->expires($time); 185 | } 186 | 187 | /** 188 | * 页面缓存控制 189 | * @param string $cache 190 | * @return $this 191 | */ 192 | public function setCacheControl($cache) 193 | { 194 | return $this->cacheControl($cache); 195 | } 196 | 197 | /** 198 | * 页面输出类型 199 | * 200 | * @param string $contentType 201 | * @param string $charset 202 | * @return $this 203 | */ 204 | public function setContentType($contentType, $charset = 'utf-8') 205 | { 206 | return $this->contentType($contentType, $charset); 207 | } 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Audience.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Audience extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'aud'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Claim.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Claim 10 | { 11 | /** 12 | * name 13 | * 14 | * @var string 15 | */ 16 | protected $name; 17 | 18 | /** 19 | * value 20 | * 21 | * @var string 22 | */ 23 | protected $value; 24 | 25 | public function __construct($value) 26 | { 27 | $this->setValue($value); 28 | } 29 | /** 30 | * 设置Name 31 | * 32 | * @param mixed $name 33 | * @return $this 34 | */ 35 | public function setName($name) 36 | { 37 | $this->name = $name; 38 | return $this; 39 | } 40 | 41 | /** 42 | * 获取Name 43 | * 44 | * @return mixed 45 | */ 46 | public function getName() 47 | { 48 | return $this->name; 49 | } 50 | 51 | /** 52 | * 设置Value 53 | * 54 | * @param mixed $name 55 | * @return $this 56 | */ 57 | public function setValue($value) 58 | { 59 | $this->value = $value; 60 | return $this; 61 | } 62 | 63 | /** 64 | * 获取Value 65 | * 66 | * @return mixed 67 | */ 68 | public function getValue() 69 | { 70 | return $this->value; 71 | } 72 | 73 | /** 74 | * 转为数组 75 | * 76 | * @return array 77 | */ 78 | public function toArray() 79 | { 80 | return [$this->getName() => $this->getValue()]; 81 | } 82 | } -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Collection.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class Collection extends ThinkCollection 12 | { 13 | 14 | } -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Custom.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Custom extends Claim 10 | { 11 | protected $name; 12 | 13 | public function __construct($name, $value) 14 | { 15 | $this->name = $name; 16 | parent::__construct($value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Expiration.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class Expiration extends Claim 12 | { 13 | /** 14 | * Name 15 | */ 16 | protected $name = 'exp'; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/IssuedAt.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class IssuedAt extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'iat'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Issuer.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Issuer extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'iss'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/JwtId.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class JwtId extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'jti'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/NotBefore.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class NotBefore extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'nbf'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Claims/Subject.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Subject extends Claim 10 | { 11 | /** 12 | * Name 13 | */ 14 | protected $name = 'sub'; 15 | } 16 | -------------------------------------------------------------------------------- /src/JWT/Factories/Code.php: -------------------------------------------------------------------------------- 1 | 14 | * @license https://opensource.org/licenses/MIT MIT 15 | * @link https://github.com/czewail/think-api 16 | */ 17 | class Code 18 | { 19 | // 配置文件信息 20 | protected $config; 21 | 22 | // 可用加密算法 23 | protected $algorithms = [ 24 | 'HS256' => ['hash', 'SHA256'], 25 | 'HS512' => ['hash', 'SHA512'], 26 | 'RS256' => ['openssl', 'SHA256'], 27 | ]; 28 | 29 | // 默认加密算法 30 | protected $algorithm = 'HS256'; 31 | 32 | // 默认hash_hmac加密私钥 33 | protected $key; 34 | 35 | // 默认openssl加密私钥路径 36 | protected $privateKeyPath; 37 | // 默认openssl加密公钥路径 38 | protected $publicKeyPath; 39 | 40 | // 默认openssl加密私钥 41 | protected $privateKey; 42 | // 默认openssl加密公钥 43 | protected $publicKey; 44 | 45 | protected $deviation = 0; 46 | 47 | 48 | function __construct() 49 | { 50 | $this->init(); 51 | } 52 | 53 | /** 54 | * 读取配置文件 55 | */ 56 | protected function init() 57 | { 58 | Set::api(function($config) { 59 | $this->config = $config; 60 | }); 61 | 62 | if (isset($this->config['algorithm']) && $this->config['algorithm']) { 63 | $this->algorithm = $config['algorithm']; 64 | } 65 | if (isset($config['key']) && $config['key']) { 66 | $this->key = $config['key']; 67 | } 68 | 69 | if (isset($this->config['deviation']) && $this->config['deviation']) { 70 | $this->deviation = $config['deviation']; 71 | } 72 | 73 | if (isset($this->config['privateKeyPath']) && $this->config['privateKeyPath'] && isset($this->config['publicKeyPath']) && $this->config['publicKeyPath']) { 74 | $this->privateKeyPath = 'file://' . $this->config['privateKeyPath']; 75 | $this->publicKeyPath = 'file://' . $this->config['publicKeyPath']; 76 | } 77 | 78 | if (empty($this->algorithms[$this->algorithm])) { 79 | throw new JWTException('加密算法不支持'); 80 | } 81 | 82 | // 检查openssl支持和配置正确性 83 | if ('openssl' === $this->algorithms[$this->algorithm][0]) { 84 | if (!extension_loaded('openssl')) { 85 | throw new JWTException('php需要openssl扩展支持'); 86 | } 87 | if (!file_exists($this->privateKeyPath) || !file_exists($this->publicKeyPath)) { 88 | throw new JWTException('密钥或者公钥的文件路径不正确'); 89 | } 90 | // 读取公钥和私钥 91 | $this->privateKey = openssl_pkey_get_private($this->privateKeyPath); 92 | $this->publicKey = openssl_pkey_get_public($this->publicKeyPath); 93 | } 94 | } 95 | 96 | 97 | public function decode($token, $key = null, $algorithm = null) 98 | { 99 | 100 | if ($key) { 101 | $this->key = $key; 102 | } else { 103 | if ('openssl' === $this->algorithms[$this->algorithm][0]) { 104 | $this->key = $this->publicKey; 105 | } 106 | } 107 | if ($algorithm) $this->algorithm = $algorithm; 108 | 109 | $segments = explode('.', $token); 110 | 111 | if (count($segments) != 3) { 112 | throw new JWTException('Token文本错误'); 113 | } 114 | 115 | list($header64, $payload64, $signature64) = $segments; 116 | 117 | // 获取3个片段 118 | $header = json_decode(UrlSafeBase64::decode($header64), false, 512, JSON_BIGINT_AS_STRING); 119 | $payload = json_decode(UrlSafeBase64::decode($payload64), false, 512, JSON_BIGINT_AS_STRING); 120 | $signature = UrlSafeBase64::decode($signature64); 121 | 122 | // 验证签名 123 | if (!$this->verify("$header64.$payload64", $signature)) { 124 | throw new TokenInvalidException('无效的 Token'); 125 | } 126 | 127 | // 在什么时间之前,该jwt都是不可用的 128 | if (isset($payload->nbf) && $payload->nbf > (time() + $this->deviation)) { 129 | throw new TokenNotBeforeException('该 Token 无法在当前时间使用'); 130 | } 131 | 132 | // 检查是否过期 133 | if (isset($payload->exp) && (time() - $this->deviation) >= $payload->exp) { 134 | throw new TokenExpiredException('该 Token 已过期'); 135 | } 136 | 137 | return $payload; 138 | } 139 | 140 | /** 141 | * 加密 142 | */ 143 | public function encode($payload, $key = null, $algorithm = null) 144 | { 145 | if ($key) { 146 | $this->key = $key; 147 | } else { 148 | if ('openssl' === $this->algorithms[$this->algorithm][0]) { 149 | $this->key = $this->privateKey; 150 | } 151 | } 152 | if ($algorithm) $this->algorithm = $algorithm; 153 | 154 | $header = ['typ' => 'JWT', 'alg' => $this->algorithm]; 155 | $segments = []; 156 | // 编码第一部分 header 157 | $segments[] = UrlSafeBase64::encode(json_encode($header)); 158 | // 编码第二部分 payload 159 | $segments[] = UrlSafeBase64::encode(json_encode($payload)); 160 | 161 | // 第三部分为header和payload signature 162 | $signature_string = implode('.', $segments); 163 | $signature = $this->signature($signature_string); 164 | // 加密第三部分 165 | $segments[] = UrlSafeBase64::encode($signature); 166 | 167 | return implode('.', $segments); 168 | } 169 | 170 | /** 171 | * jwt 第三部分签名 172 | * @param [type] $data [description] 173 | * @param [type] $key [description] 174 | * @param [type] $algorithm [description] 175 | * @return [type] [description] 176 | */ 177 | public function signature($data) 178 | { 179 | list($func, $alg) = $this->algorithms[$this->algorithm]; 180 | 181 | switch($func) { 182 | // hash_hmac 加密 183 | case 'hash': 184 | return hash_hmac($alg, $data, $this->key, true); 185 | // openssl 加密 186 | case 'openssl': 187 | $sign = ''; 188 | $ssl = openssl_sign($data, $sign, $this->key, $alg); 189 | if (!$ssl) { 190 | throw new JWTException("OpenSSL无法签名数据"); 191 | } 192 | return $sign; 193 | } 194 | } 195 | 196 | public function verify($data, $signature) 197 | { 198 | list($func, $alg) = $this->algorithms[$this->algorithm]; 199 | 200 | switch($func) { 201 | case 'hash': 202 | $hash = hash_hmac($alg, $data, $this->key, true); 203 | return hash_equals($signature, $hash); 204 | case 'openssl': 205 | $isVerify = openssl_verify($data, $signature, $this->key, $alg); 206 | if (!$isVerify) { 207 | return false; 208 | } 209 | return $signature; 210 | } 211 | return false; 212 | } 213 | } 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/JWT/Factories/Payload.php: -------------------------------------------------------------------------------- 1 | Audience::class, 23 | 'exp' => Expiration::class, 24 | 'iat' => IssuedAt::class, 25 | 'iss' => Issuer::class, 26 | 'jti' => JwtId::class, 27 | 'nbf' => NotBefore::class, 28 | 'sub' => Subject::class, 29 | ]; 30 | 31 | public function __construct($config) 32 | { 33 | if (isset($config['ttl']) && $config['ttl'] >= 0) { 34 | $this->setTTL($config['ttl']); 35 | } 36 | $this->request = Container::get('request'); 37 | } 38 | 39 | /** 40 | * 是否存在 41 | * @param [type] $name [description] 42 | * @return boolean [description] 43 | */ 44 | public function has($name) 45 | { 46 | return array_key_exists($name, $this->classMap); 47 | } 48 | 49 | /** 50 | * 通过名称调用方法创建对象 51 | * @param [type] $name [description] 52 | * @return [type] [description] 53 | */ 54 | public function make($name) 55 | { 56 | if ($this->has($name)) { 57 | return new $this->classMap[$name]($this->$name()); 58 | } 59 | } 60 | 61 | /** 62 | * Issuer 63 | * 64 | * @return url 65 | */ 66 | public function iss() 67 | { 68 | return $this->request->domain() . $this->request->baseUrl(); 69 | } 70 | 71 | /** 72 | * IssuedAt 73 | * 74 | * @return Timestamp 75 | */ 76 | public function iat() 77 | { 78 | $datetime = new DateTime; 79 | // return $datetime->format('Y-m-d H:i:s'); 80 | return $datetime->getTimestamp(); 81 | } 82 | 83 | /** 84 | * Expiration 85 | * 86 | * @return Timestamp 87 | */ 88 | public function exp() 89 | { 90 | $datetime = new DateTime; 91 | $datetime->add(new DateInterval('PT' . $this->ttl . 'M')); 92 | return $datetime->getTimestamp(); 93 | } 94 | 95 | /** 96 | * NotBefore 97 | * 98 | * @return Timestamp 99 | */ 100 | public function nbf() 101 | { 102 | $datetime = new DateTime; 103 | return $datetime->getTimestamp(); 104 | } 105 | 106 | /** 107 | * JwtId 108 | * 109 | * @return string 110 | */ 111 | public function jti() 112 | { 113 | $length = 16; 114 | $string = ''; 115 | while (($len = strlen($string)) < $length) { 116 | $size = $length - $len; 117 | $bytes = random_bytes($size); 118 | $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); 119 | } 120 | return $string; 121 | } 122 | 123 | public function setTTL($ttl) 124 | { 125 | $this->ttl = $ttl; 126 | 127 | return $this; 128 | } 129 | 130 | public function getTTL() 131 | { 132 | return $this->ttl; 133 | } 134 | 135 | 136 | } -------------------------------------------------------------------------------- /src/JWT/Factory.php: -------------------------------------------------------------------------------- 1 | 18 | * @license https://opensource.org/licenses/MIT MIT 19 | * @link https://github.com/czewail/think-api 20 | */ 21 | class Factory 22 | { 23 | 24 | protected $PayloadFactory; 25 | 26 | protected $defaultClaims = [ 27 | 'iss', 28 | 'iat', 29 | 'exp', 30 | 'nbf', 31 | 'jti', 32 | ]; 33 | 34 | protected $claims; 35 | 36 | protected $config = []; 37 | 38 | 39 | function __construct() 40 | { 41 | Set::jwt(function($config) { 42 | $this->config = $config; 43 | }); 44 | $this->PayloadFactory = new PayloadFactory($this->config); 45 | $this->claims = new Collection; 46 | } 47 | 48 | /** 49 | * 验证账号 50 | * @param array $credentials [description] 51 | * @return [type] [description] 52 | */ 53 | public function attempt(array $credentials, array $customClaims = []) 54 | { 55 | $userModel = new $this->config['user']; 56 | 57 | // jwtSub属性不存在则使用email 58 | $subField = $this->getModelSub($userModel); 59 | $pwdField = $this->getModelPwd($userModel); 60 | // 查询模型 61 | $user = $userModel->where($subField, $credentials[$subField])->find(); 62 | if ($user) { 63 | // 获取加密后的密码 64 | if (method_exists($userModel, 'jwtEncrypt')) { 65 | $inputPwd = $userModel->jwtEncrypt($credentials[$pwdField], $user); 66 | } else { 67 | $inputPwd = md5($credentials[$pwdField]); 68 | } 69 | // 验证密码 70 | if ($inputPwd !== $user->$pwdField) { 71 | throw new UnauthenticateException('账号验证失败'); 72 | } 73 | return $this->fromSubject(new Subject($user->$subField), $customClaims); 74 | } else { 75 | throw new UnauthenticateException('账号不存在'); 76 | } 77 | } 78 | 79 | /** 80 | * 从已认证的用户创建token 81 | * @param Model $user [description] 82 | * @return [type] [description] 83 | */ 84 | public function fromUser(Model $user, array $customClaims = []) 85 | { 86 | // jwtSub属性不存在则使用email 87 | $subField = isset($user->jwtSub) ? $user->jwtSub : 'email'; 88 | return $this->fromSubject(new Subject($user->$subField), $customClaims); 89 | } 90 | 91 | /** 92 | * 将payload加密成为token 93 | * @param Payload $payload [description] 94 | * @return [type] [description] 95 | */ 96 | public function encode(Payload $payload) 97 | { 98 | $code = new Code; 99 | return $code->encode($payload->toArray()); 100 | } 101 | /** 102 | * 将payload加密成为token 103 | * @param Payload $payload [description] 104 | * @return [type] [description] 105 | */ 106 | public function decode($token) 107 | { 108 | $code = new Code; 109 | return (array) $code->decode($token); 110 | } 111 | 112 | /** 113 | * 创建payload对象 114 | * @param array $customClaims [description] 115 | * @return [type] [description] 116 | */ 117 | public function makePayload(array $customClaims = []) 118 | { 119 | foreach ($customClaims as $key => $custom) { 120 | $paload = new Custom($key, $custom); 121 | $this->claims->unshift($paload->getValue(), $paload->getName()); 122 | } 123 | return new Payload($this->claims); 124 | } 125 | 126 | /** 127 | * 解析token 128 | * @return [type] [description] 129 | */ 130 | public function resolveToken() 131 | { 132 | $code = new Code; 133 | if ($token = $this->getToken()) { 134 | $payload = $code->decode($token); 135 | return (array) $payload; 136 | } 137 | return false; 138 | } 139 | 140 | /** 141 | * 验证并返回用户模型 142 | * @return [type] [description] 143 | */ 144 | public function authenticate() 145 | { 146 | $payload = $this->resolveToken(); 147 | if ($payload && isset($payload['sub'])) { 148 | $userModel = new $this->config['user']; 149 | // jwtSub属性不存在则使用email 150 | $subField = $this->getModelSub($userModel); 151 | // 查询模型 152 | $user = $userModel->where($subField, $payload['sub'])->find(); 153 | return $user; 154 | } 155 | return false; 156 | } 157 | 158 | /** 159 | * 从请求中获取token 160 | * @return [type] [description] 161 | */ 162 | public function getToken() 163 | { 164 | if ($Authorization = Request::header('Authorization')) { 165 | $authArr = explode(' ', $Authorization); 166 | if ( isset($authArr[0]) && $authArr[0] === 'Bearer') { 167 | if (isset($authArr[1])) { 168 | return $authArr[1]; 169 | } 170 | } 171 | } else if (Request::has('token')) { 172 | return Request::get('token'); 173 | } 174 | return false; 175 | } 176 | 177 | /** 178 | * 获取sub字段 179 | * @return [type] [description] 180 | */ 181 | protected function getModelSub(Model $userModel) 182 | { 183 | return isset($userModel->jwtSub) ? $userModel->jwtSub : 'email'; 184 | } 185 | 186 | /** 187 | * 获取pwd字段 188 | * @return [type] [description] 189 | */ 190 | protected function getModelPwd(Model $userModel) 191 | { 192 | return isset($userModel->jwtPassword) ? $userModel->jwtPassword : 'password'; 193 | } 194 | 195 | 196 | /** 197 | * 增加sub并构建Claims 198 | * @param Subject $sub [description] 199 | * @return [type] [description] 200 | */ 201 | protected function fromSubject(Subject $sub, array $customClaims = []) 202 | { 203 | $this->buildDefaultClaims($customClaims); 204 | $this->claims->unshift($sub->getValue(), $sub->getName()); 205 | return $this->encode(new Payload($this->claims)); 206 | } 207 | 208 | 209 | /** 210 | * 构建默认Claims 211 | * @return [type] [description] 212 | */ 213 | private function buildDefaultClaims(array $customClaims = []) 214 | { 215 | // 如果过期时间未设置则删除过期时间 216 | if ($this->PayloadFactory->getTTL() === null && $key = array_search('exp', $this->defaultClaims)) { 217 | unset($this->defaultClaims[$key]); 218 | } 219 | // 遍历默认输入 220 | foreach ($this->defaultClaims as $claim) { 221 | // $this->claims[$claim] = $this->PayloadFactory->make($claim); 222 | $paload = $this->PayloadFactory->make($claim); 223 | $this->claims->unshift($paload->getValue(), $paload->getName()); 224 | } 225 | // 遍历自定义输出 226 | foreach ($customClaims as $key => $custom) { 227 | $paload = new Custom($key, $custom); 228 | $this->claims->unshift($paload->getValue(), $paload->getName()); 229 | } 230 | return $this; 231 | } 232 | } 233 | 234 | 235 | -------------------------------------------------------------------------------- /src/JWT/Library/UrlSafeBase64.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class UrlSafeBase64 10 | { 11 | /** 12 | * 对url进行base64编码 13 | * 14 | * @param string 15 | * @return string 16 | */ 17 | public static function encode($data) 18 | { 19 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 20 | } 21 | 22 | /** 23 | * 对url进行base64解码 24 | * 25 | * @param string 26 | * @return string 27 | */ 28 | public static function decode($data) 29 | { 30 | return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 31 | } 32 | } -------------------------------------------------------------------------------- /src/JWT/Payload.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class Payload 12 | { 13 | private $claims; 14 | 15 | public function __construct(Collection $claims) 16 | { 17 | $this->claims = $claims; 18 | } 19 | 20 | // 转为数组 21 | public function toArray() 22 | { 23 | return $this->claims->toArray(); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Response/Factory.php: -------------------------------------------------------------------------------- 1 | 17 | * @license https://opensource.org/licenses/MIT MIT 18 | * @link https://github.com/czewail/think-api 19 | */ 20 | class Factory extends Status 21 | { 22 | // use Status { 23 | // Status::__construct as private __StatusConstruct; 24 | // } 25 | // 过滤器配置 26 | protected $resources = []; 27 | 28 | // 需要保留的字段 29 | protected $only = null; 30 | 31 | // 需要排除的字段 32 | protected $except = null; 33 | 34 | // 构造方法 35 | public function __construct() 36 | { 37 | // $this->__StatusConstruct(); 38 | parent::__construct(); 39 | Set::resources(function($config) { 40 | $this->resources = $config; 41 | }); 42 | } 43 | 44 | /** 45 | * 过滤单个模型 46 | * @param [type] $item [description] 47 | * @param [type] $filter [description] 48 | * @return [type] [description] 49 | */ 50 | protected function filterItem($item, $filter = null) 51 | { 52 | // 模型数组 53 | $item_array = $item->toArray(); 54 | // 过滤的交集, 默认所有 55 | $result = $item_array; 56 | // 存在方法内过滤参数,覆盖only与except方法 57 | if ($filter) { 58 | if (is_array($filter)) { 59 | $result = array_intersect_key($item_array, array_flip($filter)); 60 | } else if(is_string($filter)) { 61 | if (is_array($this->resources) && !empty($this->resources[$filter]) && array_key_exists($filter, $this->resources)) { 62 | $result = array_intersect_key($item_array, array_flip($this->resources[$filter])); 63 | } 64 | } 65 | } else { 66 | if (is_array($this->only)) { 67 | $result = array_intersect_key($item_array, array_flip($this->only)); 68 | } else if (is_string($this->only)) { 69 | if (is_array($this->resources) && !empty($this->resources[$this->only]) && array_key_exists($this->only, $this->resources)) { 70 | $result = array_intersect_key($item_array, array_flip($this->resources[$this->only])); 71 | } 72 | } 73 | if (is_array($this->except)) { 74 | $result = array_diff_key($result, array_flip($this->except)); 75 | } else if (is_string($this->except)) { 76 | if (is_array($this->resources) && !empty($this->resources[$this->except]) && array_key_exists($this->except, $this->resources)) { 77 | $result = array_diff_key($result, array_flip($this->resources[$this->except])); 78 | } 79 | } 80 | } 81 | return $result; 82 | } 83 | 84 | /** 85 | * 设置需要保留的字段 86 | * 87 | * @param array 88 | * @return Zewail\Api\Http\Response 89 | */ 90 | public function only($filter = null) 91 | { 92 | $this->only = $filter; 93 | return $this; 94 | } 95 | 96 | /** 97 | * 设置需要排除的字段 98 | * 99 | * @param array 100 | * @return Zewail\Api\Http\Response 101 | */ 102 | public function except($filter = null) 103 | { 104 | $this->except = $filter; 105 | return $this; 106 | } 107 | 108 | /** 109 | * 单个模型的响应 110 | * 111 | * @param think\Model 112 | * @param $filter 113 | * @return Zewail\Api\Http\Response 114 | */ 115 | public function item($item, $filter = null) 116 | { 117 | // 判断是否是Model实例 118 | if ($item instanceof Model) { 119 | $response = $this->filterItem($item, $filter); 120 | } else { 121 | throw new TypeErrorException("this is not a Model instance", 1); 122 | } 123 | 124 | return new Response($response); 125 | } 126 | 127 | /** 128 | * 多个模型的响应 129 | * 130 | * @param think\Model 131 | * @return Zewail\Api\Http\Response 132 | */ 133 | public function collection($collection, $filter = null) 134 | { 135 | if (is_array($collection)) { 136 | $response = array_map(function($item) use ($filter) { 137 | return $this->filterItem($item, $filter); 138 | }, $collection); 139 | return new Response($response); 140 | } else if ($collection instanceof ModelCollection) { 141 | $response = []; 142 | $collection->each(function($item) use ($filter, &$response) { 143 | $response[] = $this->filterItem($item, $filter); 144 | }); 145 | return new Response($response); 146 | } 147 | return new Response($collection); 148 | } 149 | 150 | /** 151 | * 分页模型的响应 152 | * 153 | * @param $collection 154 | * @param $filter 155 | * @return 156 | */ 157 | public function paginator($collection, $filter = null) 158 | { 159 | if (is_array($collection->items())) { 160 | $response = array_map(function($item) use ($filter) { 161 | return $this->filterItem($item, $filter); 162 | }, $collection->items()); 163 | 164 | try { 165 | $total = $collection->total(); 166 | $last_page = $collection->lastPage(); 167 | } catch (\DomainException $e) { 168 | $total = null; 169 | $last_page = null; 170 | } 171 | 172 | $meta['paginator'] = [ 173 | 'total' => $total, 174 | 'per_page' => $collection->listRows(), 175 | 'current_page' => $collection->currentPage(), 176 | 'last_page' => $last_page 177 | ]; 178 | 179 | return (new Response($response))->setMeta($meta); 180 | } 181 | return new Response($collection); 182 | } 183 | 184 | /** 185 | * 创建了资源的响应 186 | * 187 | * @param 资源响应位置 188 | * @param 资源响应内容 189 | * @return think\Response 190 | */ 191 | public function created($location = null, $content = null) 192 | { 193 | $Response = new Response($content); 194 | $Response->setCode(201); 195 | if (! is_null($location)) { 196 | $Response->addHeader('Location', $location); 197 | } 198 | return $Response; 199 | } 200 | 201 | /** 202 | * 无内容响应 203 | * 204 | * @return think\Response 205 | */ 206 | public function noContent() 207 | { 208 | $Response = new Response(null); 209 | $Response->setCode(204); 210 | return $Response; 211 | } 212 | 213 | /** 214 | * 301 资源的URI已更改 215 | * 216 | * @param string $message 217 | * 218 | * @return think\Response 219 | */ 220 | public function movedPermanently($message = 'Moved Permanently') 221 | { 222 | $Response = new Response($message); 223 | $Response->setCode(301); 224 | return $Response; 225 | } 226 | 227 | /** 228 | * 错误响应 229 | * 230 | * @param string 错误信息 231 | * @param int 状态码 232 | * 233 | * @throws think\exception\HttpException 234 | * 235 | * @return void 236 | */ 237 | public function error($message, $statusCode) 238 | { 239 | throw new ResponseException($statusCode, $message); 240 | } 241 | 242 | /** 243 | * 成功响应 244 | * 245 | * @param string 成功信息 246 | * @param int 状态码 247 | * 248 | * @throws think\exception\HttpException 249 | * 250 | * @return void 251 | */ 252 | public function success($message = null, $statusCode = 200) 253 | { 254 | $response = new Response($message); 255 | $response->setCode($statusCode); 256 | return $response; 257 | } 258 | 259 | /** 260 | * Call magic methods beginning with "with". 261 | * 262 | * @param string $method 263 | * @param array $parameters 264 | * 265 | * @throws \ErrorException 266 | * 267 | * @return mixed 268 | */ 269 | public function __call($method, array $parameters) 270 | { 271 | if ($method == 'array') { 272 | return new Response($parameters[0]); 273 | } else if (array_key_exists($method, $this->methods)) { 274 | return call_user_func_array('parent::__call', [$method, $parameters]); 275 | } 276 | throw new ErrorException('Undefined method '.get_class($this).'::'.$method); 277 | } 278 | } -------------------------------------------------------------------------------- /src/Response/Status.php: -------------------------------------------------------------------------------- 1 | 'Continue', 11 | 101 => 'Switching Protocols', 12 | 102 => 'Processing', // RFC2518 13 | 103 => 'Early Hints', 14 | 200 => 'OK', 15 | 201 => 'Created', 16 | 202 => 'Accepted', 17 | 203 => 'Non-Authoritative Information', 18 | 204 => 'No Content', 19 | 205 => 'Reset Content', 20 | 206 => 'Partial Content', 21 | 207 => 'Multi-Status', // RFC4918 22 | 208 => 'Already Reported', // RFC5842 23 | 226 => 'IM Used', // RFC3229 24 | 300 => 'Multiple Choices', 25 | 301 => 'Moved Permanently', 26 | 302 => 'Found', 27 | 303 => 'See Other', 28 | 304 => 'Not Modified', 29 | 305 => 'Use Proxy', 30 | 307 => 'Temporary Redirect', 31 | 308 => 'Permanent Redirect', // RFC7238 32 | 400 => 'Bad Request', 33 | 401 => 'Unauthorized', 34 | 402 => 'Payment Required', 35 | 403 => 'Forbidden', 36 | 404 => 'Not Found', 37 | 405 => 'Method Not Allowed', 38 | 406 => 'Not Acceptable', 39 | 407 => 'Proxy Authentication Required', 40 | 408 => 'Request Timeout', 41 | 409 => 'Conflict', 42 | 410 => 'Gone', 43 | 411 => 'Length Required', 44 | 412 => 'Precondition Failed', 45 | 413 => 'Payload Too Large', 46 | 414 => 'URI Too Long', 47 | 415 => 'Unsupported Media Type', 48 | 416 => 'Range Not Satisfiable', 49 | 417 => 'Expectation Failed', 50 | 418 => 'I am a teapot', // RFC2324 51 | 421 => 'Misdirected Request', // RFC7540 52 | 422 => 'Unprocessable Entity', // RFC4918 53 | 423 => 'Locked', // RFC4918 54 | 424 => 'Failed Dependency', // RFC4918 55 | 425 => 'Unordered Collection', // RFC2817 56 | 426 => 'Upgrade Required', // RFC2817 57 | 428 => 'Precondition Required', // RFC6585 58 | 429 => 'Too Many Requests', // RFC6585 59 | 431 => 'Request Header Fields Too Large', // RFC6585 60 | 451 => 'Unavailable For Legal Reasons', // RFC7725 61 | 500 => 'Internal Server Error', 62 | 501 => 'Not Implemented', 63 | 502 => 'Bad Gateway', 64 | 503 => 'Service Unavailable', 65 | 504 => 'Gateway Timeout', 66 | 505 => 'HTTP Version Not Supported', 67 | 506 => 'Variant Also Negotiates', // RFC2295 68 | 507 => 'Insufficient Storage', // RFC4918 69 | 508 => 'Loop Detected', // RFC5842 70 | 510 => 'Not Extended', // RFC2774 71 | 511 => 'Network Authentication Required', // RFC6585 72 | ); 73 | 74 | protected $methods = []; 75 | 76 | public function __construct() 77 | { 78 | $this->populateCodeMethods(); 79 | } 80 | 81 | /** 82 | * 转换 http 状态为方法调用 83 | */ 84 | private function populateCodeMethods() { 85 | foreach (self::$statusTexts as $code => $text) { 86 | $name = $this->toIdentifier($text); 87 | if ($code >= 400) { 88 | $this->methods[$name] = function($message = '') use ($code, $text) { 89 | if (!$message) $message = $text; 90 | throw new ResponseException($code, $message); 91 | }; 92 | } else { 93 | $this->methods[$name] = function($message = '') use ($code, $text) { 94 | if (!$message) $message = $text; 95 | $response = new Response($message); 96 | $response->setCode($code); 97 | return $response; 98 | }; 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * 转换 http 状态为方法名 105 | * 106 | * @param $str 107 | * @return mixed 108 | */ 109 | private function toIdentifier($str) { 110 | $res = []; 111 | $strArr = explode(' ', $str); 112 | foreach ($strArr as $item) { 113 | array_push($res, ucwords($item)); 114 | } 115 | $res = implode('', $res); 116 | return str_replace('/[^ _0-9a-z]/gi', '', $res); 117 | } 118 | 119 | /** 120 | * 调用动态方法 121 | * 122 | * @param $methodName 123 | * @param array $args 124 | * @return mixed 125 | */ 126 | public function __call($methodName, array $args) { 127 | if (isset($this->methods[$methodName])) { 128 | return call_user_func_array($this->methods[$methodName], $args); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/Routing/Router.php: -------------------------------------------------------------------------------- 1 | 11 | * @license https://opensource.org/licenses/MIT MIT 12 | * @link https://github.com/czewail/think-api 13 | */ 14 | class Router 15 | { 16 | // 路由版本列表 17 | private static $versions = []; 18 | 19 | protected $config = []; 20 | 21 | 22 | public function __construct() 23 | { 24 | Set::api(function($config) { 25 | $this->config = $config; 26 | }); 27 | } 28 | 29 | /** 30 | * 创建版本 31 | * 32 | * @param [type] $version 33 | * @param Closure $routes 34 | */ 35 | public function version($version = null, Closure $routes) 36 | { 37 | if (is_array($version)) { 38 | self::$versions = $version; 39 | } 40 | if (is_string($version)) { 41 | self::$versions = [$version]; 42 | } 43 | array_unique(self::$versions); 44 | 45 | $_version = Request::header('Api-Version') ?: Request::param('version'); 46 | 47 | if (!empty($_version)) { 48 | if (in_array($_version, self::$versions) ) { 49 | call_user_func_array($routes, []); 50 | } 51 | } else { 52 | if ($this->config['version']) { 53 | $default_version = $this->config['version']; 54 | if (in_array($default_version, self::$versions) ) { 55 | call_user_func_array($routes, []); 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Serializers/ArraySerializer.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class ArraySerializer extends Serializer 12 | { 13 | 14 | protected $content; 15 | 16 | function __construct($content, $meta = [], $adds = []) 17 | { 18 | $this->content = $content; 19 | $this->setContent(); 20 | parent::__construct($meta, $adds); 21 | } 22 | 23 | protected function setContent() 24 | { 25 | $this->data = $this->content; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Serializers/DataArraySerializer.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class DataArraySerializer extends Serializer 12 | { 13 | 14 | protected $content; 15 | 16 | protected $key = 'data'; 17 | 18 | function __construct($content, $meta = [], $adds = []) 19 | { 20 | $this->content = $content; 21 | $this->setContent(); 22 | parent::__construct($meta, $adds); 23 | } 24 | /** 25 | * 将接口数据赋值给key 26 | */ 27 | protected function setContent() 28 | { 29 | $this->data[$this->key] = $this->content; 30 | } 31 | 32 | /** 33 | * 修改key 34 | * @param [type] $key [description] 35 | */ 36 | public function setKey($key) 37 | { 38 | $this->key = $key; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Serializers/Serializer.php: -------------------------------------------------------------------------------- 1 | 6 | * @license https://opensource.org/licenses/MIT MIT 7 | * @link https://github.com/czewail/think-api 8 | */ 9 | class Serializer 10 | { 11 | protected $meta; 12 | protected $adds = []; 13 | 14 | public $data = []; 15 | 16 | public function __construct($meta = [], $adds = []) 17 | { 18 | $this->meta = $meta; 19 | $this->adds = $adds; 20 | $this->setMeta()->setAdds(); 21 | } 22 | 23 | protected function setMeta() 24 | { 25 | if (!empty($this->meta)) { 26 | $this->data['meta'] = $this->meta; 27 | } 28 | return $this; 29 | } 30 | 31 | protected function setAdds() 32 | { 33 | foreach ($this->adds as $key => $value) { 34 | if (!empty($value)) { 35 | $this->data[$key] = $value; 36 | } 37 | } 38 | return $this; 39 | } 40 | 41 | public function get() 42 | { 43 | return $this->data; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Setting/Set.php: -------------------------------------------------------------------------------- 1 | 8 | * @license https://opensource.org/licenses/MIT MIT 9 | * @link https://github.com/czewail/think-api 10 | */ 11 | class Set 12 | { 13 | protected static $files = [ 14 | 'resources' => 'resources.php', 15 | 'api' => 'api.php', 16 | 'jwt' => 'jwt.php', 17 | ]; 18 | 19 | public static function __callStatic($func, $args) 20 | { 21 | $path = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR; 22 | if ($file = self::$files[$func]) { 23 | $config = require($path . $file); 24 | $_config = Config::load($func); 25 | if ($_config && is_array($_config)) { 26 | $config = array_merge($config, $_config); 27 | } 28 | call_user_func_array($args[0], [$config]); 29 | } 30 | } 31 | } 32 | --------------------------------------------------------------------------------