AutoJob.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Components\Helpers;
  4. use App\Components\PushNotification;
  5. use App\Models\Config;
  6. use App\Models\Coupon;
  7. use App\Models\Invite;
  8. use App\Models\Node;
  9. use App\Models\NodeInfo;
  10. use App\Models\Order;
  11. use App\Models\Payment;
  12. use App\Models\User;
  13. use App\Models\UserBanedLog;
  14. use App\Models\UserHourlyDataFlow;
  15. use App\Models\UserSubscribe;
  16. use App\Models\UserSubscribeLog;
  17. use App\Models\VerifyCode;
  18. use Cache;
  19. use DB;
  20. use Exception;
  21. use Illuminate\Console\Command;
  22. use Log;
  23. class AutoJob extends Command {
  24. protected $signature = 'autoJob';
  25. protected $description = '自动化任务';
  26. /*
  27. * 警告:除非熟悉业务流程,否则不推荐更改以下执行顺序,随意变更以下顺序可能导致系统异常
  28. */
  29. public function handle(): void {
  30. $jobStartTime = microtime(true);
  31. // 关闭超时未支付在线订单
  32. $this->closePayments();
  33. // 关闭超时未支付订单
  34. $this->closeOrders();
  35. //过期验证码、优惠券、邀请码无效化
  36. $this->expireCode();
  37. // 封禁访问异常的订阅链接
  38. $this->blockSubscribe();
  39. // 封禁账号
  40. $this->blockUsers();
  41. // 解封被封禁的账号
  42. $this->unblockUsers();
  43. // 端口回收与分配
  44. $this->dispatchPort();
  45. // 检测节点是否离线
  46. $this->checkNodeStatus();
  47. // 检查 维护模式
  48. if(sysConfig('maintenance_mode') && strtotime(sysConfig('maintenance_time')) < time()){
  49. Config::query()->whereName('maintenance_mode')->update(['value' => 0]);
  50. Config::query()->whereName('maintenance_time')->update(['value' => '']);
  51. }
  52. $jobEndTime = microtime(true);
  53. $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
  54. Log::info('---【'.$this->description.'】完成---,耗时'.$jobUsedTime.'秒');
  55. }
  56. // 关闭超时未在线支付订单
  57. private function closePayments(): void {
  58. // 关闭超时未支付的在线订单(15分钟关闭订单)
  59. $paymentList = Payment::query()
  60. ->whereStatus(0)
  61. ->where('created_at', '<=', date("Y-m-d H:i:s", strtotime("-15 minutes")))
  62. ->get();
  63. if($paymentList->isNotEmpty()){
  64. try{
  65. DB::beginTransaction();
  66. foreach($paymentList as $payment){
  67. // 关闭支付单
  68. Payment::query()->whereId($payment->id)->update(['status' => -1]);
  69. // 关闭回调PaymentCallback::query()->whereTradeNo($payment->trade_no)->update(['status' => 0]);
  70. // 关闭订单
  71. Order::query()->whereOid($payment->oid)->update(['status' => -1]);
  72. // 退回优惠券
  73. if($payment->order->coupon_id){
  74. $result = $this->returnCoupon($payment->order->coupon_id);
  75. if($result){
  76. Helpers::addCouponLog($payment->order->coupon_id, $payment->order->goods_id, $payment->oid,
  77. '在线订单超时未支付,自动退回');
  78. }
  79. }
  80. }
  81. DB::commit();
  82. }catch(Exception $e){
  83. Log::info('【异常】自动关闭超时未支付在线订单:'.$e);
  84. DB::rollBack();
  85. }
  86. }
  87. }
  88. //返回优惠券
  89. private function returnCoupon($coupon_id): bool {
  90. $coupon = Coupon::find($coupon_id);
  91. if($coupon && $coupon->type !== 3){
  92. Coupon::query()->whereId($coupon_id)->increment('usage_count', 1, ['status' => 0]);
  93. return true;
  94. }
  95. return false;
  96. }
  97. // 关闭超时未支付订单
  98. private function closeOrders(): void {
  99. // 关闭超时未支付的支付订单(15分钟关闭订单)
  100. $orderList = Order::query()
  101. ->whereStatus(0)
  102. ->where('created_at', '<=', date("Y-m-d H:i:s", strtotime("-15 minutes")))
  103. ->get();
  104. if($orderList->isNotEmpty()){
  105. try{
  106. DB::beginTransaction();
  107. foreach($orderList as $order){
  108. // 关闭订单
  109. Order::query()->whereOid($order->oid)->update(['status' => -1]);
  110. // 退回优惠券
  111. if($order->coupon_id){
  112. $result = $this->returnCoupon($order->coupon_id);
  113. if($result){
  114. Helpers::addCouponLog($order->coupon_id, $order->goods_id, $order->oid, '订单超时未支付,自动退回');
  115. }
  116. }
  117. }
  118. DB::commit();
  119. }catch(Exception $e){
  120. Log::info('【异常】自动关闭超时未支付订单:'.$e);
  121. DB::rollBack();
  122. }
  123. }
  124. }
  125. // 注册验证码自动置无效 & 优惠券无效化
  126. private function expireCode(): void {
  127. // 注册验证码自动置无效
  128. VerifyCode::query()
  129. ->whereStatus(0)
  130. ->where('created_at', '<=', date('Y-m-d H:i:s', strtotime("-10 minutes")))
  131. ->update(['status' => 2]);
  132. // 优惠券到期自动置无效
  133. Coupon::query()->whereStatus(0)->where('available_end', '<=', time())->update(['status' => 2]);
  134. // 用尽的优惠劵
  135. Coupon::query()->whereStatus(0)->whereIn('type', [1, 2])->whereUsageCount(0)->update(['status' => 2]);
  136. // 邀请码到期自动置无效
  137. Invite::query()->whereStatus(0)->where('dateline', '<=', date('Y-m-d H:i:s'))->update(['status' => 2]);
  138. }
  139. // 封禁访问异常的订阅链接
  140. private function blockSubscribe(): void {
  141. if(sysConfig('is_subscribe_ban')){
  142. foreach(User::query()->activeUser()->get() as $user){
  143. $subscribe = UserSubscribe::query()->whereUserId($user->id)->first();
  144. if($subscribe){
  145. // 24小时内不同IP的请求次数
  146. $request_times = UserSubscribeLog::query()
  147. ->whereSid($subscribe->id)
  148. ->where('request_time', '>=',
  149. date("Y-m-d H:i:s", strtotime("-1 days")))
  150. ->distinct()
  151. ->count('request_ip');
  152. if($request_times >= sysConfig('subscribe_ban_times')){
  153. UserSubscribe::query()->whereId($subscribe->id)->update([
  154. 'status' => 0,
  155. 'ban_time' => time(),
  156. 'ban_desc' => '存在异常,自动封禁'
  157. ]);
  158. // 记录封禁日志
  159. $this->addUserBanLog($subscribe->user_id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
  160. }
  161. }
  162. }
  163. }
  164. }
  165. /**
  166. * 添加用户封禁日志
  167. *
  168. * @param int $userId 用户ID
  169. * @param int $minutes 封禁时长,单位分钟
  170. * @param string $description 封禁理由
  171. */
  172. private function addUserBanLog($userId, $minutes, $description): void {
  173. $log = new UserBanedLog();
  174. $log->user_id = $userId;
  175. $log->minutes = $minutes;
  176. $log->description = $description;
  177. $log->save();
  178. }
  179. // 封禁账号
  180. private function blockUsers(): void {
  181. // 封禁1小时内流量异常账号
  182. if(sysConfig('is_traffic_ban')){
  183. $userList = User::query()->activeUser()->whereBanTime(0)->get();
  184. foreach($userList as $user){
  185. // 对管理员豁免
  186. if($user->is_admin){
  187. continue;
  188. }
  189. // 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
  190. $totalTraffic = UserHourlyDataFlow::query()
  191. ->userHourly($user->id)
  192. ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
  193. ->sum('total');
  194. if($totalTraffic >= sysConfig('traffic_ban_value') * GB){
  195. User::query()->whereId($user->id)->update([
  196. 'enable' => 0,
  197. 'ban_time' => strtotime("+".sysConfig('traffic_ban_time')." minutes")
  198. ]);
  199. // 写入日志
  200. $this->addUserBanLog($user->id, sysConfig('traffic_ban_time'), '【临时封禁代理】-1小时内流量异常');
  201. }
  202. }
  203. }
  204. // 禁用流量超限用户
  205. $userList = User::query()->activeUser()->whereBanTime(0)->whereRaw("u + d >= transfer_enable")->get();
  206. foreach($userList as $user){
  207. User::query()->whereId($user->id)->update(['enable' => 0]);
  208. // 写入日志
  209. $this->addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
  210. }
  211. }
  212. // 解封被临时封禁的账号
  213. private function unblockUsers(): void {
  214. // 解封被临时封禁的账号
  215. $userList = User::query()->whereEnable(0)->where('status', '>=', 0)->where('ban_time', '>', 0)->get();
  216. foreach($userList as $user){
  217. if($user->ban_time < time()){
  218. User::query()->whereId($user->id)->update(['enable' => 1, 'ban_time' => 0]);
  219. // 写入操作日志
  220. $this->addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
  221. }
  222. }
  223. // 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
  224. $userList = User::query()
  225. ->whereEnable(0)
  226. ->where('status', '>=', 0)
  227. ->whereBanTime(0)
  228. ->where('expire_time', '>=', date('Y-m-d'))
  229. ->whereRaw("u + d < transfer_enable")
  230. ->get();
  231. foreach($userList as $user){
  232. User::query()->whereId($user->id)->update(['enable' => 1]);
  233. // 写入操作日志
  234. $this->addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
  235. }
  236. }
  237. // 端口回收与分配
  238. private function dispatchPort(): void {
  239. if(sysConfig('auto_release_port')){
  240. ## 自动分配端口
  241. foreach(User::query()->activeUser()->wherePort(0)->get() as $user){
  242. $port = sysConfig('is_rand_port')? Helpers::getRandPort() : Helpers::getOnlyPort();
  243. User::query()->whereId($user->id)->update(['port' => $port]);
  244. }
  245. ## 被封禁的账号自动释放端口
  246. User::query()->whereEnable(0)->whereStatus(-1)->where('port', '!=', 0)->update(['port' => 0]);
  247. ## 过期一个月的账户自动释放端口
  248. User::query()
  249. ->whereEnable(0)
  250. ->where('port', '!=', 0)
  251. ->where('expire_time', '<=', date("Y-m-d", strtotime("-30 days")))
  252. ->update(['port' => 0]);
  253. }
  254. }
  255. // 检测节点是否离线
  256. private function checkNodeStatus(): void {
  257. if(sysConfig('is_node_offline')){
  258. foreach(Node::query()->whereIsRelay(0)->whereStatus(1)->get() as $node){
  259. // 10分钟内无节点负载信息则认为是后端炸了
  260. $nodeTTL = NodeInfo::query()
  261. ->whereNodeId($node->id)
  262. ->where('log_time', '>=', strtotime("-10 minutes"))
  263. ->latest('log_time')
  264. ->doesntExist();
  265. if($nodeTTL && sysConfig('offline_check_times')){
  266. // 已通知次数
  267. $cacheKey = 'offline_check_times'.$node->id;
  268. if(Cache::has($cacheKey)){
  269. $times = Cache::get($cacheKey);
  270. }else{
  271. // 键将保留24小时
  272. Cache::put($cacheKey, 1, Day);
  273. $times = 1;
  274. }
  275. if($times < sysConfig('offline_check_times')){
  276. Cache::increment($cacheKey);
  277. PushNotification::send('节点异常警告', "节点**{$node->name}【{$node->ip}】**异常:**心跳异常,可能离线了**");
  278. }
  279. }
  280. }
  281. }
  282. }
  283. }