2238 lines
59 KiB
PHP
2238 lines
59 KiB
PHP
<?php
|
||
// 应用公共文件
|
||
// 除了 E_NOTICE,报告其他所有错误
|
||
//error_reporting(E_ALL ^ E_NOTICE);
|
||
//error_reporting(E_ERROR | E_WARNING | E_PARSE);
|
||
error_reporting(E_NOTICE);
|
||
|
||
use extend\QRcode as QRcode;
|
||
use think\facade\Session;
|
||
use think\facade\Event;
|
||
use think\facade\App;
|
||
use app\model\system\Addon;
|
||
use extend\Barcode;
|
||
use app\common\library\CallerInfo;
|
||
|
||
/*****************************************************基础函数*********************************************************/
|
||
|
||
/**
|
||
* 生成编号
|
||
* @param string $prefix
|
||
* @param string $tag 业务标识 例如member_id ...
|
||
* @return string
|
||
* @throws Exception
|
||
*/
|
||
function create_no($prefix = '', $tag = '')
|
||
{
|
||
$data_center_id = 1;
|
||
$machine_id = 2;
|
||
$snowflake = new \extend\util\Snowflake($data_center_id, $machine_id);
|
||
$id = $snowflake->generateId();
|
||
return $prefix . date('Ymd') . $tag . $id;
|
||
}
|
||
/**
|
||
* 把返回的数据集转换成Tree
|
||
*
|
||
* @param array $list
|
||
* 要转换的数据集
|
||
* @param string $pid
|
||
* parent标记字段
|
||
* @param string $level
|
||
* level标记字段
|
||
* @return array
|
||
*/
|
||
function list_to_tree($list, $pk = 'id', $pid = 'pid', $child = '_child', $root = 0)
|
||
{
|
||
// 创建Tree
|
||
$tree = [];
|
||
if (!is_array($list)) :
|
||
return false;
|
||
|
||
endif;
|
||
// 创建基于主键的数组引用
|
||
$refer = [];
|
||
foreach ($list as $key => $data) {
|
||
$refer[ $data[ $pk ] ] = &$list[ $key ];
|
||
$refer[ $data[ $pk ] ][ $child ] = [];
|
||
$refer[ $data[ $pk ] ][ 'child_num' ] = 0;
|
||
}
|
||
foreach ($refer as $key => $data) {
|
||
// 判断是否存在parent
|
||
$parentId = $data[ $pid ];
|
||
if ($root == $parentId) {
|
||
$tree[ $key ] = &$refer[ $key ];
|
||
} else if (isset($refer[ $parentId ])) {
|
||
is_object($refer[ $parentId ]) && $refer[ $parentId ] = $refer[ $parentId ]->toArray();
|
||
$parent = &$refer[ $parentId ];
|
||
$parent[ $child ][ $key ] = &$refer[ $key ];
|
||
$parent[ 'child_num' ]++;
|
||
}
|
||
}
|
||
return $tree;
|
||
}
|
||
|
||
/**
|
||
* 读取csv的内容到数组
|
||
* @param string $uploadfile
|
||
* @return array|mixed
|
||
*/
|
||
function readCsv($uploadfile)
|
||
{
|
||
$file = fopen($uploadfile, "r");
|
||
while (!feof($file)) {
|
||
$data[] = fgetcsv($file);
|
||
}
|
||
$data = eval('return ' . iconv('gbk', 'utf-8', var_export($data, true)) . ';');
|
||
foreach ($data as $key => $value) {
|
||
if (!$value) {
|
||
unset($data[ $key ]);
|
||
}
|
||
}
|
||
fclose($file);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 将list_to_tree的树还原成列表
|
||
*
|
||
* @param array $tree
|
||
* 原来的树
|
||
* @param string $child
|
||
* 孩子节点的键
|
||
* @param string $order
|
||
* 排序显示的键,一般是主键 升序排列
|
||
* @param array $list
|
||
* 过渡用的中间数组,
|
||
* @return array 返回排过序的列表数组
|
||
*/
|
||
function tree_to_list($tree, $child = '_child', $order = 'id', &$list = array ())
|
||
{
|
||
if (is_array($tree)) {
|
||
foreach ($tree as $key => $value) {
|
||
$reffer = $value;
|
||
if (isset($reffer[ $child ])) {
|
||
unset($reffer[ $child ]);
|
||
tree_to_list($value[ $child ], $child, $order, $list);
|
||
}
|
||
$list[] = $reffer;
|
||
}
|
||
$list = list_sort_by($list, $order, $sortby = 'asc');
|
||
}
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* 对查询结果集进行排序
|
||
*
|
||
* @access public
|
||
* @param array $list
|
||
* 查询结果
|
||
* @param string $field
|
||
* 排序的字段名
|
||
* @param array $sortby
|
||
* 排序类型
|
||
* asc正向排序 desc逆向排序 nat自然排序
|
||
* @return array
|
||
*/
|
||
function list_sort_by($list, $field, $sortby = 'asc')
|
||
{
|
||
if (is_array($list)) {
|
||
$refer = $resultSet = array ();
|
||
foreach ($list as $i => $data)
|
||
$refer[ $i ] = &$data[ $field ];
|
||
switch ( $sortby ) {
|
||
case 'asc': // 正向排序
|
||
asort($refer);
|
||
break;
|
||
case 'desc': // 逆向排序
|
||
arsort($refer);
|
||
break;
|
||
case 'nat': // 自然排序
|
||
natcasesort($refer);
|
||
break;
|
||
}
|
||
foreach ($refer as $key => $val)
|
||
$resultSet[] = &$list[ $key ];
|
||
return $resultSet;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 对象转化为数组
|
||
* @param object $obj
|
||
*/
|
||
function object_to_array($obj)
|
||
{
|
||
if (is_object($obj)) {
|
||
$obj = (array) $obj;
|
||
}
|
||
if (is_array($obj)) {
|
||
foreach ($obj as $key => $value) {
|
||
$obj[ $key ] = object_to_array($value);
|
||
}
|
||
}
|
||
return $obj;
|
||
}
|
||
|
||
/**
|
||
* 系统加密方法
|
||
*
|
||
* @param string $data
|
||
* 要加密的字符串
|
||
* @param string $key
|
||
* 加密密钥
|
||
* @param int $expire
|
||
* 过期时间 单位 秒
|
||
* @return string
|
||
*/
|
||
function encrypt($data, $key = '', $expire = 0)
|
||
{
|
||
$key = md5(empty ($key) ? 'luckysaas098)(*' : $key);
|
||
|
||
$data = base64_encode($data);
|
||
$x = 0;
|
||
$len = strlen($data);
|
||
$l = strlen($key);
|
||
$char = '';
|
||
|
||
for ($i = 0; $i < $len; $i++) {
|
||
if ($x == $l)
|
||
$x = 0;
|
||
$char .= substr($key, $x, 1);
|
||
$x++;
|
||
}
|
||
|
||
$str = sprintf('%010d', $expire ? $expire + time() : 0);
|
||
|
||
for ($i = 0; $i < $len; $i++) {
|
||
$str .= chr(ord(substr($data, $i, 1)) + ( ord(substr($char, $i, 1)) ) % 256);
|
||
}
|
||
return str_replace(array (
|
||
'+',
|
||
'/',
|
||
'='
|
||
), array (
|
||
'-',
|
||
'_',
|
||
''
|
||
), base64_encode($str));
|
||
}
|
||
|
||
/**
|
||
* 系统解密方法
|
||
*
|
||
* @param string $data
|
||
* 要解密的字符串 (必须是encrypt方法加密的字符串)
|
||
* @param string $key
|
||
* 加密密钥
|
||
* @return string
|
||
*/
|
||
function decrypt($data, $key = '')
|
||
{
|
||
$key = md5(empty ($key) ? 'luckysaas098)(*' : $key);
|
||
$data = str_replace(array (
|
||
'-',
|
||
'_'
|
||
), array (
|
||
'+',
|
||
'/'
|
||
), $data);
|
||
$mod4 = strlen($data) % 4;
|
||
if ($mod4) {
|
||
$data .= substr('====', $mod4);
|
||
}
|
||
$data = base64_decode($data);
|
||
$expire = substr($data, 0, 10);
|
||
$data = substr($data, 10);
|
||
|
||
if ($expire > 0 && $expire < time()) {
|
||
return '';
|
||
}
|
||
$x = 0;
|
||
$len = strlen($data);
|
||
$l = strlen($key);
|
||
$char = $str = '';
|
||
|
||
for ($i = 0; $i < $len; $i++) {
|
||
if ($x == $l)
|
||
$x = 0;
|
||
$char .= substr($key, $x, 1);
|
||
$x++;
|
||
}
|
||
|
||
for ($i = 0; $i < $len; $i++) {
|
||
if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
|
||
$str .= chr(( ord(substr($data, $i, 1)) + 256 ) - ord(substr($char, $i, 1)));
|
||
} else {
|
||
$str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
|
||
}
|
||
}
|
||
return base64_decode($str);
|
||
}
|
||
|
||
/**
|
||
* 数据签名认证
|
||
*/
|
||
function data_auth_sign($data)
|
||
{
|
||
// 数据类型检测
|
||
if (!is_array($data)) {
|
||
$data = (array) $data;
|
||
}
|
||
ksort($data); // 排序
|
||
$code = http_build_query($data); // url编码并生成query字符串
|
||
$sign = sha1($code); // 生成签名
|
||
return $sign;
|
||
}
|
||
|
||
/**
|
||
* 重写md5加密方式
|
||
*
|
||
* @param string $str
|
||
* @return string
|
||
*/
|
||
function data_md5($str)
|
||
{
|
||
return '' === $str ? '' : md5(md5($str) . 'LuckySaas');
|
||
}
|
||
|
||
/**
|
||
* 时间戳转时间
|
||
*/
|
||
function time_to_date($time_stamp, $format = 'Y-m-d H:i:s')
|
||
{
|
||
if ($time_stamp > 0) {
|
||
$time = date($format, $time_stamp);
|
||
} else {
|
||
$time = "";
|
||
}
|
||
return $time;
|
||
}
|
||
|
||
/**
|
||
* 时间转时间戳
|
||
*/
|
||
function date_to_time($date)
|
||
{
|
||
$time_stamp = strtotime($date);
|
||
return $time_stamp;
|
||
}
|
||
|
||
/**
|
||
* 获取唯一随机字符串
|
||
*/
|
||
function unique_random($len = 10)
|
||
{
|
||
$str = 'qwertyuiopasdfghjklzxcvbnm';
|
||
str_shuffle($str);
|
||
$res = 'nc_' . substr(str_shuffle($str), 0, $len) . date('is');
|
||
return $res;
|
||
}
|
||
|
||
/**
|
||
* 生成随机数
|
||
* @param int $length
|
||
* @return string
|
||
*/
|
||
function random_keys($length)
|
||
{
|
||
// $pattern = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ';
|
||
$pattern = array (
|
||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
|
||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
|
||
);
|
||
$keys = array_rand($pattern, $length);
|
||
$key = '';
|
||
for ($i = 0; $i < $length; $i++) {
|
||
$key .= $pattern[ $keys[ $i ] ]; //生成php随机数
|
||
}
|
||
return $key;
|
||
}
|
||
|
||
|
||
/**
|
||
* 发送HTTP请求方法,目前只支持CURL发送请求
|
||
*
|
||
* @param string $url
|
||
* 请求URL
|
||
* @param array $params
|
||
* 请求参数
|
||
* @param string $method
|
||
* 请求方法GET/POST
|
||
* @return array $data 响应数据
|
||
*/
|
||
function http($url, $timeout = 30, $header = array ())
|
||
{
|
||
if (!function_exists('curl_init')) {
|
||
throw new Exception('server not install curl');
|
||
}
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||
curl_setopt($ch, CURLOPT_URL, $url);
|
||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727;)');
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 关闭 SSL 证书校验
|
||
|
||
if (!empty($header)) {
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||
}
|
||
$data = curl_exec($ch);
|
||
if ($data && is_array(explode("\r\n\r\n", $data))) {
|
||
list ($header, $data) = explode("\r\n\r\n", $data);
|
||
} else {
|
||
$header = explode("\r\n\r\n", $data)[ 0 ];
|
||
$data = [];
|
||
}
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
if ($http_code == 301 || $http_code == 302) {
|
||
$matches = array ();
|
||
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||
$url = trim(array_pop($matches));
|
||
curl_setopt($ch, CURLOPT_URL, $url);
|
||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||
$data = curl_exec($ch);
|
||
}
|
||
|
||
if ($data == false) {
|
||
curl_close($ch);
|
||
}
|
||
@curl_close($ch);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 替换数组元素
|
||
* @param array $array 数组
|
||
* @param array $replace 替换元素['key' => 'value', 'key' => 'value']
|
||
*/
|
||
function replace_array_element($array, $replace)
|
||
{
|
||
foreach ($replace as $k => $v) {
|
||
if ($v == "unset" || $v == "") {
|
||
unset($array[ $k ]);
|
||
} else {
|
||
$array[ $k ] = $v;
|
||
}
|
||
}
|
||
|
||
return $array;
|
||
}
|
||
|
||
|
||
/**
|
||
* 过滤特殊符号
|
||
* @param $string
|
||
* @return array|string|string[]|null
|
||
*/
|
||
function ihtmlspecialchars($string)
|
||
{
|
||
if (is_array($string)) {
|
||
foreach ($string as $key => $val) {
|
||
$string[ $key ] = ihtmlspecialchars($val);
|
||
}
|
||
} else {
|
||
$string = preg_replace('/&((#(d{3,5}|x[a-fa-f0-9]{4})|[a-za-z][a-z0-9]{2,5});)/', '&\1',
|
||
str_replace(array ( '&', '"', '<', '>' ), array ( '&', '"', '<', '>' ), $string));
|
||
}
|
||
return $string;
|
||
}
|
||
|
||
/********************************************* 插件,站点相关函数 ************************************************************************************
|
||
*
|
||
* /**
|
||
* 插件显示内容里生成访问插件的url
|
||
*
|
||
* @param string $url
|
||
* @param array $param
|
||
* 参数格式:addon_url('HelloWorld://sitehome/Game/index', [])
|
||
*/
|
||
function addon_url($url, $param = array ())
|
||
{
|
||
if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) {
|
||
return $url;
|
||
}
|
||
$parse_url = parse_url($url);
|
||
$addon = $parse_url['scheme'] ?? '';
|
||
$controller = $parse_url['host'] ?? '';
|
||
$action = trim($parse_url[ 'path' ], '/');
|
||
/* 解析URL带的参数 */
|
||
if (isset($parse_url[ 'query' ])) {
|
||
parse_str($parse_url[ 'query' ], $query);
|
||
$param = array_merge($query, $param);
|
||
}
|
||
$url = $addon . '/' . $controller . '/' . $action;
|
||
if (empty($addon)) {
|
||
$url = $controller . '/' . $action;
|
||
if (empty($controller)) {
|
||
$url = $action;
|
||
}
|
||
}
|
||
|
||
return url($url, $param);
|
||
}
|
||
|
||
|
||
/**
|
||
* Url生成(重写url函数)
|
||
* @param string $url 路由地址
|
||
*/
|
||
function url(string $url = '', $vars = [])
|
||
{
|
||
|
||
if (!empty($vars)) {
|
||
if (is_array($vars)) {
|
||
$vars = http_build_query($vars);
|
||
}
|
||
$tag = REWRITE_MODULE ? '?' : '&';
|
||
$var_url = $tag . $vars;
|
||
} else {
|
||
|
||
$var_url = '';
|
||
}
|
||
$url = $url . '.html';
|
||
$url_arr = explode("/", $url);
|
||
foreach ($url_arr as $key => $val) {
|
||
if ($val == "shop") {
|
||
$url_arr[ $key ] = SHOP_MODULE;
|
||
break;
|
||
}
|
||
}
|
||
$url = implode("/", $url_arr);
|
||
// $url = str_replace("shop", SHOP_MODULE, $url); //针对输入
|
||
return ROOT_URL . '/' . $url . $var_url;
|
||
}
|
||
|
||
/**
|
||
* Url生成(重写url函数) 获取不含html的url
|
||
* @param string $url 路由地址
|
||
*/
|
||
function getUrl(string $url = '', $vars = [])
|
||
{
|
||
if (!empty($vars)) {
|
||
if (is_array($vars)) {
|
||
$vars = http_build_query($vars);
|
||
}
|
||
$tag = REWRITE_MODULE ? '?' : '&';
|
||
$var_url = $tag . $vars;
|
||
} else {
|
||
|
||
$var_url = '';
|
||
}
|
||
// $url = $url . '.html';
|
||
$url_arr = explode("/", $url);
|
||
foreach ($url_arr as $key => $val) {
|
||
if ($val == "shop") {
|
||
$url_arr[ $key ] = SHOP_MODULE;
|
||
break;
|
||
}
|
||
}
|
||
$url = implode("/", $url_arr);
|
||
// $url = str_replace("shop", SHOP_MODULE, $url); //针对输入
|
||
return ROOT_URL . '/' . $url . $var_url;
|
||
|
||
}
|
||
|
||
/**
|
||
* 解析url的插件,模块,控制器,方法
|
||
* @param unknown $url
|
||
*/
|
||
function url_action($url)
|
||
{
|
||
if (empty($url)) {
|
||
return [
|
||
'addon' => '',
|
||
'model' => 'index',
|
||
'controller' => 'index',
|
||
'action' => 'index'
|
||
];
|
||
}
|
||
if (!strstr($url, '://')) {
|
||
$url_array = explode('/', $url);
|
||
return [
|
||
'addon' => '',
|
||
'model' => $url_array[ 0 ],
|
||
'controller' => $url_array[ 1 ],
|
||
'action' => $url_array[ 2 ]
|
||
];
|
||
} else {
|
||
|
||
$url_addon_array = explode('://', $url);
|
||
$addon = $url_addon_array[ 0 ];
|
||
$url_array = explode('/', $url_addon_array[ 1 ]);
|
||
return [
|
||
'addon' => $addon,
|
||
'model' => $url_array[ 0 ],
|
||
'controller' => $url_array[ 1 ],
|
||
'action' => $url_array[ 2 ]
|
||
];
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* @param $url
|
||
* @param array $param
|
||
* @return string
|
||
*/
|
||
function hash_url($url, $param = array ())
|
||
{
|
||
if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) {
|
||
return $url;
|
||
}
|
||
|
||
$parse_url = parse_url($url);
|
||
|
||
$url = explode('?', $url)[ 0 ];
|
||
|
||
/* 解析URL带的参数 */
|
||
if (isset($parse_url[ 'query' ])) {
|
||
parse_str($parse_url[ 'query' ], $query);
|
||
$param = array_merge($query, $param);
|
||
}
|
||
|
||
$hash = "url={$url}";
|
||
if (!empty($param)) {
|
||
foreach ($param as $k => $v) {
|
||
$hash .= "&{$k}={$v}";
|
||
}
|
||
}
|
||
return $hash;
|
||
|
||
}
|
||
|
||
/**
|
||
* 链接跳转
|
||
* @param $url
|
||
* @param array $param
|
||
* @return string
|
||
*/
|
||
function href_url($url, $param = array ())
|
||
{
|
||
return ROOT_URL . '/shop.html#' . hash_url($url, $param);
|
||
}
|
||
|
||
/**
|
||
* 检测插件是否存在
|
||
* @param string $name
|
||
* @return number
|
||
*/
|
||
function addon_is_exit($name, $site_id = 0)
|
||
{
|
||
$addon_model = new Addon();
|
||
$addon_data = $addon_model->getAddonList([], 'name');
|
||
$addons = array_column($addon_data[ 'data' ], 'name');
|
||
if (in_array($name, $addons)) {
|
||
return 1;
|
||
} else {
|
||
return 0;
|
||
}
|
||
|
||
}
|
||
|
||
/***************************************************LuckySaaS系统函数***************************************************/
|
||
/**
|
||
* 处理事件
|
||
*
|
||
* @param string $event
|
||
* 钩子名称
|
||
* @param mixed $args
|
||
* 传入参数
|
||
* @param bool $once
|
||
* 只获取一个有效返回值
|
||
* @return void
|
||
*/
|
||
function event($event, $args = [], $once = false)
|
||
{
|
||
$res = Event::trigger($event, $args);
|
||
if (is_array($res)) {
|
||
$res = array_filter($res);
|
||
sort($res);
|
||
}
|
||
//只返回一个结果集
|
||
if ($once) {
|
||
return $res[0] ?? '';
|
||
}
|
||
return $res;
|
||
|
||
}
|
||
|
||
/**
|
||
* 错误返回值函数
|
||
* @param int $code
|
||
* @param string $message
|
||
* @param array|string $data
|
||
* @return array
|
||
*/
|
||
function error($code = -1, $message = '', $data = '')
|
||
{
|
||
return [
|
||
'code' => $code,
|
||
'message' => $message,
|
||
'data' => $data
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 返回值函数
|
||
* @param int $code
|
||
* @param string $message
|
||
* @param array|string $data
|
||
* @return array
|
||
*/
|
||
function success($code = 0, $message = '', $data = '')
|
||
{
|
||
return [
|
||
'code' => $code,
|
||
'message' => $message,
|
||
'data' => $data
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 实例化Model
|
||
* @param string $table
|
||
* @param array $option
|
||
* @return \app\model\Model
|
||
*/
|
||
function model($table = '', $option = [])
|
||
{
|
||
return new \app\model\Model($table, $option);
|
||
}
|
||
|
||
/**
|
||
* 获取带有表前缀的表名
|
||
* @param string $table
|
||
*/
|
||
function table($table = '')
|
||
{
|
||
return config('database.connections.prefix') . $table;
|
||
}
|
||
|
||
/**
|
||
* 获取图片的真实路径
|
||
*
|
||
* @param string $path 图片初始路径
|
||
* @param string $type 类型 big、mid、small
|
||
* @return string 图片的真实路径
|
||
*/
|
||
function img($path, $type = '')
|
||
{
|
||
$start = strripos($path, '.');
|
||
$type = $type ? '_' . strtoupper($type) : '';
|
||
$first = explode("/", $path);
|
||
|
||
$path = substr_replace($path, $type, $start, 0);
|
||
// 处理商品助手的图片路径
|
||
$path = str_replace('addons/NsGoodsAssist/', '', $path);
|
||
$path = str_replace('shop/goods/', '', $path);
|
||
if (stristr($path, "http://") === false && stristr($path, "https://") === false) {
|
||
if (is_numeric($first[ 0 ])) {
|
||
$true_path = __ROOT__ . '/upload/' . $path;
|
||
} else {
|
||
$true_path = __ROOT__ . '/' . $path;
|
||
}
|
||
} else {
|
||
$true_path = $path;
|
||
}
|
||
return $true_path;
|
||
}
|
||
|
||
/**
|
||
* 获取标准二维码格式
|
||
*
|
||
* @param string $url
|
||
* @param string $path
|
||
* @param $qrcode_name
|
||
* @param int $size
|
||
* @return string
|
||
*/
|
||
function qrcode($url, $path, $qrcode_name, $size = 4)
|
||
{
|
||
if (!is_dir($path)) {
|
||
$mode = intval('0777', 8);
|
||
mkdir($path, $mode, true);
|
||
chmod($path, $mode);
|
||
}
|
||
$path = $path . '/' . $qrcode_name . '.png';
|
||
if (file_exists($path)) {
|
||
unlink($path);
|
||
}
|
||
QRcode::png($url, $path, '', $size, 1);
|
||
return $path;
|
||
}
|
||
|
||
/**
|
||
* 前端页面api请求(通过api接口实现)
|
||
* @param string $method
|
||
* @param array $params
|
||
* @return mixed
|
||
*/
|
||
function api($method, $params = [])
|
||
{
|
||
//本地访问
|
||
$data = get_api_data($method, $params);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 获取Api类
|
||
*
|
||
* @param string $method
|
||
*/
|
||
function get_api_data($method, $params)
|
||
{
|
||
$method_array = explode('.', $method);
|
||
if ($method_array[ 0 ] == 'System') {
|
||
$class_name = 'app\\api\\controller\\' . $method_array[ 1 ];
|
||
if (!class_exists($class_name)) {
|
||
return error();
|
||
}
|
||
$api_model = new $class_name($params);
|
||
} else {
|
||
$class_name = "addon\\{$method_array[0]}\\api\\controller\\" . $method_array[ 1 ];
|
||
|
||
if (!class_exists($class_name)) {
|
||
return error();
|
||
}
|
||
$api_model = new $class_name($params);
|
||
}
|
||
$function = $method_array[ 2 ];
|
||
$data = $api_model->$function($params);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 根据年份计算生肖
|
||
* @param unknown $year
|
||
*/
|
||
function get_zodiac($year)
|
||
{
|
||
$animals = array (
|
||
'鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'
|
||
);
|
||
$key = ( $year - 1900 ) % 12;
|
||
return $animals[ $key ];
|
||
}
|
||
|
||
/**
|
||
* 计算.星座
|
||
* @param int $month 月份
|
||
* @param int $day 日期
|
||
* @return str
|
||
*/
|
||
function get_constellation($month, $day)
|
||
{
|
||
$constellations = array (
|
||
'水瓶座', '双鱼座', '白羊座', '金牛座', '双子座', '巨蟹座',
|
||
'狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座'
|
||
);
|
||
if ($day <= 22) {
|
||
if (1 != $month) {
|
||
$constellation = $constellations[ $month - 2 ];
|
||
} else {
|
||
$constellation = $constellations[ 11 ];
|
||
}
|
||
} else {
|
||
$constellation = $constellations[ $month - 1 ];
|
||
}
|
||
return $constellation;
|
||
}
|
||
|
||
/**
|
||
* 数组键名转化为数字
|
||
* @param $data
|
||
* @param $clild_name
|
||
* @return array
|
||
*/
|
||
function arr_key_to_int($data, $clild_name)
|
||
{
|
||
$temp_data = array_values($data);
|
||
foreach ($temp_data as $k => $v) {
|
||
if (!empty($v[ $clild_name ])) {
|
||
$temp_data[ $k ][ $clild_name ] = arr_key_to_int($v[ $clild_name ], $clild_name);
|
||
}
|
||
}
|
||
return $temp_data;
|
||
}
|
||
|
||
/**
|
||
* 以天为单位 计算间隔内的日期数组
|
||
* @param $start_time
|
||
* @param $end_time
|
||
* @param string $format
|
||
* @return array
|
||
*/
|
||
function period_group($start_time, $end_time, $format = 'Ymd')
|
||
{
|
||
$type_time = 3600 * 24;
|
||
$data = [];
|
||
for ($i = $start_time; $i <= $end_time; $i += $type_time) {
|
||
$data[] = date($format, $i);
|
||
}
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 数组删除另一个数组
|
||
* @param $arr
|
||
* @param $del_arr
|
||
* @return mixed
|
||
*/
|
||
function arr_del_arr($arr, $del_arr)
|
||
{
|
||
foreach ($arr as $k => $v) {
|
||
if (in_array($v, $del_arr)) {
|
||
unset($arr[ $k ]);
|
||
}
|
||
}
|
||
sort($arr);
|
||
return $arr;
|
||
}
|
||
|
||
|
||
/**
|
||
* 检测登录(应用于h5网页检测登录)
|
||
* @param unknown $url
|
||
*/
|
||
function check_auth($url = '')
|
||
{
|
||
$access_token = Session::get("access_token_" . request()->siteid());
|
||
if (empty($access_token)) {
|
||
if (!empty($url)) {
|
||
Session::set("redirect_login_url", $url);
|
||
}
|
||
//尚未登录(直接跳转)
|
||
return error(url('wap/login/login'));
|
||
}
|
||
$member_info = cache("member_info_" . request()->siteid() . $access_token);
|
||
if (empty($member_info)) {
|
||
$member_info = api("System.Member.memberInfo", [ 'access_token' => $access_token ]);
|
||
if ($member_info[ 'code' ] == 0) {
|
||
$member_info = $member_info[ 'data' ];
|
||
cache("member_info_" . request()->siteid() . $access_token, $member_info);
|
||
}
|
||
}
|
||
$member_info[ 'access_token' ] = $access_token;
|
||
return success($member_info);
|
||
}
|
||
|
||
/**
|
||
* 分割sql语句
|
||
* @param string $content sql内容
|
||
* @param bool $string 如果为真,则只返回一条sql语句,默认以数组形式返回
|
||
* @param array $replace 替换前缀,如:['my_' => 'me_'],表示将表前缀my_替换成me_
|
||
* @return array|string 除去注释之后的sql语句数组或一条语句
|
||
*/
|
||
function parse_sql($content = '', $string = false, $replace = [])
|
||
{
|
||
// 纯sql内容
|
||
$pure_sql = [];
|
||
// 被替换的前缀
|
||
$from = '';
|
||
// 要替换的前缀
|
||
$to = '';
|
||
// 替换表前缀
|
||
if (!empty($replace)) {
|
||
$to = current($replace);
|
||
$from = current(array_flip($replace));
|
||
}
|
||
if ($content != '') {
|
||
// 多行注释标记
|
||
$comment = false;
|
||
// 按行分割,兼容多个平台
|
||
$content = str_replace([ "\r\n", "\r" ], "\n", $content);
|
||
$content = explode("\n", trim($content));
|
||
// 循环处理每一行
|
||
foreach ($content as $key => $line) {
|
||
// 跳过空行
|
||
if ($line == '') {
|
||
continue;
|
||
}
|
||
// 跳过以#或者--开头的单行注释
|
||
if (preg_match("/^(#|--)/", $line)) {
|
||
continue;
|
||
}
|
||
// 跳过以/**/包裹起来的单行注释
|
||
if (preg_match("/^\/\*(.*?)\*\//", $line)) {
|
||
continue;
|
||
}
|
||
// 多行注释开始
|
||
if (substr($line, 0, 2) == '/*') {
|
||
$comment = true;
|
||
continue;
|
||
}
|
||
// 多行注释结束
|
||
if (substr($line, -2) == '*/') {
|
||
$comment = false;
|
||
continue;
|
||
}
|
||
// 多行注释没有结束,继续跳过
|
||
if ($comment) {
|
||
continue;
|
||
}
|
||
// 替换表前缀
|
||
if ($from != '') {
|
||
$line = str_replace('`' . $from, '`' . $to, $line);
|
||
}
|
||
// sql语句
|
||
$pure_sql[] = $line;
|
||
}
|
||
// 只返回一条语句
|
||
if ($string) {
|
||
return implode("", $pure_sql);
|
||
}
|
||
// 以数组形式返回sql语句
|
||
$pure_sql = implode("\n", $pure_sql);
|
||
$pure_sql = explode(";\n", $pure_sql);
|
||
}
|
||
return $pure_sql;
|
||
}
|
||
|
||
/**
|
||
* 执行sql
|
||
* @param string $sql_name
|
||
*/
|
||
function execute_sql($sql_name)
|
||
{
|
||
$sql_string = file_get_contents($sql_name);
|
||
$sql_string = str_replace("{{prefix}}", config("database.connections.mysql.prefix"), $sql_string);
|
||
if ($sql_string) {
|
||
$sql = explode(";\n", str_replace("\r", "\n", $sql_string));
|
||
foreach ($sql as $value) {
|
||
$value = trim($value);
|
||
if (!empty($value)) {
|
||
\think\facade\Db::execute($value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检测目录读写权限
|
||
*/
|
||
function check_dir_iswritable($dir)
|
||
{
|
||
$testDir = $dir;
|
||
sp_dir_create($testDir);
|
||
if (sp_testwrite($testDir)) {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查测试文件是否可写入
|
||
*/
|
||
function sp_testwrite($d)
|
||
{
|
||
$tfile = "_test.txt";
|
||
$fp = @fopen($d . "/" . $tfile, "w");
|
||
if (!$fp) {
|
||
return false;
|
||
}
|
||
fclose($fp);
|
||
$rs = @unlink($d . "/" . $tfile);
|
||
if ($rs) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 检查文件是否创建
|
||
*/
|
||
function sp_dir_create($path, $mode = 0777)
|
||
{
|
||
if (is_dir($path))
|
||
return true;
|
||
$ftp_enable = 0;
|
||
$path = sp_dir_path($path);
|
||
$temp = explode('/', $path);
|
||
$cur_dir = '';
|
||
$max = count($temp) - 1;
|
||
for ($i = 0; $i < $max; $i++) {
|
||
$cur_dir .= $temp[ $i ] . '/';
|
||
if (@is_dir($cur_dir))
|
||
continue;
|
||
@mkdir($cur_dir, 0777, true);
|
||
@chmod($cur_dir, 0777);
|
||
}
|
||
return is_dir($path);
|
||
}
|
||
|
||
/**
|
||
* 判断目录是否为空
|
||
* @param $dir
|
||
* @return bool
|
||
*/
|
||
function dir_is_empty($dir)
|
||
{
|
||
$handle = opendir($dir);
|
||
while (false !== ( $entry = readdir($handle) )) {
|
||
if ($entry != "." && $entry != "..") {
|
||
return FALSE;
|
||
}
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* 创建文件夹
|
||
*
|
||
* @param string $path 文件夹路径
|
||
* @param int $mode 访问权限
|
||
* @param bool $recursive 是否递归创建
|
||
* @return bool
|
||
*/
|
||
function dir_mkdir($path = '', $mode = 0777, $recursive = true)
|
||
{
|
||
clearstatcache();
|
||
if (!is_dir($path)) {
|
||
mkdir($path, $mode, $recursive);
|
||
return chmod($path, $mode);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 文件夹文件拷贝
|
||
*
|
||
* @param string $src 来源文件夹
|
||
* @param string $dst 目的地文件夹
|
||
* @return bool
|
||
*/
|
||
function dir_copy($src = '', $dst = '', $ignore_files = [])
|
||
{
|
||
if (empty($src) || empty($dst)) {
|
||
return false;
|
||
}
|
||
$dir = opendir($src);
|
||
dir_mkdir($dst);
|
||
while (false !== ( $file = readdir($dir) )) {
|
||
if (( $file != '.' ) && ( $file != '..' )) {
|
||
if (is_dir($src . '/' . $file)) {
|
||
dir_copy($src . '/' . $file, $dst . '/' . $file);
|
||
} else {
|
||
if (!in_array($file, $ignore_files)) {
|
||
copy($src . '/' . $file, $dst . '/' . $file);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
closedir($dir);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**查询存在目录
|
||
* @param $dir
|
||
* @return bool
|
||
*/
|
||
function sp_exist_dir($dir)
|
||
{
|
||
$is_exist = false;
|
||
$is_write = false;
|
||
while (!$is_exist) {
|
||
$dir = dirname($dir);
|
||
if (is_dir($dir) || $dir == ".") {
|
||
$is_exist = true;
|
||
if (is_writeable($dir)) {
|
||
$is_write = true;
|
||
}
|
||
}
|
||
}
|
||
return $is_write;
|
||
}
|
||
|
||
/**
|
||
* 拼接字符串
|
||
* @param $string
|
||
* @param string $delimiter 分割字符
|
||
* @param $value
|
||
* @return string
|
||
*/
|
||
function string_split($string, $delimiter, $value)
|
||
{
|
||
return empty($string) ? $value : $string . $delimiter . $value;
|
||
}
|
||
|
||
/**
|
||
* $str为要进行截取的字符串,$length为截取长度(汉字算一个字,字母算半个字
|
||
* @param $str
|
||
* @param int $length
|
||
* @param boolean $is_need_apostrophe 是否需要省略号
|
||
* @param string $apostrophe_pos 省略号位置 end 结尾 center 中间
|
||
* @return string
|
||
*/
|
||
function str_sub($str, $length = 10, $is_need_apostrophe = false, $apostrophe_pos = 'end')
|
||
{
|
||
$encoding = 'UTF-8';
|
||
$res_str = $str;
|
||
if(mb_strlen($str, $encoding) > $length){
|
||
if($is_need_apostrophe){
|
||
if($apostrophe_pos == 'end'){
|
||
$res_str = mb_substr($str, 0, $length, $encoding) . '...';
|
||
}else{
|
||
$half_length = floor($length / 2);
|
||
$res_str = mb_substr($str, 0, $half_length, $encoding) . '...' . mb_substr($str, mb_strlen($str, $encoding) - $half_length, $half_length, $encoding);
|
||
}
|
||
}else{
|
||
$res_str = mb_substr($str, 0, $length, 'UTF-8');
|
||
}
|
||
}
|
||
return $res_str;
|
||
}
|
||
|
||
/**
|
||
* 获取字符串长度
|
||
* @param $str
|
||
* @return int
|
||
*/
|
||
function strlen_mb($str)
|
||
{
|
||
return mb_strlen($str, 'UTF-8');
|
||
}
|
||
|
||
/**
|
||
* 删除缓存文件使用
|
||
* @param $dir
|
||
*/
|
||
function rmdirs($dir)
|
||
{
|
||
$dir = 'runtime/' . $dir;
|
||
$dh = opendir($dir);
|
||
while ($file = readdir($dh)) {
|
||
if ($file != "." && $file != "..") {
|
||
$fullpath = $dir . "/" . $file;
|
||
if (is_dir($fullpath)) {
|
||
rmdirs($fullpath);
|
||
} else {
|
||
unlink($fullpath);
|
||
}
|
||
}
|
||
}
|
||
closedir($dh);
|
||
}
|
||
|
||
/**
|
||
* 删除指定目录所有文件和目录
|
||
* @param $path
|
||
*/
|
||
function deleteDir($path)
|
||
{
|
||
if (is_dir($path)) {
|
||
//扫描一个目录内的所有目录和文件并返回数组
|
||
$dirs = scandir($path);
|
||
foreach ($dirs as $dir) {
|
||
//排除目录中的当前目录(.)和上一级目录(..)
|
||
if ($dir != '.' && $dir != '..') {
|
||
//如果是目录则递归子目录,继续操作
|
||
$sonDir = $path . '/' . $dir;
|
||
if (is_dir($sonDir)) {
|
||
//递归删除
|
||
deleteDir($sonDir);
|
||
//目录内的子目录和文件删除后删除空目录
|
||
@rmdir($sonDir);
|
||
} else {
|
||
//如果是文件直接删除
|
||
@unlink($sonDir);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 以天为单位 计算间隔内的日期数组
|
||
* @param $srart_time
|
||
* @param $end_time
|
||
* @param string $format
|
||
* @return array
|
||
*/
|
||
function periodGroup($srart_time, $end_time, $format = 'Ymd')
|
||
{
|
||
$type_time = 3600 * 24;
|
||
$data = [];
|
||
for ($i = $srart_time; $i <= $end_time; $i += $type_time) {
|
||
$data[] = date($format, $i);
|
||
}
|
||
return $data;
|
||
}
|
||
|
||
|
||
//解决个别中文乱码
|
||
function mbStrreplace($content, $to_encoding = "UTF-8", $from_encoding = "GBK")
|
||
{
|
||
$content = mb_convert_encoding($content, $to_encoding, $from_encoding);
|
||
$str = mb_convert_encoding(" ", $to_encoding, $from_encoding);
|
||
$content = mb_eregi_replace($str, " ", $content);
|
||
$content = mb_convert_encoding($content, $from_encoding, $to_encoding);
|
||
$content = trim($content);
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* 将非UTF-8字符集的编码转为UTF-8
|
||
*
|
||
* @param mixed $mixed 源数据
|
||
*
|
||
* @return mixed utf-8格式数据
|
||
*/
|
||
function charset2utf8($mixed)
|
||
{
|
||
if (is_array($mixed)) {
|
||
foreach ($mixed as $k => $v) {
|
||
if (is_array($v)) {
|
||
$mixed[ $k ] = charsetToUTF8($v);
|
||
} else {
|
||
$encode = mb_detect_encoding($v, array ( 'ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5' ));
|
||
if ($encode == 'EUC-CN') {
|
||
$mixed[ $k ] = iconv('GBK', 'UTF-8', $v);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
$encode = mb_detect_encoding($mixed, array ( 'ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5' ));
|
||
if ($encode == 'EUC-CN') {
|
||
$mixed = iconv('GBK', 'UTF-8', $mixed);
|
||
}
|
||
}
|
||
return $mixed;
|
||
}
|
||
|
||
/**
|
||
* 过滤bom
|
||
* @param $filename
|
||
* @return false|string
|
||
*/
|
||
function check_bom($filename)
|
||
{
|
||
$contents = file_get_contents($filename);
|
||
$charset[ 1 ] = substr($contents, 0, 1);
|
||
$charset[ 2 ] = substr($contents, 1, 1);
|
||
$charset[ 3 ] = substr($contents, 2, 1);
|
||
if (ord($charset[ 1 ]) == 239 && ord($charset[ 2 ]) == 187 && ord($charset[ 3 ]) == 191) {
|
||
$rest = substr($contents, 3);
|
||
return $rest;
|
||
} else {
|
||
return $contents;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 判断 文件/目录 是否可写(取代系统自带的 is_writeable 函数)
|
||
*
|
||
* @param string $file 文件/目录
|
||
* @return boolean
|
||
*/
|
||
function is_write($file)
|
||
{
|
||
if (is_dir($file)) {
|
||
$dir = $file;
|
||
if ($fp = @fopen("$dir/test.txt", 'w')) {
|
||
@fclose($fp);
|
||
@unlink("$dir/test.txt");
|
||
$writeable = true;
|
||
} else {
|
||
$writeable = false;
|
||
}
|
||
} else {
|
||
if ($fp = @fopen($file, 'a+')) {
|
||
@fclose($fp);
|
||
$writeable = true;
|
||
} else {
|
||
$writeable = false;
|
||
}
|
||
}
|
||
return $writeable;
|
||
}
|
||
|
||
/**
|
||
* 是否是url链接
|
||
* @param unknown $string
|
||
* @return boolean
|
||
*/
|
||
function is_url($string)
|
||
{
|
||
if (strstr($string, 'http://') === false && strstr($string, 'https://') === false) {
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算两点之间的距离
|
||
* @param double $lng1 经度1
|
||
* @param double $lat1 纬度1
|
||
* @param double $lng2 经度2
|
||
* @param double $lat2 纬度2
|
||
* @param int $unit m,km
|
||
* @param int $decimal 位数
|
||
* @return float 米
|
||
*/
|
||
function getDistance($lng1, $lat1, $lng2, $lat2, $unit = 1, $decimal = 0)
|
||
{
|
||
$EARTH_RADIUS = 6370.996; // 地球半径系数
|
||
$PI = 3.1415926535898;
|
||
|
||
$radLat1 = $lat1 * $PI / 180.0;
|
||
$radLat2 = $lat2 * $PI / 180.0;
|
||
|
||
$radLng1 = $lng1 * $PI / 180.0;
|
||
$radLng2 = $lng2 * $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 /= 1000;
|
||
}
|
||
|
||
return round($distance, $decimal);
|
||
}
|
||
|
||
/**
|
||
*
|
||
* @param unknown $string
|
||
* @return boolean
|
||
*/
|
||
function is_json($string)
|
||
{
|
||
json_decode($string);
|
||
return ( json_last_error() == JSON_ERROR_NONE );
|
||
}
|
||
|
||
/**
|
||
* 获取站点h5域名
|
||
* @param $site_id
|
||
* @return string
|
||
*/
|
||
function getH5Domain()
|
||
{
|
||
// $config = new \app\model\web\Config();
|
||
|
||
// $info = $config->getH5DomainName();
|
||
|
||
// $h5_name = $info[ 'data' ][ 'value' ][ 'domain_name_h5' ];
|
||
|
||
// if ($h5_name) {
|
||
// return $h5_name;
|
||
// }
|
||
|
||
return ROOT_URL . '/hwapp';
|
||
}
|
||
|
||
function get_http_type()
|
||
{
|
||
$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;
|
||
}
|
||
|
||
/**
|
||
* 获取文件地图
|
||
* @param $path
|
||
* @param array $arr
|
||
* @return array
|
||
*/
|
||
function getFileMap($path, $arr = [])
|
||
{
|
||
if (is_dir($path)) {
|
||
$dir = scandir($path);
|
||
foreach ($dir as $file_path) {
|
||
if ($file_path != '.' && $file_path != '..') {
|
||
$temp_path = $path . '/' . $file_path;
|
||
if (is_dir($temp_path)) {
|
||
$arr[ $temp_path ] = $file_path;
|
||
$arr = getFileMap($temp_path, $arr);
|
||
} else {
|
||
$arr[ $temp_path ] = $file_path;
|
||
}
|
||
}
|
||
}
|
||
return $arr;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 判断一个坐标是否在一个多边形内(由多个坐标围成的)
|
||
* 基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
|
||
* 在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
|
||
* @param array $point 指定点坐标 $point=['longitude'=>121.427417,'latitude'=>31.20357];
|
||
* @param array $pts 多边形坐标 顺时针方向 $arr=[['longitude'=>121.23036,'latitude'=>31.218609],['longitude'=>121.233666,'latitude'=>31.210579].............];
|
||
*/
|
||
function is_point_in_polygon($point, $pts)
|
||
{
|
||
$N = count($pts);
|
||
$boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
|
||
$intersectCount = 0;//cross points count of x
|
||
$precision = 2e-10; //浮点类型计算时候与0比较时候的容差
|
||
$p1 = 0;//neighbour bound vertices
|
||
$p2 = 0;
|
||
$p = $point; //测试点
|
||
|
||
$p1 = $pts[ 0 ];//left vertex
|
||
for ($i = 1; $i <= $N; ++$i) {//check all rays
|
||
// dump($p1);
|
||
if ($p[ 'longitude' ] == $p1[ 'longitude' ] && $p[ 'latitude' ] == $p1[ 'latitude' ]) {
|
||
return $boundOrVertex;//p is an vertex
|
||
}
|
||
|
||
$p2 = $pts[ $i % $N ];//right vertex
|
||
if ($p[ 'latitude' ] < min($p1[ 'latitude' ], $p2[ 'latitude' ]) || $p[ 'latitude' ] > max($p1[ 'latitude' ], $p2[ 'latitude' ])) {//ray is outside of our interests
|
||
$p1 = $p2;
|
||
continue;//next ray left point
|
||
}
|
||
|
||
if ($p[ 'latitude' ] > min($p1[ 'latitude' ], $p2[ 'latitude' ]) && $p[ 'latitude' ] < max($p1[ 'latitude' ], $p2[ 'latitude' ])) {//ray is crossing over by the algorithm (common part of)
|
||
if ($p[ 'longitude' ] <= max($p1[ 'longitude' ], $p2[ 'longitude' ])) {//x is before of ray
|
||
if ($p1[ 'latitude' ] == $p2[ 'latitude' ] && $p[ 'longitude' ] >= min($p1[ 'longitude' ], $p2[ 'longitude' ])) {//overlies on a horizontal ray
|
||
return $boundOrVertex;
|
||
}
|
||
|
||
if ($p1[ 'longitude' ] == $p2[ 'longitude' ]) {//ray is vertical
|
||
if ($p1[ 'longitude' ] == $p[ 'longitude' ]) {//overlies on a vertical ray
|
||
return $boundOrVertex;
|
||
} else {//before ray
|
||
++$intersectCount;
|
||
}
|
||
} else {//cross point on the left side
|
||
$xinters = ( $p[ 'latitude' ] - $p1[ 'latitude' ] ) * ( $p2[ 'longitude' ] - $p1[ 'longitude' ] ) / ( $p2[ 'latitude' ] - $p1[ 'latitude' ] ) + $p1[ 'longitude' ];//cross point of lng
|
||
if (abs($p[ 'longitude' ] - $xinters) < $precision) {//overlies on a ray
|
||
return $boundOrVertex;
|
||
}
|
||
|
||
if ($p[ 'longitude' ] < $xinters) {//before ray
|
||
++$intersectCount;
|
||
}
|
||
}
|
||
}
|
||
} else {//special case when ray is crossing through the vertex
|
||
if ($p[ 'latitude' ] == $p2[ 'latitude' ] && $p[ 'longitude' ] <= $p2[ 'longitude' ]) {//p crossing over p2
|
||
$p3 = $pts[ ( $i + 1 ) % $N ]; //next vertex
|
||
if ($p[ 'latitude' ] >= min($p1[ 'latitude' ], $p3[ 'latitude' ]) && $p[ 'latitude' ] <= max($p1[ 'latitude' ], $p3[ 'latitude' ])) { //p.latitude lies between p1.latitude & p3.latitude
|
||
++$intersectCount;
|
||
} else {
|
||
$intersectCount += 2;
|
||
}
|
||
}
|
||
}
|
||
$p1 = $p2;//next ray left point
|
||
}
|
||
|
||
if ($intersectCount % 2 == 0) {//偶数在多边形外
|
||
return false;
|
||
} else { //奇数在多边形内
|
||
return true;
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* 过滤特殊字符
|
||
* @param $strParam
|
||
* @return null|string|string[]
|
||
*/
|
||
function replaceSpecialChar($strParam)
|
||
{
|
||
$regex = "/\/|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\{|\}|\:|\<|\>|\?|\[|\]|\,|\.|\/|\;|\'|\`|\-|\=|\\\|\||\s+/";
|
||
return preg_replace($regex, "", $strParam);
|
||
}
|
||
|
||
function delFile($path)
|
||
{
|
||
$res = false;
|
||
if (file_exists($path)) {
|
||
$res = unlink($path);
|
||
}
|
||
return $res;
|
||
}
|
||
|
||
/**
|
||
* base64转二进制
|
||
* @param $base64Str
|
||
* @return array|boolean
|
||
*/
|
||
function base64_to_blob($base64Str)
|
||
{
|
||
if ($index = strpos($base64Str, 'base64,', 0)) {
|
||
$blobStr = substr($base64Str, $index + 7);
|
||
$typestr = substr($base64Str, 0, $index);
|
||
preg_match("/^data:(.*);$/", $typestr, $arr);
|
||
return [ 'blob' => base64_decode($blobStr), 'type' => $arr[ 1 ] ];
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 获取近七日的时间
|
||
* @param string $time
|
||
* @param string $format
|
||
* @return array|boo
|
||
*/
|
||
|
||
function getweeks($time = '', $format = 'Y-m-d')
|
||
{
|
||
$time = $time != '' ? $time : time();
|
||
//组合数据
|
||
$date = [];
|
||
for ($i = 1; $i <= 10; $i++) {
|
||
$date[ $i ] = date($format, strtotime('+' . ( $i - 10 ) . ' days', $time));
|
||
}
|
||
return $date;
|
||
}
|
||
|
||
/**
|
||
* 两个数字比
|
||
* @param $first | $second
|
||
* @return string
|
||
*/
|
||
|
||
function diff_rate($first, $second)
|
||
{
|
||
if ($second != 0) {
|
||
$result = sprintf('%.2f', ( ( $first - $second ) / $second ) * 100) . '%';
|
||
} else if ($second == 0 & $first != 0) {
|
||
$result = '100%';
|
||
} else {
|
||
$result = '0%';
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 过滤bom
|
||
* @param $contents
|
||
* @return false|mixed|string
|
||
*/
|
||
function removeBom($contents)
|
||
{
|
||
$charset[ 1 ] = substr($contents, 0, 1);
|
||
$charset[ 2 ] = substr($contents, 1, 1);
|
||
$charset[ 3 ] = substr($contents, 2, 1);
|
||
if (ord($charset[ 1 ]) == 239 && ord($charset[ 2 ]) == 187 && ord($charset[ 3 ]) == 191) {
|
||
$rest = substr($contents, 3);
|
||
return $rest;
|
||
} else {
|
||
return $contents;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 复制拷贝
|
||
* @param string $src 原目录
|
||
* @param string $dst 复制到的目录
|
||
*/
|
||
function recurseCopy($src, $dst)
|
||
{
|
||
$dir = opendir($src);
|
||
@mkdir($dst);
|
||
while (false !== ( $file = readdir($dir) )) {
|
||
if (( $file != '.' ) && ( $file != '..' )) {
|
||
if (is_dir($src . '/' . $file)) {
|
||
recurseCopy($src . '/' . $file, $dst . '/' . $file);
|
||
} else {
|
||
copy($src . '/' . $file, $dst . '/' . $file);
|
||
}
|
||
}
|
||
}
|
||
closedir($dir);
|
||
}
|
||
|
||
/**
|
||
* 获取毫秒数
|
||
* @return false|string
|
||
*/
|
||
function getMillisecond()
|
||
{
|
||
list($microsecond, $time) = explode(' ', microtime());
|
||
$time = (float) sprintf('%.0f', ( floatval($microsecond) + floatval($time) ) * 1000);
|
||
return substr($time, -3);
|
||
}
|
||
|
||
/**
|
||
* #号颜色转为rgb
|
||
* @return false|string
|
||
*/
|
||
function hex2rgb($color)
|
||
{
|
||
|
||
if ($color[ 0 ] == '#') {
|
||
$color = substr($color, 1);
|
||
}
|
||
if (strlen($color) == 6) {
|
||
list($r, $g, $b) = array ( $color[ 0 ] . $color[ 1 ], $color[ 2 ] . $color[ 3 ], $color[ 4 ] . $color[ 5 ] );
|
||
} elseif (strlen($color) == 3) {
|
||
list($r, $g, $b) = array ( $color[ 0 ] . $color[ 0 ], $color[ 1 ] . $color[ 1 ], $color[ 2 ] . $color[ 2 ] );
|
||
} else {
|
||
return false;
|
||
}
|
||
$r = hexdec($r);
|
||
$g = hexdec($g);
|
||
$b = hexdec($b);
|
||
return array ( $r, $g, $b );
|
||
}
|
||
|
||
/**
|
||
* 生成条形码
|
||
* @param $content
|
||
* @param string $path
|
||
* @param int $scale 条形码放大比率
|
||
* @param int $size 条形码文本大小
|
||
* @return string
|
||
*/
|
||
function getBarcode($content, $path = '', $scale = 2, $size = 14)
|
||
{
|
||
$barcode = new Barcode($size, $content);
|
||
$path = $barcode->generateBarcode($path, $scale);
|
||
return $path;
|
||
}
|
||
|
||
/**
|
||
* 生成不重复的随机数
|
||
*
|
||
* @param int $begin
|
||
* @param int $end
|
||
* @param int $limit
|
||
* @return string
|
||
*/
|
||
function NoRand($begin = 0, $end = 20, $limit = 5)
|
||
{
|
||
$rand_array = range($begin, $end);
|
||
shuffle($rand_array);//调用现成的数组随机排列函数
|
||
$number_arr = array_slice($rand_array, 0, $limit);//截取前$limit个
|
||
$number = '';
|
||
foreach ($number_arr as $k => $v) {
|
||
$number .= $v;
|
||
}
|
||
$number = trim($number);
|
||
return $number;
|
||
}
|
||
|
||
/**
|
||
* 获取某个日期的开始时间和结束时间
|
||
* @param string $date
|
||
* @return array
|
||
*/
|
||
function getDayStartAndEndTime($date = '')
|
||
{
|
||
if (empty($date)) {
|
||
$time = time();
|
||
} else {
|
||
$time = strtotime($date);
|
||
}
|
||
//如果是第一笔订单才能累加下单会员数
|
||
$start_time = strtotime(date("Y-m-d", $time));//当日开始时间
|
||
$end_time = $start_time + 60 * 60 * 24;//当日结束时间
|
||
return [ 'start_time' => $start_time, 'end_time' => $end_time ];
|
||
}
|
||
|
||
/**
|
||
* 核验是否开启消息队列
|
||
* @param $params
|
||
* @param $fun1
|
||
* @param $fun2
|
||
* @return array
|
||
*/
|
||
function checkQueue($params, $fun1, $fun2)
|
||
{
|
||
|
||
$system_config_model = new \app\model\system\SystemConfig();
|
||
$config = $system_config_model->getSystemConfig()[ 'data' ] ?? [];
|
||
$is_open_queue = $config[ 'is_open_queue' ] ?? 0;
|
||
if ($is_open_queue) {
|
||
$result = $fun1($params);
|
||
} else {
|
||
$result = $fun2($params);
|
||
}
|
||
return $result;
|
||
|
||
}
|
||
|
||
/**
|
||
* 通用处理金额的格式(主要用于业务)
|
||
* @param $money
|
||
* @return float
|
||
*/
|
||
function moneyFormat($money)
|
||
{
|
||
$money = round($money, 2);
|
||
return $money;
|
||
}
|
||
|
||
/**
|
||
* 长链接转短链接
|
||
* @param string $long_url
|
||
* @return string
|
||
*/
|
||
function short_url(string $url) : string
|
||
{
|
||
$result = sprintf("%u", crc32($url));
|
||
$show = '';
|
||
while ($result > 0) {
|
||
$s = $result % 62;
|
||
if ($s > 35) {
|
||
$s = chr($s + 61);
|
||
} elseif ($s > 9 && $s <= 35) {
|
||
$s = chr($s + 55);
|
||
}
|
||
$show .= $s;
|
||
$result = floor($result / 62);
|
||
}
|
||
return $show;
|
||
}
|
||
|
||
function isMobile()
|
||
{
|
||
// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
|
||
if (isset($_SERVER[ 'HTTP_X_WAP_PROFILE' ])) {
|
||
return true;
|
||
}
|
||
// 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
|
||
if (isset($_SERVER[ 'HTTP_VIA' ])) {
|
||
// 找不到为flase,否则为true
|
||
return (bool)stristr($_SERVER['HTTP_VIA'], "wap");
|
||
}
|
||
// 判断手机发送的客户端标志,兼容性有待提高。其中'MicroMessenger'是电脑微信
|
||
if (isset($_SERVER[ 'HTTP_USER_AGENT' ])) {
|
||
$clientkeywords = array ( 'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile', 'MicroMessenger' );
|
||
// 从HTTP_USER_AGENT中查找手机浏览器的关键字
|
||
if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER[ 'HTTP_USER_AGENT' ]))) {
|
||
return true;
|
||
}
|
||
}
|
||
// 协议法,因为有可能不准确,放到最后判断
|
||
if (isset ($_SERVER[ 'HTTP_ACCEPT' ])) {
|
||
// 如果只支持wml并且不支持html那一定是移动设备
|
||
// 如果支持wml和html但是wml在html之前则是移动设备
|
||
if (( strpos($_SERVER[ 'HTTP_ACCEPT' ], 'vnd.wap.wml') !== false ) && ( strpos($_SERVER[ 'HTTP_ACCEPT' ], 'text/html') === false || ( strpos($_SERVER[ 'HTTP_ACCEPT' ], 'vnd.wap.wml') < strpos($_SERVER[ 'HTTP_ACCEPT' ], 'text/html') ) )) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//读取csv数据, 配合生成器使用
|
||
function getCsvRow($file)
|
||
{
|
||
$handle = fopen($file, 'rb');
|
||
if ($handle === false) {
|
||
throw new \Exception();
|
||
}
|
||
|
||
while (feof($handle) === false) {
|
||
yield fgetcsv($handle);
|
||
}
|
||
fclose($handle);
|
||
}
|
||
|
||
/**
|
||
* 数组内部字段求和
|
||
* @param $arr
|
||
* @param $field1
|
||
* @param string $field2
|
||
* @return float|int
|
||
*/
|
||
function getArraySum($arr, $field1, $field2 = '')
|
||
{
|
||
$sum = 0;
|
||
foreach ($arr as $v) {
|
||
$num1 = $v[ $field1 ] ?? 0;
|
||
$num2 = $v[ $field2 ] ?? 1;
|
||
$sum += $num1 * $num2;
|
||
}
|
||
return $sum;
|
||
}
|
||
|
||
/**
|
||
* 过滤Emoji
|
||
* @param $str
|
||
* @return string|string[]|null
|
||
*/
|
||
function filterEmoji($str)
|
||
{
|
||
$str = preg_replace_callback(
|
||
'/./u', function(array $match) {
|
||
return strlen($match[ 0 ]) >= 4 ? '' : $match[ 0 ];
|
||
}, $str);
|
||
return $str;
|
||
}
|
||
|
||
/**
|
||
* 数字格式化,小数点保留3位,后三位都为0时,取整数
|
||
* @param $num
|
||
* @return float
|
||
*/
|
||
function numberFormat($num)
|
||
{
|
||
$num = round($num, 3);
|
||
return $num;
|
||
}
|
||
|
||
/**
|
||
* 字符串特殊字符过滤
|
||
* @param $param
|
||
* @return array|string|string[]|null
|
||
*/
|
||
function paramFilter($param)
|
||
{
|
||
// 把数据过滤
|
||
$filter_rule = [
|
||
"/<(\\/?)(script|i?frame|style|html|body|title|link|meta|object|\\?|\\%)([^>]*?)>/isU",
|
||
"/(<[^>]*)on[a-zA-Z]+\s*=([^>]*>)/isU",
|
||
"/select|join|where|drop|like|modify|rename|insert|update|table|database|alter|truncate|\'|\/\*|\.\.\/|\.\/|union|into|load_file|outfile/is"
|
||
];
|
||
return preg_replace($filter_rule, '', $param);
|
||
}
|
||
|
||
//最小公倍数
|
||
function getLeastCommonMultiple($a, $b) {
|
||
return $a * $b / getGreatestCommonDivisor($a, $b);
|
||
}
|
||
//最大公约数
|
||
function getGreatestCommonDivisor($a, $b) {
|
||
if ($b === 0) return $a;
|
||
return getGreatestCommonDivisor($b, $a % $b);
|
||
}
|
||
|
||
|
||
/**
|
||
* 关联数组变为索引数组
|
||
* @param array $list
|
||
* @return array
|
||
*/
|
||
function keyArrToIndexArr($list, $child = 'children'){
|
||
$list = array_values($list);
|
||
foreach($list as $key=>$val){
|
||
if(isset($val[$child]) && !empty($val[$child])){
|
||
$list[$key][$child] = keyArrToIndexArr($val[$child], $child);
|
||
}
|
||
}
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* 索引数组变为关联数组
|
||
* @param $list
|
||
* @param $pk
|
||
* @param string $child
|
||
* @return array
|
||
*/
|
||
function indexArrToKeyArr($list, $pk, $child = 'children'){
|
||
$new_list = [];
|
||
foreach($list as $val){
|
||
if(isset($val[$child]) && !empty($val[$child])){
|
||
$val[$child] = indexArrToKeyArr($val[$child], $pk, $child);
|
||
}
|
||
if(isset($val[$pk])){
|
||
$new_list[$val[$pk]] = $val;
|
||
}
|
||
}
|
||
return $new_list;
|
||
}
|
||
|
||
/**
|
||
* 获取树的末端节点
|
||
* @param $list
|
||
* @param $pk
|
||
* @param $child
|
||
* @return array
|
||
*/
|
||
function getTreeLeaf($list, $pk = 'id', $child = 'child')
|
||
{
|
||
$leaf_arr = [];
|
||
foreach($list as $val){
|
||
if(empty($val[$child])){
|
||
$leaf_arr[] = $val[$pk];
|
||
}else{
|
||
$leaf_arr = array_merge($leaf_arr, getTreeLeaf($val[$child], $pk, $child));
|
||
}
|
||
}
|
||
return $leaf_arr;
|
||
}
|
||
|
||
/**
|
||
* 覆盖数据
|
||
* @param $source_data
|
||
* @param $target_data
|
||
* @return mixed
|
||
*/
|
||
function assignData($source_data, $target_data)
|
||
{
|
||
if(is_array($target_data)){
|
||
foreach($target_data as $key=>$val){
|
||
if(isset($source_data[$key])){
|
||
$target_data[$key] = assignData($source_data[$key], $val);
|
||
}
|
||
}
|
||
}else{
|
||
$target_data = $source_data;
|
||
}
|
||
return $target_data;
|
||
}
|
||
|
||
//使用htmlpurifier防范xss攻击
|
||
function removeXss($string)
|
||
{
|
||
//相对index.php入口文件,引入HTMLPurifier.auto.php核心文件
|
||
//require_once './plugins/htmlpurifier/HTMLPurifier.auto.php';
|
||
// 生成配置对象
|
||
$cfg = HTMLPurifier_Config::createDefault();
|
||
// 以下就是配置:
|
||
$cfg->set('Core.Encoding', 'UTF-8');
|
||
// 设置允许使用的HTML标签
|
||
$cfg->set('HTML.Allowed', 'div,b,strong,i,em,a[href|title],ul,ol,li,br,p[style],span[style],img[width|height|alt|src],table,tbody,tr[class],th,td[width|valign|style]');
|
||
// 设置允许出现的CSS样式属性
|
||
//$cfg->set('CSS.AllowedProperties', 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align');
|
||
// 设置a标签上是否允许使用target="_blank"
|
||
$cfg->set('HTML.TargetBlank', TRUE);
|
||
// 使用配置生成过滤用的对象
|
||
$obj = new HTMLPurifier($cfg);
|
||
// 过滤字符串
|
||
$array = json_decode($string, true);
|
||
if(is_array($array)){
|
||
$array = recursiveDealWithArrayString($array, function ($str) use($obj){
|
||
return $obj->purify($str);
|
||
});
|
||
$string = json_encode($array, JSON_UNESCAPED_UNICODE);
|
||
}else{
|
||
$string = $obj->purify($string);
|
||
}
|
||
return $string;
|
||
}
|
||
|
||
/**
|
||
* 递归处理数组中的字符串
|
||
* @param $array
|
||
* @param $callback
|
||
* @return mixed
|
||
*/
|
||
function recursiveDealWithArrayString($array, $callback){
|
||
foreach($array as $key=>$val){
|
||
if(is_string($val)){
|
||
$val = $callback($val);
|
||
}else if(is_array($val)){
|
||
$val = recursiveDealWithArrayString($val, $callback);
|
||
}
|
||
$array[$key] = $val;
|
||
}
|
||
return $array;
|
||
}
|
||
|
||
function exceptionData(\Exception $e){
|
||
return [
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'message' => $e->getMessage(),
|
||
];
|
||
}
|
||
|
||
function getCertKey($str)
|
||
{
|
||
return require "extend/cert/".$str.".php";
|
||
}
|
||
|
||
if(!function_exists('http_url')){
|
||
function http_url($url,$data,$headers = [],$type = 'POST') {
|
||
$ch = curl_init();
|
||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
|
||
curl_setopt($ch, CURLOPT_POST, 1);
|
||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||
curl_setopt($ch, CURLOPT_URL, $url);
|
||
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36';
|
||
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); # 在HTTP请求中包含一个"User-Agent: "头的字符串,声明用什么浏览器来打开目标网页
|
||
if(!empty($headers)) {
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||
}
|
||
if (1 == strpos("$".$url, "https://"))
|
||
{
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||
}
|
||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
|
||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||
if($type == 'POST') {
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
if(is_array($data)) {
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
||
}else {
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||
}
|
||
}
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // ssl 访问核心参数
|
||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // ssl 访问核心参数
|
||
$result = curl_exec($ch);
|
||
curl_close($ch);
|
||
return $result;
|
||
}
|
||
|
||
function secondsToTime($seconds) {
|
||
$hours = floor($seconds / 3600);
|
||
$minutes = floor(($seconds % 3600) / 60);
|
||
$seconds = $seconds % 60;
|
||
|
||
// 格式化为两位数
|
||
return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化数据为日志友好的字符串(保持中文可读性)
|
||
*
|
||
* @param mixed $data 要格式化的数据
|
||
* @return string 格式化后的字符串
|
||
*/
|
||
function formatForLog($data): string
|
||
{
|
||
if (is_array($data) || is_object($data)) {
|
||
// 使用 JSON_UNESCAPED_UNICODE 保持中文可读性
|
||
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||
}
|
||
|
||
return (string) $data;
|
||
}
|
||
|
||
/**
|
||
* 格式化数据为可导出的字符串(完全避免Unicode转义)
|
||
*
|
||
* @param mixed $data 要格式化的数据
|
||
* @return string 格式化后的字符串
|
||
*/
|
||
function exportForLog($data): string
|
||
{
|
||
if (is_array($data) || is_object($data)) {
|
||
// 使用 var_export 完全避免Unicode转义
|
||
return var_export($data, true);
|
||
}
|
||
|
||
return (string) $data;
|
||
}
|
||
|
||
/**
|
||
* 简单日志写入
|
||
* @param string $msg 日志内容
|
||
* @param string $level 日志级别 debug、info、warning、error
|
||
* @param string $file 日志文件名(不含路径)
|
||
*/
|
||
function log_write(string $message, string $level = 'info', string $filename = ''): void
|
||
{
|
||
// 日志文件最大大小(字节)
|
||
$maxFileSize = 10485760; // 10MB
|
||
$callerInfo = CallerInfo::getCallerInfo(1);
|
||
|
||
// 格式化日志内容
|
||
$content = sprintf(
|
||
"[%s] %s: %s (文件:%s,行:%d) " . PHP_EOL,
|
||
date('Y-m-d H:i:s'),
|
||
strtoupper($level),
|
||
is_array($message) ? json_encode($message, JSON_UNESCAPED_UNICODE) : $message,
|
||
$callerInfo['file'],
|
||
$callerInfo['line']
|
||
);
|
||
|
||
$logPath = app()->getRuntimePath() . 'log/app_run_info/'; // eg. /runtime/shop/log
|
||
|
||
// 确保日志目录存在
|
||
if (!is_dir($logPath)) {
|
||
mkdir($logPath, 0755, true);
|
||
}
|
||
|
||
// 生成日志文件名
|
||
if (empty($filename)) {
|
||
$filename = date('Y-m-d') . '.log';
|
||
}
|
||
|
||
// 日志文件路径
|
||
$logFile = $logPath . $filename;
|
||
|
||
// 检查文件大小,如果超过限制则直接重新写入(不创建新文件)
|
||
$flags = FILE_APPEND | LOCK_EX;
|
||
if (file_exists($logFile) && is_readable($logFile)) {
|
||
try {
|
||
// 方法1:使用 filesize 函数(基本方法)
|
||
$fileSize = filesize($logFile);
|
||
|
||
// 如果 filesize 失败或返回 false,尝试使用 stat 函数
|
||
if ($fileSize === false) {
|
||
$stat = stat($logFile);
|
||
if ($stat && isset($stat['size'])) {
|
||
$fileSize = $stat['size'];
|
||
}
|
||
}
|
||
|
||
// 如果获取到的文件大小超过限制,使用覆盖模式
|
||
if (isset($fileSize) && $fileSize >= $maxFileSize) {
|
||
$flags = 0 | LOCK_EX;
|
||
}
|
||
} catch (Exception $e) {
|
||
// 如果发生异常,为了安全起见,继续使用追加模式
|
||
// 可以根据需要记录异常信息
|
||
}
|
||
}
|
||
|
||
// 写入文件
|
||
file_put_contents($logFile, $content, $flags);
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 将异常格式化HTML输出,方便定位
|
||
* @param mixed $th
|
||
* @return never
|
||
*/
|
||
function formatThrowForDebugWithExit($th)
|
||
{
|
||
// 以友好 HTML 格式输出异常信息并终止执行
|
||
http_response_code(500);
|
||
header('Content-Type: text/html; charset=utf-8');
|
||
|
||
$msg = htmlspecialchars($th->getMessage(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
$file = htmlspecialchars($th->getFile(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
$line = (int) $th->getLine();
|
||
$trace = htmlspecialchars($th->getTraceAsString(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
|
||
echo <<<HTML
|
||
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8"/>
|
||
<title>应用错误</title>
|
||
<style>
|
||
body{font-family:Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:#f6f8fa;color:#222;margin:0;padding:20px}
|
||
.container{max-width:900px;margin:30px auto;background:#fff;border:1px solid #e1e4e8;border-radius:6px;padding:20px;box-shadow:0 2px 6px rgba(0,0,0,.04)}
|
||
h1{margin:0 0 10px;font-size:18px;color:#d73a49}
|
||
.meta{color:#586069;margin-bottom:12px;font-size:13px}
|
||
pre{background:#f6f8fa;border:1px solid #e1e4e8;padding:12px;border-radius:4px;overflow:auto;white-space:pre-wrap;word-break:break-word}
|
||
.kv{margin:6px 0}
|
||
.key{display:inline-block;width:70px;color:#6a737d;font-weight:600}
|
||
.value{color:#24292e}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>应用发生异常</h1>
|
||
<div class="meta">请在开发或测试环境查看具体信息;生产环境建议关闭详细错误输出。</div>
|
||
<div class="kv"><span class="key">消息:</span><span class="value">{$msg}</span></div>
|
||
<div class="kv"><span class="key">文件:</span><span class="value">{$file}</span></div>
|
||
<div class="kv"><span class="key">行号:</span><span class="value">{$line}</span></div>
|
||
<h2 style="font-size:14px;margin-top:12px;color:#0366d6">调用栈</h2>
|
||
<pre>{$trace}</pre>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
HTML;
|
||
|
||
exit;
|
||
} |