count( ); // 有效用户数 $view['activeUserCount'] = User::where('t', '>=', $past)->count( ); // 活跃用户数 $view['unActiveUserCount'] = User::whereEnable(1)->whereBetween( 't', [ 1, $past, ] )->count(); // 不活跃用户数 $view['onlineUserCount'] = User::where( 't', '>=', strtotime("-10 minutes") )->count(); // 10分钟内在线用户数 $view['expireWarningUserCount'] = User::whereBetween( 'expired_at', [date('Y-m-d'), strtotime("+" . sysConfig('expire_days') . " days")] )->count(); // 临近过期用户数 $view['largeTrafficUserCount'] = User::whereRaw( '(u + d) >= 107374182400' ) ->where('status', '<>', -1) ->count(); // 流量超过100G的用户 $view['flowAbnormalUserCount'] = count( $this->trafficAbnormal() );// 1小时内流量异常用户 $view['nodeCount'] = Node::count(); $view['unnormalNodeCount'] = Node::whereStatus(0)->count(); $view['flowCount'] = flowAutoShow( NodeDailyDataFlow::where( 'created_at', '>=', date('Y-m-d', strtotime("-30 days")) )->sum('total') ); $view['totalFlowCount'] = flowAutoShow( NodeDailyDataFlow::sum('total') ); $view['totalCredit'] = User::where('credit', '<>', 0)->sum( 'credit' ) / 100; $view['totalWaitRefAmount'] = ReferralLog::whereIn('status', [0, 1]) ->sum('commission') / 100; $view['totalRefAmount'] = ReferralApply::whereStatus(2)->sum( 'amount' ) / 100; $view['totalOrder'] = Order::count(); $view['totalOnlinePayOrder'] = Order::where('pay_type', '<>', 0) ->count(); $view['totalSuccessOrder'] = Order::whereStatus(2)->count(); $view['todaySuccessOrder'] = Order::whereStatus(2)->whereDate( 'created_at', date('Y-m-d') )->count(); // 今日 $view['todayRegister'] = User::whereDate('created_at', date('Y-m-d')) ->count(); return view('admin.index', $view); } // 1小时内流量异常用户 private function trafficAbnormal(): array { $result = []; $userTotalTrafficList = UserHourlyDataFlow::whereNodeId(0) ->where('total', '>', MB * 50) ->where( 'created_at', '>=', date( 'Y-m-d H:i:s', time() - 3900 ) ) ->groupBy('user_id') ->selectRaw( "user_id, sum(total) as totalTraffic" ) ->get(); // 只统计50M以上的记录,加快速度 foreach ($userTotalTrafficList as $user) { if ($user->totalTraffic > sysConfig('traffic_ban_value') * GB) { $result[] = $user->user_id; } } return $result; } // 用户列表 public function userList(Request $request) { $id = $request->input('id'); $email = $request->input('email'); $wechat = $request->input('wechat'); $qq = $request->input('qq'); $port = $request->input('port'); $status = $request->input('status'); $enable = $request->input('enable'); $online = $request->input('online'); $flowAbnormal = $request->input('flowAbnormal'); $expireWarning = $request->input('expireWarning'); $largeTraffic = $request->input('largeTraffic'); $query = User::with('subscribe'); if (isset($id)) { $query->whereId($id); } if (isset($email)) { $query->where('email', 'like', '%' . $email . '%'); } if (isset($wechat)) { $query->where('wechat', 'like', '%' . $wechat . '%'); } if (isset($qq)) { $query->where('qq', 'like', '%' . $qq . '%'); } if (isset($port)) { $query->wherePort($port); } if (isset($status)) { $query->whereStatus($status); } if (isset($enable)) { $query->whereEnable($enable); } // 流量超过100G的 if ($largeTraffic) { $query->whereIn('status', [0, 1])->whereRaw( '(u + d) >= 107374182400' ); } // 临近过期提醒 if ($expireWarning) { $query->whereBetween( 'expired_at', [ date('Y-m-d'), date( 'Y-m-d', strtotime("+" . sysConfig('expire_days') . " days") ), ] ); } // 当前在线 if ($online) { $query->where('t', '>=', strtotime("-10 minutes")); } // 不活跃用户 if ($request->input('unActive')) { $query->whereBetween( 't', [ 1, strtotime( "-" . sysConfig( 'expire_days' ) . " days" ), ] )->whereEnable(1); } // 1小时内流量异常用户 if ($flowAbnormal) { $query->whereIn('id', $this->trafficAbnormal()); } $userList = $query->latest()->paginate(15)->appends( $request->except('page') ); foreach ($userList as $user) { $user->transfer_enable = flowAutoShow($user->transfer_enable); $user->used_flow = flowAutoShow($user->u + $user->d); if ($user->expired_at < date('Y-m-d')) { $user->expireWarning = -1; // 已过期 } elseif ($user->expired_at == date('Y-m-d')) { $user->expireWarning = 0; // 今天过期 } elseif ($user->expired_at > date( 'Y-m-d' ) && $user->expired_at <= date( 'Y-m-d', strtotime("+30 days") )) { $user->expireWarning = 1; // 最近一个月过期 } else { $user->expireWarning = 2; // 大于一个月过期 } // 流量异常警告 $totalTraffic = UserHourlyDataFlow::userRecentUsed( $user->id )->sum( 'total' ); $user->trafficWarning = $totalTraffic > (sysConfig( 'traffic_ban_value' ) * GB) ? 1 : 0; // 订阅地址 $user->link = (sysConfig('subscribe_domain') ?: sysConfig( 'website_url' )) . '/s/' . $user->subscribe->code; } $view['userList'] = $userList; return view('admin.user.userList', $view); } // 添加账号 public function addUser(Request $request) { if ($request->isMethod('POST')) { // 校验email是否已存在 $exists = User::whereEmail($request->input('email'))->first(); if ($exists) { return Response::json( ['status' => 'fail', 'message' => '用户名已存在,请重新输入'] ); } $user = new User(); $user->username = $request->input('username'); $user->email = $request->input('email'); $user->password = Hash::make( $request->input('password') ?: Str::random() ); $user->port = $request->input('port') ?: $this->makePort( ); $user->passwd = $request->input('passwd') ?: Str::random(); $user->vmess_id = $request->input('uuid') ?: Str::uuid(); $user->transfer_enable = toGB( $request->input('transfer_enable') ?: 0 ); $user->enable = $request->input('enable') ?: 0; $user->method = $request->input('method'); $user->protocol = $request->input('protocol'); $user->obfs = $request->input('obfs'); $user->speed_limit = $request->input('speed_limit') * Mbps; $user->wechat = $request->input('wechat'); $user->qq = $request->input('qq'); $user->expired_at = $request->input('expired_at') ?: date( 'Y-m-d', strtotime("+365 days") ); $user->remark = str_replace( ["atob", "eval"], "", $request->input('remark') ); $user->level = $request->input('level') ?: 0; $user->group_id = $request->input('group_id') ?: 0; $user->reg_ip = getClientIp(); $user->reset_time = $request->input('reset_time') > date( 'Y-m-d' ) ? $request->input('reset_time') : null; $user->invite_num = $request->input('invite_num') ?: 0; $user->status = $request->input('status') ?: 0; $user->save(); if ($user->id) { // 写入用户流量变动记录 Helpers::addUserTrafficModifyLog( $user->id, 0, 0, toGB($request->input('transfer_enable', 0)), '后台手动添加用户' ); return Response::json( ['status' => 'success', 'message' => '添加成功'] ); } return Response::json(['status' => 'fail', 'message' => '添加失败']); } // 生成一个可用端口 $view['methodList'] = Helpers::methodList(); $view['protocolList'] = Helpers::protocolList(); $view['obfsList'] = Helpers::obfsList(); $view['levelList'] = Level::orderBy('level')->get(); $view['groupList'] = UserGroup::orderBy('id')->get(); return view('admin.user.userInfo', $view); } // 生成端口 public function makePort(): int { return Helpers::getPort(); } // 批量生成账号 public function batchAddUsers(Request $request): ?JsonResponse { $amount = $request->input('amount'); try { DB::beginTransaction(); for ($i = 0; $i < $amount; $i++) { $uid = Helpers::addUser( Str::random(8) . '@auto.generate', Hash::make(Str::random()), toGB(1024), 365 ); // 生成一个可用端口 if ($uid) { // 写入用户流量变动记录 Helpers::addUserTrafficModifyLog( $uid, 0, 0, toGB(1024), '后台批量生成用户' ); } } DB::commit(); return Response::json( ['status' => 'success', 'message' => '批量生成账号成功'] ); } catch (Exception $e) { DB::rollBack(); return Response::json( [ 'status' => 'fail', 'message' => '批量生成账号失败:' . $e->getMessage(), ] ); } } // 编辑账号 public function editUser(Request $request, $id) { $user = User::find($id); if ($request->isMethod('POST')) { $email = $request->input('email'); $password = $request->input('password'); $port = $request->input('port'); $transfer_enable = $request->input('transfer_enable'); $is_admin = $request->input('is_admin'); $status = $request->input('status'); // 校验email是否已存在 if (User::where('id', '<>', $id)->whereEmail($email)->exists()) { return Response::json( ['status' => 'fail', 'message' => '用户名已存在,请重新输入'] ); } // 校验端口是否已存在 if (User::where('id', '<>', $id)->where('port', '>', 0)->wherePort( $port )->exists()) { return Response::json( ['status' => 'fail', 'message' => '端口已存在,请重新输入'] ); } // 禁止取消默认管理员 if ($id == 1 && $is_admin == 0) { return Response::json( ['status' => 'fail', 'message' => '系统默认管理员不可取消'] ); } try { DB::beginTransaction(); $data = [ 'username' => $request->input('username'), 'email' => $email, 'port' => $port, 'passwd' => $request->input( 'passwd' ) ?: Str::random(), 'vmess_id' => $request->input('uuid') ?: Str::uuid(), 'transfer_enable' => toGB($transfer_enable ?: 0), 'enable' => $status < 0 ? 0 : $request->input( 'enable' ), 'method' => $request->input('method'), 'protocol' => $request->input('protocol'), 'obfs' => $request->input('obfs'), 'speed_limit' => $request->input('speed_limit') * Mbps, 'wechat' => $request->input('wechat'), 'qq' => $request->input('qq'), 'expired_at' => $request->input('expired_at') ?: date( 'Y-m-d', strtotime("+365 days") ), 'remark' => str_replace( "eval", "", str_replace( "atob", "", $request->input( 'remark' ) ) ), 'level' => $request->input('level'), 'group_id' => $request->input('group_id'), 'reset_time' => $request->input('reset_time'), 'invite_num' => $request->input('invite_num'), 'status' => $status, ]; // 只有admin才有权限操作管理员属性 if (Auth::getUser()->is_admin == 1) { $data['is_admin'] = (int)$is_admin; } // 非演示环境才可以修改管理员密码 if ( ! empty($password) && ! (config('app.demo') && $id == 1)) { $data['password'] = Hash::make($password); } $user->update($data); // 写入用户流量变动记录 if ($user->transfer_enable != toGB($transfer_enable)) { Helpers::addUserTrafficModifyLog( $id, 0, $user->transfer_enable, toGB($transfer_enable), '后台手动编辑用户' ); } DB::commit(); return Response::json( ['status' => 'success', 'message' => '编辑成功'] ); } catch (Exception $e) { DB::rollBack(); Log::error('编辑用户信息异常:' . $e->getMessage()); return Response::json( ['status' => 'fail', 'message' => '编辑失败'] ); } } else { if ($user) { $user->transfer_enable = flowToGB($user->transfer_enable); } $view['user'] = $user->load('inviter:id,email'); $view['methodList'] = Helpers::methodList(); $view['protocolList'] = Helpers::protocolList(); $view['obfsList'] = Helpers::obfsList(); $view['levelList'] = Level::orderBy('level')->get(); $view['groupList'] = UserGroup::orderBy('id')->get(); return view('admin.user.userInfo', $view); } } // 删除用户 public function delUser(Request $request): ?JsonResponse { $id = $request->input('id'); if ($id <= 1) { return Response::json( ['status' => 'fail', 'message' => '系统管理员不可删除'] ); } try { DB::beginTransaction(); User::find($id)->delete(); DB::commit(); return Response::json(['status' => 'success', 'message' => '删除成功']); } catch (Exception $e) { Log::error('删除用户信息异常:' . $e->getMessage()); DB::rollBack(); return Response::json(['status' => 'fail', 'message' => '删除失败']); } } // 文章列表 public function articleList(Request $request) { $view['list'] = Article::orderByDesc('sort')->paginate(15)->appends( $request->except('page') ); return view('admin.article.articleList', $view); } // 添加文章 public function addArticle(Request $request) { if ($request->isMethod('POST')) { $article = new Article(); $article->title = $request->input('title'); $article->type = $request->input('type', 1); $article->summary = $request->input('summary'); // LOGO if ($article->type == 4) { $article->logo = $request->input('logo'); } else { $logo = ''; if ($request->hasFile('logo')) { $logo = $this->uploadFile($request->file('logo')); if ( ! $logo) { Session::flash('errorMsg', 'LOGO不合法'); return Redirect::back()->withInput(); } } $article = new Article(); $article->title = $request->input('title'); $article->type = $request->input('type', 1); $article->summary = $request->input('summary'); $article->logo = $logo; } $article->content = $request->input('content'); $article->sort = $request->input('sort', 0); $article->save(); if ($article->id) { Session::flash('successMsg', '添加成功'); } else { Session::flash('errorMsg', '添加失败'); } return Redirect::to('admin/articleList'); } return view('admin.article.addArticle'); } // 编辑文章 public function editArticle(Request $request) { $id = $request->input('id'); if ($request->isMethod('POST')) { $title = $request->input('title'); $type = $request->input('type'); $summary = $request->input('summary'); $content = $request->input('content'); $sort = $request->input('sort'); // 商品LOGO if ($type == 4) { $logo = $request->input('logo'); } else { $logo = ''; if ($request->hasFile('logo')) { $logo = $this->uploadFile($request->file('logo')); if ( ! $logo) { Session::flash('errorMsg', 'LOGO不合法'); return Redirect::back()->withInput(); } } } $data = [ 'type' => $type, 'title' => $title, 'summary' => $summary, 'content' => $content, 'sort' => $sort, ]; if ($logo) { $data['logo'] = $logo; } $ret = Article::whereId($id)->update($data); if ($ret) { Session::flash('successMsg', '编辑成功'); } else { Session::flash('errorMsg', '编辑失败'); } return Redirect::to('admin/editArticle?id=' . $id); } $view['article'] = Article::find($id); return view('admin.article.editArticle', $view); } // 删除文章 public function delArticle(Request $request): ?JsonResponse { $id = $request->input('id'); $ret = Article::whereId($id)->delete(); if ($ret) { return Response::json(['status' => 'success', 'message' => '删除成功']); } return Response::json(['status' => 'fail', 'message' => '删除失败']); } // 流量日志 public function trafficLog(Request $request) { $port = $request->input('port'); $user_id = $request->input('user_id'); $email = $request->input('email'); $nodeId = $request->input('nodeId'); $startTime = $request->input('startTime'); $endTime = $request->input('endTime'); $query = UserDataFlowLog::with(['user', 'node']); if (isset($port)) { $query->whereHas( 'user', static function ($q) use ($port) { $q->wherePort($port); } ); } if (isset($user_id)) { $query->whereUserId($user_id); } if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } if (isset($nodeId)) { $query->whereNodeId($nodeId); } if (isset($startTime)) { $query->where('log_time', '>=', strtotime($startTime)); } if (isset($endTime)) { $query->where('log_time', '<=', strtotime($endTime)); } // 已使用流量 $view['totalTraffic'] = flowAutoShow( $query->sum('u') + $query->sum('d') ); $list = $query->latest('log_time')->paginate(20)->appends( $request->except('page') ); foreach ($list as $vo) { $vo->u = flowAutoShow($vo->u); $vo->d = flowAutoShow($vo->d); $vo->log_time = date('Y-m-d H:i:s', $vo->log_time); } $view['list'] = $list; $view['nodeList'] = Node::whereStatus(1) ->orderByDesc('sort') ->latest() ->get(); return view('admin.logs.trafficLog', $view); } // 导出配置信息 public function export(Request $request, $id) { if (empty($id)) { return Redirect::to('admin/userList'); } $user = User::find($id); if (empty($user)) { return Redirect::to('admin/userList'); } if ($request->isMethod('POST')) { $infoType = $request->input('type'); $node = Node::find($request->input('id')); if ($node->type == 1) { if ($node->compatible) { $proxyType = 'SS'; } else { $proxyType = 'SSR'; } } else { $proxyType = 'V2Ray'; } $data = $this->getUserNodeInfo( $id, $node->id, $infoType !== 'text' ? 0 : 1 ); return Response::json( ['status' => 'success', 'data' => $data, 'title' => $proxyType] ); } $view['nodeList'] = Node::whereStatus(1) ->orderByDesc('sort') ->orderBy('id') ->paginate(15) ->appends($request->except('page')); $view['user'] = $user; return view('admin.user.export', $view); } // 导出原版SS用户配置信息 public function exportSSJson() { $userList = User::where('port', '>', 0)->get(); $json = ''; if ( ! $userList->isEmpty()) { $userPassword = []; foreach ($userList as $user) { $userPassword[] = $user->port . ":" . $user->passwd; } $json = json_encode( [ 'server' => '0.0.0.0', 'local_address' => '127.0.0.1', 'local_port' => 1080, 'port_password' => $userPassword, 'timeout' => 300, 'method' => Helpers::getDefaultMethod(), 'fast_open' => false, ] ); } // 生成JSON文件 $fileName = Str::random() . '_shadowsocks.json'; $filePath = public_path('downloads/' . $fileName); file_put_contents($filePath, $json); if ( ! is_file($filePath)) { exit('文件生成失败,请检查目录权限'); } return Response::download($filePath); } // 修改个人资料 public function profile(Request $request) { if ($request->isMethod('POST')) { $new_password = $request->input('new_password'); if ( ! Hash::check( $request->input('old_password'), Auth::getUser()->password )) { return Redirect::back()->withErrors('旧密码错误,请重新输入'); } if (Hash::check($new_password, Auth::getUser()->password)) { return Redirect::back()->withErrors('新密码不可与旧密码一样,请重新输入'); } $ret = Auth::getUser()->update( ['password' => Hash::make($new_password)] ); if ( ! $ret) { return Redirect::back()->withErrors('修改失败'); } return Redirect::back()->with('successMsg', '修改成功'); } return view('admin.config.profile'); } // 用户流量监控 public function userMonitor(Request $request) { $id = $request->input('id'); if (empty($id)) { return Redirect::to('admin/userList'); } $user = User::find($id); if (empty($user)) { return Redirect::to('admin/userList'); } $view['email'] = $user->email; $view = array_merge($view, $this->dataFlowChart($user->id)); return view('admin.logs.userMonitor', $view); } // 加密方式、混淆、协议、等级、国家地区 public function config(Request $request) { if ($request->isMethod('POST')) { $name = $request->input('name'); $type = $request->input( 'type', 1 ); // 类型:1-加密方式(method)、2-协议(protocol)、3-混淆(obfs) $is_default = $request->input('is_default', 0); $sort = $request->input('sort', 0); if (empty($name)) { return Response::json( ['status' => 'fail', 'message' => '配置名称不能为空'] ); } // 校验是否已存在 $config = SsConfig::type($type)->whereName($name)->first(); if ($config) { return Response::json( ['status' => 'fail', 'message' => '配置已经存在,请勿重复添加'] ); } $ssConfig = new SsConfig(); $ssConfig->name = $name; $ssConfig->type = $type; $ssConfig->is_default = $is_default; $ssConfig->sort = $sort; $ssConfig->save(); return Response::json(['status' => 'success', 'message' => '添加成功']); } $view['methodList'] = SsConfig::type(1)->get(); $view['protocolList'] = SsConfig::type(2)->get(); $view['obfsList'] = SsConfig::type(3)->get(); $view['countryList'] = Country::all(); $view['levelList'] = Level::all(); $view['labelList'] = Label::with('nodes')->get(); return view('admin.config.config', $view); } // 删除配置 public function delConfig(Request $request): ?JsonResponse { $id = $request->input('id'); $ret = SsConfig::whereId($id)->delete(); if ($ret) { return Response::json(['status' => 'success', 'message' => '删除成功']); } return Response::json(['status' => 'fail', 'message' => '删除失败']); } // 设置默认配置 public function setDefaultConfig(Request $request): JsonResponse { $id = $request->input('id'); if (empty($id)) { return Response::json(['status' => 'fail', 'message' => '非法请求']); } $config = SsConfig::find($id); if ( ! $config) { return Response::json(['status' => 'fail', 'message' => '配置不存在']); } // 去除该配置所属类型的默认值 SsConfig::default()->type($config->type)->update(['is_default' => 0]); // 将该ID对应记录值置为默认值 SsConfig::whereId($id)->update(['is_default' => 1]); return Response::json(['status' => 'success', 'message' => '操作成功']); } // 设置系统扩展信息,例如客服、统计代码 public function setExtend(Request $request): ?RedirectResponse { $websiteAnalytics = $request->input('website_analytics'); $websiteCustomerService = $request->input('website_customer_service'); try { DB::beginTransaction(); // 首页LOGO if ($request->hasFile('website_home_logo')) { $ret = $this->uploadFile($request->file('website_home_logo')); if ( ! $ret) { Session::flash('errorMsg', 'LOGO不合法'); return Redirect::back(); } Config::find('website_home_logo')->update(['value' => $ret]); } // 站内LOGO if ($request->hasFile('website_logo')) { $ret = $this->uploadFile($request->file('website_logo')); if ( ! $ret) { Session::flash('errorMsg', 'LOGO不合法'); return Redirect::back(); } Config::find('website_logo')->update(['value' => $ret]); } Config::find('website_analytics')->update( ['value' => $websiteAnalytics] ); Config::find('website_customer_service')->update( ['value' => $websiteCustomerService] ); Session::flash('successMsg', '更新成功'); DB::commit(); return Redirect::back(); } catch (Exception $e) { DB::rollBack(); Session::flash('errorMsg', '更新失败'); return Redirect::back(); } } // 添加等级 public function addLevel(Request $request): ?JsonResponse { $validator = Validator::make( $request->all(), [ 'level' => 'required|numeric|unique:level,level', 'level_name' => 'required', ] ); if ($validator->fails()) { return Response::json( ['status' => 'fail', 'message' => $validator->errors()->all()] ); } $obj = new Level(); $obj->level = $request->input('level'); $obj->name = $request->input('level_name'); $obj->save(); if ($obj->id) { return Response::json(['status' => 'success', 'message' => '提交成功']); } return Response::json(['status' => 'fail', 'message' => '操作失败']); } // 编辑等级 public function updateLevel(Request $request): JsonResponse { $id = $request->input('id'); $level = $request->input('level'); $validator = Validator::make( $request->all(), [ 'id' => 'required|numeric', 'level' => 'required|numeric', 'level_name' => 'required', ] ); if ($validator->fails()) { return Response::json( ['status' => 'fail', 'message' => $validator->errors()->all()] ); } // 校验该等级下是否存在关联账号 $levelCheck = Level::where('id', '<>', $id)->whereLevel($level)->exists( ); if ($levelCheck) { return Response::json(['status' => 'fail', 'message' => '该等级已存在!']); } // 校验该等级下是否存在关联账号 $userCount = User::whereLevel($level)->count(); if ($userCount) { return Response::json( ['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联!'] ); } Level::whereId($id)->update( ['level' => $level, 'name' => $request->input('level_name')] ); return Response::json(['status' => 'success', 'message' => '操作成功']); } // 删除等级 public function delLevel(Request $request): ?JsonResponse { $id = $request->input('id'); $validator = Validator::make( $request->all(), [ 'id' => 'required|numeric|exists:level,id', ] ); if ($validator->fails()) { return Response::json( ['status' => 'fail', 'message' => $validator->errors()->all()] ); } $level = Level::find($id); // 校验该等级下是否存在关联账号 $userCount = User::whereLevel($level->level)->count(); if ($userCount) { return Response::json( ['status' => 'fail', 'message' => '该等级下存在关联账号,请先取消关联'] ); } $ret = false; try { $ret = Level::whereId($id)->delete(); } catch (Exception $e) { Log::error('删除等级时报错:' . $e->getMessage()); } if ($ret) { return Response::json(['status' => 'success', 'message' => '操作成功']); } return Response::json(['status' => 'fail', 'message' => '操作失败']); } // 添加国家/地区 public function addCountry(Request $request): JsonResponse { $code = $request->input('code'); $name = $request->input('name'); if (empty($code)) { return Response::json( ['status' => 'fail', 'message' => '国家/地区代码不能为空'] ); } if (empty($name)) { return Response::json( ['status' => 'fail', 'message' => '国家/地区名称不能为空'] ); } $exists = Country::find($code); if ($exists) { return Response::json( ['status' => 'fail', 'message' => '该国家/地区名称已存在,请勿重复添加'] ); } $obj = new Country(); $obj->code = $code; $obj->name = $name; if ($obj->save()) { return Response::json(['status' => 'success', 'message' => '提交成功']); } return Response::json(['status' => 'fail', 'message' => '操作失败']); } // 编辑国家/地区 public function updateCountry(Request $request): JsonResponse { $code = $request->input('code'); $name = $request->input('name'); if (empty($name)) { return Response::json( ['status' => 'fail', 'message' => '国家/地区名称不能为空'] ); } if (empty($code)) { return Response::json( ['status' => 'fail', 'message' => '国家/地区代码不能为空'] ); } $country = Country::find($code); if ( ! $country) { return Response::json( ['status' => 'fail', 'message' => '国家/地区不存在'] ); } // 校验该国家/地区下是否存在关联节点 if (Node::whereCountryCode($country->code)->exists()) { return Response::json( ['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联'] ); } $ret = $country->update(['name' => $name, 'code' => $code]); if ($ret) { return Response::json(['status' => 'success', 'message' => '操作成功']); } return Response::json(['status' => 'fail', 'message' => '操作失败']); } // 删除国家/地区 public function delCountry(Request $request): ?JsonResponse { $code = $request->input('code'); if (empty($id)) { return Response::json(['status' => 'fail', 'message' => 'ID不能为空']); } $country = Country::find($code); if ( ! $country) { return Response::json( ['status' => 'fail', 'message' => '国家/地区不存在'] ); } // 校验该国家/地区下是否存在关联节点 if (Node::whereCountryCode($country->code)->exists()) { return Response::json( ['status' => 'fail', 'message' => '该国家/地区下存在关联节点,请先取消关联'] ); } if ($country->delete()) { return Response::json(['status' => 'success', 'message' => '操作成功']); } return Response::json(['status' => 'fail', 'message' => '操作失败']); } // 系统设置 public function system() { $view = Config::pluck('value', 'name')->toArray(); $view['labelList'] = Label::orderByDesc('sort')->orderBy('id')->get(); return view('admin.config.system', $view); } // 设置某个配置项 public function setConfig(Request $request): JsonResponse { $name = $request->input('name'); $value = $request->input('value'); if ( ! $name) { return Response::json( ['status' => 'fail', 'message' => '设置失败:请求参数异常'] ); } // 屏蔽异常配置 if ( ! in_array($name, Config::pluck('name')->toArray())) { return Response::json( ['status' => 'fail', 'message' => '设置失败:配置不存在'] ); } // 如果开启用户邮件重置密码,则先设置网站名称和网址 if ($value !== '0' && in_array( $name, [ 'is_reset_password', 'is_activate_account', 'expire_warning', 'traffic_warning', ], true )) { $config = Config::find('website_name'); if ( ! $config->value) { return Response::json( ['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站名称】'] ); } $config = Config::find('website_url'); if ( ! $config->value) { return Response::json( ['status' => 'fail', 'message' => '设置失败:启用该配置需要先设置【网站地址】'] ); } } // 支付设置判断 if ($value !== null && in_array( $name, [ 'is_AliPay', 'is_QQPay', 'is_WeChatPay', 'is_otherPay', ], true )) { switch ($value) { case 'f2fpay': if ( ! sysConfig('f2fpay_app_id') || ! sysConfig( 'f2fpay_private_key' ) || ! sysConfig('f2fpay_public_key')) { return Response::json( [ 'status' => 'fail', 'message' => '请先设置【支付宝F2F】必要参数', ] ); } break; case 'codepay': if ( ! sysConfig('codepay_url') || ! sysConfig( 'codepay_id' ) || ! sysConfig('codepay_key')) { return Response::json( ['status' => 'fail', 'message' => '请先设置【码支付】必要参数'] ); } break; case 'epay': if ( ! sysConfig('epay_url') || ! sysConfig( 'epay_mch_id' ) || ! sysConfig('epay_key')) { return Response::json( ['status' => 'fail', 'message' => '请先设置【易支付】必要参数'] ); } break; case 'payjs': if ( ! sysConfig('payjs_mch_id') || ! sysConfig( 'payjs_key' )) { return Response::json( ['status' => 'fail', 'message' => '请先设置【PayJs】必要参数'] ); } break; case 'bitpayx': if ( ! sysConfig('bitpay_secret')) { return Response::json( ['status' => 'fail', 'message' => '请先设置【麻瓜宝】必要参数'] ); } break; case 'paypal': if ( ! sysConfig('paypal_username') || ! sysConfig( 'paypal_password' ) || ! sysConfig('paypal_secret')) { return Response::json( [ 'status' => 'fail', 'message' => '请先设置【PayPal】必要参数', ] ); } break; default: return Response::json( ['status' => 'fail', 'message' => '未知支付渠道'] ); } } // 演示环境禁止修改特定配置项 if (env('APP_DEMO')) { $denyConfig = [ 'website_url', 'min_rand_traffic', 'max_rand_traffic', 'push_bear_send_key', 'push_bear_qrcode', 'is_forbid_china', 'website_security_code', ]; if (in_array($name, $denyConfig, true)) { return Response::json( ['status' => 'fail', 'message' => '演示环境禁止修改该配置'] ); } } // 如果是返利比例,则需要除100 if ($name === 'referral_percent') { $value = (int)$value / 100; } // 更新配置 Config::find($name)->update(['value' => $value]); return Response::json(['status' => 'success', 'message' => '操作成功']); } // 推送通知测试 public function sendTestNotification(): JsonResponse { if (sysConfig('is_notification')) { $result = PushNotification::send('这是测试的标题', 'ProxyPanel测试内容'); if ($result === false) { return Response::json( ['status' => 'fail', 'message' => '发送失败,请重新尝试!'] ); } switch (sysConfig('is_notification')) { case 'serverChan': if ( ! $result['errno']) { return Response::json( [ 'status' => 'success', 'message' => '发送成功,请查看手机是否收到推送消息', ] ); } return Response::json( [ 'status' => 'fail', 'message' => $result ? $result['errmsg'] : '未知', ] ); case 'bark': if ($result['code'] == 200) { return Response::json( [ 'status' => 'success', 'message' => '发送成功,请查看手机是否收到推送消息', ] ); } return Response::json( ['status' => 'fail', 'message' => $result['message']] ); default: } } return Response::json( ['status' => 'fail', 'message' => '请先选择【日志通知】渠道'] ); } // 邀请码列表 public function inviteList(Request $request) { $view['inviteList'] = Invite::with( ['invitee:id,email', 'inviter:id,email'] ) ->orderBy('status') ->latest() ->paginate(15) ->appends($request->except('page')); return view('admin.inviteList', $view); } // 生成邀请码 public function makeInvite(): JsonResponse { for ($i = 0; $i < 10; $i++) { $obj = new Invite(); $obj->inviter_id = 0; $obj->invitee_id = 0; $obj->code = strtoupper( substr(md5(microtime() . Str::random(6)), 8, 12) ); $obj->status = 0; $obj->dateline = date( 'Y-m-d H:i:s', strtotime( "+" . sysConfig( 'admin_invite_days' ) . " days" ) ); $obj->save(); } return Response::json(['status' => 'success', 'message' => '生成成功']); } // 导出邀请码 public function exportInvite(): void { $inviteList = Invite::whereStatus(0)->orderBy('id')->get(); $filename = '邀请码' . date('Ymd') . '.xlsx'; $spreadsheet = new Spreadsheet(); $spreadsheet->getProperties() ->setCreator('ProxyPanel') ->setLastModifiedBy('ProxyPanel') ->setTitle('邀请码') ->setSubject('邀请码') ->setDescription('') ->setKeywords('') ->setCategory(''); try { $spreadsheet->setActiveSheetIndex(0); $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('邀请码'); $sheet->fromArray(['邀请码', '有效期'], null); foreach ($inviteList as $k => $vo) { $sheet->fromArray( [$vo->code, $vo->dateline], null, 'A' . ($k + 2) ); } header( 'Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); // 输出07Excel文件 //header('Content-Type:application/vnd.ms-excel'); // 输出Excel03版本文件 header( 'Content-Disposition: attachment;filename="' . $filename . '"' ); header('Cache-Control: max-age=0'); $writer = new Xlsx($spreadsheet); $writer->save('php://output'); } catch (\PhpOffice\PhpSpreadsheet\Exception $e) { Log::error('导出优惠券时报错' . $e->getMessage()); } } // 订单列表 public function orderList(Request $request) { $email = $request->input('email'); $order_sn = $request->input('order_sn'); $is_coupon = $request->input('is_coupon'); $is_expire = $request->input('is_expire'); $pay_way = $request->input('pay_way'); $status = $request->input('status'); $range_time = $request->input('range_time'); $sort = $request->input('sort'); // 0-按创建时间降序、1-按创建时间升序 $order_id = $request->input('id'); $query = Order::with( ['user:id,email', 'goods:id,name', 'coupon:id,name,sn'] ); if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } if (isset($order_sn)) { $query->where('order_sn', 'like', '%' . $order_sn . '%'); } if (isset($is_coupon)) { if ($is_coupon) { $query->where('coupon_id', '<>', 0); } else { $query->whereCouponId(0); } } if (isset($is_expire)) { $query->whereIsExpire($is_expire); } if (isset($pay_way)) { $query->wherePayWay($pay_way); } if (isset($status)) { $query->whereStatus($status); } if (isset($range_time) && $range_time !== ',') { $range_time = explode(',', $range_time); $query->where('created_at', '>=', $range_time[0])->where( 'created_at', '<=', $range_time[1] ); } if (isset($order_id)) { $query->whereId($order_id); } if ($sort) { $query->orderBy('id'); } else { $query->orderByDesc('id'); } $view['orderList'] = $query->paginate(15)->appends( $request->except('page') ); return view('admin.logs.orderList', $view); } // 重置用户流量 public function resetUserTraffic(Request $request): JsonResponse { User::find($request->input('id'))->update(['u' => 0, 'd' => 0]); return Response::json(['status' => 'success', 'message' => '操作成功']); } // 操作用户余额 public function handleUserCredit(Request $request) { if ($request->isMethod('POST')) { $userId = $request->input('user_id'); $amount = $request->input('amount'); if (empty($userId) || empty($amount)) { return Response::json( ['status' => 'fail', 'message' => '充值异常'] ); } $user = User::find($userId); // 写入余额变动日志 Helpers::addUserCreditLog( $userId, 0, $user->credit, $user->credit + $amount, $amount, '后台手动充值' ); // 加减余额 if ((new UserService($user))->updateCredit($amount)) { return Response::json( ['status' => 'success', 'message' => '充值成功'] ); } return Response::json(['status' => 'fail', 'message' => '充值失败']); } return view('admin.handleUserCredit'); } // 用户余额变动记录 public function userCreditLogList(Request $request) { $email = $request->input('email'); $query = UserCreditLog::with('user:id,email')->latest(); if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } $view['list'] = $query->paginate(15)->appends($request->except('page')); return view('admin.logs.userCreditLogList', $view); } // 用户封禁记录 public function userBanLogList(Request $request) { $email = $request->input('email'); $query = UserBanedLog::with('user:id,email,t')->latest(); if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } $view['list'] = $query->paginate(15)->appends($request->except('page')); return view('admin.logs.userBanLogList', $view); } // 用户流量变动记录 public function userTrafficLogList(Request $request) { $email = $request->input('email'); $query = UserDataModifyLog::with( ['user:id,email', 'order.goods:id,name'] ); if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } $view['list'] = $query->latest()->paginate(15)->appends( $request->except('page') ); return view('admin.logs.userTrafficLogList', $view); } // 用户在线IP记录 public function userOnlineIPList(Request $request) { $email = $request->input('email'); $port = $request->input('port'); $wechat = $request->input('wechat'); $qq = $request->input('qq'); $query = User::activeUser(); if (isset($email)) { $query->where('email', 'like', '%' . $email . '%'); } if (isset($wechat)) { $query->where('wechat', 'like', '%' . $wechat . '%'); } if (isset($qq)) { $query->where('qq', 'like', '%' . $qq . '%'); } if (isset($port)) { $query->wherePort($port); } $userList = $query->paginate(15)->appends($request->except('page')); $nodeOnlineIPs = NodeOnlineUserIp::with('node:id,name') ->where( 'created_at', '>=', strtotime("-10 minutes") ) ->latest() ->distinct(); // Todo 优化查询 foreach ($userList as $user) { // 最近5条在线IP记录,如果后端设置为60秒上报一次,则为10分钟内的在线IP $user->onlineIPList = $nodeOnlineIPs->wherePort($user->port)->limit( 5 )->get(); } $view['userList'] = $userList; return view('admin.logs.userOnlineIPList', $view); } // 转换成某个用户的身份 public function switchToUser(Request $request): JsonResponse { $id = $request->input('user_id'); $user = User::find($id); if ( ! $user) { return Response::json(['status' => 'fail', 'message' => "用户不存在"]); } // 存储当前管理员ID,并将当前登录信息改成要切换的用户的身份信息 Session::put('admin', Auth::id()); Auth::login($user); return Response::json(['status' => 'success', 'message' => "身份切换成功"]); } // 添加标签 public function addLabel(Request $request) { if ($request->isMethod('POST')) { $name = $request->input('name'); $sort = $request->input('sort'); $label = new Label(); $label->name = $name; $label->sort = $sort; $label->save(); return Response::json(['status' => 'success', 'message' => '添加成功']); } return view('admin.label.addLabel'); } // 编辑标签 public function editLabel(Request $request) { $id = $request->input('id'); if ($request->isMethod('POST')) { $name = $request->input('name'); $sort = $request->input('sort'); Label::whereId($id)->update(['name' => $name, 'sort' => $sort]); return Response::json(['status' => 'success', 'message' => '添加成功']); } $view['label'] = Label::find($id); return view('admin.label.editLabel', $view); } // 删除标签 public function delLabel(Request $request): ?JsonResponse { $id = $request->input('id'); try { DB::beginTransaction(); Label::whereId($id)->delete(); NodeLabel::whereLabelId($id)->delete(); // 删除节点关联 DB::commit(); return Response::json(['status' => 'success', 'message' => '删除成功']); } catch (Exception $e) { DB::rollBack(); return Response::json( ['status' => 'fail', 'message' => '删除失败:' . $e->getMessage()] ); } } // 邮件发送日志列表 public function notificationLog(Request $request) { $email = $request->input('email'); $type = $request->input('type'); $query = NotificationLog::query(); if (isset($email)) { $query->where('address', 'like', '%' . $email . '%'); } if (isset($type)) { $query->whereType($type); } $view['list'] = $query->latest()->paginate(15)->appends( $request->except('page') ); return view('admin.logs.notificationLog', $view); } // 在线IP监控(实时) public function onlineIPMonitor(Request $request) { $ip = $request->input('ip'); $email = $request->input('email'); $port = $request->input('port'); $nodeId = $request->input('nodeId'); $userId = $request->input('id'); $query = NodeOnlineUserIp::with(['node:id,name', 'user:id,email']) ->where( 'created_at', '>=', strtotime("-2 minutes") ); if (isset($ip)) { $query->whereIp($ip); } if (isset($email)) { $query->whereHas( 'user', static function ($q) use ($email) { $q->where('email', 'like', '%' . $email . '%'); } ); } if (isset($port)) { $query->whereHas( 'user', static function ($q) use ($port) { $q->wherePort($port); } ); } if (isset($nodeId)) { $query->whereHas( 'node', static function ($q) use ($nodeId) { $q->whereId($nodeId); } ); } if (isset($userId)) { $query->whereHas( 'user', static function ($q) use ($userId) { $q->whereId($userId); } ); } $onlineIPLogs = $query->groupBy('user_id', 'node_id') ->latest() ->paginate(20) ->appends($request->except('page')); foreach ($onlineIPLogs as $log) { // 跳过上报多IP的 if ($log->ip == null || strpos($log->ip, ',') !== false) { continue; } $ipInfo = QQWry::ip($log->ip); if (isset($ipInfo['error'])) { // 用IPIP的库再试一下 $ipip = IPIP::ip($log->ip); $ipInfo = [ 'country' => $ipip['country_name'], 'province' => $ipip['region_name'], 'city' => $ipip['city_name'], ]; } $log->ipInfo = $ipInfo['country'] . ' ' . $ipInfo['province'] . ' ' . $ipInfo['city']; } $view['list'] = $onlineIPLogs; $view['nodeList'] = Node::whereStatus(1) ->orderByDesc('sort') ->latest() ->get(); return view('admin.logs.onlineIPMonitor', $view); } }