AutoJob.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Components\Helpers;
  4. use App\Components\Yzy;
  5. use App\Http\Models\Goods;
  6. use App\Http\Models\GoodsLabel;
  7. use App\Http\Models\ReferralLog;
  8. use Illuminate\Console\Command;
  9. use App\Http\Models\Coupon;
  10. use App\Http\Models\CouponLog;
  11. use App\Http\Models\Invite;
  12. use App\Http\Models\Order;
  13. use App\Http\Models\Payment;
  14. use App\Http\Models\User;
  15. use App\Http\Models\UserLabel;
  16. use App\Http\Models\UserBanLog;
  17. use App\Http\Models\UserSubscribe;
  18. use App\Http\Models\UserSubscribeLog;
  19. use App\Http\Models\UserTrafficHourly;
  20. use Log;
  21. use DB;
  22. class AutoJob extends Command
  23. {
  24. protected $signature = 'autoJob';
  25. protected $description = '自动化任务';
  26. protected static $systemConfig;
  27. public function __construct()
  28. {
  29. parent::__construct();
  30. self::$systemConfig = Helpers::systemConfig();
  31. }
  32. /*
  33. * 警告:除非熟悉业务流程,否则不推荐更改以下执行顺序,随意变更以下顺序可能导致系统异常
  34. */
  35. public function handle()
  36. {
  37. $jobStartTime = microtime(true);
  38. // 优惠券到期自动置无效
  39. $this->expireCoupon();
  40. // 邀请码到期自动置无效
  41. $this->expireInvite();
  42. // 封禁访问异常的订阅链接
  43. $this->blockSubscribe();
  44. // 封禁账号
  45. $this->blockUsers();
  46. // 移除过期的账号的标签和流量
  47. $this->removeUserLabels();
  48. // 解封被封禁的账号
  49. $this->unblockUsers();
  50. // 端口回收与分配
  51. $this->dispatchPort();
  52. // 审计待支付的订单
  53. $this->detectOrders();
  54. // 关闭超时未支付订单
  55. $this->closeOrders();
  56. $jobEndTime = microtime(true);
  57. $jobUsedTime = round(($jobEndTime - $jobStartTime), 4);
  58. Log::info('执行定时任务【' . $this->description . '】,耗时' . $jobUsedTime . '秒');
  59. }
  60. // 优惠券到期自动置无效
  61. private function expireCoupon()
  62. {
  63. $couponList = Coupon::query()->where('status', 0)->where('available_end', '<=', time())->get();
  64. if (!$couponList->isEmpty()) {
  65. foreach ($couponList as $coupon) {
  66. Coupon::query()->where('id', $coupon->id)->update(['status' => 2]);
  67. }
  68. }
  69. }
  70. // 邀请码到期自动置无效
  71. private function expireInvite()
  72. {
  73. $inviteList = Invite::query()->where('status', 0)->where('dateline', '<=', date('Y-m-d H:i:s'))->get();
  74. if (!$inviteList->isEmpty()) {
  75. foreach ($inviteList as $invite) {
  76. Invite::query()->where('id', $invite->id)->update(['status' => 2]);
  77. }
  78. }
  79. }
  80. // 封禁访问异常的订阅链接
  81. private function blockSubscribe()
  82. {
  83. if (self::$systemConfig['is_subscribe_ban']) {
  84. $subscribeList = UserSubscribe::query()->where('status', 1)->get();
  85. if (!$subscribeList->isEmpty()) {
  86. foreach ($subscribeList as $subscribe) {
  87. // 24小时内不同IP的请求次数
  88. $request_times = UserSubscribeLog::query()->where('sid', $subscribe->id)->where('request_time', '>=', date("Y-m-d H:i:s", strtotime("-24 hours")))->distinct('request_ip')->count('request_ip');
  89. if ($request_times >= self::$systemConfig['subscribe_ban_times']) {
  90. UserSubscribe::query()->where('id', $subscribe->id)->update(['status' => 0, 'ban_time' => time(), 'ban_desc' => '存在异常,自动封禁']);
  91. // 记录封禁日志
  92. $this->addUserBanLog($subscribe->user_id, 0, '【完全封禁订阅】-订阅24小时内请求异常');
  93. }
  94. }
  95. }
  96. }
  97. }
  98. // 封禁账号
  99. private function blockUsers()
  100. {
  101. // 过期用户处理
  102. $userList = User::query()->where('status', '>=', 0)->where('enable', 1)->where('expire_time', '<', date('Y-m-d'))->get();
  103. if (!$userList->isEmpty()) {
  104. foreach ($userList as $user) {
  105. if (self::$systemConfig['is_ban_status']) {
  106. User::query()->where('id', $user->id)->update([
  107. 'u' => 0,
  108. 'd' => 0,
  109. 'transfer_enable' => 0,
  110. 'enable' => 0,
  111. 'traffic_reset_day' => 0,
  112. 'ban_time' => 0,
  113. 'status' => -1
  114. ]);
  115. $this->addUserBanLog($user->id, 0, '【禁止登录,清空账户】-账号已过期');
  116. } else {
  117. User::query()->where('id', $user->id)->update([
  118. 'u' => 0,
  119. 'd' => 0,
  120. 'transfer_enable' => 0,
  121. 'enable' => 0,
  122. 'traffic_reset_day' => 0,
  123. 'ban_time' => 0
  124. ]);
  125. $this->addUserBanLog($user->id, 0, '【封禁代理,清空账户】-账号已过期');
  126. }
  127. }
  128. }
  129. // 封禁1小时内流量异常账号
  130. if (self::$systemConfig['is_traffic_ban']) {
  131. $userList = User::query()->where('status', '>=', 0)->where('enable', 1)->where('ban_time', 0)->get();
  132. if (!$userList->isEmpty()) {
  133. foreach ($userList as $user) {
  134. // 多往前取5分钟,防止数据统计任务执行时间过长导致没有数据
  135. $totalTraffic = UserTrafficHourly::query()->where('user_id', $user->id)->where('node_id', 0)->where('created_at', '>=', date('Y-m-d H:i:s', time() - 3900))->sum('total');
  136. if ($totalTraffic >= (self::$systemConfig['traffic_ban_value'] * 1024 * 1024 * 1024)) {
  137. User::query()->where('id', $user->id)->update(['enable' => 0, 'ban_time' => strtotime(date('Y-m-d H:i:s', strtotime("+" . self::$systemConfig['traffic_ban_time'] . " minutes")))]);
  138. // 写入日志
  139. $this->addUserBanLog($user->id, self::$systemConfig['traffic_ban_time'], '【临时封禁代理】-1小时内流量异常');
  140. }
  141. }
  142. }
  143. }
  144. // 禁用流量超限用户
  145. $userList = User::query()->where('status', '>=', 0)->where('enable', 1)->where('ban_time', 0)->whereRaw("u + d >= transfer_enable")->get();
  146. if (!$userList->isEmpty()) {
  147. foreach ($userList as $user) {
  148. User::query()->where('id', $user->id)->update(['enable' => 0]);
  149. // 写入日志
  150. $this->addUserBanLog($user->id, 0, '【封禁代理】-流量已用完');
  151. }
  152. }
  153. }
  154. // 移除过期的账号的标签和流量(临时封禁不移除)
  155. private function removeUserLabels()
  156. {
  157. $userList = User::query()->where('enable', 0)->where('ban_time', 0)->where('expire_time', '<', date('Y-m-d'))->get();
  158. if (!$userList->isEmpty()) {
  159. foreach ($userList as $user) {
  160. UserLabel::query()->where('user_id', $user->id)->delete();
  161. User::query()->where('id', $user->id)->update([
  162. 'u' => 0,
  163. 'd' => 0,
  164. 'transfer_enable' => 0,
  165. 'traffic_reset_day' => 0
  166. ]);
  167. }
  168. }
  169. }
  170. // 解封被临时封禁的账号
  171. private function unblockUsers()
  172. {
  173. // 解封被临时封禁的账号
  174. $userList = User::query()->where('status', '>=', 0)->where('enable', 0)->where('ban_time', '>', 0)->get();
  175. foreach ($userList as $user) {
  176. if ($user->ban_time < time()) {
  177. User::query()->where('id', $user->id)->update(['enable' => 1, 'ban_time' => 0]);
  178. // 写入操作日志
  179. $this->addUserBanLog($user->id, 0, '【自动解封】-临时封禁到期');
  180. }
  181. }
  182. // 可用流量大于已用流量也解封(比如:邀请返利自动加了流量)
  183. $userList = User::query()->where('status', '>=', 0)->where('enable', 0)->where('ban_time', 0)->where('expire_time', '>=', date('Y-m-d'))->whereRaw("u + d < transfer_enable")->get();
  184. if (!$userList->isEmpty()) {
  185. foreach ($userList as $user) {
  186. User::query()->where('id', $user->id)->update(['enable' => 1]);
  187. // 写入操作日志
  188. $this->addUserBanLog($user->id, 0, '【自动解封】-有流量解封');
  189. }
  190. }
  191. }
  192. // 端口回收与分配
  193. private function dispatchPort()
  194. {
  195. if (self::$systemConfig['auto_release_port']) {
  196. ## 自动分配端口
  197. $userList = User::query()->where('status', '>=', 0)->where('enable', 1)->where('port', 0)->get();
  198. if (!$userList->isEmpty()) {
  199. foreach ($userList as $user) {
  200. $port = self::$systemConfig['is_rand_port'] ? Helpers::getRandPort() : Helpers::getOnlyPort();
  201. User::query()->where('id', $user->id)->update(['port' => $port]);
  202. }
  203. }
  204. ## 被封禁的账号自动释放端口
  205. $userList = User::query()->where('status', -1)->where('enable', 0)->get();
  206. if (!$userList->isEmpty()) {
  207. foreach ($userList as $user) {
  208. if ($user->port) {
  209. User::query()->where('id', $user->id)->update(['port' => 0]);
  210. }
  211. }
  212. }
  213. ## 过期一个月的账户自动释放端口
  214. $userList = User::query()->where('enable', 0)->get();
  215. if (!$userList->isEmpty()) {
  216. foreach ($userList as $user) {
  217. if ($user->port) {
  218. $overdueDays = floor((strtotime(date('Y-m-d H:i:s')) - strtotime($user->expire_time)) / 86400);
  219. if ($overdueDays > 30) {
  220. User::query()->where('id', $user->id)->update(['port' => 0]);
  221. }
  222. }
  223. }
  224. }
  225. }
  226. }
  227. // 审计待支付的订单
  228. private function detectOrders()
  229. {
  230. /*
  231. * 因为订单在15分钟未支付则会被自动关闭
  232. * 当有赞没有正常推送消息或者其他原因导致用户已付款但是订单不生效从而导致用户无法正常加流量、置状态
  233. * 故需要每分钟请求一次未支付订单,审计一下其支付状态
  234. */
  235. $paymentList = Payment::query()->with(['order', 'user'])->where('status', 0)->get();
  236. if (!$paymentList->isEmpty()) {
  237. foreach ($paymentList as $payment) {
  238. // 跳过order丢失的订单
  239. if (!isset($payment->order)) {
  240. continue;
  241. }
  242. $yzy = new yzy();
  243. $trade = $yzy->getTradeByQrId($payment->qr_id);
  244. if ($trade['response']['total_results']) {
  245. // 再判断一遍当前要操作的订单的状态是否被改变了(可能请求延迟的时候已经回调处理完了)
  246. $payment = Payment::query()->where('id', $payment->id)->first();
  247. if ($payment->status != '0') {
  248. continue;
  249. }
  250. // 处理订单
  251. DB::beginTransaction();
  252. try {
  253. // 如果支付单中没有用户信息则创建一个用户
  254. if (!$payment->user_id) {
  255. // 生成一个可用端口
  256. $port = self::$systemConfig['is_rand_port'] ? Helpers::getRandPort() : Helpers::getOnlyPort();
  257. $user = new User();
  258. $user->username = '自动生成-' . $payment->order->email;
  259. $user->password = md5(makeRandStr());
  260. $user->port = $port;
  261. $user->passwd = makeRandStr();
  262. $user->enable = 1;
  263. $user->method = Helpers::getDefaultMethod();
  264. $user->protocol = Helpers::getDefaultProtocol();
  265. $user->obfs = Helpers::getDefaultObfs();
  266. $user->usage = 1;
  267. $user->transfer_enable = toGB(1000);
  268. $user->enable_time = date('Y-m-d');
  269. $user->expire_time = date('Y-m-d', strtotime("+" . $payment->order->goods->days . " days"));
  270. $user->reg_ip = getClientIp();
  271. $user->referral_uid = 0;
  272. $user->traffic_reset_day = 0;
  273. $user->status = 1;
  274. $user->save();
  275. if ($user->id) {
  276. Order::query()->where('oid', $payment->oid)->update(['user_id' => $user->id]);
  277. }
  278. }
  279. // 更新支付单
  280. $payment->pay_way = $trade['response']['qr_trades']['pay_type'] == 'WXPAY_BIGUNSIGN' ? 1 : 2; // 1-微信、2-支付宝
  281. $payment->status = 1;
  282. $payment->save();
  283. // 更新订单
  284. $order = Order::query()->with(['user'])->where('oid', $payment->oid)->first();
  285. $order->status = 2;
  286. $order->save();
  287. // 如果买的是套餐,则先将之前购买的所有套餐置都无效,并扣掉之前所有套餐的流量
  288. $goods = Goods::query()->where('id', $order->goods_id)->first();
  289. if ($goods->type == 2) {
  290. $existOrderList = Order::query()
  291. ->with(['goods'])
  292. ->whereHas('goods', function ($q) {
  293. $q->where('type', 2);
  294. })
  295. ->where('user_id', $order->user_id)
  296. ->where('oid', '<>', $order->oid)
  297. ->where('is_expire', 0)
  298. ->where('status', 2)
  299. ->get();
  300. foreach ($existOrderList as $vo) {
  301. Order::query()->where('oid', $vo->oid)->update(['is_expire' => 1]);
  302. User::query()->where('id', $order->user_id)->decrement('transfer_enable', $vo->goods->traffic * 1048576);
  303. }
  304. }
  305. // 把商品的流量加到账号上
  306. User::query()->where('id', $order->user_id)->increment('transfer_enable', $goods->traffic * 1048576);
  307. // 计算账号过期时间
  308. if ($order->user->expire_time < date('Y-m-d', strtotime("+" . $goods->days . " days"))) {
  309. $expireTime = date('Y-m-d', strtotime("+" . $goods->days . " days"));
  310. } else {
  311. $expireTime = $order->user->expire_time;
  312. }
  313. // 套餐就改流量重置日,流量包不改
  314. if ($goods->type == 2) {
  315. if (date('m') == 2 && date('d') == 29) {
  316. $traffic_reset_day = 28;
  317. } else {
  318. $traffic_reset_day = date('d') == 31 ? 30 : abs(date('d'));
  319. }
  320. User::query()->where('id', $order->user_id)->update(['traffic_reset_day' => $traffic_reset_day, 'expire_time' => $expireTime, 'enable' => 1]);
  321. } else {
  322. User::query()->where('id', $order->user_id)->update(['expire_time' => $expireTime, 'enable' => 1]);
  323. }
  324. // 写入用户标签
  325. if ($goods->label) {
  326. // 用户默认标签
  327. $defaultLabels = [];
  328. if (self::$systemConfig['initial_labels_for_user']) {
  329. $defaultLabels = explode(',', self::$systemConfig['initial_labels_for_user']);
  330. }
  331. // 取出现有的标签
  332. $userLabels = UserLabel::query()->where('user_id', $order->user_id)->pluck('label_id')->toArray();
  333. $goodsLabels = GoodsLabel::query()->where('goods_id', $order->goods_id)->pluck('label_id')->toArray();
  334. // 标签去重
  335. $newUserLabels = array_values(array_unique(array_merge($userLabels, $goodsLabels, $defaultLabels)));
  336. // 删除用户所有标签
  337. UserLabel::query()->where('user_id', $order->user_id)->delete();
  338. // 生成标签
  339. foreach ($newUserLabels as $vo) {
  340. $obj = new UserLabel();
  341. $obj->user_id = $order->user_id;
  342. $obj->label_id = $vo;
  343. $obj->save();
  344. }
  345. }
  346. // 写入返利日志
  347. if ($order->user->referral_uid) {
  348. $this->addReferralLog($order->user_id, $order->user->referral_uid, $order->oid, $order->amount, $order->amount * self::$systemConfig['referral_percent']);
  349. }
  350. // 取消重复返利
  351. User::query()->where('id', $order->user_id)->update(['referral_uid' => 0]);
  352. DB::commit();
  353. } catch (\Exception $e) {
  354. DB::rollBack();
  355. Log::info('【有赞云】审计订单时更新支付单和订单异常:' . $e->getMessage());
  356. }
  357. }
  358. }
  359. }
  360. }
  361. // 关闭超时未支付订单
  362. private function closeOrders()
  363. {
  364. // 关闭超时未支付的有赞云订单(有赞云收款二维码超过30分钟自动关闭,关闭后无法再支付,所以我们限制15分钟内必须付款)
  365. $paymentList = Payment::query()->with(['order', 'order.coupon'])->where('status', 0)->where('created_at', '<=', date("Y-m-d H:i:s", strtotime("-15 minutes")))->get();
  366. if (!$paymentList->isEmpty()) {
  367. DB::beginTransaction();
  368. try {
  369. foreach ($paymentList as $payment) {
  370. // 关闭支付单
  371. Payment::query()->where('id', $payment->id)->update(['status' => -1]);
  372. // 关闭订单
  373. Order::query()->where('oid', $payment->oid)->update(['status' => -1]);
  374. // 退回优惠券
  375. if ($payment->order->coupon_id) {
  376. Coupon::query()->where('id', $payment->order->coupon_id)->update(['status' => 0]);
  377. $this->addCouponLog($payment->order->coupon_id, $payment->order->goods_id, $payment->oid, '订单超时未支付,自动退回');
  378. }
  379. }
  380. DB::commit();
  381. } catch (\Exception $e) {
  382. Log::info('【异常】自动关闭超时未支付订单:' . $e);
  383. DB::rollBack();
  384. }
  385. }
  386. }
  387. /**
  388. * 添加用户封禁日志
  389. *
  390. * @param int $userId 用户ID
  391. * @param int $minutes 封禁时长,单位分钟
  392. * @param string $desc 封禁理由
  393. */
  394. private function addUserBanLog($userId, $minutes, $desc)
  395. {
  396. $log = new UserBanLog();
  397. $log->user_id = $userId;
  398. $log->minutes = $minutes;
  399. $log->desc = $desc;
  400. $log->save();
  401. }
  402. /**
  403. * 添加优惠券操作日志
  404. *
  405. * @param int $couponId 优惠券ID
  406. * @param int $goodsId 商品ID
  407. * @param int $orderId 订单ID
  408. * @param string $desc 备注
  409. */
  410. private function addCouponLog($couponId, $goodsId, $orderId, $desc = '')
  411. {
  412. $couponLog = new CouponLog();
  413. $couponLog->coupon_id = $couponId;
  414. $couponLog->goods_id = $goodsId;
  415. $couponLog->order_id = $orderId;
  416. $couponLog->desc = $desc;
  417. $couponLog->save();
  418. }
  419. /**
  420. * 添加返利日志
  421. *
  422. * @param int $userId 用户ID
  423. * @param int $refUserId 返利用户ID
  424. * @param int $oid 订单ID
  425. * @param int $amount 发生金额
  426. * @param int $refAmount 返利金额
  427. *
  428. * @return int
  429. */
  430. public function addReferralLog($userId, $refUserId, $oid, $amount, $refAmount)
  431. {
  432. $log = new ReferralLog();
  433. $log->user_id = $userId;
  434. $log->ref_user_id = $refUserId;
  435. $log->order_id = $oid;
  436. $log->amount = $amount;
  437. $log->ref_amount = $refAmount;
  438. $log->status = 0;
  439. return $log->save();
  440. }
  441. }