Request::url(), 'fullUrl' => Request::fullUrl(), 'requestUri' => Request::getRequestUri(), 'uri' => Request::getUri(), 'ip' => Base::getIp(), 'header' => Request::header(), 'input' => Request::input(), 'query' => Request::query(), 'post' => Request::post(), 'time' => date("Y-m-d H:i:s", time()) ], $title); } /** * 添加日志 * @param string|array $log * @param string $title */ public static function addLog($log, $title = '') { if (!config('app.debug') || defined('DO_NOT_ADD_LOGS')) { return; } if (is_array($log)) { $log = self::array2json($log, JSON_UNESCAPED_UNICODE); } Tmp::createInstance([ 'name' => 'log_' . ($title ?: date("Y-m-d H:i:s", time())), 'value' => date("Y-m-d H:i:s", time()), 'content' => $log, ])->save(); } /** * 获取package配置文件 * @return array */ public static function getPackage() { return Cache::remember("Base::package", now()->addSeconds(10), function () { $file = base_path('package.json'); if (file_exists($file)) { $package = json_decode(file_get_contents($file), true); return is_array($package) ? $package : []; } return []; }); } /** * 获取token * @return mixed|string */ public static function token() { return Base::headerOrInput('dootask-token') ?: Base::headerOrInput('token'); } /** * 如果header没有则通过input读取 * @param $key * @return mixed|string */ public static function headerOrInput($key) { return Base::nullShow(Request::header($key), Request::input($key)); } /** * 如果input没有则通过header读取 * @param $key * @return mixed|string */ public static function inputOrHeader($key) { return Base::nullShow(Request::input($key), Request::header($key)); } /** * 获取版本号 * @return string */ public static function getVersion() { $package = self::getPackage(); return $package['version'] ?? '1.0.0'; } /** * 获取客户端版本号 * @return string */ public static function getClientVersion() { if (RequestContext::has('client_version')) { return RequestContext::get('client_version'); } return RequestContext::save('client_version', self::headerOrInput('version') ?: '0.0.1'); } /** * 检查客户端版本 * @param string $min 最小版本 * @return void */ public static function checkClientVersion($min) { if (!self::judgeClientVersion($min)) { throw new ApiException('当前客户端版本 (' . Base::getClientVersion() . ') 过低,最低版本要求 (' . $min . ')。'); } } /** * 判断客户端版本 * @param $min // 最小版本(满足此版本返回true) * @param null $clientVersion * @return bool */ public static function judgeClientVersion($min, $clientVersion = null) { return !version_compare($clientVersion ?: Base::getClientVersion(), $min, '<'); } /** * 判断是否域名格式 * @param $domain * @return bool */ public static function is_domain($domain){ $str = "/^(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/"; if (!preg_match($str, $domain)) { return false; } else { return true; } } /** * 判断字符串是否IP获取子掩码IP * @param $cidr * @return bool */ public static function is_cidr($cidr) { if (str_contains($cidr, '/')) { list($cidr, $netmask) = explode('/', $cidr, 2); if ($netmask > 32 || $netmask < 0 || trim($netmask) == '') { return false; } } return filter_var($cidr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } /** * 判断IP是否正确 * @param string $ip * @return bool */ public static function is_ipv4($ip) { return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } /** * 判断是否外网IP * @param string $ip * @return bool */ public static function is_extranet_ip($ip) { if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return false; } return !self::is_internal_ip($ip); } /** * 判断是否内网IP * @param string $ip * @return bool */ public static function is_internal_ip($ip) { if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return false; } $ip = ip2long($ip); if (!$ip) { return false; } $net_l = ip2long('127.255.255.255') >> 24; //127.x.x.x $net_a = ip2long('10.255.255.255') >> 24; //A类网预留ip的网络地址 $net_b = ip2long('172.31.255.255') >> 20; //B类网预留ip的网络地址 $net_c = ip2long('192.168.255.255') >> 16; //C类网预留ip的网络地址 return $ip >> 24 === $net_l || $ip >> 24 === $net_a || $ip >> 20 === $net_b || $ip >> 16 === $net_c; } /** * 获取数组值 * @param $obj * @param string $key * @param string $default * @return array|string */ public static function val($obj, $key = '', $default = '') { if (!is_array($obj)) { return $default; } if (is_int($key)) { if (isset($obj[$key])) { $obj = $obj[$key]; } else { $obj = ""; } } elseif (!empty($key)) { $arr = explode(".", str_replace("|", ".", $key)); foreach ($arr as $val) { if (isset($obj[$val])) { $obj = $obj[$val]; } else { $obj = ""; break; } } } if ($default && empty($obj)) $obj = $default; return $obj; } /** * 获取当前uri * @return string */ public static function getUrl() { return Request::getRequestUri(); } /** * 跳转 * @param null $url * @return \Illuminate\Http\RedirectResponse */ public static function goUrl($url = null) { if (empty($url)) { $url = Base::getUrl(); } return Redirect::to($url, 301); } /** * 默认显示 * @param $str * @param $val * @param $default * @return mixed */ public static function nullShow($str, $val, $default = '') { return $str ? ($default ?: $str) : $val; } /** * 补零 * @param $str * @param int $length 长度 * @param bool $before 是否补在前面 * @return string */ public static function zeroFill($str, $length = 0, $before = true) { if (strlen($str) >= $length) { return $str; } $_str = ''; for ($i = 0; $i < $length; $i++) { $_str .= '0'; } if ($before) { $_ret = substr($_str . $str, $length * -1); } else { $_ret = substr($str . $_str, 0, $length); } return $_ret; } /** * 新建文件夹 * @param $path * @return mixed */ public static function makeDir($path) { try { Storage::makeDirectory($path); } catch (\Throwable $e) { } if (!file_exists($path)) { self::makeDir(dirname($path)); @mkdir($path); @chmod($path, 0777); } return $path; } /** * 删除文件夹 * @param $path */ public static function deleteDir($path) { Storage::deleteDirectory($path); } /** * 删除文件夹及文件夹下所有的文件 * @param $dirName * @param bool $undeleteDir 不删除文件夹本身(只删除文件夹里面的内容) */ public static function deleteDirAndFile($dirName, $undeleteDir = false) { if ($handle = opendir($dirName)) { while (false !== ($item = readdir($handle))) { if ($item != "." && $item != "..") { if (is_dir($dirName . "/" . $item)) { self::deleteDirAndFile($dirName . "/" . $item); } else { @unlink($dirName . "/" . $item); } } } closedir($handle); if ($undeleteDir === false) { rmdir($dirName); } } } /** * 复制文件夹 * @param $source * @param $destination * @return bool */ public static function copyDirectory($source, $destination) { if (!is_dir($source)) { return false; } if (!is_dir($destination)) { Base::makeDir($destination); } $dir = opendir($source); while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { if (is_dir($source . '/' . $file)) { self::copyDirectory($source . '/' . $file, $destination . '/' . $file); } else { copy($source . '/' . $file, $destination . '/' . $file); } } } closedir($dir); return true; } /** * * 截取字符串 * @param string $str 字符串 * @param int $length 截取长度 * @param int $start 何处开始 * @param string $suffix 后缀(超出长度显示,默认:...) * @return string */ public static function cutStr(string $str, int $length, int $start = 0, string $suffix = '...') { $strLen = mb_strlen($str); // 处理负数长度 if ($length < 0) { $length = max($strLen + $length, 0); } // 处理负数起始位置 if ($start < 0) { $start = max($strLen + $start, 0); } // 处理边界情况 if ($length === 0 || $start >= $strLen) { return ''; } $result = mb_substr($str, $start, $length); // 只有当实际截取的长度小于原字符串长度时才添加后缀 if ($start + $length < $strLen) { return $result . $suffix; } return $result; } /** * 将字符串转换为数组 * @param string $data 字符串 * @param array $default 为空时返回的默认数组 * @return array 返回数组格式,如果,data为空,则返回$default */ public static function string2array($data, $default = []) { if (is_array($data)) { return $data ?: $default; } $data = trim($data); if ($data == '') return $default; if (str_starts_with(strtolower($data), 'array') && strtolower($data) !== 'array') { @ini_set('display_errors', 'on'); @eval("\$array = $data;"); @ini_set('display_errors', 'off'); } else { if (str_starts_with($data, '{\\')) { $data = stripslashes($data); } $array = json_decode($data, true); } return isset($array) && is_array($array) && $data ? $array : $default; } /** * 将数组转换为字符串 * @param array $data 数组 * @param int $isformdata 如果为0,则不使用new_stripslashes处理,可选参数,默认为1 * @return string 返回字符串,如果,data为空,则返回空 */ public static function array2string($data, $isformdata = 1) { if ($data == '' || empty($data)) return ''; if ($isformdata) $data = Base::newStripslashes($data); if (version_compare(PHP_VERSION, '5.3.0', '<')) { return Base::newAddslashes(json_encode($data)); } else { return Base::newAddslashes(json_encode($data, JSON_FORCE_OBJECT)); } } /** * 将数组转换为字符串 (格式化) * @param array $data 数组 * @param int $isformdata 如果为0,则不使用new_stripslashes处理,可选参数,默认为1 * @return string 返回字符串,如果,data为空,则返回空 */ public static function array2string_discard($data, $isformdata = 1) { if ($data == '' || empty($data)) return ''; if ($isformdata) $data = Base::newStripslashes($data); return var_export($data, TRUE); } /** * json字符串转换成array * @param $string * @return array|mixed */ public static function json2array($string) { if (is_array($string)) { return $string; } try { $array = json_decode($string, true); return is_array($array) ? $array : []; } catch (\Throwable) { return []; } } /** * array转换成功json字符串 * @param $array * @param int $options * @return string */ public static function array2json($array, $options = 0) { if (!is_array($array)) { return $array; } try { return json_encode($array, $options); } catch (\Throwable) { return ''; } } /** * 叠加数组或对象 * @param object|array $array * @param array $over * @return object|array */ public static function array_over(&$array, $over = []) { if (is_array($over)) { foreach ($over as $key => $val) { if (is_array($array)) { $array[$key] = $val; } if (is_object($array)) { $array->$key = $val; } } } return $array; } /** * 获取数组第一个值 * @param $array * @return mixed */ public static function arrayFirst($array) { $val = ''; if (is_array($array)) { foreach ($array as $item) { $val = $item; break; } } return $val; } /** * 获取数组最后一个值 * @param $array * @return mixed */ public static function arrayLast($array) { $val = ''; if (is_array($array)) { foreach (array_reverse($array) as $item) { $val = $item; break; } } return $val; } /** * array转xml * @param $data * @param string $root 根节点 * @return string */ public static function array2xml($data, $root = '') { $str = ""; if ($root) $str .= $root; foreach ($data as $key => $val) { if (is_array($val)) { $child = self::array2xml($val, false); $str .= "<$key>$child"; } else { $str .= "<$key>"; } } if ($root) $str .= ''; return $str; } /** * xml转json * @param string $source 传的是文件,还是xml的string的判断 * @return string */ public static function xml2json($source) { if (is_file($source)) { $xml_array = @simplexml_load_file($source); } else { $xml_array = @simplexml_load_string($source, NULL, LIBXML_NOCDATA); } return json_encode($xml_array); } /** * 返回经stripslashes处理过的字符串或数组 * @param array|string $string 需要处理的字符串或数组 * @return array|int|string */ public static function newStripslashes($string) { if (is_numeric($string)) { return $string; } elseif (!is_array($string)) { return stripslashes($string); } foreach ($string as $key => $val) $string[$key] = Base::newStripslashes($val); return $string; } /** * 返回经addslashes处理过的字符串或数组 * @param array|string $string 需要处理的字符串或数组 * @return array|int|string */ public static function newAddslashes($string) { if (is_numeric($string)) { return $string; } elseif (!is_array($string)) { return addslashes($string); } foreach ($string as $key => $val) $string[$key] = Base::newAddslashes($val); return $string; } /** * 返回经trim处理过的字符串或数组 * @param $string * @return array|string */ public static function newTrim($string) { if (!is_array($string)) return trim($string); foreach ($string as $key => $val) $string[$key] = Base::newTrim($val); return $string; } /** * 返回经intval处理过的字符串或数组 * @param $string * @return array|int */ public static function newIntval($string) { if (!is_array($string)) return intval($string); foreach ($string as $key => $val) $string[$key] = Base::newIntval($val); return $string; } /** * 递归处理数组 * * @param string $callback 如:'intval'、'trim'、'addslashes'、'stripslashes'、'htmlspecialchars' * @param array $array * @return array */ public static function newArrayRecursive($callback, $array) { $func = function ($item) use (&$func, &$callback) { return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item); }; return array_map($func, $array); } /** * 重MD5加密 * @param $text * @param string $pass * @return string */ public static function md52($text, $pass = '') { $_text = md5($text) . $pass; return md5($_text); } /** * 随机字符串 * @param int $length 随机字符长度 * @param string $type * @return string 1数字、2大小写字母、21小写字母、22大写字母、默认全部; */ public static function generatePassword($length = 8, $type = '') { // 密码字符集,可任意添加你需要的字符 switch ($type) { case '1': $chars = '0123456789'; break; case '2': $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case '21': $chars = 'abcdefghijklmnopqrstuvwxyz'; break; case '22': $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; default: $chars = $type ?: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; break; } $passwordstr = ''; $max = strlen($chars) - 1; for ($i = 0; $i < $length; $i++) { $passwordstr .= $chars[mt_rand(0, $max)]; } return $passwordstr; } /** * 同 generate_password 默认获取纯数字 * @param $length * @param string $chars * @return string */ public static function strRandom($length, $chars = '0123456789') { return Base::generatePassword($length, $chars); } /** * 判断两个地址域名是否相同 * @param string $var1 * @param string|array $var2 * @return bool */ public static function hostContrast($var1, $var2) { $arr1 = parse_url($var1); $host1 = $arr1['host'] ?? $var1; // $host2 = []; foreach (is_array($var2) ? $var2 : [$var2] as $url) { $arr2 = parse_url($url); $host2[] = $arr2['host'] ?? $url; } return in_array($host1, $host2); } /** * 获取url域名 * @param string $var * @return mixed */ public static function getHost($var = '') { if (empty($var)) { $var = url("/"); } $arr = parse_url($var); return $arr['host']; } /** * 相对路径补全 * @param string|array $str * @return string|array */ public static function fillUrl($str = '') { if (is_array($str)) { foreach ($str as $key => $item) { $str[$key] = Base::fillUrl($item); } return $str; } if (empty($str)) { return $str; } if (str_starts_with($str, "//") || str_starts_with($str, "http://") || str_starts_with($str, "https://") || str_starts_with($str, "ftp://") || str_starts_with($str, "/") || str_starts_with(str_replace(' ', '', $str), "data:image/") ) { return $str; } if (RequestContext::has('fill_url_remote_url')) { return "{{RemoteURL}}" . $str; } try { $fillUrl = url($str); } catch (\Throwable) { $fillUrl = self::getSchemeAndHost() . "/" . $str; } return RequestContext::replaceBaseUrl($fillUrl); } /** * 反 fillUrl * @param string $str * @return array|string */ public static function unFillUrl($str = '') { if (is_array($str)) { foreach ($str as $key => $item) { $str[$key] = Base::unFillUrl($item); } return $str; } if (empty($str)) { return $str; } $parsedUrl = parse_url($str); if (isset($parsedUrl['scheme']) && isset($parsedUrl['host'])) { $relativePath = $parsedUrl['path'] ?? ''; $relativePath = ltrim($relativePath, '/'); $absolutePath = public_path($relativePath); if (file_exists($absolutePath) || file_exists(Base::thumbRestore($absolutePath))) { return $relativePath; } } return $str; } /** * 获取主地址 * @return string 如:http://127.0.0.1:8080 */ public static function getSchemeAndHost() { $scheme = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://'; return $scheme.($_SERVER['HTTP_HOST'] ?? ''); } /** * 地址后拼接参数 * @param $url * @param $parames * @return mixed|string */ public static function urlAddparameter($url, $parames) { if ($parames && is_array($parames)) { $array = []; foreach ($parames as $key => $val) { $array[] = $key . "=" . $val; } if ($array) { $query = implode("&", $array); if (str_contains($url, "?")) { $url .= "&" . $query; } else { $url .= "?" . $query; } } } return $url; } /** * 格式化内容图片地址 * @param $content * @return mixed */ public static function formatContentImg($content) { $pattern = '/]*?)src=(["\'])([^"\']+?)\2/i'; if (preg_match($pattern, $content)) { preg_match_all($pattern, $content, $matchs); foreach ($matchs[3] as $index => $value) { if (!(str_starts_with($value, "http://") || str_starts_with($value, "https://") || str_starts_with($value, "ftp://") || str_starts_with(str_replace(' ', '', $value), "data:image/") )) { if (str_starts_with($value, "//")) { $value = "http:" . $value; } elseif (str_starts_with($value, "/")) { $value = substr($value, 1); } $newValue = " [1], 'b' => [2]]; // false(非连续数字索引) * $arr3 = [[1], 2]; // false(含非数组元素) * $arr4 = [1, 2]; // false(全部非数组元素) * $arr5 = []; // false(空数组) * * // 非严格模式测试 ($strict = false) * $arr6 = [[1], 2, [3]]; // true(包含数组元素即可) * $arr7 = ['a' => [1], 'b' => 2]; // true(包含数组元素即可) */ public static function isTwoArray($array, bool $strict = true) { if (!is_array($array) || empty($array)) { return false; } $count = count(array_filter($array, 'is_array')); if ($strict) { return $count === count($array) && Base::arrayIsList($array); } return $count > 0; } /** * 判断数组是否为 list * @param array $array * @return bool */ public static function arrayIsList(array $array): bool { if ([] === $array || $array === array_values($array)) { return true; } $nextKey = -1; foreach ($array as $k => $v) { if ($k !== ++$nextKey) { return false; } } return true; } /** * 检测手机号码格式 * @param string $str 需要检测的字符串 * @return bool */ public static function isMobile($str) { if (preg_match("/^1([3456789])\d{9}$/", $str)) { return true; } else { return false; } } /** * 检测邮箱格式 * @param $str * @return bool */ public static function isEmail($str) { if (filter_var($str, FILTER_VALIDATE_EMAIL)) { return true; } else { return false; } } /** * 正则判断是否纯数字 * @param $str * @return bool */ public static function isNumber($str) { if (preg_match("/^\d+$/", $str)) { return true; } else { return false; } } /** * 正则判断是否MAC地址 * @param $str * @return bool */ public static function isMac($str) { if (preg_match("/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/", $str)) { return true; } else { return false; } } /** * 判断身份证是否正确 * @param $id * @return bool */ public static function isIdcard($id) { $id = strtoupper($id); $regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/"; $arr_split = array(); if (!preg_match($regx, $id)) { return FALSE; } if (15 == strlen($id)) { //检查15位 $regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/"; @preg_match($regx, $id, $arr_split); //检查生日日期是否正确 $dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4]; if (!strtotime($dtm_birth)) { return FALSE; } else { return TRUE; } } else { //检查18位 $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/"; @preg_match($regx, $id, $arr_split); $dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4]; //检查生日日期是否正确 if (!strtotime($dtm_birth)) { return FALSE; } else { //检验18位身份证的校验码是否正确。 //校验位按照ISO 7064:1983.MOD 11-2的规定生成,X可以认为是数字10。 $arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2); $arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'); $sign = 0; for ($i = 0; $i < 17; $i++) { $b = (int)$id[$i]; $w = $arr_int[$i]; $sign += $b * $w; } $n = $sign % 11; $val_num = $arr_ch[$n]; if ($val_num != substr($id, 17, 1)) { return FALSE; } else { return TRUE; } } } } /** * 阵列数组 * @param $keys * @param $src * @param bool $default * @return array */ public static function arrayElements($keys, $src, $default = FALSE) { $return = []; if (!is_array($keys)) { $keys = array($keys); } foreach ($keys as $key) { if (isset($src[$key])) { $return[$key] = $src[$key]; } else { $return[$key] = $default; } } return $return; } /** * 判断字符串存在(包含) * @param string $string * @param string $find * @return bool */ public static function strExists($string, $find) { if (!is_string($string) || !is_string($find)) { return false; } return str_contains($string, $find); } /** * 判断字符串开头包含 * @param string $string //原字符串 * @param string $find //判断字符串 * @param bool|false $lower //是否不区分大小写 * @return bool */ public static function leftExists($string, $find, $lower = false) { if (!is_string($string) || !is_string($find)) { return false; } if ($lower) { $string = strtolower($string); $find = strtolower($find); } return str_starts_with($string, $find); } /** * 判断字符串结尾包含 * @param string $string //原字符串 * @param string $find //判断字符串 * @param bool|false $lower //是否不区分大小写 * @return int */ public static function rightExists($string, $find, $lower = false) { if (!is_string($string) || !is_string($find)) { return false; } if ($lower) { $string = strtolower($string); $find = strtolower($find); } return str_ends_with($string, $find); } /** * 删除开头指定字符串 * @param $string * @param $find * @param bool $lower * @return string */ public static function leftDelete($string, $find, $lower = false) { if (Base::leftExists($string, $find, $lower)) { $string = substr($string, strlen($find)); } return $string ?: ''; } /** * 删除结尾指定字符串 * @param $string * @param $find * @param bool $lower * @return string */ public static function rightDelete($string, $find, $lower = false) { if (Base::rightExists($string, $find, $lower)) { $string = substr($string, 0, strlen($find) * -1); } return $string; } /** * 替换开头指定字符串 * @param $string * @param $find * @param $replace * @param $lower * @return mixed|string */ public static function leftReplace($string, $find, $replace, $lower = false) { if (Base::leftExists($string, $find, $lower)) { $string = $replace . substr($string, strlen($find)); } return $string; } /** * 替换结尾指定字符串 * @param $string * @param $find * @param $replace * @param $lower * @return mixed|string */ public static function rightReplace($string, $find, $replace, $lower = false) { if (Base::rightExists($string, $find, $lower)) { $string = substr($string, 0, strlen($find) * -1) . $replace; } return $string; } /** * 截取指定字符串 * @param $str * @param string $ta * @param string $tb * @return string */ public static function getMiddle($str, $ta = '', $tb = '') { if ($ta && str_contains($str, $ta)) { $str = substr($str, strpos($str, $ta) + strlen($ta)); } if ($tb && str_contains($str, $tb)) { $str = substr($str, 0, strpos($str, $tb)); } return $str; } /** * 自定义替换次数 * @param $search * @param $replace * @param $subject * @param int $limit * @return string|string[]|null */ public static function strReplaceLimit($search, $replace, $subject, $limit = -1) { if (is_array($search)) { foreach ($search as $k => $v) { $search[$k] = '`' . preg_quote($v, '`') . '`'; } } else { $search = '`' . preg_quote($search, '`') . '`'; } return preg_replace($search, $replace, $subject, $limit); } /** * 获取或设置 * @param $setname // 配置名称 * @param bool $array // 保存内容 * @param bool $isUpdate // 保存内容为更新模式,默认否 * @return array */ public static function setting($setname, $array = false, $isUpdate = false) { if (empty($setname)) { return []; } if ($array === false && RequestContext::has('setting_' . $setname)) { return RequestContext::get('setting_' . $setname); } $setting = []; $row = Setting::whereName($setname)->first(); if ($row) { $setting = Base::string2array($row->setting); } else { $row = Setting::createInstance(['name' => $setname]); $row->save(); } if ($array !== false) { if ($isUpdate && is_array($array)) { $setting = array_merge($setting, $array); } else { $setting = $array; } $row->updateInstance(['setting' => $setting]); $row->save(); } return RequestContext::save('setting_' . $setname, $setting); } /** * 获取设置值 * @param $setname * @param $keyname * @param $defaultVal * @return mixed */ public static function settingFind($setname, $keyname, $defaultVal = '') { $array = Base::setting($setname); return $array[$keyname] ?? $defaultVal; } /** * JSON返回 * @param $param * @return string */ public static function jsonEcho($param) { $json = json_encode($param); $callback = Request::input('callback'); if ($callback) { return $callback . '(' . $json . ')'; } else { return $json; } } /** * 数组返回 正确 * @param $msg * @param string|array $data * @param int $ret * @return array */ public static function retSuccess($msg, $data = [], $ret = 1) { return [ 'ret' => $ret, 'msg' => Doo::translate($msg), 'data' => $data ]; } /** * 数组返回 错误 * @param $msg * @param array $data * @param int $ret * @return array */ public static function retError($msg, $data = [], $ret = 0) { return [ 'ret' => $ret, 'msg' => Doo::translate($msg), 'data' => $data ]; } /** * Ajax 错误返回 * @param $msg * @param array $data * @param int $ret * @param int $abortCode * @return array */ public static function ajaxError($msg, $data = [], $ret = 0, $abortCode = 404) { if (Request::header('Content-Type') === 'application/json') { return Base::retError($msg, $data, $ret); } else { abort($abortCode, $msg); } } /** * JSON返回 正确 * @param $msg * @param array $data * @param int $ret * @return string */ public static function jsonSuccess($msg, $data = [], $ret = 1) { return Base::jsonEcho(Base::retSuccess($msg, $data, $ret)); } /** * JSON返回 错误 * @param $msg * @param array $data * @param int $ret * @return string */ public static function jsonError($msg, $data = [], $ret = 0) { return Base::jsonEcho(Base::retError($msg, $data, $ret)); } /** * 是否错误 * @param $param * @return bool */ public static function isError($param) { return !isset($param['ret']) || intval($param['ret']) <= 0; } /** * 是否正确 * @param $param * @return bool */ public static function isSuccess($param) { return !self::isError($param); } /** * 获取数组的第几个值 * @param $arr * @param int $i * @return array */ public static function getArray($arr, $i = 1) { $array = []; $j = 1; foreach ($arr as $item) { $array[] = $item; if ($i >= $j) { break; } $j++; } return $array; } /** * 小时转天/小时 * @param $hour * @return string */ public static function forumHourDay($hour) { $hour = intval($hour); if ($hour >= 24) { $day = floor($hour / 24); $hour -= $day * 24; if ($hour > 0) { return $day . '天' . $hour . '小时'; } return $day . '天'; } return $hour . '小时'; } /** * 分钟转天/小时/分钟 * @param $minute * @return string */ public static function forumMinuteDay($minute) { $minute = intval($minute); if ($minute >= 60) { $hour = floor($minute / 60); $minute -= $hour * 60; if ($minute > 0) { return Base::forumHourDay($hour) . $minute . '分钟'; } return Base::forumHourDay($hour); } return $minute . '分钟'; } /** * 创建Carbon对象 * @param $var * @return Carbon */ public static function newCarbon($var) { if (self::isNumber($var)) { if (preg_match("/^\d{13,}$/", $var)) { $var = $var / 1000; } return Carbon::createFromTimestamp($var); } elseif (is_string($var)) { return Carbon::parse(trim($var)); } else { return Carbon::now(); } } /** * 用户名、邮箱、手机帐号、银行卡号中间字符串以*隐藏 * @param $str * @return string */ public static function formatName($str) { if (str_contains($str, "@")) { $email_array = explode("@", $str); $prevfix = substr($str, 0, strlen($email_array[0]) < 4 ? 1 : 3); //邮箱前缀 $count = 0; $str = preg_replace('/([\d\w+_-]{0,100})@/', '***@', $str, -1, $count); return $prevfix . $str; } if (Base::isMobile($str)) { return substr($str, 0, 3) . "****" . substr($str, -4); } $pattern = '/([\d]{4})([\d]{4})([\d]{4})([\d]{4})([\d]*)?/i'; if (preg_match($pattern, $str)) { return preg_replace($pattern, '$1 **** **** **** $5', $str); } $pattern = '/([\d]{4})([\d]{4})([\d]{4})([\d]*)?/i'; if (preg_match($pattern, $str)) { return preg_replace($pattern, '$1 **** **** $4', $str); } $pattern = '/([\d]{4})([\d]{4})([\d]*)?/i'; if (preg_match($pattern, $str)) { return preg_replace($pattern, '$1 **** $3', $str); } return substr($str, 0, 3) . "***" . substr($str, -1); } /** * 数字每4位加一空格 * @param $str * @param string $interval * @return string */ public static function fourFormat($str, $interval = " ") { if (!is_numeric($str)) return $str; // $text = ''; for ($i = 0; $i < strlen($str); $i++) { $text .= $str[$i]; if ($i % 4 == 3) { $text .= $interval; } } return $text; } /** * 保留两位小数点 * @param $str * @param bool $float * @return float */ public static function twoFloat($str, $float = false) { $str = sprintf("%.2f", floatval($str)); if ($float === true) { $str = floatval($str); } return $str; } /** * 取ip前3段 * @param $ip * @return mixed|string */ public static function getIp3Pre($ip) { preg_match("/(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}/", $ip, $match); if ($match) { return $match[1]; } else { return ""; } } /** * 获取IP地址 * @return string */ public static function getIp() { if (RequestContext::has("static_ip")) { return RequestContext::get("static_ip"); } if (getenv('HTTP_CLIENT_IP') and strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) { $onlineip = getenv('HTTP_CLIENT_IP'); } elseif (isset($_SERVER['HTTP_CLIENT_IP']) and $_SERVER['HTTP_CLIENT_IP'] and strcasecmp($_SERVER['HTTP_CLIENT_IP'], 'unknown')) { $onlineip = $_SERVER['HTTP_CLIENT_IP']; } elseif (getenv('HTTP_X_FORWARDED_FOR') and strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) { $onlineip = getenv('HTTP_X_FORWARDED_FOR'); } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) and $_SERVER['HTTP_X_FORWARDED_FOR'] and strcasecmp($_SERVER['HTTP_X_FORWARDED_FOR'], 'unknown')) { $onlineip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (getenv('REMOTE_ADDR') and strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) { $onlineip = getenv('REMOTE_ADDR'); } elseif (isset($_SERVER['REMOTE_ADDR']) and $_SERVER['REMOTE_ADDR'] and strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) { $onlineip = $_SERVER['REMOTE_ADDR']; } elseif (Request::header('X-Real-IP')) { $onlineip = Request::header('X-Real-IP'); } else { $onlineip = '0,0,0,0'; } preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $onlineip, $match); return RequestContext::save("static_ip", $match ? ($match[0] ?: 'unknown') : ''); } /** * 是否是中国IP:-1错误、1是、0否 * @param string $ip * @return int */ public static function isCnIp($ip = '') { if (empty($ip)) { $ip = self::getIp(); } $cacheKey = "isCnIp::" . md5($ip); // $result = Cache::remember($cacheKey, now()->addMinutes(10), function () use ($ip) { $file = dirname(__FILE__) . '/IpAddr/all_cn.txt'; if (!file_exists($file)) { return -1; } $in = false; $myFile = fopen($file, "r"); $i = 0; while (!feof($myFile)) { $i++; $cidr = trim(fgets($myFile)); if (Base::ipInRange($ip, $cidr)) { $in = true; break; } } fclose($myFile); return $in ? 1 : 0; }); if ($result === -1) { Cache::forget($cacheKey); } // return intval($result); } /** * 验证IP地址范围 * $range 支持多种写法 * - Wildcard: 1.2.3.* * - CIRD:1.2.3/24 或者 1.2.3.4/255.255.255.0 * - Start-End: 1.2.3.0-1.2.3.255 * @param $ip * @param $range * @return bool */ public static function ipInRange($ip, $range) { if (substr_count($ip, '.') == 3 && $ip == $range) { return true; } if (str_contains($range, '/')) { list($range, $netmask) = explode('/', $range, 2); if (str_contains($netmask, '.')) { $netmask = str_replace('*', '0', $netmask); $netmask_dec = ip2long($netmask); return ((ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec)); } else { $x = explode('.', $range); while (count($x) < 4) { $x[] = '0'; } list($a, $b, $c, $d) = $x; $range = sprintf("%u.%u.%u.%u", empty($a) ? '0' : $a, empty($b) ? '0' : $b, empty($c) ? '0' : $c, empty($d) ? '0' : $d); $range_dec = ip2long($range); $ip_dec = ip2long($ip); $wildcard_dec = pow(2, (32 - $netmask)) - 1; $netmask_dec = ~$wildcard_dec; return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); } } else { if (str_contains($range, '*')) { $lower = str_replace('*', '0', $range); $upper = str_replace('*', '255', $range); $range = "$lower-$upper"; } if (str_contains($range, '-')) { list($lower, $upper) = explode('-', $range, 2); $lower_dec = (float)sprintf("%u", ip2long($lower)); $upper_dec = (float)sprintf("%u", ip2long($upper)); $ip_dec = (float)sprintf("%u", ip2long($ip)); return (($ip_dec >= $lower_dec) && ($ip_dec <= $upper_dec)); } return false; } } /** * 多维 array_values * @param $array * @param string $keyName * @param string $valName * @return array */ public static function array_values_recursive($array, $keyName = 'key', $valName = 'item') { if (is_array($array) && count($array) > 0) { $temp = []; foreach ($array as $key => $value) { $continue = false; if (is_array($value) && count($value) > 0) { $continue = true; foreach ($value as $item) { if (!is_array($item)) { $continue = false; break; } } } $temp[] = [ $keyName => $key, $valName => $continue ? self::array_values_recursive($value, $keyName, $valName) : $value, ]; } return $temp; } return $array; } /** * 多维数组只保留指定键值 * @param $array * @param $keys * @return array */ public static function array_only_recursive($array, $keys) { return array_map(function ($item) use ($keys) { return is_array($item) ? array_intersect_key($item, array_flip($keys)) : $item; }, $array); } /** * 是否微信 * @return bool */ public static function isWechat() { return str_contains(Request::server('HTTP_USER_AGENT'), 'MicroMessenger'); } /** * 获取浏览器类型 * @return string */ public static function browser() { $user_agent = Request::server('HTTP_USER_AGENT'); if (str_contains($user_agent, 'AlipayClient')) { return 'alipay'; } elseif (str_contains($user_agent, 'MicroMessenger')) { return 'weixin'; } else { return 'none'; } } /** * 获取平台类型 * @return string */ public static function platform() { $platform = strtolower(trim(Request::header('platform'))); if (in_array($platform, ['android', 'ios', 'win', 'mac', 'web'])) { return $platform; } $agent = strtolower(Request::server('HTTP_USER_AGENT')); if (str_contains($agent, 'android')) { $platform = 'android'; } elseif (str_contains($agent, 'iphone') || str_contains($agent, 'ipad')) { $platform = 'ios'; } else { $platform = 'unknown'; } return $platform; } /** * 是否是App移动端 * @return bool */ public static function isEEUIApp() { $userAgent = strtolower(Request::server('HTTP_USER_AGENT')); return str_contains($userAgent, 'kuaifan_eeui'); } /** * 返回根据距离sql排序语句 * @param $lat * @param $lng * @param string $latName * @param string $lngName * @return string */ public static function acos($lat, $lng, $latName = 'lat', $lngName = 'lng') { $lat = floatval($lat); $lng = floatval($lng); return 'ACOS( SIN((' . $lat . ' * 3.1415) / 180) * SIN((' . $latName . ' * 3.1415) / 180) + COS((' . $lat . ' * 3.1415) / 180) * COS((' . $latName . ' * 3.1415) / 180) * COS( (' . $lng . ' * 3.1415) / 180 - (' . $lngName . ' * 3.1415) / 180 ) ) * 6380'; } /** * 获取每页数量 * @param $max * @param $default * @param string $inputName * @return mixed */ public static function getPaginate($max, $default, $inputName = 'pagesize') { return Min(Max(Base::nullShow(Request::input($inputName), $default), 1), $max); } /** * base64语音保存 * @param array $param [ base64=带前缀的base64, path=>文件路径 ] * @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名] */ public static function record64save($param) { $base64 = $param['base64']; if (preg_match('/^(data:\s*audio\/(\w+);base64,)/', $base64, $res)) { $extension = $res[2]; if (!in_array($extension, ['mp3', 'wav'])) { return Base::retError('语音格式错误'); } $saveName = 'record_' . md5($base64) . '.' . $extension; $fileDir = $param['path']; $filePath = public_path($fileDir); Base::makeDir($filePath); if (file_put_contents($filePath . $saveName, base64_decode(str_replace($res[1], '', $base64)))) { $fileSize = filesize($filePath . $saveName); $array = [ "name" => $saveName, //原文件名 "size" => Base::twoFloat($fileSize / 1024, true), //大小KB "file" => $filePath . $saveName, //文件的完整路径 "D:\www....KzZ.jpg" "path" => $fileDir . $saveName, //相对路径 "uploads/pic....KzZ.jpg" "url" => Base::fillUrl($fileDir . $saveName), //完整的URL "https://.....hhsKzZ.jpg" "ext" => $extension, //文件后缀名 ]; return Base::retSuccess('success', $array); } } return Base::retError('语音保存失败'); } /** * image64图片保存 * @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, saveName=>保存文件名称, scale=>[压缩原图宽,高, 压缩方式], autoThumb=>false不要自动生成缩略图, quality=>压缩图片质量(默认:0不压缩) ] * @return array [ name=>文件名, size=>文件大小(单位KB), file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名 ] */ public static function image64save($param) { $imgBase64 = $param['image64']; if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $imgBase64, $res)) { $extension = $res[2]; if (!in_array($extension, ['png', 'jpg', 'jpeg', 'webp', 'gif'])) { return Base::retError('图片格式错误'); } $scaleName = ""; if ($param['saveName']) { $saveName = basename($param['saveName']); } else { if ($param['scale'] && is_array($param['scale'])) { list($width, $height) = $param['scale']; if ($width > 0 || $height > 0) { $scaleName = "_{WIDTH}x{HEIGHT}"; if (isset($param['scale'][2])) { $scaleName .= "_{$param['scale'][2]}"; } } } $saveName = 'paste_' . md5($imgBase64) . '.' . $extension; $scaleName = md5_file($imgBase64) . $scaleName . '.' . $extension; } $fileDir = $param['path']; $filePath = public_path($fileDir); $fileFullPath = $filePath . $saveName; Base::makeDir($filePath); if (file_put_contents($fileFullPath, base64_decode(str_replace($res[1], '', $imgBase64)))) { $fileSize = filesize($fileFullPath); $array = [ "name" => $param['fileName'] ?: $saveName, //原文件名 "size" => Base::twoFloat($fileSize / 1024, true), //大小KB "file" => $fileFullPath, //文件的完整路径 "D:\www....KzZ.jpg" "path" => $fileDir . $saveName, //相对路径 "uploads/pic....KzZ.jpg" "url" => Base::fillUrl($fileDir . $saveName), //完整的URL "https://.....hhsKzZ.jpg" "thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg" "width" => -1, //图片宽度 "height" => -1, //图片高度 "ext" => $extension, //文件后缀名 ]; // 图片验证 $res = Image::validateImage($array['file']); if (Base::isError($res)) { unlink($array['file']); return $res; } // 图片尺寸 $paramet = getimagesize($array['file']); $array['width'] = $paramet[0]; $array['height'] = $paramet[1]; // 原图裁剪 if ($param['scale'] && is_array($param['scale'])) { list($width, $height) = $param['scale']; if (($width > 0 && $array['width'] > $width) || ($height > 0 && $array['height'] > $height)) { // 图片裁剪 $cutMode = ($width > 0 && $height > 0) ? 'cover' : 'percentage'; $cutMode = $param['scale'][2] ?? $cutMode; Image::thumbImage($array['file'], $array['file'], $width, $height, true, $cutMode); // 更新图片尺寸 $paramet = getimagesize($array['file']); $array['width'] = $paramet[0]; $array['height'] = $paramet[1]; // 重命名 if ($scaleName) { $scaleName = str_replace(['{WIDTH}', '{HEIGHT}'], [$array['width'], $array['height']], $scaleName); if (rename($array['file'], Base::rightDelete($array['file'], $saveName) . $scaleName)) { $array['file'] = Base::rightDelete($array['file'], $saveName) . $scaleName; $array['path'] = Base::rightDelete($array['path'], $saveName) . $scaleName; $array['url'] = Base::rightDelete($array['url'], $saveName) . $scaleName; } } } } // 压缩图片 if ($param['quality']) { Image::compressImage($array['file'], $param['quality']); $array['size'] = Base::twoFloat(filesize($array['file']) / 1024, true); } //生成缩略图 $array['thumb'] = $array['path']; if ($extension === 'gif' && !isset($param['autoThumb'])) { $param['autoThumb'] = false; } if ($param['autoThumb'] !== false) { if ($extension = Image::thumbImage($array['file'], $array['file'] . "_thumb.{*}", 320, 0, 80)) { $array['thumb'] .= "_thumb.{$extension}"; } } $array['thumb'] = Base::fillUrl($array['thumb']); return Base::retSuccess('success', $array); } } return Base::retError('图片保存失败'); } /** * 上传文件 * @param array $param [ type=[文件类型], file=>Request::file, path=>文件路径, fileName=>文件名称, saveName=>保存文件名称, scale=>[压缩原图宽,高, 压缩方式], size=>限制大小KB, autoThumb=>false不要自动生成缩略图, chmod=>权限(默认0644), quality=>压缩图片质量(默认:0不压缩), convertVideo=>转换视频格式(默认false) , compressVideo=>压缩视频(默认false,如果转换就不压缩) , ] * @return array [ name=>原文件名, size=>文件大小(单位KB), file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名, ] */ public static function upload($param) { $file = $param['file']; $chmod = $param['chmod'] ?: 0644; if (empty($file)) { return Base::retError("您没有选择要上传的文件"); } if ($file->isValid()) { Base::makeDir(public_path($param['path'])); // switch ($param['type']) { case 'png': $type = ['png']; break; case 'image': $type = ['jpg', 'jpeg', 'webp', 'gif', 'png']; break; case 'video': $type = ['rm', 'rmvb', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'mov', 'webm']; break; case 'audio': $type = ['mp3', 'wma', 'wav', 'amr']; break; case 'excel': $type = ['xls', 'xlsx', 'xlsm', 'xlt', 'xltx', 'ods', 'ots', 'csv', 'tsv']; break; case 'app': $type = ['apk']; break; case 'zip': $type = ['zip']; break; case 'file': $type = ['jpg', 'jpeg', 'webp', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mp3', 'mp4', 'mov', 'webm', 'pr', 'psd', 'svg', 'tif']; break; case 'firmware': $type = ['img', 'tar', 'bin']; break; case 'md': $type = ['md']; break; case 'publish': $type = ['yml', 'yaml', 'dmg', 'pkg', 'blockmap', 'zip', 'exe', 'msi', 'apk']; break; case 'more': $type = []; // 不限制上传文件类型 break; default: return Base::retError('错误的类型参数'); } $extension = strtolower($file->getClientOriginalExtension()); $isImage = in_array($extension, ['jpg', 'jpeg', 'webp', 'gif', 'png']); if ($type && !in_array($extension, $type)) { return Base::retError('文件格式错误,限制类型:' . implode(",", $type)); } $limitSize = intval($param['size']); if ($limitSize <= 0) { $fileUploadLimit = intval(Base::settingFind('system', 'file_upload_limit', 0)); $limitSize = $fileUploadLimit * 1024; } try { $fileSize = $file->getSize(); if ($limitSize > 0 && $fileSize > $limitSize * 1024) { return Base::retError('文件大小超限,最大限制:' . $limitSize . 'KB'); } } catch (\Throwable) { $fileSize = 0; } if ($isImage) { $res = Image::validateImage($file); if (Base::isError($res)) { return $res; } } $scaleName = ""; if ($param['saveName'] === true) { $saveName = $file->getClientOriginalName(); } elseif ($param['saveName']) { $saveName = basename($param['saveName']); } else { if ($param['scale'] && is_array($param['scale'])) { list($width, $height) = $param['scale']; if ($width > 0 || $height > 0) { $scaleName = "_{WIDTH}x{HEIGHT}"; if (isset($param['scale'][2])) { $scaleName .= "_{$param['scale'][2]}"; } } } $saveName = md5_file($file); $scaleName = md5_file($file) . $scaleName; if ($extension) { $saveName = $saveName . '.' . $extension; $scaleName = $scaleName . '.' . $extension; } } // $file->move(public_path($param['path']), $saveName); // $path = $param['path'] . $saveName; $array = [ "name" => $param['fileName'] ?: $file->getClientOriginalName(), //原文件名 "size" => Base::twoFloat($fileSize / 1024, true), //大小KB "file" => public_path($path), //文件的完整路径 "D:\www....KzZ.jpg" "path" => $path, //相对路径 "uploads/pic....KzZ.jpg" "url" => Base::fillUrl($path), //完整的URL "https://.....hhsKzZ.jpg" "thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg" "width" => -1, //图片宽度 "height" => -1, //图片高度 "ext" => $extension, //文件后缀名 ]; if (!is_file($array['file'])) { return Base::retError('上传失败'); } @chmod($array['file'], $chmod); // iOS照片颠倒处理 if (in_array($array['ext'], ['jpg', 'jpeg']) && function_exists('exif_read_data')) { $data = imagecreatefromstring(file_get_contents($array['file'])); $exif = @exif_read_data($array['file']); if (!empty($exif['Orientation'])) { $data = match ($exif['Orientation']) { 2 => imageflip($data, IMG_FLIP_HORIZONTAL), 3 => imagerotate($data, 180, 0), 4 => imageflip($data, IMG_FLIP_VERTICAL), 5 => imageflip(imagerotate($data, -90, 0), IMG_FLIP_HORIZONTAL), 6 => imagerotate($data, -90, 0), 7 => imageflip(imagerotate($data, 90, 0), IMG_FLIP_HORIZONTAL), 8 => imagerotate($data, 90, 0), default => null, }; if ($data !== null) { imagejpeg($data, $array['file']); imagedestroy($data); } } } // if ($param['convertVideo'] && in_array($array['ext'], ['mov', 'webm'])) { // 转换视频格式 $output = Base::rightReplace($array['file'], ".{$array['ext']}", '.mp4'); if ($array['ext'] === 'webm') { $command = sprintf("ffmpeg -y -i %s -strict experimental %s 2>&1", escapeshellarg($array['file']), escapeshellarg($output)); } else { $command = sprintf("ffmpeg -y -i %s -c:v copy -c:a copy %s 2>&1", escapeshellarg($array['file']), escapeshellarg($output)); } @shell_exec($command); if (file_exists($output) && filesize($output) > 0) { // 压缩后的文件正常 @unlink($array['file']); $array = array_merge($array, [ "name" => Base::rightReplace($array['name'], ".{$array['ext']}", '.mp4'), "size" => Base::twoFloat(filesize($output) / 1024, true), "file" => $output, "path" => Base::rightReplace($array['path'], ".{$array['ext']}", '.mp4'), "url" => Base::rightReplace($array['url'], ".{$array['ext']}", '.mp4'), "ext" => 'mp4', ]); } $param['compressVideo'] = false; // 如果转换就不压缩 } if ($param['compressVideo'] && $array['ext'] == 'mp4') { // 压缩视频 $output = $array['file'] . '_compress'; $command = sprintf("ffmpeg -y -i %s -c:v libx264 -crf 28 -preset medium -c:a aac -b:a 96k %s 2>&1", escapeshellarg($array['file']), escapeshellarg($output)); @shell_exec($command); if (file_exists($output) && filesize($output) > 0) { // 压缩后的文件正常 if (filesize($output) < filesize($array['file'])) { // 小于原文件 @unlink($array['file']); $array = array_merge($array, [ "size" => Base::twoFloat(filesize($output) / 1024, true), "file" => $output, ]); } else { // 大于原文件 @unlink($output); } } } if (in_array($array['ext'], ['mov', 'webm', 'mp4'])) { // 视频尺寸 $thumbFile = $array['file'] . '_thumb.jpg'; $command = sprintf("ffmpeg -y -i %s -ss 1 -vframes 1 %s 2>&1", escapeshellarg($array['file']), escapeshellarg($thumbFile)); @shell_exec($command); if (file_exists($thumbFile) && filesize($thumbFile) > 0) { $paramet = getimagesize($thumbFile); $array['width'] = $paramet[0]; $array['height'] = $paramet[1]; $array['thumb'] = $array['path'] . '_thumb.jpg'; Image::compressImage($thumbFile, 80); } } if ($isImage) { // 获取图片尺寸 $paramet = getimagesize($array['file']); $array['width'] = $paramet[0]; $array['height'] = $paramet[1]; // 原图裁剪 if ($param['scale'] && is_array($param['scale'])) { list($width, $height) = $param['scale']; if (($width > 0 && $array['width'] > $width) || ($height > 0 && $array['height'] > $height)) { // 图片裁剪 $cutMode = ($width > 0 && $height > 0) ? 'cover' : 'percentage'; $cutMode = $param['scale'][2] ?? $cutMode; Image::thumbImage($array['file'], $array['file'], $width, $height, true, $cutMode); // 更新图片尺寸 $paramet = getimagesize($array['file']); $array['width'] = $paramet[0]; $array['height'] = $paramet[1]; // 重命名 if ($scaleName) { $scaleName = str_replace(['{WIDTH}', '{HEIGHT}'], [$array['width'], $array['height']], $scaleName); if (rename($array['file'], Base::rightDelete($array['file'], $saveName) . $scaleName)) { $array['file'] = Base::rightDelete($array['file'], $saveName) . $scaleName; $array['path'] = Base::rightDelete($array['path'], $saveName) . $scaleName; $array['url'] = Base::rightDelete($array['url'], $saveName) . $scaleName; } } } } // 压缩图片 if ($param['quality']) { Image::compressImage($array['file'], $param['quality']); $array['size'] = Base::twoFloat(filesize($array['file']) / 1024, true); } // 生成缩略图 $array['thumb'] = $array['path']; if ($array['ext'] === 'gif' && !isset($param['autoThumb'])) { $param['autoThumb'] = false; } if ($param['autoThumb'] !== false) { if ($array['ext'] = Image::thumbImage($array['file'], $array['file'] . "_thumb.{*}", 320, 0, 80)) { $array['thumb'] .= "_thumb.{$array['ext']}"; } } $array['thumb'] = Base::fillUrl($array['thumb']); } // return Base::retSuccess('success', $array); } else { return Base::retError($file->getErrorMessage()); } } /** * 上传文件移动 * @param array $uploadResult * @param string $newPath "/" 结尾 * @return array */ public static function uploadMove($uploadResult, $newPath) { if (str_ends_with($newPath, "/") && file_exists($uploadResult['file'])) { Base::makeDir(public_path($newPath)); $oldPath = dirname($uploadResult['path']) . "/"; $newFile = str_replace($oldPath, $newPath, $uploadResult['file']); if (rename($uploadResult['file'], $newFile)) { $oldUrl = $uploadResult['url']; $uploadResult['file'] = $newFile; $uploadResult['path'] = str_replace($oldPath, $newPath, $uploadResult['path']); $uploadResult['url'] = str_replace($oldPath, $newPath, $uploadResult['url']); if ($uploadResult['thumb'] == $oldUrl) { $uploadResult['thumb'] = $uploadResult['url']; } elseif ($uploadResult['thumb']) { $oldThumb = substr($uploadResult['thumb'], strpos($uploadResult['thumb'], $newPath)); $newThumb = str_replace($oldPath, $newPath, $oldThumb); if (file_exists(public_path($oldThumb)) && rename(public_path($oldThumb), public_path($newThumb))) { $uploadResult['thumb'] = str_replace($oldPath, $newPath, $uploadResult['thumb']); } } } } return $uploadResult; } /** * 是否缩略图 * @param $file * @return bool */ public static function isThumb($file): bool { return preg_match('/_thumb\.(png|jpg|jpeg)$/', $file); } /** * 获取缩略图后缀 * @param $file * @return string */ public static function getThumbExt($file): string { if (file_exists($file . '_thumb.png')) { return 'png'; } elseif (file_exists($file . '_thumb.jpg')) { return 'jpg'; } elseif (file_exists($file . '_thumb.jpeg')) { return 'jpeg'; } else { return ''; } } /** * 缩略图还原 * @param $file * @return string */ public static function thumbRestore($file): string { $file = preg_replace('/_thumb\.(png|jpg|jpeg)$/', '', $file); return preg_replace('/\/crop\/([^\/]+)$/', '', $file); } /** * 获取后缀名图标相对地址 * @param $ext * @return string */ public static function extIcon($ext) { if ($ext == "docx") { $ext = 'doc'; } elseif ($ext == "xlsx") { $ext = 'xls'; } elseif ($ext == "pptx") { $ext = 'ppt'; } if (in_array($ext, ["ai", "avi", "bmp", "cdr", "doc", "eps", "gif", "mov", "mp3", "mp4", "pdf", "ppt", "pr", "psd", "rar", "svg", "tif", "txt", "xls", "zip"])) { return 'images/ext/' . $ext . '.png'; } else { return 'images/ext/file.png'; } } /** * 排列组合(无重复) * @param $arr * @param $m * @return array */ public static function getCombinationToString($arr, $m) { $result = []; if ($m == 1) { return $arr; } if ($m == count($arr)) { $result[] = implode(',', $arr); return $result; } $temp_firstelement = $arr[0]; unset($arr[0]); $arr = array_values($arr); $temp_list1 = self::getCombinationToString($arr, ($m - 1)); foreach ($temp_list1 as $s) { $s = $temp_firstelement . ',' . $s; $result[] = $s; } unset($temp_list1); $temp_list2 = self::getCombinationToString($arr, $m); foreach ($temp_list2 as $s) { $result[] = $s; } unset($temp_list2); return $result; } /** * 不同元素交叉组合(多个数组) * @return array */ public static function getNewArray() { $args = func_get_args(); $pailie = function ($arr1, $arr2) { $arr = []; $k = 0; foreach ($arr1 as $k1 => $v1) { foreach ($arr2 as $k2 => $v2) { $arr[$k] = $v1 . "," . $v2; $k++; } } return $arr; }; $arr = []; foreach ($args as $k => $v) { if (isset($args[$k + 1]) && $args[$k + 1]) { $arr[$k] = match ($k) { 0 => $pailie($v, $args[$k + 1]), default => $pailie($arr[$k - 1], $args[$k + 1]), }; } } $key = count($arr) - 1; return array_values($arr[$key]); } /** * 把返回的数据集转换成Tree * @param array $list 要转换的数据集 * @param string $pk id标记字段 * @param string $pid parent标记字段 * @param string $child 生成子类字段 * @param int $root * @return array */ public static function list2Tree($list, $pk = 'id', $pid = 'pid', $child = 'children', $root = 0) { if (!is_array($list)) { return []; } // 创建基于主键的数组引用 $aRefer = []; foreach ($list as $key => $data) { $list[$key][$child] = []; $aRefer[$data[$pk]] = &$list[$key]; } $tree = []; foreach ($list as &$data) { // 判断是否存在parent $parentId = $data[$pid]; if ($root === $parentId) { $tree[] = &$data; } else { if (isset($aRefer[$parentId])) { $parent = &$aRefer[$parentId]; $parent[$child][] = &$data; } } } return $tree; } /** * 路径拼接 * @param ...$segments * @return string */ public static function joinPath(...$segments) { if (count($segments) === 0) { return ""; } $array = array_map(function ($segment) { return trim($segment, DIRECTORY_SEPARATOR); }, $segments); $prefix = str_starts_with($segments[0], DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : ""; return $prefix . implode(DIRECTORY_SEPARATOR, $array); } /** * 递归获取所有文件 * @param $dir * @param bool|int $recursive * @return array */ public static function recursiveFiles($dir, $recursive = true) { if ($recursive && is_numeric($recursive)) { $recursive--; } $array = []; $items = scandir($dir); foreach ($items as $item) { if ($item != '..' && $item != '.') { $itemPath = self::joinPath($dir, $item); if (is_dir($itemPath)) { if ($recursive) { $array = array_merge($array, self::recursiveFiles($itemPath, $recursive)); } } else { $array[] = $itemPath; } } } return $array; } /** * 递归获取所有目录 * @param $dir * @param bool|int $recursive * @return array */ public static function recursiveDirs($dir, $recursive = true) { if ($recursive && is_numeric($recursive)) { $recursive--; } $array = []; $items = scandir($dir); foreach ($items as $item) { if ($item != '..' && $item != '.') { $itemPath = self::joinPath($dir, $item); if (is_dir($itemPath)) { $array[] = $itemPath; if ($recursive) { $array = array_merge($array, self::recursiveDirs($itemPath, $recursive)); } } } } return $array; } /** * 获取中文字符拼音首字母 * @param $str * @return string */ public static function getFirstCharter($str) { if (empty($str)) { return ''; } $first = mb_substr($str, 0, 1); if (preg_match("/^\d$/", $first)) { return '#'; } if (!preg_match("/^[a-zA-Z]$/", $first)) { $pinyin = new Pinyin(); $first = $pinyin->abbr($first, '', PINYIN_NAME); } return $first ? strtoupper($first) : '#'; } /** * 中文转拼音 * @param $str * @param $delim * @return string */ public static function cn2pinyin($str, $delim = '') { if (empty($str)) { return ''; } if (!preg_match("/^[a-zA-Z0-9_.]+$/", $str)) { $str = Cache::rememberForever("cn2pinyin:" . md5($str . '_' . $delim), function () use ($delim, $str) { $pinyin = new Pinyin(); return $pinyin->permalink($str, $delim); }); } return $str; } /** * 驼峰转下划线 * @param $str * @return string */ public static function camel2snake($str) { if (empty($str)) { return ''; } $str = preg_replace('/([a-z])([A-Z])/', '$1_$2', $str); return strtolower($str); } /** * 缓存数据 * @param $name * @param null $value * @return mixed|null */ public static function cacheData($name, $value = null) { $name = "cacheData::" . $name; $tmp = Tmp::whereName($name)->select('value')->first(); if ($value !== null) { if (empty($tmp)) { Tmp::createInstance(['name' => $name, 'value' => $value])->save(); } else { Tmp::whereName($name)->update(['value' => $value]); } return $value; } else { return $tmp->value; } } /** * 计算两点地理坐标之间的距离 * @param float $startLong 起点经度 * @param float $startLat 起点纬度 * @param float $endLong 终点经度 * @param float $endLat 终点纬度 * @param Int $unit 单位 1:米 2:公里 * @param Int $decimal 精度 保留小数位数 * @return float */ public static function getDistance(float $startLong, float $startLat, float $endLong, float $endLat, $unit = 2, $decimal = 2): float { $EARTH_RADIUS = 6370.996; // 地球半径系数 $PI = 3.1415926; $radLat1 = $startLat * $PI / 180.0; $radLat2 = $endLat * $PI / 180.0; $radLng1 = $startLong * $PI / 180.0; $radLng2 = $endLong * $PI / 180.0; $a = $radLat1 - $radLat2; $b = $radLng1 - $radLng2; $distance = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))); $distance = $distance * $EARTH_RADIUS * 1000; if ($unit == 2) { $distance = $distance / 1000; } return round($distance, $decimal); } /** * 处理ping原格式内容 * @param $original * @return array */ public static function handlePingOriginal($original) { $original = trim($original); $ipSpeeds = []; if (preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*:\s*xmt\/rcv\/\%loss/i', $original)) { $strings = explode("\n", $original); foreach ($strings as $string) { preg_match("/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*:\s*xmt\/rcv\/\%loss\s*\=\s*(.*?)%(\,\s*min\/avg\/max\s*\=\s*(\d+.?\d+)\/(\d+.?\d+)\/(\d+.?\d+))*/i", $string, $match); if ($match) { $ipSpeeds[$match[1]] = isset($match[5]) ? max(1, intval($match[5])) : 0; } } } else { $strings = preg_split("/--- (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ping statistics ---/", $original, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); for ($i = 0; $i < count($strings); $i += 2) { $context = $strings[$i + 1]; if ($context) { preg_match("/p\(\d+\) = (\d+),/i", $context, $matches); if ($matches && intval($matches[1]) > 0) { $ipSpeeds[$strings[$i]] = max(1, intval($matches[1])); continue; } preg_match("/min\/avg\/max\/(sdev|stddev)\s*\=\s*(\d+.?\d+)\/(\d+.?\d+)\/(\d+.?\d+)\//i", $context, $matches); if ($matches && intval($matches[3]) > 0) { $ipSpeeds[$strings[$i]] = max(1, intval($matches[3])); } elseif (Base::strExists($context, '100% packet loss') || Base::strExists($context, '100.00% packet loss')) { $ipSpeeds[$strings[$i]] = 0; } } } } return $ipSpeeds; } /** * 将IP转换为十六进制数 * @param $ip * @return string */ public static function ip2hex($ip) { $hex = ''; $arr = explode('.', $ip);//分隔ip段 foreach ($arr as $value) { $ipHex = dechex($value); // 将每段ip转换成16进制 if (strlen($ipHex) < 2) { $ipHex = '0' . $ipHex;//如果转换后的16进制数长度小于2,在其前面加一个0 } $hex .= $ipHex; // 将四段IP的16进制数连接起来,得到一个16进制字符串,长度为8 } return $hex; } /** * 字符串配置项转数组 * @param String $stringConfig * @return array */ public static function stringConfig2Array(string $stringConfig): array { $strings = preg_split("/\[(.*?)\]/", trim($stringConfig), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $data = []; for ($i = 0; $i < count($strings); $i += 2) { $context = $strings[$i + 1]; if ($context) { preg_match_all("/(.*?)\s*=\s*(.*?)(\n|$)/s", trim($context), $matches); if ($matches && intval($matches[1]) > 0) { if (!isset($data[$strings[$i]])) { $data[$strings[$i]] = []; } $tmp = []; foreach ($matches[1] as $index => $match) { $tmp[$match] = $matches[2][$index]; } $data[$strings[$i]][] = $tmp; } } } return $data; } /** * 二维数组交叉排列组合 * @param array $ip * @return array */ public static function crossJoin(array $ip) { if (empty($ip)) { return []; } $collection = collect($ip); $matrix = $collection->crossJoin($ip)->toArray(); // 排序 foreach ($matrix as &$item) { sort($item); } // 删除相同数组 unset($item); foreach ($matrix as $key => $item) { if ($item[0] == $item[1]) { unset($matrix[$key]); } } $matrix = array_unique($matrix, SORT_REGULAR); return array_merge($matrix); } /** * 字节转格式 * @param $bytes * @return string */ public static function readableBytes($bytes) { $i = floor(log($bytes) / log(1024)); $sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i]; } /** * 去除emoji表情 * @param $str * @return string|string[]|null */ public static function filterEmoji($str) { return preg_replace_callback( '/./u', function (array $match) { return strlen($match[0]) >= 4 ? '' : $match[0]; }, $str); } /** * 统一验证器 * @param $data * @param $messages */ public static function validator($data, $messages) { $rules = []; foreach ($messages as $key => $item) { $keys = explode(".", $key); if (isset($keys[1])) { if (isset($rules[$keys[0]])) { $rules[$keys[0]] = $rules[$keys[0]] . '|' . $keys[1]; } else { $rules[$keys[0]] = $keys[1]; } } } $validator = Validator::make($data, $rules, $messages); if ($validator->fails()) { throw new ApiException($validator->errors()->first()); } } /** * DownloadFileResponse 下载文件 * @param File|\SplFileInfo|string $file 文件对象或路径 * @param string|null $name 下载文件名 * @return StreamedResponse */ public static function DownloadFileResponse($file, $name = null) { try { // 处理文件对象 if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { $file = new File($file->getPathname()); } else { $file = new File((string)$file); } } // 检查文件是否可读和存在 if (!$file->isReadable() || !$file->isFile()) { throw new FileException('File must be readable and exist.'); } // 获取文件信息 $size = $file->getSize(); if ($size === false || $size < 0) { throw new FileException('Unable to determine file size.'); } // 处理文件名 if (empty($name)) { $name = basename($file->getPathname()); } elseif (!str_contains($name, '.')) { $name .= '.' . $file->getExtension(); } // 文件名安全处理 $name = Base::cutStr($name, 180); $name = str_replace(['"', '<', '>', '|', '/', '\\', '?', ':'], '', $name); // 获取MIME类型 $mimeType = $file->getMimeType(); if (empty($mimeType)) { $mimeType = 'application/octet-stream'; } // 处理 Range 请求 $start = 0; $end = $size - 1; $length = $size; $isRangeRequest = false; if (isset($_SERVER['HTTP_RANGE'])) { $range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']); if (preg_match('/^(\d+)-(\d*)$/', $range, $matches)) { $start = intval($matches[1]); $end = !empty($matches[2]) ? intval($matches[2]) : $size - 1; // 验证范围的有效性 if ($start >= 0 && $end < $size && $start <= $end) { $length = $end - $start + 1; $isRangeRequest = true; } else { $start = 0; $end = $size - 1; } } } // 设置基本响应头 $headers = [ 'Content-Type' => $mimeType, 'Content-Disposition' => sprintf( 'attachment; filename="%s"; filename*=UTF-8\'\'%s', $name, rawurlencode($name) ), 'Accept-Ranges' => 'bytes', 'Cache-Control' => 'private, no-transform, no-store, must-revalidate, max-age=0', 'Content-Length' => $length, 'Last-Modified' => gmdate('D, d M Y H:i:s', $file->getMTime()) . ' GMT', 'ETag' => sprintf('"%s"', md5_file($file->getPathname())) ]; if ($isRangeRequest) { $headers['Content-Range'] = "bytes {$start}-{$end}/{$size}"; $statusCode = 206; } else { $statusCode = 200; } // 创建流式响应 return new StreamedResponse( function () use ($file, $start, $length) { $handle = fopen($file->getPathname(), 'rb'); if ($handle === false) { throw new FileException('Cannot open file for reading'); } if (fseek($handle, $start) === -1) { fclose($handle); throw new FileException('Cannot seek to position ' . $start); } $remaining = $length; $bufferSize = 8192; // 8KB chunks while ($remaining > 0 && !feof($handle)) { $readSize = min($bufferSize, $remaining); $buffer = fread($handle, $readSize); if ($buffer === false) { break; } echo $buffer; flush(); $remaining -= strlen($buffer); } fclose($handle); }, $statusCode, $headers ); } catch (\Exception $e) { \Log::error('File download failed', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'file' => $file ?? null, 'name' => $name ?? null, 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null, 'ip' => request()->ip(), 'range' => $_SERVER['HTTP_RANGE'] ?? null ]); abort(403, 'File download failed'); } } /** * 保存图片到文件(同时压缩) * @param $path * @param $content * @return bool */ public static function saveContentImage($path, $content) { if (file_put_contents($path, $content)) { Image::compressImage($path); return true; } return false; } /** * 多维数组字母转下划线格式 * @param $array * @return array */ public static function arrayKeyToUnderline($array) { $newArray = []; foreach ($array as $key => $value) { //如果是数组,递归调用 if (is_array($value)) { $value = self::arrayKeyToUnderline($value); } $newKey = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $key)); $newArray[$newKey] = $value; } return $newArray; } /** * 多维数组字母转驼峰格式 * * @param [type] $array * @return array */ public static function arrayKeyToCamel($array) { $newArray = []; foreach ($array as $key => $value) { //如果是数组,递归调用 if (is_array($value)) { $value = self::arrayKeyToCamel($value); } $newKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))); $newArray[$newKey] = $value; } return $newArray; } /** * MD(markdown) 转 html * @param $markdown * @return \League\CommonMark\Output\RenderedContentInterface|mixed */ public static function markdown2html($markdown) { try { $converter = new CommonMarkConverter(); return $converter->convert($markdown); } catch (\League\CommonMark\Exception\CommonMarkException $e) { return $markdown; } } /** * html 转 MD(markdown) * @param $html * @return mixed|string */ public static function html2markdown($html) { try { $converter = new HtmlConverter(); return $converter->convert($html); } catch (\Exception) { return $html; } } }