diff --git a/app/Events/Auth/Registered.php b/app/Events/Auth/Registered.php new file mode 100644 index 00000000..632f9579 --- /dev/null +++ b/app/Events/Auth/Registered.php @@ -0,0 +1,18 @@ +validate([ + 'phone' => ['bail', 'required', Rule::unique(User::class)], + 'password' => ['bail', 'required', 'string', 'min:6', 'max:32'], + 'verify_code' => ['bail', 'required', 'string'], + 'code' => ['bail', 'nullable', 'string'], + ]); + + // @todo 验证短信验证码 + + $inviter = null; + + if ( + $request->filled('code') + && is_null($inviter = UserInfo::where('code', $request->input('code'))->first()) + ) { + throw new BizException(__('Invalid invitation code')); + } + + $time = now(); + $ip = $request->realIp(); + + try { + DB::beginTransaction(); + + $user = new User($request->only(['phone', 'password'])); + $user->phone_verified_at = $time; + $user->register_ip = $ip; + $user->last_login_at = $time; + $user->last_login_ip = $ip; + $user->setCreatedAt($time); + $user->setUpdatedAt($time); + $user->save(); + + $user->userInfo()->create([ + 'inviter_id' => $inviter?->id, + ]); + + DB::commit(); + } catch (Throwable $e) { + DB::rollBack(); + + report($e); + + throw new BizException(__('Registration failed, please try again')); + } + + Registered::dispatch($user); + + return response()->json([ + 'token' => $user->createToken('app')->plainTextToken, + ]); + } +} diff --git a/app/Http/Controllers/Api/V1/Controller.php b/app/Http/Controllers/Api/V1/Controller.php new file mode 100644 index 00000000..77e6cdc6 --- /dev/null +++ b/app/Http/Controllers/Api/V1/Controller.php @@ -0,0 +1,9 @@ + 'int', ]; + /** + * 属于此用户的个人信息 + * + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function userInfo(): HasOne + { + return $this->hasOne(UserInfo::class); + } + /** * 设置此用户的密码 * diff --git a/app/Models/UserInfo.php b/app/Models/UserInfo.php index 0117bdf6..40a34b3a 100644 --- a/app/Models/UserInfo.php +++ b/app/Models/UserInfo.php @@ -49,12 +49,11 @@ class UserInfo extends Model // 如果没有邀请码,则自动分配邀请码 if ($userInfo->code === null) { do { - $userInfo->code = Str::randomAlpha(6); + $userInfo->code = strtolower(Str::randomAlpha(6)); } while (static::where('code', $userInfo->code)->exists()); + } elseif ($userInfo->isDirty('code')) { + $userInfo->code = strtolower($userInfo->code); } - - // 邀请码统一小写 - $userInfo->code = strtolower($userInfo->code); }); } } diff --git a/composer.json b/composer.json index 38ce153e..cd7bca12 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "guzzlehttp/guzzle": "^7.0.1", "kalnoy/nestedset": "^6.0", "laravel/framework": "^8.65", + "laravel/sanctum": "^2.12", "laravel/tinker": "^2.5" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 72222b75..00ca4326 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3f1c661b5bcaf6d0066e81774a3a2ad", + "content-hash": "50de900b3113c2d3d3f9c191fd6e6693", "packages": [ { "name": "asm89/stack-cors", @@ -1721,6 +1721,76 @@ }, "time": "2021-11-09T22:48:33+00:00" }, + { + "name": "laravel/sanctum", + "version": "v2.12.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "76b2d552c00477d520338889160f80a0cfb5fc55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/76b2d552c00477d520338889160f80a0cfb5fc55", + "reference": "76b2d552c00477d520338889160f80a0cfb5fc55", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "illuminate/contracts": "^6.9|^7.0|^8.0", + "illuminate/database": "^6.9|^7.0|^8.0", + "illuminate/support": "^6.9|^7.0|^8.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2021-11-16T16:57:07+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.0.3", diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 00000000..9281c92d --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,65 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, + ], + +]; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 00000000..3ce00023 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +} diff --git a/resources/lang/zh_CN.json b/resources/lang/zh_CN.json index 47363b12..5e512a10 100644 --- a/resources/lang/zh_CN.json +++ b/resources/lang/zh_CN.json @@ -1,3 +1,5 @@ { - ":resource not found": ":resource 未找到" + ":resource not found": ":resource 未找到", + "Invalid invitation code": "无效的邀请码", + "Registration failed, please try again": "注册失败,请重试" } diff --git a/routes/api.php b/routes/api.php index b3d9bbc7..9f44fd55 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1 +1,12 @@ 'v1', +], function () { + foreach (File::allFiles(base_path('routes/api/v1')) as $file) { + require $file; + } +}); diff --git a/routes/api/v1/auth.php b/routes/api/v1/auth.php new file mode 100644 index 00000000..d22bfa79 --- /dev/null +++ b/routes/api/v1/auth.php @@ -0,0 +1,6 @@ +