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 = '
| 金额 | 用户ID | 用户名 | 充值时间 | '; foreach ($paylists as $paylist) { $text_html .= '
| ' . $paylist->total . ' | '; $text_html .= '' . $paylist->userid . ' | '; $text_html .= '' . (new User())->find($paylist->userid)->user_name . ' | '; $text_html .= '' . Tools::toDateTime((int) $paylist->datetime) . ' | '; $text_html .= '