Lumen-实现-SQL-监听

首发于:我的博客之前 Lumen 框架从 5.6 升级到 5.7。发现 laravel-sql-logger 包不能正常纪录日志了。进行排查,发现是 Lumen 框架没有对 DB 类型注入 event 对象,导致不能正常对其进行SQL监听。 那么解决方案也非常简单。 // file: bootstrap/app.php$app["db"]->connection()->setEventDispatcher($app["events"]); // 在下面的注册前加入这一行即可$app->register(Mnabialek\LaravelSqlLogger\Providers\ServiceProvider::class);但是这也让我对如何实现SQL纪录产生了兴趣。接下来,我们就具体了解一下如何实现SQL监听。 我们知道在Larvel上非常简单。只需要如下方法即可对其进行SQL监听: namespace App\Providers;use Illuminate\Support\Facades\DB;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ /** * 启动应用服务 * * @return void */ public function boot() { DB::listen(function ($query) { // $query->sql // $query->bindings // $query->time }); } //...}但是在 Lumen 上这种办法是没有办法使用的。Lumen有一些自己的调试SQL的方法,但是这些并不是我们想要的。所以我们只能自己写监听事件。 具体的解决方案是,我们首先创建一个Listener文件。 // file: app\Listeners\QueryListener.phpnamespace App\Listeners;use Illuminate\Database\Events\QueryExecuted;class QueryListener{ /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param ExampleEvent $event * @return void */ public function handle(QueryExecuted $event) { dd($event); // 此处直接对其进行打印 }}接下来我们直接对其进行注册 ...

July 12, 2019 · 3 min · jiezi

lumen框架下jwt配置多guards使用

JWT的配置文件config/jwt.php翻译ttl:token有效期(分钟)refresh_ttl:刷新token时间(分钟)algo:token签名算法user:指向User模型的命名空间路径identifier:用于从token的sub中获取用户require_claims:必须出现在token的payload中的选项,否则会抛出TokenInvalidException异常blacklist_enabled:如果该选项被设置为false,那么我们将不能废止token,即使我们刷新了token,前一个token仍然有效providers:完成各种任务的具体实现,如果需要的话你可以重写他们User —— providers.user:基于sub获取用户的实现JWT —— providers.jwt:加密/解密tokenAuthentication —— providers.auth:通过证书/ID获取认证用户+Storage —— providers.storage:存储token直到它们失效载荷(Payload)我们先将用户认证的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT 这里面的前6个字段都是由JWT的标准所定义的。sub: 该JWT所面向的用户 (这部分就是我们使用到的。)iss: 该JWT的签发者iat(issued at): 在什么时候签发的tokenexp(expires): token什么时候过期nbf(not before):token在此时间之前不能被接收处理jti:JWT ID为web token提供唯一标识路由定义// 登陆$app->post('login', 'AuthController@login');// 使用supplier守卫$app->group(['prefix' => 'auth', 'middleware' => 'auth:supplier'], function ($app) { // 退出登陆 $app->get('logout', 'AuthController@logout'); // 刷新token $app->get('refresh', 'AuthController@refresh'); // 获取授权详情 $app->get('detail', 'AuthController@detail');});中间件定义namespace App\Http\Middleware;use Closure;use Illuminate\Contracts\Auth\Factory as Auth;class Authenticate{ /** * The authentication guard factory instance. * * @var \Illuminate\Contracts\Auth\Factory */ protected $auth; /** * Create a new middleware instance. * * @param \Illuminate\Contracts\Auth\Factory $auth * @return void */ public function __construct(Auth $auth) { $this->auth = $auth; } /** * @desc 在进入控制器之前,判断并处理请求体 * @date 2019/6/27 18:06 * @param $request * @param Closure $next * @param null $guard */ public function handle($request, Closure $next, $guard = null) { // 判断当前用户是否是游客(未登录) if ($this->auth->guard($guard)->guest()) { $response['code'] = '4001'; $response['errorMsg'] = '无效令牌,需要重新获取'; return response()->json($response); } // 判断当前用户是否登录 if ($this->auth->guard($guard)->check()) { $user = $this->auth->guard($guard)->user(); // 获取登陆信息 \Illuminate\Support\Facades\Auth::setUser($user); // 设置Auth获取的用户信息 } return $next($request); }}BaseController定义公用方法/** * @desc 初始化供应商认证器 * @date 2019/6/27 15:08 */protected function supplier(){ // 辅助函数返回守卫信息 //return auth('supplier'); // Auth授权方式返回守卫信息 return Auth::guard('supplier');}Token:创建$credentials = $request->only('username', 'password');$validate = $this->supplier()->validate($credentials); // 验证账密是否正确$token = $this->supplier()->attempt($credentials); // 根据账户密码创建token# 更具用户Model创建token$user = AdminModel::find(1000000);$token = $this->supplier()->fromUser($user);Token:获取$this->supplier()->getToken()获取token过期时间默认单位分钟,所以乘以60得到秒单位 ...

June 28, 2019 · 2 min · jiezi

Lumen-报错提示-实例不了-Response-类的问题

今天使用Lumen的时候,用到了Response类,很奇怪提示:Target [Illuminate\Contracts\Routing\ResponseFactory] is not instantiable.大概就是说实例不了Response 类,那怎么解决呢?我们以一个全新的Lumen项目来说1.我在web.php写了个路由<?php/*|--------------------------------------------------------------------------| Application Routes|--------------------------------------------------------------------------|| Here is where you can register all of the routes for an application.| It is a breeze. Simply tell Lumen the URIs it should respond to| and give it the Closure to call when that URI is requested.|*/use Illuminate\Support\Facades\Response;$router->get('/', function () use ($router) { return Response::json('123456',200);});然后访问这个路由报错如下(也就是我们要解决的错误): 2.解决办法2.1 打开项目根目录下的 bootstrap/app.php //找到这两行把注释去掉 $app->withFacades(); $app->register(App\Providers\AppServiceProvider::class);2.2 找到 项目根目录下的 app/Providers/AppServiceProvider.php <?phpnamespace App\Providers;use Illuminate\Routing\ResponseFactory;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ /** * Register any application services. * * @return void */ public function register() { }}在 register 注册 ResponseFactory 修改如下: ...

May 15, 2019 · 1 min · jiezi

PHPUnit实践三(构建模块化的测试单元)

本系列教程所有的PHPUnit测试基于PHPUnit6.5.9版本,Lumen 5.5框架目录结构模块下的目录是符合Lumen的模块结构的如:Controllers、Models、Logics等是Lumen模块目录下的结构目录如果有自己的目录同级分配即可,如我这里的Requests整体结构├── BaseCase.php 重写过Lumen基类的测试基类,用于我们用这个基类做测试基类,后续会说明├── bootstrap.php tests自动加载文件├── Cases 测试用例目录│ └── Headline 某测试模块│ ├── logs 日志输出目录│ ├── PipeTest.php PHPUnit流程测试用例│ ├── phpunit.xml phpunit配置文件xml│ └── README.md 本模块测试用例说明├── ExampleTest.php 最原始测试demo└── TestCase.php Lumen自带的测试基类某模块的目录结构Headline //某测试模块测试用例目录├── Cache├── Controllers│ ├── ArticleTest.php│ ├── …├── Listeners│ └── MyListener.php├── Logics├── Models│ ├── ArticleTest.php│ ├── …├── README.md├── Requests│ ├── ArticleTest.php│ ├── …├── logs //日志和覆盖率目录│ ├── html│ │ ├── …│ │ └── index.html│ ├── logfile.xml│ ├── testdox.html│ └── testdox.txt├── phpunit-debug-demo.xml //phpunit.xml案例├── phpunit-debug.xml //改名后测试用的└── phpunit.xml //正式用的xml配置BaseCase.php<?phpnamespace Test;use Illuminate\Database\Eloquent\Factory;class BaseCase extends TestCase{ protected $seeder = false; const DOMAIN = “http://xxx.com”; const API_URI = []; const TOKEN = [ ’local’ => ’token*’, ‘dev’ => ’token*’, ‘prod’ => ’’ //如果测试真实请填写授权token ]; /** * 重写setUp / public function setUp() { parent::setUp(); $this->seeder = false; if (method_exists($this, ‘factory’)) { $this->app->make(‘db’); $this->factory($this->app->make(Factory::class)); if (method_exists($this, ‘seeder’)) { if (!method_exists($this, ‘seederRollback’)) { dd(“请先创建seederRollback回滚方法”); } $this->seeder = true; $this->seeder(); } } } /* * 重写tearDown / public function tearDown() { if ($this->seeder && method_exists($this, ‘seederRollback’)) { $this->seederRollback(); } parent::tearDown(); } /* * 获取地址 * @param string $apiKey * @param string $token * @return string / protected function getRequestUri($apiKey = ’list’, $token = ‘dev’, $ddinfoQuery = true) { $query = “?token=” . static::TOKEN[strtolower($token)]; if ($ddinfoQuery) { $query = $query . “&” . http_build_query(static::DDINFO); } return $apiUri = static::DOMAIN . static::API_URI[$apiKey] . $query; }}phpunit-debug-demo.xml本文件是我们单独为某些正在测试的测试用例,直接编写的xml,可以不用来回测试,已经测试成功的测试用例了,最后全部编写完测试用例,再用正式phpunit.xml即可,具体在运行测试阶段看如何指定配置<?xml version=“1.0” encoding=“UTF-8”?><phpunit bootstrap="../../bootstrap.php" convertErrorsToExceptions=“true” convertNoticesToExceptions=“false” convertWarningsToExceptions=“false” colors=“true”> <filter> <whitelist processuncoveredfilesfromwhitelist=“true”> <directory suffix=".php">../../../app/Http/Controllers/Headline</directory> <directory suffix=".php">../../../app/Http/Requests/Headline</directory> <directory suffix=".php">../../../app/Models/Headline</directory> <exclude><file>../../../app/Models/Headline/ArticleKeywordsRelationModel.php</file> </exclude> </whitelist> </filter> <testsuites> <testsuite name=“Headline Test Suite”> <directory>./</directory> </testsuite> </testsuites> <php> <ini name=“date.timezone” value=“PRC”/> <env name=“APP_ENV” value=“DEV”/> </php> <logging> <log type=“coverage-html” target=“logs/html/” lowUpperBound=“35” highLowerBound=“70”/> <log type=“json” target=“logs/logfile.json”/> <log type=“tap” target=“logs/logfile.tap”/> <log type=“junit” target=“logs/logfile.xml” logIncompleteSkipped=“false”/> <log type=“testdox-html” target=“logs/testdox.html”/> <log type=“testdox-text” target=“logs/testdox.txt”/> </logging> <listeners> <!–<listener class="\Test\Cases\Headline\Listeners\MyListener" file="./Listeners/MyListener.php">–> <!–<arguments>–> <!–<array>–> <!–<element key=“0”>–> <!–<string>Sebastian</string>–> <!–</element>–> <!–</array>–> <!–<integer>22</integer>–> <!–<string>April</string>–> <!–<double>19.78</double>–> <!–<null/>–> <!–<object class=“stdClass”/>–> <!–</arguments>–> <!–</listener>–> <!–<listener class="\Test\Cases\Headline\Listeners\MyListener" file="./Listeners/MyListener.php">–> <!–<arguments>–> <!–<array>–> <!–<element key=“0”>–> <!–<string>Sebastian</string>–> <!–</element>–> <!–</array>–> <!–<integer>22</integer>–> <!–</arguments>–> <!–</listener>–> </listeners></phpunit>测试用例案例<?php/* * Created by PhpStorm. * User: qikailin * Date: 2019-01-29 * Time: 11:57 /namespace Test\Cases\Headline\Articles;use App\Http\Controllers\Headline\ArticleController;use App\Models\Headline\ArticleCategoryRelationModel;use App\Models\Headline\ArticleContentModel;use App\Models\Headline\ArticleKeywordsRelationModel;use App\Models\Headline\ArticlesModel;use Faker\Generator;use Illuminate\Http\Request;use Test\BaseCase;class ArticleTest extends BaseCase{ private static $model; public static function setUpBeforeClass() { parent::setUpBeforeClass(); self::$model = new ArticlesModel(); } /* * 生成factory faker 数据构建模型对象 * @codeCoverageIgnore / public function factory($factory) { $words = [“测试”, “文章”, “模糊”, “搜索”]; $id = 262; $factory->define(ArticlesModel::class, function (Generator $faker) use (&$id, $words) { $id++; return [ ‘id’ => $id, ‘uri’ => $faker->lexify(‘T???????????????????’), ’title’ => $id == 263 ? “搜索” : $words[rand(0, sizeof($words) - 1)], ‘authorId’ => 1, ‘state’ => 1, ‘isUpdated’ => 0, ]; }); } /* * 生成模拟的数据,需seederRollback 成对出现 / public function seeder() { $articles = factory(ArticlesModel::class, 10)->make(); foreach ($articles as $article) { // 注意: article为引用对象,不是copy if ($article->isRecommend) { $article->recommendTime = time(); } $article->save(); } } /* * getArticleList 测试数据 * @return array / public function getArticleListDataProvider() { return [ [1, “搜索”, 1, 10, 1], [2, “搜索”, 1, 10, 0], [2, null, 1, 10, 0], [3, “搜索”, 1, 10, 0], [1, null, 1, 10, 1], [2, null, 1, 10, 0], [3, null, 1, 10, 0], ]; } /* * @dataProvider getArticleListDataProvider / public function testGetArticleList($type, $searchText, $page, $pageSize, $expceted) { $rst = self::$model->getArticleList($type, $searchText, $page, $pageSize); $this->assertGreaterThanOrEqual($expceted, sizeof($rst)); $rst = self::$model->getArticleCount($type, $searchText); $this->assertGreaterThanOrEqual($expceted, $rst); } /* * addArticle 测试数据 * @return array / public function addArticleDataProvider() { return [ [ [ ‘id’ => 273, ‘uri’ => ‘dddddddddd0123’ ], ‘save’, 0 ], [ [ ‘id’ => 274, ‘uri’ => ‘dddddddddd123’ ], ‘publish’, 0 ], [ [ ‘id’ => 275, ‘uri’ => ‘dddddddddd456’ ], ‘preview’, 0 ], ]; } /* * @dataProvider addArticleDataProvider / public function testAdd($data, $action, $expected) { $rst = self::$model->addArticle($data, $action); if ($rst) { self::$model::where(‘id’, $rst)->delete(); } $this->assertGreaterThanOrEqual($expected, $rst); } public function testGetArticleInfo() { $rst = self::$model->getArticleInfo(263, 0); $this->assertGreaterThanOrEqual(1, sizeof($rst)); $rst = self::$model->getArticleInfo(2000, 1); $this->assertEquals(0, sizeof($rst)); } /* * 回滚模拟的数据到初始状态 */ public function seederRollback() { self::$model::where(‘id’, ‘>=’, 263)->where(‘id’, ‘<=’, 272)->delete(); }}运行测试cd {APPROOT}/tests/Cases/Headline# mv phpunit-debug-custom.xml -> phpunit-debug.xml../../../vendor/bin/phpunit –verbose -c phpunit-debug.xml参考PHPUnit 5.0 官方中文手册 ...

February 15, 2019 · 3 min · jiezi

基于 lumen 的微服务架构实践

lumen为速度而生的 Laravel 框架官网的介绍很简洁,而且 lumen 确实也很简单,我在调研了 lumen 相关组件(比如缓存,队列,校验,路由,中间件和最重要的容器)之后认为已经能够满足我目前这个微服务的需求了。任务目标因为业务需求,需要在内网服务B中获取到公网服务A中的数据,但是B服务并不能直接对接公网,于是需要开发一个relay 中转机来完成数据转存和交互。任务列表环境准备 【done】RSA数据加密 【done】guzzle请求封装 【done】添加monolog日志RocketMQ java请求转发程序数据库migrateEvent和Listener的业务应用Scheduler计划任务(基于crontab)Jobs和Queue业务应用使用supervisor守护queue进程和java进程添加sentry来获取服务日志信息和实现邮件报警jwt用户身份校验.env 文件的配置可能的扩展 K8S docker性能并发测试环境准备机器是centos6.8, 使用work用户, 安装 php(^7),mysql,nginx,redisyum 安装的同学可以试试 https://www.softwarecollectio…安装composerhttps://getcomposer.org/downl…# 注意php的环境变量php -r “copy(‘https://getcomposer.org/installer', ‘composer-setup.php’);“php -r “if (hash_file(‘sha384’, ‘composer-setup.php’) === ‘93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8’) { echo ‘Installer verified’; } else { echo ‘Installer corrupt’; unlink(‘composer-setup.php’); } echo PHP_EOL;“php composer-setup.phpphp -r “unlink(‘composer-setup.php’);“mv composer.phar /usr/local/bin/composer安装lumencomposer global require “laravel/lumen-installer"composer create-project –prefer-dist laravel/lumen YOURPROJECT配置 .env配置Lumen 框架所有的配置信息都是存在 .env 文件中。一旦 Lumen 成功安装,你同时也要 配置本地环境。应用程序密钥在你安装完 Lumen 后,首先需要做的事情是设置一个随机字符串到应用程序密钥。通常这个密钥会有 32 字符长。 这个密钥可以被设置在 .env 配置文件中。如果你还没将 .env.example 文件重命名为 .env,那么你现在应该去设置下。如果应用程序密钥没有被设置的话,你的用户 Session 和其它的加密数据都是不安全的!配置nginx 和 php-fpm配置nginx的serverserver { listen 8080; server_name localhost; index index.php index.html index.htm; root /home/work/YOURPROJECT/public; error_page 404 /404.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ .php$ { root /home/work/YOURPROJECT/public; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; #include fastcgi.conf; }}php-fpm的监听端口推荐一篇文章:Nginx+Php-fpm运行原理详解lumen 基础介绍lumen的入口文件是 public/index.php,在nginx配置文件中已有体现初始化核心容器是 bootstrap/app.php 它做了几件非常重要的事情加载了 composer的 autoload 自动加载创建容器并可以选择开启 Facades 和 Eloquent (建议都开启,非常方便)Register Container Bindings:注册容器绑定 ExceptionHandler(后面monolog和sentry日志收集用到了) 和 ConsoleKernel(执行计划任务)Register Middleware:注册中间件,例如auth验证: $app->routeMiddleware([‘auth’ => AppHttpMiddlewareAuthenticate::class,]);注册Service Providers$app->register(App\Providers\AppServiceProvider::class);$app->register(App\Providers\AuthServiceProvider::class);$app->register(App\Providers\EventServiceProvider::class);在AppServiceProvider 里还能一起注册多个provider// JWT$this->app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class);// redis$this->app->register(\Illuminate\Redis\RedisServiceProvider::class);// 方便IDE追踪代码的Helper,因为laravel使用了大量的魔术方法和call方法以至于,对IDE的支持并不友好,强烈推荐开发环境安装$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);// sentry$this->app->register(\Sentry\SentryLaravel\SentryLumenServiceProvider::class);加载route文件 routes/web.php//localhost:8080/test 调用app/Http/Controllers/Controller.php的 test方法$router->get("/test”, [‘uses’ => “Controller@test”]);// 使用中间件进行用户校验$router->group([‘middleware’ => ‘auth:api’], function () use ($router) { $router->get(’/auth/show’, ‘AuthController@getUser’);});还可以添加其他初始化控制的handler,比如说这个 monolog日志等级和格式,以及集成sentry的config$app->configureMonologUsing(function(Monolog\Logger $monoLog) use ($app){ // 设置processor的extra日志信息等级为WARNING以上,并且不展示Facade类的相关信息 $monoLog->pushProcessor(new \Monolog\Processor\IntrospectionProcessor(Monolog\Logger::WARNING, [‘Facade’])); // monolog 日志发送到sentry $client = new Raven_Client(env(‘SENTRY_LARAVEL_DSN’)); $handler = new Monolog\Handler\RavenHandler($client); $handler->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true, true)); $monoLog->pushHandler($handler); // 设置monolog 的日志处理handler return $monoLog->pushHandler( (new Monolog\Handler\RotatingFileHandler( env(‘APP_LOG_PATH’) ?: storage_path(’logs/lumen.log’), 90, env(‘APP_LOG_LEVEL’) ?: Monolog\Logger::DEBUG) )->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true)) );});配置文件 config/ 和 .env 文件其他目录文件用到时再具体说明RSA数据加密因为业务中包含部分敏感数据,所以,数据在传输过程中需要加密传输。选用了RSA非对称加密。借鉴了 PHP 使用非对称加密算法(RSA)但由于传输数据量较大,加密时会报错,所以采用了分段加密连接和分段解密php使用openssl进行Rsa长数据加密(117)解密(128)如果选择密钥是1024bit长的(openssl genrsa -out rsa_private_key.pem 1024),那么支持加密的明文长度字节最多只能是1024/8=128byte;如果加密的padding填充方式选择的是OPENSSL_PKCS1_PADDING(这个要占用11个字节),那么明文长度最多只能就是128-11=117字节。如果超出,那么这些openssl加解密函数会返回false。分享一个我的完成版的工具类openssl genrsa -out rsa_private_key.pem 1024//生成原始 RSA私钥文件openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem//将原始 RSA私钥转换为 pkcs8格式openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem<?phpnamespace App\Lib\Oscar;class Rsa{ private static $PRIVATE_KEY = ‘—–BEGIN RSA PRIVATE KEY—–xxxxxxxxxxxxx完整复制过来xxxxxxxxxxxxxxxxxxx—–END RSA PRIVATE KEY—–’; private static $PUBLIC_KEY = ‘—–BEGIN PUBLIC KEY—–xxxxxxxxxxxxx完整复制过来xxxxxxxxxxxxxxxxxxx—–END PUBLIC KEY—–’; /** * 获取私钥 * @return bool|resource / private static function getPrivateKey() { $privateKey = self::$PRIVATE_KEY; return openssl_pkey_get_private($privateKey); } /* * 获取公钥 * @return bool|resource / private static function getPublicKey() { $publicKey = self::$PUBLIC_KEY; return openssl_pkey_get_public($publicKey); } /* * 私钥加密 * @param string $data * @return null|string / public static function privateEncrypt($data = ‘’) { if (!is_string($data)) { return null; } $EncryptStr = ‘’; foreach (str_split($data, 117) as $chunk) { openssl_private_encrypt($chunk, $encryptData, self::getPrivateKey()); $EncryptStr .= $encryptData; } return base64_encode($EncryptStr); } /* * 公钥加密 * @param string $data * @return null|string / public static function publicEncrypt($data = ‘’) { if (!is_string($data)) { return null; } return openssl_public_encrypt($data,$encrypted,self::getPublicKey()) ? base64_encode($encrypted) : null; } /* * 私钥解密 * @param string $encrypted * @return null / public static function privateDecrypt($encrypted = ‘’) { $DecryptStr = ‘’; foreach (str_split(base64_decode($encrypted), 128) as $chunk) { openssl_private_decrypt($chunk, $decryptData, self::getPrivateKey()); $DecryptStr .= $decryptData; } return $DecryptStr; } /* * 公钥解密 * @param string $encrypted * @return null */ public static function publicDecrypt($encrypted = ‘’) { if (!is_string($encrypted)) { return null; } return (openssl_public_decrypt(base64_decode($encrypted), $decrypted, self::getPublicKey())) ? $decrypted : null; }}使用tip// 私钥加密则公钥解密,反之亦然$data = \GuzzleHttp\json_encode($data);$EncryptData = Rsa::privateEncrypt($data);$data = Rsa::publicDecrypt($EncryptData);guzzle使用安装超简单 composer require guzzlehttp/guzzle:~6.0guzzle 支持PSR-7 http://docs.guzzlephp.org/en/…官网的示例也很简单,发个post自定义参数的例子use GuzzleHttp\Client;$client = new Client();// 发送 post 请求$response = $client->request( ‘POST’, $this->queryUrl, [ ‘form_params’ => [ ‘req’ => $EncryptData ]]);$callback = $response->getBody()->getContents();$callback = json_decode($callback, true);guzzle支持 异步请求// Send an asynchronous request.$request = new \GuzzleHttp\Psr7\Request(‘GET’, ‘http://httpbin.org’);$promise = $client->sendAsync($request)->then(function ($response) { echo ‘I completed! ’ . $response->getBody();});$promise->wait();值的注意的是github上有一个很好玩的项目 https://github.com/kitetail/zttp它在guzzle的基础上做了封装,采用链式调用$response = Zttp::withHeaders([‘Fancy’ => ‘Pants’])->post($url, [ ‘foo’ => ‘bar’, ‘baz’ => ‘qux’,]);$response->json();// => [// ‘whatever’ => ‘was returned’,// ];$response->status();// int$response->isOk();// true / false#如果是guzzle 则需要更多的代码$client = new Client();$response = $client->request(‘POST’, $url, [ ‘headers’ => [ ‘Fancy’ => ‘Pants’, ], ‘form_params’ => [ ‘foo’ => ‘bar’, ‘baz’ => ‘qux’, ]]);json_decode($response->getBody());高可用问题思考数据传输量过大可能导致的问题RSA加密失败请求超时数据库存储并发列队失败重试和堵塞数据操作日志监控和到达率监控未完待续….. ...

January 9, 2019 · 3 min · jiezi

Lumen微服务生成Swagger文档

作为一名phper,在使用Lumen框架开发微服务的时候,API文档的书写总是少不了的,比较流行的方式是使用swagger来写API文档,但是与Java语言原生支持 annotation 不同,php只能单独维护一份swagger文档,或者在注释中添加annotations来实现类似的功能,但是注释中书写Swagger注解是非常痛苦的,没有代码提示,没有格式化。本文将会告诉你如何借助phpstorm中annotations插件,在开发Lumen微服务项目时(Laravel项目和其它php项目方法类似)快速的在代码中使用注释来创建swagger文档。本文将会持续修正和更新,最新内容请参考我的 GITHUB 上的 程序猿成长计划 项目,欢迎 Star,更多精彩内容请 follow me。框架配置我们使用当前最新的 Lumen 5.7 来演示。演示代码放到了github,感兴趣的可以参考一下https://github.com/mylxsw/lumen-swagger-demo安装依赖在Lumen项目中,首先需要使用 composer 安装SwaggerLume项目依赖composer require darkaonline/swagger-lume项目配置在bootstrap/app.php文件中,去掉下面配置的注释(大约在26行),启用Facades支持。$app->withFacades();启用SwaggerLume 项目的配置文件,在 Register Container Bindings部 分前面,添加$app->configure(‘swagger-lume’);然后,在 Register Service Providers 部分,注册 SwaggerLume 的ServiceProvider$app->register(\SwaggerLume\ServiceProvider::class);在项目的根目录,执行命令 php artisan swagger-lume:publish 发布swagger相关的配置执行该命令后,主要体现以下几处变更在 config/ 目录中,添加了项目的配置文件 swagger-lume.php在 resources/views/vendor 目录中,生成了 swagger-lume/index.blade.php 视图文件,用于预览生成的API文档从配置文件中我们可以获取以下关键信息api.title 生成的API文档显示标题routes.api 用于访问生成的API文档UI的路由地址默认为 /api/documentationroutes.docs 用于访问生成的API文档原文,json格式,默认路由地址为 /docspaths.docs 和 paths.docs_json 组合生成 api-docs.json 文件的地址,默认为 storage/api-docs/api-docs.json,执行php artisan swagger-lume:generate命令时,将会生成该文件语法自动提示纯手写swagger注释肯定是要不得的,太容易出错,还需要不停的去翻看文档参考语法,因此我们很有必要安装一款能够自动提示注释中的注解语法的插件,我们常用的IDE是 phpstorm,在 phpstorm 中,需要安装 PHP annotation 插件安装插件之后,我们在写Swagger文档时,就有代码自动提示功能了书写文档Swagger文档中包含了很多与具体API无关的信息,我们在 app/Http/Controllers 中创建一个 SwaggerController,该控制器中我们不实现业务逻辑,只用来放置通用的文档信息<?phpnamespace App\Http\Controllers;use OpenApi\Annotations\Contact;use OpenApi\Annotations\Info;use OpenApi\Annotations\Property;use OpenApi\Annotations\Schema;use OpenApi\Annotations\Server;/** * * @Info( * version=“1.0.0”, * title=“演示服务”, * description=“这是演示服务,该文档提供了演示swagger api的功能”, * @Contact( * email=“mylxsw@aicode.cc”, * name=“mylxsw” * ) * ) * * @Server( * url=“http://localhost”, * description=“开发环境”, * ) * * @Schema( * schema=“ApiResponse”, * type=“object”, * description=“响应实体,响应结果统一使用该结构”, * title=“响应实体”, * @Property( * property=“code”, * type=“string”, * description=“响应代码” * ), * @Property(property=“message”, type=“string”, description=“响应结果提示”) * ) * * * @package App\Http\Controllers /class SwaggerController{}接下来,在业务逻辑控制器中,我们就可以写API了<?phpnamespace App\Http\Controllers;use App\Http\Responses\DemoAdditionalProperty;use App\Http\Responses\DemoResp;use Illuminate\Http\Request;use OpenApi\Annotations\Get;use OpenApi\Annotations\MediaType;use OpenApi\Annotations\Property;use OpenApi\Annotations\RequestBody;use OpenApi\Annotations\Response;use OpenApi\Annotations\Schema;class ExampleController extends Controller{ /* * @Get( * path="/demo", * tags={“演示”}, * summary=“演示API”, * @RequestBody( * @MediaType( * mediaType=“application/json”, * @Schema( * required={“name”, “age”}, * @Property(property=“name”, type=“string”, description=“姓名”), * @Property(property=“age”, type=“integer”, description=“年龄”), * @Property(property=“gender”, type=“string”, description=“性别”) * ) * ) * ), * @Response( * response=“200”, * description=“正常操作响应”, * @MediaType( * mediaType=“application/json”, * @Schema( * allOf={ * @Schema(ref="#/components/schemas/ApiResponse"), * @Schema( * type=“object”, * @Property(property=“data”, ref="#/components/schemas/DemoResp") * ) * } * ) * ) * ) * ) * * @param Request $request * * @return DemoResp / public function example(Request $request) { // TODO 业务逻辑 $resp = new DemoResp(); $resp->name = $request->input(’name’); $resp->id = 123; $resp->age = $request->input(‘age’); $resp->gender = $request->input(‘gender’); $prop1 = new DemoAdditionalProperty(); $prop1->key = “foo”; $prop1->value = “bar”; $prop2 = new DemoAdditionalProperty(); $prop2->key = “foo2”; $prop2->value = “bar2”; $resp->properties = [$prop1, $prop2]; return $resp; }}这里,我们在响应结果中,引用了在SwaggerController中定义的 ApiResponse,还引用了一个没有定义的ExampleResp对象,我们可以 app\Http\Responses 目录(自己创建该目录)中实现该ExampleResp对象,我们将响应对象都放在这个目录中<?phpnamespace App\Http\Responses;use OpenApi\Annotations\Items;use OpenApi\Annotations\Property;use OpenApi\Annotations\Schema;/* * @Schema( * title=“demo响应内容”, * description=“demo响应内容描述” * ) * * @package App\Http\Responses /class DemoResp extends JsonResponse{ /* * @Property( * type=“integer”, * description=“ID” * ) * * @var int / public $id = 0; /* * @Property( * type=“string”, * description=“用户名” * ) * * @var string / public $name; /* * @Property( * type=“integer”, * description=“年龄” * ) * * @var integer / public $age; /* * @Property( * type=“string”, * description=“性别” * ) * * @var string / public $gender; /* * @Property( * type=“array”, * @Items(ref="#/components/schemas/DemoAdditionalProperty") * ) * * @var array / public $properties = [];}返回对象引用其它对象<?phpnamespace App\Http\Responses;use OpenApi\Annotations\Property;use OpenApi\Annotations\Schema;/* * * @Schema( * title=“额外属性”, * description=“额外属性描述” * ) * * @package App\Http\Responses /class DemoAdditionalProperty{ /* * @Property( * type=“string”, * description=“KEY” * ) * * @var string / public $key; /* * @Property( * type=“string”, * description=“VALUE” * ) * * @var string */ public $value;}生成文档执行下面的命令,就可以生成文档了,生成的文档在storage/api-docs/api-docs.json。php artisan swagger-lume:generate预览文档打开浏览器访问 http://访问地址/docs,可以看到如下内容访问 http://访问地址/api/documentation,我们看到接口详细信息展开更多本文简述了如何在Lumen项目中使用代码注释自动生成Swagger文档,并配合phpstorm的代码提示功能,然而,学会了这些还远远不够,你还需要去了解Swagger文档的语法结构,在 swagger-php 项目的 Examples 目录中包含很多使用范例,你可以参考一下。团队项目中使用了swagger文档,但是总得有个地方管理文档吧,这里推荐一下 Wizard 项目,该项目是一款用于团队协作的文档管理工具,支持Markdown文档和Swagger文档,感兴趣的不妨尝试一下。 ...

January 2, 2019 · 3 min · jiezi