<?php
/**
* 常用且复杂的方法,虽然基于YII框架写的,但是并不是和Yii紧耦合,如果在其他框架使用,简单修改即可,我已经在laravel 和 CI 框架均使用过此代码库
*/
namespace app\library;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Exception;
use Yii;
use yii\helpers\Json;
use yii\httpclient\Client;
use yii\log\FileTarget;
use yii\helpers\FileHelper;
class PublicFunction
{
/**
* 日志
*
* @param string $path
* @return void
*/
static public function logs($path = 'PubFunction')
{
$argv = func_get_args();
array_shift($argv);
$txt = '';
foreach ($argv as $val) {
if (is_array($val) || is_object($val)) {
$txt .= print_r($val, 1) . "\n";
} else {
$txt .= $val . "\n";
}
}
$time = microtime(true);
$log = new FileTarget();
$log->maxFileSize = 51200;
$log->maxLogFiles = 50;
$log->logFile = Yii::$app->getRuntimePath() . '/logs/' . $path . '/' . date('Ymd') . '.log';
$log->messages[] = ["\n" . $txt . "\n------------------------------------------------------------------------", 4, 'pubfunction', $time];
$logPath = dirname($log->logFile);
if ((!is_dir($logPath)) && (!file_exists($logPath))) {
FileHelper::createDirectory($logPath, 0777, true);
}
$log->export();
}
/**
* 获取当前登录者
*
* @return array
*/
static public function getCurrentUser()
{
if (property_exists(Yii::$app->controller, 'user')) {
return Yii::$app->controller->user;
} else {
return [];
}
}
/**
* 从电子表格中读数据放到第二个参数中
* @param string $file 文件路径
* @param array $return 读出的数据
* @return number 返回sheet的偏移量
*/
static public function readDataInSpreadsheet($file, &$return)
{
$spreadsheet = IOFactory::load($file);
$return = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
return $spreadsheet->getIndex($spreadsheet->getActiveSheet());
}
/**
* 从一个大的excel中读取数据
*
* @param string $file 文件位置
* @param callable $callable 处理一行的回掉函数
* @param string $maxColumn 处理最大的列号,超过此列的列号将忽略
* @return void
* @throws CommandImportIgnoreException 收到此异常,将退出循环,不在继续检索下一个行
*/
static public function readDataInBigSpreadsheet($file, callable $callable, $maxColumn = 'AC')
{
echo '开始执行前' . round(memory_get_usage() / 1024 / 1024, 3) . PHP_EOL;
$spreadsheet = IOFactory::load($file);
$sheet = $spreadsheet->getActiveSheet();
echo '加在excel之后' . round(memory_get_usage() / 1024 / 1024, 3) . PHP_EOL;
// exit;
$maxCol = $sheet->getHighestColumn();
$maxCol = Coordinate::columnIndexFromString($maxCol) > Coordinate::columnIndexFromString($maxColumn) ? $maxColumn : $maxCol;
$maxRow = $sheet->getHighestRow();
$err = null;
foreach ($sheet->getRowIterator() as $row) {
$lineIndex = $row->getRowIndex();
$values = [];
foreach ($row->getCellIterator() as $cell) {
$column = $cell->getColumn();
$values[$column] = static::trim($cell->getValue());
// if($lineIndex == 128)var_dump($values[$column]);
if (Coordinate::columnIndexFromString($column) >= Coordinate::columnIndexFromString($maxCol)) break;
}
try {
call_user_func_array($callable, [$values, $lineIndex]);
} catch (CommandImportIgnoreException $e) {//忽略异常,则退出循环
break;
} catch (\Exception $e) {
$err = $e;
break;
}
if ($lineIndex >= $maxRow) break;
}
echo '处理之后' . round(memory_get_usage() / 1024 / 1024, 3) . PHP_EOL;
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
$sheet->disconnectCells();
unset($sheet);
unset($maxCol);
gc_collect_cycles();
echo '回收内存之后' . round(memory_get_usage() / 1024 / 1024, 3) . PHP_EOL;
if ($err) {
throw $err;
}
}
/**
* 导出 .xlsx
* @param string $title 文件名
* @param array $header 行头,eg:['姓名','性别', '年龄']
* @param array $data 数据,eg:
* [
* [
* '王大',
* '男',
* 15,
* ],
* [
* '王大',
* '男',
* 15,
* ],
* ]
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws Exception
*/
static public function export($title, $header, &$data)
{
$spreadsheet = new Spreadsheet();
$spreadsheet->getProperties()
->setTitle($title);
$handle = $spreadsheet->setActiveSheetIndex(0);
foreach ($header as $index => $item) {
$handle->setCellValue(Coordinate::stringFromColumnIndex($index + 1) . "1", $item);
}
$loop = 2;
static::exportBody($title, $loop, $data, $handle, $spreadsheet);
}
static public function exportBody($title, $loop, $data, Worksheet $handle, Spreadsheet $spreadsheet)
{
if (is_callable($data)) {
$data = call_user_func($data);
}
foreach ($data as $values) {
$values = array_values($values);
foreach ($values as $index => $val) {
$val = is_array($val) ? implode(',', $val) : $val;
$handle->setCellValue(Coordinate::stringFromColumnIndex($index + 1) . $loop, $val);
}
$loop++;
}
$spreadsheet->setActiveSheetIndex(0);
static::exportEnd($title, $spreadsheet);
}
static public function exportEnd($title, Spreadsheet $spreadsheet)
{
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename=' . $title . '.xlsx');
header('Cache-Control: max-age=0');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
header('Pragma: public'); // HTTP/1.0
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->save('php://output');
exit;
}
/**
* 输出大文件,使用 Spout 重构,Spout号称:Spout needs only 3MB of memory to process any file.
* https://opensource.box.com/spout/
*
* @param string $fileName 文件名
* @param array $headers 表头
* @param \Closure $callable 闭包,返回数据迭代器
* @param boolean $toBrowser 是否输出到浏览器
* @param \Closure $sheets 闭包,返回sheets迭代器,导出文件包含其他sheet时,使用此参数简单扩充
* [
* 'sheetName' => 'sheet 名字,非必要',
* 'header' => [],//表头,array
* 'callback' => callable(), //\Generator 数据迭代器
* ]
*
* eg:
* PublicFunction::exportBigData('111', [1, 2, 3], call_user_func( function () {
yield [1, 2, 3];
yield ['a', 'b', 'c'];
yield [1, 2, 3];
}), false, call_user_func( function () {
yield [
'sheetName' => 'other sheet 1',
'header' => ['s11','s12','s13'],//表头,array
'data' => call_user_func(function () {
yield [1, 2, 3];
yield ['a', 'b', 'c'];
yield [1, 2, 3];
})
];
yield [
'sheetName' => 'other sheet 2',
'header' => ['s21','s22','s23'],//表头,array
'data' => call_user_func(function () {
yield [1, 2, 3,6];
yield ['a', 'b', 'c'];
yield [1, 2, 3];
})
];
}));
* @throws \Box\Spout\Common\Exception\IOException
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException
*/
static public function exportBigData($fileName, $headers, \Iterator $data, $toBrowser = true, \Iterator $sheets = null)
{
// $spreadsheet = new Spreadsheet();
//
// $spreadsheet->getProperties()
// ->setTitle($fileName);
// $handle = $spreadsheet->setActiveSheetIndex(0);
// foreach ($headers as $index => $item) {
// $handle->setCellValue(Coordinate::stringFromColumnIndex($index + 1) . "1", $item);
// }
//
// $data = call_user_func($callable);
// $loop = 2;
// foreach ($data as $values) {
// foreach ($values as $index => $val) {
// $val = is_array($val) ? implode(',', $val) : $val;
// $handle->setCellValue(Coordinate::stringFromColumnIndex($index + 1) . $loop, $val);
// }
// $loop++;
// }
//
// $spreadsheet->setActiveSheetIndex(0);
//
// $writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
// if($toBrowser){
// header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
// header('Content-Disposition: attachment;filename=' . $fileName . '.xlsx');
// header('Cache-Control: max-age=0');
//
// header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
// header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
// header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
// header('Pragma: public'); // HTTP/1.0
//
// $writer->save('php://output');
// }else{
// $writer->save($fileName);
// }
//
// exit;
$writer = WriterEntityFactory::createXLSXWriter();
$writer->setShouldUseInlineStrings(false);
$fileName = substr($fileName, -5) == '.xlsx' ? $fileName : ($fileName . '.xlsx');
if ($toBrowser) {
$writer->openToBrowser($fileName);
} else {
$writer->openToFile($fileName);
}
$writerFunc = function ($headers, $data, $writer) {
$h = WriterEntityFactory::createRowFromArray($headers);
$writer->addRow($h);
foreach ($data as $row) {
if ($row) {
foreach ($row as $k => $v) {
if (is_array($v)) {
$row[$k] = implode(',', $v);
} else {
$row[$k] = strval($v);
}
}
$r = WriterEntityFactory::createRowFromArray($row);
$writer->addRow($r);
}
}
};
$writerFunc($headers, $data, $writer);
if ($sheets) {
foreach ($sheets as $sheetData) {
$sheet = $writer->addNewSheetAndMakeItCurrent();
if (!empty($sheetData['sheetName'])) {
$sheet->setName($sheetData['sheetName']);
}
$writerFunc($sheetData['header'], $sheetData['data'], $writer);
}
}
$writer->close();
if ($toBrowser) {
exit;
}
}
static public function exportZip($fileName, $headers, \yii\db\Query $query, $callable, $exportAfterCallback)
{
$subProcess = new SubProcess(8);
$limit = 5000;
$page = 0;
$tmpDir = sys_get_temp_dir() . '/export/' . \uniqid();
$sourceDir = $tmpDir . '/' . $fileName . '/';
$toFilename = $tmpDir . '/' . $fileName . '.zip';
if (!is_dir($sourceDir)) {
FileHelper::createDirectory($sourceDir);
}
$total = $query->count();
if ($total == 0) {
throw new MCNException("导出结果为空");
}
$pageTotal = ceil($total / $limit);
while (1) {
if ($page == $pageTotal) break;
$offset = $page * $limit;
if ($page == $pageTotal - 1) {
$limit = $total % $limit;
}
$subProcess->do(function () use ($sourceDir, $offset, $limit, $callable, $headers, $query) {
Yii::$app->db->close();
$fileName = $sourceDir . ($offset + 1) . '-' . ($offset + $limit) . '.xlsx';
static::exportBigData($fileName, $headers, function () use ($callable, $query, $offset, $limit) {
foreach ($callable($query, $offset, $limit) as $item) {
yield $item;
}
}, false);
});
$page++;
}
$subProcess->wait();
// while (1){
// var_dump($sourceDir, $toFileDir.$fileName.'.zip');
// sleep(10);
// }
HZip::zipDir($sourceDir, $toFilename);
$exportAfterCallback($toFilename);
FileHelper::removeDirectory($tmpDir);
}
/**
* 输出大文件
* @param string $fileName 文件名
* @param array $headers 表头
* @param callable $callable 返回一条数据,代表一行
* @throws \Box\Spout\Common\Exception\IOException
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException
*/
static public function exportBigData2($fileName, $headers, $callable)
{
$writer = WriterEntityFactory::createXLSXWriter();
// $writer->openToFile($filePath);
$writer->openToBrowser($fileName . '.xlsx');
$writer->setShouldUseInlineStrings(false);
$h = WriterEntityFactory::createRowFromArray($headers);
$writer->addRow($h);
$data = call_user_func($callable);
foreach ($data as $row) {
if ($row) {
foreach ($row as $k => $v) {
if (is_array($v)) {
$row[$k] = implode(',', $v);
} else {
$row[$k] = strval($v);
}
}
$r = WriterEntityFactory::createRowFromArray($row);
$writer->addRow($r);
}
}
$writer->close();
\Yii::$app->end(\Yii::$app::STATE_END);
}
/**
* 判断文件夹是否存在,如果不存在则创建
*
* @param [type] $dir
* @return void
* @throws \yii\base\Exception
*/
static public function checkDir($dir)
{
if (!is_dir($dir)) {
FileHelper::createDirectory($dir, 0777, true);
}
}
/**
* 格式化字符串
*
* @param [type] $txt
* @param integer $lenght
* @return void
*/
static public function formatString($txt, $lenght = 10)
{
if (mb_strlen($txt) > $lenght) {
return mb_substr($txt, 0, $lenght - 3) . '...';
} else {
return $txt;
}
}
/**
* 下载csv,支持utf8
*
* @param [type] $filename
* @return void
*/
static public function importCSV($filename)
{
$result = [];
if (($handle = fopen($filename, 'r')) !== FALSE) {
$i = 0;
while (($data = fgetcsv($handle, 0, ",")) !== FALSE) {
$isempty = true;
array_walk($data, function (&$a) use (&$isempty) {
$a = trim($a);
if ($a != '') {
$t = mb_detect_encoding($a, ["GB18030", "UTF-8", "GB2312", "GBK", "BIG5"]);
if ($t !== false && $t != "UTF-8") {
$a = mb_convert_encoding($a, 'UTF-8', $t);
}
$isempty = false;
}
});
if ($isempty) continue;
$result[] = $data;
}
fclose($handle);
}
return $result;
}
/**
* 下载excel
*
* @param array $data
* @param string $file_name
* @return void
*/
static public function downloadCsv($data = [], $file_name = '')
{
ob_clean();
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename=' . $file_name);
header('Cache-Control: max-age=0');
$fp = fopen('php://output', 'a');
fwrite($fp, "\xEF\xBB\xBF");
foreach ($data as $v) {
echo $v['data'];
}
fclose($fp);
}
/**
* 下载cfv,支持unicode
*
* @param array $data
* @param string $file_name
* @return void
*/
static public function downloadCsv2($data = [], $file_name = '')
{
ob_clean();
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename=' . $file_name);
header('Cache-Control: max-age=0');
$fp = fopen('php://output', 'a');
fwrite($fp, "\xEF\xBB\xBF");
foreach ($data as $v) {
echo implode(',', $v) . "\n";
}
fclose($fp);
}
/**
* 写入csv文件首行
*
* @param string $path 保存路径
* @param string $fileName 文件名
* @param array $header 写入的首行
* @param integer $time 等待延时
* @param string $size 内存大小
* @return void 返回文件指针
*/
static public function downloadCsvHeader($path, $fileName, $header = array(), $time = 0, $size = '512M')
{
set_time_limit($time);
ini_set('memory_limit', $size);
if (!is_dir($path)) {
mkdir($path, 0777, true);
}
$fp = fopen($path . $fileName, 'w');
fputcsv($fp, $header);
return $fp;
}
/**
* 支持中文的rtrim
*
* @param string $string 待处理字符串
* @param string $trim_chars
* @return void
*/
static function mb_rtrim($string, $trim_chars = '\s')
{
return preg_replace('/(.*?)[' . $trim_chars . ']*$/u', '\\1', $string);
}
/**
* 是否时手机号
*
* @param number $phone 待检查手机号
* @return boolean
*/
public static function isPhone($phone)
{
// return preg_match('/^(13|14|15|16|17|18|19)[0-9]{9}$/', $phone);
return preg_match('/^1[0-9]{10}$/', $phone);
}
/**
* 检查是不是日期类型字符串
*
* @param string $date 待检查字符串
* @return bool
*/
public static function checkDateFormat($date)
{
$format = [
'Y-m-d',
'Y/m/d',
'Y-n-j',
'Y/n/j'
];
foreach ($format as $item) {
if ($date == date($item, strtotime($date))) {
return true;
}
}
return false;
}
/**
* object/数组转换为json字符串
* @param array|object $data
* @return string
*/
static function array_jsonencode($data)
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
/**
* 获取反转数据,支持数组返回
* @param $data
* @param null $value
* @param bool $key
* @param bool $showName
* @return array
*/
static function getFlip($data, $value = null, $key = true, $showName = true)
{
foreach ($data as $k => &$v) {
$v['id'] = $k;
}
$result = array_column($data, ($showName ? 'name' : ($key ? 'alias' : 'id')), ($key ? 'id' : 'alias'));
return $value !== null ? (isset($result[$value]) ? $result[$value] : '参数错误') : $result;
}
/**
* 得到客户端IP
* @param int $type 返回id类型,0:返回ip,1:返回long值
* @return string
*/
static function getClientIps($type = 0)
{
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown', $arr);
if (false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u", ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
/**
* 字符串去重
* @param $str
* @return string
*/
static function unique($str)
{
$arr = explode(',', $str);
$arr = array_unique($arr);
$data = implode(',', $arr);
$data = trim($data, ',');
return $data;
}
/**
* 获取网络资源,等价 curl 等工具,
* @param string $sUrl 资源链接
* @param string $method post|get 等
* @param array $data 资源参数
* @param bool $jsonFormat 在post请求中,请求体是否为json格式
* @param int $errTimes 当前错误次数
* @param int $maxTimes 最大重试次数
* @param string $logCategory 日志主题
* @param array $headers 添加额外的header
* @return string|null 返回响应字符串
* @throws \yii\base\InvalidConfigException
*/
public static function getResBody($sUrl, $method, $data = [], $jsonFormat = false, $errTimes = 0, $maxTimes = 0, $logCategory = 'getResBody', $headers = [])
{
if ($errTimes > $maxTimes) {
\Yii::info("已经尝试{$errTimes}次,均链接异常,目标地址失联", $logCategory);
throw new \Exception("已经尝试{$errTimes}次,均链接异常,目标地址失联");
}
try {
$timer = PublicFunction::getTimer();
$client = new Client([
'transport' => 'yii\httpclient\CurlTransport' // only cURL supports the options we need
]);
$connectTimeout = 5;
$timeout = 15;
$req = $client->createRequest()
->setMethod($method)
->setUrl($sUrl)
->setOptions([
CURLOPT_CONNECTTIMEOUT => $connectTimeout, // connection timeout
CURLOPT_TIMEOUT => $timeout, // data receiving timeout
]);
if ($headers) {
$req->setHeaders($headers);
}
if ($jsonFormat) {
$req->setFormat(Client::FORMAT_JSON);
}
if ($data) {
$req->setData($data);
};
$res = $req->send();
\Yii::info($sUrl . PHP_EOL . '请求时间' . $timer() . PHP_EOL . var_export(compact('method', 'data', 'jsonFormat', 'errTimes', 'maxTimes', 'logCategory', 'headers', 'res'), 1), $logCategory);
return $res->getContent();
} catch (\yii\httpclient\Exception $e) {
if ($e->getCode() == 28) {//接收数据超时
\Yii::info($sUrl . PHP_EOL . ' 接口超时timeout' . ($errTimes + 1) . '次,接口超时时间' . $timeout, $logCategory);
return static::getResBody($sUrl, $method, $data, $jsonFormat, $errTimes + 1, 20);
}
\Yii::info($sUrl . PHP_EOL . '链接请求超时connectTimeout' . ($errTimes + 1) . '次,超时时间' . $connectTimeout, $logCategory);
usleep(500);
return static::getResBody($sUrl, $method, $data, $jsonFormat, $errTimes + 1, 10);
}
}
/**
* 获取计时器,debug工具
*/
public static function getTimer()
{
$begin = microtime(true);
return function ($format = true) use ($begin) {
$sec = bcsub(microtime(true), $begin);
if (!$format) {
return $sec;
}
$sec = round($sec);
$m = floor($sec / 60);
$h = 0;
if ($m) {
$sec = $sec % 60;
$h = floor($m / 60);
if ($h) {
$m = $m % 60;
}
}
$res = "";
if ($h) {
$res .= $h . '小时';
$res .= $m . '分';
$res .= $sec . '秒';
} else if ($m) {
$res .= $m . '分';
$res .= $sec . '秒';
} else {
$res .= $sec . '秒';
}
return $res;
};
}
/**
* trim
*/
public static function trim($str)
{
$search = array(" ", " ", "\n", "\r", "\t", " ", "\r\t");
$replace = "";
return str_replace($search, $replace, $str);
}
/**
* 使用项其他的前缀redis链接执行方法
*
* @param string $prefix
* @param callable $callable
* @return mixed
*/
public static function doOtherRedis($prefix, $callable)
{
return call_user_func_array($callable, [static::getOtherRedis($prefix)]);
}
/**
* 获取其他前缀redis链接
* @param $prefix
* @return Redis
*/
public static function getOtherRedis($prefix): Redis
{
$prefix .= ':';
static $conns = [], $config = [];
if (key_exists($prefix, $conns)) {
return $conns[$prefix];
}
if (!$config) {
$config = require Yii::$app->basePath . '/config/redis.php';
}
$config['options']['prefix'] = $prefix;
$conns[$prefix] = Yii::createObject($config);
return $conns[$prefix];
}
/**
* 获取
* @param string $dateTime datetime
* @return float
*/
public static function diffToToday($dateTime)
{
return round((time() - strtotime($dateTime)) / 86400, 2);
}
/**
* 删除数组中的某个值
* @param $ary
* @param $val
* @return array 被移除后的数组
*/
public static function arrayRemoveVal($ary, $val)
{
if (($index = array_search($val, $ary)) !== false) {
array_splice($ary, $index, 1);
}
return $ary;
}
/**
* 兼容utf8的parse_url
* @param $url
* @return mixed
*/
public static function mb_parse_url($url)
{
$enc_url = preg_replace_callback(
'%[^:/@?&=#]+%usD',
function ($matches) {
return urlencode($matches[0]);
},
$url
);
$parts = parse_url($enc_url);
if ($parts === false) {
throw new \InvalidArgumentException('Malformed URL: ' . $url);
}
foreach ($parts as $name => $value) {
$parts[$name] = urldecode($value);
}
return $parts;
}
/**
* 生成签名
* @param $data
* @param $apiSecret
* @return string
*/
static public function getSign($data, $apiSecret)
{
unset($data['smidgn5']);
unset($data['data']);
ksort($data);
$stringA = '';
foreach ($data as $key => $value) {
$value = str_replace(' ', '+', $value);
$stringA .= $key . '=' . $value . '&';
}
$stringA = trim($stringA, '&');
$stringSignTemp = $stringA . '&key=' . $apiSecret;
$signTmp = strtoupper(md5($stringSignTemp));
return $signTmp;
}
public static function convert($size)
{
$unit = array('b', 'kb', 'mb', 'gb', 'tb', 'pb');
return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . ' ' . $unit[$i];
}
/**
* 代理下载网络资源
* @param string $filename 资源链接
* @param null|\Closure $downloadAfter 下载后执行回的调方法
*/
public static function download($filename, $downloadAfter = null)
{
//设置脚本的最大执行时间,设置为0则无时间限制
set_time_limit(0);
ini_set('max_execution_time', '0');
//通过header()发送头信息
//因为不知道文件是什么类型的,告诉浏览器输出的是字节流
header('content-type:application/octet-stream');
//告诉浏览器返回的文件大小类型是字节
header('Accept-Ranges:bytes');
//获得文件大小
$filesize = filesize($filename);//(此方法无法获取到远程文件大小)
// $header_array = get_headers($filename, true);
// $filesize = $header_array['Content-Length'];
//告诉浏览器返回的文件大小
header('Accept-Length:' . $filesize);
//告诉浏览器文件作为附件处理并且设定最终下载完成的文件名称
header('content-disposition:attachment;filename=' . basename($filename));
//针对大文件,规定每次读取文件的字节数为4096字节,直接输出数据
$read_buffer = 2048;
$handle = fopen($filename, 'rb');
@ob_clean();
@ob_end_clean();
$sum_buffer = 0;
while (!feof($handle)) {
echo fread($handle, $read_buffer);
$sum_buffer += $read_buffer;
@ob_flush();
@flush();
}
//关闭句柄
fclose($handle);
if ($downloadAfter) {
call_user_func_array($downloadAfter, []);
}
exit;
}
public static function randomStr($count = 6)
{
$base = 'abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ23456789';
$ret = '';
$strlen = strlen($base);
for ($i = 0; $i < $count; ++$i) {
$ret .= $base[random_int(0, $strlen - 1)];
}
return $ret;
}
public static function numeric2Num($val)
{
if (is_array($val)) {
array_walk($val, function (&$v) {
$v = static::numeric2Num($v);
});
} elseif (is_numeric($val)) {
$val = $val - 0;
}
return $val;
}
public static function getAgeFromBirthDay($birthday)
{
$bday = new \DateTime($birthday); // 你的出生日
$today = new \Datetime(date('Y-m-d'));
$diff = $today->diff($bday);
return $diff->y;
}
static $locks = [];
public static function lock($name, $ex = 60)
{
$redis = Yii::$app->redis;
$val = uniqid();
if ($redis->set($name, $val, 'NX', 'EX', $ex)) {
register_shutdown_function(function () use ($name, $val) {
PublicFunction::unlock($name, $val);
});
} else {
throw new \Exception('执行中,请稍等');
}
return $val;
}
public static function unlock($name, $val)
{
static $releaseLuaScript = <<<LUA
if redis.call("GET",KEYS[1])==ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end
LUA;
return Yii::$app->redis->eval($releaseLuaScript, 1, $name, $val);
}
/**
* 按首字母大写把字符串分隔成数组
* @param $str
* @return array|false|string[]
*/
public static function FUSplitStr($str)
{
return preg_split('/(?<=[a-z0-9])(?=[A-Z])/x', $str);
}
/**
* 把关联数组的健由下划线转成驼峰
* @param array $val
* @return array
*/
public static function camelizeField($val)
{
foreach ($val as $f => $v) {
unset($val[$f]);
$val[static::camelize($f)] = $v;
}
return $val;
}
/**
* * 下划线转驼峰
* * 思路:
* * step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
* * step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
* @param $uncamelized_words
* @param string $separator
* @return string
*/
public static function camelize($uncamelized_words, $separator = '_')
{
$uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator);
}
/**
* 驼峰命名转下划线命名
* @param $camelCaps
* @param string $separator
* @return string
*/
public static function uncamelize($camelCaps, $separator = '_')
{
return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', $separator . '$1', $camelCaps));
}
/**
* 获取对象的反射句柄
* @param $class
* @return \ReflectionClass
*/
public static function getReflection($class)
{
static $handles = [];
if (!isset($handles[$class])) {
$handles[$class] = new \ReflectionClass($class);
}
return $handles[$class];
}
public static function getConstantsNameByVal($class, $prefix, $val)
{
static $contents = [];
if (!isset($contents[$class][$prefix])) {
$contents[$class][$prefix] = array_flip(static::getConstantsInClass($class, $prefix));
}
if (isset($contents[$class][$prefix][$val])) return substr($contents[$class][$prefix][$val], strlen($prefix));
throw new \Exception('常量不存在');
}
/**
* 获取一个类中指定的常量
* @param string $className 类的名字
* @param string|null $prefix 常量前缀
* @return array
*/
public static function getConstantsInClass($className, $prefix = null)
{
$objClass = static::getReflection($className);
$arrConst = $objClass->getConstants();
if (!$prefix) {
return $arrConst;
}
$res = [];
foreach ($arrConst as $k => $v) {
if (strpos($k, $prefix) === 0) {
$res[$k] = $v;
}
}
return $res;
}
/**
* file_get_contents post 请求
* @param $url
* @param $curlPost
* @return mixed
*/
public static function http_post($url, $curlPost)
{
$query = http_build_query($curlPost);
$options['http'] = array(
'timeout' => 60,
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $query
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$userInfo = json_decode($result, true);
return $userInfo;
}
/*
* redis
* 用scan实现keys
* $limit 获取数量,0不限
* $prefix是否需要连接配置前缀
* */
public static function redisScan($pattern, $limit = 0, $prefix = true)
{
$data = [];
$cursor = 0;
if ($prefix === true) $pattern = redis()->options['prefix'] . $pattern;//是否添加前缀
do {
list($cursor, $keys) = redis()->scan($cursor, 'MATCH', $pattern);
$cursor = (int)$cursor;
if (!empty($keys)) {
$data = array_merge($data, $keys);
if (count($keys) >= $limit and $limit) break;
}
} while ($cursor !== 0);
return $limit ? array_slice($data, 0, $limit) : $data;
}
}