From 777c682a4e7401ff140eea16c13b9539ba7e9fcb Mon Sep 17 00:00:00 2001 From: Boen_Shi Date: Thu, 30 Apr 2026 14:41:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=94=A8=E6=88=B7=E5=AE=89=E5=85=A8):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A6=81=E6=B1=82=E6=9B=B4=E6=94=B9=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=B9=B6=E5=BC=BA=E5=88=B6=E7=99=BB=E5=BD=95=E5=90=8E?= =?UTF-8?q?=E6=94=B9=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 users.force_password_change 字段与迁移 - 用户新增/编辑/批量导入支持要求更改密码 - 登录后未改密用户仅允许访问改密相关接口 --- app/Http/Controllers/Api/AuthController.php | 25 +++++++++++++++++++ app/Http/Controllers/Api/UserController.php | 17 ++++++++++--- app/Http/Requests/StoreUserRequest.php | 1 + app/Http/Requests/UpdateUserRequest.php | 1 + app/Models/User.php | 4 ++- ...d_force_password_change_to_users_table.php | 25 +++++++++++++++++++ 6 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2026_04_30_220000_add_force_password_change_to_users_table.php diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php index cbc88c4..c52bdf8 100644 --- a/app/Http/Controllers/Api/AuthController.php +++ b/app/Http/Controllers/Api/AuthController.php @@ -16,6 +16,29 @@ class AuthController extends Controller public function __construct() { $this->middleware('auth:api')->except('login'); + $this->middleware(function (Request $request, \Closure $next) { + /** @var User|null $user */ + $user = Auth::guard('api')->user(); + if (! $user || ! $user->force_password_change) { + return $next($request); + } + + $allowed = [ + 'api/auth/me', + 'api/auth/password', + 'api/auth/logout', + ]; + + if (in_array($request->path(), $allowed, true)) { + return $next($request); + } + + return response()->json([ + 'code' => 423, + 'message' => '请先修改密码后再继续操作', + 'data' => ['force_password_change' => true], + ], 423); + })->except('login'); } #[Apidoc\Title('登录'), Apidoc\Method('POST'), Apidoc\Url('/auth/login')] @@ -70,6 +93,7 @@ class AuthController extends Controller 'data' => [ 'user' => $user?->load('roles'), 'permissions' => $user?->getAllPermissions()->pluck('name')->values(), + 'force_password_change' => (bool) ($user?->force_password_change ?? false), ], ]); } @@ -104,6 +128,7 @@ class AuthController extends Controller ]); $user->password = $validated['password']; + $user->force_password_change = false; $user->save(); $this->auditLog($request, 'password_update'); diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index a806da8..233fd47 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -148,6 +148,7 @@ class UserController extends Controller 'email' => trim($this->firstMatchedValue($normalizedRow, ['email', 'mail', '邮箱'])), 'phone' => trim($this->firstMatchedValue($normalizedRow, ['phone', 'mobile', '手机号', '手机', '电话'])), 'password' => trim($this->firstMatchedValue($normalizedRow, ['password', 'passwd', 'pwd', '密码'])), + 'force_password_change' => $this->parseBooleanValue($this->firstMatchedValue($normalizedRow, ['force_password_change', 'require_password_change', '要求更改密码', 'force_change_password'])), ]; $validator = Validator::make($payload, [ @@ -387,6 +388,16 @@ class UserController extends Controller return ''; } + private function parseBooleanValue(string $value): bool + { + $normalized = strtolower(trim($value)); + if ($normalized === '') { + return false; + } + + return in_array($normalized, ['1', 'true', 'yes', 'y', 'on', '是', '真'], true); + } + private function buildUsersImportTemplateXlsx(string $xlsxPath): void { $zip = new \ZipArchive; @@ -418,9 +429,9 @@ class UserController extends Controller .''; $rows = [ - ['nickname', 'email', 'phone', 'password', 'role_ids'], - ['张三', 'zhangsan@example.com', '13800138000', 'Pass@123456', '1'], - ['李四', 'lisi@example.com', '13900139000', 'Pass@123456', '1,2'], + ['nickname', 'email', 'phone', 'password', 'role_ids', 'force_password_change'], + ['张三', 'zhangsan@example.com', '13800138000', 'Pass@123456', '1', '0'], + ['李四', 'lisi@example.com', '13900139000', 'Pass@123456', '1,2', '1'], ]; $sheetData = ''; diff --git a/app/Http/Requests/StoreUserRequest.php b/app/Http/Requests/StoreUserRequest.php index 0888c95..7879ce6 100644 --- a/app/Http/Requests/StoreUserRequest.php +++ b/app/Http/Requests/StoreUserRequest.php @@ -18,6 +18,7 @@ class StoreUserRequest extends FormRequest 'email' => ['required', 'email', 'max:255', 'unique:users,email'], 'phone' => ['nullable', 'string', 'max:32', 'unique:users,phone'], 'password' => ['required', 'string', 'min:6'], + 'force_password_change' => ['sometimes', 'boolean'], 'role_ids' => ['sometimes', 'array'], 'role_ids.*' => ['integer', 'exists:roles,id'], ]; diff --git a/app/Http/Requests/UpdateUserRequest.php b/app/Http/Requests/UpdateUserRequest.php index afe6cab..650354e 100644 --- a/app/Http/Requests/UpdateUserRequest.php +++ b/app/Http/Requests/UpdateUserRequest.php @@ -21,6 +21,7 @@ class UpdateUserRequest extends FormRequest 'email' => ['sometimes', 'required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($userId)], 'phone' => ['nullable', 'string', 'max:32', Rule::unique('users', 'phone')->ignore($userId)], 'password' => ['sometimes', 'required', 'string', 'min:6'], + 'force_password_change' => ['sometimes', 'boolean'], 'role_ids' => ['sometimes', 'array'], 'role_ids.*' => ['integer', 'exists:roles,id'], ]; diff --git a/app/Models/User.php b/app/Models/User.php index 9da9c1a..37a0242 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,8 +3,8 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; @@ -23,6 +23,7 @@ class User extends Authenticatable implements JWTSubject 'email', 'phone', 'password', + 'force_password_change', ]; protected $hidden = [ @@ -62,6 +63,7 @@ class User extends Authenticatable implements JWTSubject return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'force_password_change' => 'boolean', ]; } } diff --git a/database/migrations/2026_04_30_220000_add_force_password_change_to_users_table.php b/database/migrations/2026_04_30_220000_add_force_password_change_to_users_table.php new file mode 100644 index 0000000..fd08d83 --- /dev/null +++ b/database/migrations/2026_04_30_220000_add_force_password_change_to_users_table.php @@ -0,0 +1,25 @@ +boolean('force_password_change') + ->default(false) + ->after('password') + ->comment('是否要求下次登录后必须修改密码'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('force_password_change'); + }); + } +};