feat(认证与用户): 新增账号申请并支持批量分配角色权限

This commit is contained in:
Boen_Shi 2026-05-05 22:02:00 +08:00
parent 239e0edd05
commit ce907ee7c4
5 changed files with 114 additions and 10 deletions

View File

@ -9,13 +9,14 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
use Spatie\Permission\Models\Role;
#[Apidoc\Title('认证模块')] #[Apidoc\Title('认证模块')]
class AuthController extends Controller class AuthController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth:api')->except('login'); $this->middleware('auth:api')->except(['login', 'applyAccount']);
$this->middleware(function (Request $request, \Closure $next) { $this->middleware(function (Request $request, \Closure $next) {
/** @var User|null $user */ /** @var User|null $user */
$user = Auth::guard('api')->user(); $user = Auth::guard('api')->user();
@ -41,7 +42,7 @@ class AuthController extends Controller
'message' => '请先修改密码后再继续操作', 'message' => '请先修改密码后再继续操作',
'data' => ['force_password_change' => true], 'data' => ['force_password_change' => true],
], 423); ], 423);
})->except('login'); })->except(['login', 'applyAccount']);
} }
#[Apidoc\Title('登录'), Apidoc\Method('POST'), Apidoc\Url('/auth/login')] #[Apidoc\Title('登录'), Apidoc\Method('POST'), Apidoc\Url('/auth/login')]
@ -71,6 +72,19 @@ class AuthController extends Controller
return response()->json(['code' => 401, 'message' => '账号或密码错误', 'data' => null], 401); return response()->json(['code' => 401, 'message' => '账号或密码错误', 'data' => null], 401);
} }
/** @var User|null $user */
$user = Auth::guard('api')->user();
$permissionCount = (int) ($user?->getAllPermissions()->count() ?? 0);
if ($permissionCount <= 0) {
Auth::guard('api')->logout();
return response()->json([
'code' => 403,
'message' => '账号申请已通过,但尚未开通任何权限,请联系管理员分配权限后再登录',
'data' => null,
], 403);
}
$this->auditLog($request, 'login', ['metadata' => ['account_type' => $accountType]]); $this->auditLog($request, 'login', ['metadata' => ['account_type' => $accountType]]);
return response()->json([ return response()->json([
@ -84,6 +98,39 @@ class AuthController extends Controller
]); ]);
} }
#[Apidoc\Title('账号申请'), Apidoc\Method('POST'), Apidoc\Url('/auth/apply-account')]
public function applyAccount(Request $request): JsonResponse
{
$validated = $request->validate([
'nickname' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'phone' => ['required', 'string', 'max:32', 'unique:users,phone'],
'password' => ['required', 'confirmed', Password::min(6)],
]);
$user = User::query()->create([
'nickname' => $validated['nickname'],
'email' => $validated['email'],
'phone' => $validated['phone'],
'password' => $validated['password'],
'force_password_change' => false,
]);
$guestRole = Role::query()->firstOrCreate(
['name' => 'guest', 'guard_name' => 'api'],
['name' => 'guest', 'guard_name' => 'api']
);
$user->syncRoles([$guestRole->id]);
$this->auditLog($request, 'account_apply', ['metadata' => ['target_user_id' => $user->id]]);
return response()->json([
'code' => 0,
'message' => '申请提交成功,请联系或等待管理员开通权限后登录控制台',
'data' => null,
], 201);
}
#[Apidoc\Title('当前用户信息'), Apidoc\Method('GET'), Apidoc\Url('/auth/me')] #[Apidoc\Title('当前用户信息'), Apidoc\Method('GET'), Apidoc\Url('/auth/me')]
public function me(): JsonResponse public function me(): JsonResponse
{ {

View File

@ -26,9 +26,14 @@ class BastionAccountController extends Controller
} }
#[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')] #[Apidoc\Title('账号列表'), Apidoc\Method('GET'), Apidoc\Url('/accounts')]
public function index(): JsonResponse public function index(Request $request): JsonResponse
{ {
return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->latest()->paginate(20)]); $validated = $request->validate([
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
]);
$perPage = (int) ($validated['per_page'] ?? 20);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => BastionAccount::query()->latest()->paginate($perPage)]);
} }
#[Apidoc\Title('创建账号'), Apidoc\Method('POST'), Apidoc\Url('/accounts')] #[Apidoc\Title('创建账号'), Apidoc\Method('POST'), Apidoc\Url('/accounts')]

View File

@ -22,9 +22,13 @@ class PermissionController extends Controller
} }
#[Apidoc\Title('权限列表'), Apidoc\Method('GET'), Apidoc\Url('/permissions')] #[Apidoc\Title('权限列表'), Apidoc\Method('GET'), Apidoc\Url('/permissions')]
public function index(): JsonResponse public function index(Request $request): JsonResponse
{ {
$permissions = Permission::query()->latest()->paginate(200); $validated = $request->validate([
'per_page' => ['nullable', 'integer', 'min:1', 'max:200'],
]);
$perPage = (int) ($validated['per_page'] ?? 20);
$permissions = Permission::query()->latest()->paginate($perPage);
$permissions->getCollection()->transform(function (Permission $permission) { $permissions->getCollection()->transform(function (Permission $permission) {
return $this->applyDefaultMeta($permission); return $this->applyDefaultMeta($permission);

View File

@ -21,9 +21,13 @@ class RoleController extends Controller
} }
#[Apidoc\Title('角色列表'), Apidoc\Method('GET'), Apidoc\Url('/roles')] #[Apidoc\Title('角色列表'), Apidoc\Method('GET'), Apidoc\Url('/roles')]
public function index(): JsonResponse public function index(Request $request): JsonResponse
{ {
$roles = Role::query()->with('permissions')->latest()->paginate(20); $validated = $request->validate([
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
]);
$perPage = (int) ($validated['per_page'] ?? 20);
$roles = Role::query()->with('permissions')->latest()->paginate($perPage);
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $roles]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $roles]);
} }

View File

@ -24,7 +24,7 @@ class UserController extends Controller
{ {
$this->middleware('auth:api'); $this->middleware('auth:api');
$this->middleware('permission:platform.users.view,api')->only(['index', 'show']); $this->middleware('permission:platform.users.view,api')->only(['index', 'show']);
$this->middleware('permission:platform.users.manage,api')->only(['store', 'update', 'destroy', 'syncPermissions', 'import', 'importTemplate']); $this->middleware('permission:platform.users.manage,api')->only(['store', 'update', 'destroy', 'syncPermissions', 'syncBatchAssignments', 'import', 'importTemplate']);
} }
#[Apidoc\Title('用户列表'), Apidoc\Method('GET'), Apidoc\Url('/users')] #[Apidoc\Title('用户列表'), Apidoc\Method('GET'), Apidoc\Url('/users')]
@ -71,8 +71,12 @@ class UserController extends Controller
} }
#[Apidoc\Title('更新用户'), Apidoc\Method('PUT'), Apidoc\Url('/users/{id}')] #[Apidoc\Title('更新用户'), Apidoc\Method('PUT'), Apidoc\Url('/users/{id}')]
public function update(UpdateUserRequest $request, int $id): JsonResponse public function update(UpdateUserRequest $request, string $id): JsonResponse
{ {
if ($id === 'batch-assignments') {
return $this->syncBatchAssignments($request);
}
$user = User::query()->findOrFail($id); $user = User::query()->findOrFail($id);
$user->fill($request->safe()->except(['role_ids'])); $user->fill($request->safe()->except(['role_ids']));
@ -108,6 +112,46 @@ class UserController extends Controller
return response()->json(['code' => 0, 'message' => 'ok', 'data' => $user->load(['roles', 'permissions'])]); return response()->json(['code' => 0, 'message' => 'ok', 'data' => $user->load(['roles', 'permissions'])]);
} }
#[Apidoc\Title('批量设置用户组和用户权限'), Apidoc\Method('PUT'), Apidoc\Url('/users/batch-assignments')]
public function syncBatchAssignments(Request $request): JsonResponse
{
$validated = $request->validate([
'user_ids' => ['required', 'array', 'min:1'],
'user_ids.*' => ['integer', 'exists:users,id'],
'role_ids' => ['present', 'array'],
'role_ids.*' => ['integer', 'exists:roles,id'],
'permission_ids' => ['present', 'array'],
'permission_ids.*' => ['integer', 'exists:permissions,id'],
]);
$userIds = collect($validated['user_ids'])->map(fn (int $id): int => (int) $id)->unique()->values()->all();
$roleIds = collect($validated['role_ids'])->map(fn (int $id): int => (int) $id)->unique()->values()->all();
$permissionIds = collect($validated['permission_ids'])->map(fn (int $id): int => (int) $id)->unique()->values()->all();
$users = User::query()->whereIn('id', $userIds)->get();
foreach ($users as $user) {
$user->syncRoles($roleIds);
$user->syncPermissions($permissionIds);
$this->syncServerResourcePermissionsByDirectPermissions($user, $permissionIds);
}
$this->auditLog($request, 'users_batch_assignments_update', [
'metadata' => [
'target_user_ids' => $userIds,
'role_ids' => $roleIds,
'permission_ids' => $permissionIds,
],
]);
return response()->json([
'code' => 0,
'message' => 'ok',
'data' => [
'updated_count' => $users->count(),
],
]);
}
#[Apidoc\Title('删除用户'), Apidoc\Method('DELETE'), Apidoc\Url('/users/{id}')] #[Apidoc\Title('删除用户'), Apidoc\Method('DELETE'), Apidoc\Url('/users/{id}')]
public function destroy(Request $request, int $id): JsonResponse public function destroy(Request $request, int $id): JsonResponse
{ {