AutoJob.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Components\Helpers;
  4. use App\Components\PushNotification;
  5. use App\Http\Models\Config;
  6. use App\Http\Models\Coupon;
  7. use App\Http\Models\Invite;
  8. use App\Http\Models\Order;
  9. use App\Http\Models\Payment;
  10. use App\Http\Models\SsNode;
  11. use App\Http\Models\SsNodeInfo;
  12. use App\Http\Models\User;
  13. use App\Http\Models\UserBanLog;
  14. use App\Http\Models\UserSubscribe;
  15. use App\Http\Models\UserSubscribeLog;
  16. use App\Http\Models\UserTrafficHourly;
  17. use App\Http\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. // 关闭超时未支付的在线支付订单(在线支付收款二维码超过30分钟自动关闭,关闭后无法再支付,所以我们限制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. DB::beginTransaction();
  72. try{
  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. Coupon::query()->whereId($payment->order->coupon_id)->update(['status' => 0]);
  81. Helpers::addCouponLog($payment->order->coupon_id, $payment->order->goods_id, $payment->oid,
  82. '在线订单超时未支付,自动退回');
  83. }
  84. }
  85. DB::commit();
  86. }catch(Exception $e){
  87. Log::info('【异常】自动关闭超时未支付在线订单:'.$e);
  88. DB::rollBack();
  89. }
  90. }
  91. }
  92. // 关闭超时未支付订单
  93. private function closeOrders() {
  94. // 关闭超时未支付的在线支付订单(在线支付收款二维码超过30分钟自动关闭,关闭后无法再支付,所以我们限制15分钟内必须付款)
  95. $orderList = Order::query()
  96. ->whereStatus(0)
  97. ->where('created_at', '<=', date("Y-m-d H:i:s", strtotime("-30 minutes")))
  98. ->get();
  99. if($orderList->isNotEmpty()){
  100. DB::beginTransaction();
  101. try{
  102. foreach($orderList as $order){
  103. // 关闭订单
  104. Order::query()->whereOid($order->oid)->update(['status' => -1]);
  105. // 退回优惠券
  106. if($order->coupon_id){
  107. Coupon::query()->whereId($order->coupon_id)->update(['status' => 0]);
  108. Helpers::addCouponLog($order->coupon_id, $order->goods_id, $order->oid, '订单超时未支付,自动退回');
  109. }
  110. }
  111. DB::commit();
  112. }catch(Exception $e){
  113. Log::info('【异常】自动关闭超时未支付订单:'.$e);
  114. DB::rollBack();
  115. }
  116. }
  117. }
  118. // 注册验证码自动置无效
  119. private function expireCode() {
  120. // 注册验证码自动置无效
  121. VerifyCode::query()
  122. ->whereStatus(0)
  123. ->where('created_at', '<=', date('Y-m-d H:i:s', strtotime("-10 minutes")))
  124. ->update(['status' => 2]);
  125. // 优惠券到期自动置无效
  126. Coupon::query()->whereStatus(0)->where('available_end', '<=', time())->update(['status' => 2]);
  127. // 邀请码到期自动置无效
  128. Invite::query()->whereStatus(0)->where('dateline', '<=', date('Y-m-d H:i:s'))->update(['status' => 2]);
  129. }
  130. // 封禁访问异常的订阅链接
  131. private function blockSubscribe() {
  132. if(self::$systemConfig['is_subscribe_ban']){
  133. $userList = User::query()->where('status', '>=', 0)->whereEnable(1)->get();
  134. foreach($userList as $user){
  135. $subscribe = UserSubscribe::query()->whereUserId($user->id)->first();
  136. if($subscribe){
  137. // 24小时内不同IP的请求次数
  138. $request_times = UserSubscribeLog::query()
  139. ->whereSid($subscribe->id)
  140. ->where('request_time', '>=',
  141. date("Y-m-d H:i:s", strtotime("-24 hours")))
  142. ->distinct('request_ip')
  143. ->count('request_ip');
  144. if($request_times >= self::$systemConfig['subscribe_ban_times']){
  145. UserSubscribe::query()->whereId($subscribe->id)->update([
  146. 'status' => 0,
  147. 'ban_time' => time(),
  148. 'ban_desc' => '存在异常,自动封禁'
  149. ]);
  150. // 记录封禁日志
  151. $this->addUserBanLog($subscribe->user_id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
  152. }
  153. }
  154. }
  155. }
  156. }
  157. /**
  158. * 添加用户封禁日志
  159. *
  160. * @param int $userId 用户ID
  161. * @param int $minutes 封禁时长,单位分钟
  162. * @param string $desc 封禁理由
  163. */
  164. private function addUserBanLog($userId, $minutes, $desc) {
  165. $log = new UserBanLog();
  166. $log->user_id = $userId;
  167. $log->minutes = $minutes;
  168. $log->desc = $desc;
  169. $log->save();
  170. }
  171. // 封禁账号
  172. private function blockUsers() {
  173. // 封禁1小时内流量异常账号
  174. if(self::$systemConfig['is_traffic_ban']){
  175. $userList = User::query()->whereEnable(1)->where('status', '>=', 0)->whereBanTime(0)->get();
  176. foreach($userList as $user){
  177. // 对管理员豁免
  178. if($user->is_admin){
  179. continue;
  180. }
  181. // 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
  182. $totalTraffic = UserTrafficHourly::query()
  183. ->whereUserId($user->id)
  184. ->whereNodeId(0)
  185. ->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))
  186. ->sum('total');
  187. if($totalTraffic >= (self::$systemConfig['traffic_ban_value'] * 1073741824)){
  188. User::query()->whereId($user->id)->update([
  189. 'enable' => 0,
  190. 'ban_time' => strtotime(date('Y-m-d H:i:s',
  191. strtotime("+".self::$systemConfig['traffic_ban_time']." minutes")))
  192. ]);
  193. // 写入日志
  194. $this->addUserBanLog($user->id, self::$systemConfig['traffic_ban_time'], '【临时封禁代理】-1小时内流量异常');
  195. }
  196. }
  197. }
  198. // 禁用流量超限用户
  199. $userList = User::query()
  200. ->whereEnable(1)
  201. ->where('status', '>=', 0)
  202. ->whereBanTime(0)
  203. ->whereRaw("u + d >= transfer_enable")
  204. ->get();
  205. foreach($userList as $user){
  206. User::query()->whereId($user->id)->update(['enable' => 0]);
  207. // 写入日志
  208. $this->addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
  209. }
  210. }
  211. // 解封被临时封禁的账号
  212. private function unblockUsers() {
  213. // 解封被临时封禁的账号
  214. $userList = User::query()->whereEnable(0)->where('status', '>=', 0)->where('ban_time', '>', 0)->get();
  215. foreach($userList as $user){
  216. if($user->ban_time < time()){
  217. User::query()->whereId($user->id)->update(['enable' => 1, 'ban_time' => 0]);
  218. // 写入操作日志
  219. $this->addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
  220. }
  221. }
  222. // 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
  223. $userList = User::query()
  224. ->whereEnable(0)
  225. ->where('status', '>=', 0)
  226. ->whereBanTime(0)
  227. ->where('expire_time', '>=', date('Y-m-d'))
  228. ->whereRaw("u + d < transfer_enable")
  229. ->get();
  230. foreach($userList as $user){
  231. User::query()->whereId($user->id)->update(['enable' => 1]);
  232. // 写入操作日志
  233. $this->addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
  234. }
  235. }
  236. // 端口回收与分配
  237. private function dispatchPort() {
  238. if(self::$systemConfig['auto_release_port']){
  239. ## 自动分配端口
  240. $userList = User::query()->whereEnable(1)->where('status', '>=', 0)->wherePort(0)->get();
  241. foreach($userList as $user){
  242. $port = self::$systemConfig['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() {
  257. if(self::$systemConfig['is_node_offline']){
  258. $nodeList = SsNode::query()->whereIsTransit(0)->whereStatus(1)->get();
  259. foreach($nodeList as $node){
  260. // 10分钟内无节点负载信息则认为是后端炸了
  261. $nodeTTL = SsNodeInfo::query()
  262. ->whereNodeId($node->id)
  263. ->where('log_time', '>=', strtotime("-10 minutes"))
  264. ->orderBy('id', 'desc')
  265. ->doesntExist();
  266. if($nodeTTL){
  267. if(self::$systemConfig['offline_check_times']){
  268. // 已通知次数
  269. $cacheKey = 'offline_check_times'.$node->id;
  270. if(Cache::has($cacheKey)){
  271. $times = Cache::get($cacheKey);
  272. }else{
  273. // 键将保留24小时
  274. Cache::put($cacheKey, 1, 86400);
  275. $times = 1;
  276. }
  277. if($times < self::$systemConfig['offline_check_times']){
  278. Cache::increment($cacheKey);
  279. PushNotification::send('节点异常警告', "节点**{$node->name}【{$node->ip}】**异常:**心跳异常,可能离线了**");
  280. }
  281. }
  282. }
  283. }
  284. }
  285. }
  286. }