| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- <?php
- namespace App\Http\Controllers\admin;
- use App\Constants\HttpStatus;
- use App\Http\Controllers\Controller;
- use App\Services\SecretService;
- use App\Services\TopUpService;
- use Illuminate\Support\Facades\App;
- use Illuminate\Support\Facades\DB;
- use App\Services\UserService;
- use Exception;
- use Illuminate\Validation\ValidationException;
- use App\Services\AddressService;
- use Illuminate\Http\JsonResponse;
- use App\Models\User as UserModel;
- use App\Models\UserSession;
- use App\Models\UserLogin;
- use App\Models\ThirdGameRecycle;
- use App\Models\Wallet;
- use App\Services\BalanceLogService;
- use App\Services\ThirdGameBalanceService;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Str;
- class User extends Controller
- {
- //修改用户密码/资金密码
- function setPassword()
- {
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- 'password' => ['nullable'],
- 'payment_password' => ['nullable'],
- ]);
- $user = UserModel::where('member_id', $params['member_id'])->first();
- if (!$user) throw new Exception("用户不存在", HttpStatus::CUSTOM_ERROR);
- if (!empty($params['password'])) {
- $user->password = create_password($params['password']);
- $user->save();
- //删除缓存
- $token = UserSession::where('user_id', $params['member_id'])->orderByDesc('expire_time')->value('token');
- Cache::delete('token_user_' . $token);
- UserSession::where('user_id', $params['member_id'])->delete();
- }
- if (!empty($params['payment_password'])) {
- $user->payment_password = password_hash($params['payment_password'], PASSWORD_DEFAULT);
- $user->save();
- }
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success();
- }
- function banned()
- {
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- 'is_banned' => ['required', 'integer', 'in:0,1'],
- ]);
- UserModel::where('member_id', $params['member_id'])->update(['is_banned' => $params['is_banned']]);
- if ($params['is_banned'] == 1) {
- //如果用户被禁用,删除所有会话
- UserSession::where('user_id', $params['member_id'])->delete();
- return $this->success();
- }
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success();
- }
- function setNote()
- {
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- 'admin_note' => ['required', 'string', 'min:1', 'max:120'],
- ]);
- $user = UserModel::where('member_id', $params['member_id'])->first();
- if (!$user) throw new Exception("用户不存在", HttpStatus::CUSTOM_ERROR);
- $user->admin_note = $params['admin_note'];
- $user->save();
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success();
- }
- public function index(): JsonResponse
- {
- try {
- $search = request()->validate([
- 'page' => ['nullable', 'integer', 'min:1'],
- 'limit' => ['nullable', 'integer', 'min:1'],
- 'member_id' => ['nullable', 'string', 'min:1'],
- 'like_first_name' => ['nullable', 'string', 'min:1'],
- 'username' => ['nullable', 'string', 'min:1'],
- 'register_ip' => ['nullable', 'string', 'min:1'],
- 'order' => ["nullable", 'string', "in:asc,desc"],
- 'by' => ['nullable', 'string', "in:available_balance,created_at,last_active_time"],
- 'user_code' => ['nullable'],
- 'agent_user_code' => ['nullable'],
- 'level' => ['nullable'],
- 'from' => ['nullable'],
- 'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
- 'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
- 'recharge_channel_group_id' => ['nullable'],
- ]);
- $order = request()->input('order', 'desc');
- $by = request()->input('by', 'available_balance');
- $result = UserService::paginate($search,$order,$by);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first().'ssss');
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success($result);
- }
- /**
- * 查询指定用户在 ag、pg 等三方游戏平台的余额明细。
- */
- public function thirdGameDetail(ThirdGameBalanceService $service): JsonResponse
- {
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- ]);
- if (!UserModel::where('member_id', $params['member_id'])->exists()) {
- throw new Exception('用户不存在', HttpStatus::CUSTOM_ERROR);
- }
- $result = $service->detail((string) $params['member_id']);
- if (!$result['ok']) {
- throw new Exception($result['msg'], HttpStatus::CUSTOM_ERROR);
- }
- unset($result['ok']);
- return $this->success($result);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- }
- /**
- * 一键回收指定用户的全部三方游戏余额并入账主钱包。
- */
- public function recycleThirdGameBalance(ThirdGameBalanceService $service): JsonResponse
- {
- $lock = null;
- $lockAcquired = false;
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- ]);
- $memberId = (string) $params['member_id'];
- if (!UserModel::where('member_id', $memberId)->exists()) {
- throw new Exception('用户不存在', HttpStatus::CUSTOM_ERROR);
- }
- if (!Wallet::where('member_id', $memberId)->exists()) {
- throw new Exception('用户钱包不存在', HttpStatus::CUSTOM_ERROR);
- }
- $lock = Cache::lock('admin_third_game_recycle:' . $memberId, 120);
- $lockAcquired = $lock->get();
- if (!$lockAcquired) {
- throw new Exception('该用户正在回收三方余额,请勿重复操作', HttpStatus::CUSTOM_ERROR);
- }
- $pendingCredit = ThirdGameRecycle::where('member_id', $memberId)
- ->where('status', ThirdGameRecycle::STATUS_PROVIDER_SUCCEEDED)
- ->orderBy('id')
- ->first();
- if ($pendingCredit) {
- try {
- $credited = $this->creditThirdGameRecycle($pendingCredit->id);
- } catch (Exception $e) {
- throw new Exception(
- '三方余额已回收但钱包补入账失败,流水号:' . $pendingCredit->operation_id,
- HttpStatus::CUSTOM_ERROR
- );
- }
- $credited['recovered_pending_operation'] = true;
- return $this->success($credited);
- }
- $unresolved = ThirdGameRecycle::where('member_id', $memberId)
- ->whereIn('status', [
- ThirdGameRecycle::STATUS_PENDING,
- ThirdGameRecycle::STATUS_UNCERTAIN,
- ])
- ->orderByDesc('id')
- ->first();
- if ($unresolved) {
- throw new Exception(
- '存在待核对的三方回收流水:' . $unresolved->operation_id . ',请先人工核对三方账单',
- HttpStatus::CUSTOM_ERROR
- );
- }
- $operation = ThirdGameRecycle::create([
- 'operation_id' => (string) Str::uuid(),
- 'member_id' => $memberId,
- 'player_id' => $service->playerId($memberId),
- 'status' => ThirdGameRecycle::STATUS_PENDING,
- ]);
- $recycled = $service->recycle($memberId);
- if (!$recycled['ok']) {
- $operation->status = !empty($recycled['uncertain'])
- ? ThirdGameRecycle::STATUS_UNCERTAIN
- : ThirdGameRecycle::STATUS_FAILED;
- $operation->error = $recycled['msg'];
- $operation->save();
- throw new Exception(
- $recycled['msg'] . ',流水号:' . $operation->operation_id,
- HttpStatus::CUSTOM_ERROR
- );
- }
- $operation->status = ThirdGameRecycle::STATUS_PROVIDER_SUCCEEDED;
- $operation->game_balance = $recycled['game_balance'];
- $operation->wallet_balance = $recycled['wallet_balance'];
- $operation->save();
- try {
- $credited = $this->creditThirdGameRecycle($operation->id);
- } catch (Exception $e) {
- throw new Exception(
- '三方余额已回收但钱包入账失败,请重试回收以补入账,流水号:' . $operation->operation_id,
- HttpStatus::CUSTOM_ERROR
- );
- }
- return $this->success($credited);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- } finally {
- if ($lockAcquired && $lock) {
- $lock->release();
- }
- }
- }
- /**
- * 将三方已回收的流水幂等入账;事务失败时保留 provider_succeeded 供下次补偿。
- */
- private function creditThirdGameRecycle(int $operationId): array
- {
- return DB::transaction(function () use ($operationId) {
- $operation = ThirdGameRecycle::where('id', $operationId)->lockForUpdate()->firstOrFail();
- if ($operation->status === ThirdGameRecycle::STATUS_COMPLETED) {
- $walletBalance = (float) Wallet::where('member_id', $operation->member_id)
- ->value('available_balance');
- return $this->recycleResponse($operation, $walletBalance);
- }
- if ($operation->status !== ThirdGameRecycle::STATUS_PROVIDER_SUCCEEDED) {
- throw new Exception('三方回收流水状态不可入账', HttpStatus::CUSTOM_ERROR);
- }
- $wallet = Wallet::where('member_id', $operation->member_id)->lockForUpdate()->firstOrFail();
- $before = (string) $wallet->available_balance;
- $walletGain = (string) $operation->wallet_balance;
- $after = bcadd($before, $walletGain, 10);
- if (bccomp($walletGain, '0', 10) > 0) {
- BalanceLogService::addLog(
- $operation->member_id,
- $walletGain,
- $before,
- $after,
- '三方游戏转出',
- $operation->id,
- '后台一键回收游戏余额'
- );
- $wallet->available_balance = $after;
- $wallet->save();
- }
- $operation->status = ThirdGameRecycle::STATUS_COMPLETED;
- $operation->credited_at = now();
- $operation->save();
- return $this->recycleResponse($operation, (float) $after);
- });
- }
- private function recycleResponse(ThirdGameRecycle $operation, float $walletBalance): array
- {
- return [
- 'operation_id' => $operation->operation_id,
- 'member_id' => $operation->member_id,
- 'recovered_game_balance' => (float) $operation->game_balance,
- 'recovered_balance' => (float) $operation->wallet_balance,
- 'wallet_balance' => $walletBalance,
- ];
- }
- /**
- * 查询三方游戏回收流水,用于核对 pending/uncertain 状态。
- */
- public function thirdGameRecycleRecords(): JsonResponse
- {
- try {
- $params = request()->validate([
- 'page' => ['nullable', 'integer', 'min:1'],
- 'limit' => ['nullable', 'integer', 'min:1', 'max:100'],
- 'member_id' => ['nullable', 'string', 'min:1'],
- 'status' => ['nullable', 'string', 'in:pending,provider_succeeded,completed,failed,uncertain'],
- ]);
- $page = (int) ($params['page'] ?? 1);
- $limit = (int) ($params['limit'] ?? 15);
- $query = ThirdGameRecycle::query()
- ->when(!empty($params['member_id']), function ($query) use ($params) {
- $query->where('member_id', $params['member_id']);
- })
- ->when(!empty($params['status']), function ($query) use ($params) {
- $query->where('status', $params['status']);
- });
- $total = (clone $query)->count();
- $list = $query->orderByDesc('id')->forPage($page, $limit)->get();
- return $this->success(['total' => $total, 'data' => $list]);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- }
- /**
- * 人工核对待确认流水:确认未回收,或按三方实际金额补入账。
- */
- public function resolveThirdGameRecycle(ThirdGameBalanceService $service): JsonResponse
- {
- $lock = null;
- $lockAcquired = false;
- try {
- $params = request()->validate([
- 'operation_id' => ['required', 'uuid'],
- 'action' => ['required', 'string', 'in:failed,credit'],
- 'game_balance' => ['nullable', 'required_if:action,credit', 'numeric', 'min:0'],
- 'remark' => ['nullable', 'string', 'max:500'],
- ]);
- $operation = ThirdGameRecycle::where('operation_id', $params['operation_id'])->first();
- if (!$operation) {
- throw new Exception('三方回收流水不存在', HttpStatus::CUSTOM_ERROR);
- }
- $lock = Cache::lock('admin_third_game_recycle:' . $operation->member_id, 120);
- $lockAcquired = $lock->get();
- if (!$lockAcquired) {
- throw new Exception('该用户正在处理三方回收,请稍后再试', HttpStatus::CUSTOM_ERROR);
- }
- $operation = DB::transaction(function () use ($operation, $params, $service) {
- $operation = ThirdGameRecycle::where('id', $operation->id)->lockForUpdate()->firstOrFail();
- if (!in_array($operation->status, [
- ThirdGameRecycle::STATUS_PENDING,
- ThirdGameRecycle::STATUS_UNCERTAIN,
- ], true)) {
- throw new Exception('当前流水状态无需人工处理', HttpStatus::CUSTOM_ERROR);
- }
- $remark = trim((string) ($params['remark'] ?? ''));
- if ($params['action'] === 'failed') {
- $operation->status = ThirdGameRecycle::STATUS_FAILED;
- $operation->resolution_remark = '后台人工核对:确认三方未回收'
- . ($remark !== '' ? ';' . $remark : '');
- } else {
- $gameBalance = (float) $params['game_balance'];
- $operation->status = ThirdGameRecycle::STATUS_PROVIDER_SUCCEEDED;
- $operation->game_balance = $gameBalance;
- $operation->wallet_balance = $service->toWalletBalance($gameBalance);
- $operation->resolution_remark = '后台人工核对:确认三方已回收'
- . ($remark !== '' ? ';' . $remark : '');
- }
- $operation->save();
- return $operation;
- });
- if ($params['action'] === 'failed') {
- return $this->success([
- 'operation_id' => $operation->operation_id,
- 'member_id' => $operation->member_id,
- 'status' => $operation->status,
- ]);
- }
- try {
- $credited = $this->creditThirdGameRecycle($operation->id);
- } catch (Exception $e) {
- throw new Exception(
- '核对金额已保存但钱包入账失败,请再次提交回收操作补入账,流水号:' . $operation->operation_id,
- HttpStatus::CUSTOM_ERROR
- );
- }
- $credited['resolved_manually'] = true;
- return $this->success($credited);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- } finally {
- if ($lockAcquired && $lock) {
- $lock->release();
- }
- }
- }
- public function merge(): JsonResponse
- {
- DB::beginTransaction();
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'string', 'min:1'],
- 'secret_key' => ['required', 'string', 'min:1'],
- ]);
- $res = SecretService::migration($params['member_id'], $params['secret_key']);
- if (!$res) {
- throw new Exception(lang("迁移失败"), HttpStatus::CUSTOM_ERROR);
- }
- $oldUser = UserModel::where('secret_key', $params['secret_key'])->first();
- $newUser = UserModel::where('member_id', $params['member_id'])->first();
- App::setLocale($oldUser->language);
- $text = lang('账户转移通知') . ":\n";
- $text .= lang('管理员已将您的账户转移至新用户') . "\n\n";
- $text .= lang('新用户信息') . "\n";
- $text .= lang('用户ID') . ":{$newUser->getMemberId()}\n";
- if ($newUser->getUsername()) {
- $text .= lang("用户名") . ":@{$newUser->getUsername()}\n";
- }
- $text .= lang('昵称') . ":{$newUser->getFirstName()}\n";
- TopUpService::notifyTransferSuccess($oldUser->getMemberId(), $text);
- App::setLocale($newUser->language);
- $text = lang("账户转移通知") . ":\n";
- $text .= lang("管理员已将指定账户转移至您的账户") . "\n\n";
- $text .= lang('原账户信息') . "\n\n";
- $text .= lang('用户ID') . ":{$oldUser->getMemberId()}\n";
- if ($oldUser->getUsername()) {
- $text .= lang('用户名') . ":@{$oldUser->getUsername()}\n";
- }
- $text .= lang('昵称') . ":{$oldUser->getFirstName()}\n";
- TopUpService::notifyTransferSuccess($newUser->getMemberId(), $text);
- DB::commit();
- } catch (ValidationException $e) {
- DB::rollBack();
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- DB::rollBack();
- if ($e->getCode() == HttpStatus::CUSTOM_ERROR) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->error(intval($e->getCode()));
- }
- return $this->success(msg: '已完成迁移');
- }
- public function address()
- {
- try {
- request()->validate([
- 'member_id' => ['required', 'integer', 'min:1'],
- ]);
- $search = request()->all();
- $result = AddressService::findAll($search);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(intval($e->getCode()));
- }
- return $this->success($result);
- }
- /**
- * 用户登录日志
- */
- public function loginLog()
- {
- try {
- $params = request()->validate([
- 'page' => ['nullable', 'integer', 'min:1'],
- 'limit' => ['nullable', 'integer', 'min:1'],
- 'user_id' => ['nullable'],
- 'member_id' => ['nullable'],
- 'first_name' => ['nullable'],
- 'start_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:end_time'],
- 'end_time' => ['nullable', 'date', 'date_format:Y-m-d', 'required_with:start_time'],
- ]);
- $page = request()->input('page', 1);
- $limit = request()->input('limit', 15);
- $query = UserLogin::join('users', 'user_login.user_id', '=', 'users.user_id');
- if (!empty($params['user_id'])) {
- $query = $query->where('user_login.user_id', $params['user_id']);
- }
- if (!empty($params['member_id'])) {
- $query = $query->where('user_login.user_id', $params['member_id']);
- }
- if (!empty($params['first_name'])) {
- $query = $query->where('users.first_name', 'like', "%{$params['first_name']}%");
- }
- if (!empty($params['start_time'])) {
- $startTime = $params['start_time'] . " 00:00:00";
- $query = $query->where('user_login.created_at', '>=', $startTime);
- }
- if (!empty($params['end_time'])) {
- $endTime = $params['end_time'] . " 23:59:59";
- $query = $query->where('user_login.updated_at', '<=', $endTime);
- }
- $count = $query->count();
- $list = $query
- ->forPage($page, $limit)
- ->orderByDesc('user_login.created_at')
- ->get();
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR,$e->getMessage());
- }
- return $this->success(['total' => $count, 'data' => $list]);
- }
- function setRechargeChannelGroup()
- {
- try {
- $params = request()->validate([
- 'member_id' => ['required', 'array'],
- 'recharge_channel_group_id' => ['required', 'integer', 'min:1'],
- ]);
- UserModel::whereIn('member_id', $params['member_id'])->update(['recharge_channel_group_id' => $params['recharge_channel_group_id']]);
- } catch (ValidationException $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->validator->errors()->first());
- } catch (Exception $e) {
- return $this->error(HttpStatus::CUSTOM_ERROR, $e->getMessage());
- }
- return $this->success();
- }
- }
|