719 lines
27 KiB
PHP
719 lines
27 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Controller\Api\V1;
|
||
|
||
use App\Controller\AbstractController;
|
||
use App\Middleware\AuthMiddleware;
|
||
use App\Model\User;
|
||
use App\Service\OperationLogService;
|
||
use App\Utils\RequestHelper;
|
||
use Carbon\Carbon;
|
||
use Hyperf\HttpServer\Annotation\Controller;
|
||
use Hyperf\HttpServer\Annotation\Middleware;
|
||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||
use OpenApi\Attributes as OA;
|
||
use Qbhy\HyperfAuth\AuthManager;
|
||
|
||
#[OA\Tag(name: 'Auth', description: '认证与个人信息')]
|
||
#[Controller(prefix: "/api/v1")]
|
||
class AuthController extends AbstractController
|
||
{
|
||
/**
|
||
* 用户注册
|
||
*/
|
||
#[OA\Post(
|
||
path: '/register',
|
||
summary: '用户注册',
|
||
description: '注册新用户,需提供用户名、密码和邮箱',
|
||
tags: ['Auth'],
|
||
requestBody: new OA\RequestBody(
|
||
required: true,
|
||
content: new OA\JsonContent(
|
||
required: ['username', 'password', 'email'],
|
||
properties: [
|
||
new OA\Property(property: 'username', type: 'string', minLength: 3, maxLength: 20, example: 'new_user'),
|
||
new OA\Property(property: 'password', type: 'string', minLength: 6, maxLength: 32, example: 'Pass_1234'),
|
||
new OA\Property(property: 'email', type: 'string', format: 'email', maxLength: 100, example: 'user@example.com'),
|
||
]
|
||
)
|
||
),
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '注册成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '注册成功'),
|
||
new OA\Property(property: 'data', properties: [
|
||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||
new OA\Property(property: 'username', type: 'string', example: 'new_user'),
|
||
new OA\Property(property: 'email', type: 'string', example: 'user@example.com'),
|
||
], type: 'object'),
|
||
])
|
||
),
|
||
new OA\Response(response: 400, description: '参数校验失败或唯一性冲突', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "register", methods: "POST")]
|
||
public function register(RequestInterface $request, ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$username = $request->input('username');
|
||
$password = $request->input('password');
|
||
$email = $request->input('email');
|
||
|
||
// 校验 username
|
||
if (!is_string($username) || trim($username) === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '用户名不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$username = trim($username);
|
||
$username_length = strlen($username);
|
||
if ($username_length < 3 || $username_length > 20) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '用户名长度需在 3-20 个字符',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 校验 password
|
||
if (!is_string($password) || $password === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '密码不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$password_length = strlen($password);
|
||
if ($password_length < 6 || $password_length > 32) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '密码长度需在 6-32 个字符',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 校验 email
|
||
if (!is_string($email) || trim($email) === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$email = trim($email);
|
||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱格式不正确',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (strlen($email) > 100) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱长度不能超过 100 个字符',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 唯一性检查
|
||
if (User::query()->where('username', $username)->exists()) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '用户名已存在',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (User::query()->where('email', $email)->exists()) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱已被注册',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 创建用户
|
||
$user = User::create([
|
||
'username' => $username,
|
||
'password' => $password,
|
||
'email' => $email,
|
||
'status' => 1,
|
||
]);
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '注册成功',
|
||
'data' => [
|
||
'id' => $user->id,
|
||
'username' => $user->username,
|
||
'email' => $user->email,
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 用户登录
|
||
*/
|
||
#[OA\Post(
|
||
path: '/login',
|
||
summary: '用户登录',
|
||
description: '使用用户名和密码登录,返回 access_token 和 refresh_token',
|
||
tags: ['Auth'],
|
||
requestBody: new OA\RequestBody(
|
||
required: true,
|
||
content: new OA\JsonContent(
|
||
required: ['username', 'password'],
|
||
properties: [
|
||
new OA\Property(property: 'username', type: 'string', example: 'admin'),
|
||
new OA\Property(property: 'password', type: 'string', example: 'Pass_1234'),
|
||
]
|
||
)
|
||
),
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '登录成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '登录成功'),
|
||
new OA\Property(property: 'data', properties: [
|
||
new OA\Property(property: 'access_token', type: 'string'),
|
||
new OA\Property(property: 'refresh_token', type: 'string'),
|
||
new OA\Property(property: 'token_type', type: 'string', example: 'Bearer'),
|
||
new OA\Property(property: 'expires_in', type: 'integer', example: 7200),
|
||
new OA\Property(property: 'user', properties: [
|
||
new OA\Property(property: 'id', type: 'integer'),
|
||
new OA\Property(property: 'username', type: 'string'),
|
||
new OA\Property(property: 'email', type: 'string'),
|
||
new OA\Property(property: 'role', type: 'string', example: 'administrator'),
|
||
], type: 'object'),
|
||
], type: 'object'),
|
||
])
|
||
),
|
||
new OA\Response(response: 400, description: '参数校验失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 401, description: '用户名或密码错误', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 403, description: '账号已被禁用', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "login", methods: "POST")]
|
||
public function login(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$username = $request->input('username');
|
||
$password = $request->input('password');
|
||
|
||
// 校验参数
|
||
if (!is_string($username) || trim($username) === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '用户名不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (!is_string($password) || $password === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '密码不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 查找用户
|
||
$user = User::query()->where('username', trim($username))->first();
|
||
|
||
if (!$user) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '用户名或密码错误',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
// 验证密码
|
||
if (!$user->verifyPassword($password)) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '用户名或密码错误',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
// 检查用户状态
|
||
if ($user->status !== 1) {
|
||
return $response->json([
|
||
'code' => 403,
|
||
'message' => '账号已被禁用',
|
||
])->withStatus(403);
|
||
}
|
||
|
||
$user->load('role');
|
||
|
||
// 生成 Access Token(注入 role 到 JWT payload,前端从 JWT 解码 role 作为可信权限来源)
|
||
$token = $auth->guard('jwt')->login($user, ['role' => $user->role?->name ?? 'accessor']);
|
||
|
||
// 生成 Refresh Token
|
||
$refreshToken = bin2hex(random_bytes(32));
|
||
$user->refresh_token = $refreshToken;
|
||
$user->refresh_token_expires_at = Carbon::now()->addDays(30);
|
||
$user->save();
|
||
|
||
OperationLogService::log(
|
||
user_id: $user->id,
|
||
action: 'auth.login',
|
||
target_type: 'user',
|
||
target_id: $user->id,
|
||
description: "用户 {$user->username} 登录",
|
||
ip: RequestHelper::getClientIp($this->request),
|
||
);
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '登录成功',
|
||
'data' => [
|
||
'access_token' => $token,
|
||
'refresh_token' => $refreshToken,
|
||
'token_type' => 'Bearer',
|
||
'expires_in' => 7200,
|
||
'user' => [
|
||
'id' => $user->id,
|
||
'username' => $user->username,
|
||
'email' => $user->email,
|
||
'role' => $user->role?->name ?? 'accessor',
|
||
],
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 刷新 Access Token
|
||
*/
|
||
#[OA\Get(
|
||
path: '/refresh',
|
||
summary: '刷新 Access Token',
|
||
description: '使用 refresh_token 获取新的 access_token,同时轮换 refresh_token',
|
||
security: [['bearerAuth' => []]],
|
||
tags: ['Auth'],
|
||
parameters: [
|
||
new OA\Parameter(name: 'refresh_token', in: 'query', required: true, description: 'Refresh Token', schema: new OA\Schema(type: 'string')),
|
||
],
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: 'Token 刷新成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: 'Token 刷新成功'),
|
||
new OA\Property(property: 'data', properties: [
|
||
new OA\Property(property: 'access_token', type: 'string'),
|
||
new OA\Property(property: 'refresh_token', type: 'string'),
|
||
new OA\Property(property: 'token_type', type: 'string', example: 'Bearer'),
|
||
new OA\Property(property: 'expires_in', type: 'integer', example: 7200),
|
||
], type: 'object'),
|
||
])
|
||
),
|
||
new OA\Response(response: 400, description: '缺少 refresh_token 参数', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 401, description: 'refresh_token 无效或已过期', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 403, description: '账号已被禁用', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "refresh", methods: "GET")]
|
||
#[Middleware(AuthMiddleware::class)]
|
||
public function refresh(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$refreshToken = $request->input('refresh_token');
|
||
|
||
if (!is_string($refreshToken) || $refreshToken === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '缺少 refresh_token 参数',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 查找用户
|
||
$user = User::query()->where('refresh_token', $refreshToken)->first();
|
||
|
||
if (!$user) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '无效的 refresh_token',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
// 验证 refresh token 是否过期
|
||
if (!$user->isRefreshTokenValid()) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => 'refresh_token 已过期,请重新登录',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
// 检查用户状态
|
||
if ($user->status !== 1) {
|
||
return $response->json([
|
||
'code' => 403,
|
||
'message' => '账号已被禁用',
|
||
])->withStatus(403);
|
||
}
|
||
|
||
// 生成新的 Access Token(注入 role 到 JWT payload)
|
||
$user->load('role');
|
||
$token = $auth->guard('jwt')->login($user, ['role' => $user->role?->name ?? 'accessor']);
|
||
|
||
// 生成新的 Refresh Token(轮换以提升安全性)
|
||
$newRefreshToken = bin2hex(random_bytes(32));
|
||
$user->refresh_token = $newRefreshToken;
|
||
$user->refresh_token_expires_at = Carbon::now()->addDays(30);
|
||
$user->save();
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => 'Token 刷新成功',
|
||
'data' => [
|
||
'access_token' => $token,
|
||
'refresh_token' => $newRefreshToken,
|
||
'token_type' => 'Bearer',
|
||
'expires_in' => 7200,
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 获取当前用户信息
|
||
*/
|
||
#[OA\Get(
|
||
path: '/me',
|
||
summary: '获取当前用户信息',
|
||
security: [['bearerAuth' => []]],
|
||
tags: ['Auth'],
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '获取成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '获取成功'),
|
||
new OA\Property(property: 'data', properties: [
|
||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||
new OA\Property(property: 'username', type: 'string', example: 'admin'),
|
||
new OA\Property(property: 'email', type: 'string', example: 'admin@example.com'),
|
||
new OA\Property(property: 'status', type: 'integer', example: 1),
|
||
new OA\Property(property: 'api_key_enabled', type: 'boolean', example: true, description: 'API Key 功能是否启用'),
|
||
new OA\Property(property: 'ext', type: 'object', nullable: true),
|
||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
|
||
], type: 'object'),
|
||
])
|
||
),
|
||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "me", methods: "GET")]
|
||
#[Middleware(AuthMiddleware::class)]
|
||
public function me(AuthManager $auth, ResponseInterface $response): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$user = $auth->guard('jwt')->user();
|
||
|
||
if (!$user) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '未授权',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
$user->load('role');
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '获取成功',
|
||
'data' => [
|
||
'id' => $user->id,
|
||
'username' => $user->username,
|
||
'email' => $user->email,
|
||
'role' => $user->role?->name ?? 'accessor',
|
||
'status' => $user->status,
|
||
'api_key_enabled' => $user->api_key_enabled,
|
||
'ext' => $user->ext,
|
||
'created_at' => $user->created_at->toDateTimeString(),
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 更新个人信息
|
||
*
|
||
* 当前用户更新自己的 email 和 ext 字段
|
||
*/
|
||
#[OA\Put(
|
||
path: '/me/profile',
|
||
summary: '更新个人信息',
|
||
description: '当前用户更新自己的 email 和 ext 字段',
|
||
security: [['bearerAuth' => []]],
|
||
tags: ['Auth'],
|
||
requestBody: new OA\RequestBody(
|
||
required: true,
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'email', type: 'string', format: 'email', maxLength: 100, example: 'new@example.com'),
|
||
new OA\Property(property: 'ext', type: 'object', nullable: true, example: ['nickname' => 'user']),
|
||
])
|
||
),
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '更新成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '个人信息更新成功'),
|
||
new OA\Property(property: 'data', properties: [
|
||
new OA\Property(property: 'id', type: 'integer'),
|
||
new OA\Property(property: 'username', type: 'string'),
|
||
new OA\Property(property: 'email', type: 'string'),
|
||
new OA\Property(property: 'status', type: 'integer'),
|
||
new OA\Property(property: 'ext', type: 'object', nullable: true),
|
||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
|
||
], type: 'object'),
|
||
])
|
||
),
|
||
new OA\Response(response: 400, description: '参数校验失败或唯一性冲突', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "me/profile", methods: "PUT")]
|
||
#[Middleware(AuthMiddleware::class)]
|
||
public function updateProfile(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$user = $auth->guard('jwt')->user();
|
||
|
||
if (!$user instanceof User) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '未授权',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
$email = $request->input('email');
|
||
$ext = $request->input('ext');
|
||
$updates = [];
|
||
|
||
if ($email !== null) {
|
||
if (!is_string($email) || trim($email) === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$email = trim($email);
|
||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱格式不正确',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (strlen($email) > 100) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱长度不能超过 100 个字符',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (User::query()->where('email', $email)->where('id', '!=', $user->id)->exists()) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '邮箱已被注册',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$updates['email'] = $email;
|
||
}
|
||
|
||
if ($ext !== null) {
|
||
if (!is_array($ext)) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => 'ext 必须为对象',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$updates['ext'] = $ext;
|
||
}
|
||
|
||
if ($updates === []) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '缺少可更新字段',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$user->fill($updates);
|
||
$user->save();
|
||
$user->refresh();
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '个人信息更新成功',
|
||
'data' => [
|
||
'id' => $user->id,
|
||
'username' => $user->username,
|
||
'email' => $user->email,
|
||
'status' => $user->status,
|
||
'ext' => $user->ext,
|
||
'created_at' => $user->created_at->toDateTimeString(),
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 修改密码
|
||
*
|
||
* 需验证旧密码,修改成功后清除 refresh_token 强制重新登录
|
||
*/
|
||
#[OA\Put(
|
||
path: '/me/password',
|
||
summary: '修改密码',
|
||
description: '修改当前用户密码,需验证旧密码。修改成功后清除 refresh_token,需重新登录',
|
||
security: [['bearerAuth' => []]],
|
||
tags: ['Auth'],
|
||
requestBody: new OA\RequestBody(
|
||
required: true,
|
||
content: new OA\JsonContent(
|
||
required: ['old_password', 'new_password'],
|
||
properties: [
|
||
new OA\Property(property: 'old_password', type: 'string', example: 'OldPass_1234'),
|
||
new OA\Property(property: 'new_password', type: 'string', minLength: 6, maxLength: 32, example: 'NewPass_5678'),
|
||
]
|
||
)
|
||
),
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '密码修改成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '密码修改成功,请重新登录'),
|
||
])
|
||
),
|
||
new OA\Response(response: 400, description: '参数校验失败或旧密码不正确', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "me/password", methods: "PUT")]
|
||
#[Middleware(AuthMiddleware::class)]
|
||
public function changePassword(RequestInterface $request, ResponseInterface $response, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array
|
||
{
|
||
$user = $auth->guard('jwt')->user();
|
||
|
||
if (!$user instanceof User) {
|
||
return $response->json([
|
||
'code' => 401,
|
||
'message' => '未授权',
|
||
])->withStatus(401);
|
||
}
|
||
|
||
$old_password = $request->input('old_password');
|
||
$new_password = $request->input('new_password');
|
||
|
||
// 验证旧密码
|
||
if (!is_string($old_password) || $old_password === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '旧密码不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
if (!$user->verifyPassword($old_password)) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '旧密码不正确',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 验证新密码
|
||
if (!is_string($new_password) || $new_password === '') {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '新密码不能为空',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
$new_password_length = strlen($new_password);
|
||
if ($new_password_length < 6 || $new_password_length > 32) {
|
||
return $response->json([
|
||
'code' => 400,
|
||
'message' => '新密码长度需在 6-32 个字符',
|
||
])->withStatus(400);
|
||
}
|
||
|
||
// 修改密码并清除 refresh_token
|
||
$user->password = $new_password;
|
||
$user->refresh_token = null;
|
||
$user->refresh_token_expires_at = null;
|
||
$user->save();
|
||
|
||
OperationLogService::log(
|
||
user_id: $user->id,
|
||
action: 'user.password_change',
|
||
target_type: 'user',
|
||
target_id: $user->id,
|
||
description: "用户 {$user->username} 修改密码",
|
||
ip: RequestHelper::getClientIp($this->request),
|
||
);
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '密码修改成功,请重新登录',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 退出登录
|
||
*/
|
||
#[OA\Get(
|
||
path: '/logout',
|
||
summary: '退出登录',
|
||
description: '退出登录,清除 refresh_token 并注销当前 JWT token',
|
||
security: [['bearerAuth' => []]],
|
||
tags: ['Auth'],
|
||
responses: [
|
||
new OA\Response(
|
||
response: 200,
|
||
description: '退出成功',
|
||
content: new OA\JsonContent(properties: [
|
||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||
new OA\Property(property: 'message', type: 'string', example: '退出成功'),
|
||
])
|
||
),
|
||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||
]
|
||
)]
|
||
#[RequestMapping(path: "logout", methods: "GET")]
|
||
#[Middleware(AuthMiddleware::class)]
|
||
public function logout(AuthManager $auth): array
|
||
{
|
||
$user = $auth->guard('jwt')->user();
|
||
|
||
if ($user instanceof User) {
|
||
OperationLogService::log(
|
||
user_id: $user->id,
|
||
action: 'auth.logout',
|
||
target_type: 'user',
|
||
target_id: $user->id,
|
||
description: "用户 {$user->username} 退出登录",
|
||
ip: RequestHelper::getClientIp($this->request),
|
||
);
|
||
|
||
// 清除 refresh token
|
||
$user->refresh_token = null;
|
||
$user->refresh_token_expires_at = null;
|
||
$user->save();
|
||
}
|
||
|
||
// 注销当前 token
|
||
$auth->guard('jwt')->logout();
|
||
|
||
return [
|
||
'code' => 0,
|
||
'message' => '退出成功',
|
||
];
|
||
}
|
||
}
|