AutoJob.php 11 KB

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