Commit 98854963 authored by luhongguang's avatar luhongguang

init

parent 71c72b9d
composer.lock
/yaconf/
.idea
.DS_Store
.project
\ No newline at end of file
商品系统 yaf框架目录组织
\ No newline at end of file
0. 生成一个新的项目
- 下载 ``` deploy_init.sh ``` 运行脚本文件,到本地项目空间下
- 运行 ``` sh deploy_init.sh project_name ``` 则在对应的目录下生成project_name的文件夹
- 注 : 该项目是一个空的git,需根据项目自行添加
1. php.ini 的配置如下
```
[yaf]
extension=yaf.so
yaf.environ=dev
yaf.use_namespace=1
yaf.use_spl_autoload=1
yaconf.directory=/Users/admin/works/php/yaconf # 这里是你本地开发所用的yaconf路径
```
2. 配置 Nginx 的 Rewrite
```
location / { # 因所有地址都以/开头,所以该规则将匹配到所有请求,但正则和最长字符串会优先匹配
if (!-e $request_filename) {
rewrite ^/(.*) /index.php?$1 last;
}
}
location ~ \.php$ { #匹配所有以.php结尾的文件
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param YIDIAN_LOCAL_IP '127.0.0.1';
fastcgi_pass 127.0.0.1:9000;
}
```
3. composer update
4. 浏览器访问 http://your_domain/test/test/index
5. 命令行执行 php cli.php test index "a=1&b=2"
6. 如果是内部使用的方法文件,请在文件前添加注释,如下:
```
<?php
/**
* @mark FOR_INTERNAL_USE_ONLY
*/
class Test{
public function TestAction()
{
}
}
```
- 项目依赖项包括 yaconf,php_utils, php_services三个项目,
- composer update 有依赖更新要及时执行
- yaconf统一管理,不要在自己的项目中建yaconf目录
- protobuf3的定义文件统一放到yaconf中管理
<?php
use Yaf\Bootstrap_Abstract;
use Yaf\Dispatcher;
use App\Plugins\Hook;
use Yaf\Registry;
class Bootstrap extends Bootstrap_Abstract {
/**
* 项目基本初始化操作.
*
* @param Dispatcher $dispatcher
*/
public function _initProject(Dispatcher $dispatcher)
{
date_default_timezone_set('PRC');
//是否返回Response对象, 如果启用, 则Response对象在分发完成以后不会自动输出给请求端, 而是交给程序员自己控制输出.
$dispatcher->returnResponse(true);
$dispatcher->disableView();
}
/**
* autoload.
*
* @param Dispatcher $dispatcher
*/
public function _initLoader(Dispatcher $dispatcher)
{
$loader = Yaf\Loader::getInstance();
$loader->import(ROOT_PATH.'/vendor/autoload.php');
$loader->import(APP_PATH.'/library/helper.php');
}
public function _initConfig() {
//把配置保存起来
$arrConfig = Yaf\Application::app()->getConfig();
Registry::set('config', $arrConfig);
}
public function _initPlugin(Dispatcher $dispatcher) {
//注册一个插件
$objSamplePlugin = new Hook();
$dispatcher->registerPlugin($objSamplePlugin);
}
public function _initRoute(Dispatcher $dispatcher) {
//在这里注册自己的路由协议,默认使用简单路由
}
}
\ No newline at end of file
<?php
class Bootstrap extends Yaf\Bootstrap_Abstract{
/**
* 初始化配置文件
* 全局配置文件 ,环境配置文件 错误码配置文件等
*/
public function _initConfig() {
$globalConfig = Yaf\Application::app()->getConfig();
Yaf\Registry::set('cliConfig', $globalConfig);
}
/**
* 禁用视图
*/
public function _initView(\Yaf\Dispatcher $dispatcher) {
$dispatcher->disableView();
}
}
<?php
use Yaf\Controller_Abstract;
/**
* 每个项目按需定义异常处理,记录日志等,确定的异常请在自定义异常处理,详细在exception目录中查看
*/
class ErrorController extends Controller_Abstract
{
use \Helpers\ApiResponse;
public function errorAction($exception)
{
if (\Yaf\Application::app()->environ() == 'dev') {
// var_dump($exception);
}
if ($exception->getPrevious() !== NULL) {
$exception = $exception->getPrevious();
}
switch ($exception->getCode()) {
// 404
case YAF\ERR\NOTFOUND\MODULE:
case YAF\ERR\NOTFOUND\CONTROLLER:
case YAF\ERR\NOTFOUND\ACTION:
case YAF\ERR\NOTFOUND\VIEW:
// todo 各项目自定义
// log
// LogUtil::ERROR('error code'.$exc->getCode(), '', $exc);
$this->failed(10, 'failed', 'reason');
return false;
break;
default:
// todo 各项目自定义
// log
$reason = '服务器忙, 请稍后再试[' . $exception->getCode() . ']';
$this->failed($exception->getCode(), 'failed', $reason);
return false;
}
}
}
<?php
use Yaf\Controller_Abstract;
/**
* 不要删除默认的控制器,deploy等服务会调用此/indix/index接口,判断项目是否可用
*/
class IndexController extends Controller_Abstract
{
use \Helpers\ApiResponse;
public function IndexAction() {
$this->success();
}
}
<?php
namespace App\Exception;
use Helpers\ApiResponse;
use Yaf\Registry;
/**
* Class BaseException
* 自定义异常类的基类
*/
class BaseException extends \Exception
{
use ApiResponse;
public $msg;
public $code;
/**
* 构造函数,接收一个关联数组
* @param array $params 关联数组只应包含code、msg和errorCode,且不应该是空值
*/
public function __construct($params = [])
{
if (!is_array($params)) {
return;
}
$this->code = $params['code'] ?? Registry::get('config')->exception->user->code;
$this->msg = $params['msg'] ?? Registry::get('config')->exception->user->msg;
if (isset($params['cus']) && $this->cus && isset($this->cus[intval($params['cus'])]) && $this->base_code) {
$cus_code = intval($params['cus']);
$base_code = intval($this->base_code);
$this->code = $base_code + $cus_code;
$this->msg = $this->cus[$cus_code];
}
}
public function __get($name): bool
{
return false;
}
}
<?php
namespace App\Exception;
/*
* 重写Handle的render方法,实现自定义异常消息
*/
use Helpers\ApiResponse;
use Yaf\Registry;
class ErrorHandler
{
use ApiResponse;
private $http_code;
private $msg;
private $code;
public function render($errno, $errstr, $errfile, $errline)
{
$debug = Registry::get('config')->debug;
$msg = ' file_name : '.$errfile . '<br> line_num : '.$errline . '<br> message : ' . $errstr;
if($debug){
echo $msg;
exit;
}
$this->msg = Registry::get('config')->exception->sys->msg;
$this->code = Registry::get('config')->exception->sys->code;
$this->recordErrorLog($msg);
$this->failed($this->code, $this->msg);
}
/*
* 将异常写入日志
*/
private function recordErrorLog($msg)
{
// LogUtil::error('Error handler', $msg);
}
}
\ No newline at end of file
<?php
namespace App\Exception;
class ExceptionErrorCatch
{
public static function register()
{
error_reporting(E_ALL & ~E_NOTICE);
set_exception_handler([__CLASS__, 'appException']);
set_error_handler([__CLASS__, 'appError'], E_ALL);
}
public static function appException(\Throwable $e)
{
$handler = new ExceptionHandler();
$handler->render($e);
exit;
}
public static function appError($errno, $errstr, $errfile, $errline)
{
$handler = new ErrorHandler();
$handler->render($errno, $errstr, $errfile, $errline);
exit;
}
}
\ No newline at end of file
<?php
namespace App\Exception;
/*
* 重写Handle的render方法,实现自定义异常消息
*/
use Helpers\ApiResponse;
use Yaf\Registry;
class ExceptionHandler
{
use ApiResponse;
private $msg;
private $code;
public function render(\Throwable $e)
{
$debug = Registry::get('config')->debug;
if ($e instanceof BaseException)
{
// 自定义异常
$this->code = $e->code;
$this->msg = $e->msg;
}
else{
// 服务器未处理异常
if($debug){
$message = $e->getMessage();
$file_name = $e->getFile();
$line_num = $e->getLine();
echo ' file_name : '.$file_name . '<br> line_num : '.$line_num . '<br> message : ' . $message;
exit;
}
$this->msg = Registry::get('config')->exception->sys->msg;
$this->code = Registry::get('config')->exception->sys->code;
$this->recordErrorLog($e);
}
$this->failed($this->code, $this->msg);
}
/*
* 将异常写入日志
*/
private function recordErrorLog($e)
{
if($e instanceof \ErrorException || $e instanceof \Exception){
$message = $e->getMessage();
$file_name = $e->getFile();
$line_num = $e->getLine();
$msg = ' file_name : '.$file_name . ' line_num : '.$line_num . ' message : ' . $message;
// LogUtil::error('exception handler', $msg);
}
if(is_string($e) || is_int($e)){
// LogUtil::error('exception handler', $e);
}
}
}
\ No newline at end of file
<?php
namespace App\Exception\custom;
/**
* 异常code规则:前3位是大类,后三位是细分
* 比如 参数异常、验签异常、用户异常 三大类,分别是 101000、102000、103000
* 用户异常又细分,比如未登录103001、密码错误103002
*/
class Code
{
const PARAM = 101000;
const SIGN = 102000;
}
\ No newline at end of file
<?php
namespace App\Exception\custom;
use App\Exception\BaseException;
/**
* Class TestException
* @package App\Exception\custom
*/
class CodeSpecialException extends BaseException
{
public function __construct($err, $re_msg="")
{
$params = [];
$code = \Yaconf::get("errorcode.common.code.".$err);
$msg = $re_msg ?? \Yaconf::get("errorcode.common.msg.".$err);
if(strlen($code) && strlen($msg)){
$params = [
'code' => $code,
'msg' => $msg
];
}
parent::__construct($params);
}
}
\ No newline at end of file
<?php
namespace App\Exception\custom;
use App\Exception\BaseException;
class ParamException extends BaseException
{
protected $base_code = Code::PARAM;
protected $cus = [
0 => 'param cannot be empty',
1 => 'result is empty'
];
}
\ No newline at end of file
<?php
namespace App\Exception\custom;
use App\Exception\BaseException;
class SignException extends BaseException
{
protected $base_code = Code::SIGN;
protected $cus = [
0 => '验签失败',
1 => '2失败',
2 => '2失败',
];
}
\ No newline at end of file
<?php
namespace App\Exception\custom;
use App\Exception\BaseException;
/**
* Class TestException
* @package App\Exception\custom
*/
class TestException extends BaseException
{
/**
* Test 模块的错误码起始值
* 最终用户获取到的错误码,是 $base_code + 指定的 $cus 的 key
* @var int
*/
protected $base_code = Code::TEST;
protected $cus = [
0 => 'test error',
1 => 'test error2'
];
}
\ No newline at end of file
<?php
namespace Helpers;
/**
* AES 加密 解密类库
*/
class Aes {
/**
* 加密
* @param String content 加密的字符串
* @return string|string[]
*/
public static function encrypt($content = '') {
$key = \Yaf\Registry::get('config')->aes->key;
$iv = \Yaf\Registry::get('config')->aes->iv;
$data = openssl_encrypt($content, "AES-128-CBC", $key, 0, $iv);
return self::urlsafeEncrypt($data);
}
/**
* 解密
* @param String content 解密的字符串
* @return String
*/
public static function decrypt($content) {
$content = self::urlsafeDecrypt($content);
$key = \Yaf\Registry::get('config')->aes->key;
$iv = \Yaf\Registry::get('config')->aes->iv;
return openssl_decrypt($content, "AES-128-CBC", $key, 0, $iv);
}
public static function urlsafeEncrypt($string) {
return str_replace(array('+','/','='), array('-','_',''), $string);
}
public static function urlsafeDecrypt($string) {
$string = str_replace(array('-','_'), array('+','/'), $string);
$mod4 = strlen($string) % 4;
if ($mod4) {
$string .= substr('====', $mod4);
}
return $string;
}
// openssl AES 向量长度固定 16 位 这里为兼容建议固定长度为 16 位
// 随机字符串
public static function getRandomStr($length = 16) {
$char_set = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9'));
shuffle($char_set);
return implode('', array_slice($char_set, 0, $length));
}
/**
* 生成每次请求的sign
* @param array $data
* @return string
*/
public static function createSign($data = []) {
// 1 按字段排序
ksort($data);
// 2拼接字符串数据 &
$string = http_build_query($data);
// 3通过aes来加密
$string = self::encrypt($string);
return $string;
}
/**
* 检查sign是否正常
* @param array $data
* @param $data
* @return bool
*/
public static function checkSign($data) {
if(!isset($data['sign'])
|| empty($data['sign'])
|| !isset($data['reqid'])
|| !isset($data['appid'])
|| !isset($data['platform'])
) {
return false;
}
$str = self::decrypt($data['sign']);
if(empty($str)) {
return false;
}
// appid=xx&version=023300&....
parse_str($str, $arr);
if(!is_array($arr)
|| !isset($arr['reqid'])
|| !isset($arr['appid'])
|| !isset($arr['platform'])
|| $arr['reqid'] != $data['reqid']
|| $arr['appid'] != $data['appid']
|| $arr['platform'] != $data['platform']
) {
return false;
}
$timeout = \Yaf\Registry::get('config')->aes->timeout;
$switch = \Yaf\Registry::get('config')->aes->switch;
if($switch) {
if ((time() - ceil($arr['ts'] / 1000)) > $timeout) {
return false;
}
}
return true;
}
}
\ No newline at end of file
<?php
namespace Helpers;
trait ApiResponse
{
/**
* 成功返回格式
*
* @param array $data
* @param string $status
* @param array $headers
* $headers['content-type'] = 'application/json'
* @return void
* @throws \Exception
*/
public function success($data = [], $status = "success", $headers = [])
{
$this->respond(0, $status, '', $data, $headers);
}
/**
* 失败返回格式,有失败原因
*
* @param [type] $code
* @param string $reason
* @param array $headers
* @return void
* @throws \Exception
*/
public function failed($code, $status = "failed", $reason = '', $headers = [])
{
$this->respond($code, $status, $reason, [], $headers);
}
public function respond($code, $status, $reason, array $data = [], $headers = [])
{
$resp = [
'code' => $code,
'status' => $status
];
if ($reason) {
$resp['reason'] = $reason;
}
if ($data && ! is_array($data)) {
$data = json_decode(strval($data), true);
if ($$data === NULL) {
throw new \Exception('非json格式', 13);
}
}
$response = \Yaf\Dispatcher::getInstance()->getResponse();
$response->setHeader('content-type', 'application/json');
if ($data) {
$apiHeader = "";
if (isset($data['code'])) {
$apiHeader = $apiHeader . strval($data['code']);
}
if (isset($_SESSION['userid'])) {
$apiHeader = $apiHeader . "_" . strval($_SESSION['userid']);
}
if (strlen($apiHeader) > 0) {
$response->setHeader('Api-Result', $apiHeader);
}
}
// headder中输出主要依赖服务的处理时间
if (isset($GLOBALS['DEPENDENT-REQUEST-TIME'])) {
$response->setHeader('Dependent-Request-Time', $GLOBALS['DEPENDENT-REQUEST-TIME']);
}
if (isset($GLOBALS['DEPENDENT-STATUS'])) {
$response->setHeader('Dependent-Status', $GLOBALS['DEPENDENT-STATUS']);
}
if (isset($GLOBALS['DEPENDENT-URI'])) {
$response->setHeader('Actual-Request-Url', $GLOBALS['DEPENDENT-URI']);
}
if ($headers) {
foreach ($headers as $name => $value) {
$response->setHeader($name, $value);
}
}
$data = array_merge($resp, $data);
$response->setBody(json_encode($data, JSON_UNESCAPED_UNICODE));
$response->response();
if (function_exists('fastcgi_finish_request')) {
ignore_user_abort(true);
fastcgi_finish_request();
}
}
}
\ No newline at end of file
This diff is collapsed.
<?php
namespace Validate;
use Api\PhpUtils\Validate\Validate;
use App\Exception\custom\Code;
use App\Exception\custom\ParamException;
use Yaf\Dispatcher;
class BaseValidate extends Validate
{
/**
* 参数校验
* @return array | boolean
* @throws
*/
public function validate()
{
$params = Dispatcher::getInstance()->getRequest()->getRequest();
$result = $this->batch()->check($params);
if (!$result) {
$msg = $this->getError() ? array_shift($this->error) : "参数错误";
throw new ParamException([
'msg' => $msg,
"code" => Code::PARAM
]);
} else {
return true;
}
}
protected function isPositiveInteger($value)
{
if (is_numeric($value) && is_int($value + 0) && ($value + 0) > 0) {
return true;
} else {
return false;
}
}
protected function isMobile($value)
{
$rule = '^1(3|4|5|7|8)[0-9]\d{8}$^';
$result = preg_match($rule, $value);
if ($result) {
return true;
} else {
return false;
}
}
}
\ No newline at end of file
<?php
namespace Validate;
class TestValidate extends BaseValidate
{
/**
* @var array
* 基础规则
* 可以自定义方法,比如 checkIDs
* 可以使用系统已定义的验证规则,在 Validate 基类的 $typeMsg 中查看
*/
protected $rule = [
'ids' => 'checkIDs',
'email' => 'email',
'age' => 'between:0,100',
];
/**
* 自定义某一规则不符合后,输出给用户的提示语
* @var string[]
*/
protected $message = [
"ids.require" => "ids 参数必填",
"ids.checkIDs" => "ids 格式有误",
"email.email" => "邮箱格式有误",
];
/**
* 场景定义
* edit 场景仅需要验证 email 与 age,并且不需要增加或删除验证条件
* @var \string[][]
*/
protected $scene = [
'edit' => ['email','age'],
];
/**
* 场景定义2
* 定义一个方法,scene 做前缀,驼峰的加上场景名
* only 定义需要验证的参数
* append 在 rule 定义的条件基础上,再增加其他验证条件
* remove 删除在 rule 中定义的某条件
* @return TestValidate
*/
public function sceneAdd()
{
return $this->only(['ids','email','age'])
->append('ids', 'require')
->append('email', 'require');
}
protected function checkIDs($params)
{
//id字符串转为id数组
$params = explode(',', $params);
if (empty($params)) {
return false;
}
//每个id只能由字母数字组成
foreach ($params as $param) {
if (!$this->regex($param, '/^[A-Za-z0-9]+$/')) {
return false;
}
}
return true;
}
}
\ No newline at end of file
<?php
use Yaf\Application;
if (!function_exists('config')) {
/**
* 假设当前环境为 dev(在php.ini中配置)
* 当前 appid 为 ProgramA (在conf/application.ini中配置)
* 则 config("test", "a"); 将取到 yaconf/test.ini 中, [dev-ProgramA] 下定义的配置 a 的值
*
* @param $file
* @param string $param
* @return string
*/
function config($file, $param = ''){
if (empty($file) || !file_exists(ROOT_PATH."/yaconf/".$file.".ini")){
return "";
}
$env = Application::app()->environ() ?? "dev";
$appid = Application::app()->getConfig()->get("appid");
$key = $file.'.'. (empty($appid) ? $env : $env.'-'.$appid);
$key = $param ==='' ? $key : $key.".".$param;
return \Yaconf::get($key);
}
if (!function_exists('appConfig')) {
/**
* 获取框架的 conf/application 中的配置
* @param $param
* @return mixed
*/
function appConfig($param){
return Application::app()->getConfig()->get($param);
}
}
}
\ No newline at end of file
## library
#### 全局的处理函数请在heapler.php中编写
#### 验证器,工具类,其他类库请新建目录,指定命名空间,细节参考已有的类库文件
<?php
namespace App\Models\demo\mongo;
use Api\PhpUtils\Mongo\MongoBase;
class Test extends MongoBase
{
protected function getConfigIndex()
{
return "demo";
}
protected function getDatabaseName()
{
return "demo";
}
protected function getCollectionName()
{
return "user";
}
protected function getWhere($params)
{
return $params;
}
protected function getQueryOptions($fields = [], $sort = [], $limit = [])
{
$options = [
'maxTimeMS' => 800,
'limit' => 100
];
if (is_array($fields) && !empty($fields)) {
$options['projection'] = $fields;
}
if (is_array($sort) && !empty($sort)) {
$options['sort'] = $sort;
}
if (is_array($limit)) {
if (isset($limit['start'])) {
$options['skip'] = intval($limit['start']);
}
if (isset($limit['count'])) {
$options['limit'] = intval($limit['count']);
}
}
return $options;
}
}
<?php
namespace App\Models\demo\mongo;
use Api\PhpUtils\Mongo\Base;
class User extends Base
{
protected static $instance;
protected static $db_config_index = 'metro';
protected function getCollectionName()
{
return 'user';
}
protected function getWhere($params)
{
$where = [];
if (isset($params['name']) && !empty($params['name'])) {
$where['name'] = $params['name'];
}
return $where;
}
protected function getQueryOptions()
{
$options = [
'projection' => [
'mobile' => 1
],
'maxTimeMS' => 800,
'limit' => 5,
'sort' => ['mobile' => -1]
];
return $options;
}
}
<?php
namespace App\Models\demo\mysql;
use Api\PhpUtils\Mysql\Base;
class User extends Base
{
protected static $write;
protected static $read;
protected static $db_config_index = 'metro';
protected function getConfigIndex()
{
return 'metro';
}
protected function getTableName()
{
return 'user';
}
protected function getPKey()
{
return 'id';
}
protected function getWhere($params)
{
$where = [];
if (isset($params['ids']) && !empty($params['ids'])) {
$where['id'] = $params['ids'];
}
if (isset($params['name']) && !empty($params['name'])) {
$where['name'] = $params['name'];
}
if (isset($params['id[>]']) && !empty($params['id[>]'])) {
$where['id[>]'] = $params['id[>]'];
}
return $where;
}
}
## Models
#### 已有的demo请在开发时删除
<?php
namespace App\Base;
use App\Exception\custom\SignException;
use Helpers\ApiResponse;
use Yaf\Controller_Abstract;
use Helpers\Aes;
abstract class Base extends Controller_Abstract
{
use ApiResponse;
/**
* 前置操作方法列表
*
* protected $beforeActionList = [
* 'first', //在所有方法前执行
* 'second' => ['except'=>'hello'], //在除了hello以外的其它方法之前执行
* 'three' => ['only'=>'hello,data'] //只在hello,data方法前执行
* ];
*
* @var array $beforeActionList
* @access protected
*/
protected $beforeActionList = [];
public function init(){
// 前置操作方法
if (!empty($this->beforeActionList)) {
foreach ($this->beforeActionList as $method => $options) {
is_numeric($method) ? $this->beforeAction($options) : $this->beforeAction($method, $options);
}
}
}
/**
* 前置操作
* @access protected
* @param string $method 前置操作方法名
* @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
*/
private function beforeAction($method, $options = [])
{
$action = $this->getRequest()->getActionName();
if (isset($options['only'])) {
if (is_string($options['only'])) {
$options['only'] = explode(',', $options['only']);
}
if (!in_array($action, $options['only'])) {
return;
}
} elseif (isset($options['except'])) {
if (is_string($options['except'])) {
$options['except'] = explode(',', $options['except']);
}
if (in_array($action, $options['except'])) {
return;
}
}
call_user_func([$this, $method]);
}
/**
* 验签
*/
public function checkAes(){
$data = $this->getRequest()->getRequest();
if (! Aes::checkSign($data)) {
throw new SignException(["cus"=>0]);
}
}
}
<?php
namespace App\Base;
use Yaf\Controller_Abstract;
class Cli extends Controller_Abstract
{
public function init() {
\Yaf\Dispatcher::getInstance()->disableView();
}
}
\ No newline at end of file
<?php
use Api\PhpServices\Daemon\Daemon;
use App\Base\Cli;
use Yaf\Application;
/**
* 守护进程入口脚本
* conf/cli.ini 中配置脚本目录,默认在 /daemon
* 脚本必须继承 DaemonServiceInterface,参考 daemon/Test.php
* 以守护进程方式执行 Test 脚本命令参考:php public/cli.php daemon run "t=Test&n=2&j=abc"
* t : 脚本类名
* n : 进程数
* j : 进程别名,默认为脚本类名
*/
class DaemonController extends Cli
{
public function RunAction(){
$request = $this->getRequest();
$params = $request->getParams();
$task_name = $params['t'] ?? "";
$worker_num = $params['n'] ?? 4;
$rename = $params['j'] ?? $task_name;
if(empty($task_name)){
echo <<<EOD
usage: php cli.php daemon run "t=[string]&n=[int|default:4]&j=[string]"
t daemo 脚本类名
n 进程数
j 进程别名,默认同脚本类名
stop : kill -SIGUSR1 master进程id\n
EOD;
return;
}
$script_dir = Application::app()->getConfig()->get("daemon.script_dir");
$task_class = $script_dir.$task_name;
if(!class_exists($task_class)){
die("脚本不存在!");
}
$check = new ReflectionClass($task_class);
if(!$check->implementsInterface('Api\PhpServices\Daemon\DaemonServiceInterface')){
die("脚本需实现 DaemonServiceInterface 接口!");
}
(new Daemon($rename, $task_class, $worker_num))->start();
}
}
\ No newline at end of file
<?php
use App\Base\Cli;
class TestController extends Cli
{
public function IndexAction() {
$request = $this->getRequest();
$params = $request->getParams();
$res = [
"data" => $params,
"code" => 0
];
$response = $this->getResponse();
$response->setBody(json_encode($res));
}
}
\ No newline at end of file
<?php
use App\Base\Base;
use App\Services\demo\MongoService;
class MongoController extends Base
{
public function addAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->addUser();
var_dump($ret);
exit;
}
public function addManyAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->addManyUser();
var_dump($ret);
exit;
}
public function deleteAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->deleteUser();
var_dump($ret);
exit;
}
public function updateAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->updateUser();
var_dump($ret);
exit;
}
public function getAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->getUser();
var_dump($ret);
exit;
}
public function getManyAction()
{
$mongo_service = new MongoService();
$ret = $mongo_service->getManyUser();
var_dump($ret);
exit;
}
}
<?php
use App\Base\Base;
use App\Services\demo\MysqlService;
class MysqlController extends Base
{
public function addAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->addUser();
var_dump($ret);
exit;
}
public function addManyAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->addManyUser();
var_dump($ret);
exit;
}
public function deleteAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->deleteUser();
var_dump($ret);
exit;
}
public function updateAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->updateUser();
var_dump($ret);
exit;
}
public function getAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->getUser();
var_dump($ret);
exit;
}
public function getManyAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->getManyUser();
var_dump($ret);
exit;
}
public function countAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->countUser();
var_dump($ret);
exit;
}
public function maxAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->maxUser();
var_dump($ret);
exit;
}
public function minAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->minUser();
var_dump($ret);
exit;
}
public function avgAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->avgUser();
var_dump($ret);
exit;
}
public function sumAction(){
$mysql_service = new MysqlService();
$ret = $mysql_service->sumUser();
var_dump($ret);
exit;
}
}
<?php
use App\Base\Base;
use Api\PhpUtils\Http\Request;
class RequestController extends Base
{
public function GetAction()
{
$url = 'http://10.103.17.132:8007/adserver/goodsAds';
$options = [
'query' => [
'docIdList' => '0SQ0d3dH',
'appId' => 'pro',
'platform' => 0
],
'timeout' => 0.5 //自定义超时秒数,覆盖默认值
];
$request = new Request();
$ret = $request->get($url, $options);
var_dump($ret);
exit;
}
public function PostAction(){
$url = 'http://sso.dengwei4378.com/api/master/getInfoByMasterName';
$options = [
'form_params' => [
"masterName" => 'dengwei4378',
"system" => 'rbac'
],
'timeout' => 1 //自定义超时秒数,覆盖默认值
];
$request = new Request();
$post_ret = $request->post($url, $options);
var_dump($post_ret);
exit;
}
/**
* 并发GET
*/
public function ConGetAction(){
$params = [
0 => [
'url' => 'http://sso.dengwei4378.com',
'timeout' => 3 //自定义超时秒数,覆盖默认值
],
1 => [
'url' => 'https://api.github.com/',
'timeout' => 3 //自定义超时秒数,覆盖默认值
],
];
$request = new Request();
$ret = $request->concurrencyGet($params);
var_dump($ret);
exit;
}
public function ConPostAction(){
$params = [
0 => [
'url' => 'http://lock-screen-push.int.yidian-inc.com/lock-screen/list',
'headers' => [
'Content-type' => 'application/json',
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
],
'json' => [ //json格式
"bizid" => "YDZX",
"uid" => "765073697",
"platform" => "1",
"appid" => "hipu"
],
'timeout' => 0.1 //自定义超时秒数,覆盖默认值
],
1 => [
'url' => 'http://sso.dengwei4378.com/api/master/getInfoByMasterName',
'form_params' => [ //表单格式
"masterName" => 'dengwei4378',
"system" => 'rbac'
],
'timeout' => 0.2 //自定义超时秒数,覆盖默认值
],
];
$request = new Request();
$ret = $request->concurrencyPost($params);
var_dump($ret);
exit;
}
}
<?php
use App\Base\Base;
use Helpers\Aes;
use Api\PhpUtils\Http\Request;
use Api\PhpServices\Session\SessionManager;
use App\Exception\BaseException;
class SessionController extends Base
{
public function StartAction()
{
// $key = 'asdfasdfasdfasdf';
// $iv = 'fdsafdsafdsafdsa';
// $salt = "12341/5";
// var_dump(base64_encode($salt));
// var_dump(strtolower(str_replace(array('+','/','='), array(''), base64_encode($salt))));
// var_dump($data = openssl_encrypt($salt, "AES-128-CBC", $key, 0, $iv));
// var_dump(openssl_decrypt($data, "AES-128-CBC", $key, 0, $iv));
// exit;
// $result = (new Request())->get('http://10.126.171.30:18080/topom', [], 1);
// // $proxy = $result['stats']['proxy'];
// $server = $result['stats']['group']['models'];
// $arr = [];
// foreach ($server as $item) {
// $arr[] = $item['servers'][1]['server'];
// }
// print_r($arr);
// exit;
// print_r(\Yaconf::get('interface.test.service'));exit;
// echo $s = '{'.str_replace(array('+','/','='), array('-','_',''), base64_encode('12345')).'}';
// exit;
// $s = json_decode(json_encode(['a' =>'{MTIzNDU}e3eec597a5a3f9420b445c89d38a6fbc7c']),true);
// print_r($s);exit;
// phpinfo();exit;
// $key = 'interface.test.service.session.get';
// echo \Yaconf::get($key);
// exit;
$ret = SessionManager::start();
// print_r($_SERVER);
// print_r($_COOKIE);
var_dump($ret);
print_r($_SESSION);
// $ret1 = SessionManager::destroy();
// // print_r($_SERVER);
// // print_r($_COOKIE);
// var_dump($ret1);
// print_r($_SESSION);
// var_dump($ret);
}
public function CreateAction()
{
$ret = SessionManager::set('metro', '12345', true, ['a' => 'aaa', 'b' => 'bbb', 'c' => 'ccc', 'd' => 'ddd'], 1);
var_dump($ret);
print_r($_SESSION);
exit;
}
public function DestroyAction()
{
$ret = SessionManager::destroy();
// print_r($_SERVER);
// print_r($_COOKIE);
var_dump($ret);
print_r($_SESSION);
}
public function GetAction()
{
print_r($request = $this->getRequest()->getRequest());
$sessionid = $request['sid'] ?? 'aedcc37ea232aade90037716ddab1debe6';
$url = $this->getBaseUri() . '/get-session';
$query = ['sessionid' => $sessionid];
// $query = ['sessionid' => 'jssionidtest'];
$result = (new Request())->get($url, $query, 3000);
print_r($result);
exit;
if (isset($result['code']) && $result['code'] == 0 ) {
return true;
}
return false;
}
public function SetAction()
{
$url = $this->getBaseUri() . '/set-session';
$sessionData = ['id' => 123, 'name' => 'test'];
$params = ['sessionid' => 'jssionidtest11', 'data' => ($sessionData), 'expire' => 600, 'key' => 'c071f6e5923a648a5ef88f32b6f738e1'];
$result = (new Request())->post($url, $params, 1000, 'json');
print_r($result);
}
public function DelAction()
{
$url = $this->getBaseUri() . '/delete-session';
$query = ['sessionid' => ['jssionidtest1', 'jssionidtest'], 'key' =>'c071f6e5923a648a5ef88f32b6f738e1'];
$result = (new Request())->get($url, $query, 1000);
print_r($result);
}
public function MgetAction()
{
$url = $this->getBaseUri() . '/get-many-session';
$query = ['sessionid' =>['YPZQISU1Tjxg3wJLFWJmVA', 'E4vo3R5EfaPS-pnyDzia3Q']];
$result = (new Request())->get($url, $query, 2000);
print_r($result);
exit;
$url = $this->getBaseUri() . '/get-many-session';
$query = ['sessionid' =>['jssionidtest2', 'jssionidtest1']];
$result = (new Request())->get($url, $query, 2000);
print_r($result);
exit;
// $validSessionIds[] = 'a';
// $validSessionIds[] = 'b';
// $validSessionIds[] = 'c';
// echo $validSessionIds[rand(0, count($validSessionIds) - 1)];
// exit;
// // todo 请求接口读redis
// echo $url = $this->getBaseUri() . '/get-session1';
// $query = ['sessionid' => 'jssionidtest'];
// $result = (new Request())->get($url, $query, 1000);
// if (isset($result['code']) && $result['code'] == 0 && isset($result['result'])) {
// $data = $result['result'];
// print_r($data);
// }else {
// print_r($result);
// }
// exit;
// $r = \Api\PhpUtils\Common\IP::find('114.114.114.114');
// print_r($r);
// exit;
// $_SESSION['abc'] = 'ttt';
// echo $_SESSION['abc'];
// echo $_SESSION['abc'];
// echo $_SESSION['abc'];
// echo $_SESSION['abc'];
// exit;
// $abc = '123'. "\x7F" . 456;
// var_dump($abc);exit;//string(7) "123456" 不可见字任作为分隔符
// $a = base64_encode('6543211111dasfsdfasdf1111asdfasdfs1111');
// var_dump($a);
// var_dump(sha1($a));
// var_dump(substr(sha1($a), -2));
// $a = $a . substr(sha1($a), -2);
// var_dump($a);
// var_dump(substr($a, 0, -2));
// $a = substr($a, 0, -2);
// var_dump($a);
// var_dump(sha1($a));
// var_dump(substr(sha1($a), -2));
// exit;
// var_dump(strlen($a));
// var_dump($a);exit;
// var_dump($this->response);
// var_dump(Yaf\Dispatcher::getInstance()->getResponse());
// exit;
// $data = ['appid'=> 'appid', 'verison'=>'023200', 'reqid'=>'11adddddddfasdfasdfasdfasdfdsafwerqwersfasfasdvasdfsdfffs1', 'platform'=>'1', 'ts' => ceil(microtime(true) * 1000)];
// echo $sign = Aes::createSign($data);
// exit;
// $data = ['appid'=> 'appid', 'verison'=>'023200', 'reqid'=>'111', 'platform'=>'1'];
// $data['sign'] = 'l4lBCE3B_v-ODFeuoxFksl0IUY9ehBnjN_FtU3ESdrn_bMPDsEIxA8IUw0pDJ_kX06P6OOw23in46acBucgceSBE2Vx3Ew52w3FfmRn8IhE';
// $sign = Aes::checkSign($data);
// var_dump($sign);
// exit;
// $obj = new App\Models\User();
// print_r($obj->getUserData());//Array ( [name] => zhangsan [age] => 18 )
// exit;
// $http = $this->getRequest()->getRequest();
// var_dump($http);
// var_dump(file_get_contents("php://input"));
// var_dump($this->requestAll());yaf
// exit;
$res = [
"userid" => 'user',
"code" => 0
];
// print_r($this->failed(-1,'reason'));
$this->success($res);
}
/**
* get uri
* @return string
*/
public function getBaseUri()
{
$env = \Yaf\Application::app()->environ() ?? "dev1";
if ($env == 'product') {
$this->baseUri = 'http://a4.go2yd.com/Website/session';
}elseif ($env == 'dev') {
$this->baseUri = 'http://127.0.0.1/Website/session';
}else {
$this->baseUri = 'http://a4-1.go2yd.com/Website/session';
}
return $this->baseUri;
}
}
\ No newline at end of file
<?php
use App\Base\Base;
use App\Exception\custom\CodeSpecialException;
use App\Exception\custom\TestException;
use Validate\TestValidate;
class TestController extends Base
{
public function IndexAction()
{
/**
* 假设当前环境为 dev(在php.ini中配置)
* 当前 appid 为 ProgramA (在conf/application.ini中配置)
* 则下面将取到 yaconf/test.ini 中, [dev-ProgramA] 下定义的配置 a 的值
*/
echo config("test", "a");
/**
* 模拟抛出兼容老版本/app工厂的异常
*/
throw new CodeSpecialException("wechat_not_match");
/**
* 模拟抛出用户自定义异常
*/
throw new TestException([
'cus' =>1
]);
/**
* 模拟验证参数
*/
(new TestValidate())->scene("add")->validate();
/**
* 模拟正常返回结果
*/
$this->success([1]);
/**
* yaconf 配置文件规则
api php相关公共配置仓库,包含所有数据字典,公共配置文件及各项目特有配置文件。
以mysql.ini为例,里面包括多个项目多个环境的继承关系
节 的格式结构如下
公共的配置内容,不区分应用及环境
[commen]
;地铁项目配置
地铁项目继承common + 自己的配置,此节中配置会覆盖父级的相同配置(不区分环境)
[metro : commen]
地铁项目继承common + 自己的配置 + 生产环境特有有配置,此节中配置会覆盖父级的相同配置
[product : metro : commen]
地铁项目继承common + 自己的配置 + 测试环境特有有配置,此节中配置会覆盖父级的相同配置
[pre : product : metro : commen]
[test : product : metro : commen]
[dev : product : metro : commen]
;VPN项目配置
VPN项目继承common + 自己的配置,此节中配置会覆盖父级的相同配置(不区分环境)
[hubble : commen]
VPN项目继承common + 自己的配置 + 生产环境特有有配置,此节中配置会覆盖父级的相同配置
[product : hubble : commen]
VPN项目继承common + 自己的配置 + 测试环境特有有配置,此节中配置会覆盖父级的相同配置
[pre : product : hubble : commen]
[test : product : hubble : commen]
[dev : product : hubble : commen]
*/
}
}
This diff is collapsed.
## modules
#### 第一层文件夹 首字母要大写 ,请参考Test/controllers目录,
#### 已有的Test目录,请在开发时删除
\ No newline at end of file
<?php
namespace App\Plugins;
use App\Exception\ExceptionErrorCatch;
use Helpers\DebugLog;
class Hook extends \Yaf\Plugin_Abstract {
public function routerStartup(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
// 重定义异常处理
ExceptionErrorCatch::register();
}
public function routerShutdown(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
/*---Debug Begin---*/
if((defined('_IS_DEBUG') && _IS_DEBUG) || (isset($_REQUEST['__debug']) && strpos($_REQUEST['__debug'], _DEBUG_PASS) !== false))
{
// $_REQUEST['__debug'] = _DEBUG_PASS + 1 (2 数字表示级别 )
$debug_level = intval(substr($_REQUEST['__debug'], -1));
if ($debug_level > 0) {
define('DEBUG_LEVEL', $debug_level );
} else {
define('DEBUG_LEVEL', 1);
}
//Debug模式将错误打开
ini_set('display_errors', true);
//设置错误级别
error_reporting(_ERROR_LEVEL);
//开启ob函数
ob_start();
//Debug开关打开
DebugLog::init();
//注册shutdown函数用来Debug显示
register_shutdown_function(array('Helpers\DebugLog', 'show'));
} else {
define('DEBUG_LEVEL', 0);
}
// 调试探针,初始化完成,页面开始执行
DebugLog::time('init.php, start page');
}
public function dispatchLoopStartup(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
}
public function preDispatch(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
//处理$_POST, $_REQUEST请求,兼容application/json形式
if(!$_POST && $request->isPost()
&& $request->getServer('CONTENT_TYPE') == 'application/json') {
$jsonPost = file_get_contents("php://input");
if($jsonPost) {
$_POST = json_decode($jsonPost, true);
$ini = ini_get('request_order');
if($ini) {
$arrIni = str_split($ini, 1);
foreach($arrIni as $type) {
if(strtoupper($type) === 'G') {
$_REQUEST = array_merge($_REQUEST, $_GET);
}elseif(strtoupper($type) === 'P') {
$_REQUEST = array_merge($_REQUEST, $_POST);
}
}
}
}
}
}
public function postDispatch(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
}
public function dispatchLoopShutdown(\Yaf\Request_Abstract $request, \Yaf\Response_Abstract $response) {
}
}
<?php
namespace App\Services\demo;
use App\Models\demo\mongo\User;
class MongoService
{
public function addUser()
{
$user = User::getInstance();
$data = [
'_id' => 4,
'name' => 'dw04',
'mobile' => '17701340004'
];
return $user->add($data);
}
public function addManyUser()
{
$user = User::getInstance();
$data = [
[
'_id' => 5,
'name' => 'dw05',
'mobile' => '17701340005'
],
[
'_id' => 6,
'name' => 'dw06',
'mobile' => '17701340006'
]
];
return $user->addMany($data);
}
public function deleteUser()
{
$user = User::getInstance();
$params = [
'name' => 'dw01'
];
return $user->delete($params);
}
public function updateUser()
{
$user = User::getInstance();
$params = [
'name' => 'dw02'
];
$data = [
'mobile' => '17701340002'
];
return $user->update($params, $data);
}
public function getUser()
{
$user = User::getInstance();
$params = [
'name' => 'dw02'
];
return $user->get($params);
}
public function getManyUser()
{
$user = User::getInstance();
$params = [
'name' => 'dw02'
];
return $user->getMany($params);
}
}
<?php
namespace App\Services\demo;
use App\Models\demo\mysql\User;
class MysqlService
{
public function addUser()
{
$user = User::getWriteInstance();
$data = [
'name' => 'dw07',
'mobile' => '17701340007'
];
return $user->add($data);
}
public function addManyUser()
{
$user = User::getWriteInstance();
$data = [
[
'name' => 'dw08',
'mobile' => '17701340008'
],
[
'name' => 'dw09',
'mobile' => '17701340009'
]
];
return $user->addMany($data);
}
public function deleteUser()
{
$user = User::getWriteInstance();
$params = [
'name' => 'dw08'
];
return $user->delete($params);
}
public function updateUser()
{
$user = User::getWriteInstance();
$params = [
'name' => 'dw02'
];
$data = [
'mobile' => '15030690002'
];
return $user->update($params, $data);
}
public function getUser()
{
$user = User::getInstance();
$params = [
'name' => 'dw02'
];
$column = [
'name',
'mobile'
];
return $user->get($params, $column);
}
public function getManyUser()
{
$user = User::getInstance();
$params = [
'id[>]' => 3
];
$column = [
'name',
'mobile'
];
return $user->getMany($params, $column);
}
public function countUser()
{
$user = User::getInstance();
$params = [
'master_name' => 'dw04'
];
$column = 'id';
return $user->count($params, $column);
}
public function maxUser()
{
$user = User::getInstance();
$params = [
'id[>]' => 1
];
$column = 'id';
return $user->max($params, $column);
}
public function minUser()
{
$user = User::getInstance();
$params = [
'id[>]' => 1
];
$column = 'id';
return $user->min($params, $column);
}
public function avgUser()
{
$user = User::getInstance();
$params = [
'id[>]' => 1
];
$column = 'id';
return $user->avg($params, $column);
}
public function sumUser()
{
$user = User::getInstance();
$params = [
'id[>]' => 1
];
$column = 'id';
return $user->sum($params, $column);
}
}
## 业务处理放在这里
\ No newline at end of file
{
"name" : "yidian/yaf_demo",
"description": "",
"type": "project",
"license": "MIT",
"require": {
"php": ">=7.2",
"ext-json": "*",
"api/php_utils":"dev-master",
"api/php_services":"dev-master",
"ext-openssl": "*"
},
"minimum-stability": "dev",
"autoload" : {
"psr-4" : {
"App\\Plugins\\" : "application/plugins",
"App\\Services\\" : "application/services",
"App\\Models\\" : "application/models",
"App\\Base\\" : "application/modules/Base",
"App\\Exception\\" : "application/exception",
"Daemon\\" : "daemon"
}
},
"scripts": {
"post-update-cmd": [
"php -f rmgit.php"
]
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"repositories": {
"api/php_utils":{
"type":"vcs",
"url":"https://git.yidian-inc.com:8021/api0/php_utils.git"
},
"api/php_services":{
"type":"vcs",
"url":"https://git.yidian-inc.com:8021/api0/php_services.git"
}
}
}
\ No newline at end of file
[common]
application.directory = APP_PATH
application.bootstrap = APP_PATH "/Bootstrap.php"
application.library = APP_PATH"/library"
application.library.namespace = ""
application.modules="Index,Test"
appid = "metro"
;AES密钥 服务端和客户端必须保持一致,每个应用设置各自的16位key和iv
aes.key = "xxxxxxxxxxxxxxxx"
aes.iv = "xxxxxxxxxxxxxxxx"
aes.timeout = 5
;校验时间的开关
aes.switch = true
[exception]
debug = false
exception.user.code = -1
exception.user.msg = "error"
exception.sys.code = -1
exception.sys.msg = "system error"
[product : common : exception]
[pre : common : exception]
[test : common : exception]
[dev : common : exception]
[common]
application.directory = APPLICATION_PATH"/application"
application.library = APPLICATION_PATH"/application/library"
application.bootstrap = APPLICATION_PATH"/application/BootstrapCli.php"
application.dispatcher.catchException = true
application.dispatcher.throwException = true
daemon.script_dir = "\\Daemon\\"
appid = ""
[product : common]
[pre : common ]
[test: common ]
[dev : common ]
\ No newline at end of file
<?php
namespace Daemon;
use Api\PhpServices\Daemon\DaemonServiceInterface;
class Test implements DaemonServiceInterface
{
public function run()
{
// throw new \Exception("test exception");
sleep(10);
}
}
\ No newline at end of file
ipdata等文件放在这里
\ No newline at end of file
#!/usr/bin/env bash
# Start commands for each container, one cmd a line
START_CMDS="
cd /home/services && ${start_cmd}
"
QA_PRE_START_CMD="
"
# Container names for each container, one name a line, same order with $start_cmds
CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Port maps for each container, one map a line, same order with $start_cmds
DOCKER_PORT_MAPS="
${TARGET_PORT}:9000
"
# This is for changing container name, remove old containers when deploy new one
OLD_CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Volumn maps for each container, one map a line, same order with $start_cmds
DOCKER_VOLUMN_MAPS="
/home/worker/_logs/api-${env}-${domain_prefix}:/home/services/api.go2yd.com/logs
"
# Other docker run options
DOCKER_RUN_OPTIONS="--cap-add SYS_PTRACE --restart=always --privileged"
# Image name
IMAGE_NAME="docker2.yidian.com:5000/publish/${COMMIT_JOB}-${COMMIT_NUMBER}-image"
# This is for stopping container, kill sepicify process inside the container before 'docker stop' and 'docker rm'
DOCKER_PRESTOP_CMD='mv /var/lib/logrotate.status /home/services/api.go2yd.com/logs/logrotate.status'
# Service port for apitest
SERVICE_PORT=${TARGET_PORT}
# Service port inside container
ORIGIN_SERVICE_PORT="9000"
#!/usr/bin/env bash
# Start commands for each container, one cmd a line
START_CMDS="
cd /home/services && ${start_cmd}
"
QA_PRE_START_CMD="
"
# Container names for each container, one name a line, same order with $start_cmds
CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Port maps for each container, one map a line, same order with $start_cmds
DOCKER_PORT_MAPS="
${TARGET_PORT}:9000
"
# This is for changing container name, remove old containers when deploy new one
OLD_CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Volumn maps for each container, one map a line, same order with $start_cmds
DOCKER_VOLUMN_MAPS="
/home/worker/_logs/api-${env}-${domain_prefix}:/home/services/api.go2yd.com/logs
"
# Other docker run options
DOCKER_RUN_OPTIONS="--cap-add SYS_PTRACE --restart=always --privileged"
# Image name
IMAGE_NAME="docker2.yidian.com:5000/publish/${release_job}-${release_number}-image"
# This is for stopping container, kill sepicify process inside the container before 'docker stop' and 'docker rm'
DOCKER_PRESTOP_CMD='mv /var/lib/logrotate.status /home/services/api.go2yd.com/logs/logrotate.status'
# Service port for apitest
SERVICE_PORT=${TARGET_PORT}
# Service port inside container
ORIGIN_SERVICE_PORT="9000"
#!/usr/bin/env bash
# Start commands for each container, one cmd a line
START_CMDS="cd /home/services && ${start_cmd}
"
# QA_PRE_START_CMD='
# export collect_coverage=true
# '
# Container names for each container, one name a line, same order with $start_cmds
CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Port maps for each container, one map a line, same order with $start_cmds
DOCKER_PORT_MAPS="
${TARGET_PORT}:9000
"
# This is for changing container name, remove old containers when deploy new one
OLD_CONTAINER_NAMES="
api-${env}-${domain_prefix}
"
# Volumn maps for each container, one map a line, same order with $start_cmds
DOCKER_VOLUMN_MAPS="
/home/worker/_logs/api-${env}-${domain_prefix}:/home/services/api.go2yd.com/logs
"
# Other docker run options
DOCKER_RUN_OPTIONS="--cap-add SYS_PTRACE --restart=always --privileged"
# Image name
IMAGE_NAME="docker2.yidian.com:5000/publish/${COMMIT_JOB}-${COMMIT_NUMBER}-image"
# This is for stopping container, kill sepicify process inside the container before 'docker stop' and 'docker rm'
DOCKER_PRESTOP_CMD='mv /var/lib/logrotate.status /home/services/api.go2yd.com/logs/logrotate.status'
# Service port for apitest
SERVICE_PORT=${TARGET_PORT}
# Service port inside container
ORIGIN_SERVICE_PORT="9000"
#!/usr/bin/env bash
DIST_FILE_NAME="*.tar.gz"
PROJECT_DIR="api.go2yd.com"
START_SCRIPT="./start_env/start_api.sh"
SYNC_DATA_OPERATIONS="
tar zxf *.tar.gz -C start_env/api.go2yd.com/htdocs/Website
"
DEST_FILE_NAME=""
DEST_FILE_PATH=""
BASE_IMAGE="docker2.yidian.com:5000/centos7/php72_without_nginx:20210201"
MAINTAINER="cuiweifeng \"cuiweifeng@yidian-inc.com\""
HOME_DIR="/home/services"
LOG_DIRS="
${HOME_DIR}/${PROJECT_DIR}/logs
"
DATA_DIRS="
"
#!/usr/bin/env bash
DIST_FILE_NAME="*.tar.gz"
PROJECT_DIR="api.go2yd.com"
START_SCRIPT="./start_env/start_api.sh"
SYNC_DATA_OPERATIONS="
tar zxf *.tar.gz -C start_env/api.go2yd.com/htdocs/Website
"
DEST_FILE_NAME=""
DEST_FILE_PATH=""
BASE_IMAGE="docker2.yidian.com:5000/centos7/php72_without_nginx:20210201"
MAINTAINER="cuiweifeng \"cuiweifeng@yidian-inc.com\""
HOME_DIR="/home/services"
LOG_DIRS="
${HOME_DIR}/${PROJECT_DIR}/logs
"
DATA_DIRS="
"
#!/usr/bin/env bash
DIST_FILE_NAME="*.tar.gz"
PROJECT_DIR="api.go2yd.com"
START_SCRIPT="./start_env/start_api.sh"
SYNC_DATA_OPERATIONS="
tar zxf *.tar.gz -C start_env/api.go2yd.com/htdocs/Website
"
DEST_FILE_NAME=""
DEST_FILE_PATH=""
BASE_IMAGE="docker2.yidian.com:5000/centos7/php72_without_nginx:20210201"
MAINTAINER="cuiweifeng \"cuiweifeng@yidian-inc.com\""
HOME_DIR="/home/services"
LOG_DIRS="
${HOME_DIR}/${PROJECT_DIR}/logs
"
DATA_DIRS="
"
This diff is collapsed.
[api.go2yd.com]
prefix = /home/services/api.go2yd.com
user = nobody
group = nobody
listen = 0.0.0.0:9000
;listen = /var/run/php-fpm/api.go2yd.com.sock
listen.backlog = 2048
listen.owner = nobody
listen.group = nobody
listen.mode = 0600
pm = static
pm.max_children = 256
pm.max_requests = 100
pm.status_path = /status-fpm
access.log = /home/services/api.go2yd.com/logs/access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %{mili}d %{kilo}M %C%%"
slowlog = /home/services/api.go2yd.com/logs/fpm-slow.log
request_slowlog_timeout = 1s
request_terminate_timeout = 6s
catch_workers_output = yes
env[PATH] = /usr/local/bin:/usr/bin:/bin
<?php
$config = array(
'debug' => false,
'mode' => 'development',
'extension' => 'tideways_xhprof',
'save.handler' => 'mongodb',
'db.host' => 'mongodb://10.126.150.23:27017',
'db.db' => 'xhprof',
'db.options' => array(),
'date.format' => 'Y-m-d H:i:s',
'detail.count' => 6,
'page.limit' => 25,
// Profile 1 in 100 requests.
// You can return true to profile every request.
'profiler.enable' => function () {
// 暂时关闭xhgui性能监控
// if (strpos($_SERVER['SCRIPT_NAME'], 'Action.php') !== false)
// {
// return true;
// }
return false;
},
'profiler.simple_url' => function ($url) {
return preg_replace('/\=\d+/', '', $url);
},
//'/home/admin/www/xhgui/webroot','F:/phpPro'
'profiler.filter_path' => array()
);
if (!extension_loaded('tideways_xhprof'))
{
error_log('xhgui - extension tideways_xhprof must be loaded');
return;
}
if ($config['debug'])
{
ini_set('display_errors', 1); // 该选项设置是否将错误信息作为输出的一部分显示到屏幕,或者对用户隐藏而不显示。
}
// 指定监控的目录
if (is_array($config['profiler.filter_path']) && in_array($_SERVER['DOCUMENT_ROOT'], $config['profiler.filter_path']))
{
return;
}
if ((!extension_loaded('mongo') && !extension_loaded('mongodb')) && $config['save.handler'] === 'mongodb')
{
error_log('xhgui - extension mongo not loaded');
return;
}
if (!shouldRun())
{
return;
}
if (!isset($_SERVER['REQUEST_TIME_FLOAT']))
{
$_SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
}
if ($config['extension'] == 'tideways_xhprof' && extension_loaded('tideways_xhprof'))
{
tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_MEMORY | TIDEWAYS_XHPROF_FLAGS_MEMORY_MU | TIDEWAYS_XHPROF_FLAGS_MEMORY_PMU | TIDEWAYS_XHPROF_FLAGS_CPU);
}
else
{
error_log("Please check the extension name in config.php, you can use the 'php -m' command.");
return;
}
//注册一个会在php中止时执行的函数
register_shutdown_function(
function () {
$data['profile'] = tideways_xhprof_disable();
// ignore_user_abort(true) allows your PHP script to continue executing, even if the user has terminated their request.
// Further Reading: http://blog.preinheimer.com/index.php?/archives/248-When-does-a-user-abort.html
// flush() asks PHP to send any data remaining in the output buffers. This is normally done when the script completes, but
// since we're delaying that a bit by dealing with the xhprof stuff, we'll do it now to avoid making the user wait.
ignore_user_abort(true);
flush();
$uri = array_key_exists('REQUEST_URI', $_SERVER) ? $_SERVER['REQUEST_URI'] : null;
if (empty($uri) && isset($_SERVER['argv']))
{
$cmd = basename($_SERVER['argv'][0]);
$uri = $cmd . ' ' . implode(' ', array_slice($_SERVER['argv'], 1));
}
$time = array_key_exists('REQUEST_TIME', $_SERVER) ? $_SERVER['REQUEST_TIME'] : time();
$requestTimeFloat = explode('.', $_SERVER['REQUEST_TIME_FLOAT']);
if (!isset($requestTimeFloat[1]))
{
$requestTimeFloat[1] = 0;
}
$requestTs = new MongoDB\BSON\UTCDateTime($time * 1000);
$requestTsMicro = new MongoDB\BSON\UTCDateTime($_SERVER['REQUEST_TIME_FLOAT'] * 1000);
$data['meta'] = array(
'url' => $uri,
'SERVER' => $_SERVER,
'get' => $_GET,
'env' => $_ENV,
'simple_url' => simpleUrl($uri),
'request_ts' => $requestTs,
'request_ts_micro' => $requestTsMicro,
'request_date' => date('Y-m-d', $time),
);
// 保存数据到MongoDB
save2mongo($data);
}
);
function simpleUrl($url)
{
global $config;
$callable = $config['profiler.simple_url'];
if (is_callable($callable))
{
return call_user_func($callable, $url);
}
return preg_replace('/\=\d+/', '', $url);
}
function shouldRun()
{
global $config;
$callback = $config['profiler.enable'];
if (!is_callable($callback))
{
return false;
}
return (bool)$callback();
}
function save2mongo($data)
{
global $config;
$manager = new MongoDB\Driver\Manager($config['db.host']);
$bulk = new MongoDB\Driver\BulkWrite();
try
{
$bulk->insert($data);
$writeConcern = new MongoDB\Driver\WriteConcern(1, 100);
$manager->executeBulkWrite($config['db.db'] . '.results', $bulk, $writeConcern);
} catch (Exception $e)
{
error_log('xhgui - ' . $e->getMessage());
}
}
#!/usr/bin/env bash
working_dir=`dirname $0`
echo "working_dir :"$working_dir
CGI_CLIENT="${working_dir}/cgi-fcgi"
function log()
{
echo "[`date +%Y-%m-%d %H:%M:%S`] $@"
}
export LD_LIBRARY_PATH=${working_dir}
if [ ! -x "${CGI_CLIENT}" ]
then
log "cgi client not found or not executable"
exit 1
fi
function fcgi_get()
{
document_root=$1
script_file=$2
host=$3
port=$4
server_name=$5
REQUEST_URI=${script} \
SERVER_ADDR=$3 \
REQUEST_METHOD=GET \
SERVER_NAME=${server_name} \
DOCUMENT_ROOT=${document_root} \
DOCUMENT_URI=${script} \
SCRIPT_NAME=${script} \
SCRIPT_FILENAME=${document_root}${script} \
QUERY_STRING= \
REMOTE_ADDR=127.0.0.1 \
GATEWAY_INTERFACE=CGI/1.1 \
${CGI_CLIENT} -bind -connect ${host}:${port}
}
function api_get()
{
script=$1
port=$2
domain_prefix=$3
fcgi_get "/home/services/api.go2yd.com/htdocs/Website/public" ${script} 127.0.0.1 ${port} {$domain_prefix}".go2yd.com"
}
result=`api_get "/test.php" $1 $2 | awk -F"\r" '{print $NF}'`
check=`echo ${result} | grep "success"`
if [ "" = "${check}" ]
then
echo ${result}
exit 1
else
echo "success"
fi
#!/usr/bin/env bash
working_dir=`dirname $0`
CGI_CLIENT="${working_dir}/cgi-fcgi"
export LD_LIBRARY_PATH=${working_dir}
function fcgi_get()
{
document_root=$1
script_file=$2
host=$3
port=$4
server_name=$5
REQUEST_METHOD=GET \
SERVER_NAME=${server_name} \
DOCUMENT_ROOT=${document_root} \
DOCUMENT_URI=${script} \
SCRIPT_NAME=${script} \
SCRIPT_FILENAME=${document_root}${script} \
QUERY_STRING= \
GATEWAY_INTERFACE=CGI/1.1 \
REMOTE_ADDR = 127.0.0.1 \
REQUEST_URI=${script}
${CGI_CLIENT} -bind -connect ${host}:${port}
}
function api_get()
{
script=$1
fcgi_get "/home/services/api.go2yd.com/htdocs/Website" ${script} 127.0.0.1 9000 "qa-int.go2yd.com/Website"
}
while [ "`ps -ax | grep php-fpm -c`" = 0 ]; do
sleep 5;
done
sleep 10
for file in `find /home/services/api.go2yd.com/htdocs/Website/ | grep -oP '(?<=/home/services/api.go2yd.com/htdocs/Website).*\.php$'`; do
api_get $file
done
<?php
if ($_GET['clear'] == "true")
{
apcu_delete("coverage");
print("done!");
exit();
} else {
print("need param 'clear' be 'true'");
}
<?php
$coverage = apcu_fetch("coverage");
echo json_encode($coverage);
<?php
var_dump(apcu_cache_info(true));
var_dump(apcu_sma_info());
var_dump(memory_get_usage());
#!/usr/bin/env bash
dir=`dirname $0`
cp -r ${dir}/coverage /home/services/api.go2yd.com/
cp -r ${dir}/collect_coverage /home/services/api.go2yd.com/htdocs/Website/
echo "zend_extension=/usr/lib64/php/modules/xdebug.so" >> /etc/php.ini
echo "auto_prepend_file=/home/services/api.go2yd.com/coverage/StartRecordCoverage.php" >> /etc/php.ini
sed -i 's/php_admin_value\[memory_limit\].*$/php_admin_value[memory_limit] = 1024M/g' /etc/php-fpm.d/api.go2yd.com.conf
nohup sh ${dir}/call_all.sh &
#日志分割
59 23 * * * /usr/sbin/logrotate -f /etc/logrotate.conf
#ipip 更新数据字典
0 3 * * * /bin/bash /home/services/ipip/ipip.sh > /home/services/api.go2yd.com/logs/ipip.log
This diff is collapsed.
This diff is collapsed.
#!/bin/bash
cd /home/services/api.go2yd.com/htdocs/Website/data && wget http://10.103.17.28/ipip/ipdata_ipv6v4_2in1.ipdb.zip && unzip -o ipdata_ipv6v4_2in1.ipdb.zip && rm -f ipdata_ipv6v4_2in1.ipdb.zip && cd -
/home/services/api.go2yd.com/logs/*.log {
su root root
daily
dateext
rotate 7
missingok
notifempty
create 755 nobody nobody
postrotate
/bin/kill -SIGUSR1 `cat /var/run/php-fpm/php-fpm.pid 2>/dev/null` 2>/dev/null || true
endscript
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
echo "success";
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment