DiverseYouthNightSchool/extend/bw/Common.php

1105 lines
40 KiB
PHP
Raw Normal View History

2024-11-04 15:00:20 +08:00
<?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;
2024-11-04 15:00:20 +08:00
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){
2024-11-04 15:00:20 +08:00
// 写入到文件
$file_name = md5( $page.$scene.'MiniCode') . '.png';
2024-11-04 15:00:20 +08:00
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';
2024-11-04 15:00:20 +08:00
}
// $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)) {
2024-11-04 15:00:20 +08:00
$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);
}
2024-11-04 15:00:20 +08:00
//保存到fastadmin框架
$attachment = Common::setFastAdminFile($filePath, $file_name);
$attachment['full_url'] = cdnurl($attachment['url'],true);
// TODO: 生成后删除源文件
@unlink($filePath);
return $attachment;
} else {
var_dump(gettype($content));
2024-11-04 15:00:20 +08:00
// 小程序码获取失败
$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 = "&nbsp;", $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 = "&nbsp;", $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 = "&nbsp;", $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 .= "&emsp;│";
}
}
if ($v['_level'] != 1) {
$t = $title ? $v[$title] : "";
if (isset($arr[$k + 1]) && $arr[$k + 1]['_level'] >= $arr[$k]['_level']) {
$arr[$k]['_name'] = $str . "&emsp;├─ " . $v['_html'] . $t;
} else {
$arr[$k]['_name'] = $str . "&emsp;└─ " . $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;
}
}