You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
4.7 KiB

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AdminUser;
use App\Models\OperationLog;
use App\Services\AdminMenuService;
use App\Services\GridMemberScopeService;
use App\Support\ApiResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
use ApiResponse;
public function __construct(
protected AdminMenuService $menuService,
protected GridMemberScopeService $gridScope
) {}
public function login(Request $request): JsonResponse
{
$started = microtime(true);
$data = $request->validate([
'username' => ['required', 'string', 'max:64'],
'password' => ['required', 'string', 'max:255'],
]);
$admin = AdminUser::query()->where('username', $data['username'])->first();
if (! $admin || ! Hash::check($data['password'], $admin->password_hash)) {
throw ValidationException::withMessages([
'username' => ['账号或密码错误'],
]);
}
if ((int) $admin->status !== 1) {
$this->recordLoginOperationLog($admin, $request, $started, 403);
return $this->fail('账号已停用', 403);
}
$admin->forceFill([
'last_login_at' => now(),
'last_login_ip' => $request->ip(),
])->save();
$admin->tokens()->delete();
$token = $admin->createToken('admin-api')->plainTextToken;
$this->recordLoginOperationLog($admin, $request, $started, 200);
return $this->ok([
'token' => $token,
'token_type' => 'Bearer',
'user' => $this->userPayload($admin),
]);
}
/**
* 登录时请求尚未通过 Sanctum 认证,操作日志中间件无法识别用户,故在控制器内单独写入。
*/
protected function recordLoginOperationLog(
AdminUser $admin,
Request $request,
float $startedAt,
int $responseCode,
): void {
OperationLog::query()->create([
'admin_user_id' => $admin->id,
'operator_name' => $admin->real_name ?: $admin->username,
'operated_at' => now(),
'http_method' => 'POST',
'api_path' => '/'.$request->path(),
'action_label' => $responseCode === 200 ? '管理员登录' : '管理员登录拒绝(账号停用)',
'ip' => $request->ip(),
'user_agent' => Str::limit((string) $request->userAgent(), 512, ''),
'request_summary' => ['username' => $admin->username],
'response_code' => $responseCode,
'duration_ms' => (int) round((microtime(true) - $startedAt) * 1000),
]);
}
public function logout(Request $request): JsonResponse
{
$request->user()?->currentAccessToken()?->delete();
return $this->ok(null, '已退出');
}
public function me(Request $request): JsonResponse
{
/** @var AdminUser $admin */
$admin = $request->user();
$admin->load('roles');
return $this->ok([
'user' => $this->userPayload($admin),
'permissions' => $admin->permissionCodes(),
'menus' => $this->menuService->treeForUser($admin),
]);
}
public function changePassword(Request $request): JsonResponse
{
$data = $request->validate([
'password' => ['required', 'string', 'min:6', 'max:255', 'confirmed'],
]);
/** @var AdminUser $admin */
$admin = $request->user();
$admin->forceFill([
'password_hash' => Hash::make($data['password']),
])->save();
return $this->ok(null, '密码已更新');
}
/** @return array<string, mixed> */
protected function userPayload(AdminUser $admin): array
{
return [
'id' => $admin->id,
'username' => $admin->username,
'real_name' => $admin->real_name,
'mobile' => $admin->mobile,
'email' => $admin->email,
'avatar_url' => $admin->avatar_url,
'status' => (int) $admin->status,
'roles' => $admin->roles->map(fn ($r) => [
'id' => $r->id,
'code' => $r->code,
'name' => $r->name,
])->values()->all(),
'is_super_admin' => $admin->isSuperAdmin(),
'is_grid_member' => $this->gridScope->isGridMember($admin),
'grid_scope' => $this->gridScope->isGridMember($admin)
? $this->gridScope->scopePayload($admin)
: null,
];
}
}