Browse Source

feat:用户申请技师

刘学玺 4 months ago
parent
commit
0baa27ea16

+ 2 - 0
.gitignore

@@ -28,3 +28,5 @@ public/admin-assets
 /database.sql
 /sys_region.sql
 /字典规则.md
+.htaccess
+/owl-admin.code-workspace

+ 18 - 0
app/Exceptions/ApiException.php

@@ -0,0 +1,18 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/3/22 17:24
+ */
+namespace App\Exceptions;
+
+use Exception;
+
+class ApiException extends Exception
+{
+    public function __construct(array $apiErrorConst, \Throwable $previous = null)
+    {
+        parent::__construct($apiErrorConst['message'], $apiErrorConst['code'], $previous);
+    }
+}

+ 67 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Exceptions;
+
+use Closure;
+use Exception;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Session\TokenMismatchException;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Validation\ValidationException;
+use Symfony\Component\HttpFoundation\Response;
+use Throwable;
+
+class Handler
+{
+    public function handle($request, Closure $next)
+    {
+        $response = $next($request);
+        if ($response->exception)
+            return $this->render($request, $response->exception);
+        return $response;
+    }
+
+    public function render($request, Throwable $e): JsonResponse
+    {
+        $code = $e->getCode();
+        $message = $e->getMessage();
+        $status = Response::HTTP_INTERNAL_SERVER_ERROR;
+
+        // CSRF 跨站请求伪造
+        if ($e instanceof TokenMismatchException) {
+            $status = 419;
+        }
+
+        if ($e instanceof AuthenticationException) {
+            //Unauthenticated.
+            $status = Response::HTTP_UNAUTHORIZED;
+            $message = '登录超时!';
+        }
+
+        // 表单验证
+        if ($e instanceof ValidationException) {
+            $status = Response::HTTP_UNPROCESSABLE_ENTITY;
+//            $status = 200;
+        }
+
+        if ($e instanceof ApiException) {
+//            $status = match ($code) {
+//                Response::HTTP_UNPROCESSABLE_ENTITY => 200, // 表单验证
+//                default => $code,
+//            };
+            $status = 200;
+            // 400 错误请求
+            // 401 未授权
+            // 403 禁止访问
+            // 409 数据冲突
+            // 419 CSRF跨域伪造
+            // 422 验证规则
+            // 500 服务器内部错误
+
+        }
+        return response()->json(['code' => $code, 'msg' => $message], $status);
+    }
+
+}

+ 7 - 1
app/Http/Controllers/Client/ProjectController.php

@@ -15,6 +15,12 @@ class ProjectController extends Controller
         $this->service = $service;
     }
 
+    public function index(Request $request)
+    {
+        $areaCode = $request->input('area_code');
+        return $this->service->getProjectList($areaCode);
+    }
+
     /**
      * 获取项目详情
      */
@@ -33,4 +39,4 @@ class ProjectController extends Controller
         $areaCode = $request->input('area_code');
         return $this->service->getCoachProjectList($coachId, $areaCode);
     }
-} 
+}

+ 30 - 122
app/Http/Controllers/Client/UserController.php

@@ -18,98 +18,11 @@ class UserController extends Controller
         $this->service = $service;
     }
 
-    /**
-     * [用户管理] 发送验证码
-     * 
-     * 向指定手机号发送验证码
-     * 
-     * @bodyParam mobile string required 手机号码. Example: 13800138000
-     * @response {
-     *   "code": 200,
-     *   "message": "验证码发送成功",
-     *   "data": null
-     * }
-     */
-    public function sendVerifyCode(Request $request)
-    {
-        $mobile = $request->input('mobile');
-        return $this->service->sendVerifyCode($mobile);
-    }
-
-    /**
-     * [用户管理] 用户登录
-     * 
-     * 使用手机号和验证码登录
-     * 
-     * @bodyParam mobile string required 手机号码. Example: 13800138000
-     * @bodyParam code string required 验证码. Example: 123456
-     * @response {
-     *   "code": 200,
-     *   "message": "登录成功",
-     *   "data": {
-     *     "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
-     *     "user": {
-     *       "id": 1,
-     *       "mobile": "13800138000",
-     *       "nickname": "用户昵称"
-     *     }
-     *   }
-     * }
-     */
-    public function login(Request $request)
-    {
-        $mobile = $request->input('mobile');
-        $code = $request->input('code');
-        return $this->service->login($mobile, $code);
-    }
-
-    /**
-     * [用户管理] 微信登录
-     * 
-     * 使用微信openid登录
-     * 
-     * @bodyParam openid string required 微信openid. Example: wx_123456789
-     * @response {
-     *   "code": 200,
-     *   "message": "登录成功",
-     *   "data": {
-     *     "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
-     *     "user": {
-     *       "id": 1,
-     *       "openid": "wx_123456789",
-     *       "nickname": "微信昵称"
-     *     }
-     *   }
-     * }
-     */
-    public function wxLogin(Request $request)
-    {
-        $openid = $request->input('openid');
-        return $this->service->wxLogin($openid);
-    }
-
-    /**
-     * [用户管理] 用户退出
-     * 
-     * 退出登录,清除token
-     * 
-     * @authenticated
-     * @response {
-     *   "code": 200,
-     *   "message": "退出成功",
-     *   "data": null
-     * }
-     */
-    public function logout()
-    {
-        return $this->service->logout();
-    }
-
     /**
      * [用户管理] 获取用户信息
-     * 
+     *
      * 获取当前用户的信息
-     * 
+     *
      * @authenticated
      * @response {
      *   "code": 200,
@@ -121,16 +34,16 @@ class UserController extends Controller
      *   }
      * }
      */
-    public function info()
+    public function show()
     {
         return $this->service->getUserInfo();
     }
 
     /**
      * [用户管理] 修改用户信息
-     * 
+     *
      * 修改当前用户的信息
-     * 
+     *
      * @authenticated
      * @bodyParam nickname string 用户昵称. Example: 用户昵称
      * @bodyParam avatar string 用户头像. Example: https://example.com/avatar.jpg
@@ -140,7 +53,7 @@ class UserController extends Controller
      *   "data": null
      * }
      */
-    public function updateInfo(Request $request)
+    public function update(Request $request)
     {
         $data = $request->all();
         return $this->service->updateUserInfo($data);
@@ -148,9 +61,9 @@ class UserController extends Controller
 
     /**
      * [用户管理] 获取用户钱包
-     * 
+     *
      * 获取当前用户的钱包信息
-     * 
+     *
      * @authenticated
      * @response {
      *   "code": 200,
@@ -168,11 +81,13 @@ class UserController extends Controller
 
     /**
      * [用户管理] 用户提现
-     * 
+     *
      * 提现用户的余额
-     * 
+     *
      * @authenticated
      * @bodyParam amount decimal 提现金额. Example: 100.00
+     * @bodyParam type string 提现方式. Example: wechat
+     * @bodyParam area_code string 行政区划代码. Example: 330100
      * @response {
      *   "code": 200,
      *   "message": "提现成功",
@@ -182,31 +97,16 @@ class UserController extends Controller
     public function withdraw(Request $request)
     {
         $amount = $request->input('amount');
-        return $this->service->withdraw($amount);
-    }
-
-    /**
-     * [用户管理] 用户注销
-     * 
-     * 注销当前用户的账号
-     * 
-     * @authenticated
-     * @response {
-     *   "code": 200,
-     *   "message": "注销成功",
-     *   "data": null
-     * }
-     */
-    public function delete()
-    {
-        return $this->service->deleteAccount();
+        $type = $request->input('type', 'wechat');
+        $area_code = $request->input('area_code', '');
+        return $this->service->withdraw($amount, $type, $area_code);
     }
 
     /**
      * [用户管理] 用户反馈
-     * 
+     *
      * 提交用户的反馈信息
-     * 
+     *
      * @authenticated
      * @bodyParam content string 反馈内容. Example: 这是一个反馈信息
      * @response {
@@ -223,18 +123,26 @@ class UserController extends Controller
 
     /**
      * [用户管理] 申请成为技师
-     * 
+     *
      * 申请成为技师
-     * 
+     *
      * @authenticated
+     * @bodyParam mobile string 手机号. Example: 13800138000
+     * @bodyParam gender string 性别. Example: male
+     * @bodyParam work_years string 工作年限. Example: 5
+     * @bodyParam intention_city string 意向城市. Example: 杭州
      * @response {
      *   "code": 200,
      *   "message": "申请成功",
      *   "data": null
      * }
      */
-    public function applyCoach()
+    public function applyCoach(Request $request)
     {
-        return $this->service->applyCoach();
+        $mobile = $request->input('mobile');
+        $gender = $request->input('gender');
+        $work_years = $request->input('work_years');
+        $intention_city = $request->input('intention_city');
+        return $this->service->applyCoach($mobile, $gender, $work_years, $intention_city);
     }
-}
+}

+ 4 - 1
app/Models/CoachInfoRecord.php

@@ -13,4 +13,7 @@ class CoachInfoRecord extends Model
 	use SoftDeletes;
 
 	protected $table = 'coach_info_records';
-}
+
+    protected $guarded = [];
+
+}

+ 13 - 1
app/Models/CoachUser.php

@@ -13,4 +13,16 @@ class CoachUser extends Model
 	use SoftDeletes;
 
 	protected $table = 'coach_users';
-}
+
+    protected $guarded = [];
+
+    protected static function booted()
+	{
+		static::created(function ($user) {
+			$user->wallet()->create([
+				'owner_type' => CoachUser::class,
+				'owner_id' => $user->id
+			]);
+		});
+	}
+}

+ 4 - 1
app/Models/WalletWithdrawRecord.php

@@ -13,4 +13,7 @@ class WalletWithdrawRecord extends Model
 	use SoftDeletes;
 
 	protected $table = 'wallet_withdraw_records';
-}
+
+	protected $guarded = [];
+
+}

+ 52 - 3
app/Services/Client/ProjectService.php

@@ -5,11 +5,60 @@ namespace App\Services\Client;
 use App\Models\CoachUser;
 use App\Models\ShopCoachService;
 use App\Models\Project;
+use App\Models\AgentInfo;
 use Illuminate\Support\Facades\DB;
 
 class ProjectService
 {
 
+    /**
+     * 获取项目列表
+     */
+    public function getProjectList($areaCode)
+    {
+        // 根据区域代码获取代理商
+        $agent = $this->findAvailableAgent($areaCode);
+
+        // 获取项目列表
+        if ($agent) {
+            $projects = Project::where('state', 'enable')
+                ->where('agent_id', $agent->id)
+                ->paginate(10);
+        } else {
+            $projects = Project::where('state', 'enable')
+                ->paginate(10);
+        }
+
+        return $projects;
+    }
+
+    /**
+     * 递归查找可用的代理商
+     *
+     * @param string $areaCode 区域代码
+     * @return Agent|null
+     */
+    private function findAvailableAgent($areaCode)
+    {
+        // 先查找当前区域的代理商
+        $agent = AgentInfo::where('area_code', $areaCode)
+            ->where('state', 'enable')
+            ->first();
+
+        if ($agent) {
+            return $agent;
+        }
+
+        // 如果找不到,且区域代码长度大于2,则向上查找
+        if (strlen($areaCode) > 2) {
+            // 去掉最后两位,获取上级区域代码
+            $parentAreaCode = substr($areaCode, 0, -2);
+            return $this->findAvailableAgent($parentAreaCode);
+        }
+
+        return null;
+    }
+
     /**
      * 获取项目详情
      */
@@ -19,7 +68,7 @@ class ProjectService
         $agent = AgentInfo::where('business_area', 'like', "%{$areaCode}%")
             ->where('state', 'enable')
             ->first();
-            
+
         if (!$agent) {
             throw new \Exception('该区域暂无代理商');
         }
@@ -41,7 +90,7 @@ class ProjectService
 
         return $project;
     }
-    
+
     /**
      * 获取技师开通的项目列表
      */
@@ -74,4 +123,4 @@ class ProjectService
 
         return $projects;
     }
-} 
+}

+ 72 - 35
app/Services/Client/UserService.php

@@ -50,51 +50,67 @@ class UserService
      */
     public function getUserWallet()
     {
-        $wallet = Wallet::where('owner_id', Auth::id())
-            ->where('owner_type', MemberUser::class)
-            ->first();
-
+        $user = Auth::user();
+        $wallet = $user->wallet;
         if (!$wallet) {
             throw new \Exception('钱包不存在');
         }
 
         return $wallet;
+
     }
 
     /**
      * 用户提现
      */
-    public function withdraw($amount)
+    public function withdraw($amount, $type = 'wechat', $area_code = '')
     {
         // 获取当前用户
         $user = Auth::user();
         if (!$user || $user->state !== 'enable') {
             throw new \Exception('用户状态异常');
         }
-        
+
+        // 获取钱包并检查余额
+        $wallet = $user->wallet;
+        if ($wallet->state !== 'enable') {
+            throw new \Exception('钱包已冻结');
+        }
+
+        if ($wallet->available_balance < $amount) {
+            throw new \Exception('钱包余额不足');
+        }
+
         $config = SysConfig::where('key', 'withdraw_min_amount')->first();
-        $configValue = json_decode($config->value, true);
-        $minAmount = $configValue['minAmount'];
-        $maxAmount = $configValue['maxAmount'];
-        $fee = $configValue['fee'];
-        
+        $configValue = json_decode($config?->value, true);
+
+        $minAmount = $configValue['minAmount'] ?? 1;
+        $maxAmount = $configValue['maxAmount'] ?? 10000;
+        $fee = $configValue['fee'] ?? 0.00;
+
         if ($amount < $minAmount) {
             throw new \Exception("提现金额不能小于{$minAmount}元");
         }
-        
+
         if ($amount > $maxAmount) {
-            throw new \Exception("提现金额不能大于{$maxAmount}元"); 
+            throw new \Exception("提现金额不能大于{$maxAmount}元");
         }
 
         // 创建提现记录
-        DB::transaction(function() use ($user, $amount, $fee) {
+        DB::transaction(function() use ($user, $amount, $fee, $wallet, $type, $area_code) {
             WalletWithdrawRecord::create([
-                'user_id' => $user->id,
-                'amount' => $amount,
-                'fee' => $fee,
-                'type' => MemberUser::class,
-                'state' => 'pending'
+                'wallet_id' => $wallet->id,
+                'amount' => $amount,    // 提现金额
+                'withdraw_type' => $type,   // 提现方式
+                'fee' => $fee,    // 提现手续费
+                'area_code' => $area_code,    // 行政区划代码
+                'state' => 'processing'    // 状态
             ]);
+
+            // 扣除钱包余额
+            $wallet->available_balance -= $amount;
+            $wallet->total_balance -= $amount;
+            $wallet->save();
         });
 
         return ['message' => '提现申请已提交'];
@@ -122,31 +138,52 @@ class UserService
     /**
      * 申请成为技师
      */
-    public function applyCoach()
+    public function applyCoach($mobile = '', $gender = '', $work_years = '', $intention_city = '')
     {
         // 获取当前用户
-        $userId = Auth::id();
-        $user = MemberUser::findOrFail($userId);
-        
+        $user = Auth::user();
+
         // 检查用户状态
         if ($user->state !== 'enable') {
             throw new \Exception('用户状态异常');
         }
-        
-        // 检查是否已经是技师
-        $exists = CoachUser::where('user_id', $userId)->exists();
-        if ($exists) {
-            throw new \Exception('您已经是技师了');
+
+        // 检查是否已经申请过
+        $coach = $user->coach;
+        if ($coach) {
+            if ($coach->state === 'pending') {
+                throw new \Exception('您已提交申请,请等待审核');
+            }
+            if ($coach->state === 'enable') {
+                throw new \Exception('您已经是技师了');
+            }
+            if ($coach->state === 'disable') {
+                throw new \Exception('您的技师资格已被禁用');
+            }
         }
-        
+
         // 创建技师申请
-        DB::transaction(function() use ($userId) {
-            CoachUser::create([
-                'user_id' => $userId,
+        DB::transaction(function() use ($user , $mobile, $gender, $work_years, $intention_city) {
+            // 创建技师基本信息
+            $coach = CoachUser::create([
+                'user_id' => $user->id,
                 'state' => 'pending'  // 待审核状态
             ]);
+
+            $coach->infoRecords()->create([
+                'nickname' => $user->nickname ?? '',  // 姓名
+                'mobile' => $mobile,    // 服务电话
+                'avatar' => $user->avatar ?? '',  // 头像
+                'gender' => $gender ?? 'unknown',  // 性别
+                'work_years' => $work_years ?? '',  // 从业年份
+                'intention_city' => $intention_city ?? '',   // 意向城市
+                'state' => 'pending'
+            ]);
+
         });
-        
-        return ['message' => '申请已提交'];
+
+        return [
+            'message' => '申请已提交,请等待审核',
+        ];
     }
-} 
+}

+ 3 - 0
bootstrap/app.php

@@ -13,6 +13,9 @@ return Application::configure(basePath: dirname(__DIR__))
     )
     ->withMiddleware(function (Middleware $middleware) {
         //
+        $middleware->use([
+            \App\Exceptions\Handler::class
+        ]);
     })
     ->withExceptions(function (Exceptions $exceptions) {
         //

+ 3 - 3
database/migrations/2024_11_14_034951_create_coach_users_table.php

@@ -24,9 +24,9 @@ return new class extends Migration
             $table->string('level')->nullable()->comment('技师等级');
             $table->integer('virtual_order')->default(new \Illuminate\Database\Query\Expression('0'))->nullable()->comment('虚拟订单数');
             $table->decimal('score')->default(new \Illuminate\Database\Query\Expression('5.0'))->nullable()->comment('评分');
-            $table->string('work_status')->default('REST')->comment('工作状态');
-            $table->string('virtual_status')->default('DISABLE')->comment('虚拟状态');
-            $table->string('state')->default('ENABLE')->comment('状态');
+            $table->string('work_status')->default('rest')->comment('工作状态');
+            $table->string('virtual_status')->default('disable')->comment('虚拟状态');
+            $table->string('state')->default('pending')->comment('状态');
             $table->timestamps();
             $table->softDeletes();
         });

+ 3 - 3
database/migrations/2024_11_14_091306_create_wallet_withdraw_records_table.php

@@ -23,14 +23,14 @@ return new class extends Migration
             $table->string('withdraw_account_name')->default('')->comment('提现账户名');
             $table->decimal('amount')->comment('提现金额');
             $table->decimal('fee')->default(new \Illuminate\Database\Query\Expression('0.00'))->comment('提现手续费');
-            $table->timestamp('withdraw_time')->comment('提现时间');
+            $table->timestamp('withdraw_time')->nullable()->comment('提现时间');
             $table->string('area_code')->default('')->comment('行政区划代码');
             $table->string('remark')->nullable()->comment('备注');
             $table->string('auditor')->nullable()->comment('审核人');
             $table->timestamp('audit_time')->nullable()->comment('审核时间');
             $table->string('audit_remark')->nullable()->comment('审核回执');
-            $table->string('audit_state')->default('PENDING')->comment('审核状态');
-            $table->string('state')->default('PROCESSING')->comment('状态');
+            $table->string('audit_state')->default('pending')->comment('审核状态');
+            $table->string('state')->default('processing')->comment('状态');
             $table->timestamps();
             $table->softDeletes();
         });

+ 1 - 0
database/migrations/xxxx_xx_xx_create_coach_users_table.php

@@ -0,0 +1 @@
+ 

+ 0 - 0
laravel10-owladmin → nginx.conf


+ 1 - 0
php.ini

@@ -0,0 +1 @@
+ 

+ 12 - 5
routes/api.php

@@ -3,6 +3,7 @@
 use App\Http\Controllers\Client\AccountController;
 use App\Http\Controllers\Client\UserController;
 use App\Http\Controllers\ScribeController;
+use App\Http\Controllers\Client\ProjectController;
 use Illuminate\Support\Facades\Route;
 
 // API文档相关
@@ -31,17 +32,23 @@ Route::middleware('auth:sanctum')->group(function () {
     // 用户相关
     Route::prefix('user')->group(function () {
         // 获取用户信息
-        Route::get('info', [UserController::class, 'getUserInfo']);
+        Route::get('/', [UserController::class, 'show']);
         // 更新用户信息
-        Route::put('info', [UserController::class, 'updateUserInfo']);
+        Route::put('/', [UserController::class, 'update']);
+
         // 获取钱包信息
-        Route::get('wallet', [UserController::class, 'getUserWallet']);
+        Route::get('wallet', [UserController::class, 'wallet']);
         // 提现
         Route::post('withdraw', [UserController::class, 'withdraw']);
-    
+
         // 用户反馈
-        Route::post('feedback', [UserController::class, 'feedback']);
+        // Route::post('feedback', [UserController::class, 'feedback']);
         // 申请成为技师
         Route::post('apply-coach', [UserController::class, 'applyCoach']);
     });
+
+    Route::prefix('project')->group(function () {
+        Route::get('/{id}/detail', [ProjectController::class, 'detail']);
+        Route::get('/coach-list', [ProjectController::class, 'coachProjectList']);
+    });
 });