// +---------------------------------------------------------------------- use app\common\model\Setting; use GuzzleHttp\Client; use think\Cache; use think\Config; use think\Db; use think\db\exception\DataNotFoundException; use think\db\exception\ModelNotFoundException; use think\Env; use think\Exception; use think\exception\DbException; /** * 取得随机数 * * @param int $length 生成随机数的长度 * @param int $numeric 是否只产生数字随机数 1是0否 * @return string */ function random($length, $numeric = 0) { $seed = base_convert(md5(microtime() . $_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35); $seed = $numeric ? (str_replace('0', '', $seed) . '012340567890') : ($seed . 'zZ' . strtoupper($seed)); $hash = ''; $max = strlen($seed) - 1; for ($i = 0; $i < $length; $i++) { $hash .= $seed[mt_rand(0, $max)]; } return $hash; } /** * 二维数组根据字段进行排序 * @params array $array 需要排序的数组 * @params string $field 排序的字段 * @params string $sort 排序顺序标志 SORT_DESC 降序;SORT_ASC 升序 * */ function arraySequence($array, $field, $sort = 'SORT_DESC') { $arrSort = array(); if (empty($array)) { return $arrSort; } foreach ($array as $uniqid => $row) { foreach ($row as $key => $value) { $arrSort[$key][$uniqid] = $value; } } array_multisort($arrSort[$field], constant($sort), $array); return $array; } /** * 因为 < php7.0自带的array_column函数无法作用于对象数组,该函数用于返回对象数组中指定的一列 * * @param $arr : array 需要取出数组列的二维数组 * @param $column_key :数组的列的键 * @return array :一维数组 * */ function arrayColumnByObject($arr, $column_key, $index_key = null) { $result = array(); if (!empty($arr)) { foreach ($arr as $value) { if (!empty($index_key)) { $result[$value[$index_key]] = $value[$column_key]; } else $result[] = $value[$column_key]; } } return $result; } /** * 转换字符串中的HTML、js等XSS字符 * @param string $value 需要转义的值 * * @return string */ function escape($value) { if (empty($value)) { return $value; } return htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); } /** * HTML过滤XSS字符 * @param mixed $value 需要转义的值 * @return mixed */ function removeXss($value) { if (empty($value)) { return $value; } vendor('htmlpurifier.library.HTMLPurifier#auto'); // 实例化 HTMLPurifier对象 $purifier = new HTMLPurifier(); // 返回过滤后的数据 return $purifier->purify($value); } /** * 生成返回跳转的url * * @param string $url 生成的URL地址或路由地址 * @param string|array $params 参数 * @param bool|string $suffix 生成的URL后缀 * @param bool|string $domain 域名 * * @return string */ function createRefererUrl($url = null, $params = '', $suffix = true, $domain = false) { if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { $url = $_SERVER["HTTP_REFERER"]; } else { $url = empty($params) ? url($url, '', $suffix, $domain) : url($url, '', $suffix, $domain) . '?' . http_build_query($params, null, '&'); } return urlencode($url); } /** * 旧版SDK加密解密函数(来自discuz) * @param $string string 明文或密文 * @param $operation string DECODE为解密,ENCODE为加密 * @param $key string 密钥 * @param $expiry int 有效期,秒为单位,0为永久 */ function auth_code($string, $operation = 'DECODE', $key = '', $expiry = 0) { $ckey_length = 0; $key = md5($key ? $key : Env::get('auth_key')); $keya = md5(substr($key, 0, 16)); $keyb = md5(substr($key, 16, 16)); $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : ''; $cryptkey = $keya . md5($keya . $keyc); $key_length = strlen($cryptkey); $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string; $string_length = strlen($string); $result = ''; $box = range(0, 255); // $box = 100; $rndkey = array(); for ($i = 0; $i <= 255; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for ($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for ($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } if ($operation == 'DECODE') { if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) { return substr($result, 26); } else { return ''; } } else { return $keyc . str_replace('=', '', base64_encode($result)); } } /** * 字符串模糊处理,中间4位以*代替 * @param string $str 需要模糊处理的字符串 * @param int $startlen 开头明文显示的长度 * * @return string */ function stringObfuscation($str, $startlen = 3) { if (!$str) { return '***'; } $len = mb_strlen($str, 'utf-8'); if ($len <= $startlen) { return mb_substr($str, 0, 1) . str_repeat('*', $len - 1); } elseif ($len <= ($startlen + 4)) { return mb_substr($str, 0, $startlen) . str_repeat('*', $len - $startlen); } else { return mb_substr($str, 0, $startlen) . str_repeat('*', 4) . mb_substr($str, $startlen + 4); } return $str; } /** * 身份证模糊处理,中间以*代替 * @param string $str * @return string */ function idcardObfuscation($str) { if (!$str || $str == '-1') { return $str; } $len = mb_strlen($str, 'utf-8'); return mb_substr($str, 0, 4) . str_repeat('*', $len - 8) . mb_substr($str, -4); } /** * 密码模糊处理,中间以*代替 * @param string $str * @return string */ function passwordObfuscation($str) { if (!$str) { return '***'; } $len = mb_strlen($str, 'utf-8'); return mb_substr($str, 0, 3) . str_repeat('*', $len - 6) . mb_substr($str, -3); } /** * 手机号模糊处理,中间4位以*代替 * * @param string $mobile * @return string 处理过的手机号 */ function mobileObfuscation($mobile) { if (empty($mobile)) { return null; } return substr($mobile, 0, 3) . '****' . substr($mobile, 7); } /** * 用户名模糊处理 * @param $username * @return string */ function usernameObfuscation($username) { $len = mb_strlen($username, 'utf-8'); if ($len == 2) { return mb_substr($username, 0, 1) . '*'; } else if ($len > 2) { return mb_substr($username, 0, 1) . str_repeat('*', $len - 2) . mb_substr($username, -1); } return $username; } /** * 邮箱地址模糊处理,显示头两位及@之后的 * @param string $mail * @return string 处理过的邮箱地址 */ function mailObfuscation($mail) { if (empty($mail)) { return null; } return substr($mail, 0, 3) . '****' . strstr($mail, '@'); } if (!function_exists('filterAndTrimInput')) { function filterAndTrimInput($val) { return escape(trim($val)); } } /** * 根据传来的日期,生成时间戳区间查询条件,日期格式:2016-01-01 * @param string $start_time 开始时间 * @param string $end_time 结束时间 * @return array 时间戳查询条件 */ function makeTimeCondition($start_time = '', $end_time = '') { $start_time = !empty($start_time) ? strtotime($start_time) : ''; $end_time = !empty($end_time) ? strtotime($end_time . ' 23:59:59') : ''; $condition = array(); if (!empty($start_time)) { $condition = array('EGT', $start_time); } if (!empty($end_time)) { $condition = array('ELT', $end_time); } if (!empty($start_time) && !empty($end_time)) { $condition = array(array('EGT', $start_time), array('ELT', $end_time)); } return $condition; } /** * 写入日志 * * @param string $path 日志路径 * @param string $level * log 常规日志,用于记录日志 * error 错误,一般会导致程序的终止 * notice 警告,程序可以运行但是还不够完美的错误 * info 信息,程序输出信息 * debug 调试,用于调试信息 * sql SQL语句,用于SQL记录,只在数据库的调试模式开启时有效 */ function log_message($msg, $level = 'info', $path = LOG_PATH) { \think\Log::init([ 'type' => 'File', 'path' => $path, 'file_size' => 10485760 ]); \think\Log::write($msg, $level); } /** * 获得cURL请求数据 * * @param string $url 请求的URL * @param array $post_fields “POST”请求的数据 * @return mixed */ function getCurlContent($url, $post_fields = array()) { $h_curl = curl_init(); curl_setopt($h_curl, CURLOPT_URL, $url); curl_setopt($h_curl, CURLOPT_HEADER, false); curl_setopt($h_curl, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($h_curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($h_curl, CURLOPT_TIMEOUT, 60); curl_setopt($h_curl, CURLOPT_SSL_VERIFYPEER, false); // 阻止对证书的合法性的检查 curl_setopt($h_curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在 if (!empty($post_fields)) { curl_setopt($h_curl, CURLOPT_POST, 1); curl_setopt($h_curl, CURLOPT_POSTFIELDS, $post_fields); if (is_string($post_fields)) { curl_setopt($h_curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($post_fields)) ); } } $contents = curl_exec($h_curl); curl_close($h_curl); return $contents; } /** * 生成订单号,每秒可产生9999个不重复的订单号 * @return string */ function makeOrderid($head = 'WL') { $redis = think\Cache::store('default'); $orderid = (microtime(true) * 10000) . substr(str_pad(mt_rand(), 6, "0", STR_PAD_BOTH), 0, 6); // 判断是否有同个订单号的请求还没处理完,防止因为网络问题,导致同个订单并发 $cache_key = 'orderid:' . $orderid; if ($redis->has($cache_key)) { return makeOrderid(); } else { $redis->set($cache_key, $orderid, 86400); return $head . $orderid; } } /** * 生成平台币订单号 * */ function makeCoinSn() { return 'sn' . makeOrderid(); } /** * 获取渠道顶级信息 * * @param $channelId int 当前渠道ID * * @return array 渠道信息 */ function get_top_channel($channelId) { $channelModel = \Think\Db::name('nw_channel'); // $channelLevel = get_channel_level($channelId); $i = 0; while ($info = $channelModel->where(['id' => $channelId])->find()) { $i++; if ($info['level'] == 1 || $info['parent_id'] == $info['id'] || $i > 5) { return $info; } $channelId = $info['parent_id']; } } /** * 获取渠道联盟信息 * * @param $channelId int 当前渠道ID * * @return array 渠道信息 */ function get_union_channel($channelId) { $data = \think\Cache::get('get_union_channel'.$channelId); if(!empty($data)){ return json_decode($data,true); } $channelModel = \Think\Db::name('nw_channel'); // $channelLevel = get_channel_level($channelId); $i = 0; while ($info = $channelModel->where(['id' => $channelId])->find()) { $i++; if ($info['level'] == 0 || $info['parent_id'] == $info['id'] || $i > 5) { \think\Cache::set('get_union_channel'.$channelId,json_encode($info),60); return $info; } $channelId = $info['parent_id']; } } /** * 获取渠道等级 */ function get_channel_level($channelId) { $level = 3; $channelModel = \Think\Db::name('nw_channel'); while ($info = $channelModel->where(['id' => $channelId])->find()) { return $info['level']; /* if ($info['level'] == 1) { return $level; } $channelId = $info['parent_id']; $level++; */ } } /** * 获取顶级二级渠道 * 获取渠道信息 */ function get_top_second_channel_name($channelId) { $keyName = 'get_top_second_channel_name:'.$channelId; $data = \think\Cache::get($keyName); if(!empty($data)){ return json_decode($data,true); } $channel = []; $channelModel = \think\Db::name('nw_channel'); $level = 0; $newChannelId = $channelId; while ($info = $channelModel->field('id,name,parent_id,level')->where(['id' => $newChannelId, 'flag' => ['in', '1,2,3']])->find()) { $channel[$level] = $info['name']; $level++; //如果上级是最高级渠道退出循环 if ($info['level'] == 1) { break; } //测试数据存在 多个parent_id = 0的情况 后续不需要去掉 if (!$info['parent_id']) { break; } $newChannelId = $info['parent_id']; } //反转数组 $channel = array_reverse($channel); if (!$level) { $res = [ 'channel_name' => '', 'second_name' => '', 'top_name' => '', ]; } //二级渠道情况 if ($level == 2) { $res = [ 'channel_name' => isset($channel[1]) ? $channel[1] : '', 'second_name' => '', 'top_name' => isset($channel[0]) ? $channel[0] : '', ]; } elseif ($level > 2) { //三级渠道情况 $res = [ 'channel_name' => isset($channel[count($channel) - 1]) ? $channel[count($channel) - 1] : "", 'second_name' => isset($channel[1]) ? $channel[1] : '', 'top_name' => isset($channel[0]) ? $channel[0] : '', ]; } else { //最高级渠道情况 $res = [ 'channel_name' => isset($channel[0]) ? $channel[0] : '', 'second_name' => '', 'top_name' => '', ]; } \think\Cache::set($keyName, json_encode($res),60); return $res; } /** * 获取顶级二级渠道名称 - v2 优化版 * * 原版问题:while 循环逐级向上查 parent_id,N 级渠道就打 N 次 SQL。 * 优化思路:nw_channel.id_path 已存储完整祖先 ID 链(逗号分隔), * 只需 1 次查询取出当前渠道的 id_path + level, * 再用 1 次 IN 查询批量拿到所有祖先名称,合计固定 2 次 SQL。 * * id_path 格式示例(自身 id 也包含在内): * level=0(B+): "10" * level=1(B): "10,20" * level=2(B-): "10,20,300" * level=3(推广员): "10,20,300,500" * * 返回结构与原版保持一致: * [ * 'channel_name' => 当前渠道名(最末级), * 'second_name' => 二级渠道名(level=1,B账号),无则空字符串, * 'top_name' => 顶级渠道名(level=0,B+账号), * ] * * @param int $channelId * @return array */ function get_top_second_channel_name_v2($channelId) { // ---------- 0. 空值快速返回 ---------- $emptyRes = ['channel_name' => '', 'second_name' => '', 'top_name' => '', 'union_name' => '']; if (empty($channelId)) { return $emptyRes; } // ---------- 1. 缓存读取 ---------- $keyName = 'get_top_second_channel_name_v2:' . $channelId; $cached = \think\Cache::get($keyName); if (!empty($cached)) { return json_decode($cached, true); } // ---------- 2. 查询当前渠道的 id_path ---------- $self = \think\Db::name('nw_channel') ->field('id, name, level, id_path') ->where(['id' => $channelId]) ->find(); if (empty($self)) { \think\Cache::set($keyName, json_encode($emptyRes), 60); return $emptyRes; } // ---------- 3. 解析 id_path,获取所有上级渠道 ID ---------- // id_path 格式:",10,5213,5214," $idPath = trim($self['id_path'], ','); // 当前渠道名称 $channelName = $self['name']; $topName = $twoName = $threeName =''; if (!empty($idPath)) { // 解析上级 ID 列表 $parentIds = array_filter(array_map('intval', explode(',', $idPath))); if (!empty($parentIds)) { // ---------- 4. 批量查询所有上级渠道的 id/name/level ---------- $parents = \think\Db::name('nw_channel') ->field('id, name, level') ->where(['id' => ['in', $parentIds]]) ->select(); // 以 level 为键建立映射 $levelMap = []; foreach ($parents as $row) { $levelMap[(int)$row['level']] = $row['name']; } // 根据层级组装返回 // level: 0=B+(顶级), 1=B(二级), 2=B-(三级), 3=推广员(四级) $topName = $levelMap[0] ?? ''; // 顶级渠道(B+) $twoName = $levelMap[1] ?? ''; // 二级渠道(B) $threeName = $levelMap[2] ?? ''; // 三级渠道(B-) } } // ---------- 5. 组装返回结构 ---------- $res = [ 'channel_name' => $channelName, 'second_name' => $threeName, 'top_name' => $twoName, 'union_name' => $topName, ]; // ---------- 6. 写缓存并返回 ---------- \think\Cache::set($keyName, json_encode($res), 60); return $res; } /** * 根据渠道ID,获取四级渠道,并根据渠道等级为键 * 四级渠道=层级:0(B+账号),1(B账号),2(B-账号),3(推广员) * @param $channelId * * @return array * @throws DataNotFoundException * @throws ModelNotFoundException * @throws DbException */ function get_channel_top_by_aggregate($channelId) { $id_path = Db::table('nw_channel')->field('id,name,level,id_path')->where('id', $channelId)->value('id_path'); if(!$id_path) return []; $id_path = $channelId . ',' . $id_path; $id_path = array_filter(explode(',', $id_path)); $list = Db::table('nw_channel')->field('id,name,level')->where('id', 'in', $id_path)->select(); if(!$list) return []; $result = array_column($list, null, 'level'); return $result; } /** * 获取渠道名称 */ function get_channel_name($id) { $info = \Think\Db::name('nw_channel')->field('name')->where(['id' => $id])->find(); return $info['name']; } /** * 获取聚合渠道名称 */ function get_complex_channel_name($id) { $info = \Think\Db::name('nw_complex_channel')->field('name')->where(['id' => $id])->find(); return $info['name']; } /** * 获取子渠道 * * @param $channelId integer 当前渠道ID * @param $deep 层级深度(默认不传值时,取所有子渠道) * */ function get_child_channel_arr($channelId, $deep = '') { $result = []; $channelModel = \think\Db::name('nw_channel'); //所有子渠道 if (empty($deep)) { while ($info = $channelModel->field('id')->whereIn('parent_id', $channelId)->select()) { $tmp = []; if (!empty($info)) { foreach ($info as $v) { $result[] = $v['id']; $tmp[] = $v['id']; } $channelId = $tmp; } } } //层级深度以内的子渠道 else { while ($info = $channelModel->field('id')->whereIn('parent_id', $channelId)->select()) { $deep--; $tmp = []; if (!empty($info)) { foreach ($info as $v) { $result[] = $v['id']; $tmp[] = $v['id']; } $channelId = $tmp; } if ($deep == 0) break; } } return $result; } /** * 获取游戏名称 */ function get_game_name($id) { $info = \Think\Db::name('cy_game')->field('name')->where(['id' => $id])->find(); return $info['name']; } /** * 清理游戏名称末尾的平台标识,得到公共游戏组名称。 * * @param string $name * @return string */ function cleanGameName(string $name): string { $patterns = [ '/\s*((?:安卓|IOS|ios|H5|h5))\s*$/iu', '/\s*[-—]?\s*(安卓|IOS|ios|H5|h5)\s*$/iu', '/\s*\(?(?:安卓|IOS|ios|H5|h5)\)?\s*$/iu', ]; foreach ($patterns as $pattern) { $name = preg_replace($pattern, '', $name); } return trim($name); } /** * 获取后台管理员名称 */ function get_admin_name($id) { $info = \Think\Db::name('cy_admin')->field('id,username')->where(['id' => $id])->find(); return $info['username']; } /** * 后台及推广平台用户密码加密方法 * @param string $pw 要加密的原始密码 * @return string */ function mg_password($pw) { return md5(Env::get('ADMIN_PASS_PRE') . $pw); } /** * 判断游戏母包是否存在,查询结果做10秒缓存,减轻接口的压力 * @param $pinyin string * @param $platform integer 游戏平台 0:安卓; 1:IOS * @param $apktype integer 游戏包类型 0:普通包,1:光环包 * * @return bool | string 返回false或者'False'可判断为母包不存在 */ function isRawGameApkExistBak($pinyin, $platform, $apktype) { $cache_key = 'subpackage:apkexist:' . $pinyin . ':apktype:' . $apktype; $redis = \think\Cache::store('default'); if ($redis->has($cache_key)) { $redisInfo = $redis->get($cache_key); //日志记录 if (is_array($redisInfo)) { log_message('请求母包是否存在redis:' . print_r($redisInfo, true), 'log', LOG_PATH . 'subpackage/'); } else { log_message('请求母包是否存在redis:' . $redisInfo, 'log', LOG_PATH . 'subpackage/'); } return $redisInfo; } $apk_verify_url = Env::get('mubao.upload_url', 'http://127.0.0.1:9998') . '/apk_exists?gamename=' . $pinyin . '&platform=' . ($platform == 1 ? 'ios' : 'android') . '&apktype=' . $apktype; try { $request_result = (new Client(['timeout' => 30]))->get($apk_verify_url); $response = $request_result->getBody(); //返回的是个对象 } catch (Exception $e) { log_message('请求母包是否存在接口失败: gamename=' . $pinyin . '; ' . $e->getMessage(). " -- url:". $apk_verify_url, 'error', LOG_PATH . 'subpackage/'); return false; } $json = json_decode($response, true); $error = json_last_error(); //没有json错误时 if (json_last_error() == JSON_ERROR_NONE) { $result = $json; } else { $result = 'False'; } //日志记录 if (is_array($result)) { log_message('请求母包是否存在接口: ' . $apk_verify_url . ' data: ' . print_r($result, true), 'log', LOG_PATH . 'subpackage/'); } else { log_message('请求母包是否存在接口: ' . $apk_verify_url . ' data: ' . $result, 'log', LOG_PATH . 'subpackage/'); } $redis->set($cache_key, $result, 300); return $result; } // 判断母包是否存在 function isRawGameApkExist($pinyin, $platform, $apktype) { $apk_verify_url = Env::get('mubao.upload_url', 'http://127.0.0.1:9998') . '/apk_exists?gamename=' . $pinyin . '&platform=' . ($platform == 1 ? 'ios' : 'android') . '&apktype=' . $apktype; try { $request_result = (new Client(['timeout' => 30]))->get($apk_verify_url); $response = $request_result->getBody(); //返回的是个对象 return $response; } catch (Exception $e) { log_message('请求母包是否存在接口失败: gamename=' . $pinyin . '; ' . $e->getMessage() . " -- url:" . $apk_verify_url, 'error', LOG_PATH . 'subpackage/'); return false; } } // 同步分包请求 function gameApkPack($data) { $url = Env::get('mubao.upload_url', 'http://127.0.0.1:9998') . '/pack'; try { // $request_result = (new Client(['timeout' => 60]))->post($url, $data); // $response = $request_result->getBody(); //返回的是个对象 $response = getHttpPost($url, $data); return $response; } catch (Exception $e) { log_message("同步分包: channel_id={$data['channel_id']}, filename=" . $data['filename'] . '; error=' . $e->getMessage() . " -- url:" . $url, 'error', LOG_PATH . 'subpackage/'); return false; } } /** * 获取渠道的指定冻结选项的状态 * 此方法除了判断自身渠道的状态, 也将追溯所有层级的父渠道相应的状态 * 如果有被禁止的就会返回被禁止状态 * * @param $p_depId 渠道ID * @param $p_gameId 游戏ID * @param $p_field 选项的数据库字段名 * * @return true: 被禁止; false: 未禁止 */ function isFrozenOption($p_depId, $p_gameId, $p_field = 'subpackage') { $isFrozen = false; $depId = (int)$p_depId; $gameId = (int)$p_gameId; $field = trim($p_field); $i = 0; $findFrozenStatus = true; //循环控制标识 do { $channelFrozenModel = \think\Db::name('nw_channel_frozen'); $frozenStatus = $channelFrozenModel->cache('common:isFrozenOption:' . $p_field . ':' . $depId . ':' . $gameId, 300)->field($field)->where(['channel_id' => $depId, 'game_id' => $gameId])->find(); if (!empty($frozenStatus) && $frozenStatus[$p_field] == 1) { $isFrozen = true; $findFrozenStatus = false; } if ($depId == config('TOP_CHANNEL_ID')) { $findFrozenStatus = false; } if ($findFrozenStatus) { $channelModel = \think\Db::name('nw_channel'); $depId = (int)$channelModel->cache('common:isFrozenOptionChannel:' . $depId, 300)->where(['id' => $depId])->value('parent_id'); if (empty($depId)) { $findFrozenStatus = false; } } if ($i > 10) { $findFrozenStatus = false; } $i++; } while ($findFrozenStatus); return $isFrozen; } /** * 调用钉钉机器人函数 * */ function curlDD($msg = '', $url, $all = false) { // return false; // 30分钟只提醒一次 $cacheName = md5($msg); $redis = think\Cache::store('default')->handler(); $cacheKey = "dd:message:".$cacheName; $cacheNum = $redis->get($cacheKey); if($cacheNum){ $redis->incr($cacheKey); if(!in_array($cacheNum, [1,5,10, 30, 50, 100])){ return true; } }else{ $cacheNum = 1; $redis->setex($cacheKey, 1800, 1); } $msg .= ' - 循环次数:'.$cacheNum; if($msg && Env::get('dingtalk.server_name', '')){ $server_name = Env::get('dingtalk.server_name', ''); $msg = "[{$server_name}]: ".$msg; } if ($all) { $postData = [ 'msgtype' => 'text', 'text' => [ 'content' => mb_convert_encoding($msg, "UTF-8", "auto"), 'mentioned_list' => array('@all'), ], 'at' => [ "atMobiles" => [Config::get('dd_message')['develop']], ] ]; } else { $postData = [ 'msgtype' => 'text', 'text' => [ 'content' => mb_convert_encoding($msg, "UTF-8", "auto"), ] ]; } $postString = json_encode($postData); $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在 curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); curl_setopt($ch, CURLOPT_HTTPHEADER, array( "Content-Type: application/json" )); $result = curl_exec($ch); $HTTP_CODE = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // if($HTTP_CODE<>200){ // $arr=array("state"=>-100); // echo json_encode($arr); // exit; // }elseif($result==""){ // $arr=array("state"=>0,"errMsg"=>"接口返回为空"); // echo json_encode($arr); // exit; // } return $result; } /** * 调用钉钉机器人函数-markdown * * @param $msg 消息内容字符串 * @param $url 机器人地址 * @param $devalop 是否@负责人 * * @return bool|string */ function curlDDMarkdown($msg, $url, $devalop = false) { // 30分钟只提醒一次 $cacheName = md5($msg); $redis = think\Cache::store('default')->handler(); $cacheKey = "dd:message:".$cacheName; $cacheNum = $redis->get($cacheKey); if($cacheNum){ $redis->incr($cacheKey); if(!in_array($cacheNum, [1,5,10, 30, 50, 100])){ return true; } }else{ $cacheNum = 1; $redis->setex($cacheKey, 1800, 1); } $msg .= ' - 循环次数:'.$cacheNum; $title = config('dd_notice.server_name')??'祁盟'; if($devalop){ $msg = $title." @".config('dd_notice.develop')." \n ". $msg; $postData = [ 'msgtype' => 'text', 'text' => [ // 'content' => mb_convert_encoding($msg, "UTF-8", "auto"), 'content' => $msg, 'mentioned_list' => array('@all'), ], 'at' => [ "atMobiles" => [Config::get('dd_notice')['develop']], ] ]; }else{ $postData = [ 'msgtype' => 'markdown', "markdown" => [ 'title' => $title, 'text' => $title.' '.$msg, ], ]; } $postString = json_encode($postData); $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在 curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); curl_setopt($ch, CURLOPT_HTTPHEADER, array( "Content-Type: application/json" )); $result = curl_exec($ch); $HTTP_CODE = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $result; } /** * 钉钉markdown通知管理 * * @param string $notice_type 通知类型: warning/weelfar/notic/operat * @param string $msg_template 消息模板: 使用默认要为空 * @param string $msg_data 通知数据: 默认=标题、报错说明、相关标识 * @param bool $develop 是否呼叫管理员 * * @return false|void */ function ddMsg($notice_type, $msg_template = 'defaults', $msg_data = [], $develop = false){ if(!array_key_exists($notice_type, Config::get('dd_notice.notice_type'))){ return false; } $url = Config::get('dd_notice.notice_type')[$notice_type]; if(array_key_exists($msg_template, Config::get('dd_notice.msg_template'))){ // TODO: 不同模板多个 data 判断,未完善。 $warning_msg = sprintf(Config::get('dd_notice')['msg_template'][$msg_template],$msg_data[0], $msg_data[1], $msg_data[2], $msg_data[3], date('Y-m-d H:i:s', time())); }else{ $warning_msg = sprintf(Config::get('dd_notice')['msg_template']['defaults'],$msg_data[0], $msg_data[1], $msg_data[2], date('Y-m-d H:i:s', time())); } if($notice_type == 'warning'){ $develop = true; } curlDDMarkdown($warning_msg, $url, $develop); } /** * 发送飞书机器人消息 * * @param string $webhook 飞书机器人的 Webhook URL * @param string $content 消息内容 * @param string $msgType 消息类型:text 或 post(富文本,支持格式化) * @param string $title 富文本标题(msgType=post 时有效) * * @return bool|string * * 范例: * **加粗文本** * ~~删除线~~ * [超链接](https://example.com) * @人 * * * // 纯文本 * sendFeishuMessage('https://open.feishu.cn/open-apis/bot/v2/hook/你的webhook', '这是一条测试消息'); * * // 富文本(支持格式化) * $message = "任务执行完成!\n耗时:5.2秒\n状态:成功"; * sendFeishuMessage('https://open.feishu.cn/...', $message, 'post', '✅❌⚠️ 通知标题'); * */ function sendFeishuMessage($webhook, $content, $msgType = 'text', $title = '') { if (empty($webhook) || empty($content)) { return ['success' => false, 'error' => 'webhook or content is empty']; } $data = []; if ($msgType === 'interactive') { // 卡片消息 - 支持 Markdown 格式(推荐) $data = [ 'msg_type' => 'interactive', 'card' => [ 'header' => [ 'title' => [ 'tag' => 'plain_text', 'content' => $title ?: '祈盟-系统通知' ], 'template' => 'red' // 红色警示 ], 'elements' => [ [ 'tag' => 'div', 'text' => [ 'tag' => 'lark_md', 'content' => $content ] ] ] ] ]; } elseif ($msgType === 'post') { // 富文本格式(飞书原生格式,不是 Markdown) $data = [ 'msg_type' => 'post', 'content' => [ 'post' => [ 'zh_cn' => [ 'title' => $title ?: '祈盟-系统通知', 'content' => [ [ [ 'tag' => 'text', 'text' => $content ] ] ] ] ] ] ]; } else { // 纯文本 $data = [ 'msg_type' => 'text', 'content' => [ 'text' => "祈盟-".$content ] ]; } $ch = curl_init($webhook); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); // SSL 配置(飞书是 HTTPS) if (substr($webhook, 0, 5) == 'https') { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } $result = curl_exec($ch); // 检查 curl 是否执行成功 if ($result === false) { $error = curl_error($ch); $errno = curl_errno($ch); curl_close($ch); return ['success' => false, 'error' => "CURL_ERROR [{$errno}]: {$error}"]; } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { return ['success' => false, 'error' => "HTTP_ERROR: status={$httpCode}, body={$result}"]; } $response = json_decode($result, true); if ($response === null) { return ['success' => false, 'error' => "JSON_DECODE_ERROR: {$result}"]; } if (isset($response['code']) && $response['code'] == 0) { return ['success' => true, 'data' => $response]; } return ['success' => false, 'error' => "FEISHU_ERROR: code=" . ($response['code'] ?? 'unknown') . ", msg=" . ($response['msg'] ?? 'unknown')]; } /** * 输出xml字符 * @param $params array 参数 * * @return string 返回组装的xml **/ function dataToXml($params) { if (!is_array($params) || count($params) <= 0) { return false; } $xml = ""; foreach ($params as $key => $val) { if (is_numeric($val)) { $xml .= "<" . $key . ">" . $val . ""; } else { $xml .= "<" . $key . ">"; } } $xml .= ""; return $xml; } /** * 防止重复提交的并发控制 * * @param $cache_key string redis的key * @param $time_limit int 限制时间 * * @return boolean true:允许 false:禁止 */ function requestDuplicateCheck($cache_key, $time_limit) { $redis = \think\Cache::store('default')->handler(); //redis的句柄对象 //redis系统的当前时间 $redis_time = $redis->time()[0]; //初步加锁 $isLock = $redis->setnx($cache_key, $redis_time + $time_limit); if ($isLock) { $redis->expire($cache_key, $time_limit); return true; } else { //加锁失败的情况下。判断锁是否已经存在,如果锁存在切已经过期,那么删除锁。进行重新加锁 $val = $redis->get($cache_key); if ($val && $val < $redis_time) { $redis->del($cache_key); } $result = $redis->setnx($cache_key, $redis_time + $time_limit); if ($result) { $redis->expire($cache_key, $time_limit); } return $result; } } /** * 并发解锁 * @param $cache_key */ function requestDuplicateCheckUnlock($cache_key) { $redis = \think\Cache::store('default')->handler(); //redis的句柄对象 $redis->del($cache_key); } /** * 如果是当月7日之前(包括7日),则可以导出上个月和这个月的报表; 如果是7日之后,则只能导出这个月报表 * @param $start 开始时间戳 * @param $end 结束时间戳 * @param int $day 日期分界(例:7号填 7 ) * @return array */ function judgeReportTime($start, $end, $day = 7) { $data = ['code' => 0, 'msg' => '违规操作,禁止导出报表']; $nowDay = date('d'); $lastMonth = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month'))); // 上个月1号时间戳 $beginThismonth = mktime(0, 0, 0, date('m'), 1, date('Y')); // 这个月的开始时间 $endThismonth = mktime(23, 59, 59, date('m'), date('t'), date('Y')); // 这个月的结束时间 if ($end < $start || $lastMonth > $start || $end > $endThismonth) { return $data; } if ($nowDay <= $day && $lastMonth >= $start) { $data['code'] = 1; } elseif ($nowDay <= $day && date("Y-m", $start) == date("Y-m", $end)) { $data['code'] = 1; } elseif ($nowDay > $day && date("Y-m", $start) == date("Y-m", $end) && $start >= $beginThismonth) { $data['code'] = 1; } return $data; } //以下微信公众号使用 function apipost($url, $arr = array(), $parse = true, $authentication = false) { $param = ""; if ($parse) { foreach ($arr as $key => $value) { $param = $param . $key . "=" . $value . "&"; } $len = strlen($param) - 1; $param = substr($param, 0, $len); } else { $param = $arr; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); if ($authentication) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_POST, 1); //启用POST提交 curl_setopt($ch, CURLOPT_POSTFIELDS, $param); $file_contents = curl_exec($ch); curl_close($ch); return $file_contents; } function dd() { echo Db::getLastSql(); die; } function priceFormat($number, $decimals = 2, $dec_point = '.', $thousands_sep = '') { return number_format($number, $decimals, $dec_point, $thousands_sep); } function getCurl($url, $data = [], $timeout = 30) { if ($url == "" || $timeout <= 0) { return false; } if ($data) { $url = $url . '?' . http_build_query($data); } $con = curl_init((string)$url); curl_setopt($con, CURLOPT_HEADER, false); curl_setopt($con, CURLOPT_RETURNTRANSFER, true); curl_setopt($con, CURLOPT_TIMEOUT, (int)$timeout); return curl_exec($con); } function opensslEncrypt($data) { $key = 'ukN3KYVdI2sPtjpL'; return base64_encode(openssl_encrypt($data, 'AES-128-ECB', $key, 0)); } function opensslDecrypt($data) { $key = 'ukN3KYVdI2sPtjpL'; return openssl_decrypt(base64_decode($data), 'AES-128-ECB', $key, 0); } function curl($url, $params = [], $returnJson = false) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // 获取数据返回 curl_setopt($curl, CURLOPT_TIMEOUT, 30); //超时时长,单位秒 if ($params) { curl_setopt($curl, CURLOPT_POST, true); // post传输数据 curl_setopt($curl, CURLOPT_POSTFIELDS, $params); // post传输数据 } // curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // 设置请求头 $result = curl_exec($curl); if ($result === false) { $result = '接口超时'; } curl_close($curl); if ($returnJson) { $result = json_decode($result, true); } return $result; } function is_mobile_request() { $_SERVER['ALL_HTTP'] = isset($_SERVER['ALL_HTTP']) ? $_SERVER['ALL_HTTP'] : ''; $mobile_browser = '0'; if (preg_match('/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|iphone|ipad|ipod|android|xoom)/i', strtolower($_SERVER['HTTP_USER_AGENT']))) $mobile_browser++; if ((isset($_SERVER['HTTP_ACCEPT'])) and (strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'application/vnd.wap.xhtml+xml') !== false)) $mobile_browser++; if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) $mobile_browser++; if (isset($_SERVER['HTTP_PROFILE'])) $mobile_browser++; $mobile_ua = strtolower(substr($_SERVER['HTTP_USER_AGENT'], 0, 4)); $mobile_agents = array( 'w3c ', 'acs-', 'alav', 'alca', 'amoi', 'audi', 'avan', 'benq', 'bird', 'blac', 'blaz', 'brew', 'cell', 'cldc', 'cmd-', 'dang', 'doco', 'eric', 'hipt', 'inno', 'ipaq', 'java', 'jigs', 'kddi', 'keji', 'leno', 'lg-c', 'lg-d', 'lg-g', 'lge-', 'maui', 'maxo', 'midp', 'mits', 'mmef', 'mobi', 'mot-', 'moto', 'mwbp', 'nec-', 'newt', 'noki', 'oper', 'palm', 'pana', 'pant', 'phil', 'play', 'port', 'prox', 'qwap', 'sage', 'sams', 'sany', 'sch-', 'sec-', 'send', 'seri', 'sgh-', 'shar', 'sie-', 'siem', 'smal', 'smar', 'sony', 'sph-', 'symb', 't-mo', 'teli', 'tim-', 'tosh', 'tsm-', 'upg1', 'upsi', 'vk-v', 'voda', 'wap-', 'wapa', 'wapi', 'wapp', 'wapr', 'webc', 'winw', 'winw', 'xda', 'xda-' ); if (in_array($mobile_ua, $mobile_agents)) $mobile_browser++; if (strpos(strtolower($_SERVER['ALL_HTTP']), 'operamini') !== false) $mobile_browser++; // Pre-final check to reset everything if the user is on Windows if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows') !== false) $mobile_browser = 0; // But WP7 is also Windows, with a slightly different characteristic if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows phone') !== false) $mobile_browser++; if ($mobile_browser > 0) return true; else return false; } function timingCurl($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_exec($ch); curl_close($ch); } function getMonthRange($date, $returnFirstDay = true) { $timestamp = strtotime($date); if ($returnFirstDay) { $monthFirstDay = date('Y-m-1 00:00:00', $timestamp); return $monthFirstDay; } else { $mdays = date('t', $timestamp); $monthLastDay = date('Y-m-' . $mdays . ' 23:59:59', $timestamp); return $monthLastDay; } } /** * @return float 获取当前毫秒时间戳 */ function getMillisecond() { list($microsecond, $time) = explode(' ', microtime()); //' '中间是一个空格 return (float)sprintf('%.0f', (floatval($microsecond) + floatval($time)) * 1000); } /** * 是否json字符串 * * @param $string * * @return bool */ function is_json($string) { if (!is_string($string) || $string === '') { return false; } $decoded = json_decode($string); // 检查是否为对象或数组,且没有错误 return (json_last_error() === JSON_ERROR_NONE) && ($decoded !== null) && (is_object($decoded) || is_array($decoded)); } function curlHeader($url, $params = '', $header = [], $returnJson = false) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // 获取数据返回 curl_setopt($curl, CURLOPT_TIMEOUT, 30); //超时时长,单位秒 curl_setopt($curl, CURLOPT_POST, true); // post传输数据 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); if ($params) { curl_setopt($curl, CURLOPT_POSTFIELDS, $params); } if (!empty($header)) { curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // 设置请求头 } $result = curl_exec($curl); if ($result === false) { $result = '接口超时'; } $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if ($returnJson) { $result = json_decode($result, true); $result['code_qf'] = $code; } return $result; } function temporary() { $time = time(); $start_time = '1670256000'; $end_time = '1670342400'; if($start_time<$time && $end_time>time()){ return true; }else{ return false; } } /** * 生成唯一值 * @return string */ function makeUniqueid($head = 'FL') { $redis = think\Cache::store('default'); $uniqueid = (date('YmdHis').getRandomStr(8, false) . substr(str_pad(mt_rand(), 6, "0", STR_PAD_BOTH), 0, 6)); // 判断是否有同个唯一值的请求还没处理完,防止因为网络问题,导致同个订单并发 $cache_key = 'orderid:' . $uniqueid; if ($redis->has($cache_key)) { return makeUniqueid(); } else { $redis->set($cache_key, $uniqueid, 86400); return $head . $uniqueid; } } /** * 获得随机字符串 * @param $len 需要的长度 * @param $special 是否需要特殊符号 * @return string 返回随机字符串 */ function getRandomStr($len, $special=true){ $chars = array( "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", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ); if($special){ $chars = array_merge($chars, array( "!", "@", "#", "$", "?", "|", "{", "/", ":", ";", "%", "^", "&", "*", "(", ")", "-", "_", "[", "]", "}", "<", ">", "~", "+", "=", ",", "." )); } $charsLen = count($chars) - 1; shuffle($chars); //打乱数组顺序 $str = ''; for($i=0; $i<$len; $i++){ $str .= $chars[mt_rand(0, $charsLen)]; //随机取出一位 } return $str; } function setChannelId($userid,$channelId){ // \think\Cache::store('default')->set('token:channelId:'.$userid, $channelId, 60 * 60 * 24 * 30); } function getChannelId($userid, $gameid = ''){ // $channelId = \think\Cache::store('default')->get('token:channelId:'.$userid); // ->cache(true, 86400) $channelId = Db::table('nw_subaccount')->where(['member_id' => $userid, 'game_id' => $gameid])->value('channel_id'); if($channelId > 0){ return $channelId; }else{ return 0; } } // 获取折扣比例 function getDiscount($userid = 0,$gameId = 0,$channelId = 0){ $proportion = \Think\Db::name('cy_game_pay_discount')->where(['game_id' => $gameId])->value('proportion'); return $proportion??"1"; } /** * 金额分转元 * @param int $fen * @return string */ function formatFenToYuan($fen, $long = 2) { return number_format($fen / 100, $long, '.', ''); } /** * 金额元转分 * * @param int $fen * * @return string */ function formatYuanToFen($yuan, $long = 0) { return number_format($yuan * 100, $long, '.', ''); } /** * 获取渠道游戏支付金额的产品标识 * * @param string $money 获取金额(元)(支付金额最大为9w元) * @param string $mark 前缀标识 * * @return string */ function formatComplexProductMark($mark, $game_id, $money) { $money = formatYuanToFen($money); $cpMark = $mark . '_' . str_pad($money, 7, "0", STR_PAD_LEFT); // 拼接标识(0.06 = kdzz_0000006) if (!isset(config('config_complex_goods')[$mark][$cpMark])) { return false; } return $cpMark; } /** * 获取游戏所属渠道的标识 * * @param $jh_mark 渠道标识 * @param $game_id 游戏ID * * @return array */ function isComplexProduct($jh_mark, $game_id) { $ids = Env::get('complex.sj_game_ids', ''); $ids = explode(',', $ids); $channelList = []; foreach ($ids as $v) { $channel = explode('_', $v); $channelList[$channel[0]] = $channel[1]; } if(!in_array($game_id, array_keys($channelList))){ return ""; } return $jh_mark.'_'.$channelList[$game_id]; } function dump($data, $type = true) { $params = func_get_args(); if ($type == true) { echo "
";
    }
    var_dump(...$params);
    die;
}

/**
 * 发起请求提交
 *
 * @param string $type   请求类型 GET/POST
 * @param string $url    请求URL
 * @param array  $data   POST请求数据
 * @param array  $header Header头数据
 *
 * @return
 */
function getHttp($type = 'GET', $url, $data = [], $header = ['Content-Type: application/json'], $headerStatus = false)
{
    $curl = curl_init($url);
    // curl_setopt($curl, CURLOPT_URL, $url); // 设置抓取的url
    // curl_setopt($curl, CURLOPT_HEADER, 0); // 设置头文件的信息作为数据流输出
    curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 超时设置,以秒为单位

    // 超时设置,以毫秒为单位
    // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 300);
    if ($header) {
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // 设置请求头
    }

    //设置获取的信息以文件流的形式返回,而不是直接输出。
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);     // 执行后不直接打印出来
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); // 跳过证书检查
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在

    if ($type == 'POST') {
        // curl_setopt($curl, CURLOPT_POST, 1); // 设置请求方式为post
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // post的变量
        // curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // post的变量
    }

    if ($headerStatus == true) {
        $header = curl_getinfo($curl);
    }

    $result = curl_exec($curl); // 执行并获取內容
    curl_close($curl);          // 释放curl句柄

    if (!is_json($result)) {
        $result = json_encode($result, true);
    }

    // trace("## common@getHttp: ".$type . " - URL: " . $url . " - Data: " . json_encode($data) . ' - Result: ' . $result, 'common@getHttp.$result');
    log_message("## common@getHttp - ".date("Y-m-d H:i:s", time()).": ".$type . " - URL: " . $url . " - Data: " . json_encode($data) . ' - Result: ' . $result, 'log', LOG_PATH . 'httpLog/'); // ## aplog log

    return $result;
}


/**
 * 模拟 GET 请求
 *
 * @param string $url    请求地址
 * @param array  $header Header头数据
 *
 * @return
 */
function getHttpGet(string $url, $query = [], $header = [], $json = true)
{
    // $res = getHttp('GET', $url . '?' . assemblyByQuery($query), [], $header);
    $query = http_build_query($query);
    if (!empty($query)) {
        $query = "?" . $query;
    }
    return getHttp('GET', $url . $query, [], $header);
}

/**
 * 模拟 POST 请求
 *
 * @param string $url          请求地址
 * @param array  $data         请求数据
 * @param array  $header       Header头数据
 * @param array  $headerStatus 是否返回请求返回的Header数据
 *
 * @return array
 */
function getHttpPost(string $url, $data = [], $header = [], $json = true, $headerStatus = false)
{
    return getHttp('POST', $url, $data, $header, $headerStatus);
}

/**
 * GET 数组数据处理为拼接字符串
 *
 * @param array   $query 处理的数据数组
 * @param boolean $isUrl 是否是URL参数标识(是的话,返回的字符串前缀加?标识)
 *
 * @return string
 */
function assemblyByQuery($query, $isUrl = false)
{
    ksort($query);
    $queryData = '';
    foreach ($query as $key => $value) {
        if (!$queryData) {
            if ($isUrl) {
                $queryData = '?';
            }
            $queryData .= $key . '=' . $value;
        } else {
            $queryData .= '&' . $key . '=' . $value;
        }
    }
    return $queryData;
}

//获取真实IP,可以根据实际情况自行获取
function getUserIp()
{
    try {
        $info = getCurl('https://myip.ipip.net');
        if (!$info) {
            $info = file_get_contents('http://myip.ipip.net');
        }

        $ipstr = explode(':', $info);
        $ip = explode(' ', $ipstr[1]);
        return $ip[0];
    } catch (Exception $e) {
        return request()->ip();
    }
}

/**
 * 为支付方式增加其他附加支付类型
 *  conin- = 代金卷
 *  mix- = 专属平台币(目前好像没用)
 *
 * @param $list
 *
 * @return array
 */
function handlePayType($list)
{
    $newList = [];
    $markList = ['coin-', 'mix-'];
    foreach ($list as $key => $value) {
        $newPayType = [];
        foreach ($value as $value1) {
            $newPayType[] = $value1;
            foreach ($markList as $value2) {
                $newPayType[] = $value2 . $value1;
            }
        }
        $newList[$key] = $newPayType;
    }
    return $newList;
}

/**
 * 判断二维数组中是否包含某个字符串
 *
 * @param $arr         array 二维数组
 * @param $searchValue string 包含值
 *
 * @return bool
 */
function isInArrType($arr, $searchValue)
{
    foreach ($arr as $key => $values) {
        if (in_array($searchValue, $values)) {
            return true;
        }
    }
    return false;
}

/**
 * 获取游戏的分包域名地址
 * 配置了则走CDN域名,没有则走默认分包服务器
 *
 * @param $game_id
 *
 * @return string
 */
function getHandleCdnGamesByIds($game_id)
{
    $result = (new Setting())->getSetting('handle_cdn_games');
    if ($result) {
        if ($result == 'all') {
            return APK_DOWN_DOMAIN_NEW;
        }
        $res = json_decode($result, true);
        if (in_array($game_id, $res)) {
            return APK_DOWN_DOMAIN_NEW;
        }
    }
    return APK_DOWN_DOMAIN;
}

function getChannelH5Url($code, $gameId){
    $h5_url = (new Setting)::getSetting('HTTP_HOST_URL_H5');
    return $h5_url.'/?c='. $code.'&g='.$gameId;
}


if (!function_exists('definition_log')) {
    function definition_log($pathType, $message, $level = 'info') {
        $customLogPath = LOG_PATH . "definition/{$pathType}/";
        // dump($customLogPath);
        static $logInited = false;
        if (!$logInited) {
            \think\Log::init([
                // 日志记录方式,内置 file socket 支持扩展
                'type'  => 'File',
                // 日志保存目录
                'path'  => $customLogPath,
                // 日志记录级别
                'level' => ['log', 'error', 'notice', 'info', 'debug', 'sql'],
                'max_files'	=> 30,
                'realtime_write' => true,
            ]);
            $logInited = true;
        }
        \think\Log::write($message, $level);
    }
}

/**
 * 获取游戏绑定的其他端游戏ID
 *
 * @param $gameId
 *
 * @return array|mixed
 * @throws DataNotFoundException
 * @throws DbException
 * @throws ModelNotFoundException
 */
function getBandGameId($gameId){

    $ameBandList = Db::table('nw_game_band')->select();
    $newBandGameList = [];
    foreach($ameBandList as $v){
        if(!empty($v['android_game_id'])){
            $newBandGameList[$v['android_game_id']] = [$v['ios_game_id'], $v['h5_game_id']];
        }
        if(!empty($v['ios_game_id'])){
            $newBandGameList[$v['ios_game_id']] = [$v['android_game_id'], $v['h5_game_id']];
        }
        if(!empty($v['h5_game_id'])){
            $newBandGameList[$v['h5_game_id']] = [$v['android_game_id'], $v['ios_game_id']];
        }
    }
    if(empty($newBandGameList[$gameId])){
        return [];
    }
    return $newBandGameList[$gameId]; // [20, 30]
}

/**
 * 清理金额输入,保留两位小数
 *
 * @param $value
 *
 * @return float
 */
function cleanAmount($value) {
    /**
     * ¥1,000.00 => 1000.00
     * $2,345.67  => 2345.67
     * 1 234.50元 => 1234.50
     * 3.000,50 => 3000.50
     * abc1234.56def => 1234.56
     */
    // 1. 转为字符串,防止传入非字符串类型
    $value = (string)$value;

    // 2. 去掉所有非数字、小数点、负号
    $value = preg_replace('/[^\d.\-]/', '', $value);

    // 3. 防止多个小数点导致格式错误,只保留第一个
    if (substr_count($value, '.') > 1) {
        $parts = explode('.', $value, 2);
        $value = $parts[0] . '.' . preg_replace('/\./', '', $parts[1]);
    }

    return (float)$value;
    // 4. 转为浮点数并保留两位小数(或根据需求改为字符串)
    // return number_format((float)$value, 2, '.', '');
}

// 根据渠道ID获取渠道所属的上级渠道ID
function getChannel($channel_id)
{
    // 进程级静态缓存,一次请求只读一次文件
    static $channelList = null;
    if ($channelList === null) {
        $channelList = Cache::store('File')->get('cache_retaine_channel_list');
        if (!$channelList) {
            $channelList = model('common/Channel')->column('id,name,id_path,level', 'id');
            Cache::store('File')->set('cache_retaine_channel_list', $channelList, 3600);
        }
    }

    if (!isset($channelList[$channel_id])) {
        return [];
    }

    $data = [];
    $id_path = explode(',', $channelList[$channel_id]['id_path']);
    foreach ($id_path as $vv) {
        if ($vv == $channel_id || !isset($channelList[$vv])) {
            continue;
        }
        $ch = $channelList[$vv];
        if ($ch['level'] == 0) {
            $data['s_channel_id'] = $ch['id'];
        } elseif ($ch['level'] == 1) {
            $data['b_channel_id'] = $ch['id'];
        } elseif ($ch['level'] == 2) {
            $data['bz_channel_id'] = $ch['id'];
        }
    }
    return $data;
}

/**
 * 解析 URL 参数,保留 + 号不转换成空格
 * PHP 的 parse_str() 和 $_POST 会自动把 + 转成空格,导致签名验证失败
 *
 * @param string $queryString 原始查询字符串
 * @return array 解析后的参数数组
 */
function parseParamsKeepPlus($queryString)
{
    $params = [];
    if (empty($queryString)) {
        return $params;
    }

    $pairs = explode('&', $queryString);
    foreach ($pairs as $pair) {
        $parts = explode('=', $pair, 2);
        if (count($parts) === 2) {
            // 只解码 %XX 格式的编码,保留 + 号
            $key = rawurldecode($parts[0]);
            $value = rawurldecode($parts[1]);
            $params[$key] = $value;
        }
    }

    return $params;
}