1151 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1151 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Bwsaas
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Author: buwangyun <hnlg666@163.com>
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
// | Date: 2020-9-28 10:55:00
 | 
						||
// +----------------------------------------------------------------------
 | 
						||
 | 
						||
namespace bw;
 | 
						||
 | 
						||
 | 
						||
use addons\epay\library\Service;
 | 
						||
use app\admin\model\Miniqrcode;
 | 
						||
use app\admin\model\Xftts;
 | 
						||
use app\common\library\Upload;
 | 
						||
use think\Cache;
 | 
						||
use think\File;
 | 
						||
use app\common\model\Attachment;
 | 
						||
use addons\xftts\library\Tts;
 | 
						||
use addons\xftts\library\WebSocket\Client;
 | 
						||
use addons\xftts\library\WebSocket\Exception;
 | 
						||
use think\Response;
 | 
						||
use traits\CacheTrait;
 | 
						||
 | 
						||
/** 商城工具类
 | 
						||
 * Class Common
 | 
						||
 * @package app\bwmall\model
 | 
						||
 */
 | 
						||
class Common
 | 
						||
{
 | 
						||
 | 
						||
    use CacheTrait;
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取图片完整连接
 | 
						||
     */
 | 
						||
    public static function getImagesFullUrl($value = '')
 | 
						||
    {
 | 
						||
        if (stripos($value, 'http') === 0 || $value === '' || stripos($value, 'data:image') === 0) {
 | 
						||
            return $value;
 | 
						||
        } else {
 | 
						||
            $upload = \think\Config::get('upload');
 | 
						||
            if (!empty($upload['cdnurl'])) {
 | 
						||
                return  $upload['cdnurl'] . $value;
 | 
						||
            } else {
 | 
						||
                return self::getHttpLocation() . $value;
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获取当前地址
 | 
						||
     * @return string
 | 
						||
     */
 | 
						||
    public static function getHttpLocation() {
 | 
						||
        $http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
 | 
						||
        return $http_type . $_SERVER['HTTP_HOST'];
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 时间戳 - 精确到毫秒
 | 
						||
     * @return float
 | 
						||
     */
 | 
						||
    public static function getMillisecond() {
 | 
						||
        list($t1, $t2) = explode(' ', microtime());
 | 
						||
        return (float)sprintf('%.0f',(floatval($t1)+floatval($t2))*1000);
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 判断文件是否存在,支持本地及远程文件
 | 
						||
     * @param  String  $file 文件路径
 | 
						||
     * @return Boolean
 | 
						||
     */
 | 
						||
    public static function check_file_exists($file){
 | 
						||
// 屏蔽域名不存在等访问问题的警告
 | 
						||
        error_reporting(E_ALL ^ (E_WARNING|E_NOTICE));
 | 
						||
        // 远程文件
 | 
						||
        if(strtolower(substr($file, 0, 4))=='http'){
 | 
						||
 | 
						||
            $header = get_headers($file, true);
 | 
						||
 | 
						||
            return isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'));
 | 
						||
 | 
						||
            // 本地文件
 | 
						||
        }else{
 | 
						||
            return file_exists($file);
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    /**将目录下的文件保存到框架文件系统
 | 
						||
     * @param $file_path 原文件全路径(物理=>[绝对|相对])
 | 
						||
     * @param $file_name 源文件名
 | 
						||
     * @return \app\common\model\attachment|\think\Model
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     */
 | 
						||
    public static function setFastAdminFile($file_path,$file_name){
 | 
						||
        //保存到第三方文件
 | 
						||
        //name 原文件名 type 文件类型  tmp_name 原目录 error =0 size 文件大小
 | 
						||
        $fi = new \finfo(FILEINFO_MIME_TYPE);
 | 
						||
        $mime_type = $fi->file($file_path);
 | 
						||
        $temp = [
 | 
						||
            'name'=> $file_name,
 | 
						||
            'size'=> filesize($file_path),
 | 
						||
            'tmp_name'=>$file_path,
 | 
						||
            'error'=>0,
 | 
						||
            'type'=>$mime_type
 | 
						||
        ];
 | 
						||
        $file = (new File($file_path))->isTest(true)->setUploadInfo($temp);
 | 
						||
 | 
						||
        $category = $_POST["category"] ?? 'code';
 | 
						||
        $_POST["category"] = 'code';
 | 
						||
        $upload = new Upload($file,$category);
 | 
						||
        $res = $upload->upload();
 | 
						||
        $_POST["category"] = $category;
 | 
						||
        return $res;
 | 
						||
    }
 | 
						||
 | 
						||
    public $temp_url = 'uploads/qrcode';//临时,目录
 | 
						||
 | 
						||
    public $path = '';
 | 
						||
 | 
						||
    /**将目录下的文件保存到框架文件系统
 | 
						||
     * @param $file_path 原文件全路径(物理=>[绝对|相对])
 | 
						||
     * @param $file_name 源文件名
 | 
						||
     * @return \app\common\model\attachment|\think\Model
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     */
 | 
						||
    public  function setFastAdminFileByUrl($url,$fileName){
 | 
						||
        ob_start();
 | 
						||
        readfile($url);
 | 
						||
        $res = ob_get_contents();//文件二进制流
 | 
						||
        ob_end_clean();
 | 
						||
        $outfile = $this->temp_url . '/';  //本地缓存地址
 | 
						||
        if (!file_exists('./' . $outfile . $this->path)) mkdir('./' . $outfile . $this->path, 0777, true);
 | 
						||
        $filepath = './' . $outfile . $this->path . '/' . $fileName;
 | 
						||
 | 
						||
        file_put_contents($filepath, $res);
 | 
						||
        //保存到fastadmin框架
 | 
						||
        $attachment = Common::setFastAdminFile($filepath, $fileName);
 | 
						||
        // TODO: 生成后删除源文件
 | 
						||
        @unlink($filepath);
 | 
						||
        return $attachment;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 字符串命名风格转换
 | 
						||
     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
 | 
						||
     * @access public
 | 
						||
     * @param string $name 字符串
 | 
						||
     * @param integer $type 转换类型
 | 
						||
     * @param bool $ucfirst 首字母是否大写(驼峰规则)
 | 
						||
     * @return string
 | 
						||
     */
 | 
						||
    public static function parseName($name, $type = 0, $ucfirst = true)
 | 
						||
    {
 | 
						||
        if ($type) {
 | 
						||
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
 | 
						||
                return strtoupper($match[1]);
 | 
						||
            }, $name);
 | 
						||
            return $ucfirst ? ucfirst($name) : lcfirst($name);
 | 
						||
        }
 | 
						||
 | 
						||
        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    public static function toreplace($str, $find)//$str是你需要操作的字符串,$find是你指定的字符串
 | 
						||
    {
 | 
						||
        if (strpos($str, $find) === false) return false;
 | 
						||
 | 
						||
        $a = explode($find, $str);
 | 
						||
        return $a[0] . $find;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 无限级归类
 | 
						||
     *
 | 
						||
     * @param array $list 归类的数组
 | 
						||
     * @param string $id 父级ID
 | 
						||
     * @param string $pid 父级PID
 | 
						||
     * @param string $child key
 | 
						||
     * @param string $root 顶级
 | 
						||
     *
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    public static function tree(array $list, string $pk = 'id', string $pid = 'pid', string $child = 'child', int $root = 0): array
 | 
						||
    {
 | 
						||
        $tree = [];  //最终得到的树形数据
 | 
						||
 | 
						||
        if (is_array($list)) {
 | 
						||
            $refer = [];
 | 
						||
 | 
						||
            //基于数组的指针(引用) 并 同步改变数组
 | 
						||
            foreach ($list as $key => $val) {
 | 
						||
                $list[$key][$child] = [];
 | 
						||
                $refer[$val[$pk]] = &$list[$key];  //以主键为下标,值为列表数据的引用
 | 
						||
            }
 | 
						||
 | 
						||
 | 
						||
            foreach ($list as $key => $val) {
 | 
						||
                //是否存在parent
 | 
						||
                $parentId = isset($val[$pid]) ? $val[$pid] : $root;  //取出父级id
 | 
						||
 | 
						||
                //如果是根节点,直接放入根层级(实际放入的是一个组装好的树分支)
 | 
						||
                if ($root == $parentId) {
 | 
						||
                    $tree[$val[$pk]] = &$list[$key];
 | 
						||
                } else {  //如果是其他节点,通过引用传入树分支
 | 
						||
 | 
						||
 | 
						||
                    if (isset($refer[$parentId])) {
 | 
						||
 | 
						||
                        $refer[$parentId][$child][] = &$list[$key];  //1  3  4
 | 
						||
 | 
						||
                    }
 | 
						||
                }
 | 
						||
 | 
						||
            }
 | 
						||
            // die;
 | 
						||
        }
 | 
						||
        //var_dump(array_values($tree));
 | 
						||
        return array_values($tree);
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**得到关系树中的全部用户id(递归)
 | 
						||
     * @param array $list tree数据来源
 | 
						||
     * @param string $name 需要获取的数据名
 | 
						||
     * @param string $child 树子节点名
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    public static function getTreeItem(array $list, string $name = 'pid', string $child = 'child')
 | 
						||
    {
 | 
						||
        $item = [];
 | 
						||
        //遍历关系
 | 
						||
        foreach ($list as &$value) {
 | 
						||
            $item[] = $value[$name];
 | 
						||
            if ($value[$child]) $item = array_merge($item, self::getTreeItem($value[$child], $name, $child));//合并两个数组
 | 
						||
        }
 | 
						||
        return $item;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 排列组合
 | 
						||
     *
 | 
						||
     * @param array $input 排列的数组
 | 
						||
     *
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static public function arrayArrange(array $input): array
 | 
						||
    {
 | 
						||
        $temp = [];
 | 
						||
        $result = array_shift($input);  //挤出数组第一个元素
 | 
						||
        while ($item = array_shift($input))  //循环每次挤出数组的一个元素,直到数组元素全部挤出
 | 
						||
        {
 | 
						||
            $temp = $result;
 | 
						||
            $result = [];
 | 
						||
            foreach ($temp as $v) {
 | 
						||
                foreach ($item as $val) {
 | 
						||
                    $result[] = array_merge_recursive($v, $val);
 | 
						||
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        return $result;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 富文本base64解码
 | 
						||
     */
 | 
						||
    public static function r_text_decode($text)
 | 
						||
    {
 | 
						||
        $text = base64_decode($text);
 | 
						||
        return htmlspecialchars_decode(urldecode($text));
 | 
						||
    }
 | 
						||
 | 
						||
    /**得到小程序太阳码
 | 
						||
     * @param $path
 | 
						||
     * @param string $scene
 | 
						||
     * @param bool $cache
 | 
						||
     * @return attachment|array|false|\PDOStatement|string|\think\Model
 | 
						||
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
 | 
						||
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     * @throws \think\db\exception\DataNotFoundException
 | 
						||
     * @throws \think\db\exception\ModelNotFoundException
 | 
						||
     * @throws \think\exception\DbException
 | 
						||
     */
 | 
						||
    public static function getMiniappCode($scene, $page, $width = 430, $auto_color = false, $line_color = ["r" => "0", "g" => "0", "b" => "0"], $is_hyaline = true, $outType = null, $check_path = true, $env_version = 'release',$cache = true){
 | 
						||
        // 写入到文件
 | 
						||
        $file_name = md5( $page.$scene.'MiniCode') . '.png';
 | 
						||
        if($cache){
 | 
						||
            $file_info = Attachment::where('filename',$file_name)->find();
 | 
						||
            if($file_info){
 | 
						||
                $file_info['full_url'] = cdnurl($file_info['url'],true);
 | 
						||
                return $file_info;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        if (empty($page)) {
 | 
						||
            $page = 'pages/index/index';
 | 
						||
        }
 | 
						||
//        $wechat = new \addons\shopro\library\Wechat('wxMiniProgram');
 | 
						||
//        $content = $wechat->getApp()->app_code->getUnlimit($scene, [
 | 
						||
//            'page' => $path,
 | 
						||
//            'is_hyaline' => true,
 | 
						||
//        ]);
 | 
						||
 | 
						||
                    // 实例对应的接口对象
 | 
						||
          $scheme = new \WeMini\Qrcode(Service::wechatConfig());
 | 
						||
          $content =  $scheme->createMiniScene($scene, $page, $width, $auto_color, $line_color ? ( is_array($line_color) ? $line_color : json_decode($line_color, true))  : ["r" => "0", "g" => "0", "b" => "0"], $is_hyaline, null, $check_path, $env_version);
 | 
						||
 | 
						||
 | 
						||
 | 
						||
        if ($content instanceof \EasyWeChat\Kernel\Http\StreamResponse || is_string($content)) {
 | 
						||
            $filePath = ROOT_PATH . 'public/uploads/qrcode/' . $file_name;
 | 
						||
            if(is_string($content)){
 | 
						||
                file_put_contents(ROOT_PATH . 'public/uploads/qrcode/'.$file_name, $content);
 | 
						||
            }else{
 | 
						||
                $content->saveAs(ROOT_PATH . 'public/uploads/qrcode', $file_name);
 | 
						||
            }
 | 
						||
 | 
						||
            //保存到fastadmin框架
 | 
						||
            $attachment = Common::setFastAdminFile($filePath, $file_name);
 | 
						||
            $attachment['full_url'] = cdnurl($attachment['url'],true);
 | 
						||
            // TODO: 生成后删除源文件
 | 
						||
            @unlink($filePath);
 | 
						||
            return $attachment;
 | 
						||
 | 
						||
        } else {
 | 
						||
            var_dump(gettype($content));
 | 
						||
            // 小程序码获取失败
 | 
						||
            $msg = isset($content['errcode']) ? $content['errcode'] : '-';
 | 
						||
            $msg .= isset($content['errmsg']) ? $content['errmsg'] : '';
 | 
						||
            \think\Log::write('wxacode-error' . $msg);
 | 
						||
            throw new \Exception($msg);
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**得到小程序二维码(调用次数上限为10万)
 | 
						||
     * @param $path
 | 
						||
     * @param bool $cache
 | 
						||
     * @return attachment|array|false|\PDOStatement|string|\think\Model
 | 
						||
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
 | 
						||
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     * @throws \think\db\exception\DataNotFoundException
 | 
						||
     * @throws \think\db\exception\ModelNotFoundException
 | 
						||
     * @throws \think\exception\DbException
 | 
						||
     */
 | 
						||
    public static function getMiniappQrCode($path,$getContent=false,$cache = true){
 | 
						||
        // 写入到文件
 | 
						||
        $file_name = md5( $path.'MiniQrCode') . '.png';
 | 
						||
        if($cache){
 | 
						||
            $file_info = Attachment::where('filename',$file_name)->find();
 | 
						||
            if($file_info){
 | 
						||
                $file_info['full_url'] = cdnurl($file_info['url'],true);
 | 
						||
                $file_info['common_content'] = null;
 | 
						||
                if($getContent){
 | 
						||
                    //得到返解析的内容
 | 
						||
                    $qrcode = new \Zxing\QrReader( $file_info['full_url']);   //绝对路径
 | 
						||
                    $file_info['common_content'] = $qrcode->text(); //返回二维码的内容
 | 
						||
                }
 | 
						||
                return $file_info;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        if (empty($path)) {
 | 
						||
            $path = 'pages/index/index';
 | 
						||
        }
 | 
						||
//        $wechat = new \addons\shopro\library\Wechat('wxMiniProgram');
 | 
						||
//        $content = $wechat->getApp()->app_code->getQrCode($path);
 | 
						||
 | 
						||
        $qrcode = new \WeMini\Qrcode(Service::wechatConfig());
 | 
						||
        $content = $qrcode->createDefault($path, $width = 430, $outType = null);
 | 
						||
//        if ($content instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
 | 
						||
            $filePath = ROOT_PATH . 'public/uploads/qrcode/' . $file_name;
 | 
						||
            file_put_contents($filePath, $content);
 | 
						||
            //保存到fastadmin框架
 | 
						||
            $attachment = Common::setFastAdminFile($filePath, $file_name);
 | 
						||
            $attachment['full_url'] = cdnurl($attachment['url'],true);
 | 
						||
            $attachment['common_content'] = null;
 | 
						||
            if($getContent) {
 | 
						||
                //得到返解析的内容
 | 
						||
                $qrcode = new \Zxing\QrReader($attachment['full_url']);   //绝对路径
 | 
						||
                $attachment['common_content'] = $qrcode->text(); //返回二维码的内容
 | 
						||
            }
 | 
						||
            // TODO: 生成后删除源文件
 | 
						||
            @unlink($filePath);
 | 
						||
            return $attachment;
 | 
						||
 | 
						||
//        } else {
 | 
						||
////            var_dump(gettype($content));
 | 
						||
//            // 小程序码获取失败
 | 
						||
//            $msg = isset($content['errcode']) ? $content['errcode'] : '-';
 | 
						||
//            $msg .= isset($content['errmsg']) ? $content['errmsg'] : '';
 | 
						||
//            \think\Log::write('wxacode-error' . $msg);
 | 
						||
//            throw new \Exception($msg);
 | 
						||
//        }
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**生成二维码
 | 
						||
     * @param $params
 | 
						||
     * @throws \Endroid\QrCode\Exception\InvalidPathException
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     */
 | 
						||
    public static function getQrcode($params,$stream = false,$cache = true,$response = false){
 | 
						||
        if($response){
 | 
						||
            return self::getQRcodeResponse($params);
 | 
						||
        }
 | 
						||
        $qrCode = \addons\qrcode\library\Service::qrcode($params);
 | 
						||
        if($stream)return $qrCode;
 | 
						||
        // 写入到文件
 | 
						||
        $file_name = md5(implode('', $params).'Qrcode') . '.png';
 | 
						||
        if($cache){
 | 
						||
            $file_info = Attachment::where('filename',$file_name)->find();
 | 
						||
            if($file_info){
 | 
						||
                $file_info['full_url'] = cdnurl($file_info['url'],true);
 | 
						||
                return $file_info;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        $filePath = ROOT_PATH . 'public/uploads/qrcode/' . $file_name;
 | 
						||
        $qrCode->writeFile($filePath);
 | 
						||
        //保存到fastadmin框架
 | 
						||
        $attachment = Common::setFastAdminFile($filePath, $file_name);
 | 
						||
        // TODO: 生成后删除源文件
 | 
						||
        @unlink($filePath);
 | 
						||
        $attachment['full_url'] = cdnurl($attachment['url'],true);
 | 
						||
        return $attachment;
 | 
						||
    }
 | 
						||
 | 
						||
    /**生成一维码
 | 
						||
     * @param $params
 | 
						||
     * @throws \Endroid\QrCode\Exception\InvalidPathException
 | 
						||
     * @throws \app\common\exception\UploadException
 | 
						||
     */
 | 
						||
    public static function getBarcode($params,$stream = false,$cache = true){
 | 
						||
        $qrCode = \addons\barcode\library\Service::barcode($params);
 | 
						||
        if($stream)return $qrCode;
 | 
						||
        // 写入到文件
 | 
						||
        $file_name = md5(implode('', $params).'Barcode') . '.png';
 | 
						||
        if($cache){
 | 
						||
            $file_info = Attachment::where('filename',$file_name)->find();
 | 
						||
            if($file_info){
 | 
						||
                $file_info['full_url'] = cdnurl($file_info['url'],true);
 | 
						||
                return $file_info;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        $outfile =  'uploads/barcode/';
 | 
						||
        if (!file_exists('./' . $outfile )) mkdir('./' . $outfile, 0777, true);
 | 
						||
        $filePath = './' . $outfile  . '/' . $file_name;
 | 
						||
        file_put_contents($filePath, $qrCode);
 | 
						||
        //保存到fastadmin框架
 | 
						||
        $attachment = Common::setFastAdminFile($filePath, $file_name);
 | 
						||
        // TODO: 生成后删除源文件
 | 
						||
        @unlink($filePath);
 | 
						||
        $attachment['full_url'] = cdnurl($attachment['url'],true);
 | 
						||
        return $attachment;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**执行字符串模板替换
 | 
						||
     * @param $template
 | 
						||
     * @param array $params
 | 
						||
     * @param string $expression
 | 
						||
     */
 | 
						||
    public static function parsePrintTemplateString($template,$params=[],$expression = '{{KEYWORD}}'){
 | 
						||
        foreach ($params as $name => $value)
 | 
						||
        {
 | 
						||
            //得到需要替换的字符串
 | 
						||
            $replace_name = str_replace("KEYWORD",$name,$expression);
 | 
						||
            //执行模板字符串替换
 | 
						||
            $template = str_replace($replace_name,$value,$template);
 | 
						||
        }
 | 
						||
        return $template;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 返回多层栏目
 | 
						||
     * @param $data 操作的数组
 | 
						||
     * @param int $pid 一级PID的值
 | 
						||
     * @param string $html 栏目名称前缀
 | 
						||
     * @param string $fieldPri 唯一键名,如果是表则是表的主键
 | 
						||
     * @param string $fieldPid 父ID键名
 | 
						||
     * @param int $level 不需要传参数(执行时调用)
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static public function channelLevel($data, $pid = 0, $html = " ", $fieldPri = 'cid', $fieldPid = 'pid', $level = 1)
 | 
						||
    {
 | 
						||
        if (empty($data)) {
 | 
						||
            return array();
 | 
						||
        }
 | 
						||
        $arr = array();
 | 
						||
        foreach ($data as $v) {
 | 
						||
            if ($v[$fieldPid] == $pid) {
 | 
						||
                $arr[$v[$fieldPri]] = $v;
 | 
						||
                $arr[$v[$fieldPri]]['_level'] = $level;
 | 
						||
                $arr[$v[$fieldPri]]['_html'] = str_repeat($html, $level - 1);
 | 
						||
                $arr[$v[$fieldPri]]["_data"] = self::channelLevel($data, $v[$fieldPri], $html, $fieldPri, $fieldPid, $level + 1);
 | 
						||
            }
 | 
						||
        }
 | 
						||
        return $arr;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获得所有子栏目
 | 
						||
     * @param $data 栏目数据
 | 
						||
     * @param int $pid 操作的栏目
 | 
						||
     * @param string $html 栏目名前字符
 | 
						||
     * @param string $fieldPri 表主键
 | 
						||
     * @param string $fieldPid 父id
 | 
						||
     * @param int $level 等级
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static public function channelList($data, $pid = 0, $html = " ", $fieldPri = 'cid', $fieldPid = 'pid', $level = 1)
 | 
						||
    {
 | 
						||
        $data = self::_channelList($data, $pid, $html, $fieldPri, $fieldPid, $level);
 | 
						||
        if (empty($data))
 | 
						||
            return $data;
 | 
						||
        foreach ($data as $n => $m) {
 | 
						||
            if ($m['_level'] == 1)
 | 
						||
                continue;
 | 
						||
            $data[$n]['_first'] = false;
 | 
						||
            $data[$n]['_end'] = false;
 | 
						||
            if (!isset($data[$n - 1]) || $data[$n - 1]['_level'] != $m['_level']) {
 | 
						||
                $data[$n]['_first'] = true;
 | 
						||
            }
 | 
						||
            if (isset($data[$n + 1]) && $data[$n]['_level'] > $data[$n + 1]['_level']) {
 | 
						||
                $data[$n]['_end'] = true;
 | 
						||
            }
 | 
						||
        }
 | 
						||
        //更新key为栏目主键
 | 
						||
        $category = array();
 | 
						||
        foreach ($data as $d) {
 | 
						||
            $category[$d[$fieldPri]] = $d;
 | 
						||
        }
 | 
						||
        return $category;
 | 
						||
    }
 | 
						||
 | 
						||
    //只供channelList方法使用
 | 
						||
    static private function _channelList($data, $pid = 0, $html = " ", $fieldPri = 'cid', $fieldPid = 'pid', $level = 1)
 | 
						||
    {
 | 
						||
        if (empty($data))
 | 
						||
            return array();
 | 
						||
        $arr = array();
 | 
						||
        foreach ($data as $v) {
 | 
						||
            $id = $v[$fieldPri];
 | 
						||
            if ($v[$fieldPid] == $pid) {
 | 
						||
                $v['_level'] = $level;
 | 
						||
                $v['_html'] = str_repeat($html, $level - 1);
 | 
						||
                array_push($arr, $v);
 | 
						||
                $tmp = self::_channelList($data, $id, $html, $fieldPri, $fieldPid, $level + 1);
 | 
						||
                $arr = array_merge($arr, $tmp);
 | 
						||
            }
 | 
						||
        }
 | 
						||
        return $arr;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获得展示状态的树 数据 例如  ├─ xxx
 | 
						||
     * @param $data 数据
 | 
						||
     * @param $title 字段名
 | 
						||
     * @param string $fieldPri 主键id
 | 
						||
     * @param string $fieldPid 父id
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static public function exhibition_tree($data, $title, $fieldPri = 'cid', $fieldPid = 'pid')
 | 
						||
    {
 | 
						||
        if (!is_array($data) || empty($data))
 | 
						||
            return array();
 | 
						||
        $arr = self::channelList($data, 0, '', $fieldPri, $fieldPid);
 | 
						||
        foreach ($arr as $k => $v) {
 | 
						||
            $str = "";
 | 
						||
            if ($v['_level'] > 2) {
 | 
						||
                for ($i = 1; $i < $v['_level'] - 1; $i++) {
 | 
						||
                    $str .= " │";
 | 
						||
                }
 | 
						||
            }
 | 
						||
            if ($v['_level'] != 1) {
 | 
						||
                $t = $title ? $v[$title] : "";
 | 
						||
                if (isset($arr[$k + 1]) && $arr[$k + 1]['_level'] >= $arr[$k]['_level']) {
 | 
						||
                    $arr[$k]['_name'] = $str . " ├─ " . $v['_html'] . $t;
 | 
						||
                } else {
 | 
						||
                    $arr[$k]['_name'] = $str . " └─ " . $v['_html'] . $t;
 | 
						||
                }
 | 
						||
            } else {
 | 
						||
                $arr[$k]['_name'] = $v[$title];
 | 
						||
            }
 | 
						||
        }
 | 
						||
        //设置主键为$fieldPri
 | 
						||
        $data = array();
 | 
						||
        foreach ($arr as $d) {
 | 
						||
            $data[$d[$fieldPri]] = $d;
 | 
						||
        }
 | 
						||
        return $data;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 获得所有父级栏目
 | 
						||
     * @param $data 栏目数据
 | 
						||
     * @param $sid 子栏目
 | 
						||
     * @param string $fieldPri 唯一键名,如果是表则是表的主键
 | 
						||
     * @param string $fieldPid 父ID键名
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static public function parentChannel($data, $sid, $fieldPri = 'cid', $fieldPid = 'pid')
 | 
						||
    {
 | 
						||
        if (empty($data)) {
 | 
						||
            return $data;
 | 
						||
        } else {
 | 
						||
            $arr = array();
 | 
						||
            foreach ($data as $v) {
 | 
						||
                if ($v[$fieldPri] == $sid) {
 | 
						||
                    $arr[] = $v;
 | 
						||
                    $_n = self::parentChannel($data, $v[$fieldPid], $fieldPri, $fieldPid);
 | 
						||
                    if (!empty($_n)) {
 | 
						||
                        $arr = array_merge($arr, $_n);
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            }
 | 
						||
            return $arr;
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 判断$s_cid是否是$d_cid的子栏目
 | 
						||
     * @param $data 栏目数据
 | 
						||
     * @param $sid 子栏目id
 | 
						||
     * @param $pid 父栏目id
 | 
						||
     * @param string $fieldPri 主键
 | 
						||
     * @param string $fieldPid 父id字段
 | 
						||
     * @return bool
 | 
						||
     */
 | 
						||
    static function isChild($data, $sid, $pid, $fieldPri = 'cid', $fieldPid = 'pid')
 | 
						||
    {
 | 
						||
        $_data = self::channelList($data, $pid, '', $fieldPri, $fieldPid);
 | 
						||
        foreach ($_data as $c) {
 | 
						||
            //目标栏目为源栏目的子栏目
 | 
						||
            if ($c[$fieldPri] == $sid)
 | 
						||
                return true;
 | 
						||
        }
 | 
						||
        return false;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 检测是不否有子栏目
 | 
						||
     * @param $data 栏目数据
 | 
						||
     * @param $cid 要判断的栏目cid
 | 
						||
     * @param string $fieldPid 父id表字段名
 | 
						||
     * @return bool
 | 
						||
     */
 | 
						||
    static function hasChild($data, $cid, $fieldPid = 'pid')
 | 
						||
    {
 | 
						||
        foreach ($data as $d) {
 | 
						||
            if ($d[$fieldPid] == $cid) return true;
 | 
						||
        }
 | 
						||
        return false;
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 递归实现迪卡尔乘积
 | 
						||
     * @param $arr 操作的数组
 | 
						||
     * @param array $tmp
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    static function descarte($arr, $tmp = array())
 | 
						||
    {
 | 
						||
        static $n_arr = array();
 | 
						||
        foreach (array_shift($arr) as $v) {
 | 
						||
            $tmp[] = $v;
 | 
						||
            if ($arr) {
 | 
						||
                self::descarte($arr, $tmp);
 | 
						||
            } else {
 | 
						||
                $n_arr[] = $tmp;
 | 
						||
            }
 | 
						||
            array_pop($tmp);
 | 
						||
        }
 | 
						||
        return $n_arr;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**得到可复用的小程序二维码
 | 
						||
     * @param $link_id
 | 
						||
     * @param bool $cache
 | 
						||
     * @return Miniqrcode|array|false|\PDOStatement|string|\think\Model
 | 
						||
     * @throws \Exception
 | 
						||
     */
 | 
						||
    public static function getLinkMiniQrCode($link_id,$mini_path=null,$type = 'mock',$cache = true){
 | 
						||
        try{
 | 
						||
         $miniqrcode =  Miniqrcode::where("link_id",$link_id)->where("type",$type)->where("value","not null")->find();
 | 
						||
         if($miniqrcode && $miniqrcode['value']){
 | 
						||
             $miniqrcode['status'] = '2';
 | 
						||
             $miniqrcode->save();
 | 
						||
             return $miniqrcode;
 | 
						||
         }
 | 
						||
        //判断是否有空余小程序二维码
 | 
						||
         $miniqrcode  =  Miniqrcode::where("status","1")->where("value","not null")->where("value","<>","")->find();
 | 
						||
 | 
						||
        //如果有,则更新二维码为使用中,保存新的连接信息并返回
 | 
						||
        if($miniqrcode  && $miniqrcode['value']){
 | 
						||
            $miniqrcode['status'] = '2';
 | 
						||
            $miniqrcode['link_id'] = $link_id;
 | 
						||
            $miniqrcode['type'] = $type;
 | 
						||
            $miniqrcode->save();
 | 
						||
            return Miniqrcode::where('id',$miniqrcode['id'])->find();
 | 
						||
        }else{
 | 
						||
        //如果没有,则
 | 
						||
            $count = Miniqrcode::count();
 | 
						||
            $miniqrcode_limit = config("site.miniqrcode_limit");//练车小程序码url
 | 
						||
            $mock_minicode_url = $mini_path ?: config("site.mock_minicode_url");//练车码生成上限数量
 | 
						||
            //1,判断是否超出使用数量,如果超出则抛异常
 | 
						||
            if($count >= $miniqrcode_limit)throw new \Exception("已超出能生成的小程序二维码上限!");
 | 
						||
           //如果未超出
 | 
						||
               //1插入小程序二维码表
 | 
						||
            $miniqrcode     = new Miniqrcode;
 | 
						||
            $miniqrcode->link_id     = $link_id;
 | 
						||
            $miniqrcode['type'] = $type;
 | 
						||
            $miniqrcode['status'] = '2';
 | 
						||
            $miniqrcode->save();
 | 
						||
             // 获取自增ID
 | 
						||
            $miniqrcode_id = $miniqrcode->id;
 | 
						||
            //拼接二维码内容
 | 
						||
            $path = "{$mock_minicode_url}?link_id={$miniqrcode_id}";
 | 
						||
               //2生成小程序二维码,并逆解析内容:找到能解析出内容的二维码
 | 
						||
            while(true)
 | 
						||
            {
 | 
						||
                $attachment = Common::getMiniappQrCode($path,true,$cache);
 | 
						||
                $image = $attachment['url'];
 | 
						||
                $value = $attachment['common_content'];
 | 
						||
                //解析不出二维码,存入码表,循环下一张
 | 
						||
                if(!$value){
 | 
						||
                    //原码更新成失败码
 | 
						||
                    $error_miniqrcode  =  Miniqrcode::where("id",$miniqrcode_id)->find();
 | 
						||
                    $error_miniqrcode['link_id']     = 0;
 | 
						||
                    $error_miniqrcode['status'] = '1';
 | 
						||
                    $error_miniqrcode['type'] = 'other';
 | 
						||
                    $error_miniqrcode['path'] = $path;
 | 
						||
                    $error_miniqrcode['image'] = $image;
 | 
						||
                    $error_miniqrcode['value'] = $value;
 | 
						||
                    $error_miniqrcode->save();
 | 
						||
 | 
						||
                    //1插入新的小程序二维码
 | 
						||
                    $new_miniqrcode     = new Miniqrcode;
 | 
						||
                    $new_miniqrcode->link_id     = $link_id;
 | 
						||
                    $new_miniqrcode['type'] = $type;
 | 
						||
                    $new_miniqrcode['status'] = '2';
 | 
						||
                    $new_miniqrcode->save();
 | 
						||
 | 
						||
                    //赋值新自增ID
 | 
						||
                    $miniqrcode_id = $new_miniqrcode->id;
 | 
						||
                    //拼接新二维码内容
 | 
						||
                    $path = "{$mock_minicode_url}?link_id={$miniqrcode_id}";
 | 
						||
                    //开始新一轮循环,直到获取到能解析的二维码为止
 | 
						||
 | 
						||
                }else{
 | 
						||
                //解析得出二维码直接返回
 | 
						||
                    //3返回表信息
 | 
						||
                    $miniqrcode  =  Miniqrcode::where("id",$miniqrcode_id)->find();
 | 
						||
                    $miniqrcode['path'] = $path;
 | 
						||
                    $miniqrcode['image'] = $image;
 | 
						||
                    $miniqrcode['value'] = $value;
 | 
						||
                    $miniqrcode->save();
 | 
						||
                    break;
 | 
						||
                }
 | 
						||
            }
 | 
						||
 | 
						||
            return $miniqrcode;
 | 
						||
        }
 | 
						||
 | 
						||
        }catch (\Throwable $e){
 | 
						||
            throw new \Exception($e->getMessage());
 | 
						||
        }
 | 
						||
 | 
						||
    }
 | 
						||
 | 
						||
    /** 释放被占用的小程序二维码
 | 
						||
     * @param $link_id
 | 
						||
     * @param string $type
 | 
						||
     * @return bool
 | 
						||
     * @throws \think\db\exception\DataNotFoundException
 | 
						||
     * @throws \think\db\exception\ModelNotFoundException
 | 
						||
     * @throws \think\exception\DbException
 | 
						||
     */
 | 
						||
    public static function freeLinkMiniQrCode($link_id,$type = 'mock'){
 | 
						||
        $miniqrcode =  Miniqrcode::where("link_id",$link_id)->where("type",$type)->find();
 | 
						||
        if(!$miniqrcode)return true;
 | 
						||
        $miniqrcode['status']  = '1';
 | 
						||
        $miniqrcode['link_id']  = 0;
 | 
						||
        $miniqrcode['type']  = 'other';
 | 
						||
        $miniqrcode->save();
 | 
						||
        return true;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**射线与边是否有交点
 | 
						||
     * @param $poi
 | 
						||
     * @param $sPoi
 | 
						||
     * @param $ePoi
 | 
						||
     */
 | 
						||
    public static function isRayIntersectsSegment($poi, $sPoi, $ePoi){
 | 
						||
 | 
						||
 | 
						||
    // 排除 与射线平行、重合、是一个点的情况
 | 
						||
    if($sPoi[0] == $ePoi[0])return false;
 | 
						||
    //# 排除 线段在射线上边
 | 
						||
    if($sPoi[0] > $poi[0] && $ePoi[0] > $poi[0])return false;
 | 
						||
    // # 排除 线段在射线下边
 | 
						||
    if($sPoi[0] < $poi[0] && $ePoi[0] < $poi[0])return false;
 | 
						||
    // # 排除 交点为下端点
 | 
						||
    if($sPoi[0] == $poi[0] && $ePoi[0] > $poi[0])return false;
 | 
						||
    // # 排除 交点为上端点
 | 
						||
    if($ePoi[0] == $poi[0] && $sPoi[0] > $poi[0])return false;
 | 
						||
    //  # 排除 线段在射线左边
 | 
						||
    if($sPoi[1] < $poi[1] && $ePoi[1] < $poi[1])return false;
 | 
						||
    // # 求交,相似多边形性质
 | 
						||
        //规避除零异常
 | 
						||
        $meddle_v = ($sPoi[0] - $ePoi[0]) * ($sPoi[1] - $ePoi[1]);
 | 
						||
        if($meddle_v == 1)return false;
 | 
						||
       $xseg = $ePoi[1] + ($poi[0] - $ePoi[0]) / ($sPoi[0] - $ePoi[0]) * ($sPoi[1] - $ePoi[1]);
 | 
						||
    //  # 交点在射线起点的左侧
 | 
						||
    if($xseg < $poi[1])return false;
 | 
						||
    // #  排除上述情况之后真正有效的点
 | 
						||
    return true;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**地理围栏算法实现-单围栏(射线法:多变遍历)
 | 
						||
     * @param $poi
 | 
						||
     * @param $poly
 | 
						||
     * @return bool
 | 
						||
     */
 | 
						||
    public static function poiInPoly($poi, $poly,$count_check=true){
 | 
						||
       // # 交点个数
 | 
						||
        $count = 0 ;
 | 
						||
       // # 逐个二维数组进行判断
 | 
						||
        $last_index = count($poly)-1; //最后一个数组下标
 | 
						||
        foreach ($poly as $index=>$epoly)
 | 
						||
        {
 | 
						||
            //最后一个节点
 | 
						||
           if($last_index == $index){
 | 
						||
               $e_poi = $poly[$index];
 | 
						||
               $s_poi = $poly[0];
 | 
						||
           }else{
 | 
						||
               //正常节点
 | 
						||
               $e_poi = $poly[$index];
 | 
						||
               $s_poi = $poly[$index + 1];
 | 
						||
           }
 | 
						||
 | 
						||
            //刚好压在点上
 | 
						||
            if($poi[0] == $e_poi[0] && $poi[1] == $e_poi[1])return true;
 | 
						||
            if($poi[0] == $s_poi[0] && $poi[1] == $s_poi[1])return true;
 | 
						||
 | 
						||
            if(self::isRayIntersectsSegment($poi, $e_poi, $s_poi))$count += 1;
 | 
						||
        }
 | 
						||
        if($count_check)return $count % 2 != 0;
 | 
						||
        return $count;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**地理围栏算法实现-多围栏(射线法:多变遍历)
 | 
						||
     * @param $poi
 | 
						||
     * @param $polys
 | 
						||
     */
 | 
						||
    public static function poiInPolyArray($poi, $polys){
 | 
						||
        // # 交点个数
 | 
						||
        //记录下包含围栏数
 | 
						||
        $contain = [];
 | 
						||
        foreach ($polys as $poly)
 | 
						||
        {
 | 
						||
            if(is_string($poly))$poly = json_decode($poly, true);
 | 
						||
            if(self::poiInPoly($poi, $poly))$contain[] = $poly;
 | 
						||
        }
 | 
						||
        return $contain;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 得到时间标识(当天6点前算当天)
 | 
						||
     */
 | 
						||
    public static function getTimeflag($change=false,$tomorrow_s=""){
 | 
						||
        //得到当前时间
 | 
						||
        $time = time();
 | 
						||
        if($tomorrow_s){
 | 
						||
            $tomorrow_string = $tomorrow_s;
 | 
						||
        }else{
 | 
						||
            $tomorrow_string = config("site.queue_tomorrow_string") ?: "6:0:0";
 | 
						||
        }
 | 
						||
 | 
						||
        //得到当前六点的时间
 | 
						||
        $format_six_time = date("Y-m-d {$tomorrow_string}",$time);
 | 
						||
        //得到当前六点的时间戳
 | 
						||
        $time_six = strtotime($format_six_time);
 | 
						||
        //如果小于六点取前一天的六点
 | 
						||
        if($time < $time_six){
 | 
						||
            $time_six = strtotime("{$format_six_time} -1 day");
 | 
						||
        } //如果大于六点取今天的六点的时间戳
 | 
						||
        //如果是取范围则返回当天时间范围
 | 
						||
        if($change){
 | 
						||
            $format_time_end = date("Y-m-d H:i:s",$time_six);
 | 
						||
            $time_end = strtotime("{$format_time_end} +1 day") - 1; //59分秒时
 | 
						||
            return ['time_start'=>$time_six,'time_end'=>$time_end];
 | 
						||
        }
 | 
						||
        //不是取范围则直接返回
 | 
						||
        return $time_six;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 下划线转驼峰
 | 
						||
     * 思路:
 | 
						||
     * step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
 | 
						||
     * step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
 | 
						||
     */
 | 
						||
    public static function  camelize($uncamelized_words,$separator='_')
 | 
						||
    {
 | 
						||
        $uncamelized_words = $separator. str_replace($separator, " ", strtolower($uncamelized_words));
 | 
						||
        return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator );
 | 
						||
    }
 | 
						||
 | 
						||
    /**
 | 
						||
     * 驼峰命名转下划线命名
 | 
						||
     * 思路:
 | 
						||
     * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
 | 
						||
     */
 | 
						||
    public static function uncamelize($camelCaps,$separator='_')
 | 
						||
    {
 | 
						||
        return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
 | 
						||
    }
 | 
						||
 | 
						||
    /**讯飞语音合成
 | 
						||
     * @param $params
 | 
						||
     * @return array
 | 
						||
     */
 | 
						||
    public static function xftts($params)
 | 
						||
    {
 | 
						||
        if (!isset($params['text']) || empty($params['text'])) {
 | 
						||
            throw new\Exception('请输入合成文字');
 | 
						||
        }
 | 
						||
        $config = get_addon_config('xftts');
 | 
						||
 | 
						||
        $tts = new Tts();
 | 
						||
        $url = $tts->createAuthUrl($config['APIKey'], $config['APISecret'], time());
 | 
						||
        $client = new Client($url);
 | 
						||
        $message = $tts->createMsg(
 | 
						||
            $config['APPID'],
 | 
						||
            $params['text'].",,,,,,,,,,,,,,,,,,,,,,,,,,",
 | 
						||
            isset($params['ent']) ? $params['ent'] : $config['ent'],
 | 
						||
            isset($params['aue']) ? $params['aue'] : $config['aue'],
 | 
						||
            isset($params['auf']) ? $params['auf'] : $config['auf'],
 | 
						||
            isset($params['vcn']) ? $params['vcn'] : $config['vcn'],
 | 
						||
            isset($params['speed']) ? $params['speed'] : $config['speed'],
 | 
						||
            isset($params['volume']) ? $params['volume'] : $config['volume'],
 | 
						||
            isset($params['pitch']) ? $params['pitch'] : $config['pitch'],
 | 
						||
            isset($params['bgs']) ? $params['bgs'] : $config['bgs'],
 | 
						||
            isset($params['tte']) ? $params['tte'] : $config['tte'],
 | 
						||
            isset($params['reg']) ? $params['reg'] : $config['reg'],
 | 
						||
            isset($params['ram']) ? $params['ram'] : $config['ram'],
 | 
						||
            isset($params['rdn']) ? $params['rdn'] : $config['rdn']
 | 
						||
        );
 | 
						||
        $params = array_merge($config,$params);
 | 
						||
        try {
 | 
						||
            $client->send(json_encode($message, true));
 | 
						||
            $date = date('YmdHis', time());
 | 
						||
            $file_name = $date . ($params['aue'] === 'raw' ? '.pcm' : '.mp3');
 | 
						||
            $folder = '/uploads/tts/';
 | 
						||
            //判断文件夹是否存在
 | 
						||
            if (!is_dir(ROOT_PATH . 'public' . $folder)) {
 | 
						||
                @mkdir(ROOT_PATH . 'public' . $folder);
 | 
						||
            }
 | 
						||
            $audio_file = fopen(ROOT_PATH . 'public' . $folder . $file_name, 'ab');
 | 
						||
            $response = $client->receive();
 | 
						||
            $response = json_decode($response, true);
 | 
						||
            do {
 | 
						||
                if ($response['code']) {
 | 
						||
                    throw new \Exception($response['msg']);
 | 
						||
                }
 | 
						||
                //返回的音频需要进行base64解码
 | 
						||
                $audio = base64_decode($response['data']['audio']);
 | 
						||
                fwrite($audio_file, $audio);
 | 
						||
                // 第一次消息就收到结束标志的情况
 | 
						||
                if ($response['data']['status'] == 2) {
 | 
						||
                    fclose($audio_file);
 | 
						||
                    $client->close();
 | 
						||
                    $url = $folder . $file_name;
 | 
						||
                    $filesize = filesize(ROOT_PATH . 'public' . $url);
 | 
						||
                    //保存语音数据
 | 
						||
                    $params['speech_url'] = $url;
 | 
						||
                    $params['filesize'] = $filesize;
 | 
						||
                    $params['admin_id'] = 1;
 | 
						||
                    $result = (new Xftts())->allowField(true)->save($params);
 | 
						||
 | 
						||
 | 
						||
                    return [
 | 
						||
                        'code' => 0,
 | 
						||
                        'msg'  => '合成成功',
 | 
						||
                        'data' => [
 | 
						||
                            'url'      => $url,
 | 
						||
                            'full_url'      => cdnurl($url,true),
 | 
						||
                            'filesize' => $filesize,
 | 
						||
                            'object'      => Xftts::where('speech_url',$url)->find(),
 | 
						||
                        ]
 | 
						||
                    ];
 | 
						||
                }
 | 
						||
                //继续接收消息
 | 
						||
                $response = $client->receive();
 | 
						||
                $response = json_decode($response, true);
 | 
						||
            } while ($response['data']['status'] != 2);
 | 
						||
            fclose($audio_file);
 | 
						||
 | 
						||
            $url = $folder . $file_name;
 | 
						||
            $filesize = filesize(ROOT_PATH . 'public' . $url);
 | 
						||
            //保存语音数据
 | 
						||
            $params['speech_url'] = $url;
 | 
						||
            $params['filesize'] = $filesize;
 | 
						||
            $params['admin_id'] = 1;
 | 
						||
            $result = (new Xftts())->allowField(true)->save($params);
 | 
						||
 | 
						||
            return [
 | 
						||
                'code' => 0,
 | 
						||
                'msg'  => '合成成功',
 | 
						||
                'data' => [
 | 
						||
                    'url'      => $url,
 | 
						||
                    'full_url'      => cdnurl($url,true),
 | 
						||
                    'filesize' => $filesize,
 | 
						||
                    'object'      => Xftts::where('speech_url',$url)->find(),
 | 
						||
                ]
 | 
						||
            ];
 | 
						||
        } catch (Exception $e) {
 | 
						||
            throw new \Exception($e->getMessage(),1);
 | 
						||
//            return [
 | 
						||
//                'code' => 1,
 | 
						||
//                'msg'  => $e->getMessage(),
 | 
						||
//            ];
 | 
						||
        } finally {
 | 
						||
            $client->close();
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    /**得到音频信息  composer require james-heinrich/getid3
 | 
						||
     * @param $localRelativePath
 | 
						||
     * @return array
 | 
						||
     * @throws \getid3_exception
 | 
						||
     */
 | 
						||
    public static function getAudioInfo($fullPath){
 | 
						||
 | 
						||
        $remotefilename = $fullPath;
 | 
						||
        if ($fp_remote = fopen($remotefilename, 'rb')) {
 | 
						||
            $localtempfilename = tempnam('/tmp', 'getID3');
 | 
						||
            if ($fp_local = fopen($localtempfilename, 'wb')) {
 | 
						||
                while ($buffer = fread($fp_remote, 8192)) {
 | 
						||
                    fwrite($fp_local, $buffer);
 | 
						||
                }
 | 
						||
                fclose($fp_local);
 | 
						||
                // 初始化getID3引擎
 | 
						||
                $getID3 = new \getID3;
 | 
						||
                $ThisFileInfo = $getID3->analyze($localtempfilename);
 | 
						||
                // 删除临时文件
 | 
						||
                unlink($localtempfilename);
 | 
						||
            }
 | 
						||
            fclose($fp_remote);
 | 
						||
        }
 | 
						||
     return $ThisFileInfo;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
 | 
						||
    /**
 | 
						||
     * 得到语音播报内容(讯飞语音)
 | 
						||
     */
 | 
						||
    public static function getAudioContent($template_content,$params=[],$expression = '{{KEYWORD}}'){
 | 
						||
 | 
						||
        //解析获取文本内容
 | 
						||
        $content = self::parsePrintTemplateString($template_content,$params,$expression);
 | 
						||
        //判断是否存在当前文件
 | 
						||
        $xftts = Xftts::where("text",$content)->find();
 | 
						||
        //如果存在,则继续往下走
 | 
						||
        if(!$xftts){
 | 
						||
            //如果不存在,则生成文件继续往下走
 | 
						||
            $audio_info = self::xftts(['text'=> $content]);
 | 
						||
            $xftts = $audio_info['data']['object'];
 | 
						||
        }
 | 
						||
        $tts_key = "xftts_{$content}";
 | 
						||
        //判断是否存在当前播放秒数
 | 
						||
        $content_seconds = Cache::get($tts_key);
 | 
						||
        if(!$content_seconds){
 | 
						||
            //不存在当前秒数则解析当前秒数并存入缓存
 | 
						||
            $audio_info = self::getAudioInfo(cdnurl($xftts['speech_url'],true));
 | 
						||
//            var_dump($audio_info['playtime_seconds']);die;
 | 
						||
            Cache::set($tts_key,$audio_info['playtime_seconds'],3600);
 | 
						||
            $content_seconds = $audio_info['playtime_seconds'];
 | 
						||
        }
 | 
						||
        $xftts['content_seconds'] = $content_seconds;
 | 
						||
 | 
						||
        return $xftts;
 | 
						||
    }
 | 
						||
 | 
						||
    /**    生成并直接返回二维码http响应而不存储
 | 
						||
     * @param $text
 | 
						||
     * @param $label
 | 
						||
     * @param $params
 | 
						||
     * @return Response|\think\response\Json|\think\response\Jsonp|\think\response\Redirect|\think\response\View|\think\response\Xml
 | 
						||
     * @throws \Endroid\QrCode\Exception\InvalidPathException
 | 
						||
     */
 | 
						||
    public static function getQRcodeResponse($params=[]){
 | 
						||
 | 
						||
        $params = array_intersect_key($params, array_flip(['text', 'size', 'padding', 'errorlevel', 'foreground', 'background', 'logo', 'logosize', 'logopath', 'label', 'labelfontsize', 'labelalignment']));
 | 
						||
 | 
						||
//        $params['text'] = $text;
 | 
						||
//        $params['label'] = $label;
 | 
						||
 | 
						||
        $qrCode = \addons\qrcode\library\Service::qrcode($params);
 | 
						||
 | 
						||
        $mimetype = 'image/png';
 | 
						||
 | 
						||
        $response = Response::create()->header("Content-Type", $mimetype);
 | 
						||
 | 
						||
        // 直接显示二维码
 | 
						||
        header('Content-Type: ' . $qrCode->getContentType());
 | 
						||
 | 
						||
        // 设置缓存过期时间(例如,30天)
 | 
						||
        $expiresTime = strtotime('+30 days');
 | 
						||
        header('Expires: ' . gmdate('D, d M Y H:i:s', $expiresTime) . ' GMT');
 | 
						||
        header('Cache-Control: max-age=' . (30 * 24 * 60 * 60));
 | 
						||
 | 
						||
 | 
						||
        $response->content($qrCode->writeString());
 | 
						||
        return $response;
 | 
						||
    }
 | 
						||
 | 
						||
 | 
						||
} |