From 4eb74366ec6952e6d8586f8c7ddd047e759d675e Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Tue, 17 Mar 2026 15:54:53 +0800 Subject: [PATCH] update --- .../Api/V1/OperationLogController.php | 137 ++++++++++++++++++ .../Controller/api/v1/DataScopeController.php | 11 ++ .../app/Controller/api/v1/RoleController.php | 11 ++ .../app/Controller/api/v1/UserController.php | 32 ++++ 4 files changed, 191 insertions(+) create mode 100644 backend/app/Controller/Api/V1/OperationLogController.php diff --git a/backend/app/Controller/Api/V1/OperationLogController.php b/backend/app/Controller/Api/V1/OperationLogController.php new file mode 100644 index 0000000..a9118e0 --- /dev/null +++ b/backend/app/Controller/Api/V1/OperationLogController.php @@ -0,0 +1,137 @@ + 'exact', + 'action' => 'exact', + 'target_type' => 'exact', + 'created_at_from' => 'date_from', + 'created_at_to' => 'date_to', + ]; + } + + protected function getDefaultSort(): string + { + return 'created_at'; + } + + /** + * 操作日志列表 + */ + #[OA\Get( + path: '/api/v1/logs/operations', + summary: '操作日志列表', + description: '获取操作日志列表,支持分页、按用户/操作类型/目标类型/时间筛选。仅 admin 可访问。', + security: [['bearerAuth' => []]], + tags: ['Operation Logs'], + parameters: [ + new OA\Parameter(name: 'page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 1)), + new OA\Parameter(name: 'per_page', in: 'query', required: false, schema: new OA\Schema(type: 'integer', default: 15, maximum: 100)), + new OA\Parameter(name: 'user_id', in: 'query', required: false, description: '用户 ID 精确筛选', schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'action', in: 'query', required: false, description: '操作类型精确筛选', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'target_type', in: 'query', required: false, description: '目标类型精确筛选', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'created_at_from', in: 'query', required: false, description: '创建时间起始(含)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-01-01')), + new OA\Parameter(name: 'created_at_to', in: 'query', required: false, description: '创建时间截止(含)', schema: new OA\Schema(type: 'string', format: 'date', example: '2026-12-31')), + ], + 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: 'items', type: 'array', items: new OA\Items(ref: '#/components/schemas/OperationLogList')), + new OA\Property(property: 'total', type: 'integer', example: 100), + new OA\Property(property: 'page', type: 'integer', example: 1), + new OA\Property(property: 'per_page', type: 'integer', example: 15), + ], type: 'object'), + ]) + ), + 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: "", methods: "GET")] + public function index(): ResponseInterface|array + { + return parent::index(); + } + + /** + * 操作日志详情 + */ + #[OA\Get( + path: '/api/v1/logs/operations/{id}', + summary: '操作日志详情', + description: '获取操作日志详情,含完整操作详情 JSON。仅 admin 可访问。', + security: [['bearerAuth' => []]], + tags: ['Operation Logs'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: '操作日志 ID', schema: new OA\Schema(type: 'integer')), + ], + 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', ref: '#/components/schemas/OperationLogDetail'), + ]) + ), + 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')), + new OA\Response(response: 404, description: '数据不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "{id}", methods: "GET")] + public function show(int $id): ResponseInterface|array + { + return parent::show($id); + } +} diff --git a/backend/app/Controller/api/v1/DataScopeController.php b/backend/app/Controller/api/v1/DataScopeController.php index b845930..c421fae 100644 --- a/backend/app/Controller/api/v1/DataScopeController.php +++ b/backend/app/Controller/api/v1/DataScopeController.php @@ -8,6 +8,7 @@ use App\Controller\AbstractController; use App\Middleware\AuthMiddleware; use App\Middleware\PermissionMiddleware; use App\Model\Company; +use App\Service\OperationLogService; use App\Model\Platform; use App\Model\Store; use App\Model\User; @@ -238,6 +239,16 @@ class DataScopeController extends AbstractController // 重建 bitmap 并更新 Swoole\Table $this->scopeTableManager->rebuildUserScope($id); + OperationLogService::log( + user_id: OperationLogService::getCurrentUserId() ?? 0, + action: 'scope.update', + target_type: 'user', + target_id: $id, + description: "更新用户 #{$id} 数据权限", + detail: ['scopes' => $scopes], + ip: OperationLogService::getRequestIp(), + ); + return [ 'code' => 0, 'message' => '数据权限更新成功', diff --git a/backend/app/Controller/api/v1/RoleController.php b/backend/app/Controller/api/v1/RoleController.php index b73ef4c..f4e7cba 100644 --- a/backend/app/Controller/api/v1/RoleController.php +++ b/backend/app/Controller/api/v1/RoleController.php @@ -8,6 +8,7 @@ use App\Controller\AbstractController; use App\Middleware\AuthMiddleware; use App\Middleware\PermissionMiddleware; use App\Model\Role; +use App\Service\OperationLogService; use App\Model\RoleRouteOverride; use App\Model\Route; use App\Model\RouteGroup; @@ -172,6 +173,16 @@ class RoleController extends AbstractController $target_user->refresh(); $target_user->load('role'); + OperationLogService::log( + user_id: OperationLogService::getCurrentUserId() ?? 0, + action: 'role.update', + target_type: 'user', + target_id: $id, + description: "用户 #{$id} 角色变更为 {$new_role->name}", + detail: ['role_id' => $role_id, 'role_name' => $new_role->name], + ip: OperationLogService::getRequestIp(), + ); + return [ 'code' => 0, 'message' => '角色分配成功', diff --git a/backend/app/Controller/api/v1/UserController.php b/backend/app/Controller/api/v1/UserController.php index 51cf9f4..ffb500b 100644 --- a/backend/app/Controller/api/v1/UserController.php +++ b/backend/app/Controller/api/v1/UserController.php @@ -8,6 +8,7 @@ use App\Controller\AbstractController; use App\Middleware\AuthMiddleware; use App\Middleware\PermissionMiddleware; use App\Model\User; +use App\Service\OperationLogService; use Hyperf\HttpServer\Annotation\Controller; use Hyperf\HttpServer\Annotation\Middleware; use Hyperf\HttpServer\Annotation\RequestMapping; @@ -230,6 +231,16 @@ class UserController extends AbstractController 'status' => $status, ]); + OperationLogService::log( + user_id: OperationLogService::getCurrentUserId() ?? 0, + action: 'user.create', + target_type: 'user', + target_id: $user->id, + description: "创建用户 {$username}", + detail: ['email' => $email, 'status' => $status], + ip: OperationLogService::getRequestIp(), + ); + return [ 'code' => 0, 'message' => '创建成功', @@ -430,6 +441,16 @@ class UserController extends AbstractController $user->save(); $user->refresh(); + OperationLogService::log( + user_id: OperationLogService::getCurrentUserId() ?? 0, + action: 'user.update', + target_type: 'user', + target_id: $user->id, + description: "更新用户 {$user->username} 信息", + detail: $updates, + ip: OperationLogService::getRequestIp(), + ); + return [ 'code' => 0, 'message' => '更新成功', @@ -504,10 +525,21 @@ class UserController extends AbstractController ])->withStatus(400); } + $old_status = $user->status; $user->status = (int) $status_input; $user->save(); $user->refresh(); + OperationLogService::log( + user_id: OperationLogService::getCurrentUserId() ?? 0, + action: 'user.status_change', + target_type: 'user', + target_id: $user->id, + description: "用户 {$user->username} 状态变更", + detail: ['old_status' => $old_status, 'new_status' => $user->status], + ip: OperationLogService::getRequestIp(), + ); + return [ 'code' => 0, 'message' => '状态更新成功',