fuc-new/src/Services/Cron.php
2025-10-17 13:17:40 +08:00

1900 lines
70 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Ann;
use App\Models\Config;
use App\Models\DetectLog;
use App\Models\EmailQueue;
use App\Models\HourlyUsage;
use App\Models\Invoice;
use App\Models\Node;
use App\Models\OnlineLog;
use App\Models\Order;
use App\Models\Paylist;
use App\Models\SubscribeLog;
use App\Models\User;
use App\Models\TgStep;
use App\Models\Zhuanfas;
use App\Models\Ducks;
use App\Models\Visitor;
use App\Models\CountVisitor;
use App\Models\CountUser;
use App\Models\Product;
use App\Models\UserMoneyLog;
use App\Models\OrderLog;
//use App\Models\UserRenew;
//use App\Models\ExpireConfrm;
use App\Models\PlanActivated;
use App\Models\PlanExpired;
use App\Models\PlanPending;
use App\Models\PlanQueue;
use App\Services\TomTool\TgSendFormat;
use App\Services\IM\Telegram;
use App\Services\IM\TelegramV2;
use App\Services\Tg\ReportUser;
use App\Utils\Tools;
use DateTime;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client\ClientExceptionInterface;
use Telegram\Bot\Exceptions\TelegramSDKException;
use function array_map;
use function date;
use function in_array;
use function json_decode;
use function str_replace;
use function strtotime;
use function time;
use const PHP_EOL;
use App\Models\Smtp as ModelSmtp;
//use Illuminate\Support\Facades\DB; // 线上可能要开
final class Cron
{
//// 商品自动化
public static function productAuto(): void
{
$i = 0;
$time = time();
//// 售空下架,前提不限时或过期
$oProduct = Product::where("status", 1)
->where("stock", "!=", -1)
->whereRaw("sale_count >= stock")
->get();
if (!$oProduct->isEmpty()) {
foreach ($oProduct as $oProductRow) {
// 不限时 或 过期 >> 下架
// 不限时是0自然也小于当前时间
if ($oProductRow->end_time < $time) {
$oProductRow->status = 0;
$oProductRow->save();
$i++;
}
}
}
//// 售空下架 end
//// 过期无条件下架,没售空也下架
$oProduct = Product::where("status", 1)->where("end_time", "!=", 0)->where("end_time", "<", $time)->get();
if (!$oProduct->isEmpty()) {
foreach ($oProduct as $oProductRow) {
$oProductRow->status = 0;
$oProductRow->save();
$i++;
}
}
echo Tools::toDateTime(time()) . ' 下架'.$i.'个商品' . PHP_EOL;
//// 过期下架 end
//// 假销售
$iRand = rand(1, 288); // 一天288个五分钟cron也就是说一天最多假销售288个没毛病够用
$oProduct = Product::where("status", 1)
->where("stock", "!=", -1)
->whereRaw("sale_count < stock")
->where("sale_shadow_oneday", ">=", $iRand)
->get();
if (!$oProduct->isEmpty()) {
$i = 0;
foreach ($oProduct as $oProductRow) {
$oProductRow->sale_count += 1;
$oProductRow->sale_count_shadow += 1;
$oProductRow->save();
$i++;
}
echo Tools::toDateTime(time()) . ' 假销售'.$i.'个商品' . PHP_EOL;
}
//// 假销售 end
}
// 数据库清理
public static function cleanDb(): void
{
(new SubscribeLog())->where(
'request_time',
'<',
time() - 86400 * Config::obtain('subscribe_log_retention_days')
)->delete();
(new HourlyUsage())->where(
'date',
'<',
date('Y-m-d', time() - 86400 * Config::obtain('traffic_log_retention_days'))
)->delete();
(new DetectLog())->where('datetime', '<', time() - 86400 * 3)->delete();
(new EmailQueue())->where('time', '<', time() - 86400)->delete();
(new OnlineLog())->where('last_time', '<', time() - 86400)->delete();
echo Tools::toDateTime(time()) . ' 数据库清理完成' . PHP_EOL;
}
// 清理tg step暂时没用
public static function clearTgStep(): void
{
TgStep::where("date", "<=", date('Y-m-d', strtotime('-1 day')))->delete();
}
// 统计用户数据入库发送tg
public static function userCount(): void
{
$sPrevDate = date("Y-m-d", strtotime("-1 day"));
$sPrevDateStart = date("Y-m-d 00:00:00", strtotime("-1 day"));
$sPrevDateEnd = date("Y-m-d 23:59:59", strtotime("-1 day"));
$sPrevTimeStart = strtotime($sPrevDateStart);
$sPrevTimeEnd = strtotime($sPrevDateEnd);
$sPrevDate30 = date("Y-m-d", strtotime("-30 day"));
//// 访客统计 start
$aVisitorCount = Visitor::select('from', DB::raw('count(*) as count'))
->groupBy('from')
->get()
->toArray();
$aVisitorMix = [];
foreach ($aVisitorCount as $row) {
$aVisitorMix[$row['from']]['count'] = $row['count'];
}
//// 访客统计 end
// 注册数量
$aRegCount = User::select('from', DB::raw('count(*) as count'))
->whereDate('reg_date', $sPrevDate)
->groupBy('from')
->get()
->toArray();
$aRegMix = [];
foreach ($aRegCount as $row) {
if (isset($row['from'])) {
$sFrom = $row['from'];
} else {
$sFrom = '无';
}
$aRegMix[$sFrom]['count'] = $row['count'];
}
$aOrder = Order::whereBetween("update_time", [$sPrevTimeStart, $sPrevTimeEnd])
->where("status", "activated")
->get()
->toArray();
// 标记是否是新用户
$oUserModel = new User();
$aUserMix = [];
foreach ($aOrder as $row) {
$oUser = $oUserModel->where("id", $row["user_id"])->first();
if (isset($oUser->from)) {
$sFrom = $oUser->from;
} else {
$sFrom = "";
}
// 初始化用户数据
if (!isset($aUserMix[$sFrom])) {
$aUserMix[$sFrom] = [
'pay_count_new' => 0,
'money_count_new' => 0,
'pay_count_old' => 0,
'money_count_old' => 0,
'pay_count' => 0,
'money_count' => 0,
];
}
// 确保时间比较的正确性
if (isset($oUser->reg_date) && ($row['update_time'] - strtotime($oUser->reg_date)) < (86400 * 30)) {
$aUserMix[$sFrom]['pay_count_new']++;
$aUserMix[$sFrom]['money_count_new'] += $row["price"];
} else {
$aUserMix[$sFrom]['pay_count_old']++;
$aUserMix[$sFrom]['money_count_old'] += $row["price"];
}
$aUserMix[$sFrom]['pay_count']++;
$aUserMix[$sFrom]['money_count'] += $row["price"];
}
$aAllMix = array_merge($aVisitorMix, $aRegMix, $aUserMix);
foreach ($aAllMix as $k => $v) {
$oCountUserModel = new CountUser();
$oCountUserModel->from = $k;
$oCountUserModel->count_from = $aVisitorMix[$k]['count'] ?? 0;
$oCountUserModel->count_reg = $aRegMix[$k]['count'] ?? 0;
$oCountUserModel->count_pay = $aUserMix[$k]['pay_count'] ?? 0;
$oCountUserModel->count_pay_new = $aUserMix[$k]['pay_count_new'] ?? 0;
$oCountUserModel->count_pay_old = $aUserMix[$k]['pay_count_old'] ?? 0;
$oCountUserModel->count_money = $aUserMix[$k]['money_count'] ?? 0;
$oCountUserModel->count_money_new = $aUserMix[$k]['money_count_new'] ?? 0;
$oCountUserModel->count_money_old = $aUserMix[$k]['money_count_old'] ?? 0;
$oCountUserModel->date = $sPrevDate;
$r = $oCountUserModel->save();
}
Visitor::truncate();
$sMsg = (new ReportUser())->ByDay(1);
(new Telegram())->send(0, $sMsg);
}
// public static function visitorCount(): void
// {
// $oVisitor = Visitor::select('from', DB::raw('count(*) as count'))
// ->groupBy('from')
// ->get();
//
// foreach ($oVisitor as $row) {
//
// $oCountVisitor = new CountVisitor();
// $oCountVisitor->from = $row->from;
// $oCountVisitor->count = $row->count;
// $oCountVisitor->date = date('Y-m-d', strtotime('-1 day'));
// $oCountVisitor->save();
//
// }
//
// Visitor::truncate();
//
// // tg报表
// $aCountVisitor = CountVisitor::where('date', date('Y-m-d', strtotime('-1 day')))->get()->toArray();
//
// $sTgReport = '';
// foreach ($aCountVisitor as $row) {
//
// $sTgReport .= $row["from"]."".$row["count"]."\n";
//
// }
//
// (new Telegram())->send(0, $sTgReport);
//
// }
// 限制帐户检测
public static function detectInactiveUser(): void
{
$checkin_days = Config::obtain('detect_inactive_user_checkin_days');
$login_days = Config::obtain('detect_inactive_user_login_days');
$use_days = Config::obtain('detect_inactive_user_use_days');
(new User())->where('is_admin', 0)
->where('is_inactive', 0)
->where('last_check_in_time', '<', time() - 86400 * $checkin_days)
->where('last_login_time', '<', time() - 86400 * $login_days)
->where('last_use_time', '<', time() - 86400 * $use_days)
->update(['is_inactive' => 1]);
(new User())->where('is_admin', 0)
->where('is_inactive', 1)
->where('last_check_in_time', '>', time() - 86400 * $checkin_days)
->where('last_login_time', '>', time() - 86400 * $login_days)
->where('last_use_time', '>', time() - 86400 * $use_days)
->update(['is_inactive' => 0]);
echo Tools::toDateTime(time()) .
' 检测到 ' . (new User())->where('is_inactive', 1)->count() . ' 个账户处于闲置状态' . PHP_EOL;
}
// 节点掉线检测
public static function detectNodeOffline(): void
{
$nodes = (new Node())->where('type', 1)->get();
foreach ($nodes as $node) {
if ($node->getNodeOnlineStatus() >= 0 && $node->online === 1) {
continue;
}
if ($node->getNodeOnlineStatus() === -1 && $node->online === 1) {
echo 'Send Node Offline Email to admin users' . PHP_EOL;
try {
Notification::notifyAdmin(
$_ENV['appName'] . '-系统警告',
'管理员你好,系统发现节点 ' . $node->name . ' 掉线了,请你及时处理。'
);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
if (Config::obtain('telegram_node_offline')) {
$notice_text = str_replace(
'%node_name%',
$node->name,
Config::obtain('telegram_node_offline_text')
);
try {
(new Telegram())->send(0, $notice_text);
} catch (TelegramSDKException $e) {
echo $e->getMessage();
}
}
$node->online = 0;
$node->save();
continue;
}
if ($node->getNodeOnlineStatus() === 1 && $node->online === 0) {
echo 'Send Node Online Email to admin user' . PHP_EOL;
try {
Notification::notifyAdmin(
$_ENV['appName'] . '-系统提示',
'管理员你好,系统发现节点 ' . $node->name . ' 恢复上线了。'
);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
if (Config::obtain('telegram_node_online')) {
$notice_text = str_replace(
'%node_name%',
$node->name,
Config::obtain('telegram_node_online_text')
);
try {
(new Telegram())->send(0, $notice_text);
} catch (TelegramSDKException $e) {
echo $e->getMessage();
}
}
$node->online = 1;
$node->save();
}
}
echo Tools::toDateTime(time()) . ' 节点离线检测完成' . PHP_EOL;
}
// 新增,专门检测处理体验用户
// class=1为免费体验用户免费体验到期转为0彻底不能用
public static function expireFreeUserAccount(): void
{
$paidUsers = (new User())->where('class', 1)->get();
foreach ($paidUsers as $user) {
if (strtotime($user->class_expire) < time()) {
$user->u = 0;
$user->d = 0;
$user->transfer_today = 0;
$user->transfer_enable = 0;
$user->class = 0;
$user->save();
}
}
echo Tools::toDateTime(time()) . ' 体验用户过期检测完成' . PHP_EOL;
}
// 付费用户过期检测
// 现在付费用户是2+体验用户是1。付费到期转为体验。
// 快要到期应该也提醒,目前好像没有,就是没有
// 作废了
public static function expirePaidUserAccount(): void
{
return; // 作废
$paidUsers = (new User())->where('class', '>=', 2)->get();
foreach ($paidUsers as $user) {
if (strtotime($user->class_expire) < time()) {
$text = '您好,系统发现您的套餐已经过期了。';
$reset_traffic = $_ENV['class_expire_reset_traffic'];
$reset_day = $_ENV['class_expire_reset_day'];
// 这里目前有问题,$reset_traffic不大于零就乱套了
if ($reset_traffic >= 0) {
$user->transfer_enable = Tools::toGB($reset_traffic);
$user->class_expire = date('Y-m-d H:i:s', strtotime("+$reset_day days"));
$text .= '已转为免费体验帐户,赠送' . $reset_traffic . 'GB体验流量'.$reset_day.'天体验时长。';
}
// todo 反水
// 这里返水的话,等于是直接覆盖的用户不返水,自然过期的用户返水,这到合理,自然过期的才是好用户
try {
Notification::notifyUser($user, $_ENV['appName'] . '-您的套餐已经过期', $text);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
$user->u = 0;
$user->d = 0;
$user->transfer_today = 0;
$user->class = 1;
$user->save();
}
//// 流量到期处理 不重置了,默认暂停就行
// 这里只重置付费用户不重置免费用户。那么假如免费用户领了60g流量他就真的能用到没。付费用户则会连带套餐一起重置掉。
// 应该问题不大,赠送流量有关时,区分免费付费用户,对付费大方(陷阱),对免费抠门。
// 其实压根就不叠加,套餐流量直接覆盖免费流量。
// 再者,免费流量
// 总之,免费流量就是零头,无所谓重置的。
// 用户流量用光,或者用户等级到期,就重置。
// 流量用光时顺便标记订单。在这里标记不容易埋坑。
// $ud = $user->u + $user->d;
// if ($ud >= $user->transfer_enable) {
//
// // 订单标记为过期
// // 既然class大于0就说明肯定有激活的套餐对吧
// $activated_order = (new Order())->where('user_id', $user->id)
// ->where('status', 'activated')
// ->where('product_type', 'tabp')
// ->orderBy('id')
// ->first();
//
// // 以防万一,判断一下
// if (!$activated_order) {
// $activated_order->status = 'expired';
// $activated_order->update_time = time();
// $activated_order->save();
// }
//
//// tomd($activated_order, 1);
// // 用户流量重置
// $user->u = 0;
// $user->d = 0;
// $user->class = 0;
// $user->transfer_today = 0;
// $user->transfer_enable = 0;
// $user->class_expire = date("Y-m-d H:i:s"); // 用户等级时间到当前,注意订单没变,应该合理
// $user->save();
//
// $sText = "你好,系统发现您的流量已用完,已转为免费体验帐户,可用每日签到获取免费流量。";
//
// try {
// Notification::notifyUser($user, $_ENV['appName'] . '-您的流量已用完', $sText);
// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
// echo $e->getMessage() . PHP_EOL;
// }
//
// }
//// end
}
echo Tools::toDateTime(time()) . ' 付费用户过期检测完成' . PHP_EOL;
}
// 邮件队列处理
public static function processEmailQueue(): void
{
if ((new EmailQueue())->count() === 0) {
echo Tools::toDateTime(time()) . ' 邮件队列为空' . PHP_EOL;
} else {
//记录当前时间戳
$timestamp = time();
//邮件队列处理
while (true) {
if (time() - $timestamp > 299) {
echo Tools::toDateTime(time()) . '邮件队列处理超时,已跳过' . PHP_EOL;
break;
}
DB::beginTransaction();
try {
$email_queues_raw = DB::select('SELECT * FROM email_queue where `type` is NULL LIMIT 1 FOR UPDATE SKIP LOCKED');
if (count($email_queues_raw) === 0) {
DB::commit();
break;
}
$email_queues = array_map(static function ($value) {
return (array) $value;
}, $email_queues_raw);
$email_queue = $email_queues[0];
echo '发送邮件至 ' . $email_queue['to_email'] . PHP_EOL;
DB::delete('DELETE FROM email_queue WHERE id = ?', [$email_queue['id']]);
if (Tools::isEmail($email_queue['to_email'])) {
Mail::send(
$email_queue['to_email'],
$email_queue['subject'],
$email_queue['template'],
json_decode($email_queue['array'])
);
} else {
echo $email_queue['to_email'] . ' 邮箱格式错误,已跳过' . PHP_EOL;
}
DB::commit();
} catch (Exception|ClientExceptionInterface $e) {
DB::rollBack();
echo $e->getMessage();
}
}
echo Tools::toDateTime(time()) . ' 邮件队列处理完成' . PHP_EOL;
}
}
public static function smtpClearDayCount()
{
$sDate = date("Y-m-d");
$r = DB::update("UPDATE smtp_list SET `day_count` = 0, `day_clear_date` = '".$sDate."' WHERE `day_count` >= `day_max`");
echo Tools::toDateTime(time()) . ' smtp的dayCount重置'.$r.'条' . PHP_EOL;
}
public static function processEmailQueue_bulk(): void
{
if ((new EmailQueue())->count() === 0) {
echo Tools::toDateTime(time()) . ' 队列邮件队列为空' . PHP_EOL;
} else {
//记录当前时间戳
$timestamp = time();
//邮件队列处理
while (true) {
if (time() - $timestamp > 299) {
echo Tools::toDateTime(time()) . '队列邮件队列处理超时,已跳过' . PHP_EOL;
break;
}
DB::beginTransaction();
try {
$sDate = date("Y-m-d");
$aSmtp = DB::select('SELECT * FROM smtp_list WHERE `day_clear_date` != "'.$sDate.'" AND `day_count` < `day_max` ORDER BY `day_clear_date` ASC LIMIT 1 FOR UPDATE SKIP LOCKED');
if (empty($aSmtp) || !isset($aSmtp[0])) {
echo Tools::toDateTime(time()) . ' 今日以没有可用smtp' . PHP_EOL;
DB::commit();
break;
}
$oSmtp = $aSmtp[0];
$email_queues_raw = DB::select('SELECT * FROM email_queue where `type` = "bulk" LIMIT 1 FOR UPDATE SKIP LOCKED');
if (count($email_queues_raw) === 0) {
DB::commit();
break;
}
$email_queues = array_map(static function ($value) {
return (array) $value;
}, $email_queues_raw);
$email_queue = $email_queues[0];
echo '发送邮件至 ' . $email_queue['to_email'] . PHP_EOL;
DB::delete('DELETE FROM email_queue WHERE id = ?', [$email_queue['id']]);
if (Tools::isEmail($email_queue['to_email'])) {
Mail::send_bulk(
$email_queue['to_email'],
$email_queue['subject'],
$email_queue['template'],
json_decode($email_queue['array']),
$oSmtp
);
DB::update("UPDATE smtp_list SET `day_count` = `day_count` + 1 WHERE id = ".$oSmtp->id);
} else {
echo $email_queue['to_email'] . ' 邮箱格式错误,已跳过' . PHP_EOL;
}
DB::commit();
} catch (Exception|ClientExceptionInterface $e) {
echo $e->getMessage();
}
}
echo Tools::toDateTime(time()) . ' 群发邮件队列处理完成' . PHP_EOL;
}
}
// 激活订单
// 作废
public static function processTabpOrderActivation(): void
{
return; // 作废
$users = User::all();
foreach ($users as $user) {
$user_id = $user->id;
// 获取用户账户等待激活的TABP订单
$pending_activation_orders = (new Order())->where('user_id', $user_id)
->where('status', 'pending_activation')
->where('product_type', 'tabp')
->orderBy('id')
->get();
// 获取用户账户已激活的TABP订单一个用户同时只能有一个已激活的TABP订单
$activated_order = (new Order())->where('user_id', $user_id)
->where('status', 'activated')
->where('product_type', 'tabp')
->orderBy('id')
->first();
// * 如果用户流量已满,就直接激活待激活订单,不考虑是否有已激活。(之前想的是流量满时把订单标记成过期,这虽然也可以,但是业务逻辑会显得奇怪)
$ud = $user->u + $user->d;
// 待激活订单 > 0 && (没有已激活订单 || 用户流量满)
if (count($pending_activation_orders) > 0 && ($activated_order === null || $ud >= $user->transfer_enable)) {
// if ($activated_order === null && count($pending_activation_orders) > 0) {
// if (($activated_order === null && count($pending_activation_orders) > 0) || ($ud > $user->transfer_enable && count($pending_activation_orders) > 0)) {
$order = $pending_activation_orders[0];
// 获取TABP订单内容准备激活
$content = json_decode($order->product_content);
//// * 是用户流量满时,主动标记订单过期
// 按理说可以有多个已激活,但是以防万一,标记掉
// 假如等级还过期了,就是标记两次,无所谓
if ($ud >= $user->transfer_enable && $activated_order !== null) {
$activated_order->status = 'expired';
$activated_order->update_time = time();
$activated_order->save();
echo "TABP订单 #{$activated_order->id} 已过期(流量满了)。\n";
}
//// end
//// * 特别活动追加,季套餐赠月套餐,年套餐赠季套餐
if ($order->huodong_class && $order->huodong_class == "A") {
// 按理是大于30天做个冗余
if ($content->class_time > 32) {
$oProduct = Product::where("name", $order->product_name)
->where("class_time", "<", $content->class_time)
->orderBy("class_time", "desc")
->first();
if ($oProduct) {
$oProductContent = json_decode($oProduct->content);
$content->class_time += $oProductContent->class_time;
$content->bandwidth += $oProductContent->bandwidth;
}
}
}
//// end
// 激活TABP
// 等级时间,流量,都是覆盖刷新,不是叠加
$user->u = 0;
$user->d = 0;
$user->transfer_today = 0;
$user->transfer_enable = Tools::toGB($content->bandwidth);
$user->class = $content->class;
$old_class_expire = new DateTime();
$user->class_expire = $old_class_expire
->modify('+' . $content->class_time . ' days')->format('Y-m-d H:i:s'); // 当前时间+订单等级时间
$user->node_group = $content->node_group;
$user->node_speedlimit = $content->speed_limit;
$user->node_iplimit = $content->ip_limit;
$user->save();
$order->status = 'activated';
$order->update_time = time();
$order->save();
echo "TABP订单 #{$order->id} 已激活。\n";
continue;
}
// 如果用户账户中有已激活的TABP订单则判断是否过期
if ($activated_order !== null) {
$content = json_decode($activated_order->product_content);
// 原来用的是$content->time
// 这个time应该是原本的账号时间
if ($activated_order->update_time + $content->class_time * 86400 < time()) {
$activated_order->status = 'expired';
$activated_order->update_time = time();
$activated_order->save();
echo "TABP订单 #{$activated_order->id} 已过期。\n";
}
}
}
echo Tools::toDateTime(time()) . ' TABP订单激活处理完成' . PHP_EOL;
}
// 流量包激活,暂时没用
public static function processBandwidthOrderActivation(): void
{
$users = User::all();
foreach ($users as $user) {
$user_id = $user->id;
// 获取用户账户等待激活的流量包订单
$order = (new Order())->where('user_id', $user_id)
->where('status', 'pending_activation')
->where('product_type', 'bandwidth')
->orderBy('id')
->first();
if ($order !== null) {
// 获取流量包订单内容准备激活
$content = json_decode($order->product_content);
// 激活流量包
$user->transfer_enable += Tools::toGB($content->bandwidth);
$user->save();
$order->status = 'activated';
$order->update_time = time();
$order->save();
echo "流量包订单 #{$order->id} 已激活。\n";
}
}
echo Tools::toDateTime(time()) . ' 流量包订单激活处理完成' . PHP_EOL;
}
// 时间包激活,暂时没用
public static function processTimeOrderActivation(): void
{
$users = User::all();
foreach ($users as $user) {
$user_id = $user->id;
// 获取用户账户等待激活的时间包订单
$order = (new Order())->where('user_id', $user_id)
->where('status', 'pending_activation')
->where('product_type', 'time')
->orderBy('id')
->first();
if ($order !== null) {
$content = json_decode($order->product_content);
// 跳过当前账户等级不等于时间包等级的非免费用户订单
if ($user->class !== (int) $content->class && $user->class > 0) {
continue;
}
// 激活时间包
$user->class = $content->class;
$old_class_expire = new DateTime($user->class_expire);
$user->class_expire = $old_class_expire
->modify('+' . $content->class_time . ' days')->format('Y-m-d H:i:s');
$user->node_group = $content->node_group;
$user->node_speedlimit = $content->speed_limit;
$user->node_iplimit = $content->ip_limit;
$user->save();
$order->status = 'activated';
$order->update_time = time();
$order->save();
echo "时间包订单 #{$order->id} 已激活。\n";
}
}
echo Tools::toDateTime(time()) . ' 时间包订单激活处理完成' . PHP_EOL;
}
////// 新版组合拳,按循序巡行 //////
//// 支付成功,标记订单 # 步骤1
public static function orderToPlan(): void
{
self::_helper_log("支付完成 - 开始");
$iPayClass = $_ENV["user_payClass"];
$pending_payment_orders = (new Order())->where('status', 'payment_pending')->get(); // 这里还是无脑检索order了但是现在order表可以只保留一年内数据问题不大
$aUserInQueue = []; // 用户可能在5分钟之内多次下单但一次只处理一个坚固
foreach ($pending_payment_orders as $oOrder) {
if (in_array($oOrder->user_id, $aUserInQueue)) {
continue;
}
$invoice = (new Invoice())->where('order_id', $oOrder->id)->first();
if ($invoice === null) {
continue;
}
DB::beginTransaction();
try {
// 付款完成后
if (in_array($invoice->status, ['paid_gateway', 'paid_balance', 'paid_admin'])) {
$oUser = User::find($invoice->user_id);
$iPayAmount = $invoice->price - $invoice->money_use;
if ($oOrder->product_code == "code") { // 钱包充值
$iUserMoneyOld = $oUser->money;
$iUserMoneyNew = $oUser->money + $invoice->price;
$iAmount = $invoice->price;
$oUser->money = $iUserMoneyNew;
$oUser->total_pay += $iPayAmount;
$oUser->save();
(new UserMoneyLog())->add(
$oUser->id,
(float) $iUserMoneyOld,
(float) $iUserMoneyNew,
(float) $iAmount,
'钱包充值 #账单号:'.$invoice->id
);
$oOrder->status = "code_done";
$oOrder->_save();
self::_helper_log("订单".$oOrder->id."钱包充值到账");
} else if ($oUser->class < $iPayClass) { // 当前无套餐,直接进激活队列
$oUser->total_pay += $iPayAmount;
$oUser->save();
$oOrder->status = "pending";
$oOrder->_save();
$oPlanQueue = new PlanQueue();
$oPlanQueue->_add($oOrder, "new");
self::_helper_log("订单".$oOrder->id."进入到激活队列");
} else { // 当前有套餐进入pending表
$oUser->total_pay += $iPayAmount;
$oUser->save();
$oOrder->status = "pending";
$oOrder->_save();
self::_helper_log("订单".$oOrder->id."转为待激活状态");
}
$aUserInQueue[] = $oOrder->user_id;
} else if ($oOrder->create_time + 86400 < time()) {
$invoice->status = 'cancelled';
$invoice->update_time = time();
$invoice->save();
$oOrder->status = 'cancelled';
$oOrder->update_time = time();
$oOrder->save();
if ($invoice->money_use > 0) {
$iUserMoneyOld = $oUser->money;
$oUser->money += $invoice->money_use;
$oUser->save();
(new UserMoneyLog())->add(
$oUser->id,
(float) $iUserMoneyOld,
(float) $oUser->money,
(float) $invoice->money_use,
'支付超时退回余额 #账单号:'.$invoice->id
);
}
self::_helper_log("已取消超时账单".$oOrder->id);
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
// if ($oOrder) {
// $oOrder->error_code = "error";
// $oOrder->error_str = "支付未到账,请联系管理员";
// $oOrder->_save();
// }
$sMsg = self::_helper_toJson($e->getMessage());
self::_helper_log($sMsg, self::_eToArr($e, $sMsg));
}
}
}
//// 检查过期 # 步骤2
public static function plan_expireToQueue(): void
{
self::_helper_log("套餐过期标记 - 开始");
$iFreeClass = $_ENV["user_freeClass"];
$iPayClass = $_ENV["user_payClass"];
$oUserList = User::where("class", ">=", $iPayClass)->get();
foreach ($oUserList as $oUser) {
DB::beginTransaction();
try {
// $oUser->isPayClass = $oUser->class >= $iPayClass ? true : false;
if (strtotime($oUser->class_expire) < time()) { // 时间到期
self::_class_expire($oUser);
DB::commit();
continue;
}
if ($oUser->u + $oUser->d >= $oUser->transfer_enable) { // 流量用完
self::_class_expire($oUser);
DB::commit();
continue;
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
$sMsg = self::_helper_toJson($e->getMessage());
self::_helper_log($sMsg, self::_eToArr($e, $sMsg));
}
}
}
//// pendding转激活 # 步骤3
public static function planQueue_pendingToActivation()
{
self::_helper_log("套餐激活 - 开始");
$oPlanQueueList = PlanQueue::whereNull("status")->get();
foreach ($oPlanQueueList as $oPlanQueue) {
// $oPlanPending = PlanPending::where("user_id", $oPlanQueue->user_id)->where("status", "!=", "fail")->orderBy("id", "ASC")->first(); // * 待激活表
$oPlanPending = Order::where("user_id", $oPlanQueue->user_id)
->where("status", "pending")
->whereNull("error_code")
->orderBy("id", "ASC")
->first();
// 没有待激活套餐时,跳过。否则都必然$oPlanQueue->_delete();
if (!$oPlanPending) {
$oPlanQueue->type = "expire";
$oPlanQueue->save();
continue; // 没有待激活就直接桡过,而不是报错
}
DB::beginTransaction();
try {
$oProduct = Product::where("code", $oPlanPending->product_code)->first(); // * 商品表
// 没有对应商品时
if (!$oProduct) {
$oPlanPending->error_code = "fail";
$oPlanPending->error_str = "激活失败,该商品已从数据库存中移除,金额以退回至钱包。";
// 退回钱包
$oUser = User::find($oPlanQueue->user_id);
$iUserMoneyOld = $oUser->money;
$iUserMoneyNew = $oUser->money + $oPlanPending->user_price;
$oUser->money = $iUserMoneyNew;
$oUser->save();
(new UserMoneyLog())->add(
$oUser->id,
(float) $iUserMoneyOld,
(float) $iUserMoneyNew,
(float) $oPlanPending->user_price,
'套餐激活失败,金额退回钱包'
);
$oPlanQueue->_delete();
DB::commit();
continue;
}
//// 用户激活
$oProductContent = json_decode($oProduct->content);
$oUser = User::find($oPlanQueue->user_id);
self::_plan_to_user($oUser, $oProductContent); // 套餐赋予用户
// 以激活表
// $oPlanActivated = PlanActivated::where("user_id", $oPlanQueue->user_id)->orderBy("id", "ASC")->first(); // 目前已激活plan
$oPlanActivated = Order::where("user_id", $oPlanQueue->user_id)
->where("status", "activated")
->orderBy("id", "ASC")
->first();
if ($oPlanActivated) {
// self::plan_activatedToExpired($oPlanActivated); // 已激活转为过期
$oPlanActivated->status = "expired";
$oPlanActivated->_save();
}
// self::plan_pendingToActivated($oPlanPending); // 待激活转为激活
$oPlanPending->status = "activated";
$oPlanPending->_save();
$oPlanQueue->_delete();
DB::commit();
self::_helper_log("用户".$oUser->id."激活完成");
} catch (\Exception $e) {
DB::rollBack();
$oPlanQueue->status = "fail:planQueue_pendingToActivation";
$oPlanQueue->type .= "_fail_".rand(1, 9999);
$oPlanQueue->_save();
// $oPlanPending = $oPlanPending::
// $oPlanPending = PlanPending::where("user_id", $oPlanQueue->user_id)->where("status", "!=", "fail")->orderBy("id", "ASC")->first();
if ($oPlanPending) {
$oPlanPending->error_code = "error";
$oPlanPending->error_str = "套餐激活失败,请联系管理员";
$oPlanPending->_save();
}
$sMsg = self::_helper_toJson($e->getMessage());
self::_helper_log($sMsg, self::_eToArr($e, $sMsg));
}
}
}
// 自动续订 # 步骤 4
public static function planQueue_expireToBuy()
{
self::_helper_log("自动续订 - 开始");
$oPlanQueueList = PlanQueue::where("type", "expire")->whereNull("status")->get();
foreach ($oPlanQueueList as $oPlanQueue) {
// 检查自动续订是否开启
$oUser = User::where("id", $oPlanQueue->user_id)->first(); // * 数据
if ($oUser->autorenew != 1) {
continue;
}
// $oPlanActivated = PlanActivated::where("user_id", $oPlanQueue->user_id)->orderBy("id", "ASC")->first(); // * 当前激活套餐
$oPlanActivated = Order::where("user_id", $oPlanQueue->user_id)->where("status", "activated")->OrderBy("id", "ASC")->first();
if (!$oPlanActivated) {
continue;
}
DB::beginTransaction();
try {
//// 开始自动续订
$iUserMoneyOld = $oUser->money;
$iUserMoneyNew = $oUser->money - $oPlanActivated->user_price;
// 余额不足
if ($iUserMoneyNew < 0) {
// 关闭自动订阅
$oUser->autorenew = 0;
$oUser->save();
// todo 发站内信 成功失败都发
$text = "钱包余额不足,自动续订失败。";
Notification::notifyUser($oUser, $_ENV['appName'] . ' - 自动续订失败', $text);
DB::commit();
continue;
}
// 扣款
$oUser->money = $iUserMoneyNew;
$oUser->save();
(new UserMoneyLog())->add(
$oUser->id,
$iUserMoneyOld,
(float) $iUserMoneyNew,
-$oPlanActivated->user_price,
'余额抵付 #账单号:'.$oPlanActivated->id
);
// 创建订单
$order = new Order();
$order->user_id = $oPlanQueue->user_id;
$order->product_code = $oPlanActivated->product_code;
$order->user_product_name = $oPlanActivated->user_product_name;
$order->user_coupon = $oPlanActivated->user_coupon;
$order->user_price = $oPlanActivated->user_price;
$order->status = 'payment_pending';
$order->create_time = time();
$order->update_time = time();
$order->_save();
$invoice = new Invoice();
$invoice->user_id = $oUser->id;
$invoice->order_id = $order->id;
$invoice->price = $oPlanActivated->user_price;
$invoice->status = 'paid_balance';
$invoice->create_time = time();
$invoice->update_time = time();
$invoice->pay_time = time();
$invoice->money_use = $oPlanActivated->user_price;
$invoice->save();
$text = "自动续订成功,钱包金额扣除".$oPlanActivated->user_price."元,还剩".$iUserMoneyNew."元。";
Notification::notifyUser($oUser, $_ENV['appName'] . ' - 自动续订成功', $text);
$oPlanQueue->_delete();
self::_helper_log("用户".$oUser->id."续订购买套餐成功");
DB::commit();
// continue;
} catch (\Exception $e) {
DB::rollBack();
$sMsg = self::_helper_toJson($e->getMessage());
self::_helper_log($sMsg, self::_eToArr($e, $sMsg));
$oPlanQueue->status = "fail:planQueue_expireToBuy";
$oPlanQueue->type .= "_fail_".rand(1, 9999);
$oPlanQueue->_save();
}
}
}
//// 过期处理完成 # 步骤 5
public static function planQueue_expireDone()
{
self::_helper_log("过期 - 开始");
$text = '您好,系统发现您的套餐已经过期了。';
$reset_traffic = $_ENV['class_expire_reset_traffic'];
$reset_day = $_ENV['class_expire_reset_day'];
$iPayClass = $_ENV["user_payClass"];
$iFreeClass = $_ENV["user_freeClass"];
$iToEnable = 0;
$iToExpire = date('Y-m-d H:i:s');
$iToClass = 0;
$oPlanExpireQueueList = PlanQueue::where("type", "expire")->whereNull("status")->get();
foreach ($oPlanExpireQueueList as $oPlanExpireQueue)
{
DB::beginTransaction();
try {
$oUser = User::where("id", $oPlanExpireQueue->user_id)->first();
//// activated转移到expired
// $oPlanActivated = PlanActivated::where("user_id", $oPlanExpireQueue->user_id)->first(); // 必然有没有自然报错user唯一
$oPlanActivated = Order::where("user_id", $oPlanExpireQueue->user_id)->where("status", "activated")->first(); // 没有就不管,只过期用户
if ($oPlanActivated) {
$oPlanActivated->status = "expired";
$oPlanActivated->update_time = time();
$oPlanActivated->_save();
// self::plan_activatedToExpired($oPlanActivated);
}
//// end
//// 通知
if ($reset_traffic >= 0) { // 如果设置了那个,付费用户过期转成体验用户。没设置就直接转普通用户。
$iToEnable = Tools::toGB($reset_traffic);
$iToExpire = date('Y-m-d H:i:s', strtotime("+$reset_day days"));
$iToClass = $iFreeClass;
$text .= '已转为免费体验帐户,赠送' . $reset_traffic . 'GB体验流量'.$reset_day.'天体验时长。';
}
$oUser->class = $iToClass;
$oUser->u = 0;
$oUser->d = 0;
$oUser->transfer_today = 0;
$oUser->transfer_enable = $iToEnable;
$oUser->class_expire = $iToExpire;
$oUser->save();
Notification::notifyUser($oUser, $_ENV['appName'] . '-您的套餐已经过期', $text);
// try {
// Notification::notifyUser($oUser, $_ENV['appName'] . '-您的套餐已经过期', $text);
// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
// echo $e->getMessage() . PHP_EOL;
// }
//// end
$oPlanExpireQueue->delete();
self::_helper_log("用户".$oUser->id."彻底过期");
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
$sMsg = self::_helper_toJson($e->getMessage());
self::_helper_log($sMsg, self::_eToArr($e, $sMsg));
$oPlanExpireQueue->status = "fail:planQueue_expireDone";
$oPlanExpireQueue->type .= "_fail_".rand(1, 9999);
$oPlanExpireQueue->_save();
}
}
}
//// helper系列
private static function _class_expire(&$oUser)
{
$oPlanQueue = new PlanQueue();
$oPlanQueue->user_id = $oUser->id;
$oPlanQueue->type = "expire";
$oPlanQueue->updated_at = time();
$oPlanQueue->save();
self::_helper_log("用户id".$oPlanQueue->user_id."加入plan过期队列");
}
// private static function plan_activatedToExpired(&$oPlanActivated)
// {
// if (!$oPlanActivated) {
// return false;
// }
//
// $oPlanExpired = new PlanExpired();
// $oPlanExpired->_add($oPlanActivated);
//
// return $oPlanActivated->_delete();
// }
// private static function plan_pendingToActivated(&$oPlanPending)
// {
// if (!$oPlanPending) {
// return false;
// }
//
// $oPlanActivated = new PlanActivated();
// $oPlanActivated->_add($oPlanPending);
//
// return $oPlanPending->_delete();
// }
public static function _helper_toJson($data)
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
// public static function _helper_fail(&$oPlanPending)
// {
//
// $oPlanPending->status_str = "套餐激活失败,请联系管理员。";
// $oPlanPending->status = "fail";
// $oPlanPending->_save();
//
// }
// private static function _eToArr($oError, $sTitle = false)
// {
// $arr = [];
//
// if ($sTitle) {
// $arr['title'] = $sTitle;
// }
//
// $arr['code'] = $oError->getCode();
// $arr['file'] = $oError->getFile();
// $arr['line'] = $oError->getLine();
//
// return $arr;
// }
private static function _helper_log($sMsg, $sTgMsg = false)
{
echo Tools::toDateTime(time()).' '.$sMsg.PHP_EOL;
if ($sTgMsg) {
$oTelegramV2 = new TelegramV2();
$oTelegramV2->send($sTgMsg, "arr");
}
}
// private static function _helper_throw($sMsg, $aTgMsg = [])
// {
// if ($aTgMsg) {
// $oTelegramV2 = new TelegramV2();
// $oTelegramV2->send(json_encode($aTgMsg));
// }
//
// throw new \Exception($sMsg);
// }
private static function _plan_to_user(&$oUser, $aProductContent)
{
$oUser->class = $aProductContent->class;
$oUser->class_expire = date('Y-m-d H:i:s', strtotime("+$aProductContent->class_time days"));
$oUser->transfer_enable = Tools::toGB($aProductContent->bandwidth);
$oUser->u = 0;
$oUser->d = 0;
$oUser->transfer_today = 0;
$oUser->node_iplimit = $aProductContent->ip_limit;
$oUser->node_speedlimit = $aProductContent->speed_limit;
$oUser->save();
}
//// helper order 系列 end
////// 新版组合拳 end //////
public static function removeInactiveUserLinkAndInvite(): void
{
$inactive_users = (new User())->where('is_inactive', 1)->get();
foreach ($inactive_users as $user) {
$user->removeLink();
$user->removeInvite();
}
echo Tools::toDateTime(time()) . ' Successfully removed inactive user\'s Link and Invite' . PHP_EOL;
}
// 废弃
public static function resetNodeBandwidth(): void
{
(new Node())->where('bandwidthlimit_resetday', date('d'))->update(['node_bandwidth' => 0]);
echo Tools::toDateTime(time()) . ' 重设节点流量完成' . PHP_EOL;
}
// 机器到日期重置流量
public static function resetDuckBandwidth(): void
{
(new Ducks())->where('width_reset_day', date('d'))->update(['use_width' => 0]);
echo Tools::toDateTime(time()) . ' 重设机器流量完成' . PHP_EOL;
}
// public static function resetZhuanfaBandwidth(): void
// {
// (new Ducks())->where('end_date', date('Y-m-d'))->update(['use_width' => 0]);
//
// echo Tools::toDateTime(time()) . ' 重设机器流量完成' . PHP_EOL;
// }
// 每小时统计节点流量入库tg警报
public static function mergeBandWidth(): void
{
$oNodeAll = Node::where('type', 1)->get();
$aZhuanfaFlowNow = [];
$aDuckFlowNow = [];
$sMsg = "";
// 统计node流量
foreach ($oNodeAll as $oNodeRow) {
if ($oNodeRow->node_bandwidth == 0) {
if ($oNodeRow->is_shadow != 1) {
$sMsg .= '🚨【'.$oNodeRow->name."】小时未耗流,可能异常。\n";
}
}
@$aZhuanfaFlowNow[$oNodeRow->zhuanfa_code] += $oNodeRow->node_bandwidth;
@$aDuckFlowNow[$oNodeRow->duck_code] += $oNodeRow->node_bandwidth;
}
// 入库zhunafa
foreach ($aZhuanfaFlowNow as $k => $v) {
if ($v) {
Zhuanfas::where('code', $k)->increment('today_width', $v);
Zhuanfas::where('code', $k)->increment('use_width', $v);
}
}
// 入库duck
foreach ($aDuckFlowNow as $k => $v) {
if ($v) {
Ducks::where('code', $k)->increment('today_width', $v);
Ducks::where('code', $k)->increment('use_width', $v);
}
}
// 清空node流量
Node::query()->update(['node_bandwidth' => 0]);
if ($sMsg) {
(new Telegram())->send(0, $sMsg);
}
}
// 每日报表
public static function reportBandWidth(): void
{
$oZhuanfas = new Zhuanfas();
$oDucks = new Ducks();
// 流量报告,转发
$oTgSendFormat = new TgSendFormat();
$oTgSendFormat->sTitleEmoji = "\n\n";
$oTgSendFormat->sTitle = "# 转发流量";
$today = new DateTime();
$oZhuanfasAll = $oZhuanfas->all();
foreach ($oZhuanfasAll as $oZhuanfasRow) {
$targetDate = new DateTime($oZhuanfasRow->end_date);
$interval = $today->diff($targetDate);
if ($today > $targetDate) {
$iDiffDay = -$interval->days;
} else {
$iDiffDay = $interval->days;
}
$oTgSendFormat->rowAdd(
$oZhuanfasRow->name,
$oZhuanfasRow->today_width,
$oZhuanfasRow->use_width,
$oZhuanfasRow->max_width,
$iDiffDay
);
}
$sTgReport = $oTgSendFormat->typeA();
(new Telegram())->send(0, $sTgReport);
// 流量报告,机器
$oTgSendFormat = new TgSendFormat();
$oTgSendFormat->sTitleEmoji = "\n\n";
$oTgSendFormat->sTitle = "# 机器流量";
$oDucksAll = $oDucks->orderBy('code')->get();
foreach ($oDucksAll as $oDucksRow) {
if ($oDucksRow->width_reset_day == 0) {
$iWidthResetDay = 0;
} if ($oDucksRow->width_reset_day > date('d')) {
$iWidthResetDay = $oDucksRow->width_reset_day - date('d');
} else {
$date = new DateTime();
$date->modify('first day of next month');
$date->setDate((int)$date->format('Y'), (int)$date->format('m'), $oDucksRow->width_reset_day);
$sEndDate = $date;
$currentDate = new DateTime();
$interval = $currentDate->diff($sEndDate);
$iWidthResetDay = $interval->days;
}
$oTgSendFormat->rowAdd(
$oDucksRow->code,
$oDucksRow->today_width,
$oDucksRow->use_width,
$oDucksRow->max_width,
$iWidthResetDay,
$oDucksRow->memo
);
}
$sTgReport = $oTgSendFormat->typeA();
(new Telegram())->send(0, $sTgReport);
}
// 每日清空duck和转发的每日流量
public static function clearTodayWidth(): void
{
Ducks::query()->update(['today_width' => 0]);
Zhuanfas::query()->update(['today_width' => 0]);
}
public static function reportBandWidth_back(): void
{
$oNode = new Node();
$oZhuanfas = new Zhuanfas();
$oDucks = new Ducks();
$oNodeAll = $oNode->all();
foreach ($oNodeAll as $oNodeRow) {
@ $iZhuanfaFlowToday[$oNodeRow->zhuanfa_code] += $oNodeRow->node_bandwidth;
@ $iDuckFlowToday[$oNodeRow->duck_code] += $oNodeRow->node_bandwidth;
$oZhuanfas->where('code', $oNodeRow->zhuanfa_code)->increment('use_width', $oNodeRow->node_bandwidth);
$oDucks->where('code', $oNodeRow->duck_code)->increment('use_width', $oNodeRow->node_bandwidth);
$oNode->where('id', $oNodeRow->id)->update(['node_bandwidth' => 0]);
}
// 流量报告,转发
$oTgSendFormat = new TgSendFormat();
$oTgSendFormat->sTitleEmoji = "⚡️⚡️⚡️⚡️⚡️⚡️";
$oTgSendFormat->sTitle = "# 转发流量";
$today = new DateTime();
$oZhuanfasAll = $oZhuanfas->all();
foreach ($oZhuanfasAll as $oZhuanfasRow) {
$targetDate = new DateTime($oZhuanfasRow->end_date);
$interval = $today->diff($targetDate);
$oTgSendFormat->rowAdd(
$oZhuanfasRow->name,
$iZhuanfaFlowToday[$oZhuanfasRow->code],
$oZhuanfasRow->use_width,
$oZhuanfasRow->max_width,
$interval->days
);
}
$sTgReport = $oTgSendFormat->typeA();
(new Telegram())->send(0, $sTgReport);
// 流量报告,机器
$oTgSendFormat = new TgSendFormat();
$oTgSendFormat->sTitleEmoji = "\n\n";
$oTgSendFormat->sTitle = "# 机器流量";
$oDucksAll = $oDucks->orderBy('code')->get();
foreach ($oDucksAll as $oDucksRow) {
if ($oDucksRow->width_reset_day == 0) {
$iWidthResetDay = 0;
} if ($oDucksRow->width_reset_day > date('d')) {
$iWidthResetDay = $oDucksRow->width_reset_day - date('d');
} else {
$date = new DateTime();
$date->modify('first day of next month');
$date->setDate((int)$date->format('Y'), (int)$date->format('m'), $oDucksRow->width_reset_day);
$sEndDate = $date;
$currentDate = new DateTime();
$interval = $currentDate->diff($sEndDate);
$iWidthResetDay = $interval->days;
}
$oTgSendFormat->rowAdd(
$oDucksRow->code,
$iDuckFlowToday[$oDucksRow->code] ?? 0,
$oDucksRow->use_width,
$oDucksRow->max_width,
$iWidthResetDay
);
}
$sTgReport = $oTgSendFormat->typeA();
(new Telegram())->send(0, $sTgReport);
}
// 用户流量每日重置
public static function resetTodayBandwidth(): void
{
(new User())->query()->update(['transfer_today' => 0]);
echo Tools::toDateTime(time()) . ' 重设用户每日流量完成' . PHP_EOL;
}
// 这是那个每月几日重置
// 是重置免费用户
// 现在没用了,傻逼功能。用户光有流量没有等级照样用不了,取消等级限制则变成所有用户都运作,根本不合理
public static function resetFreeUserBandwidth(): void
{
// $freeUsers = (new User())->where('class', 0)
// ->where('auto_reset_day', date('d'))->get();
//
// foreach ($freeUsers as $user) {
// try {
// Notification::notifyUser(
// $user,
// $_ENV['appName'] . '-免费流量重置通知',
// '你好,你的免费流量已经被重置为' . $user->auto_reset_bandwidth . 'GB。'
// );
// } catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
// echo $e->getMessage() . PHP_EOL;
// }
//
// $user->u = 0;
// $user->d = 0;
// $user->transfer_enable = $user->auto_reset_bandwidth * 1024 * 1024 * 1024;
// $user->save();
// }
//
// echo Tools::toDateTime(time()) . ' 免费用户流量重置完成' . PHP_EOL;
}
public static function resetFreeUserBandwidth_new(): void
{
echo 1111;
}
// 财务日报,好像没用
public static function sendDailyFinanceMail(): void
{
$today = strtotime('00:00:00');
$paylists = (new Paylist())->where('status', 1)
->whereBetween('datetime', [strtotime('-1 day', $today), $today])->get();
$text_html = '<table border=1><tr><td>金额</td><td>用户ID</td><td>用户名</td><td>充值时间</td>';
foreach ($paylists as $paylist) {
$text_html .= '<tr>';
$text_html .= '<td>' . $paylist->total . '</td>';
$text_html .= '<td>' . $paylist->userid . '</td>';
$text_html .= '<td>' . (new User())->find($paylist->userid)->user_name . '</td>';
$text_html .= '<td>' . Tools::toDateTime((int) $paylist->datetime) . '</td>';
$text_html .= '</tr>';
}
$text_html .= '</table>';
$text_html .= '<br>昨日总收入笔数:' . count($paylists) . '<br>昨日总收入金额:' . $paylists->sum('total');
echo 'Sending daily finance email to admin user' . PHP_EOL;
try {
Notification::notifyAdmin(
'财务日报',
$text_html,
'finance.tpl'
);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
echo Tools::toDateTime(time()) . ' 成功发送财务日报' . PHP_EOL;
}
// 财务周报,好像没用
public static function sendWeeklyFinanceMail(): void
{
$today = strtotime('00:00:00');
$paylists = (new Paylist())->where('status', 1)
->whereBetween('datetime', [strtotime('-1 week', $today), $today])
->get();
$text_html = '<br>上周总收入笔数:' . count($paylists) . '<br>上周总收入金额:' . $paylists->sum('total');
echo 'Sending weekly finance email to admin user' . PHP_EOL;
try {
Notification::notifyAdmin(
'财务周报',
$text_html,
'finance.tpl'
);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
echo Tools::toDateTime(time()) . ' 成功发送财务周报' . PHP_EOL;
}
// 财务越好,好像没用
public static function sendMonthlyFinanceMail(): void
{
$today = strtotime('00:00:00');
$paylists = (new Paylist())->where('status', 1)
->whereBetween('datetime', [strtotime('-1 month', $today), $today])
->get();
$text_html = '<br>上月总收入笔数:' . count($paylists) . '<br>上月总收入金额:' . $paylists->sum('total');
echo 'Sending monthly finance email to admin user' . PHP_EOL;
try {
Notification::notifyAdmin(
'财务月报',
$text_html,
'finance.tpl'
);
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
echo $e->getMessage() . PHP_EOL;
}
echo Tools::toDateTime(time()) . ' 成功发送财务月报' . PHP_EOL;
}
// 流量提醒
// 只提醒付费用户
public static function sendPaidUserUsageLimitNotification(): void
{
$paidUsers = (new User())->where('class', '>=', 2)->get();
foreach ($paidUsers as $user) {
$user_traffic_left = $user->transfer_enable - $user->u - $user->d;
$under_limit = false;
$unit_text = '';
if ($_ENV['notify_limit_mode'] === 'per' &&
$user_traffic_left / $user->transfer_enable * 100 < $_ENV['notify_limit_value']
) {
$under_limit = true;
$unit_text = '%';
} elseif ($_ENV['notify_limit_mode'] === 'mb' &&
Tools::flowToMB($user_traffic_left) < $_ENV['notify_limit_value']
) {
$under_limit = true;
$unit_text = 'MB';
}
if ($under_limit && ! $user->traffic_notified) {
try {
Notification::notifyUser(
$user,
$_ENV['appName'] . '-你的剩余流量过低',
'你好,系统发现你剩余流量已经低于 ' . $_ENV['notify_limit_value'] . $unit_text . ' 。',
);
$user->traffic_notified = true;
} catch (GuzzleException|ClientExceptionInterface|TelegramSDKException $e) {
$user->traffic_notified = false;
echo $e->getMessage() . PHP_EOL;
}
$user->save();
} elseif (! $under_limit && $user->traffic_notified) {
$user->traffic_notified = false;
$user->save();
}
}
echo Tools::toDateTime(time()) . ' 付费用户用量限制提醒完成' . PHP_EOL;
}
public static function sendDailyTrafficReport(): void
{
$users = (new User())->whereIn('daily_mail_enable', [1, 2])->get();
$ann_latest_raw = (new Ann())->orderBy('date', 'desc')->first();
if ($ann_latest_raw === null) {
$ann_latest = '<br><br>';
} else {
$ann_latest = $ann_latest_raw->content . '<br><br>';
}
foreach ($users as $user) {
$user->sendDailyNotification($ann_latest);
}
echo Tools::toDateTime(time()) . ' 成功发送每日流量报告' . PHP_EOL;
}
/**
* @throws TelegramSDKException
*/
public static function sendTelegramDailyJob(): void
{
(new Telegram())->send(0, Config::obtain('telegram_daily_job_text'));
echo Tools::toDateTime(time()) . ' 成功发送 Telegram 每日任务提示' . PHP_EOL;
}
/**
* @throws TelegramSDKException
*/
public static function sendTelegramDiary(): void
{
(new Telegram())->send(
0,
str_replace(
[
'%getTodayCheckinUser%',
'%lastday_total%',
],
[
Analytics::getTodayCheckinUser(),
Analytics::getTodayTrafficUsage(),
],
Config::obtain('telegram_diary_text')
)
);
echo Tools::toDateTime(time()) . ' 成功发送 Telegram 系统运行日志' . PHP_EOL;
}
public static function updateNodeIp(): void
{
$nodes = (new Node())->where('type', 1)->get();
foreach ($nodes as $node) {
$node->updateNodeIp();
$node->save();
}
echo Tools::toDateTime(time()) . ' 更新节点 IP 完成' . PHP_EOL;
}
}