feat: add user deletion functionality and fix known issues

This commit is contained in:
xboard 2025-01-25 16:57:17 +08:00
parent 1ae8deca99
commit b2e7ed44f3
10 changed files with 125 additions and 43 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Resources\CouponResource;
use App\Services\CouponService;
use Illuminate\Http\Request;
@ -11,19 +12,14 @@ class CouponController extends Controller
{
public function check(Request $request)
{
// $request->validate([
// 'code' => 'required|string',
// 'plan_id' => 'required|integer',
// 'period' => 'nullable|string',
// ]);
if (empty($request->input('code'))) {
return $this->fail([422,__('Coupon cannot be empty')]);
return $this->fail([422, __('Coupon cannot be empty')]);
}
$couponService = new CouponService($request->input('code'));
$couponService->setPlanId($request->input('plan_id'));
$couponService->setUserId($request->user()->id);
$couponService->setPeriod($request->input('period'));
$couponService->check();
return $this->success($couponService->getCoupon());
return $this->success(CouponResource::make($couponService->getCoupon()));
}
}

View File

@ -13,6 +13,7 @@ use App\Services\AuthService;
use App\Utils\Helper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
class UserController extends Controller
@ -86,11 +87,11 @@ class UserController extends Controller
}
[$operator, $filterValue] = explode(':', $value, 2);
// Convert numeric strings to appropriate type
if (is_numeric($filterValue)) {
$filterValue = strpos($filterValue, '.') !== false
? (float) $filterValue
$filterValue = strpos($filterValue, '.') !== false
? (float) $filterValue
: (int) $filterValue;
}
@ -149,12 +150,12 @@ class UserController extends Controller
{
$current = $request->input('current', 1);
$pageSize = $request->input('pageSize', 10);
$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);
$users = $userModel->orderBy('id', 'desc')
->paginate($pageSize, ['*'], 'page', $current);
@ -397,4 +398,35 @@ class UserController extends Controller
return $this->success(true);
}
/**
* 删除用户及其关联数据
*
* @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, '删除失败']);
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* 优惠券资源类
*
* @property array|null $limit_plan_ids 限制可用的套餐ID列表
*/
class CouponResource extends JsonResource
{
/**
* 将资源转换为数组
*
* @param Request $request 请求实例
* @return array<string, mixed> 转换后的数组
*/
public function toArray(Request $request): array
{
return [
...$this->resource->toArray(),
'limit_plan_ids' => $this->when(
!empty($this->limit_plan_ids),
fn() => collect($this->limit_plan_ids)
->map(fn(mixed $id): string => (string) $id)
->values()
->all()
)
];
}
}

View File

@ -108,6 +108,7 @@ class AdminRoute
$router->post('/ban', [UserController::class, 'ban']);
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
$router->post('/destroy', [UserController::class, 'destroy']);
});
// Stat

View File

@ -24,17 +24,4 @@ class Coupon extends Model
})->toArray();
}
public function getLimitPlanIdsAttribute($value)
{
$planIds = json_decode($value, true);
if (blank($planIds)) {
return null;
}
return collect($planIds)
->map(fn($id) => (string) $id)
->values()
->all();
}
}

View File

@ -42,6 +42,16 @@ class User extends Authenticatable
return $this->hasMany(InviteCode::class, 'user_id', 'id');
}
public function orders()
{
return $this->hasMany(Order::class, 'user_id', 'id');
}
public function stat()
{
return $this->hasMany(StatUser::class, 'user_id', 'id');
}
// 关联工单列表
public function tickets()
{

File diff suppressed because one or more lines are too long

View File

@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"success": "Success",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"confirm": "Confirm",
"delete": {
"success": "Deleted successfully",
"failed": "Failed to delete"
},
"edit": "Edit",
"view": "View",
"toggleNavigation": "Toggle Navigation",
@ -832,6 +836,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"logout": "Logout",
"copy": {
"success": "Copied successfully",
"failed": "Failed to copy",
"error": "Copy failed",
"errorLog": "Error copying to clipboard"
},
@ -1740,7 +1745,10 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
"reset_secret": "Reset UUID & URL",
"orders": "Orders",
"invites": "Invites",
"traffic_records": "Traffic Records"
"traffic_records": "Traffic Records",
"delete": "Delete",
"delete_confirm_title": "Confirm Delete User",
"delete_confirm_description": "This action will permanently delete user {{email}} and all associated data, including orders, coupons, traffic records, and support tickets. This action cannot be undone. Do you want to continue?"
}
},
"filter": {

View File

@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"success": "성공",
"save": "저장",
"cancel": "취소",
"delete": "삭제",
"confirm": "확인",
"delete": {
"success": "삭제되었습니다",
"failed": "삭제에 실패했습니다"
},
"edit": "편집",
"view": "보기",
"toggleNavigation": "네비게이션 전환",
@ -831,9 +835,8 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"settings": "설정",
"logout": "로그아웃",
"copy": {
"success": "복사 성공",
"error": "복사 실패",
"errorLog": "클립보드에 복사하는 중 오류 발생"
"success": "복사되었습니다",
"failed": "복사에 실패했습니다"
},
"table": {
"noData": "데이터가 없습니다",
@ -1692,9 +1695,12 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
"assign_order": "주문 할당",
"copy_url": "구독 URL 복사",
"reset_secret": "UUID 및 URL 재설정",
"orders": "주문",
"invites": "초대",
"traffic_records": "트래픽 기록"
"orders": "주문 내역",
"invites": "초대 내역",
"traffic_records": "트래픽 기록",
"delete": "삭제",
"delete_confirm_title": "사용자 삭제 확인",
"delete_confirm_description": "이 작업은 사용자 {{email}}와 관련된 모든 데이터(주문, 쿠폰, 트래픽 기록, 지원 티켓 등)를 영구적으로 삭제합니다. 이 작업은 취소할 수 없습니다. 계속하시겠습니까?"
}
},
"filter": {

View File

@ -820,7 +820,11 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"success": "成功",
"save": "保存",
"cancel": "取消",
"delete": "删除",
"confirm": "确认",
"delete": {
"success": "删除成功",
"failed": "删除失败"
},
"edit": "编辑",
"view": "查看",
"toggleNavigation": "切换导航",
@ -837,6 +841,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"logout": "退出登录",
"copy": {
"success": "复制成功",
"failed": "复制失败",
"error": "复制失败",
"errorLog": "复制到剪贴板时出错"
},
@ -1707,7 +1712,10 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
"reset_secret": "重置UUID及订阅URL",
"orders": "TA的订单",
"invites": "TA的邀请",
"traffic_records": "TA的流量记录"
"traffic_records": "TA的流量记录",
"delete": "删除",
"delete_confirm_title": "确认删除用户",
"delete_confirm_description": "此操作将永久删除用户 {{email}} 及其所有相关数据,包括订单、优惠码、流量记录、工单记录等信息。删除后无法恢复,是否继续?"
}
},
"filter": {