UserController.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Components\Helpers;
  4. use App\Components\PushNotification;
  5. use App\Mail\newTicket;
  6. use App\Mail\replyTicket;
  7. use App\Models\Article;
  8. use App\Models\Coupon;
  9. use App\Models\Goods;
  10. use App\Models\Invite;
  11. use App\Models\Node;
  12. use App\Models\NodeHeartBeat;
  13. use App\Models\NodePing;
  14. use App\Models\Order;
  15. use App\Models\Ticket;
  16. use App\Models\TicketReply;
  17. use App\Models\User;
  18. use App\Models\UserHourlyDataFlow;
  19. use App\Models\UserLoginLog;
  20. use App\Models\UserSubscribe;
  21. use App\Services\UserService;
  22. use Auth;
  23. use Cache;
  24. use DB;
  25. use Exception;
  26. use Hash;
  27. use Illuminate\Http\JsonResponse;
  28. use Illuminate\Http\Request;
  29. use Illuminate\Validation\Rule;
  30. use Log;
  31. use Mail;
  32. use Redirect;
  33. use Response;
  34. use Session;
  35. use Str;
  36. use Validator;
  37. /**
  38. * 用户控制器
  39. *
  40. * Class UserController
  41. *
  42. * @package App\Http\Controllers
  43. */
  44. class UserController extends Controller
  45. {
  46. public function index()
  47. {
  48. $user = Auth::getUser();
  49. $totalTransfer = $user->transfer_enable;
  50. $usedTransfer = $user->u + $user->d;
  51. $unusedTransfer = $totalTransfer - $usedTransfer > 0 ? $totalTransfer - $usedTransfer : 0;
  52. $expireTime = $user->expired_at;
  53. $view['remainDays'] = $expireTime < date(
  54. 'Y-m-d'
  55. ) ? -1 : Helpers::daysToNow($expireTime);
  56. $view['resetDays'] = $user->reset_time ? Helpers::daysToNow(
  57. $user->reset_time
  58. ) : 0;
  59. $view['unusedTransfer'] = $unusedTransfer;
  60. $view['expireTime'] = $expireTime;
  61. $view['banedTime'] = $user->ban_time ? date(
  62. 'Y-m-d H:i:s',
  63. $user->ban_time
  64. ) : 0;
  65. $view['unusedPercent'] = $totalTransfer > 0 ? round(
  66. $unusedTransfer / $totalTransfer,
  67. 2
  68. ) : 0;
  69. $view['noticeList'] = Article::type(2)->latest()->Paginate(1); // 公告
  70. //流量异常判断
  71. $hourlyTraffic = UserHourlyDataFlow::userRecentUsed(
  72. $user->id
  73. )->sum(
  74. 'total'
  75. );
  76. $view['isTrafficWarning'] = $hourlyTraffic >= (sysConfig(
  77. 'traffic_ban_value'
  78. ) * GB) ?: 0;
  79. //付费用户判断
  80. $view['not_paying_user'] = Order::uid()->active()->where(
  81. 'origin_amount',
  82. '>',
  83. 0
  84. )->doesntExist();
  85. $view['userLoginLog'] = UserLoginLog::whereUserId($user->id)->latest(
  86. )->first(); // 近期登录日志
  87. $view = array_merge(
  88. $view,
  89. $this->dataFlowChart($user->id)
  90. );
  91. return view('user.index', $view);
  92. }
  93. // 签到
  94. public function checkIn(): JsonResponse
  95. {
  96. $user = Auth::getUser();
  97. // 系统开启登录加积分功能才可以签到
  98. if ( ! sysConfig('is_checkin')) {
  99. return Response::json(
  100. ['status' => 'fail', 'message' => '系统未开启签到功能']
  101. );
  102. }
  103. // 已签到过,验证是否有效
  104. if (Cache::has('userCheckIn_' . $user->id)) {
  105. return Response::json(
  106. ['status' => 'fail', 'message' => '已经签到过了,明天再来吧']
  107. );
  108. }
  109. $traffic = random_int(
  110. (int)sysConfig('min_rand_traffic'),
  111. (int)sysConfig('max_rand_traffic')
  112. ) * MB;
  113. if ( ! (new UserService())->incrementData($traffic)) {
  114. return Response::json(
  115. ['status' => 'fail', 'message' => '签到失败,系统异常']
  116. );
  117. }
  118. // 写入用户流量变动记录
  119. Helpers::addUserTrafficModifyLog(
  120. $user->id,
  121. 0,
  122. $user->transfer_enable,
  123. $user->transfer_enable + $traffic,
  124. '[签到]'
  125. );
  126. // 多久后可以再签到
  127. $ttl = sysConfig('traffic_limit_time') ? sysConfig(
  128. 'traffic_limit_time'
  129. ) * Minute : Day;
  130. Cache::put('userCheckIn_' . $user->id, '1', $ttl);
  131. return Response::json(
  132. [
  133. 'status' => 'success',
  134. 'message' => '签到成功,系统送您 ' . flowAutoShow($traffic) . '流量',
  135. ]
  136. );
  137. }
  138. // 节点列表
  139. public function nodeList(Request $request)
  140. {
  141. $user = Auth::getUser();
  142. if ($request->isMethod('POST')) {
  143. $infoType = $request->input('type');
  144. $node = Node::find($request->input('id'));
  145. // 生成节点信息
  146. if ($node->type == 1) {
  147. $proxyType = $node->compatible ? 'SS' : 'SSR';
  148. } else {
  149. $proxyType = 'V2Ray';
  150. }
  151. $data = $this->getUserNodeInfo(
  152. $user->id,
  153. $node->id,
  154. $infoType !== 'text' ? 0 : 1
  155. );
  156. return Response::json(
  157. ['status' => 'success', 'data' => $data, 'title' => $proxyType]
  158. );
  159. }
  160. // 获取当前用户可用节点
  161. $nodeList = $user->userAccessNodes()
  162. ->with(['labels', 'level_table'])
  163. ->get();
  164. $view['nodesGeo'] = $nodeList->pluck('name', 'geo')->toArray();
  165. $onlineNode = NodeHeartBeat::recently()->distinct()->pluck(
  166. 'node_id'
  167. )->toArray();
  168. $pingNodeLogs = NodePing::whereMonth('created_at', date('m'))->get(
  169. ['node_id', 'ct', 'cu', 'cm', 'hk']
  170. );
  171. foreach ($nodeList as $node) {
  172. $data = $pingNodeLogs->where('node_id', $node->id);
  173. $node->ct = round($data->pluck('ct')->filter()->avg(), 2);
  174. $node->cu = round($data->pluck('cu')->filter()->avg(), 2);
  175. $node->cm = round($data->pluck('cm')->filter()->avg(), 2);
  176. $node->hk = round($data->pluck('hk')->filter()->avg(), 2);
  177. // 节点在线状态
  178. $node->offline = ! in_array($node->id, $onlineNode);
  179. }
  180. $view['nodeList'] = $nodeList ?: [];
  181. return view('user.nodeList', $view);
  182. }
  183. // 公告详情
  184. public function article(Request $request)
  185. {
  186. $view['info'] = Article::findOrFail($request->input('id'));
  187. return view('user.article', $view);
  188. }
  189. // 修改个人资料
  190. public function profile(Request $request)
  191. {
  192. $user = Auth::getUser();
  193. if ($request->isMethod('POST')) {
  194. $old_password = $request->input('old_password');
  195. $new_password = $request->input('new_password');
  196. $username = $request->input('username');
  197. $wechat = $request->input('wechat');
  198. $qq = $request->input('qq');
  199. $passwd = $request->input('passwd');
  200. // 修改密码
  201. if ($old_password && $new_password) {
  202. if ( ! Hash::check($old_password, $user->password)) {
  203. return Redirect::to('profile#tab_1')->withErrors(
  204. '旧密码错误,请重新输入'
  205. );
  206. }
  207. if (Hash::check($new_password, $user->password)) {
  208. return Redirect::to('profile#tab_1')->withErrors(
  209. '新密码不可与旧密码一样,请重新输入'
  210. );
  211. }
  212. // 演示环境禁止改管理员密码
  213. if ($user->id === 1 && env('APP_DEMO')) {
  214. return Redirect::to('profile#tab_1')->withErrors(
  215. '演示环境禁止修改管理员密码'
  216. );
  217. }
  218. if ( ! $user->update(
  219. ['password' => Hash::make($new_password)]
  220. )) {
  221. return Redirect::to('profile#tab_1')->withErrors('修改失败');
  222. }
  223. return Redirect::to('profile#tab_1')->with(
  224. 'successMsg',
  225. '修改成功'
  226. );
  227. // 修改代理密码
  228. }
  229. if ($passwd) {
  230. if ( ! $user->update(['passwd' => $passwd])) {
  231. return Redirect::to('profile#tab_3')->withErrors('修改失败');
  232. }
  233. return Redirect::to('profile#tab_3')->with(
  234. 'successMsg',
  235. '修改成功'
  236. );
  237. }
  238. // 修改联系方式
  239. if (empty($username)) {
  240. return Redirect::to('profile#tab_2')->withErrors(
  241. '修改失败,昵称不能为空值'
  242. );
  243. }
  244. if ( ! $user->update(
  245. ['username' => $username, 'wechat' => $wechat, 'qq' => $qq]
  246. )) {
  247. return Redirect::to('profile#tab_2')->withErrors('修改失败');
  248. }
  249. return Redirect::to('profile#tab_2')->with('successMsg', '修改成功');
  250. }
  251. return view('user.profile');
  252. }
  253. // 商品列表
  254. public function services(Request $request)
  255. {
  256. $user = Auth::getUser();
  257. // 余额充值商品,只取10个
  258. $view['chargeGoodsList'] = Goods::type(3)->whereStatus(1)->orderBy(
  259. 'price'
  260. )->limit(10)->get();
  261. $view['goodsList'] = Goods::whereStatus(1)
  262. ->where('type', '<=', '2')
  263. ->orderByDesc('type')
  264. ->orderByDesc('sort')
  265. ->paginate(10)
  266. ->appends($request->except('page'));
  267. $renewOrder = Order::userActivePlan($user->id)->first();
  268. $renewPrice = $renewOrder ? $renewOrder->goods : 0;
  269. $view['renewTraffic'] = $renewPrice ? $renewPrice->renew : 0;
  270. // 有重置日时按照重置日为标准,否者就以过期日为标准
  271. $dataPlusDays = $user->reset_time ?: $user->expired_at;
  272. $view['dataPlusDays'] = $dataPlusDays > date(
  273. 'Y-m-d'
  274. ) ? Helpers::daysToNow($dataPlusDays) : 0;
  275. return view('user.services', $view);
  276. }
  277. //重置流量
  278. public function resetUserTraffic(): ?JsonResponse
  279. {
  280. $user = Auth::getUser();
  281. $order = Order::userActivePlan()->first();
  282. $renewCost = $order->goods->renew;
  283. if ($user->credit < $renewCost) {
  284. return Response::json(
  285. ['status' => 'fail', 'message' => '余额不足,请充值余额']
  286. );
  287. }
  288. $user->update(['u' => 0, 'd' => 0]);
  289. // 扣余额
  290. (new UserService($user))->updateCredit(-$renewCost);
  291. // 记录余额操作日志
  292. Helpers::addUserCreditLog(
  293. $user->id,
  294. '',
  295. $user->credit,
  296. $user->credit - $renewCost,
  297. -1 * $renewCost,
  298. '用户自行重置流量'
  299. );
  300. return Response::json(['status' => 'success', 'message' => '重置成功']);
  301. }
  302. // 工单
  303. public function ticketList(Request $request)
  304. {
  305. $view['ticketList'] = Ticket::uid()->latest()->paginate(10)->appends(
  306. $request->except('page')
  307. );
  308. return view('user.ticketList', $view);
  309. }
  310. // 订单
  311. public function invoices(Request $request)
  312. {
  313. $view['orderList'] = Order::uid()
  314. ->with(['goods', 'payment'])
  315. ->orderByDesc('id')
  316. ->paginate(10)
  317. ->appends($request->except('page'));
  318. $view['prepaidPlan'] = Order::userPrepay()->exists();
  319. return view('user.invoices', $view);
  320. }
  321. public function closePlan(): JsonResponse
  322. {
  323. $activePlan = Order::userActivePlan()->first();
  324. $activePlan->is_expire = 1;
  325. if ($activePlan->save()) {
  326. // 关闭先前套餐后,新套餐自动运行
  327. if (Order::userActivePlan()->exists()) {
  328. return Response::json(
  329. ['status' => 'success', 'message' => '激活成功']
  330. );
  331. }
  332. return Response::json(['status' => 'success', 'message' => '关闭']);
  333. }
  334. return Response::json(['status' => 'fail', 'message' => '关闭失败']);
  335. }
  336. // 订单明细
  337. public function invoiceDetail($sn)
  338. {
  339. $view['order'] = Order::uid()
  340. ->with(['goods', 'coupon', 'payment'])
  341. ->whereOrderSn($sn)
  342. ->firstOrFail();
  343. return view('user.invoiceDetail', $view);
  344. }
  345. // 添加工单
  346. public function createTicket(Request $request): ?JsonResponse
  347. {
  348. $user = Auth::getUser();
  349. $title = $request->input('title');
  350. $content = clean($request->input('content'));
  351. $content = str_replace(["atob", "eval"], "", $content);
  352. if (empty($title) || empty($content)) {
  353. return Response::json(
  354. ['status' => 'fail', 'message' => '请输入标题和内容']
  355. );
  356. }
  357. $obj = new Ticket();
  358. $obj->user_id = $user->id;
  359. $obj->title = $title;
  360. $obj->content = $content;
  361. $obj->status = 0;
  362. $obj->save();
  363. if ($obj->id) {
  364. $emailTitle = "新工单提醒";
  365. $content = "标题:【" . $title . "】<br>用户:" . $user->email . "<br>内容:" . $content;
  366. // 发邮件通知管理员
  367. if (sysConfig('webmaster_email')) {
  368. $logId = Helpers::addNotificationLog(
  369. $emailTitle,
  370. $content,
  371. 1,
  372. sysConfig(
  373. 'webmaster_email'
  374. )
  375. );
  376. Mail::to(sysConfig('webmaster_email'))->send(
  377. new newTicket($logId, $emailTitle, $content)
  378. );
  379. }
  380. PushNotification::send($emailTitle, $content);
  381. return Response::json(['status' => 'success', 'message' => '提交成功']);
  382. }
  383. return Response::json(['status' => 'fail', 'message' => '提交失败']);
  384. }
  385. // 回复工单
  386. public function replyTicket(Request $request)
  387. {
  388. $id = $request->input('id');
  389. $ticket = Ticket::uid()->with('user')->whereId($id)->firstOrFail();
  390. if ($request->isMethod('POST')) {
  391. $content = clean($request->input('content'));
  392. $content = str_replace(["atob", "eval"], "", $content);
  393. $content = substr($content, 0, 300);
  394. if (empty($content)) {
  395. return Response::json(
  396. ['status' => 'fail', 'message' => '回复内容不能为空']
  397. );
  398. }
  399. if ($ticket->status == 2) {
  400. return Response::json(
  401. ['status' => 'fail', 'message' => '错误:该工单已关闭']
  402. );
  403. }
  404. $obj = new TicketReply();
  405. $obj->ticket_id = $id;
  406. $obj->user_id = Auth::id();
  407. $obj->content = $content;
  408. $obj->save();
  409. if ($obj->id) {
  410. // 重新打开工单
  411. $ticket->status = 0;
  412. $ticket->save();
  413. $title = "工单回复提醒";
  414. $content = "标题:【" . $ticket->title . "】<br>用户回复:" . $content;
  415. // 发邮件通知管理员
  416. if (sysConfig('webmaster_email')) {
  417. $logId = Helpers::addNotificationLog(
  418. $title,
  419. $content,
  420. 1,
  421. sysConfig(
  422. 'webmaster_email'
  423. )
  424. );
  425. Mail::to(sysConfig('webmaster_email'))->send(
  426. new replyTicket($logId, $title, $content)
  427. );
  428. }
  429. PushNotification::send($title, $content);
  430. return Response::json(
  431. ['status' => 'success', 'message' => '回复成功']
  432. );
  433. }
  434. return Response::json(['status' => 'fail', 'message' => '回复失败']);
  435. }
  436. $view['ticket'] = $ticket;
  437. $view['replyList'] = TicketReply::whereTicketId($id)
  438. ->with('user')
  439. ->oldest()
  440. ->get();
  441. return view('user.replyTicket', $view);
  442. }
  443. // 关闭工单
  444. public function closeTicket(Request $request): ?JsonResponse
  445. {
  446. $id = $request->input('id');
  447. $ret = Ticket::uid()->whereId($id)->update(['status' => 2]);
  448. if ($ret) {
  449. PushNotification::send('工单关闭提醒', '工单:ID' . $id . '用户已手动关闭');
  450. return Response::json(['status' => 'success', 'message' => '关闭成功']);
  451. }
  452. return Response::json(['status' => 'fail', 'message' => '关闭失败']);
  453. }
  454. // 邀请码
  455. public function invite()
  456. {
  457. if (Order::uid()->active()->where('origin_amount', '>', 0)->doesntExist(
  458. )) {
  459. return Response::view(
  460. 'auth.error',
  461. ['message' => '本功能对非付费用户禁用!请 <a class="btn btn-sm btn-danger" href="/">返 回</a>'],
  462. 402
  463. );
  464. }
  465. $view['num'] = Auth::getUser()->invite_num; // 还可以生成的邀请码数量
  466. $view['inviteList'] = Invite::uid()
  467. ->with(['invitee', 'inviter'])
  468. ->paginate(10); // 邀请码列表
  469. $view['referral_traffic'] = flowAutoShow(
  470. sysConfig('referral_traffic') * MB
  471. );
  472. $view['referral_percent'] = sysConfig('referral_percent');
  473. return view('user.invite', $view);
  474. }
  475. // 生成邀请码
  476. public function makeInvite(): JsonResponse
  477. {
  478. $user = Auth::getUser();
  479. if ($user->invite_num <= 0) {
  480. return Response::json(
  481. ['status' => 'fail', 'message' => '生成失败:已无邀请码生成名额']
  482. );
  483. }
  484. $obj = new Invite();
  485. $obj->inviter_id = $user->id;
  486. $obj->invitee_id = 0;
  487. $obj->code = strtoupper(
  488. mb_substr(md5(microtime() . Str::random()), 8, 12)
  489. );
  490. $obj->status = 0;
  491. $obj->dateline = date(
  492. 'Y-m-d H:i:s',
  493. strtotime(
  494. "+" . sysConfig(
  495. 'user_invite_days'
  496. ) . " days"
  497. )
  498. );
  499. $obj->save();
  500. User::uid()->decrement('invite_num', 1);
  501. return Response::json(['status' => 'success', 'message' => '生成成功']);
  502. }
  503. // 使用优惠券
  504. public function redeemCoupon(Request $request): JsonResponse
  505. {
  506. $coupon_sn = $request->input('coupon_sn');
  507. $good_price = $request->input('price');
  508. if (empty($coupon_sn)) {
  509. return Response::json(
  510. [
  511. 'status' => 'fail',
  512. 'title' => '使用失败',
  513. 'message' => '请输入您的优惠劵!',
  514. ]
  515. );
  516. }
  517. $coupon = Coupon::whereSn($coupon_sn)->whereIn('type', [1, 2])->first();
  518. if ( ! $coupon) {
  519. return Response::json(
  520. [
  521. 'status' => 'fail',
  522. 'title' => '优惠券不存在',
  523. 'message' => '请确认优惠券是否输入正确!',
  524. ]
  525. );
  526. }
  527. if ($coupon->status == 1) {
  528. return Response::json(
  529. ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已被使用!']
  530. );
  531. }
  532. if ($coupon->status == 2) {
  533. return Response::json(
  534. ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']
  535. );
  536. }
  537. if ($coupon->end_time < time()) {
  538. $coupon->status = 2;
  539. $coupon->save();
  540. return Response::json(
  541. ['status' => 'fail', 'title' => '抱歉', 'message' => '优惠券已失效!']
  542. );
  543. }
  544. if ($coupon->start_time > time()) {
  545. return Response::json(
  546. [
  547. 'status' => 'fail',
  548. 'title' => '优惠券尚未生效',
  549. 'message' => '请等待活动正式开启',
  550. ]
  551. );
  552. }
  553. if ($good_price < $coupon->rule) {
  554. return Response::json(
  555. [
  556. 'status' => 'fail',
  557. 'title' => '使用条件未满足',
  558. 'message' => '请购买价格更高的套餐',
  559. ]
  560. );
  561. }
  562. $data = [
  563. 'name' => $coupon->name,
  564. 'type' => $coupon->type,
  565. 'value' => $coupon->value,
  566. ];
  567. return Response::json(
  568. ['status' => 'success', 'data' => $data, 'message' => '优惠券有效']
  569. );
  570. }
  571. // 购买服务
  572. public function buy($goods_id)
  573. {
  574. $user = Auth::getUser();
  575. $goods = Goods::whereId($goods_id)->whereStatus(1)->first();
  576. if (empty($goods)) {
  577. return Redirect::to('services');
  578. }
  579. // 有重置日时按照重置日为标准,否者就以过期日为标准
  580. $dataPlusDays = $user->reset_time ?: $user->expired_at;
  581. $view['dataPlusDays'] = $dataPlusDays > date(
  582. 'Y-m-d'
  583. ) ? Helpers::daysToNow($dataPlusDays) : 0;
  584. $view['activePlan'] = Order::userActivePlan()->exists();
  585. $view['goods'] = $goods;
  586. return view('user.buy', $view);
  587. }
  588. // 帮助中心
  589. public function help()
  590. {
  591. //$view['articleList'] = Article::type(1)->orderByDesc('sort')->latest()->limit(10)->paginate(5);
  592. $data = [];
  593. if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
  594. $data[] = 'ss';
  595. //array_push
  596. }
  597. if (Node::whereType(2)->whereStatus(1)->exists()) {
  598. $data[] = 'v2';
  599. }
  600. if (Node::whereType(3)->whereStatus(1)->exists()) {
  601. $data[] = 'trojan';
  602. }
  603. $view['sub'] = $data;
  604. //付费用户判断
  605. $view['not_paying_user'] = Order::uid()->active()->where(
  606. 'origin_amount',
  607. '>',
  608. 0
  609. )->doesntExist();
  610. //客户端安装
  611. $view['Shadowrocket_install'] = 'itms-services://?action=download-manifest&url=' . sysConfig(
  612. 'website_url'
  613. ) . '/clients/Shadowrocket.plist';
  614. $view['Quantumult_install'] = 'itms-services://?action=download-manifest&url=' . sysConfig(
  615. 'website_url'
  616. ) . '/clients/Quantumult.plist';
  617. // 订阅连接
  618. $subscribe = UserSubscribe::whereUserId(
  619. Auth::id()
  620. )
  621. ->firstOrFail();
  622. $view['subscribe_status'] = $subscribe->status;
  623. $subscribe_link = (sysConfig(
  624. 'subscribe_domain'
  625. ) ?: sysConfig(
  626. 'website_url'
  627. )) . '/s/' . $subscribe->code;
  628. $view['link'] = $subscribe_link;
  629. $view['subscribe_link'] = 'sub://' . base64url_encode(
  630. $subscribe_link
  631. );
  632. $view['Shadowrocket_link'] = 'shadowrocket://add/sub://' . base64url_encode(
  633. $subscribe_link
  634. ) . '?remarks=' . (sysConfig('website_name') . '-' . sysConfig(
  635. 'website_url'
  636. ));
  637. $view['Shadowrocket_linkQrcode'] = 'sub://' . base64url_encode(
  638. $subscribe_link
  639. ) . '#' . base64url_encode(sysConfig('website_name'));
  640. $view['Quantumult_linkOut'] = 'quantumult://configuration?server=' . base64url_encode(
  641. $subscribe_link
  642. ) . '&filter=' . base64url_encode(
  643. 'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Pro.conf'
  644. ) . '&rejection=' . base64url_encode(
  645. 'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf'
  646. );
  647. $view['Quantumult_linkIn'] = 'quantumult://configuration?server=' . base64url_encode(
  648. $subscribe_link
  649. ) . '&filter=' . base64url_encode(
  650. 'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/BacktoCN.conf'
  651. ) . '&rejection=' . base64url_encode(
  652. 'https://raw.githubusercontent.com/ZBrettonYe/VPN-Rules-Collection/master/Profiles/Quantumult/Rejection.conf'
  653. );
  654. return view('user.help', $view);
  655. }
  656. // 更换订阅地址
  657. public function exchangeSubscribe(): ?JsonResponse
  658. {
  659. try {
  660. DB::beginTransaction();
  661. // 更换订阅码
  662. Auth::getUser()->subscribe->update(
  663. ['code' => Helpers::makeSubscribeCode()]
  664. );
  665. // 更换连接密码
  666. Auth::getUser()->update(['passwd' => Str::random()]);
  667. DB::commit();
  668. return Response::json(['status' => 'success', 'message' => '更换成功']);
  669. } catch (Exception $e) {
  670. DB::rollBack();
  671. Log::error("更换订阅地址异常:" . $e->getMessage());
  672. return Response::json(
  673. ['status' => 'fail', 'message' => '更换失败' . $e->getMessage()]
  674. );
  675. }
  676. }
  677. // 转换成管理员的身份
  678. public function switchToAdmin(): JsonResponse
  679. {
  680. if ( ! Session::has('admin')) {
  681. return Response::json(['status' => 'fail', 'message' => '非法请求']);
  682. }
  683. // 管理员信息重新写入user
  684. $user = Auth::loginUsingId(Session::get('admin'));
  685. Session::forget('admin');
  686. if ($user) {
  687. return Response::json(
  688. ['status' => 'success', 'message' => "身份切换成功"]
  689. );
  690. }
  691. return Response::json(['status' => 'fail', 'message' => '身份切换失败']);
  692. }
  693. // Todo 卡券余额合并至CouponService
  694. public function charge(Request $request): ?JsonResponse
  695. {
  696. $validator = Validator::make(
  697. $request->all(),
  698. [
  699. 'coupon_sn' => [
  700. 'required',
  701. Rule::exists('coupon', 'sn')->where(
  702. static function ($query) {
  703. $query->whereType(3)->whereStatus(0);
  704. }
  705. ),
  706. ],
  707. ],
  708. ['coupon_sn.required' => '券码不能为空', 'coupon_sn.exists' => '该券不可用']
  709. );
  710. if ($validator->fails()) {
  711. return Response::json(
  712. [
  713. 'status' => 'fail',
  714. 'message' => $validator->getMessageBag()->first(),
  715. ]
  716. );
  717. }
  718. $coupon = Coupon::whereSn($request->input('coupon_sn'))->firstOrFail();
  719. try {
  720. DB::beginTransaction();
  721. // 写入日志
  722. $user = Auth::getUser();
  723. Helpers::addUserCreditLog(
  724. $user->id,
  725. 0,
  726. $user->credit,
  727. $user->credit + $coupon->value,
  728. $coupon->value,
  729. '用户手动充值 - [充值券:' . $request->input('coupon_sn') . ']'
  730. );
  731. // 余额充值
  732. (new UserService($user))->updateCredit($coupon->value);
  733. // 更改卡券状态
  734. Coupon::find($coupon->id)->update(['status' => 1]);
  735. // 写入卡券日志
  736. Helpers::addCouponLog('账户余额充值使用', $coupon->id);
  737. DB::commit();
  738. return Response::json(['status' => 'success', 'message' => '充值成功']);
  739. } catch (Exception $e) {
  740. Log::error('卡劵充值错误:' . $e->getMessage());
  741. DB::rollBack();
  742. return Response::json(['status' => 'fail', 'message' => '充值失败']);
  743. }
  744. }
  745. }