diff --git a/.kiro/specs/admin-management-features/requirements.md b/.kiro/specs/admin-management-features/requirements.md index 32a7ec8..752fdf4 100644 --- a/.kiro/specs/admin-management-features/requirements.md +++ b/.kiro/specs/admin-management-features/requirements.md @@ -217,6 +217,148 @@ --- +### 4. 权限管理系统 + +#### 4.1 用户故事 +作为系统管理员,我需要能够灵活地为用户、角色和分组配置不同功能模块的访问权限,以便实现细粒度的权限控制和数据隔离。 + +#### 4.2 功能描述 +实现基于角色、用户和分组的多维度权限管理系统,支持功能模块级和数据级权限控制。 + +#### 4.3 验收标准 +- [ ] 角色管理 + - [ ] 角色列表(名称、描述、权限数量、用户数量) + - [ ] 角色创建/编辑(名称、描述、权限配置) + - [ ] 角色删除(检查是否有关联用户) + - [ ] 预设角色(超级管理员、管理员、普通用户) + +- [ ] 权限配置界面 + - [ ] 按功能模块分组展示权限 + - [ ] 权限类型:viewAny(列表)、view(详情)、create(创建)、update(编辑)、delete(删除)、特殊操作 + - [ ] 支持批量授权/撤销 + - [ ] 权限继承关系展示 + +- [ ] 用户权限管理 + - [ ] 用户角色分配(支持多角色) + - [ ] 用户特殊权限配置(覆盖角色权限) + - [ ] 用户分组关联 + - [ ] 权限预览(显示用户的最终权限) + +- [ ] 分组权限管理 + - [ ] 分组数据访问权限(如专用知识库) + - [ ] 分组成员管理 + - [ ] 跨分组访问控制 + +- [ ] 权限验证 + - [ ] 菜单项根据权限动态显示/隐藏 + - [ ] 操作按钮根据权限动态显示/隐藏 + - [ ] API请求权限验证 + - [ ] 数据查询自动应用权限过滤 + +#### 4.4 权限模块定义 +使用 Spatie Permission 的命名约定(module.action格式): + +- **文档管理**: + - document.viewAny - 查看文档列表 + - document.view - 查看文档详情 + - document.create - 创建文档 + - document.update - 编辑文档 + - document.delete - 删除文档 + - document.download - 下载文档 + +- **系统设置**: + - system-setting.viewAny - 查看系统设置 + - system-setting.view - 查看设置详情 + - system-setting.update - 修改系统设置 + +- **操作日志**: + - activity-log.viewAny - 查看操作日志 + - activity-log.view - 查看日志详情 + - activity-log.export - 导出日志 + +- **终端管理**: + - terminal.viewAny - 查看终端列表 + - terminal.view - 查看终端详情 + - terminal.create - 创建终端 + - terminal.update - 编辑终端 + - terminal.delete - 删除终端 + - terminal.sync - 同步终端配置 + +- **SOP模板**: + - sop-template.viewAny - 查看SOP列表 + - sop-template.view - 查看SOP详情 + - sop-template.create - 创建SOP + - sop-template.update - 编辑SOP + - sop-template.delete - 删除SOP + - sop-template.publish - 发布SOP + - sop-template.archive - 归档SOP + +- **分组管理**: + - group.viewAny - 查看分组列表 + - group.view - 查看分组详情 + - group.create - 创建分组 + - group.update - 编辑分组 + - group.delete - 删除分组 + +- **用户管理**: + - user.viewAny - 查看用户列表 + - user.view - 查看用户详情 + - user.create - 创建用户 + - user.update - 编辑用户 + - user.delete - 删除用户 + +- **角色管理**: + - role.viewAny - 查看角色列表 + - role.view - 查看角色详情 + - role.create - 创建角色 + - role.update - 编辑角色 + - role.delete - 删除角色 + +#### 4.5 数据模型需求 +使用 Spatie Laravel Permission 包提供的模型和表结构: + +- **Role 模型**(角色)- 由 Spatie 包提供 + - name: string(角色名称,如 super-admin) + - guard_name: string(守卫名称,默认 web) + - 关联关系:belongsToMany(Permission)、belongsToMany(User) + +- **Permission 模型**(权限)- 由 Spatie 包提供 + - name: string(权限名称,如 document.create) + - guard_name: string(守卫名称,默认 web) + - 关联关系:belongsToMany(Role) + +- **model_has_permissions 表**(用户直接权限)- 由 Spatie 包提供 + - permission_id: bigint + - model_type: string(通常是 User) + - model_id: bigint(用户ID) + +- **model_has_roles 表**(用户角色关联)- 由 Spatie 包提供 + - role_id: bigint + - model_type: string(通常是 User) + - model_id: bigint(用户ID) + +- **role_has_permissions 表**(角色权限关联)- 由 Spatie 包提供 + - permission_id: bigint + - role_id: bigint + +注:Spatie 包会自动创建这些表和模型,无需手动创建。 + +#### 4.6 技术实现 +- 使用 **Spatie Laravel Permission** 包实现权限管理 +- 包提供的核心功能: + - Role(角色)模型和管理 + - Permission(权限)模型和管理 + - 用户角色和权限关联 + - 权限检查方法(hasPermissionTo、hasRole等) + - 中间件支持(role、permission) + - Blade指令支持(@role、@can等) +- 使用 Laravel Policy 实现业务逻辑权限验证 +- 使用 Gate 定义额外的权限规则 +- 在 Filament Resource 中集成权限检查 +- 权限缓存自动管理 + +--- + ## 技术栈 - **后端框架**: Laravel 12 @@ -237,7 +379,12 @@ - 所有操作需要身份验证 - 敏感配置(API密钥)需要加密存储 - 操作日志不可删除,只能归档 -- 权限控制:系统设置仅管理员可访问 +- **权限管理**: + - 支持基于角色的权限控制(RBAC) + - 支持基于用户的权限控制 + - 支持基于分组的权限控制 + - 功能模块级别的权限控制(查看、创建、编辑、删除、特殊操作) + - 数据级别的权限控制(如文档的全局/专用访问) ### 可用性要求 - 界面响应式设计,支持1920x1080及以上分辨率 @@ -256,13 +403,14 @@ ### Composer包 ```bash composer require spatie/laravel-activitylog +composer require spatie/laravel-permission # 权限管理包 composer require amidesfahani/filament-monaco-editor composer require maatwebsite/excel # 用于日志导出 ``` ### 数据库表 - system_settings -- activity_log +- activity_log(由 spatie/laravel-activitylog 创建) - terminals - terminal_knowledge_bases - terminal_prompts @@ -271,6 +419,11 @@ composer require maatwebsite/excel # 用于日志导出 - sop_steps - sop_interactive_tasks - sop_template_versions +- roles(由 spatie/laravel-permission 创建) +- permissions(由 spatie/laravel-permission 创建) +- model_has_permissions(由 spatie/laravel-permission 创建) +- model_has_roles(由 spatie/laravel-permission 创建) +- role_has_permissions(由 spatie/laravel-permission 创建) ## 实施优先级 diff --git a/.kiro/specs/admin-management-features/tasks.md b/.kiro/specs/admin-management-features/tasks.md index 92481dd..64148ee 100644 --- a/.kiro/specs/admin-management-features/tasks.md +++ b/.kiro/specs/admin-management-features/tasks.md @@ -269,140 +269,259 @@ - [x] 15.4.3 测试JSON导入 - [x] 15.4.4 测试批量导入 -## 阶段五:权限和安全(优先级:中) +## 阶段五:权限管理系统(优先级:高) -### 16. 权限策略实现 -- [ ] 16.1 创建SystemSettingPolicy - - [ ] 16.1.1 实现viewAny权限 - - [ ] 16.1.2 实现update权限 -- [ ] 16.2 创建TerminalPolicy - - [ ] 16.2.1 实现viewAny权限 - - [ ] 16.2.2 实现create权限 - - [ ] 16.2.3 实现update权限 - - [ ] 16.2.4 实现delete权限 - - [ ] 16.2.5 实现sync权限 -- [ ] 16.3 创建SopTemplatePolicy - - [ ] 16.3.1 实现viewAny权限 - - [ ] 16.3.2 实现create权限 - - [ ] 16.3.3 实现update权限 - - [ ] 16.3.4 实现delete权限 - - [ ] 16.3.5 实现publish权限 -- [ ] 16.4 注册所有策略 -- [ ] 16.5 测试权限控制 - - [ ] 16.5.1 测试管理员权限 - - [ ] 16.5.2 测试普通用户权限 - - [ ] 16.5.3 测试特殊权限 +### 16. Spatie Permission 包安装和配置 +- [ ] 16.1 安装 Spatie Permission 包 + - [ ] 16.1.1 运行 composer require spatie/laravel-permission + - [ ] 16.1.2 发布配置文件和迁移文件 + - [ ] 16.1.3 运行迁移创建权限表 + - [ ] 16.1.4 清除缓存 +- [ ] 16.2 配置 User 模型 + - [ ] 16.2.1 在 User 模型中添加 HasRoles trait + - [ ] 16.2.2 配置守卫(guard) + - [ ] 16.2.3 测试基本权限方法 +- [ ] 16.3 创建权限种子数据 + - [ ] 16.3.1 创建 PermissionSeeder + - [ ] 16.3.2 定义所有功能模块的权限(45个权限) + - [ ] 16.3.3 创建预设角色(super-admin、admin、user) + - [ ] 16.3.4 为角色分配权限 + - [ ] 16.3.5 运行种子数据 -### 17. 安全加固 -- [ ] 17.1 实现敏感配置加密 - - [ ] 17.1.1 创建加密服务 - - [ ] 17.1.2 在SystemSetting模型中集成 - - [ ] 17.1.3 更新表单字段类型 -- [ ] 17.2 实现操作确认 - - [ ] 17.2.1 为删除操作添加确认 - - [ ] 17.2.2 为发布操作添加确认 - - [ ] 17.2.3 为同步操作添加确认 -- [ ] 17.3 实现输入验证和过滤 - - [ ] 17.3.1 添加XSS过滤 - - [ ] 17.3.2 添加SQL注入防护 - - [ ] 17.3.3 添加文件上传验证 -- [ ] 17.4 测试安全功能 - - [ ] 17.4.1 测试加密存储 - - [ ] 17.4.2 测试操作确认 - - [ ] 17.4.3 测试输入验证 +### 17. 角色管理功能 +- [ ] 17.1 创建 RoleResource + - [ ] 17.1.1 定义表格列(名称、守卫、权限数、用户数) + - [ ] 17.1.2 添加搜索和筛选功能 + - [ ] 17.1.3 添加系统角色标识(super-admin不可删除) +- [ ] 17.2 创建角色表单 + - [ ] 17.2.1 添加基本信息字段(名称、守卫) + - [ ] 17.2.2 添加权限选择器(使用 CheckboxList,按模块分组) + - [ ] 17.2.3 添加表单验证规则 + - [ ] 17.2.4 实现权限同步逻辑(使用 syncPermissions) +- [ ] 17.3 实现角色删除保护 + - [ ] 17.3.1 检查角色是否为 super-admin + - [ ] 17.3.2 检查角色是否有关联用户 + - [ ] 17.3.3 添加删除确认提示 +- [ ] 17.4 测试角色管理功能 + - [ ] 17.4.1 测试角色 CRUD 操作 + - [ ] 17.4.2 测试权限分配(syncPermissions) + - [ ] 17.4.3 测试删除保护 + +### 18. 用户权限管理功能 +- [ ] 18.1 更新 UserResource + - [ ] 18.1.1 添加角色分配字段(使用 Select,支持多选) + - [ ] 18.1.2 添加分组关联字段 + - [ ] 18.1.3 添加直接权限配置 Section(使用 CheckboxList) + - [ ] 18.1.4 显示用户的所有权限预览(角色权限+直接权限) +- [ ] 18.2 实现用户权限保存逻辑 + - [ ] 18.2.1 使用 syncRoles 同步角色 + - [ ] 18.2.2 使用 syncPermissions 同步直接权限 + - [ ] 18.2.3 处理权限冲突(直接权限优先) +- [ ] 18.3 创建权限预览组件 + - [ ] 18.3.1 使用 Placeholder 组件显示权限 + - [ ] 18.3.2 按模块分组显示 + - [ ] 18.3.3 标识权限来源(角色/直接授予) + - [ ] 18.3.4 支持权限搜索 +- [ ] 18.4 测试用户权限功能 + - [ ] 18.4.1 测试角色分配(assignRole、syncRoles) + - [ ] 18.4.2 测试直接权限配置(givePermissionTo、syncPermissions) + - [ ] 18.4.3 测试权限检查(hasPermissionTo、can) + +### 19. 权限策略实现 +- [ ] 19.1 DocumentPolicy(已部分实现,需完善) + - [ ] 19.1.1 在 viewAny 中添加权限检查(document.viewAny) + - [ ] 19.1.2 在 view 中添加权限检查(document.view) + - [ ] 19.1.3 在 create 中添加权限检查(document.create) + - [ ] 19.1.4 在 update 中添加权限检查(document.update) + - [ ] 19.1.5 在 delete 中添加权限检查(document.delete) + - [ ] 19.1.6 在 download 中添加权限检查(document.download) + - [ ] 19.1.7 保留现有的分组访问控制逻辑 +- [ ] 19.2 SystemSettingPolicy + - [ ] 19.2.1 实现 viewAny(system-setting.viewAny) + - [ ] 19.2.2 实现 view(system-setting.view) + - [ ] 19.2.3 实现 update(system-setting.update) +- [ ] 19.3 ActivityLogPolicy + - [ ] 19.3.1 实现 viewAny(activity-log.viewAny) + - [ ] 19.3.2 实现 view(activity-log.view) + - [ ] 19.3.3 实现 export(activity-log.export) +- [ ] 19.4 TerminalPolicy(已部分实现,需完善) + - [ ] 19.4.1 在所有方法中添加权限检查 + - [ ] 19.4.2 实现 sync 权限检查(terminal.sync) + - [ ] 19.4.3 保留现有的管理员检查作为后备 +- [ ] 19.5 SopTemplatePolicy(已部分实现,需完善) + - [ ] 19.5.1 在所有方法中添加权限检查 + - [ ] 19.5.2 实现 publish 权限检查(sop-template.publish) + - [ ] 19.5.3 实现 archive 权限检查(sop-template.archive) + - [ ] 19.5.4 保留现有的状态检查逻辑 +- [ ] 19.6 GroupPolicy + - [ ] 19.6.1 实现 viewAny(group.viewAny) + - [ ] 19.6.2 实现 view(group.view) + - [ ] 19.6.3 实现 create(group.create) + - [ ] 19.6.4 实现 update(group.update) + - [ ] 19.6.5 实现 delete(group.delete,需检查关联文档) +- [ ] 19.7 UserPolicy + - [ ] 19.7.1 实现 viewAny(user.viewAny) + - [ ] 19.7.2 实现 view(user.view) + - [ ] 19.7.3 实现 create(user.create) + - [ ] 19.7.4 实现 update(user.update) + - [ ] 19.7.5 实现 delete(user.delete,不能删除自己) +- [ ] 19.8 RolePolicy + - [ ] 19.8.1 实现 viewAny(role.viewAny) + - [ ] 19.8.2 实现 view(role.view) + - [ ] 19.8.3 实现 create(role.create) + - [ ] 19.8.4 实现 update(role.update) + - [ ] 19.8.5 实现 delete(role.delete,super-admin 保护) +- [ ] 19.9 策略注册 + - [ ] 19.9.1 在 AppServiceProvider 中注册所有策略 + - [ ] 19.9.2 配置策略自动发现 + +### 20. Filament 资源权限集成 +- [ ] 20.1 更新所有 Resource 的权限检查 + - [ ] 20.1.1 DocumentResource 集成权限(使用 can 方法) + - [ ] 20.1.2 SystemSettingResource 集成权限 + - [ ] 20.1.3 ActivityLogResource 集成权限 + - [ ] 20.1.4 TerminalResource 集成权限 + - [ ] 20.1.5 SopTemplateResource 集成权限 + - [ ] 20.1.6 GroupResource 集成权限 + - [ ] 20.1.7 UserResource 集成权限 + - [ ] 20.1.8 RoleResource 集成权限 +- [ ] 20.2 实现导航菜单权限控制 + - [ ] 20.2.1 配置 Resource 的 shouldRegisterNavigation 方法 + - [ ] 20.2.2 使用 auth()->user()->can() 检查权限 + - [ ] 20.2.3 根据权限动态显示/隐藏菜单项 +- [ ] 20.3 实现操作按钮权限控制 + - [ ] 20.3.1 配置 Action 的 visible 方法 + - [ ] 20.3.2 使用 $this->can() 检查权限 + - [ ] 20.3.3 根据权限动态显示/隐藏按钮 +- [ ] 20.4 实现批量操作权限控制 + - [ ] 20.4.1 配置 BulkAction 的 visible 方法 + - [ ] 20.4.2 根据权限控制批量操作可用性 +- [ ] 20.5 实现中间件保护 + - [ ] 20.5.1 在路由中使用 permission 中间件 + - [ ] 20.5.2 在路由中使用 role 中间件 + - [ ] 20.5.3 测试未授权访问的重定向 + +### 21. 权限测试 +- [ ] 21.1 单元测试 + - [ ] 21.1.1 测试 User 模型的 HasRoles trait + - [ ] 21.1.2 测试 hasPermissionTo 方法 + - [ ] 21.1.3 测试 hasRole 方法 + - [ ] 21.1.4 测试 assignRole 和 removeRole + - [ ] 21.1.5 测试 givePermissionTo 和 revokePermissionTo + - [ ] 21.1.6 测试权限缓存 +- [ ] 21.2 策略测试 + - [ ] 21.2.1 测试所有 Policy 的权限检查 + - [ ] 21.2.2 测试 super-admin 角色的完整权限 + - [ ] 21.2.3 测试 admin 角色的权限 + - [ ] 21.2.4 测试普通用户的权限限制 + - [ ] 21.2.5 测试直接权限覆盖角色权限 +- [ ] 21.3 集成测试 + - [ ] 21.3.1 测试角色分配后的权限生效 + - [ ] 21.3.2 测试权限撤销后的访问限制 + - [ ] 21.3.3 测试跨分组访问控制 + - [ ] 21.3.4 测试数据级权限过滤 +- [ ] 21.4 UI 测试 + - [ ] 21.4.1 测试菜单项权限控制 + - [ ] 21.4.2 测试操作按钮权限控制 + - [ ] 21.4.3 测试批量操作权限控制 + - [ ] 21.4.4 测试未授权访问的错误提示 ## 阶段六:测试和优化(优先级:低) -### 18. 单元测试 -- [ ] 18.1 SystemSetting模型测试 - - [ ] 18.1.1 测试get方法 - - [ ] 18.1.2 测试set方法 -- [ ] 18.2 Terminal模型测试 - - [ ] 18.2.1 测试关联关系 - - [ ] 18.2.2 测试作用域 -- [ ] 18.3 SopTemplate模型测试 - - [ ] 18.3.1 测试关联关系 - - [ ] 18.3.2 测试状态转换 -- [ ] 18.4 Service类测试 - - [ ] 18.4.1 测试SystemSettingService - - [ ] 18.4.2 测试TerminalSyncService - - [ ] 18.4.3 测试SopTemplateService +### 22. 单元测试 +- [ ] 22.1 SystemSetting模型测试 + - [ ] 22.1.1 测试get方法 + - [ ] 22.1.2 测试set方法 +- [ ] 22.2 Terminal模型测试 + - [ ] 22.2.1 测试关联关系 + - [ ] 22.2.2 测试作用域 +- [ ] 22.3 SopTemplate模型测试 + - [ ] 22.3.1 测试关联关系 + - [ ] 22.3.2 测试状态转换 +- [ ] 22.4 Service类测试 + - [ ] 22.4.1 测试SystemSettingService + - [ ] 22.4.2 测试TerminalSyncService + - [ ] 22.4.3 测试SopTemplateService -### 19. 功能测试 -- [ ] 19.1 系统设置功能测试 - - [ ] 19.1.1 测试配置保存 - - [ ] 19.1.2 测试配置读取 -- [ ] 19.2 操作日志功能测试 - - [ ] 19.2.1 测试日志记录 - - [ ] 19.2.2 测试日志筛选 - - [ ] 19.2.3 测试日志导出 -- [ ] 19.3 终端管理功能测试 - - [ ] 19.3.1 测试终端CRUD - - [ ] 19.3.2 测试配置同步 -- [ ] 19.4 SOP模板功能测试 - - [ ] 19.4.1 测试模板CRUD - - [ ] 19.4.2 测试步骤编辑 - - [ ] 19.4.3 测试模板发布 - - [ ] 19.4.4 测试导入导出 +### 23. 功能测试 +- [ ] 23.1 系统设置功能测试 + - [ ] 23.1.1 测试配置保存 + - [ ] 23.1.2 测试配置读取 +- [ ] 23.2 操作日志功能测试 + - [ ] 23.2.1 测试日志记录 + - [ ] 23.2.2 测试日志筛选 + - [ ] 23.2.3 测试日志导出 +- [ ] 23.3 终端管理功能测试 + - [ ] 23.3.1 测试终端CRUD + - [ ] 23.3.2 测试配置同步 +- [ ] 23.4 SOP模板功能测试 + - [ ] 23.4.1 测试模板CRUD + - [ ] 23.4.2 测试步骤编辑 + - [ ] 23.4.3 测试模板发布 + - [ ] 23.4.4 测试导入导出 -### 20. 性能优化 -- [ ] 20.1 数据库优化 - - [ ] 20.1.1 添加必要索引 - - [ ] 20.1.2 优化查询语句 - - [ ] 20.1.3 实现Eager Loading -- [ ] 20.2 缓存优化 - - [ ] 20.2.1 实现系统设置缓存 - - [ ] 20.2.2 实现终端状态缓存 - - [ ] 20.2.3 实现模板列表缓存 -- [ ] 20.3 前端优化 - - [ ] 20.3.1 实现Lazy Loading - - [ ] 20.3.2 优化Monaco Editor加载 - - [ ] 20.3.3 优化图片加载 -- [ ] 20.4 性能测试 - - [ ] 20.4.1 测试日志查询性能 - - [ ] 20.4.2 测试终端列表性能 - - [ ] 20.4.3 测试模板编辑性能 +### 24. 性能优化 +- [ ] 24.1 数据库优化 + - [ ] 24.1.1 添加必要索引 + - [ ] 24.1.2 优化查询语句 + - [ ] 24.1.3 实现Eager Loading +- [ ] 24.2 缓存优化 + - [ ] 24.2.1 实现系统设置缓存 + - [ ] 24.2.2 实现终端状态缓存 + - [ ] 24.2.3 实现模板列表缓存 + - [ ] 24.2.4 实现权限缓存 +- [ ] 24.3 前端优化 + - [ ] 24.3.1 实现Lazy Loading + - [ ] 24.3.2 优化Monaco Editor加载 + - [ ] 24.3.3 优化图片加载 +- [ ] 24.4 性能测试 + - [ ] 24.4.1 测试日志查询性能 + - [ ] 24.4.2 测试终端列表性能 + - [ ] 24.4.3 测试模板编辑性能 + - [ ] 24.4.4 测试权限检查性能 ## 阶段七:文档和部署(优先级:低) -### 21. 文档编写 -- [ ] 21.1 编写用户手册 - - [ ] 21.1.1 系统设置使用说明 - - [ ] 21.1.2 操作日志使用说明 - - [ ] 21.1.3 终端管理使用说明 - - [ ] 21.1.4 SOP模板使用说明 -- [ ] 21.2 编写开发文档 - - [ ] 21.2.1 API文档 - - [ ] 21.2.2 数据库设计文档 - - [ ] 21.2.3 部署文档 -- [ ] 21.3 编写测试文档 - - [ ] 21.3.1 测试用例文档 - - [ ] 21.3.2 测试报告模板 +### 25. 文档编写 +- [ ] 25.1 编写用户手册 + - [ ] 25.1.1 系统设置使用说明 + - [ ] 25.1.2 操作日志使用说明 + - [ ] 25.1.3 终端管理使用说明 + - [ ] 25.1.4 SOP模板使用说明 + - [ ] 25.1.5 权限管理使用说明 +- [ ] 25.2 编写开发文档 + - [ ] 25.2.1 API文档 + - [ ] 25.2.2 数据库设计文档 + - [ ] 25.2.3 部署文档 + - [ ] 25.2.4 权限系统架构文档 +- [ ] 25.3 编写测试文档 + - [ ] 25.3.1 测试用例文档 + - [ ] 25.3.2 测试报告模板 -### 22. 部署准备 -- [ ] 22.1 准备生产环境配置 - - [ ] 22.1.1 更新.env.production - - [ ] 22.1.2 配置队列服务 - - [ ] 22.1.3 配置缓存服务 -- [ ] 22.2 数据迁移准备 - - [ ] 22.2.1 准备迁移脚本 - - [ ] 22.2.2 准备回滚脚本 - - [ ] 22.2.3 准备种子数据 -- [ ] 22.3 部署到生产环境 - - [ ] 22.3.1 执行数据库迁移 - - [ ] 22.3.2 发布静态资源 - - [ ] 22.3.3 重启服务 -- [ ] 22.4 生产环境验证 - - [ ] 22.4.1 验证所有功能 - - [ ] 22.4.2 验证性能指标 - - [ ] 22.4.3 验证安全配置 +### 26. 部署准备 +- [ ] 26.1 准备生产环境配置 + - [ ] 26.1.1 更新.env.production + - [ ] 26.1.2 配置队列服务 + - [ ] 26.1.3 配置缓存服务 +- [ ] 26.2 数据迁移准备 + - [ ] 26.2.1 准备迁移脚本 + - [ ] 26.2.2 准备回滚脚本 + - [ ] 26.2.3 准备种子数据 +- [ ] 26.3 部署到生产环境 + - [ ] 26.3.1 执行数据库迁移 + - [ ] 26.3.2 发布静态资源 + - [ ] 26.3.3 重启服务 +- [ ] 26.4 生产环境验证 + - [ ] 26.4.1 验证所有功能 + - [ ] 26.4.2 验证性能指标 + - [ ] 26.4.3 验证安全配置 ## 任务统计 -- 总任务数:22个主任务 -- 子任务数:约200+个子任务 -- 预计工作量:40-60工作日 +- 总任务数:26个主任务 +- 子任务数:约250+个子任务 +- 预计工作量:50-70工作日 - 优先级分布: - - 高优先级:阶段一、二(约30%) - - 中优先级:阶段三、四、五(约50%) - - 低优先级:阶段六、七(约20%) + - 高优先级:阶段一、二、五(约40%) + - 中优先级:阶段三、四(约35%) + - 低优先级:阶段六、七(约25%) diff --git a/app/Models/User.php b/app/Models/User.php index 5efed8c..06b0a56 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,11 +8,12 @@ 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; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. @@ -71,4 +72,20 @@ class User extends Authenticatable { return $this->hasMany(DownloadLog::class); } + + /** + * 检查用户是否为超级管理员 + */ + public function isSuperAdmin(): bool + { + return $this->hasRole('super-admin'); + } + + /** + * 检查用户是否为管理员(包括超级管理员) + */ + public function isAdmin(): bool + { + return $this->hasAnyRole(['super-admin', 'admin']); + } } diff --git a/composer.json b/composer.json index 8d58836..e288350 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "maatwebsite/excel": "^3.1", "meilisearch/meilisearch-php": "^1.16", "phpoffice/phpword": "^1.4", - "spatie/laravel-activitylog": "^4.12" + "spatie/laravel-activitylog": "^4.12", + "spatie/laravel-permission": "^6.24" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 8f01c84..b785ed2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c39c6bce26908705b817350435bee16e", + "content-hash": "955960e117170ae4d776f49e01bab40f", "packages": [ { "name": "abdelhamiderrahmouni/filament-monaco-editor", @@ -6294,6 +6294,89 @@ ], "time": "2025-07-17T15:46:43+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.24.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "eefc9d17eba80d023d6bff313f882cb2bcd691a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/eefc9d17eba80d023d6bff313f882cb2bcd691a3", + "reference": "eefc9d17eba80d023d6bff313f882cb2bcd691a3", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.24.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-09T21:10:03+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", diff --git a/config/livewire.php b/config/livewire.php index 294b7a4..f028833 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -65,7 +65,7 @@ return [ 'temporary_file_upload' => [ 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) + 'rules' => ['required', 'file', 'max:51200'], // 最大 50MB 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... @@ -73,7 +73,7 @@ return [ 'mov', 'avi', 'wmv', 'mp3', 'm4a', 'jpg', 'jpeg', 'mpga', 'webp', 'wma', ], - 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... + 'max_upload_time' => 10, // Max duration (in minutes) before an upload is invalidated... 'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs... ], diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..f39f6b5 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/database/migrations/2026_03_11_015326_create_permission_tables.php b/database/migrations/2026_03_11_015326_create_permission_tables.php new file mode 100644 index 0000000..66ce1f9 --- /dev/null +++ b/database/migrations/2026_03_11_015326_create_permission_tables.php @@ -0,0 +1,134 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 0000000..a1dcc18 --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,204 @@ +forgetCachedPermissions(); + + // 定义所有权限 + $permissions = [ + // 文档管理权限 + 'document.viewAny' => '查看文档列表', + 'document.view' => '查看文档详情', + 'document.create' => '创建文档', + 'document.update' => '编辑文档', + 'document.delete' => '删除文档', + 'document.download' => '下载文档', + + // 系统设置权限 + 'system-setting.viewAny' => '查看系统设置', + 'system-setting.view' => '查看设置详情', + 'system-setting.update' => '修改系统设置', + + // 操作日志权限 + 'activity-log.viewAny' => '查看操作日志', + 'activity-log.view' => '查看日志详情', + 'activity-log.export' => '导出日志', + + // 终端管理权限 + 'terminal.viewAny' => '查看终端列表', + 'terminal.view' => '查看终端详情', + 'terminal.create' => '创建终端', + 'terminal.update' => '编辑终端', + 'terminal.delete' => '删除终端', + 'terminal.sync' => '同步终端配置', + + // SOP模板权限 + 'sop-template.viewAny' => '查看SOP列表', + 'sop-template.view' => '查看SOP详情', + 'sop-template.create' => '创建SOP', + 'sop-template.update' => '编辑SOP', + 'sop-template.delete' => '删除SOP', + 'sop-template.publish' => '发布SOP', + 'sop-template.archive' => '归档SOP', + + // 分组管理权限 + 'group.viewAny' => '查看分组列表', + 'group.view' => '查看分组详情', + 'group.create' => '创建分组', + 'group.update' => '编辑分组', + 'group.delete' => '删除分组', + + // 用户管理权限 + 'user.viewAny' => '查看用户列表', + 'user.view' => '查看用户详情', + 'user.create' => '创建用户', + 'user.update' => '编辑用户', + 'user.delete' => '删除用户', + + // 角色管理权限 + 'role.viewAny' => '查看角色列表', + 'role.view' => '查看角色详情', + 'role.create' => '创建角色', + 'role.update' => '编辑角色', + 'role.delete' => '删除角色', + ]; + + // 创建所有权限 + foreach ($permissions as $name => $description) { + Permission::create([ + 'name' => $name, + 'guard_name' => 'web', + ]); + } + + // 创建角色并分配权限 + $this->createSuperAdminRole(); + $this->createAdminRole(); + $this->createUserRole(); + } + + /** + * 创建超级管理员角色 + */ + private function createSuperAdminRole(): void + { + $role = Role::create([ + 'name' => 'super-admin', + 'guard_name' => 'web', + ]); + + // 超级管理员拥有所有权限 + $role->givePermissionTo(Permission::all()); + } + + /** + * 创建管理员角色 + */ + private function createAdminRole(): void + { + $role = Role::create([ + 'name' => 'admin', + 'guard_name' => 'web', + ]); + + // 管理员权限(除了角色管理) + $permissions = [ + // 文档管理 + 'document.viewAny', + 'document.view', + 'document.create', + 'document.update', + 'document.delete', + 'document.download', + + // 系统设置 + 'system-setting.viewAny', + 'system-setting.view', + 'system-setting.update', + + // 操作日志 + 'activity-log.viewAny', + 'activity-log.view', + 'activity-log.export', + + // 终端管理 + 'terminal.viewAny', + 'terminal.view', + 'terminal.create', + 'terminal.update', + 'terminal.delete', + 'terminal.sync', + + // SOP模板 + 'sop-template.viewAny', + 'sop-template.view', + 'sop-template.create', + 'sop-template.update', + 'sop-template.delete', + 'sop-template.publish', + 'sop-template.archive', + + // 分组管理 + 'group.viewAny', + 'group.view', + 'group.create', + 'group.update', + 'group.delete', + + // 用户管理 + 'user.viewAny', + 'user.view', + 'user.create', + 'user.update', + 'user.delete', + ]; + + $role->givePermissionTo($permissions); + } + + /** + * 创建普通用户角色 + */ + private function createUserRole(): void + { + $role = Role::create([ + 'name' => 'user', + 'guard_name' => 'web', + ]); + + // 普通用户权限(基本查看和操作) + $permissions = [ + // 文档管理 + 'document.viewAny', + 'document.view', + 'document.create', + 'document.download', + + // 终端管理(仅查看) + 'terminal.viewAny', + 'terminal.view', + + // SOP模板(仅查看) + 'sop-template.viewAny', + 'sop-template.view', + + // 分组管理(仅查看) + 'group.viewAny', + 'group.view', + ]; + + $role->givePermissionTo($permissions); + } +}