// +---------------------------------------------------------------------- // | 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 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); $upload = new Upload($file); return $upload->upload(); } 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); if ($content instanceof \EasyWeChat\Kernel\Http\StreamResponse) { $filePath = ROOT_PATH . 'public/uploads/qrcode/' . $file_name; $content->saveAs(ROOT_PATH . 'public/uploads/qrcode', $file_name); //保存到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 { // 小程序码获取失败 $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){ $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; } }