feat(权限): 安装和配置 Spatie Permission 包

- 安装 spatie/laravel-permission 包(v6.24.1)
- 发布配置文件和迁移文件
- 运行迁移创建权限表
- 在 User 模型中添加 HasRoles trait
- 添加 isSuperAdmin 和 isAdmin 辅助方法
- 创建 PermissionSeeder 定义 45 个权限
- 创建 3 个预设角色(super-admin、admin、user)
- 为角色分配相应权限
- 为第一个用户分配超级管理员角色
This commit is contained in:
2026-03-11 09:55:40 +08:00
parent 7a4fa7cc18
commit 7d13a560f3
9 changed files with 1042 additions and 129 deletions

View File

@@ -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 创建)
## 实施优先级

View File

@@ -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 实现 viewAnysystem-setting.viewAny
- [ ] 19.2.2 实现 viewsystem-setting.view
- [ ] 19.2.3 实现 updatesystem-setting.update
- [ ] 19.3 ActivityLogPolicy
- [ ] 19.3.1 实现 viewAnyactivity-log.viewAny
- [ ] 19.3.2 实现 viewactivity-log.view
- [ ] 19.3.3 实现 exportactivity-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 实现 viewAnygroup.viewAny
- [ ] 19.6.2 实现 viewgroup.view
- [ ] 19.6.3 实现 creategroup.create
- [ ] 19.6.4 实现 updategroup.update
- [ ] 19.6.5 实现 deletegroup.delete需检查关联文档
- [ ] 19.7 UserPolicy
- [ ] 19.7.1 实现 viewAnyuser.viewAny
- [ ] 19.7.2 实现 viewuser.view
- [ ] 19.7.3 实现 createuser.create
- [ ] 19.7.4 实现 updateuser.update
- [ ] 19.7.5 实现 deleteuser.delete不能删除自己
- [ ] 19.8 RolePolicy
- [ ] 19.8.1 实现 viewAnyrole.viewAny
- [ ] 19.8.2 实现 viewrole.view
- [ ] 19.8.3 实现 createrole.create
- [ ] 19.8.4 实现 updaterole.update
- [ ] 19.8.5 实现 deleterole.deletesuper-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%

View File

@@ -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']);
}
}

View File

@@ -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",

85
composer.lock generated
View File

@@ -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",

View File

@@ -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...
],

202
config/permission.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
return [
'models' => [
/*
* 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',
],
];

View File

@@ -0,0 +1,134 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->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']);
}
};

View File

@@ -0,0 +1,204 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class PermissionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// 重置缓存的角色和权限
app()[\Spatie\Permission\PermissionRegistrar::class]->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);
}
}