Revert "fix: resolve PHPStan static analysis warnings"

This reverts commit 2d3e4b4a95.
This commit is contained in:
xboard
2025-04-14 21:23:08 +08:00
parent 2d3e4b4a95
commit db235c10e8
84 changed files with 1190 additions and 2330 deletions

View File

@@ -7,89 +7,227 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use App\Http\Requests\Passport\AuthRegister;
use App\Services\Auth\LoginService;
use App\Services\Auth\MailLinkService;
use App\Services\Auth\RegisterService;
use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Models\Plan;
use App\Models\User;
use App\Services\AuthService;
use App\Utils\CacheKey;
use App\Utils\Dict;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use ReCaptcha\ReCaptcha;
class AuthController extends Controller
{
protected MailLinkService $mailLinkService;
protected RegisterService $registerService;
protected LoginService $loginService;
public function __construct(
MailLinkService $mailLinkService,
RegisterService $registerService,
LoginService $loginService
) {
$this->mailLinkService = $mailLinkService;
$this->registerService = $registerService;
$this->loginService = $loginService;
}
/**
* 通过邮件链接登录
*/
public function loginWithMailLink(Request $request)
{
if (!(int)admin_setting('login_with_mail_link_enable')) {
return $this->fail([404,null]);
}
$params = $request->validate([
'email' => 'required|email:strict',
'redirect' => 'nullable'
]);
[$success, $result] = $this->mailLinkService->handleMailLink(
$params['email'],
$request->input('redirect')
);
if (!$success) {
return $this->fail($result);
if (Cache::get(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']))) {
return $this->fail([429 ,__('Sending frequently, please try again later')]);
}
return $this->success($result);
$user = User::where('email', $params['email'])->first();
if (!$user) {
return $this->success(true);
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 300);
Cache::put(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']), time(), 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$link = admin_setting('app_url') . $redirect;
} else {
$link = url($redirect);
}
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => __('Login to :name', [
'name' => admin_setting('app_name', 'XBoard')
]),
'template_name' => 'login',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'link' => $link,
'url' => admin_setting('app_url')
]
]);
return $this->success($link);
}
/**
* 用户注册
*/
public function register(AuthRegister $request)
{
[$success, $result] = $this->registerService->register($request);
if (!$success) {
return $this->fail($result);
if ((int)admin_setting('register_limit_by_ip_enable', 0)) {
$registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0;
if ((int)$registerCountByIP >= (int)admin_setting('register_limit_count', 3)) {
return $this->fail([429,__('Register frequently, please try again after :minute minute', [
'minute' => admin_setting('register_limit_expire', 60)
])]);
}
}
if ((int)admin_setting('recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(admin_setting('recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
return $this->fail([400,__('Invalid code is incorrect')]);
}
}
if ((int)admin_setting('email_whitelist_enable', 0)) {
if (!Helper::emailSuffixVerify(
$request->input('email'),
admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
) {
return $this->fail([400,__('Email suffix is not in the Whitelist')]);
}
}
if ((int)admin_setting('email_gmail_limit_enable', 0)) {
$prefix = explode('@', $request->input('email'))[0];
if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) {
return $this->fail([400,__('Gmail alias is not supported')]);
}
}
if ((int)admin_setting('stop_register', 0)) {
return $this->fail([400,__('Registration has closed')]);
}
if ((int)admin_setting('invite_force', 0)) {
if (empty($request->input('invite_code'))) {
return $this->fail([422,__('You must use the invitation code to register')]);
}
}
if ((int)admin_setting('email_verify', 0)) {
if (empty($request->input('email_code'))) {
return $this->fail([422,__('Email verification code cannot be empty')]);
}
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
return $this->fail([400,__('Incorrect email verification code')]);
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
return $this->fail([400201,__('Email already exists')]);
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
// TODO 增加过期默认值、流量告急提醒默认值
$user->remind_expire = admin_setting('default_remind_expire',1);
$user->remind_traffic = admin_setting('default_remind_traffic',1);
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)admin_setting('invite_force', 0)) {
return $this->fail([400,__('Invalid invitation code')]);
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)admin_setting('invite_never_expire', 0)) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
$authService = new AuthService($result);
return $this->success($authService->generateAuthData());
// try out
if ((int)admin_setting('try_out_plan_id', 0)) {
$plan = Plan::find(admin_setting('try_out_plan_id'));
if ($plan) {
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (admin_setting('try_out_hour', 1) * 3600);
$user->speed_limit = $plan->speed_limit;
}
}
if (!$user->save()) {
return $this->fail([500,__('Register failed')]);
}
if ((int)admin_setting('email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$user->last_login_at = time();
$user->save();
if ((int)admin_setting('register_limit_by_ip_enable', 0)) {
Cache::put(
CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()),
(int)$registerCountByIP + 1,
(int)admin_setting('register_limit_expire', 60) * 60
);
}
$authService = new AuthService($user);
$data = $authService->generateAuthData();
return $this->success($data);
}
/**
* 用户登录
*/
public function login(AuthLogin $request)
{
$email = $request->input('email');
$password = $request->input('password');
[$success, $result] = $this->loginService->login($email, $password);
if (!$success) {
return $this->fail($result);
if ((int)admin_setting('password_limit_enable', 1)) {
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
if ($passwordErrorCount >= (int)admin_setting('password_limit_count', 5)) {
return $this->fail([429,__('There are too many password errors, please try again after :minute minutes.', [
'minute' => admin_setting('password_limit_expire', 60)
])]);
}
}
$authService = new AuthService($result);
$user = User::where('email', $email)->first();
if (!$user) {
return $this->fail([400, __('Incorrect email or password')]);
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$password,
$user->password)
) {
if ((int)admin_setting('password_limit_enable')) {
Cache::put(
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
(int)$passwordErrorCount + 1,
60 * (int)admin_setting('password_limit_expire', 60)
);
}
return $this->fail([400, __('Incorrect email or password')]);
}
if ($user->banned) {
return $this->fail([400, __('Your account has been suspended')]);
}
$authService = new AuthService($user);
return $this->success($authService->generateAuthData());
}
/**
* 通过token登录
*/
public function token2Login(Request $request)
{
// 处理直接通过token重定向
if ($token = $request->input('token')) {
$redirect = '/#/login?verify=' . $token . '&redirect=' . ($request->input('redirect', 'dashboard'));
@@ -100,9 +238,9 @@ class AuthController extends Controller
);
}
// 处理通过验证码登录
if ($verify = $request->input('verify')) {
$userId = $this->mailLinkService->handleTokenLogin($verify);
$key = CacheKey::get('TEMP_TOKEN', $verify);
$userId = Cache::get($key);
if (!$userId) {
return response()->json([
@@ -110,14 +248,15 @@ class AuthController extends Controller
], 400);
}
$user = \App\Models\User::find($userId);
$user = User::findOrFail($userId);
if (!$user) {
if ($user->banned) {
return response()->json([
'message' => __('User not found')
'message' => __('Your account has been suspended')
], 400);
}
Cache::forget($key);
$authService = new AuthService($user);
return response()->json([
@@ -130,9 +269,6 @@ class AuthController extends Controller
], 400);
}
/**
* 获取快速登录URL
*/
public function getQuickLoginUrl(Request $request)
{
$authorization = $request->input('auth_data') ?? $request->header('authorization');
@@ -151,25 +287,38 @@ class AuthController extends Controller
], 401);
}
$url = $this->mailLinkService->getQuickLoginUrl($user, $request->input('redirect'));
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user['id'], 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$url = admin_setting('app_url') . $redirect;
} else {
$url = url($redirect);
}
return $this->success($url);
}
/**
* 忘记密码处理
*/
public function forget(AuthForget $request)
{
[$success, $result] = $this->loginService->resetPassword(
$request->input('email'),
$request->input('email_code'),
$request->input('password')
);
if (!$success) {
return $this->fail($result);
$forgetRequestLimitKey = CacheKey::get('FORGET_REQUEST_LIMIT', $request->input('email'));
$forgetRequestLimit = (int)Cache::get($forgetRequestLimitKey);
if ($forgetRequestLimit >= 3) return $this->fail([429, __('Reset failed, Please try again later')]);
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
Cache::put($forgetRequestLimitKey, $forgetRequestLimit ? $forgetRequestLimit + 1 : 1, 300);
return $this->fail([400,__('Incorrect email verification code')]);
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
return $this->fail([400,__('This email is not registered in the system')]);
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
return $this->fail([500,__('Reset failed')]);
}
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
return $this->success(true);
}
}

View File

@@ -15,10 +15,14 @@ use ReCaptcha\ReCaptcha;
class CommController extends Controller
{
private function isEmailVerify()
{
return $this->success((int)admin_setting('email_verify', 0) ? 1 : 0);
}
public function sendEmailVerify(CommSendEmailVerify $request)
{
if ((int) admin_setting('recaptcha_enable', 0)) {
if ((int)admin_setting('recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(admin_setting('recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
@@ -59,4 +63,12 @@ class CommController extends Controller
return $this->success(true);
}
private function getEmailSuffix()
{
$suffix = admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
if (!is_array($suffix)) {
return preg_split('/,/', $suffix);
}
return $suffix;
}
}

View File

@@ -150,20 +150,6 @@ class UniProxyController extends Controller
'socks' => [
'server_port' => (int) $serverPort,
],
'naive' => [
'server_port' => (int) $serverPort,
'tls' => (int) $protocolSettings['tls'],
'tls_settings' => $protocolSettings['tls_settings']
],
'http' => [
'server_port' => (int) $serverPort,
'tls' => (int) $protocolSettings['tls'],
'tls_settings' => $protocolSettings['tls_settings']
],
'mieru' => [
'server_port' => (string) $serverPort,
'protocol' => (int) $protocolSettings['protocol'],
],
default => []
};
@@ -177,7 +163,7 @@ class UniProxyController extends Controller
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match', ''), $eTag) !== false) {
if (strpos($request->header('If-None-Match', '') ?? '', $eTag) !== false) {
return response(null, 304);
}
return response($response)->header('ETag', "\"{$eTag}\"");

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\NoticeSave;
use App\Models\Notice;
use Illuminate\Http\Request;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
$data = Notice::orderBy('id', 'DESC')->get();
return $this->success($data);
}
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!$request->input('id')) {
if (!Notice::create($data)) {
return $this->fail([500, '创建失败']);
}
} else {
try {
Notice::find($request->input('id'))->update($data);
} catch (\Exception $e) {
\Log::error($e);
return $this->fail([500, '保存失败']);
}
}
return $this->success(true);
}
public function drop(Request $request)
{
$request->validate([
'id' => 'required'
],[
'id.required' => '公告ID不能为空'
]);
$notice = Notice::find($request->input('id'));
if (!$notice) {
return $this->fail([400202,'公告不存在']);
}
if (!$notice->delete()) {
return $this->fail([500,'公告删除失败']);
}
return $this->success(true);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$counts = User::select(
DB::raw("plan_id"),
DB::raw("count(*) as count")
)
->where('plan_id', '!=', NULL)
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->groupBy("plan_id")
->get();
$plans = Plan::orderBy('sort', 'ASC')->get();
foreach ($plans as $k => $v) {
$plans[$k]->count = 0;
foreach ($counts as $kk => $vv) {
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
}
}
return $this->success($plans);
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Services\TicketService;
use Illuminate\Http\Request;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
return $this->fail([400,'工单不存在']);
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) {
$ticket['message'][$i]['is_me'] = true;
} else {
$ticket['message'][$i]['is_me'] = false;
}
}
return $this->success($ticket);
}
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$model = Ticket::orderBy('created_at', 'DESC');
if ($request->input('status') !== NULL) {
$model->where('status', $request->input('status'));
}
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
public function reply(Request $request)
{
$request->validate([
'id' => 'required',
'message' => 'required|string'
],[
'id.required' => '工单ID不能为空',
'message.required' => '消息不能为空'
]);
$ticketService = new TicketService();
$ticketService->replyByAdmin(
$request->input('id'),
$request->input('message'),
$request->user()->id
);
return $this->success(true);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
return $this->fail([422,'工单ID不能为空']);
}
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
return $this->fail([400202,'工单不存在']);
}
$ticket->status = Ticket::STATUS_CLOSED;
if (!$ticket->save()) {
return $this->fail([500, '工单关闭失败']);
}
return $this->success(true);
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Staff\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
return $this->fail([422,'用户ID不能为空']);
}
$user = User::where('is_admin', 0)
->where('id', $request->input('id'))
->where('is_staff', 0)
->first();
if (!$user) return $this->fail([400202,'用户不存在']);
return $this->success($user);
}
public function update(UserUpdate $request)
{
$params = $request->validated();
$user = User::find($request->input('id'));
if (!$user) {
return $this->fail([400202,'用户不存在']);
}
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
return $this->fail([400201,'邮箱已被使用']);
}
if (isset($params['password'])) {
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$params['password_algo'] = NULL;
} else {
unset($params['password']);
}
if (isset($params['plan_id'])) {
$plan = Plan::find($params['plan_id']);
if (!$plan) {
return $this->fail([400202,'订阅不存在']);
}
$params['group_id'] = $plan->group_id;
}
try {
$user->update($params);
} catch (\Exception $e) {
\Log::error($e);
return $this->fail([500,'更新失败']);
}
return $this->success(true);
}
public function sendMail(UserSendMail $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
$users = $builder->get();
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'url' => admin_setting('app_url'),
'content' => $request->input('content')
]
]);
}
return $this->success(true);
}
public function ban(Request $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
\Log::error($e);
return $this->fail([500,'处理上失败']);
}
return $this->success(true);
}
}

View File

@@ -43,15 +43,16 @@ class OrderController extends Controller
$request->validate([
'trade_no' => 'required|string',
]);
$order = Order::with(['payment','plan'])
$order = Order::with('payment')
->where('user_id', $request->user()->id)
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
return $this->fail([400, __('Order does not exist or has been paid')]);
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int) admin_setting('try_out_plan_id');
if (!$order->plan) {
if (!$order['plan']) {
return $this->fail([400, __('Subscription plan does not exist')]);
}
if ($order->surplus_order_ids) {
@@ -80,7 +81,7 @@ class OrderController extends Controller
// Validate plan purchase
$planService->validatePurchase($user, $request->input('period'));
return DB::transaction(function () use ($request, $plan, $user, $userService) {
return DB::transaction(function () use ($request, $plan, $user, $userService, $planService) {
$period = $request->input('period');
$newPeriod = PlanService::getPeriodKey($period);
@@ -168,13 +169,12 @@ class OrderController extends Controller
]);
}
$payment = Payment::find($method);
if (!$payment || !$payment->enable) {
if (!$payment || $payment->enable !== 1)
return $this->fail([400, __('Payment method is not available')]);
}
$paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
$order->handling_amount = (int) round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
}
$order->payment_id = $method;
if (!$order->save())

View File

@@ -58,13 +58,11 @@ class UserController extends Controller
if (!$user) {
return $this->fail([400, __('The user does not exist')]);
}
if (
!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$request->input('old_password'),
$user->password
)
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$request->input('old_password'),
$user->password)
) {
return $this->fail([400, __('The old password is wrong')]);
}

View File

@@ -64,6 +64,12 @@ class ConfigController extends Controller
return $this->success(true);
}
private function getTemplateContent(string $filename): string
{
$path = resource_path("rules/{$filename}");
return File::exists($path) ? File::get($path) : '';
}
public function fetch(Request $request)
{
$key = $request->input('key');

View File

@@ -15,7 +15,6 @@ use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;
class OrderController extends Controller
{
@@ -28,7 +27,7 @@ class OrderController extends Controller
if ($order->surplus_order_ids) {
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
}
$order['period'] = PlanService::getLegacyPeriod((string) $order->period);
$order['period'] = PlanService::getLegacyPeriod($order->period);
return $this->success($order);
}
@@ -46,21 +45,17 @@ class OrderController extends Controller
$this->applyFiltersAndSorts($request, $orderModel);
/** @var \Illuminate\Pagination\LengthAwarePaginator $paginatedResults */
$paginatedResults = $orderModel
->latest('created_at')
->paginate(
perPage: $pageSize,
page: $current
);
$paginatedResults->getCollection()->transform(function($order) {
$orderArray = $order->toArray();
$orderArray['period'] = PlanService::getLegacyPeriod((string) $order->period);
return $orderArray;
});
return response()->json($paginatedResults);
return response()->json(
$orderModel
->latest('created_at')
->paginate(
perPage: $pageSize,
page: $current
)->through(fn($order) => [
...$order->toArray(),
'period' => PlanService::getLegacyPeriod($order->period)
]),
);
}
private function applyFiltersAndSorts(Request $request, Builder $builder): void
@@ -117,8 +112,8 @@ class OrderController extends Controller
'lte' => '<=',
'like' => 'like',
'notlike' => 'not like',
'null' => static fn($q) => $q->whereNull($field),
'notnull' => static fn($q) => $q->whereNotNull($field),
'null' => static fn($q) => $q->whereNull($queryField),
'notnull' => static fn($q) => $q->whereNotNull($queryField),
default => 'like'
}, match (strtolower($operator)) {
'like', 'notlike' => "%{$filterValue}%",
@@ -189,7 +184,7 @@ class OrderController extends Controller
try {
$order->update($params);
} catch (\Exception $e) {
Log::error($e);
\Log::error($e);
return $this->fail([500, '更新失败']);
}
@@ -220,12 +215,11 @@ class OrderController extends Controller
$orderService = new OrderService($order);
$order->user_id = $user->id;
$order->plan_id = $plan->id;
$period = $request->input('period');
$order->period = (int) PlanService::getPeriodKey((string) $period);
$order->period = PlanService::getPeriodKey($request->input('period'));
$order->trade_no = Helper::guid();
$order->total_amount = $request->input('total_amount');
if (PlanService::getPeriodKey((string) $order->period) === Plan::PERIOD_RESET_TRAFFIC) {
if (PlanService::getPeriodKey($order->period) === Plan::PERIOD_RESET_TRAFFIC) {
$order->type = Order::TYPE_RESET_TRAFFIC;
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
$order->type = Order::TYPE_UPGRADE;

View File

@@ -8,7 +8,6 @@ use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PlanController extends Controller
{
@@ -59,7 +58,7 @@ class PlanController extends Controller
return $this->success(true);
} catch (\Exception $e) {
DB::rollBack();
Log::error($e);
\Log::error($e);
return $this->fail([500, '保存失败']);
}
}
@@ -77,12 +76,12 @@ class PlanController extends Controller
if (User::where('plan_id', $request->input('id'))->first()) {
return $this->fail([400201, '该订阅下存在用户无法删除']);
}
$plan = Plan::find($request->input('id'));
if (!$plan) {
return $this->fail([400202, '该订阅不存在']);
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
return $this->fail([400202, '该订阅不存在']);
}
}
return $this->success($plan->delete());
}
@@ -102,7 +101,7 @@ class PlanController extends Controller
try {
$plan->update($updateData);
} catch (\Exception $e) {
Log::error($e);
\Log::error($e);
return $this->fail([500, '保存失败']);
}
@@ -125,7 +124,7 @@ class PlanController extends Controller
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
Log::error($e);
\Log::error($e);
return $this->fail([500, '保存失败']);
}
return $this->success(true);

View File

@@ -14,15 +14,15 @@ class GroupController extends Controller
{
public function fetch(Request $request): JsonResponse
{
$serverGroups = ServerGroup::query()
->orderByDesc('id')
->withCount('users')
->get();
// 只在需要时手动加载server_count
$serverGroups->each(function ($group) {
$group->setAttribute('server_count', $group->server_count);
});
->get()
->transform(function ($group) {
$group->server_count = $group->servers()->count();
return $group;
});
return $this->success($serverGroups);
}

View File

@@ -10,13 +10,12 @@ use App\Models\ServerGroup;
use App\Services\ServerService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ManageController extends Controller
{
public function getNodes(Request $request)
{
$servers = ServerService::getAllServers()->map(function ($item) {
$servers = collect(ServerService::getAllServers())->map(function ($item) {
$item['groups'] = ServerGroup::whereIn('id', $item['group_ids'])->get(['name', 'id']);
$item['parent'] = $item->parent;
return $item;
@@ -42,7 +41,7 @@ class ManageController extends Controller
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
Log::error($e);
\Log::error($e);
return $this->fail([500, '保存失败']);
}
@@ -61,7 +60,7 @@ class ManageController extends Controller
$server->update($params);
return $this->success(true);
} catch (\Exception $e) {
Log::error($e);
\Log::error($e);
return $this->fail([500, '保存失败']);
}
}
@@ -70,7 +69,7 @@ class ManageController extends Controller
Server::create($params);
return $this->success(true);
} catch (\Exception $e) {
Log::error($e);
\Log::error($e);
return $this->fail([500, '创建失败']);
}
@@ -84,7 +83,7 @@ class ManageController extends Controller
'show' => 'integer',
]);
if (!Server::where('id', $request->id)->update(['show' => $request->show])) {
if (Server::where('id', $request->id)->update(['show' => $request->show]) === false) {
return $this->fail([500, '保存失败']);
}
return $this->success(true);

View File

@@ -25,7 +25,8 @@ class StatController extends Controller
{
// 获取在线节点数
$onlineNodes = Server::all()->filter(function ($server) {
return !!$server->is_online;
$server->loadServerStatus();
return $server->is_online;
})->count();
// 获取在线设备数和在线用户数
$onlineDevices = User::where('t', '>=', time() - 600)
@@ -267,7 +268,8 @@ class StatController extends Controller
// 获取在线节点数
$onlineNodes = Server::all()->filter(function ($server) {
return !!$server->is_online;
$server->loadServerStatus();
return $server->is_online;
})->count();
// 获取在线设备数和在线用户数

View File

@@ -55,10 +55,13 @@ class TicketController extends Controller
if (!$ticket) {
return $this->fail([400202, '工单不存在']);
}
$result = $ticket->toArray();
$result['user'] = UserController::transformUserData($ticket->user);
$ticket->user = UserController::transformUserData($ticket->user);
$ticket->messages->each(function ($message) use ($ticket) {
$message->is_me = $message->user_id !== $ticket->user_id;
return $this->success($result);
});
return $this->success($ticket);
}
/**
@@ -88,16 +91,12 @@ class TicketController extends Controller
perPage: $request->integer('pageSize', 10),
page: $request->integer('current', 1)
);
// 获取items然后映射转换
$items = collect($tickets->items())->map(function ($ticket) {
$ticketData = $ticket->toArray();
$ticketData['user'] = UserController::transformUserData($ticket->user);
return $ticketData;
})->all();
$tickets->getCollection()->transform(function ($ticket) {
$ticket->user = UserController::transformUserData($ticket->user);
return $ticket;
});
return response([
'data' => $items,
'data' => $tickets->items(),
'total' => $tickets->total()
]);
}
@@ -138,19 +137,4 @@ class TicketController extends Controller
return $this->fail([500101, '关闭失败']);
}
}
public function show($ticketId)
{
$ticket = Ticket::with([
'user',
'messages' => function ($query) {
$query->with(['user']); // 如果需要用户信息
}
])->findOrFail($ticketId);
// 自动包含 is_me 属性
return response()->json([
'data' => $ticket
]);
}
}

View File

@@ -81,7 +81,7 @@ class UserController extends Controller
// 处理关联查询
if (str_contains($field, '.')) {
[$relation, $relationField] = explode('.', $field);
$query->whereHas($relation, function ($q) use ($relationField, $value) {
$query->whereHas($relation, function($q) use ($relationField, $value) {
if (is_array($value)) {
$q->whereIn($relationField, $value);
} else if (is_string($value) && str_contains($value, ':')) {
@@ -163,7 +163,7 @@ class UserController extends Controller
$users = $userModel->orderBy('id', 'desc')
->paginate($pageSize, ['*'], 'page', $current);
$users->getCollection()->map(function ($user) {
$users->getCollection()->transform(function ($user) {
return self::transformUserData($user);
});
@@ -177,14 +177,13 @@ class UserController extends Controller
* Transform user data for response
*
* @param User $user
* @return array<string, mixed>
* @return User
*/
public static function transformUserData(User $user): array
public static function transformUserData(User $user): User
{
$user = $user->toArray();
$user['balance'] = $user['balance'] / 100;
$user['commission_balance'] = $user['commission_balance'] / 100;
$user['subscribe_url'] = Helper::getSubscribeUrl($user['token']);
$user->subscribe_url = Helper::getSubscribeUrl($user->token);
$user->balance = $user->balance / 100;
$user->commission_balance = $user->commission_balance / 100;
return $user;
}
@@ -236,7 +235,7 @@ class UserController extends Controller
if (isset($params['banned']) && (int) $params['banned'] === 1) {
$authService = new AuthService($user);
$authService->removeAllSessions();
$authService->removeSession();
}
if (isset($params['balance'])) {
$params['balance'] = $params['balance'] * 100;
@@ -264,7 +263,7 @@ class UserController extends Controller
{
ini_set('memory_limit', '-1');
gc_enable(); // 启用垃圾回收
// 优化查询使用with预加载plan关系避免N+1问题
$query = User::with('plan:id,name')
->orderBy('id', 'asc')
@@ -279,18 +278,18 @@ class UserController extends Controller
'token',
'plan_id'
]);
$this->applyFiltersAndSorts($request, $query);
$filename = 'users_' . date('Y-m-d_His') . '.csv';
return response()->streamDownload(function () use ($query) {
return response()->streamDownload(function() use ($query) {
// 打开输出流
$output = fopen('php://output', 'w');
// 添加BOM标记确保Excel正确显示中文
fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF));
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
// 写入CSV头部
fputcsv($output, [
'邮箱',
@@ -302,9 +301,9 @@ class UserController extends Controller
'订阅计划',
'订阅地址'
]);
// 分批处理数据以减少内存使用
$query->chunk(500, function ($users) use ($output) {
$query->chunk(500, function($users) use ($output) {
foreach ($users as $user) {
try {
$row = [
@@ -326,11 +325,11 @@ class UserController extends Controller
continue; // 继续处理下一条记录
}
}
// 清理内存
gc_collect_cycles();
});
fclose($output);
}, $filename, [
'Content-Type' => 'text/csv; charset=UTF-8',