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加密失败
请求超时
数据库存储并发
列队失败重试和堵塞
数据操作日志监控和到达率监控
未完待续…..
发表回复