lumen
为速度而生的 Laravel 框架
官网的介绍很简洁,而且 lumen 确实也很简单,我在调研了 lumen 相关组件(比如缓存,队列,校验,路由,中间件和最重要的容器)之后认为已经能够满足我目前这个微服务的需求了。
任务目标
因为业务需求,需要在内网服务 B 中获取到公网服务 A 中的数据,但是 B 服务并不能直接对接公网,于是需要开发一个 relay 中转机来完成数据转存和交互。
任务列表
环境准备【done】
RSA 数据加密【done】
guzzle 请求封装【done】
添加 monolog 日志
RocketMQ java 请求转发程序
数据库 migrate
Event 和 Listener 的业务应用
Scheduler 计划任务(基于 crontab)
Jobs 和 Queue 业务应用
使用 supervisor 守护 queue 进程和 java 进程
添加 sentry 来获取服务日志信息和实现邮件报警
jwt 用户身份校验
.env 文件的配置
可能的扩展 K8S docker
性能并发测试
环境准备
机器是 centos6.8, 使用 work 用户, 安装 php(^7),mysql,nginx,redis
yum 安装的同学可以试试 https://www.softwarecollectio…
安装 composer
https://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.php
php -r “unlink(‘composer-setup.php’);”
mv composer.phar /usr/local/bin/composer
安装 lumen
composer 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 的 server
server {
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
<?php
namespace 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.0
guzzle 支持 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 加密失败
请求超时
数据库存储并发
列队失败重试和堵塞
数据操作日志监控和到达率监控
未完待续 …..