* @date 2019/3/3
* @time 16:34
*/
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\SetCookie;
use Luolongfei\App\Console\GlobalValue;
use Luolongfei\App\Console\MigrateEnvFile;
use Luolongfei\App\Console\Upgrade;
use Luolongfei\App\Constants\CommonConst;
use Luolongfei\App\Exceptions\LlfException;
use Luolongfei\Libs\Argv;
use Luolongfei\Libs\Config;
use Luolongfei\Libs\Env;
use Luolongfei\Libs\Lang;
use Luolongfei\Libs\Log;
use Luolongfei\Libs\PhpColor;
if (!function_exists('config')) {
/**
* 获取配置
*
* @param string $key 键,支持点式访问
* @param string $default 默认值
*
* @return array|mixed
*/
function config($key = '', $default = null)
{
return Config::getInstance()->get($key, $default);
}
}
if (!function_exists('lang')) {
/**
* 读取语言包
*
* @param string $key 键,支持点式访问
*
* @return array|mixed|null
*/
function lang($key = '')
{
return Lang::getInstance()->get($key);
}
}
if (!function_exists('system_log')) {
/**
* 写日志
*
* @param $content
* @param array $response
* @param string $fileName
* @description 受支持的着色标签
* 'reset', 'bold', 'dark', 'italic', 'underline', 'blink', 'reverse', 'concealed', 'default', 'black', 'red',
* 'green', 'yellow', 'blue', 'magenta', 'cyan', 'light_gray', 'dark_gray', 'light_red', 'light_green',
* 'light_yellow', 'light_blue', 'light_magenta', 'light_cyan', 'white', 'bg_default', 'bg_black', 'bg_red',
* 'bg_green', 'bg_yellow', 'bg_blue', 'bg_magenta', 'bg_cyan', 'bg_light_gray', 'bg_dark_gray', 'bg_light_red',
* 'bg_light_green','bg_light_yellow', 'bg_light_blue', 'bg_light_magenta', 'bg_light_cyan', 'bg_white'
*
* system_log('颜色 light_magenta');
*/
function system_log($content, array $response = [], $fileName = '')
{
try {
$msg = sprintf(
"[%s] %s %s\n",
date('Y-m-d H:i:s'),
is_string($content) ? $content : json_encode($content),
$response ? json_encode($response, JSON_UNESCAPED_UNICODE) : '');
// 过滤敏感信息
if ((int)env('MOSAIC_SENSITIVE_INFO') === 1) {
// 在 php 7.3 之前,连字符“-”在中括号中随便放,但在之后,只能放在开头或结尾或者转义后才能随便放
$msg = preg_replace_callback('/(?P[\w.-]{1,3}?)(?=@[\w.-]+)/ui', function ($m) {
return str_ireplace($m['secret'], str_repeat('*', strlen($m['secret'])), $m['secret']);
}, $msg);
}
// 尝试为消息着色
$c = PhpColor::getInstance()->getColorInstance();
echo $c($msg)->colorize();
// 干掉着色标签
$msg = strip_tags($msg); // 不完整或者破损标签将导致更多的数据被删除
// 写入日志文件
if (is_writable(ROOT_PATH)) {
$path = sprintf('%s/logs/%s/', ROOT_PATH, date('Y-m'));
$file = $path . ($fileName ?: date('d')) . '.log';
if (!is_dir($path)) {
mkdir($path, 0766, true); // 0766 赋予所有用户读写
chmod($path, 0766); // 显式 chmod 确保权限设置正确
}
$handle = fopen($file, 'a'); // 追加而非覆盖
if ($handle !== false) {
if (!filesize($file)) {
chmod($file, 0666);
}
fwrite($handle, $msg);
fclose($handle);
}
}
flush();
} catch (\Exception $e) {
// do nothing
}
}
}
if (!function_exists('is_locked')) {
/**
* 检查任务是否已被锁定
*
* @param string $taskName
* @param bool $always 是否被永久锁定
*
* @return bool
* @throws Exception
*/
function is_locked($taskName = '', $always = false)
{
try {
$lock = sprintf(
'%s/num_limit/%s/%s.lock',
APP_PATH,
$always ? 'always' : date('Y-m-d'),
$taskName
);
return file_exists($lock);
} catch (\Exception $e) {
system_log(sprintf('检查任务%s是否锁定时出错,错误原因:%s', $taskName, $e->getMessage()));
}
return false;
}
}
if (!function_exists('lock_task')) {
/**
* 锁定任务
*
* 防止重复执行
*
* @param string $taskName
* @param bool $always 是否永久锁定
*
* @return bool
*/
function lock_task($taskName = '', $always = false)
{
try {
$lock = sprintf(
'%s/num_limit/%s/%s.lock',
APP_PATH,
$always ? 'always' : date('Y-m-d'),
$taskName
);
$path = dirname($lock);
if (!is_dir($path)) {
mkdir($path, 0777, true);
chmod($path, 0777);
}
if (file_exists($lock)) {
return true;
}
$handle = fopen($lock, 'a'); // 追加而非覆盖
if (!filesize($lock)) {
chmod($lock, 0666);
}
fwrite($handle, sprintf(
"Locked at %s.\n",
date('Y-m-d H:i:s')
)
);
fclose($handle);
Log::info(sprintf('%s已被锁定,此任务%s已不会再执行,请知悉', $taskName, $always ? '' : '今天内'));
} catch (\Exception $e) {
system_log(sprintf('创建锁定任务文件%s时出错,错误原因:%s', $lock, $e->getMessage()));
return false;
}
return true;
}
}
if (!function_exists('env')) {
/**
* 获取环境变量值
*
* @param string $key
* @param string $default 默认值
*
* @return array | bool | false | null | string
*/
function env($key = '', $default = null)
{
return Env::getInstance()->get($key, $default);
}
}
if (!function_exists('get_argv')) {
/**
* 获取命令行传参
*
* @param string $name
* @param string $default 默认值
*
* @return mixed|string
*/
function get_argv(string $name, string $default = '')
{
return Argv::getInstance()->get($name, $default);
}
}
if (!function_exists('system_check')) {
/**
* 检查环境是否满足要求
*
* @throws LlfException
*/
function system_check()
{
// 由于各种云函数目前支持的最大的 PHP 版本为 7.2,故此处暂时不强制要求升级 PHP 7.3 以获得更好的兼容性
if (version_compare(PHP_VERSION, '7.2.0') < 0) {
throw new LlfException(34520006, ['7.3', PHP_VERSION]);
}
// 特殊环境无需检查这几项
if (IS_SCF || !is_writable(ROOT_PATH) || (int)env('IS_KOYEB') === 1 || (int)env('IS_HEROKU') === 1) {
system_log(lang('100009'));
} else {
if (!function_exists('putenv')) {
throw new LlfException(34520005);
}
$envFile = ROOT_PATH . '/.env';
if (!file_exists($envFile)) {
throw new LlfException(copy(ROOT_PATH . '/.env.example', $envFile) ? 34520007 : 34520008);
}
// 检查当前 .env 文件版本是否过低,过低自动升级
MigrateEnvFile::getInstance()->handle();
}
// 是否有新版可用
if (config('new_version_detection')) {
Upgrade::getInstance()->handle();
} else {
system_log(lang('100012'));
}
if (!extension_loaded('curl')) {
throw new LlfException(34520010);
}
}
}
if (!function_exists('get_local_num')) {
/**
* 获取当地数字
*
* @param string|int $num
*
* @return string
*/
function get_local_num($num)
{
$num = (string)$num;
if (is_chinese()) {
return $num;
}
// 英文数字规则
$lastDigit = substr($num, -1);
switch ($lastDigit) {
case '1':
return $num . 'st';
case '2':
return $num . 'nd';
case '3':
return $num . 'rd';
default:
return $num . 'th';
}
}
}
if (!function_exists('is_chinese')) {
/**
* 判断当前语言环境
*
* @return bool
*/
function is_chinese()
{
return config('custom_language', 'zh') === 'zh';
}
}
if (!function_exists('get_ip_info')) {
/**
* 获取 ip 信息
*
* @return string
*/
function get_ip_info()
{
return \Luolongfei\Libs\IP::getInstance()->get();
}
}
if (!function_exists('get_random_user_agent')) {
/**
* 获取随机 user-agent
*
* @return string
*/
function get_random_user_agent()
{
$chromeVersions = [
'121.0.0.0',
];
return $chromeVersions[array_rand($chromeVersions)];
}
}
if (!function_exists('autoRetry')) {
/**
* 自动重试
*
* @param $func
* @param int $maxRetryCount
* @param array $params
*
* @return mixed|void
* @throws Exception
*/
function autoRetry($func, $maxRetryCount = 3, $params = [])
{
$retryCount = 0;
while (true) {
try {
return call_user_func_array($func, $params);
} catch (\Exception $e) {
$retryCount++;
if ($retryCount > $maxRetryCount) {
throw $e;
}
$sleepTime = getSleepTime($retryCount, 2, 10);
if (stripos($e->getMessage(), '405') !== false) {
system_log(\lang('100141'));
sleep(9);
// aws waf token 失效,将重新获取新的 token
$handleInvalidToken = false;
foreach ($params as &$param) {
if ($param instanceof CookieJar) {
$handleInvalidToken = true;
$sleepTime = 1;
delGlobalValue(CommonConst::AWS_WAF_TOKEN);
$param->setCookie(buildAwsWafCookie(getAwsWafToken()));
break;
}
}
system_log($handleInvalidToken ? \lang('exception_msg.34520019') : sprintf(lang('exception_msg.34520015'), $sleepTime, $maxRetryCount, $retryCount, $maxRetryCount));
continue;
} else {
system_log(sprintf(lang('exception_msg.34520016'), $e->getMessage(), $sleepTime, $maxRetryCount, $retryCount, $maxRetryCount));
}
sleep($sleepTime);
}
}
}
}
if (!function_exists('buildAwsWafCookie')) {
/**
* 构建 aws waf cookie
*
* @param string $awsWafToken
*
* @return SetCookie
*/
function buildAwsWafCookie(string $awsWafToken)
{
$cookie = new SetCookie();
$cookie->setName('aws-waf-token');
$cookie->setValue($awsWafToken);
$cookie->setDomain('.my.freenom.com');
return $cookie;
}
}
if (!function_exists('getSleepTime')) {
/**
* 获取睡眠秒数
*
* @param int $i
* @param int $magRatio
* @param int $minSleepTime
*
* @return int
*/
function getSleepTime($i, $magRatio = 4, $minSleepTime = 20)
{
$sleepTime = $i * $magRatio;
if ($sleepTime < $minSleepTime) { // 最小休眠 $minSleepTime 秒
return $minSleepTime;
}
return $sleepTime;
}
}
if (!function_exists('getAwsWafToken')) {
/**
* 获取 aws waf token
*
* @return string
* @throws LlfException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
function getAwsWafToken()
{
// 优先从全局变量中获取
$AWS_WAF_TOKEN = getGlobalValue(CommonConst::AWS_WAF_TOKEN);
if ($AWS_WAF_TOKEN !== null) {
return $AWS_WAF_TOKEN;
}
$client = new Client([
'headers' => [
'Accept' => 'application/json',
'Authorization' => \env('FF_SECRET_KEY', '')
],
'timeout' => 32,
]);
// 调用开源的接口获取
$USE_OPEN_SOURCE_WAF_SOLVER_API = (int)\env('USE_OPEN_SOURCE_WAF_SOLVER_API', 1);
if ($USE_OPEN_SOURCE_WAF_SOLVER_API === 1) {
$OPEN_SOURCE_WAF_SOLVER_URL = \env('OPEN_SOURCE_WAF_SOLVER_URL');
if (!$OPEN_SOURCE_WAF_SOLVER_URL) {
throw new LlfException(34520020);
}
$OPEN_SOURCE_WAF_SOLVER_URL = rtrim($OPEN_SOURCE_WAF_SOLVER_URL, '/');
$startTime = time();
$maxWaitSeconds = 300;
$n = 0;
while (true) {
try {
if (time() - $startTime >= $maxWaitSeconds) {
break;
}
$r = $client->get($OPEN_SOURCE_WAF_SOLVER_URL);
$body = json_decode($r->getBody()->getContents(), true);
if (!isset($body['status']) || $body['status'] !== 'OK') {
throw new \Exception(isset($body['msg']) ? $body['msg'] : json_encode($body));
}
$awsWafToken = $body['data']['token'];
setGlobalValue(CommonConst::AWS_WAF_TOKEN, $awsWafToken);
system_log(sprintf(lang('100139'), $awsWafToken));
return $awsWafToken;
} catch (\Exception $e) {
system_log('getAwsWafToken error: ' . $e->getMessage());
}
$n++;
sleep($n > 5 ? 60 : 10); // 前 5 次每次休眠 10 秒,之后每次休眠 60 秒
}
throw new LlfException(34520021, $maxWaitSeconds);
}
// 使用自建接口获取
$AWS_WAF_SOLVER_URL = \env('AWS_WAF_SOLVER_URL');
if (!$AWS_WAF_SOLVER_URL) {
throw new LlfException(34520017);
}
$AWS_WAF_SOLVER_URL = rtrim($AWS_WAF_SOLVER_URL, '/');
$i = 0;
do {
try {
// 获取任务 ID
$r = $client->get($AWS_WAF_SOLVER_URL);
$body = json_decode($r->getBody()->getContents(), true);
if (!isset($body['status']) || $body['status'] !== 'OK') {
// 一般情况下走不到这个分支
if (isset($body['msg']) && $body['msg'] === 'A task is already running') {
sleep(180);
}
throw new \Exception(isset($body['msg']) ? $body['msg'] : json_encode($body));
}
// 已获取任务 ID,等待任务完成
$taskId = $body['data']['task_id'];
$startTime = time();
while (true) {
// 最多等 10 分钟
if (time() - $startTime >= 600) {
break;
}
$r = $client->get(sprintf('%s/%s', $AWS_WAF_SOLVER_URL, $taskId));
$body = json_decode($r->getBody()->getContents(), true);
if (!isset($body['status']) || $body['status'] !== 'OK') {
throw new \Exception(isset($body['msg']) ? $body['msg'] : json_encode($body));
}
$taskStatus = $body['data']['task_status'];
if ($taskStatus !== 'done') { // 任务进行中,继续等待
sleep(2);
continue;
}
if (!isset($body['data']['result']) || $body['data']['result'] === '') {
throw new \Exception('no result');
}
$awsWafToken = $body['data']['result'] ?? '';
setGlobalValue(CommonConst::AWS_WAF_TOKEN, $awsWafToken);
system_log(sprintf(lang('100139'), $awsWafToken));
return $awsWafToken;
}
} catch (\Exception $e) {
system_log('getAwsWafToken error: ' . $e->getMessage());
}
sleep(1);
$i++;
} while ($i <= 10);
throw new LlfException('34520018');
}
}
if (!function_exists('getGlobalValue')) {
/**
* 获取全局变量
*
* @param string $name
*
* @return string|null
*/
function getGlobalValue(string $name, ?string $default = null)
{
return GlobalValue::getInstance()->get($name, $default);
}
}
if (!function_exists('setGlobalValue')) {
/**
* 设置全局变量
*
* @param string $name
* @param string $value
*
* @return void
*/
function setGlobalValue(string $name, string $value)
{
GlobalValue::getInstance()->set($name, $value);
}
}
if (!function_exists('hasGlobalValue')) {
/**
* 是否存在全局变量
*
* @param string $name
*
* @return bool
*/
function hasGlobalValue(string $name)
{
return GlobalValue::getInstance()->has($name);
}
}
if (!function_exists('delGlobalValue')) {
/**
* 删除全局变量
*
* @param string $name
*
* @return void
*/
function delGlobalValue(string $name)
{
GlobalValue::getInstance()->del($name);
}
}
if (!function_exists('needAwsWafToken')) {
/**
* @return bool
*/
function needAwsWafToken()
{
try {
$client = new Client([
'headers' => [
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding' => 'gzip, deflate, br',
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
],
'timeout' => 4.2011,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_AUTOREFERER => true,
'verify' => config('verify_ssl'),
'proxy' => config('freenom_proxy'),
]);
$res = $client->get('https://my.freenom.com/clientarea.php');
return $res->getStatusCode() != 200;
} catch (\Exception $e) {
return stripos($e->getMessage(), '405') !== false;
}
}
}