Commit 28daa54f authored by cuiweifeng's avatar cuiweifeng

update :init

parents
composer.lock
/vendor/
.idea
.DS_Store
.project
\ No newline at end of file
生活圈商户系统工具类库,配合yaf框架使用的基础库
\ No newline at end of file
{
"name": "bp/php_utils",
"description": "bp php_utils",
"type": "library",
"require": {
"php": ">=7.2",
"guzzlehttp/guzzle": "6.3",
"catfan/medoo": "1.7.10",
"mongodb/mongodb": "1.4.3",
"ext-mbstring": "*",
"ext-exif": "*",
"ext-fileinfo": "*",
"ext-ctype": "*",
"elasticsearch/elasticsearch": "~7.0"
},
"autoload": {
"psr-4": {
"Api\\PhpUtils\\": "src/"
}
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Cache;
use Yaf\Application;
use Api\PhpUtils\Log\FileLog;
/**
* ApcuUtil 工具类
*
* apcu扩展不支持前缀,在此类中做统一的前缀管理
* 注:
* 1,目前使用apcu4.0,5.0需php7支持(apcu_entry)
* 2,所有实现的方法在原apcu函数基础上添加了prefix参数,并增加appid做为统一的前缀,具体请查看php手册
* 3,因apcu_add及apcu_store等方法批量及单个操作参数完全不同,此处将其分离成两个函数
*
* @date 2021-03-29
*/
class ApcuUtil
{
const PREFIX_NONE = '';
const EXPIRE_NEVER = 0;
const EXPIRE_AFTER_FIVE_SECONDS = 5;
const EXPIRE_AFTER_ONE_MINUTE = 60;
const EXPIRE_AFTER_FIVE_MINUTES = 300;
const EXPIRE_AFTER_HALF_HOUR = 1800;
const EXPIRE_AFTER_ONE_HOUR = 3600;
const EXPIRE_AFTER_SIX_HOURS = 21600;
const EXPIRE_AFTER_ONE_DAY = 86400;
const EXPIRE_AFTER_THREE_DAYS = 259200;
const EXPIRE_AFTER_FIFTEEN_DAYS = 1296000;
const EXPIRE_AFTER_ONE_MONTH = 2592000;
/**
* 为指定key添加前缀
*
* @param [str] $prefix
* @param [mixed] $key
*/
public static function add_prefix($prefix, $key)
{
$appid = Application::app()->getConfig()->get("appid");
$prefix = $appid . ':' . $prefix;
if (is_array($key)) {
foreach ($key as $key_key => $key_value) {
$key[$key_key] = $prefix . $key_value;
}
} else {
$key = $prefix . $key;
}
return $key;
}
/**
* 移除apcu批量获取结果中key的前缀,当单个key时原样返回
*
* @param [str] $prefix
* @param [mixed] $key
* @param [mixed] $apc_result
* @return [mixed]
*/
public static function remove_result_prefix($prefix, $key, $apc_result)
{
$appid = Application::app()->getConfig()->get("appid");
$prefix = $appid . ':' . $prefix;
if (is_array($key)) {
$result = array();
if (! is_array($apc_result)) {
return $apc_result;
}
foreach ($apc_result as $apc_key => $apc_value) {
$result[substr($apc_key, strlen($prefix))] = $apc_value;
}
return $result;
}
return $apc_result;
}
/**
* @link http://php.net/manual/zh/function.apcu-fetch.php
*
* @return mixed The stored variable or array of variables on success; false on failure
*/
public static function apcu_fetch($prefix, $key, &$success = true)
{
$perfix_key = self::add_prefix($prefix, $key);
$apc_result = apcu_fetch($perfix_key, $success);
return self::remove_result_prefix($prefix, $key, $apc_result);
}
/**
* @link http://php.net/manual/zh/function.apcu-store.php
*/
public static function apcu_store_one($prefix, $key, $value, $ttl = 0)
{
$perfix_key = self::add_prefix($prefix, $key);
return apcu_store($perfix_key, $value, $ttl);
}
/**
* @link http://php.net/manual/zh/function.apcu-store.php
*/
public static function apcu_store_multi($prefix, $values, $unused = NULL, $ttl = 0)
{
if (! empty($values)) {
$apc_to_set = array();
foreach ($values as $key => $value) {
$perfix_key = self::add_prefix($prefix, $key);
$apc_to_set[$perfix_key] = $value;
}
return apcu_store($apc_to_set, $unused, $ttl);
}
return false;
}
/**
* @link http://php.net/manual/zh/function.apcu-add.php
*/
public static function apcu_add_one($prefix, $key, $value, $ttl = 0)
{
$perfix_key = self::add_prefix($prefix, $key);
return apcu_add($perfix_key, $value, $ttl);
}
/**
* @link http://php.net/manual/zh/function.apcu-add.php
*/
public static function apcu_add_multi($prefix, $values, $unused = NULL, $ttl = 0)
{
if (! empty($values)) {
$apc_to_add = array();
foreach ($values as $key => $value) {
$perfix_key = self::add_prefix($prefix, $key);
$apc_to_add[$perfix_key] = $value;
}
return apcu_add($apc_to_add, $unused, $ttl);
}
return false;
}
/**
* @link http://php.net/manual/zh/function.apcu-delete.php
*/
public static function apcu_delete($prefix, $key)
{
$perfix_key = self::add_prefix($prefix, $key);
return apcu_delete($perfix_key);
}
/**
* @link http://php.net/manual/zh/function.apcu-exists.php
*/
public static function apcu_exists($prefix, $key)
{
$perfix_key = self::add_prefix($prefix, $key);
if (is_array($key)) {
$apc_result = apcu_exists($perfix_key);
$result = array();
if (empty($apc_result)) {
return $result;
}
return self::remove_result_prefix($prefix, $key, $apc_result);
} else {
return apcu_exists($perfix_key);
}
}
/**
* @link http://php.net/manual/zh/function.apcu-inc.php
*/
public static function apcu_inc($prefix, $key, $step = 1, &$success = true, $ttl = 0)
{
$perfix_key = self::add_prefix($prefix, $key);
return apcu_inc($perfix_key, $step, $success, $ttl);
}
/**
* @link http://php.net/manual/zh/function.apcu-dec.php
*/
public static function apcu_dec($prefix, $key, $step = 1, &$success = true, $ttl = 0)
{
$perfix_key = self::add_prefix($prefix, $key);
return apcu_dec($perfix_key, $step, $success, $ttl);
}
public static function retry_apcu_store_one($prefix, $key, $value, $ttl = 0)
{
$retry = 0;
do {
$res = self::apcu_store_one($prefix, $key, $value, $ttl);
} while (!$res && ++$retry < 3);
return $res;
}
/**
* @param $method mixed(array | object_method) 获取信息的方法
*/
public static function period_get($key, $prefix, $ttl, $lock_prefix, $lock_time, $method)
{
$res = self::apcu_fetch($prefix, $key);
if ($res !== false) {
if (! isset($res['data']) || ! isset($res['expire_at'])) {
FileLog::waring('get bad value from apcu', $key . ' ' . $prefix);
return false;
}
if ($_SERVER['REQUEST_TIME'] >= $res['expire_at']) {
// update
if (self::apcu_add_one($lock_prefix, $key, 1, $lock_time)) {
$data = call_user_func($method, $key);
if ($data !== false) {
$value = array(
'data' => $data,
'expire_at' => $_SERVER['REQUEST_TIME'] + ceil($ttl / 2)
);
self::retry_apcu_store_one($prefix, $key, $value, $ttl);
return $data;
}
}
}
return $res['data'];
} else {
// set
if (self::apcu_add_one($lock_prefix, $key, 1, $lock_time)) {
$data = call_user_func($method, $key);
if ($data !== false) {
$value = array(
'data' => $data,
'expire_at' => $_SERVER['REQUEST_TIME'] + ceil($ttl / 2)
);
self::retry_apcu_store_one($prefix, $key, $value, $ttl);
return $data;
}
}
return false;
}
}
}
<?php
namespace Api\PhpUtils\Cache;
use Api\PhpUtils\Redis\RedisUtil;
use Api\PhpUtils\Log\FileLog;
/**
* CacheUtil 工具类
*
* @date 2021-03-30
*/
class CacheUtil
{
const PREFIX_NONE = '';
const EXPIRE_NEVER = 0;
const EXPIRE_AFTER_FIVE_SECONDS = 5;
const EXPIRE_AFTER_ONE_MINUTE = 60;
const EXPIRE_AFTER_FIVE_MINUTES = 300;
const EXPIRE_AFTER_HALF_HOUR = 1800;
const EXPIRE_AFTER_ONE_HOUR = 3600;
const EXPIRE_AFTER_SIX_HOURS = 21600;
const EXPIRE_AFTER_ONE_DAY = 86400;
const EXPIRE_AFTER_THREE_DAYS = 259200;
const EXPIRE_AFTER_FIFTEEN_DAYS = 1296000;
const EXPIRE_AFTER_ONE_MONTH = 2592000;
private static $instances = [];
private $handler = null;
private $expire = 86400; //@todo 讨论一下,如果不设置有效时间,默认1天,不允许设置长期有效
/**
* 获取实例
*
* @param string $serverName 服务名
* @param array $options 其他参数数组,优先级高于yaconf中的相同配置
* @param string $options['serverRegion'] 服务地区
* @param bool $options['master'] 哨兵模式,默认使用主库,使用从库值为false
* @param string $options['serializer'] 序列化选项,包括 none|php|igbinary, msgpack自己实现msgpack_pack|msgpack_unpack, incr等方法只能使用none,set等方法使用none不能存Array或Object
* @param boolean $master
* @return mixed
*/
public static function getInstance($serverName = '', array $options = [])
{
if (empty($serverName)) {
return null;
}
if (isset(self::$instances[$serverName])) {
return self::$instances[$serverName];
}
return self::$instances[$serverName] = new Self($serverName, $options);
}
private function __construct($serverName = '', array $options = [])
{
if (empty($this->handler)){
//获取连接句柄
$this->handler = RedisUtil::getInstance($serverName, $options);
}
}
public function __call($method, array $args)
{
if (!$this->handler) {
return false;
}
return call_user_func_array([$this->handler, $method], $args);
}
/**
* 获取原始数据
* @param $key
* @return mixed
*/
public function getUnChange($key, $prefix = ''){
return $this->handler->get($this->getCacheKey($prefix, $key));
}
/**
* 保存原始数据
* @param $key
* @param $value
* @param $expire
* @return mixed
*/
public function setUnChange($key , $value , $expire = null, $prefix = ''){
if (is_null($expire)) {
// $expire = $this->expire;
return false;
}
$key = $this->getCacheKey($prefix, $key);
if (is_int($expire) && $expire) {
$result = $this->handler->setex($key, $expire, $value);
} else {
$result = $this->handler->set($key, $value);
}
return $result;
}
/**
* 读取缓存
* @access public
* @param string $key 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($key, $prefix = '', $default = false)
{
$value = $this->handler->get($this->getCacheKey($prefix, $key));
if (is_null($value) || false === $value) {
return $default;
}
$jsonData = json_decode($value, true);
// 检测是否为JSON数据 true 返回JSON解析数组, false返回源数据
return (null === $jsonData) ? $value : $jsonData;
}
/**
* 读取多个缓存
* @access public
* @param array $keys 缓存变量名数级
* @param mixed $default 默认值
* @return mixed
*/
public function gets($keys, $prefix = '', $default = false)
{
if(empty($keys) || !is_array($keys)) {
return $default;
}
$keys = array_values($keys);
$values = $this->handler->mGet($this->getCacheKey($prefix, $keys));
if (is_null($values) || false === $values) {
return $default;
}
$result = [];
foreach((array)$values as $i => $value) {
if($value !== false) {
$jsonData = json_decode($value, true);
$result[$keys[$i]] = (null === $jsonData) ? $value : $jsonData;
}
}
return $result;
}
/**
* 写入缓存
* @access public
* @param string $key 缓存变量名
* @param mixed $value 存储数据
* @param integer $expire 有效时间(秒)
* @return boolean
*/
public function set($key, $value, $expire = null, $prefix = '')
{
if (is_null($expire) || empty($expire)) {
// $expire = $this->expire;
return false;
}
$key = $this->getCacheKey($prefix, $key);
//对数组/对象数据进行缓存处理,保证数据完整性
$value = (is_object($value) || is_array($value)) ? json_encode($value) : $value;
if (is_int($expire) && $expire) {
$result = $this->handler->setex($key, $expire, $value);
} else {
$result = $this->handler->set($key, $value);
}
return $result;
}
/**
* 写入多个缓存
* @access public
* @param array $items 缓存数组
* @param integer $expire 有效时间(秒)
* @return boolean
*/
public function sets($items, $expire = null, $prefix = '')
{
if (is_null($expire) || empty($expire)) {
return false;
}
$this->handler->multi();
foreach($items as $key => $value) {
$key = $this->getCacheKey($prefix, $key);
$value = (is_object($value) || is_array($value)) ? json_encode($value) : $value;
$this->handler->setex($key, $expire, $value);
}
return $this->handler->exec();
}
/**
* 新增缓存,只有不存在时才写入成功
* @access public
* @param string $key 缓存变量名
* @param mixed $value 存储数据
* @param integer $expire 有效时间(秒)
* @return boolean
*/
public function add($key, $value, $expire = null, $prefix = '')
{
if (is_null($expire) || empty($expire)) {
// $expire = $this->expire;
}
$key = $this->getCacheKey($prefix, $key);
//对数组/对象数据进行缓存处理,保证数据完整性
$value = (is_object($value) || is_array($value)) ? json_encode($value) : $value;
if(is_int($expire) && $expire) {
$result = $this->handler->setenx($key, $expire, $value);
} else {
$result = $this->handler->setnx($key, $value);
}
return $result;
}
/**
* 自增缓存(针对数值缓存,且不自动序列化)
* @access public
* @param string $key 缓存变量名
* @return false|int
*/
public function incr($key, $prefix = ''){
return $this->handler->incr($this->getCacheKey($prefix, $key));
}
/**
* 自减缓存(针对数值缓存,且不自动序列化)
* @access public
* @param string $key 缓存变量名
* @return false|int
*/
public function decr($key, $prefix = ''){
return $this->handler->decr($this->getCacheKey($prefix, $key));
}
/**
* 自增缓存(针对数值缓存,且不自动序列化)
* @access public
* @param string $key 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function incrby($key, $step = 1, $prefix = '')
{
$key = $this->getCacheKey($prefix, $key);
return $this->handler->incrby($key, $step);
}
/**
* 自减缓存(针对数值缓存,且不自动序列化)
* @access public
* @param string $key 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function decrby($key, $step = 1, $prefix = '')
{
$key = $this->getCacheKey($prefix, $key);
return $this->handler->decrby($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $key 缓存变量名
* @return boolean
*/
public function del($key, $prefix = '')
{
return $this->handler->del($this->getCacheKey($prefix, $key));
}
/**
* 判断缓存
* @access public
* @param string $key 缓存变量名
* @return bool
*/
public function has($key, $prefix = '')
{
return $this->handler->get($this->getCacheKey($prefix, $key)) ? true : false;
}
/**
* 设置有效期
*
* @param string $key
* @param integer $expire
* @param string $prefix
* @return bool
*/
public function expire($key , $expire = 3600, $prefix = ''){
$cacheKey = $this->getCacheKey($prefix, $key);
return $this->handler->expire($cacheKey, $expire);
}
/**
* 返回句柄对象,可执行其它高级方法
*
* @access public
* @return object
*/
public function handler()
{
return $this->handler;
}
/**
* 获取实际的缓存标识
* @access public
* @param string|array $key 缓存名
* @return mixed
*/
public function getCacheKey($prefix, $key)
{
if ($prefix === '') {
return $key;
}
if (is_array($key)) {
$keyArr = [];
foreach ($key as $val) {
$keyArr[] = $prefix . $val;
}
return $keyArr;
}
return $prefix . $key;
}
}
<?php
namespace Api\PhpUtils\Common;
/**
* Class BaseConvertUtil 10进制数和62进制(包含)以内进制数的互转
* 对比PHP自带base_convert函数的测试, 对10000个随机数进行进制转换(10进制到36进制, 36进制到10进制)的耗时(单位s):
* decToOther(10 => 36): 3.4464011192322
* base_convert(10 => 36): 0.3307638168335
* otherToDec(36 => 10): 8.2434189319611
* base_convert(36 => 10): 0.3491358757019
* 从测试结果来看, 36进制内的转换请尽量使用PHP自带base_convert函数
*/
class BaseConvert
{
private static $dict = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* 十进制数转换成其它进制: 可以转换成2-62任何进制
*
* @param int $num
* @param int $to
* @return string 转换成功返回转换后的字符串, convertStatus转换失败返回false转换失败返回false
*/
public static function decToOther($num, $to = 62, &$convertStatus = true)
{
if ($to == 10)
{
return strval($num);
}
if ($to > 62 || $to < 2)
{
$convertStatus = false;
return '';
}
$ret = '';
do
{
$mod = bcmod($num . '', $to . '');
$ret = self::$dict[intval($mod)] . $ret;
$num = bcdiv($num, $to);
} while ($num > 0);
return $ret;
}
/**
* 其它进制数转换成十进制数: 适用2-62的任何进制
*
* @param string $num
* @param int $from
* @return int|bool 转换成功返回转换后的数值, convertStatus转换失败返回false
*/
public static function otherToDec($num, $from = 62, &$convertStatus = true)
{
if ($from == 10)
{
return $num;
}
if ($from > 62 || $from < 2)
{
$convertStatus = false;
return 0;
}
$num = strval($num);
$len = strlen($num);
$dec = 0;
for ($i = 0; $i < $len; $i++)
{
$pos = strpos(self::$dict, $num[$i]);
if ($pos >= $from)
{
$convertStatus = false;// 如果出现非法字符, 比如16进制中出现w、x、y、z等, 直接返回false
return 0;
}
$dec = bcadd(bcmul(bcpow($from, $len - $i - 1), $pos), $dec);
}
return $dec;
}
}
<?php
namespace Api\PhpUtils\Common;
use Api\PhpUtils\Common\BaseConvert;
class Docid
{
/**
* 校验docid,10位以上docid为自媒体系统post_id fake的docid,U开头
*
* @param string $str
* @return bool
*/
public static function validDocid($str)
{
$strlen = strlen($str);
if ($strlen === 37 && substr($str, 0, 5) === 'news_') return true;
if (0 === strpos($str, 'C-'))//商品的 global id 也是有效的 docid
{
return self::validGlobalid($str);
}
$pos = strpos($str, '_');
if ($pos !== false)
{
$str = substr($str, $pos + 1);
$strlen = $strlen - ($pos + 1);
}
$chklen = 2;
$code = substr($str, -$chklen);
$str = substr($str, 0, $strlen-$chklen);
$strlen -= $chklen;
$base = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($strlen % 2 == 1)
{
$str = '0' . $str;
$strlen += 1;
}
$sum = 0;
$len = $strlen/2;
for ($i=0; $i<$len; $i++)
{
$sum *= 137;
$sum += ord($str[$i]) ^ ord($str[$strlen-$i-1]);
}
$ret = '';
for ($i=$chklen; $i>0; $i--)
{
$idx = $sum % 62;
$sum = ($sum-$idx) / 62;
$ret = $base[$idx] . $ret;
}
return $ret === $code;
}
/**
* docid生成器, [不能用于全局], 仅为了兼容某些文章在未过审时, 没有全局docid, 而客户端又需要文章有docid的情况.
* 用该生成器生成的docid能通过上述validDocid方法的检测
*
* @param int $id
* @param string 生成的docid前缀
* @return string
*/
public static function generateDocid($id, $prefix)
{
$tmp = BaseConvert::decToOther($id);
$len = strlen($tmp);
$len = $len<6?6:$len;
if ($len % 2 == 1)
{
$tmp = '0' . $tmp;
$len += 1;
}
$tmp = str_pad(strval($tmp), $len<6?6:$len, '0', STR_PAD_LEFT);
$sum = 0;
for ($i = 0; $i < $len/2; $i++)
{
$sum *= 137;
$sum += ord($tmp[$i]) ^ ord($tmp[$len - $i - 1]);
}
$code = '';
$base = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
for ($i = 2; $i > 0; $i--)
{
$idx = $sum % 62;
$sum = ($sum - $idx) / 62;
$code = $base[$idx] . $code;
}
return $prefix . $tmp . $code;
}
private static function generateGlobalidCheckString($global_id)
{
$md5_string = md5($global_id);
$crc_32_string = strval(crc32($md5_string));
$pos_array = array("0" => intval(substr($crc_32_string, 1, 1)), "1" => intval(substr($crc_32_string, 3, 1)), "2" => intval(substr($crc_32_string, 5, 1)));
$check_string = substr($md5_string, $pos_array[0], 1) . substr($md5_string, $pos_array[1], 1) . substr($md5_string, $pos_array[2], 1);
return $check_string;
}
private static function validGlobalid($global_id)
{
$str_len = strlen($global_id);
$true_globalid = substr($global_id, 0, ($str_len - 3));
$check_string = self::generateGlobalidCheckString($true_globalid);
if ($check_string !== substr($global_id, -3))
{
return false;
}
return true;
}
}
<?php
namespace Api\PhpUtils\Common;
/**
* geo工具类
*/
class Geo
{
// 地球半径,单位米
const EARTH_RADIUS = 6378137;
/**
* 计算经纬度距离(单位米)
*
* @param float $lat1
* @param float $lng1
* @param float $lat2
* @param float $lng2
* @param bool $km 如果为true则返回单位为km
* @return int
*/
public static function geoDistance($lat1, $lng1, $lat2, $lng2, $km = false)
{
// 转换为弧度
$rad_lat1 = deg2rad($lat1);
$rad_lat2 = deg2rad($lat2);
$rad_lng1 = deg2rad($lng1);
$rad_lng2 = deg2rad($lng2);
$diff_lat = $rad_lat1 - $rad_lat2;
$diff_lng = $rad_lng1 - $rad_lng2;
$distance = 2 * asin(sqrt(pow(sin($diff_lat / 2), 2) + cos($rad_lat1) * cos($rad_lat2) * pow(sin($diff_lng / 2), 2))) * self::EARTH_RADIUS;
if ($km)
{
return round($distance / 1000, 1);
}
return round($distance, 2);
}
}
This diff is collapsed.
Common工具库
\ No newline at end of file
This diff is collapsed.
<?php
namespace Api\PhpUtils\Common;
use Api\PhpUtils\Http\Request;
class TimeOut
{
private $timeout_95;
private $timeout_99;
private $retry_95;
private $retry_99;
/**
* TimeOut constructor.
* @param int $timeout_95 毫秒
* @param int $timeout_99 毫秒
* @param int $retry_95 重试次数
* @param int $retry_99 重试次数
*/
public function __construct($timeout_95 = 300, $timeout_99 = 800, $retry_95 = 1, $retry_99 = 2)
{
$this->timeout_95 = $timeout_95;
$this->timeout_99 = $timeout_99;
$this->retry_95 = $retry_95;
$this->retry_99 = $retry_99;
}
/**
* GET请求
* @param $url
* @param array $params
* @param array $headers
* @return array|bool|mixed
*/
public function runGet($url, $params = [], $headers = []){
$res = (new Request())->get($url, $params, $this->timeout_95, $headers, $this->retry_95);
if($res["code"] != 999999){
return $res['response'];
}
$res = (new Request())->get($url, $params, $this->timeout_99, $headers, $this->retry_99);
if($res["code"] != 999999){
return $res['response'];
}
return false;
}
public function runPost($url, $params = [], $content_type = null, $headers = []){
$res = (new Request())->post($url, $params, $this->timeout_95, $content_type, $headers, $this->retry_95);
if($res["code"] != 999999){
return $res['response'];
}
$res = (new Request())->post($url, $params, $this->timeout_99, $content_type, $headers, $this->retry_99);
if($res["code"] != 999999){
return $res['response'];
}
return false;
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Elastic;
use Api\PhpUtils\Elastic\Manager\DocumentManager;
use Api\PhpUtils\Elastic\Manager\IndexManager;
use Elasticsearch\ClientBuilder;
final class ElasticUtil
{
use IndexManager, DocumentManager;
/**
* @var ElasticUtil
*/
private static $instance;
/**
* ElasticUtil constructor.
*/
public function __construct()
{
$hosts = ['http://localhost:9200']; //todo 读取yaconf
$this->client = ClientBuilder::create()->setHosts($hosts)->build();
}
/**
* @return ElasticUtil
*/
public static function getInstance(): ElasticUtil
{
if (empty(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Elastic\Manager;
use Elasticsearch\Client;
trait DocumentManager
{
/**
* @var Client
*/
protected $client;
/**
* 索引文档
* @param $index
* @param $data
* @param string $id
* @return array|callable
*/
public function indexDoc($index, $data, string $id = '')
{
$params = [
'index' => $index,
'body' => $data
];
if (!empty($id)) {
$params['id'] = $id;
}
return $this->client->index($params);
}
/**
* @param $data
* @return array|callable
*/
public function bulkIndexDoc($data)
{
$params = ['body' => $data];
return $this->client->bulk($params);
}
/**
* 删除文档
* @param $index
* @param $id
* @return array|callable
*/
public function deleteDoc($index, $id)
{
$params = [
'index' => $index,
'id' => $id
];
return $this->client->delete($params);
}
/**
* 获取文档
* @param $index
* @param $id
* @return array|callable
*/
public function getDoc($index, $id)
{
$params = [
'index' => $index,
'id' => $id
];
return $this->client->get($params);
}
/**
* 更新文档
* @param $index
* @param $id
* @param $data
* @return array|callable
*/
public function updateDoc($index, $id, $data)
{
$params = [
'index' => $index,
'id' => $id,
'body' => $data
];
return $this->client->update($params);
}
/**
* 搜索操作
* @param $index
* @param $data
* @return array|callable
*/
public function searchDoc($index, $data)
{
$params = [
'index' => $index,
'body' => $data
];
return $this->client->search($params);
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Elastic\Manager;
use Elasticsearch\Client;
trait IndexManager
{
/**
* @var Client
*/
protected $client;
/**
* 创建一个索引
* @param $index
* @param $data
* @return array
*/
public function createIndex($index, $data): array
{
$params = [
'index' => $index,
'body' => $data
];
return $this->client->indices()->create($params);
}
/**
* 删除一个索引
* @param $index
* @return array
*/
public function deleteIndex($index): array
{
$params = [
'index' => $index,
];
return $this->client->indices()->delete($params);
}
/**
* 获取一个或多个索引的当前配置参数
* @param array $indices
* @return array
*/
public function getSettings(array $indices): array
{
$params = [
'index' => $indices
];
return $this->client->indices()->getSettings($params);
}
/**
* 更改索引的配置参数
* @param $index
* @param $data
* @return array
*/
public function putSettings($index, $data): array
{
$params = [
'index' => $index,
'body' => $data
];
return $this->client->indices()->putSettings($params);
}
/**
* 获取索引和类型的映射细节
* @param $index
* @return array
*/
public function getMapping($index): array
{
$params = [
'index' => $index,
];
return $this->client->indices()->getMapping($params);
}
/**
* 更改或增加一个索引的映射
* @param $index
* @param $data
* @return array
*/
public function putMapping($index, $data): array
{
$params = [
'index' => $index,
'body' => $data
];
return $this->client->indices()->putMapping($params);
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Http;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RetryMiddleware;
use Api\PhpUtils\Log\FileLog;
class Base
{
/**
* 最大重试次数
*/
private $retry = 0;
/**
* 配置(参照https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
*/
private $config = [
'allow_redirects' => true, //允许302跳转
'verify' => false, //禁用证书验证
'connect_timeout' => 3, //连接超时毫秒数
'timeout' => 3, //请求超时毫秒数
];
private $log = [
'url' => '',
'params' => [],
'response' => ''
];
public function __construct()
{
}
public function initClient($custom_config = [], $url = [], $params = [])
{
$this->log['url'] = $url;
$this->log['params'] = $params;
//
if (isset($custom_config['retry']) && !empty($custom_config['retry'])) {
$this->retry = intval($custom_config['retry']);
}
//创建Handler
if (isset($custom_config['concurrent']) && !empty($custom_config['concurrent'])) {
$handlerStack = HandlerStack::create(new CurlMultiHandler());
} else {
$handlerStack = HandlerStack::create(new CurlHandler());
}
//创建重试中间件,指定决策者为 $this->retryDecider(),指定重试延迟为 $this->retryDelay()
$handlerStack->push(Middleware::retry($this->retryDecider(), $this->retryDelay()));
//指定handler
$this->config['handler'] = $handlerStack;
//使用自定义timeout覆盖默认timeout
if (is_array($custom_config) && !empty($custom_config)) {
$this->config = array_merge($this->config, $custom_config);
}
return new Client($this->config);
}
/**
* 确定重试逻辑:false表示不重试,否则重试
* @return
*/
protected function retryDecider()
{
return function (
$retries,
Request $request,
Response $response = null,
RequestException $exception = null
) {
// 超过最大重试次数,不再重试
if ($retries >= $this->retry) {
return false;
}
// 请求失败,继续重试
if ($exception instanceof ConnectException) {
$this->log['response'] = $exception->getMessage();
$this->addLog();
return true;
}
if (!empty($response)) {
// 如果请求有响应,但是状态码大于等于500,继续重试
if ($response->getStatusCode() >= 500) {
$this->log['response'] = 'getStatusCode = ' . $response->getStatusCode();
$this->addLog();
return true;
}
}
return false;
};
}
/**
* 返回一个匿名函数,该匿名函数拉开重试间隔(毫秒)
* @return
*/
protected function retryDelay()
{
return function ($numberOfRetries) {
return RetryMiddleware::exponentialDelay($numberOfRetries) * 100;
};
}
private function addLog(){
FileLog::info('retry', json_encode($this->log));
}
}
Http工具库
\ No newline at end of file
This diff is collapsed.
<?php
namespace Api\PhpUtils\Hystrix;
use Odesk\Phystrix\StateStorageInterface;
class ApcuStateStorage implements StateStorageInterface
{
const BUCKET_EXPIRE_SECONDS = 120;
const CACHE_PREFIX = 'phystrix_cb_';
const OPENED_NAME = 'opened';
const SINGLE_TEST_BLOCKED = 'single_test_blocked';
/**
* Constructor
*/
public function __construct()
{
if (!extension_loaded('apcu')) {
throw new Exception\ApcNotLoadedException('"apcu" PHP extension is required for Phystrix to work');
}
}
/**
* Prepends cache prefix and filters out invalid characters
*
* @param string $name
* @return string
*/
protected function prefix($name)
{
return self::CACHE_PREFIX . $name;
}
/**
* Returns counter value for the given bucket
*
* @param string $commandKey
* @param string $type
* @param integer $index
* @return integer
*/
public function getBucket($commandKey, $type, $index)
{
$bucketName = $this->prefix($commandKey . '_' . $type . '_' . $index);
return apcu_fetch($bucketName);
}
/**
* Increments counter value for the given bucket
*
* @param string $commandKey
* @param string $type
* @param integer $index
*/
public function incrementBucket($commandKey, $type, $index)
{
$bucketName = $this->prefix($commandKey . '_' . $type . '_' . $index);
if (!apcu_add($bucketName, 1, self::BUCKET_EXPIRE_SECONDS)) {
apcu_inc($bucketName);
}
}
/**
* If the given bucket is found, sets counter value to 0.
*
* @param string $commandKey Circuit breaker key
* @param integer $type
* @param integer $index
*/
public function resetBucket($commandKey, $type, $index)
{
$bucketName = $this->prefix($commandKey . '_' . $type . '_' . $index);
if (apcu_exists($bucketName)) {
apcu_store($bucketName, 0, self::BUCKET_EXPIRE_SECONDS);
}
}
/**
* Marks the given circuit as open
*
* @param string $commandKey Circuit key
* @param integer $sleepingWindowInMilliseconds In how much time we should allow a single test
*/
public function openCircuit($commandKey, $sleepingWindowInMilliseconds)
{
$openedKey = $this->prefix($commandKey . self::OPENED_NAME);
$singleTestFlagKey = $this->prefix($commandKey . self::SINGLE_TEST_BLOCKED);
apcu_store($openedKey, true);
// the single test blocked flag will expire automatically in $sleepingWindowInMilliseconds
// thus allowing us a single test. Notice, APC doesn't allow us to use
// expire time less than a second.
$sleepingWindowInSeconds = ceil($sleepingWindowInMilliseconds / 1000);
apcu_add($singleTestFlagKey, true, $sleepingWindowInSeconds);
}
/**
* Whether a single test is allowed
*
* @param string $commandKey Circuit breaker key
* @param integer $sleepingWindowInMilliseconds In how much time we should allow the next single test
* @return boolean
*/
public function allowSingleTest($commandKey, $sleepingWindowInMilliseconds)
{
$singleTestFlagKey = $this->prefix($commandKey . self::SINGLE_TEST_BLOCKED);
// using 'add' enforces thread safety.
$sleepingWindowInSeconds = ceil($sleepingWindowInMilliseconds / 1000);
// another APC limitation is that within the current request variables will never expire.
return (boolean) apcu_add($singleTestFlagKey, true, $sleepingWindowInSeconds);
}
/**
* Whether a circuit is open
*
* @param string $commandKey Circuit breaker key
* @return boolean
*/
public function isCircuitOpen($commandKey)
{
$openedKey = $this->prefix($commandKey . self::OPENED_NAME);
return (boolean) apcu_fetch($openedKey);
}
/**
* Marks the given circuit as closed
*
* @param string $commandKey Circuit key
*/
public function closeCircuit($commandKey)
{
$openedKey = $this->prefix($commandKey . self::OPENED_NAME);
apcu_store($openedKey, false);
}
}
<?php
namespace Api\PhpUtils\Hystrix;
use Odesk\Phystrix\AbstractCommand;
class DemoCommand extends AbstractCommand
{
private $b;
private $a;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
protected function run()
{
$arr = [0,1];
shuffle($arr);
$int = array_pop($arr);
if($int){
throw new \Exception();
}
return $this->a.$this->b;
}
protected function getFallback()
{
return "Fallback";
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Hystrix;
use Odesk\Phystrix\AbstractCommand;
use Zend\Config\Config;
use Odesk\Phystrix\ApcStateStorage;
use Odesk\Phystrix\CircuitBreakerFactory;
use Odesk\Phystrix\CommandMetricsFactory;
use Odesk\Phystrix\CommandFactory;
class Hystrix
{
private $phystrix;
public function __construct(){
$config = new Config($this->getConfig());
$stateStorage = new ApcuStateStorage();
$circuitBreakerFactory = new CircuitBreakerFactory($stateStorage);
$commandMetricsFactory = new CommandMetricsFactory($stateStorage);
$this->phystrix = new CommandFactory(
$config, new \Zend\Di\ServiceLocator(), $circuitBreakerFactory, $commandMetricsFactory,
new \Odesk\Phystrix\RequestCache(), new \Odesk\Phystrix\RequestLog()
);
}
public function run($class, ...$params){
$command = $this->phystrix->getCommand($class, ...$params);
return $command->execute();
}
private function getConfig(){
return [
'default' => [
'fallback' => array(
'enabled' => config("request","hystrix.fallback.enabled"),
),
'circuitBreaker' => array(
'enabled' => config("request","hystrix.circuitBreaker.enabled"),
// 失败率达到多少百分比后熔断
'errorThresholdPercentage' => config("request","hystrix.circuitBreaker.errorThresholdPercentage"),
// 是否强制开启熔断
'forceOpen' => config("request","hystrix.circuitBreaker.forceOpen"),
// 是否强制关闭熔断
'forceClosed' => config("request","hystrix.circuitBreaker.forceClosed"),
// 一个统计窗口内熔断触发的最小个数/10s
'requestVolumeThreshold' => config("request","hystrix.circuitBreaker.requestVolumeThreshold"),
// 熔断多少秒后去尝试请求
'sleepWindowInMilliseconds' => config("request","hystrix.circuitBreaker.sleepWindowInMilliseconds"),
),
'metrics' => array(
// 采样时间间隔
'healthSnapshotIntervalInMilliseconds' => config("request","hystrix.metrics.healthSnapshotIntervalInMilliseconds"),
// 设置统计滚动窗口的长度,以毫秒为单位。用于监控和熔断器。
'rollingStatisticalWindowInMilliseconds' => config("request","hystrix.metrics.rollingStatisticalWindowInMilliseconds"),
// 设置统计窗口的桶数量
'rollingStatisticalWindowBuckets' => config("request","hystrix.metrics.rollingStatisticalWindowBuckets"),
),
'requestCache' => array(
// 设置是否缓存请求
'enabled' => config("request","hystrix.requestCache.enabled"),
),
'requestLog' => array(
// 设置HystrixCommand执行和事件是否打印到HystrixRequestLog中
'enabled' => config("request","hystrix.requestLog.enabled"),
)
]
];
}
}
\ No newline at end of file
This diff is collapsed.
#Kafka工具库
###旧版本使用示例(KafkaProducer has been DEPRECATED)
```php
$service = KafkaProducer::getInstance();
$message= json_encode(array('test' => 'kafka produce', 'reason' => 'send message'), JSON_UNESCAPED_UNICODE);
var_dump($service);
$service->produce('10.120.25.19:9092,10.120.24.27:9092,10.120.25.6:9092', 'push_content_sync', $message);
```
###新版本使用示例
```php
public function kafkaAction()
{
$topicName = 'topicA';
$setConfDrMsgCb = function ($kafka, \RdKafka\Message $message) {
echo '打印消息:' . "\n";
var_export($message);
echo "\n";
if($message->err){
//@todo 生产失败的逻辑处理
} else{
//@todo 生产成功的逻辑处理
}
};
$setErrorCbCallback = function ($kafka, $err, $reason) {
echo sprintf("setErrorCb (error_ori:%s)(error: %s) (reason: %s)", $err, rd_kafka_err2str($err), $reason);
};
// $kafka = KafkaUtil::getInstance('test', ['context' => ['contextArr']]);
// $ret = $kafka->produce('eee');
$brokers = '127.0.0.1:9092';
$kafka = KafkaUtil::getInstance();
$ret = $kafka->setBrokers($brokers)->setTopicName('topicA')->produce('ccc');
var_dump($kafka);
exit;
//设置brokers-支持数组或者字符串
$kafka->setBrokers($brokers)
//设置topic
->setTopicName($topicName)
//setConfDrMsgCb--设置生产消息失败与成功的回调,默认初始化时会调用该事件,如果自定义后就会覆盖默认的
->setConfDrMsgCb($setConfDrMsgCb)
//setConfErrorCb--设置错误回调,如若不设置默认会走默认逻辑
->setConfErrorCb($setErrorCbCallback)
//支持连贯用法生产多条数据并且是不同的topic
->run('message111', $topicName);
}
```
<?php
namespace Api\PhpUtils\Ksy;
use Api\PhpUtils\Http\Request;
/**
* 金山云ks3相关
* 仅实现了签名及删除单个方法,其他未实现
* ks3直接访问地址: https://ks3-cn-beijing.ksyun.com/article-yidian/article/0JRvMeHk
*/
class Ks3Util
{
const KS3_BASE_URL = 'ks3-cn-beijing.ksyun.com';
const FALLBACK_BUIDLER_BUCKET = 'article-yidian';
const KS_DATE_SCHEME = 'D, d M Y H:i:s \G\M\T';
const KSYUN_SK = "OMw7UAOkrLVsRevuP44ctAlfzriDzUm9+JvOFoYuiSoLgaLJWi+kjUa+4G3EWXFakQ==";
const KSYUN_AK = "AKLTnATdoXUnR72IDSGgN2WSOQ";
const YIDIAN_PROXY = true;
//删除fallback builder 对于ks3节点的文章
public static function delete_doc($docid, $timeout = 1000)
{
return self::delete(self::FALLBACK_BUIDLER_BUCKET, 'article/'.$docid, $timeout);
}
public static function put_object($data,$bucket,$object_key, $timeout = 1000)
{
$url = self::get_url($bucket).'/'.$object_key;
$canonicalized_resource = self::get_canonicalized_resource($bucket, $object_key);
$headers = array(
'Host' => self::get_url($bucket),
'Date' => gmdate(self::KS_DATE_SCHEME),
'Content-Type' => 'multipart/form-data',
'Referer' => $url,
'x-kss-acl' => 'public-read',
//'Expect' => '100-continue',
);
$headers['Authorization'] = self::sign('PUT', $canonicalized_resource, $headers);
foreach ($headers as $key => $value)
{
$header[] = $key.':'.$value;
}
$request = new Request();
$str = $request->uploadPut($url, $data, $timeout, 1, $header, self::YIDIAN_PROXY);
return $str;
}
//查询文章在ks3是否存在,返回金山云返回的head http状态吗
public static function exist($bucket, $object_key, $timeout = 1000)
{
$url = self::get_url($bucket).'/'.$object_key;
$canonicalized_resource = self::get_canonicalized_resource($bucket, $object_key);
$headers = array(
'Host' => self::get_url($bucket),
'Date' => gmdate(self::KS_DATE_SCHEME),
'Content-Type' => 'application/xml',
);
$headers['Authorization'] = self::sign('HEAD', $canonicalized_resource, $headers);
foreach ($headers as $key => $value)
{
$header[] = $key.':'.$value;
}
$request = new Request();
$str = $request->head($url, $timeout, 1, $header, self::YIDIAN_PROXY);
return $str;
}
//删除ks3 节点指定bucket中的object
private static function delete($bucket, $object_key, $timeout)
{
$url = self::get_url($bucket).'/'.$object_key;
$headers = array(
'Host' => self::get_url($bucket),
'Accept' => '*/*',
'Accept-Encoding' => 'deflate, gzip',
'Referer' => $url,
'Content-Type' => 'application/xml',
'Date' => gmdate(self::KS_DATE_SCHEME),
'Content-Length' => 0,
);
$canonicalized_resource = self::get_canonicalized_resource($bucket, $object_key);
$headers['Authorization'] = self::sign('DELETE', $canonicalized_resource, $headers);
foreach ($headers as $key => $value)
{
$header[] = $key.':'.$value;
}
$request = new Request();
$str = $request->delete($url, $timeout, 1, $header, self::YIDIAN_PROXY);
return $str;
}
private static function get_url($bucket)
{
return $bucket.'.'.self::KS3_BASE_URL;
}
private static function sign($method,$canonicalized_resource, $headers=array(), $query = array())
{
$canonicalized_kss_headers = self::format_headers($headers);
$tosign = $method . "\n"
. '' . "\n"
. $headers['Content-Type'] . "\n"
. $headers['Date'] . "\n"
. $canonicalized_kss_headers
. $canonicalized_resource;
$signature = base64_encode(hash_hmac('sha1', $tosign, self::KSYUN_SK, true));
$signature = 'KSS ' . self::KSYUN_AK . ':' . $signature;
return $signature;
}
//生成签名所需CanonicalizedKssHeaders,详见https://ks3.ksyun.com/doc/api/index.html
private static function format_headers(array $headers)
{
if( empty($headers) )
{
return '';
}
foreach ($headers as $key => $header) {
if( substr($key,0,6) == 'x-kss-' )
{
$headers_to_sign[strtolower($key)] = $header;
}
}
if( empty($headers_to_sign) )
{
return '';
}
ksort($headers_to_sign);
$ret = '';
foreach ($headers_to_sign as $key => $value)
{
$ret = $key.':'.$value."\n";
}
return $ret;
}
//
private static function get_canonicalized_resource($bucket, $object_key)
{
return '/'.$bucket.'/'.$object_key;
}
}
Lock工具库
\ No newline at end of file
<?php
namespace Api\PhpUtils\Log;
class DaemoLog
{
public static function info($task_name, $msg)
{
$msg = $task_name.' '.posix_getpid().' [INFO]: '.$msg;
error_log($msg);
}
public static function error($task_name, $msg)
{
$msg = $task_name.' '.posix_getpid().' [ERROR]: '.$msg;
error_log($msg);
}
}
\ No newline at end of file
<?php
namespace Api\PhpUtils\Log;
use Api\PhpUtils\Message\Email;
class FileLog
{
/**
* 用于记录info级别的错误
* 会记录请求上下文,不会发生报警邮件
* @param string $signature 本条日志签名
* @param string $detail_info 日志详细信息
* @param bool $with_access_log
*/
public static function info($signature, $detail_info = '', $with_access_log = false)
{
$log = 'PHP User_info: [' . $signature . ']' . ' [detail info:] ' . $detail_info;
if ($with_access_log) {
$log .= ' [request info:] ' . self::accessLog();
}
self::writeInfoLog($log);
}
/**
* 用于记录waring级别的错误,在日志分析时此级别日志会使用signature进行聚合
* 会记录请求上下文,不会发生报警邮件
* @param string $signature 本条日志签名
* @param string $detail_info 日志详细信息
* @param \Exception $exception 异常信息
*/
public static function waring($signature, $detail_info = '', $exception = null)
{
$log = 'PHP User_warning: [' . $signature . ']' . ' [detail info:] ' . $detail_info . ' [request info:] ' . self::accessLog();
if ($exception != '') {
$exception_info = 'In ' . $exception->getFile() . ' on line ' . $exception->getLine() . ' ' . $exception->getCode() . ': ' . $exception->getMessage();
$log .= ' [exception info: ]' . $exception_info;
}
self::writeWarningLog($log);
}
/**
* 用于记录error级别的错误,在日志分析时此级别日志会使用signatrue进行聚合
* 会记录请求上下文,如果设置了mail_to参数会发生报警邮件
* @param string $signature 本条日志签名
* @param string $detail_info 日志详细信息
* @param \Exception $exception 异常信息
* @param array|string $mail_to 邮件接受者
*/
public static function error($signature, $detail_info = '', $exception = null, $mail_to = '')
{
$log = 'PHP User_error: [' . $signature . ']' . ' [detail info:] ' . $detail_info . ' [request info:] ' . self::accessLog();
if ($exception != '') {
$exception_info = ' [exception info: ] In ' . $exception->getFile() . ' on line ' . $exception->getLine() . ' ' . $exception->getCode() . ': ' . $exception->getMessage();
} else {
$exception_info = '';
}
$log .= ' [exception info: ]' . $exception_info;
error_log($log);
if (!empty($mail_to)) {
$subject = 'App api #' . $signature . '# ' . $_SERVER['SERVER_NAME'] . ' (' . $_SERVER['SERVER_ADDR'] . ') Alert Message';
$body = 'Error: ' . $signature . "\n\n";
$body .= 'Detail info: ' . $detail_info . "\n\n";
$body .= 'Exception info: ' . $exception_info . "\n\n";
$body .= 'Request info: ' . self::accessLog() . "\n\n";
$body .= 'Machine: ' . gethostname();
if (!is_array($mail_to)) {
$mail_to = [$mail_to];
}
foreach ($mail_to as $mail) {
$key = md5(sprintf("%s,%s", $mail, md5($signature)));
if (self::shouldSendEmail($key) === true) {
Email::sendMail('noreply@yidian-inc.com', $mail, $subject, $body);
}
}
}
}
private static function accessLog()
{
$url = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'];
$query = http_build_query($_GET, '', '&');
$post_data = json_encode($_POST);
$agent = $_SERVER['HTTP_USER_AGENT'] ?? '-';
$cookie = $_COOKIE['JSESSIONID'] ?? '-';
return sprintf('%s?%s "%s" "%s" "%s" "%s"', $url, $query, $post_data, $agent, self::ip(), $cookie);
}
private static function writeInfoLog($log)
{
$log = '[' . date('d-M-Y H:i:s', time()) . ' ' . date_default_timezone_get() . '] ' . $log;
$log_file = fopen(dirname(ini_get('error_log')) . '/php-infos.log', 'a+');
fwrite($log_file, $log . "\n");
fclose($log_file);
}
private static function writeWarningLog($log)
{
$log = '[' . date('d-M-Y H:i:s', time()) . ' ' . date_default_timezone_get() . '] ' . $log;
$log_file = fopen(dirname(ini_get('error_log')) . '/php-warnings.log', 'a+');
fwrite($log_file, $log . "\n");
fclose($log_file);
}
private static function ip()
{
if (isset($_SERVER['IPV6_REMOTE_ADDR']) && !empty($_SERVER['IPV6_REMOTE_ADDR']) && trim($_SERVER['IPV6_REMOTE_ADDR']) !== '-') { // 如果没有ipv6地址, 运维会传一个占位符“-”
$ips = $_SERVER['IPV6_REMOTE_ADDR'];
} elseif (!empty($_SERVER['HTTP_CDN_SRC_IP'])) {
$ips = $_SERVER['HTTP_CDN_SRC_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ips = $_SERVER['HTTP_CLIENT_IP'];
} else {
$ips = $_SERVER['REMOTE_ADDR'];
}
$ip = explode(',', $ips);
return $ip[0];
}
private static function shouldSendEmail($key)
{
$result = true;
// $cache = new CommonCacheUtil(RedisUtil::CODIS_CLUSTER_ACTION);
// if ($cache->add($key, true, 60, CacheUtil::PREFIX_SEND_MAIL) === false) {
// $result = false;
// }
return $result;
}
}
Log工具库
\ No newline at end of file
<?php
namespace Api\PhpUtils\Message;
class Email
{
/**
* @param string $from 发件人
* @param string|array $to 收件人, 支持多个收件人
* @param string $subject 邮件主题
* @param string $body 邮件内容
* @param string $smtp_server
* @param int $smtp_port
* @param string $content_type body内容的格式和编码, 可支持html格式邮件 $content_type="Content-Type: text/html; charset=\"utf-8\"\r\n\r\n"
* @return bool
*/
public static function sendMail($from, $to, $subject, $body, $smtp_server = 'smtp.yidian.com', $smtp_port = 25, $content_type = "Content-Type: text/plain; charset=\"utf-8\"\r\n\r\n")
{
if (empty($to)) {
return false;
}
if (empty($from)) {
$from = 'noreply@yidian-inc.com';
}
try {
$email = explode('@', $from);
$domain = $email[1];
$sock = fsockopen($smtp_server, $smtp_port, $errno, $errstr, 1);
fwrite($sock, "HELO " . $domain . "\r\n");
fgets($sock);
fwrite($sock, "MAIL FROM:<" . $from . ">\r\n");
fgets($sock);
if (!is_array($to)) {
$to = [$to];
}
foreach ($to as $t) {
fwrite($sock, "RCPT TO:<" . $t . ">\r\n");
}
fgets($sock);
fwrite($sock, "DATA\r\n");
fgets($sock);
fwrite($sock, "Subject: " . $subject . "\r\n");
fwrite($sock, "From: <" . $from . ">\r\n");
foreach ($to as $t) {
fwrite($sock, "To: <" . $t . ">\r\n");
}
fwrite($sock, $content_type);
fwrite($sock, $body . "\r\n.\r\n");
fgets($sock);
fwrite($sock, "QUIT\r\n");
fgets($sock);
fclose($sock);
} catch (\Exception $e) {
return false;
}
}
}
Email工具库
\ No newline at end of file
<?php
/**
* Description of MonUtil.php
*
* @author caoyunmeng
* Date: 2021/2/2
* Time: 2:27 PM
*/
namespace Api\PhpUtils\Mon;
class MonUtil{
/**
* 向安装有ydbot服务的机器发送信息,进行监控打点
*
* @param string $send_msg 打点信息
* @param string $ip 服务器ip,默认为宿主机ip(172.17.0.1)
* @param string $port 端口,默认为15688
* @return string
*/
public static function base($send_msg, $ip = '172.17.0.1', $port = '15688'){
if(isset($_SERVER['YIDIAN_LOCAL_IP']) && !empty($_SERVER['YIDIAN_LOCAL_IP'])){
$ip = $_SERVER['YIDIAN_LOCAL_IP'];
}
$handle = stream_socket_client("udp://{$ip}:{$port}", $errno,$errstr, 1);
if($handle){
//在一个stream上设置超时
stream_set_timeout($handle,0,15000);
fwrite($handle, $send_msg."\n");
//从封装协议文件指针中取得报头/元数据,返回数组,索引timed_out为超时信息,超时则为true,未超时则为false
$info = stream_get_meta_data($handle);
fclose($handle);
if ($info['timed_out']) {
return 'fwrite timeout!';
} else {
return $send_msg;
}
}else{
return "stream_socket_client timeout ($errno):$errstr";
}
}
/**
* 针对第三方服务接入,返回状态码与请求响应总时间进行监控打点
*
* @param string $module 请求模块
* @param string $url 请求第三方服务url
* @param int $code 请求返回状态码
* @param int $request_time 请求总时长
* @return string 打点信息或""
*/
public static function proxyMon($module, $url, $code, $request_time = -899){
$result = "";
if(is_string($url) && !empty($url)){
//截取请求第三方服务的uri
$index = strpos($url,'?');
if($index != 0){
$preg_uri = substr($url,0, $index);
}else{
$preg_uri = $url;
}
//替换$uri中的"."为"_", ":"为"_", "/"为"-"
$request_uri = str_replace(array(".", ":", "/"),array("_", "_", "-"), $preg_uri);
//接口返回状态码打点
if(!empty($code) && is_numeric($code)){
$result = MonUtil::counting($module . "." . strval($request_uri), strval($code),1) . "\n";
}else{
$result = MonUtil::counting($module . "." . strval($request_uri), strval(-999),1) . "\n";
}
//接口请求响应总时间打点
if(!empty($request_time) && is_numeric($request_time) && $request_time != -899){
$result = $result . MonUtil::timing(strval($request_uri),"TotalTime", $request_time);
}
}
return $result;
}
/**
* @param string $module
* @param string $index
* @param int $value
* @param string $type 监控打点类型
* @return string 返回监控打点信息
*/
public static function getMsg($module, $index, $value, $type){
if(is_string($module) && !empty($module) && is_string($index) && !empty($index)){
return "Ydbot.statsd.superfe.api." . $module . "." . $index . ".1sec:" . $value . "|" . $type;
}
return "";
}
/**
* 对值进行计数。在一个flush区间,把上报的值累加;flush输出的时候,将上报的值重新清零
*
* @param string $module 子模块名字
* @param string $index 指标
* @param int $value
* @return mixed
*/
public static function counting($module = 'api', $index = '', $value = 0){
$result = "";
$send_msg = self::getMsg($module, $index, $value, "c");
if($send_msg){
$result = self::base($send_msg);
}
return $result;
}
/**
* 标量是任何可以测量的一维变量,可以通过显示的加入符号让系统进行值的累加,不加符号时添加的新值会覆盖旧值
*
* @param string $module 子模块名字
* @param string $index 指标
* @param int $value
* @return mixed
*/
public static function gauges($module = 'api', $index = '', $value = 0){
$result = "";
$send_msg = self::getMsg($module, $index, $value, "g");
if($send_msg){
$result = self::base($send_msg);
}
return $result;
}
/**
* 记录flush期间,不重复的值。譬如对于一个接口,记录有多少个user访问
*
* @param string $module 子模块名字
* @param string $index 指标
* @param int $value
* @return mixed
*/
public static function sets($module = 'api', $index = '', $value = 0){
$result = "";
$send_msg = self::getMsg($module, $index, $value, "s");
if($send_msg){
$result = self::base($send_msg);
}
return $result;
}
/**
* 记录某个操作的耗时,单位ms。在一个flush期间,会自动记录平均值(mean)、最大值(upper)、最小值(lower)、
* 个数(count)以及部分百分值(P95/P99/P999)
*
* @param string $module 子模块名字
* @param string $index 指标
* @param int $value
* @return mixed
*/
public static function timing($module = 'api', $index = '', $value = 0){
$result = "";
$send_msg = self::getMsg($module, $index, $value, "ms");
if($send_msg){
$result = self::base($send_msg);
}
return $result;
}
/**
* 记录指标概率,Value为“100”和“0”。在一个flush期间,成功的概率
*
* @param string $module 子模块名字
* @param string $index 指标
* @param int $value
* @return mixed
*/
public static function rate($module = 'api', $index = '', $value = 0){
$result = "";
$send_msg = self::getMsg($module, $index, $value, "rate");
if($send_msg){
$result = self::base($send_msg);
}
return $result;
}
}
This diff is collapsed.
## Mongo工具库
### 请使用MongoBase类,原有的Base类将废弃
## Mongo工具库使用示例
``` php
namespace App\Models\demo\mongo;
use Api\PhpUtils\Mongo\MongoBase;
class Merchant extends MongoBase
{
protected function getConfigIndex()
{
return "merchant";
}
protected function getDatabaseName()
{
return "merchant";
}
protected function getCollectionName()
{
return "test";
}
protected function getWhere($params)
{
return $params;
}
}
```
This diff is collapsed.
This diff is collapsed.
## Mysql工具库
### 主从模式请使用 MysqlBase,分库分表模式请使用 MysqlClusterBase
### MysqlBase 主从模式示例
``` php
<?php
namespace App\Models\demo\mysql;
use Api\PhpUtils\Mysql\MysqlBase;
class TestMysql extends MysqlBase
{
const TABLE_NAME = 'demo'; //表名,必填
const CONFIG_INDEX = 'test'; //yaconf中的index索引,必填
public static function getBook1()
{
return self::get('*',[
'id' => 1
]);
}
public static function insertBook()
{
return self::insert([
'name' => 'bbb',
]);
}
public static function insertManyBook()
{
return self::insert([
['name' => 'bbb'],
['name' => 'ccc'],
]);
}
public static function updateBook()
{
return self::update([
'name' => 'abc',
], ['id'=> 1]);
}
public static function countBook()
{
return self::count();
}
public static function debugBook()
{
return self::debug();
}
public static function logBook()
{
return self::log();
}
public static function lastBook()
{
return self::last();
}
public static function errorBook()
{
return self::error();
}
public static function actionBook()
{
return self::debug();
}
public static function transBook()
{
$t = self::beginTransaction();
var_dump($t);
$t1 = self::insert([
'name' => 'ccc',
]);
$t2 = self::update([
// 'name' => 'abc',
'name' => 'abcdefg',
], ['id'=> 1]);
var_dump([$t1, $t2]);
if($t1 && $t2) {
return self::commit();
}else {
return self::rollback();
}
}
}
```
## MysqlClusterBase 主从分片模式
``` php
<?php
namespace App\Models\demo\mysql;
use Api\PhpUtils\Mysql\MysqlClusterBase;
class TestClusterMysql extends MysqlClusterBase
{
const TABLE_NAME = 'demo_cluster'; //表名,必填
const CONFIG_INDEX = 'orderIndex'; //yaconf中的index索引,必填
const SHARDING_DATABASE_COLUMN = 'order_id'; // 数据库分片字段
const SHARDING_DATABASE_ALGORITHM = 'mod(-7,-5)'; // 数据库分片算法策略
const SHARDING_DATABASE_COMPAT_COLUMN = 'user_id'; // 兼容数据库分片字段
const SHARDING_DATABASE_COMPAT_ALGORITHM = 'mod(-2)'; // 兼容数据库分片算法表达式
public static function getBook()
{
return self::getMaster('*',[
'order_id' => 2105111104548610002,
'user_id' => 0,
// 'id' => 1234567890
]);
}
public static function selectBook()
{
return self::selectMaster('*',[
'order_id' => 2105111104548610002,
'user_id' => 12386,
// 'id' => 1234567890
]);
}
public static function getBook1()
{
return self::get('*',[
'id' => 2
]);
}
public static function insertBook()
{
return self::insert([
'order_id' => 2105111104548610002,
'user_id' => 12386,
'name' => 'bbb',
]);
}
public static function insertManyBook()
{
return self::insert([
[
'order_id' => 2105111104548610002,
'user_id' => 12386,
'name' => 'aaa',
],
[
'order_id' => 2105111104548610002,
'user_id' => 12386,
'name' => 'bbb',
],
[
'order_id' => 2105111104548610002,
'user_id' => 12386,
'name' => 'ccc',
]
]);
}
public static function updateBook()
{
return self::update([
'name' => 'eee',
],
[
'order_id'=> 2105111104548610002,
'user_id'=> 12386,
]);
}
public static function countBook()
{
return self::count([
'order_id' => 2105111104548610002,
'user_id' => 12386,
// 'id' => 1234567890
]);
}
public static function debugBook()
{
return self::debug();
}
public static function logBook()
{
return self::log();
}
public static function lastBook()
{
return self::last();
}
public static function actionBook()
{
return self::debug();
}
public static function errorBook()
{
return self::error();
}
public static function transBook()
{
$t = self::beginTransaction(2105111104548610002);
var_dump($t);
$t1 = self::insert([
'name' => 'aaa',
'order_id' => 2105111104548610002
]);
$t2 = self::update([
// 'name' => 'abc',
'name' => 'abcdefg',
], ['order_id'=> 2105111104548610002]);
var_dump([$t1, $t2]);
if($t1 && $t2) {
echo 'commit';
return self::commit();
}else {
echo 'rollback';
return self::rollback();
}
}
}
```
<?php
namespace Api\PhpUtils\RabbitMq;
use Api\PhpUtils\Log\FileLog;
/**
* RabiitMq工具类
*
* amqp.so verion 1.10.2
*/
class DelayRabbitMq
{
private $conn;
private $channel;
private $exchange;
private $queue;
private $config;
public $work;
public $configIndex;
public function connection()
{
try {
$this->conn = new \AMQPConnection(
$this->config
);
$this->conn->connect();
if ($this->conn->isConnected()) {
FileLog::info('RabbitMqUtil __construct-info:' . '连接成功');
} else {
FileLog::error('RabbitMqUtil __construct-error:' . '连接失败-' . json_encode($this->config));
}
} catch (\Exception $e) {
FileLog::error('RabbitMqUtil __construct-Exception:' . $e->getMessage());
}
}
private function getConfig($configIndex, $flags)
{
$arr = \Yaconf::get('rabbitmq');
$this->config = $arr['common'][$configIndex]['proxy'];
if ($flags != 'produce') {
$this->config = $arr['common'][$configIndex]['consume'];
}
}
/**
* 初始化
* @param string $exchange [交换机]
* @param string $queue [队列名]
* @param string $routing_key [路由]
* @return null
*/
private function init($exchange, $queue, $routingKey, $flags = 'produce')
{
$this->getConfig($this->configIndex, $flags);
$this->connection($flags);
try {
$this->channel = new \AMQPChannel($this->conn);
/*
name: $exchange
type: x-delayed-message
passive: false
durable: true // the exchange will survive server restarts
auto_delete: false //the exchange won't be deleted once the channel is closed.
*/
$this->exchange = new \AMQPExchange($this->channel);
$this->exchange->setName($exchange);
$this->exchange->setType('x-delayed-message');
$this->exchange->setArgument('x-delayed-type', 'direct');
if ($flags == 'produce') {
$this->exchange->setFlags(AMQP_DURABLE);
} else {
$this->exchange->setFlags(AMQP_PASSIVE); //声明一个已存在的交换器的,如果不存在将抛出异常,这个一般用在consume端
}
$this->exchange->declareExchange();
/*
name: $queue
passive: false // don't check if an exchange with the same name exists
durable: true // the queue will survive server restarts
exclusive: false // the queue can be accessed in other channels
auto_delete: false //the queue won't be deleted once the channel is closed.
*/
$this->queue = new \AMQPQueue($this->channel);
$this->queue->setName($queue);
$this->queue->setFlags(AMQP_DURABLE);
$this->queue->declareQueue();
$this->queue->bind($exchange, $routingKey);
} catch (\Exception $e) {
FileLog::error('RabbitMqUtil __init-Exception' . $e->getMessage());
}
}
/**
* 发送消息
* @param string $queue [队列名]
* @param string $sendMessage [发送消息]
* @param int $minute [延迟时间 - 分]
* @param string $exchange [交换机]
* @return null
*/
public function produce($queue, $sendMessage, $minute, $exchange = 'x-delayed-message')
{
$routing_key = $queue;
try {
$this->init($exchange, $queue, $routing_key);
$this->exchange->publish(
$sendMessage,
$routing_key,
AMQP_MANDATORY, //mandatory标志告诉服务器至少将该消息route到一个队列中,否则将消息返还给生产者
array(
'headers' => [
'x-delay' => 1000 * 60 * $minute
]
)
);
$this->conn->disconnect();
} catch (\Exception $e) {
FileLog::error('RabbitMqUtil __produce-Exception:' . $e->getMessage());
}
}
public function process($queue, $exchange = 'x-delayed-message')
{
$routing_key = $queue;
$this->init($exchange, $queue, $routing_key, 'process');
try {
$this->queue->consume([$this, 'processMessage']);
} catch (\Exception $e) {
FileLog::error('RabbitMqUtil __process-Exception:' . $e->getMessage());
}
$this->conn->disconnect();
}
public function processMessage($envelope)
{
$message_body = $envelope->getBody();
$userLogic = $this->work;
if (!is_callable($userLogic)) {
throw new \Exception("需要实现自己的方法逻辑");
}
$is_ack = $userLogic($message_body);
if ($is_ack) {
$this->queue->ack($envelope->getDeliveryTag());
} else {
//$this->queue->nack($envelope->getDeliveryTag());
//无应答 会存放在unacked状态下重连会放回ready,如果发送unack 状态 后会直接放回 ready
}
}
}
#RabbitMq 延迟队列
###新版本使用示例
```php
$mq = new DelayRabbitMq();
$queue = 'delay_list';
$minute = 2; //延是1分钟处理
$message = json_encode(['test' => 'rabbitmq produce', 'reason' => 'send message']);
$mq->produce($queue, $message, $minute);
$mq->work = function ($message) {
$message = json_decode($message,true);
var_dump($message);
return true; //应答
};
$mq->process($queue);
```
\ No newline at end of file
Redis工具库
\ No newline at end of file
This diff is collapsed.
Validate工具库
\ No newline at end of file
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