Browse Source

feat:我的团队列表

刘学玺 4 months ago
parent
commit
0dac8ecb0e

+ 64 - 0
app/Admin/Controllers/MarketDistTeamController.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\MarketDistTeamService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 我的分销团队
+ *
+ * @property MarketDistTeamService $service
+ */
+class MarketDistTeamController extends AdminController
+{
+	protected string $serviceName = MarketDistTeamService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('object_id', '对象编号'),
+				amis()->TableColumn('object_type', '对象类型'),
+				amis()->TableColumn('user_id', '被邀用户编号'),
+				amis()->TableColumn('level', '层级'),
+				amis()->TableColumn('state', '状态'),
+				amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+				amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+				$this->rowActions('dialog')
+			]);
+
+		return $this->baseList($crud);
+	}
+
+	public function form($isEdit = false)
+	{
+		return $this->baseForm()->body([
+			amis()->TextControl('object_id', '对象编号'),
+			amis()->TextControl('object_type', '对象类型'),
+			amis()->TextControl('user_id', '被邀用户编号'),
+			amis()->TextControl('level', '层级'),
+			amis()->TextControl('state', '状态'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('object_id', '对象编号')->static(),
+			amis()->TextControl('object_type', '对象类型')->static(),
+			amis()->TextControl('user_id', '被邀用户编号')->static(),
+			amis()->TextControl('level', '层级')->static(),
+			amis()->TextControl('state', '状态')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

+ 23 - 12
app/Exceptions/Handler.php

@@ -2,14 +2,10 @@
 
 namespace App\Exceptions;
 
-use Closure;
 use Exception;
 use Illuminate\Auth\AuthenticationException;
 use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
-use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
 use Illuminate\Session\TokenMismatchException;
-use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Validation\ValidationException;
 use Symfony\Component\HttpFoundation\Response;
@@ -45,9 +41,9 @@ class Handler extends ExceptionHandler
                 'ip' => request()->ip(),
                 'user_agent' => request()->userAgent(),
             ];
-            
+
             if ($e instanceof NotFoundHttpException) {
-                Log::error('Route not found: ' . request()->fullUrl(), $context);
+                Log::error('Route not found: '.request()->fullUrl(), $context);
             } else {
                 Log::error($e->getMessage(), $context);
             }
@@ -56,43 +52,58 @@ class Handler extends ExceptionHandler
         $this->renderable(function (NotFoundHttpException $e) {
             return response()->json([
                 'code' => 404,
-                'msg' => 'Route not found: ' . request()->fullUrl()
+                'msg' => 'Route not found: '.request()->fullUrl(),
             ], Response::HTTP_NOT_FOUND);
         });
 
         $this->renderable(function (TokenMismatchException $e) {
             return response()->json([
                 'code' => $e->getCode() ?: 419,
-                'msg' => $e->getMessage()
+                'msg' => $e->getMessage(),
             ], 419);
         });
 
         $this->renderable(function (AuthenticationException $e) {
             return response()->json([
                 'code' => $e->getCode() ?: 401,
-                'msg' => '登录超时!'
+                'msg' => '登录超时!',
             ], Response::HTTP_UNAUTHORIZED);
         });
 
         $this->renderable(function (ValidationException $e) {
             return response()->json([
                 'code' => $e->getCode() ?: 422,
-                'msg' => $e->getMessage()
+                'msg' => $e->getMessage(),
             ], Response::HTTP_UNPROCESSABLE_ENTITY);
         });
 
         $this->renderable(function (ApiException $e) {
             return response()->json([
                 'code' => $e->getCode() ?: 200,
-                'msg' => $e->getMessage()
+                'msg' => $e->getMessage(),
             ], 200);
         });
 
         $this->renderable(function (Throwable $e) {
             return response()->json([
                 'code' => $e->getCode() ?: 500,
-                'msg' => $e->getMessage()
+                'msg' => $e->getMessage(),
             ], Response::HTTP_INTERNAL_SERVER_ERROR);
         });
     }
+
+    public function render($request, Throwable $exception)
+    {
+        if ($request->expectsJson()) {
+            if ($exception instanceof HttpException) {
+                return response()->json([
+                    'code' => $exception->getStatusCode(),
+                    'message' => $exception->getMessage(),
+                    'data' => null,
+                ], $exception->getStatusCode());
+            }
+        }
+
+        return parent::render($request, $exception);
+    }
 }

+ 59 - 0
app/Http/Controllers/Client/AuthController.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Http\Controllers\Client;
+
+use App\Http\Controllers\Controller;
+use App\Services\Client\UserService;
+use Illuminate\Http\Request;
+
+class AuthController extends Controller
+{
+    protected $userService;
+
+    public function __construct(UserService $userService)
+    {
+        $this->userService = $userService;
+    }
+
+    /**
+     * 用户注册
+     *
+     * @return \Illuminate\Http\JsonResponse
+     *
+     * @description 用户注册接口
+     *
+     * @bodyParam mobile string required 手机号 Example: 13800138000
+     * @bodyParam code string required 验证码 Example: 123456
+     * @bodyParam invite_code string optional 邀请码 Example: ABC123
+     * @bodyParam invite_id integer optional 邀请人ID Example: 1
+     * @bodyParam invite_role string optional 邀请人角色(user) Example: user
+     *
+     * @response {
+     *  "code": 200,
+     *  "message": "注册成功",
+     *  "data": {
+     *    "user_id": 1,
+     *    "mobile": "13800138000",
+     *    "invite_code": "ABC123"
+     *  }
+     * }
+     */
+    public function register(Request $request)
+    {
+        $validated = $request->validate([
+            'mobile' => 'required|string|size:11|regex:/^1[3-9]\d{9}$/',
+            'code' => 'required|string|size:6',
+            'invite_code' => 'nullable|string|size:6',
+            'invite_id' => 'nullable|integer|exists:member_users,id',
+            'invite_role' => 'nullable|string|in:user',
+        ]);
+
+        return $this->userService->register(
+            $validated['mobile'],
+            $validated['code'],
+            $validated['invite_code'] ?? null,
+            $validated['invite_id'] ?? null,
+            $validated['invite_role'] ?? null
+        );
+    }
+}

+ 64 - 0
app/Http/Controllers/Client/MarkDistTeamController.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http\Controllers\Client;
+
+use App\Http\Controllers\Controller;
+use App\Services\Client\MarketDistTeamService;
+use Auth;
+
+/**
+ * @group 用户端
+ *
+ * 团队相关的API接口
+ */
+class MarkDistTeamController extends Controller
+{
+    protected MarketDistTeamService $service;
+
+    public function __construct(MarketDistTeamService $service)
+    {
+        $this->service = $service;
+    }
+
+    /**
+     * [团队]我的团队
+     *
+     * @description 获取我的团队列表,包含团队成员信息
+     *
+     * @queryParam page int 页码. Example: 1
+     * @queryParam per_page int 每页数量. Example: 15
+     *
+     * @response {
+     *  "code": 200,
+     *  "message": "success",
+     *  "data": {
+     *    "current_page": 1,
+     *    "data": [
+     *      {
+     *        "id": 1,
+     *        "team_name": "销售一组",
+     *        "leader_id": 10,
+     *        "leader_name": "张三",
+     *        "member_count": 5,
+     *        "created_at": "2024-03-20 10:00:00",
+     *        "members": [
+     *          {
+     *            "id": 11,
+     *            "name": "李四",
+     *            "avatar": "http://example.com/avatar.jpg",
+     *            "role": "member",
+     *            "join_time": "2024-03-20 10:00:00"
+     *          }
+     *        ]
+     *      }
+     *    ],
+     *    "total": 10,
+     *    "per_page": 15
+     *  }
+     * }
+     */
+    public function list()
+    {
+        return $this->service->getTeamList(Auth::user()->id);
+    }
+}

+ 0 - 30
app/Http/Controllers/Client/TeamController.php

@@ -1,30 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Client;
-
-use App\Http\Controllers\Controller;
-use App\Services\Client\TeamService;
-use Auth;
-
-/**
- * @group 用户端
- *
- * 团队相关的API接口
- */
-class TeamController extends Controller
-{
-    protected TeamService $service;
-
-    public function __construct(TeamService $service)
-    {
-        $this->service = $service;
-    }
-
-    /**
-     * 获取我的团队列表
-     */
-    public function list()
-    {
-        return $this->service->getTeamList(Auth::user()->id);
-    }
-}

+ 42 - 0
app/Http/Controllers/Client/UserController.php

@@ -42,6 +42,48 @@ class UserController extends Controller
         return $this->service->getUserInfo();
     }
 
+    /**
+     * [用户]用户注册
+     *
+     * @return \Illuminate\Http\JsonResponse
+     *
+     * @description 用户注册接口
+     *
+     * @bodyParam mobile string required 手机号 Example: 13800138000
+     * @bodyParam code string required 验证码 Example: 123456
+     * @bodyParam invite_code string optional 邀请码 Example: ABC123
+     * @bodyParam invite_id integer optional 邀请人ID Example: 1
+     * @bodyParam invite_role string optional 邀请人角色(user) Example: memberUser
+     *
+     * @response {
+     *  "code": 200,
+     *  "message": "注册成功",
+     *  "data": {
+     *    "user_id": 1,
+     *    "mobile": "13800138000",
+     *    "invite_code": "ABC123"
+     *  }
+     * }
+     */
+    public function register(Request $request)
+    {
+        $validated = $request->validate([
+            'mobile' => 'required|string|size:11|regex:/^1[3-9]\d{9}$/',
+            'code' => 'required|string|size:6',
+            'invite_code' => 'nullable|string|size:6',
+            'invite_id' => 'nullable|integer',
+            'invite_role' => 'nullable|string|in:memberUser',
+        ]);
+
+        return $this->service->register(
+            $validated['mobile'],
+            $validated['code'],
+            $validated['invite_code'] ?? null,
+            $validated['invite_id'] ?? null,
+            $validated['invite_role'] ?? null
+        );
+    }
+
     /**
      * [用户]修改用户信息
      *

+ 34 - 0
app/Models/MarketDistTeam.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 我的分销团队
+ */
+class MarketDistTeam extends Model
+{
+    use SoftDeletes;
+
+    protected $table = 'market_dist_teams';
+
+    protected $guarded = [];
+
+    /**
+     * 获取被邀请用户信息
+     */
+    public function user()
+    {
+        return $this->belongsTo(MemberUser::class, 'user_id');
+    }
+
+    /**
+     * 获取邀请人信息
+     */
+    public function inviter()
+    {
+        return $this->morphTo('object');
+    }
+}

+ 11 - 0
app/Models/MemberUser.php

@@ -11,6 +11,17 @@ class MemberUser extends Model
 {
     use HasApiTokens, SoftDeletes;
 
+    protected $guarded = [];
+
+    /**
+     * 隐藏字段
+     *
+     * @var array
+     */
+    protected $hidden = [
+        'password',
+    ];
+
     /**
      * 获取用户的默认地址
      */

+ 100 - 0
app/Services/Client/MarketDistTeamService.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace App\Services\Client;
+
+use App\Models\MarketDistTeam;
+use App\Models\MemberUser;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class MarketDistTeamService
+{
+    /**
+     * 角色与模型的映射关系
+     */
+    protected $roleModelMap = [
+        'user' => MemberUser::class,
+        // 可以添加其他角色映射
+        // 'agent' => Agent::class,
+        // 'coach' => Coach::class,
+    ];
+
+    /**
+     * 创建邀请关系
+     *
+     * @param  MemberUser  $user  被邀请用户
+     * @param  string|null  $invite_code  邀请码
+     * @param  int|null  $invite_id  邀请人ID
+     * @param  string|null  $invite_role  邀请人角色
+     */
+    public function createInviteRelation(MemberUser $user, ?string $invite_code = null, ?int $invite_id = null, ?string $invite_role = null): bool
+    {
+        try {
+            DB::beginTransaction();
+
+            // 检查用户是否已有邀请关系
+            $existingTeam = $user->inviter;
+
+            if (! $existingTeam) {
+                // 获取对应的模型类
+                $inviterModelClass = $this->roleModelMap[$invite_role] ?? null;
+
+                if ($inviterModelClass) {
+                    $inviter = $inviterModelClass::find($invite_id);
+
+                    if ($inviter && $invite_code === strtoupper(substr(md5($inviter->id), 0, 6))) {
+                        // 记录邀请关系到Team表
+                        MarketDistTeam::create([
+                            'object_id' => $inviter->id,
+                            'object_type' => get_class($inviter),
+                            'user_id' => $user->id,
+                            'level' => 1,
+                            'state' => 1,
+                        ]);
+
+                        DB::commit();
+
+                        return true;
+                    }
+                }
+            }
+
+            DB::commit();
+
+            return false;
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('创建邀请关系失败: '.$e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取团队列表
+     */
+    public function getTeamList($userId)
+    {
+        try {
+            // 获取当前用户
+            $user = MemberUser::findOrFail($userId);
+
+            // 检查用户状态
+            abort_if($user->state !== 'enable', 403, '用户状态异常');
+
+            // 获取下级用户列表
+            $teamList = $user->teams()->where('state', 1)
+                ->with(['user'])  // 关联用户
+                ->orderBy('created_at', 'desc')
+                ->get();
+
+            return response()->json([
+                'code' => 200,
+                'message' => '获取成功',
+                'data' => $teamList,
+            ]);
+        } catch (\Exception $e) {
+            Log::error('获取团队列表失败: '.$e->getMessage());
+            throw $e;
+        }
+    }
+}

+ 0 - 29
app/Services/Client/TeamService.php

@@ -1,29 +0,0 @@
-<?php
-
-namespace App\Services\Client;
-
-use App\Models\MemberUser;
-
-class TeamService
-{
-    /**
-     * 获取团队列表
-     */
-    public function getTeamList($userId)
-    {
-        // 获取当前用户
-        $user = MemberUser::findOrFail($userId);
-
-        // 检查用户状态
-        abort_if($user->state !== 'enable', 403, '用户状态异常');
-
-        // 获取下级用户列表
-        $teamList = MemberUser::where('parent_id', $userId)
-            ->where('state', 'enable')
-            ->with(['coachUser'])  // 关联技师信息
-            ->orderBy('created_at', 'desc')
-            ->paginate(10);
-
-        return $teamList;
-    }
-}

+ 110 - 4
app/Services/Client/UserService.php

@@ -4,12 +4,22 @@ namespace App\Services\Client;
 
 use App\Models\CoachApplication;
 use App\Models\Feedback;
+use App\Models\MemberUser;
+use App\Models\User;
 use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use SimpleSoftwareIO\QrCode\Facades\QrCode;
 
 class UserService
 {
+    protected $marketDistTeamService;
+
+    public function __construct(MarketDistTeamService $marketDistTeamService)
+    {
+        $this->marketDistTeamService = $marketDistTeamService;
+    }
+
     /**
      * 获取当前用户信息
      *
@@ -32,6 +42,101 @@ class UserService
         }
     }
 
+    /**
+     * 用户注册
+     *
+     * @param  string  $mobile  手机号
+     * @param  string  $code  验证码
+     * @param  string|null  $invite_code  邀请码(选填)
+     * @param  int|null  $invite_id  邀请人ID(选填)
+     * @param  string|null  $invite_role  邀请人角色(选填)
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function register(string $mobile, string $code, ?string $invite_code = null, ?int $invite_id = null, ?string $invite_role = null)
+    {
+        try {
+            // 开启事务
+            DB::beginTransaction();
+
+            // 检查手机号是否已注册
+            if (MemberUser::where('mobile', $mobile)->exists()) {
+                return response()->json([
+                    'code' => 422,
+                    'message' => '该手机号已注册',
+                    'data' => null,
+                ]);
+            }
+
+            // 验证手机验证码
+            if (! $this->verifySmsCode($mobile, $code)) {
+                return response()->json([
+                    'code' => 422,
+                    'message' => '验证码错误或已过期',
+                    'data' => null,
+                ]);
+            }
+
+            // 创建用户数据
+            $userData = [
+                'mobile' => $mobile,
+                'password' => bcrypt(substr($mobile, -6)), // 默认密码为手机号后6位
+                'nickname' => substr_replace($mobile, '****', 3, 4), // 默认昵称为手机号(中间4位隐藏)
+            ];
+
+            // 创建用户
+            $user = MemberUser::create($userData);
+
+            // 处理邀请关系
+            if ($invite_code && $invite_role && $invite_id) {
+                $this->marketDistTeamService->createInviteRelation(
+                    $user,
+                    $invite_code,
+                    $invite_id,
+                    $invite_role
+                );
+            }
+
+            DB::commit();
+
+            return response()->json([
+                'code' => 200,
+                'message' => '注册成功',
+                'data' => [
+                    'user_id' => $user->id,
+                    'mobile' => $mobile,
+                    'invite_code' => $user->invite_code,
+                ],
+            ]);
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+            Log::error('用户注册失败: '.$e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证短信验证码
+     */
+    private function verifySmsCode(string $mobile, string $code): bool
+    {
+        try {
+            // TODO: 实现验证码验证逻辑
+            // 可以通过Redis验证,示例:
+            // $cacheKey = "sms_code:{$mobile}";
+            // $cacheCode = Redis::get($cacheKey);
+            // if (!$cacheCode || $cacheCode !== $code) {
+            //     return false;
+            // }
+            // Redis::del($cacheKey); // 验证成功后删除验证码
+            return true;
+        } catch (\Exception $e) {
+            Log::error('验证码验证失败: '.$e->getMessage());
+
+            return false;
+        }
+    }
+
     /**
      * 更新当前用户信息
      *
@@ -124,9 +229,9 @@ class UserService
 
             // 生成带参数的网页链接
             $qrContent = config('app.url').'/invite?'.http_build_query([
-                'user_id' => $user->id,
-                'role' => 'user',
-                'code' => $inviteCode,
+                'invite_id' => $user->id,
+                'invite_role' => 'user',
+                'invite_code' => $inviteCode,
             ]);
 
             // 使用QrCode库生成SVG格式的二维码
@@ -141,7 +246,8 @@ class UserService
 
             // 记录生成日志
             Log::info('用户生成邀请码:', [
-                'user_id' => $user->id,
+                'invite_id' => $user->id,
+                'invite_role' => 'user',
                 'invite_code' => $inviteCode,
                 'invite_url' => $qrContent,  // 记录生成的邀请链接
             ]);

+ 17 - 0
app/Services/MarketDistTeamService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\MarketDistTeam;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 我的分销团队
+ *
+ * @method MarketDistTeam getModel()
+ * @method MarketDistTeam|\Illuminate\Database\Query\Builder query()
+ */
+class MarketDistTeamService extends AdminService
+{
+	protected string $modelName = MarketDistTeam::class;
+}

+ 38 - 0
database/migrations/2024_11_25_130010_create_market_dist_teams_table.php

@@ -0,0 +1,38 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('market_dist_teams', function (Blueprint $table) {
+            $table->comment('我的分销团队');
+            $table->increments('id');
+            $table->unsignedBigInteger('object_id')->comment('对象编号');
+            $table->string('object_type')->default('')->comment('对象类型');
+            $table->unsignedBigInteger('user_id')->comment('被邀用户编号');
+            $table->string('level')->default('LEVEL_1')->comment('层级');
+            $table->string('state')->default('ENABLE')->comment('状态');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('market_dist_teams');
+    }
+};

+ 2 - 0
routes/admin.php

@@ -89,5 +89,7 @@ Route::group([
     $router->resource('coach_project', \App\Admin\Controllers\CoachProjectController::class);
     // 代理商项目设置
     $router->resource('agent_project_config', \App\Admin\Controllers\AgentProjectConfigController::class);
+    // 我的分销团队
+    $router->resource('market_dist_teams', \App\Admin\Controllers\MarketDistTeamController::class);
 
 });

+ 7 - 0
routes/api.php

@@ -38,6 +38,8 @@ Route::middleware('auth:sanctum')->group(function () {
     Route::prefix('user')->group(function () {
         // 获取用户信息
         Route::get('/', [UserController::class, 'show']);
+        // 用户注册
+        Route::post('/', [UserController::class, 'register']);
         // 更新用户信息
         Route::put('/', [UserController::class, 'update']);
 
@@ -112,4 +114,9 @@ Route::middleware('auth:sanctum')->group(function () {
         // 获取钱包信息
         Route::get('wallet', [WalletController::class, 'wallet']);
     });
+
+    // 团队管理路由
+    Route::prefix('team')->group(function () {
+        Route::get('list', [App\Http\Controllers\Client\MarkDistTeamController::class, 'list']);
+    });
 });