AutoJob.php 11 KB

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