Xboard/app/Http/Controllers/V2/Admin/UserController.php

495 lines
17 KiB
PHP
Raw Normal View History

2023-11-17 01:44:01 -05:00
<?php
2025-01-21 01:57:54 -05:00
namespace App\Http\Controllers\V2\Admin;
2023-11-17 01:44:01 -05:00
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Models\Plan;
use App\Models\User;
use App\Services\AuthService;
use App\Traits\QueryOperators;
2023-11-17 01:44:01 -05:00
use App\Utils\Helper;
2025-01-21 01:57:54 -05:00
use Illuminate\Database\Eloquent\Builder;
2023-11-17 01:44:01 -05:00
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
2023-11-17 01:44:01 -05:00
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
2023-11-17 01:44:01 -05:00
class UserController extends Controller
{
use QueryOperators;
2023-11-17 01:44:01 -05:00
public function resetSecret(Request $request)
{
$user = User::find($request->input('id'));
2025-01-21 01:57:54 -05:00
if (!$user)
return $this->fail([400202, '用户不存在']);
2023-11-17 01:44:01 -05:00
$user->token = Helper::guid();
$user->uuid = Helper::guid(true);
return $this->success($user->save());
2023-11-17 01:44:01 -05:00
}
2025-01-21 01:57:54 -05:00
/**
* Apply filters and sorts to the query builder
*
* @param Request $request
* @param Builder $builder
* @return void
*/
private function applyFiltersAndSorts(Request $request, Builder $builder): void
2023-11-17 01:44:01 -05:00
{
2025-01-21 01:57:54 -05:00
$this->applyFilters($request, $builder);
$this->applySorting($request, $builder);
}
/**
* Apply filters to the query builder
*
* @param Request $request
* @param Builder $builder
* @return void
*/
private function applyFilters(Request $request, Builder $builder): void
{
if (!$request->has('filter')) {
return;
2023-11-17 01:44:01 -05:00
}
2025-01-21 01:57:54 -05:00
collect($request->input('filter'))->each(function ($filter) use ($builder) {
$field = $filter['id'];
$value = $filter['value'];
$builder->where(function ($query) use ($field, $value) {
$this->buildFilterQuery($query, $field, $value);
});
});
2023-11-17 01:44:01 -05:00
}
2025-01-21 01:57:54 -05:00
/**
* Build the filter query based on field and value
*
* @param Builder $query
* @param string $field
* @param mixed $value
* @return void
*/
private function buildFilterQuery(Builder $query, string $field, mixed $value): void
2023-11-17 01:44:01 -05:00
{
// 处理关联查询
if (str_contains($field, '.')) {
[$relation, $relationField] = explode('.', $field);
$query->whereHas($relation, function($q) use ($relationField, $value) {
if (is_array($value)) {
$q->whereIn($relationField, $value);
} else if (is_string($value) && str_contains($value, ':')) {
[$operator, $filterValue] = explode(':', $value, 2);
$this->applyQueryCondition($q, $relationField, $operator, $filterValue);
} else {
$q->where($relationField, 'like', "%{$value}%");
}
});
return;
}
// 处理数组值的 'in' 操作
2025-01-21 01:57:54 -05:00
if (is_array($value)) {
$query->whereIn($field === 'group_ids' ? 'group_id' : $field, $value);
return;
}
// 处理基于运算符的过滤
2025-01-21 01:57:54 -05:00
if (!is_string($value) || !str_contains($value, ':')) {
$query->where($field, 'like', "%{$value}%");
return;
}
[$operator, $filterValue] = explode(':', $value, 2);
// 转换数字字符串为适当的类型
2025-01-21 01:57:54 -05:00
if (is_numeric($filterValue)) {
$filterValue = strpos($filterValue, '.') !== false
? (float) $filterValue
2025-01-21 01:57:54 -05:00
: (int) $filterValue;
}
// 处理计算字段
2025-01-21 01:57:54 -05:00
$queryField = match ($field) {
'total_used' => DB::raw('(u + d)'),
default => $field
};
$this->applyQueryCondition($query, $queryField, $operator, $filterValue);
2025-01-21 01:57:54 -05:00
}
/**
* Apply sorting to the query builder
*
* @param Request $request
* @param Builder $builder
* @return void
*/
private function applySorting(Request $request, Builder $builder): void
{
if (!$request->has('sort')) {
return;
2023-11-17 01:44:01 -05:00
}
2025-01-21 01:57:54 -05:00
collect($request->input('sort'))->each(function ($sort) use ($builder) {
$field = $sort['id'];
$direction = $sort['desc'] ? 'DESC' : 'ASC';
$builder->orderBy($field, $direction);
});
}
/**
* Fetch paginated user list with filters and sorting
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function fetch(Request $request)
{
$current = $request->input('current', 1);
$pageSize = $request->input('pageSize', 10);
2025-01-21 01:57:54 -05:00
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])
->select(DB::raw('*, (u+d) as total_used'));
$this->applyFiltersAndSorts($request, $userModel);
2025-01-21 01:57:54 -05:00
$users = $userModel->orderBy('id', 'desc')
->paginate($pageSize, ['*'], 'page', $current);
$users->getCollection()->transform(function ($user) {
2025-01-25 22:42:30 -05:00
return self::transformUserData($user);
2025-01-21 01:57:54 -05:00
});
2023-11-17 01:44:01 -05:00
return response([
2025-01-21 01:57:54 -05:00
'data' => $users->items(),
'total' => $users->total()
2023-11-17 01:44:01 -05:00
]);
}
2025-01-21 01:57:54 -05:00
/**
* Transform user data for response
*
* @param User $user
* @return User
*/
2025-01-25 22:42:30 -05:00
public static function transformUserData(User $user): User
2025-01-21 01:57:54 -05:00
{
$user->subscribe_url = Helper::getSubscribeUrl($user->token);
$user->balance = $user->balance / 100;
$user->commission_balance = $user->commission_balance / 100;
return $user;
}
2023-11-17 01:44:01 -05:00
public function getUserInfoById(Request $request)
{
$request->validate([
2025-01-21 01:57:54 -05:00
'id' => 'required|numeric'
], [
'id.required' => '用户ID不能为空'
2023-11-17 01:44:01 -05:00
]);
$user = User::find($request->input('id'))->load('invite_user');
return $this->success($user);
2023-11-17 01:44:01 -05:00
}
public function update(UserUpdate $request)
{
$params = $request->validated();
2023-11-17 01:44:01 -05:00
$user = User::find($request->input('id'));
if (!$user) {
return $this->fail([400202, '用户不存在']);
2023-11-17 01:44:01 -05:00
}
// 检查邮箱是否被使用
2023-11-17 01:44:01 -05:00
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
return $this->fail([400201, '邮箱已被使用']);
2023-11-17 01:44:01 -05:00
}
// 处理密码
2023-11-17 01:44:01 -05:00
if (isset($params['password'])) {
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$params['password_algo'] = NULL;
} else {
unset($params['password']);
}
// 处理订阅计划
2023-11-17 01:44:01 -05:00
if (isset($params['plan_id'])) {
$plan = Plan::find($params['plan_id']);
if (!$plan) {
return $this->fail([400202, '订阅计划不存在']);
2023-11-17 01:44:01 -05:00
}
2025-01-21 01:57:54 -05:00
// return json_encode($plan);
2023-11-17 01:44:01 -05:00
$params['group_id'] = $plan->group_id;
}
// 处理邀请用户
if ($request->input('invite_user_email') && $inviteUser = User::where('email', $request->input('invite_user_email'))->first()) {
$params['invite_user_id'] = $inviteUser->id;
2023-11-17 01:44:01 -05:00
} else {
$params['invite_user_id'] = null;
}
2025-01-21 01:57:54 -05:00
if (isset($params['banned']) && (int) $params['banned'] === 1) {
2023-11-17 01:44:01 -05:00
$authService = new AuthService($user);
2025-01-21 01:57:54 -05:00
$authService->removeSession();
}
if (isset($params['balance'])) {
$params['balance'] = $params['balance'] * 100;
}
if (isset($params['commission_balance'])) {
$params['commission_balance'] = $params['commission_balance'] * 100;
2023-11-17 01:44:01 -05:00
}
try {
$user->update($params);
} catch (\Exception $e) {
Log::error($e);
2025-01-21 01:57:54 -05:00
return $this->fail([500, '保存失败']);
2023-11-17 01:44:01 -05:00
}
return $this->success(true);
2023-11-17 01:44:01 -05:00
}
/**
* 导出用户数据为CSV格式
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
2023-11-17 01:44:01 -05:00
public function dumpCSV(Request $request)
{
ini_set('memory_limit', '-1');
gc_enable(); // 启用垃圾回收
// 优化查询使用with预加载plan关系避免N+1问题
$query = User::with('plan:id,name')
->orderBy('id', 'asc')
->select([
'email',
'balance',
'commission_balance',
'transfer_enable',
'u',
'd',
'expired_at',
'token',
'plan_id'
]);
$this->applyFiltersAndSorts($request, $query);
$filename = 'users_' . date('Y-m-d_His') . '.csv';
return response()->streamDownload(function() use ($query) {
// 打开输出流
$output = fopen('php://output', 'w');
// 添加BOM标记确保Excel正确显示中文
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
// 写入CSV头部
fputcsv($output, [
'邮箱',
'余额',
'推广佣金',
'总流量',
'剩余流量',
'套餐到期时间',
'订阅计划',
'订阅地址'
]);
// 分批处理数据以减少内存使用
$query->chunk(500, function($users) use ($output) {
foreach ($users as $user) {
try {
$row = [
$user->email,
number_format($user->balance / 100, 2),
number_format($user->commission_balance / 100, 2),
Helper::trafficConvert($user->transfer_enable),
Helper::trafficConvert($user->transfer_enable - ($user->u + $user->d)),
$user->expired_at ? date('Y-m-d H:i:s', $user->expired_at) : '长期有效',
$user->plan ? $user->plan->name : '无订阅',
Helper::getSubscribeUrl($user->token)
];
fputcsv($output, $row);
} catch (\Exception $e) {
Log::error('CSV导出错误: ' . $e->getMessage(), [
'user_id' => $user->id,
'email' => $user->email
]);
continue; // 继续处理下一条记录
}
2023-11-17 01:44:01 -05:00
}
// 清理内存
gc_collect_cycles();
});
fclose($output);
}, $filename, [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"'
]);
2023-11-17 01:44:01 -05:00
}
public function generate(UserGenerate $request)
{
if ($request->input('email_prefix')) {
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
2025-01-21 01:57:54 -05:00
return $this->fail([400202, '订阅计划不存在']);
2023-11-17 01:44:01 -05:00
}
}
$user = [
'email' => $request->input('email_prefix') . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid()
];
if (User::where('email', $user['email'])->first()) {
2025-01-21 01:57:54 -05:00
return $this->fail([400201, '邮箱已存在于系统中']);
2023-11-17 01:44:01 -05:00
}
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
if (!User::create($user)) {
2025-01-21 01:57:54 -05:00
return $this->fail([500, '生成失败']);
2023-11-17 01:44:01 -05:00
}
return $this->success(true);
2023-11-17 01:44:01 -05:00
}
if ($request->input('generate_count')) {
$this->multiGenerate($request);
}
}
private function multiGenerate(Request $request)
{
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
2025-01-21 01:57:54 -05:00
return $this->fail([400202, '订阅计划不存在']);
2023-11-17 01:44:01 -05:00
}
}
$users = [];
2025-01-21 01:57:54 -05:00
for ($i = 0; $i < $request->input('generate_count'); $i++) {
2023-11-17 01:44:01 -05:00
$user = [
'email' => Helper::randomChar(6) . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid(),
'created_at' => time(),
'updated_at' => time()
];
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
array_push($users, $user);
}
2025-01-21 01:57:54 -05:00
try {
DB::beginTransaction();
if (!User::insert($users)) {
throw new \Exception();
}
DB::commit();
2025-01-21 01:57:54 -05:00
} catch (\Exception $e) {
2023-11-17 01:44:01 -05:00
DB::rollBack();
Log::error($e);
2025-01-21 01:57:54 -05:00
return $this->fail([500, '生成失败']);
2023-11-17 01:44:01 -05:00
}
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
2025-01-21 01:57:54 -05:00
foreach ($users as $user) {
2023-11-17 01:44:01 -05:00
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
2025-01-21 01:57:54 -05:00
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
2023-11-17 01:44:01 -05:00
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
}
echo $data;
}
public function sendMail(UserSendMail $request)
{
ini_set('memory_limit', '-1');
2023-11-17 01:44:01 -05:00
$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->applyFilters($request, $builder);
2023-11-17 01:44:01 -05:00
$users = $builder->get();
return $this->success($users->count());
2023-11-17 01:44:01 -05:00
foreach ($users as $user) {
2025-01-21 01:57:54 -05:00
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')
]
],
'send_email_mass'
);
2023-11-17 01:44:01 -05:00
}
return $this->success(true);
2023-11-17 01:44:01 -05:00
}
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->applyFilters($request, $builder);
2023-11-17 01:44:01 -05:00
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
Log::error($e);
2025-01-21 01:57:54 -05:00
return $this->fail([500, '处理失败']);
2023-11-17 01:44:01 -05:00
}
return $this->success(true);
2023-11-17 01:44:01 -05:00
}
/**
* 删除用户及其关联数据
*
* @param Request $request
* @return JsonResponse
*/
public function destroy(Request $request)
{
$request->validate([
'id' => 'required|exists:App\Models\User,id'
], [
'id.required' => '用户ID不能为空',
'id.exists' => '用户不存在'
]);
$user = User::find($request->input('id'));
try {
DB::beginTransaction();
$user->orders()->delete();
$user->codes()->delete();
$user->stat()->delete();
$user->tickets()->delete();
$user->delete();
DB::commit();
return $this->success(true);
} catch (\Exception $e) {
DB::rollBack();
Log::error($e);
return $this->fail([500, '删除失败']);
}
}
2023-11-17 01:44:01 -05:00
}