mysql 新增数据时防止唯一字段重复导致插入错误

insert into a (id) 
select 1 from DUAL
where not exists (select id from a where id = 1)
发表在 未分类 | 留下评论

git tag 管理

查看所有标签

git tag

查看指定格式的标签

git tag -l v1.*.*

检出到某个标签

git checkout v1.0.0

创建标签(本地)
1.

git tag v1.0.0

2. (推荐)

git tag -a v1.0.0 -m "这是备注信息"

3.

git tag -a v1.0.0 [分支版本号] -m "这是备注信息"

删除标签

git tag -d v1.0.0

删除远程标签
1. 删除本地标签
2.

git push origin :refs/tags/v1.0.0

把标签推送到服务器上
1. 推动所有标签

git push origin --tags

2. 推送指定版本

git push origin v1.0.0
发表在 git | 标签为 , , | 留下评论

js 一键复制文本

function copyToClipboard(t) {
	if (window.clipboardData && window.clipboardData.setData) return clipboardData.setData("Text", t);
	if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
		var e = document.createElement("textarea");
		e.textContent = t, e.style.position = "fixed", document.body.appendChild(e), e.select();
		try {
			return document.execCommand("copy")
		} catch (t) {
			return console.warn("Copy to clipboard failed.", t), !1
		} finally {
			document.body.removeChild(e)
		}
	}
}
发表在 js, 前端 | 标签为 | 留下评论

基于swoole实现超简单子进程管理工具

<?php
/**
 * 子进程管理工具,处理大数据时,防止创建过多的子进程导致内存过载
 * swoole 官网 https://wiki.swoole.com/
 */

namespace app\library;


use Swoole\Process;

/**
 * Class SubProcess 基于swoole实现的子简单进程管理,主要用于让"子进程"完成数据处理任务。
 * eg:
 * $subProcess = new SubProcess(10); //最多启用10个子进程
 * $id = 0;
 * while(1){
 *      $list = $db->where(['>', 'id', $id])->limit()->queryAll();//获取一批数据
 *.     if(!$list) break;
 *.     
 *      $id = $list[count($list)-1]['id'];
 *      $subProcess->do(function()use($list){//交付给子进程执行
 *          Yii::$app->db->close();//重新启用db,子进程中资源类型的变量将会失效,重新获取
 *          //do something
 *      });
 * }
 * $subProcess->wait();//等待所有子进程执行完毕
 * @package app\library
 */
Class SubProcess
{
    protected $maxNum = 0;
    protected $list = [];

    public function __construct($maxNum = 10)
    {
        $this->maxNum = $maxNum;
    }

    /**
     * 在子进程中处理
     * @param $callback
     */
    public function do($callback)
    {
        $this->canAdd();
        $process = new Process(function () use ($callback) {
            call_user_func($callback);
        });
        $process->start();
        $this->list[$process->pid] = 1;
    }

    /**
     * 等待所有子进程完成
     */
    public function wait()
    {
        while (count($this->list)) {
            $this->waitASubProcess();
        }
    }

    /**
     * 是否需要添加子进程
     * @return bool
     */
    protected function canAdd()
    {
        if (count($this->list) < $this->maxNum) {
            return true;
        }

        $this->waitASubProcess();
        return true;
    }

    /**
     * 等待一个子进程结束
     */
    protected function waitASubProcess()
    {
        $status = Process::wait();
        unset($this->list[$status['pid']]);
    }
}
发表在 php, php函数集, swoole, 基础, 框架 | 标签为 , , | 留下评论

常用php函数总结

<?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;
    }
}
发表在 php, php函数集, 基础, 程序基础 | 标签为 , | 留下评论

php8 docker file

FROM php:8.1.7-fpm-alpine


USER root

# 更改镜像源为阿里云
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/' /etc/apk/repositories \
    && apk update \
    && apk upgrade \
    && apk add --no-cache bash

# 安装相关的依耐包
RUN apk --update add wget \
  curl \
  curl-dev \
  git \
  build-base \
  libmemcached-dev \
  libmcrypt-dev \
  libxml2-dev \
  pcre-dev \
  zlib-dev \
  autoconf \
  cyrus-sasl-dev \
  libgsasl-dev \
  oniguruma-dev \
  openssl \
  openssl-dev \
  g++ \
  libtool \
  make \
  linux-headers 

# 安装 mysqli mbstring pdo pdo_mysql xml pcntl
RUN docker-php-ext-install pdo pdo_mysql mysqli mbstring bcmath

# 安装GD库 7.4 安装参数发生变化 @https://www.php.net/manual/zh/migration74.other-changes.php#migration74.other-changes.pkg-config
RUN apk add --update --no-cache freetype-dev libjpeg-turbo-dev jpeg-dev libpng-dev; \
        docker-php-ext-configure gd --with-freetype=/usr/lib/ --with-jpeg=/usr/lib/ && \
        docker-php-ext-install gd; 


# 安装ZipArchive
RUN apk --update add libzip-dev && \
    docker-php-ext-configure zip && \
    docker-php-ext-install zip;

# 安装redis扩展
RUN pecl install -o -f redis \
    &&  rm -rf /tmp/pear \
    &&  docker-php-ext-enable redis

# 安装composer
# ADD ./composer /usr/local/bin/composer

# Composer install
RUN curl -sS http://getcomposer.org/installer | php \
   && mv composer.phar /usr/local/bin/composer \
   && chmod u+x /usr/local/bin/composer \
   && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/


# Swoole install
RUN docker-php-ext-install -j 2 sockets \
    && pecl install -D 'enable-sockets="yes" enable-openssl="yes" enable-http2="yes" enable-mysqlnd="yes" enable-swoole-json="yes" enable-swoole-curl="yes" enable-cares="yes"' swoole \
    && docker-php-ext-enable swoole

# 安装 memcached、mongodb
RUN pecl install memcached mongodb && \
    docker-php-ext-enable memcached mongodb

# Clean up
RUN rm /var/cache/apk/* \
    && mkdir -p /var/www \
    && rm -rf /usr/src/php


CMD ["php-fpm"]

EXPOSE 9000
发表在 docker, 基础 | 标签为 , , , , | 留下评论

php 创建守护进程

function createDeamon() {
        set_time_limit(0);

        // 只允许在cli下面运行  
        if (php_sapi_name() != "cli") {
            die("only run in command line mode\n");
        }

        umask(0); //把文件掩码清0  

        if (pcntl_fork() != 0) { //是父进程,父进程退出  
            exit();
        } 

        posix_setsid(); //设置新会话组长,脱离终端  

        if (pcntl_fork() != 0) { //第二次fock子进程  
            exit();
        }


        chdir("/"); //改变工作目录  

        $user = posix_getpwnam(self::getConfig('deamon', 'user'));
        if ($user) {
            $uid = $user['uid'];
            $gid = $user['gid'];
            $result = posix_setuid($uid);
            posix_setgid($gid);
        } else {
            die('守护进程用户权限设置失败,请重新设置!');
        }

        //关闭打开的文件描述符  
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
        
        global $STDIN, $STDOUT, $STDERR;
        $outputfile = self::getConfig('deamon', 'outputfile');
        $dirname = dirname($outputfile);
        if( !file_exists($dirname) ){
            mkdir($dirname);
        }
        
        $STDIN = fopen('/dev/null', "a");
        $STDOUT = fopen($outputfile, "a");
        $STDERR = fopen($outputfile, "a");
    }
发表在 php, php函数集 | 留下评论

mysql 忘记root密码,重置mysql root

1. service mysql stop
2. 进入到mysql安装目录 比如/usr/mysql
3. 在安装目录执行(不再安装目录执行可能报错) ./bin/mysqld_safe –skip-grant-tables –skip-networking &
4. mysql -uroot -p (回车、回车)进入到mysql命令行
5. update mysql.user set Password=password(‘123′) where user=’root’ and Host = ‘localhost’;
flush privileges;
(如果没有 Password字段,则把Password换成authentication_string)
6. 退出mysql命令行
7. service mysql restart
8. service mysql stop
9. service mysql start
(7 ~ 9步保证mysql正常启动)
10. mysql -uroot -p123
11. set password for ‘root’@’localhost’=password(‘123’);
(保证新密码完全生效)

发表在 mysql | 留下评论

go, 监控git是否更新, 并且自动构建

使用方式 ./command -p [git 目录] -s [每s秒查看一次git版本变化,默认60]

package main

import (
	"bytes"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"
)

var gitVersion = ""
var wg sync.WaitGroup
var path string
var sleepTime int

func ExecCommand(command string, params []string, dir string) string{
	cmd := exec.Command(command, params...)

	if dir != "" {
		cmd.Dir = dir
	}

	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Fatal("error   ", err)
	}

	return out.String()
}

func InitArgs() {
	flag.StringVar(&path, "p", "", "git路径")
	flag.IntVar(&sleepTime, "s", 60, "每多少秒检查一次git version")
	flag.Parse()
}

func dirExists(p string) bool{
	fi, err := os.Stat(p)
	if err != nil {
		return false
	}

	return fi.IsDir()
}

func getVerson(vChan chan <- string){
	for {
		ExecCommand("git", []string{"pull"}, path)
		out := ExecCommand("git", []string{ "log", "-1"}, path)
		lines := strings.Split(out, "\n")
		vChan <- strings.Split(lines[0], " ")[1]
		time.Sleep( time.Second * time.Duration(sleepTime) )
	}

	wg.Done()
}


func do(vChan <- chan string){
	for{
		_v := <- vChan
		if _v != gitVersion {
			fmt.Println("获取到新版本,开始更新...")
			gitVersion = _v

			res2 := ExecCommand("yarn", []string{}, path)
			res3 := ExecCommand("yarn", []string{"build"}, path)
			fmt.Println("已经构建完成:", res2, res3)

			fmt.Println("当前版本为:"+gitVersion)
		}
	}

}

func main() {
	InitArgs()

	if path == "" {
		log.Fatal("路径参数不能为空")
	}

	if !dirExists(path)  {
		log.Fatal("目录不存在:" + path)
	}

	fmt.Println("开始监控目录:",path)

	vChan := make(chan string, 1)

	wg.Add(2)
	go getVerson(vChan)
	go do(vChan)
	wg.Wait()
}
发表在 go | 标签为 | 留下评论

yii2 实现灵活的限流方式

yii2自带的限流方式使用做在某路径下的,但是想要实现在某种规则下才限流,该怎么做呢?
(如果对限流的逻辑不明白,请搜索“漏斗算法”)
1. 对自带的限流工具做一下简单的修改,个人觉得,自带的限流算法有个小小的问题,在超限调用接口的时候,不应当记录次数
(library是我个人创建的放公共包的目录)

<?php
namespace app\library;

use yii\web\TooManyRequestsHttpException;

class RateLimiter extends \yii\filters\RateLimiter
{
    public function checkRateLimit($user, $request, $response, $action)
    {
        list($limit, $window) = $user->getRateLimit($request, $action);
        list($allowance, $timestamp) = $user->loadAllowance($request, $action);

        $current = time();

        $allowance += (int) (($current - $timestamp) * $limit / $window);
        if ($allowance > $limit) {
            $allowance = $limit;
        }

        if ($allowance < 1) {
            //只注释了下面这一行代码,如果次数不足,则不更新时间
//            $user->saveAllowance($request, $action, 0, $current);
            $this->addRateLimitHeaders($response, $limit, 0, $window);
            throw new TooManyRequestsHttpException($this->errorMessage);
        }

        $user->saveAllowance($request, $action, $allowance - 1, $current);
        $this->addRateLimitHeaders($response, $limit, $allowance - 1, (int) (($limit - $allowance + 1) * $window / $limit));
    }


}

2. 创建限流的对象(代码仅仅做个抛砖引玉,可以根据个人需要优化哈,也可以搞一个独立的限流工具)

        $this->rateLimiter = Yii::createObject([
            'class' => \app\library\RateLimiter::class,
            'errorMessage' => '您已经提交过,请勿重复提交',//超限提示
            'enableRateLimitHeaders' => false,//是否把限流的详情展示到header中
            'user' => new class implements RateLimitInterface {//实现限流对象
                public function __construct()
                {//限流使用session,这里可以改成你想要的,也可以封装成store工厂
                 //提示,使用session实现的限流算法可能是不安全的(达不到限流的目的),请自行调整
                    $session = Yii::$app->getSession();
                    $session->open();
                    $this->store = $session;
                }

                public function getRateLimit($request, $action)
                {
                    if ($action->id == 'xxxx') {//这里可以针对不同的action做到不同的限流方式
                        return [1, 300];//300秒之内只能执行一次
                    }
                    return [2, 1];//1秒之内只能执行2次
                }

                public function loadAllowance($request, $action)
                {
                    return $this->get($action->id);//获取上一次的限流情况
                }

                public function saveAllowance($request, $action, $allowance, $timestamp)
                {
                    $this->save($action->id, $allowance, $timestamp);//保存本次的限流情况
                }

                public function get($id)//返回的是[上一次的使用后剩余次数,上一次的更新时间]
                {
                    return [$this->store[$id . '-allowance'], $this->store[$id . '-timestamp']];
                }

                public function save($id, $allowance, $timestamp)
                {//保存上本次的限流详情
                    $this->store[$id . '-allowance'] = $allowance;//剩余次数
                    $this->store[$id . '-timestamp'] = $timestamp;//调用时间
                }
            }
        ]);

3. 使用

//在你想要限流的地方加入下面的代码
$this->rateLimiter->checkRateLimit($this->rateLimiter->user, Yii::$app->request, Yii::$app->response, $this->action);
发表在 php, yii2 | 标签为 , , | 留下评论