Browse Source

feat:后端-用户管理

刘学玺 4 months ago
parent
commit
6da46f7d41

+ 67 - 0
app/Admin/Controllers/MemberUserController.php

@@ -2,6 +2,7 @@
 
 namespace App\Admin\Controllers;
 
+use App\Enums\BadBehaviorType;
 use App\Services\MemberUserService;
 use Illuminate\Http\Request;
 use Slowlyo\OwlAdmin\Controllers\AdminController;
@@ -143,4 +144,70 @@ class MemberUserController extends AdminController
 
         return $this->service->blockUser($validated);
     }
+
+    /**
+     * [用户管理]冻结用户余额
+     *
+     * @description 冻结用户钱包余额
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam user_id integer required 用户ID Example: 1
+     * @bodyParam amount numeric required 冻结金额 Example: 100.00
+     * @bodyParam reason string required 冻结原因 Example: 涉嫌违规操作
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "冻结成功",
+     *   "data": null
+     * }
+     */
+    public function freezeBalance(Request $request)
+    {
+        $validated = $request->validate([
+            'user_id' => 'required|integer|exists:member_users,id',
+            'amount' => 'required|numeric|min:0.01',
+            'reason' => 'required|string|max:255',
+        ]);
+
+        return $this->service->freezeUserBalance($validated);
+    }
+
+    /**
+     * [用户管理]记录不良行为
+     *
+     * @description 记录用户不良行为
+     *
+     * @header x-xsrf-token required CSRF令牌 Example: your_csrf_token
+     *
+     * @bodyParam user_id integer required 用户ID Example: 1
+     * @bodyParam type integer required 不良行为类型(1:恶意取消订单,2:恶意投诉,3:违规操作,4:骚扰技师,5:欺诈行为,99:其他违规) Example: 1
+     * @bodyParam title string required 标题 Example: 多次恶意取消订单
+     * @bodyParam description string required 详细描述 Example: 用户在2024年3月连续恶意取消3次订单
+     * @bodyParam evidence array optional 证据(图片/视频URL数组) Example: ["http://example.com/evidence1.jpg"]
+     * @bodyParam related_order_id integer optional 关联订单ID Example: 1001
+     * @bodyParam occurred_at datetime optional 发生时间 Example: 2024-03-20 10:00:00
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "记录成功",
+     *   "data": null
+     * }
+     */
+    public function recordBadBehavior(Request $request)
+    {
+
+        $validated = $request->validate([
+            'user_id' => 'required|integer|exists:member_users,id',
+            'type' => 'required|integer|in:'.implode(',', BadBehaviorType::values()),
+            'title' => 'required|string|max:100',
+            'description' => 'required|string',
+            'evidence' => 'nullable|array',
+            'evidence.*' => 'url',
+            'related_order_id' => 'nullable|integer|exists:orders,id',
+            'occurred_at' => 'nullable|date',
+        ]);
+
+        return $this->service->recordBadBehavior($validated);
+    }
 }

+ 61 - 0
app/Enums/BadBehaviorType.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Enums;
+
+/**
+ * 不良行为类型枚举类
+ */
+enum BadBehaviorType: int
+{
+    /**
+     * 恶意取消订单
+     */
+    case MALICIOUS_CANCEL = 1;
+
+    /**
+     * 恶意投诉
+     */
+    case MALICIOUS_COMPLAINT = 2;
+
+    /**
+     * 违规操作
+     */
+    case VIOLATION = 3;
+
+    /**
+     * 骚扰技师
+     */
+    case HARASSMENT = 4;
+
+    /**
+     * 欺诈行为
+     */
+    case FRAUD = 5;
+
+    /**
+     * 其他违规
+     */
+    case OTHER = 99;
+
+    /**
+     * 获取所有状态的值数组
+     *
+     * @return array 包含所有状态值的数组
+     */
+    public static function values(): array
+    {
+        return array_column(self::cases(), 'value');
+    }
+
+    public function label(): string
+    {
+        return match ($this) {
+            self::MALICIOUS_CANCEL => '恶意取消订单',
+            self::MALICIOUS_COMPLAINT => '恶意投诉',
+            self::VIOLATION => '违规操作',
+            self::HARASSMENT => '骚扰技师',
+            self::FRAUD => '欺诈行为',
+            self::OTHER => '其他违规',
+        };
+    }
+}

+ 10 - 2
app/Enums/TransactionType.php

@@ -42,6 +42,11 @@ enum TransactionType: int
      */
     case PENALTY = 7;
 
+    /**
+     * 交易类型:冻结
+     */
+    case FREEZE = 8;
+
     /**
      * 获取类型的显示文本
      *
@@ -57,6 +62,7 @@ enum TransactionType: int
             self::COMMISSION => '佣金',
             self::BONUS => '奖励',
             self::PENALTY => '罚款',
+            self::FREEZE => '冻结',
         };
     }
 
@@ -73,7 +79,7 @@ enum TransactionType: int
     /**
      * 检查当前类型是否与指定类型相同
      *
-     * @param self $type 要比较的类型
+     * @param  self  $type  要比较的类型
      * @return bool 如果类型相同返回 true,否则返回 false
      */
     public function is(self $type): bool
@@ -84,7 +90,7 @@ enum TransactionType: int
     /**
      * 根据整数值创建对应的类型枚举实例
      *
-     * @param int $value 类型值
+     * @param  int  $value  类型值
      * @return self|null 返回对应的类型枚举实例,如果值无效则返回 null
      */
     public static function fromValue(int $value): ?self
@@ -97,6 +103,7 @@ enum TransactionType: int
             self::COMMISSION->value => self::COMMISSION,
             self::BONUS->value => self::BONUS,
             self::PENALTY->value => self::PENALTY,
+            self::FREEZE->value => self::FREEZE,
             default => null
         };
     }
@@ -126,6 +133,7 @@ enum TransactionType: int
             self::COMMISSION->value => self::COMMISSION->label(),
             self::BONUS->value => self::BONUS->label(),
             self::PENALTY->value => self::PENALTY->label(),
+            self::FREEZE->value => self::FREEZE->label(),
         ];
     }
 }

+ 40 - 0
app/Models/MemberBadBehavior.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+class MemberBadBehavior extends Model
+{
+    use SoftDeletes;
+
+    protected $table = 'member_bad_behaviors';
+
+    protected $fillable = [
+        'user_id',
+        'type',
+        'title',
+        'description',
+        'evidence',
+        'related_order_id',
+        'operator_id',
+        'operator_type',
+        'occurred_at',
+    ];
+
+    protected $casts = [
+        'evidence' => 'array',
+        'occurred_at' => 'datetime',
+    ];
+
+    public function user()
+    {
+        return $this->belongsTo(MemberUser::class, 'user_id');
+    }
+
+    public function order()
+    {
+        return $this->belongsTo(Order::class, 'related_order_id');
+    }
+}

+ 107 - 0
app/Services/MemberUserService.php

@@ -2,8 +2,12 @@
 
 namespace App\Services;
 
+use App\Enums\TransactionStatus;
+use App\Enums\TransactionType;
 use App\Enums\UserStatus;
+use App\Models\MemberBadBehavior;
 use App\Models\MemberUser;
+use App\Models\WalletTransRecord;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
@@ -99,4 +103,107 @@ class MemberUserService extends AdminService
             throw $e;
         }
     }
+
+    /**
+     * 冻结用户余额
+     *
+     * @param  array  $data  包含 user_id, amount 和 reason
+     */
+    public function freezeUserBalance(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 获取用户及其钱包信息
+            $user = MemberUser::with('wallet')->findOrFail($data['user_id']);
+            $wallet = $user->wallet()->lockForUpdate()->firstOrFail();
+
+            // 验证可用余额是否足够
+            abort_if($wallet->available_amount < $data['amount'], 422, '用户可用余额不足');
+
+            // 更新钱包余额
+            $wallet->available_amount -= $data['amount'];
+            $wallet->frozen_amount += $data['amount'];
+            $wallet->save();
+
+            // 记录冻结流水
+            WalletTransRecord::create([
+                'user_id' => $data['user_id'],
+                'type' => TransactionType::FREEZE->value,
+                'amount' => $data['amount'],
+                'before_amount' => $wallet->available_amount + $data['amount'],
+                'after_amount' => $wallet->available_amount,
+                'operator_id' => Auth::user()->id,
+                'operator_type' => 'admin',
+                'remark' => $data['reason'],
+                'state' => TransactionStatus::SUCCESS->value,
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '冻结成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('冻结用户余额失败', [
+                'user_id' => $data['user_id'],
+                'amount' => $data['amount'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
+
+    /**
+     * 记录用户不良行为
+     *
+     * @param  array  $data  包含 user_id, type, title, description, evidence(可选), related_order_id(可选)
+     */
+    public function recordBadBehavior(array $data): array
+    {
+        try {
+            DB::beginTransaction();
+
+            // 验证用户是否存在
+            $user = MemberUser::findOrFail($data['user_id']);
+
+            // 创建不良行为记录
+            MemberBadBehavior::create([
+                'user_id' => $data['user_id'],
+                'type' => $data['type'],
+                'title' => $data['title'],
+                'description' => $data['description'],
+                'evidence' => $data['evidence'] ?? null,
+                'related_order_id' => $data['related_order_id'] ?? null,
+                'operator_id' => Auth::user()->id,
+                'operator_type' => 'admin',
+                'occurred_at' => $data['occurred_at'] ?? now(),
+            ]);
+
+            DB::commit();
+
+            return [
+                'code' => 200,
+                'message' => '记录成功',
+                'data' => null,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('记录用户不良行为失败', [
+                'user_id' => $data['user_id'],
+                'type' => $data['type'],
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+
+            throw $e;
+        }
+    }
 }

+ 2 - 0
doc/系统设计/数据库设计/物理模型/xiaoding_test.md

@@ -168,5 +168,7 @@ Route::group([
         Route::post('order/end-service', [OrderController::class, 'endService']);
         Route::post('order/handle-alarm', [OrderController::class, 'handleAlarm']);
         Route::post('user/block', [MemberUserController::class, 'blockUser']);
+        Route::post('user/freeze-balance', [MemberUserController::class, 'freezeBalance']);
+        Route::post('user/bad-behavior', [MemberUserController::class, 'recordBadBehavior']);
     });
 });