# 使用指南:RBAC 表结构与 GORM 数据权限过滤 **功能编号**: 004-rbac-data-permission **适用版本**: v1.0.0 **更新日期**: 2025-11-18 ## 目录 1. [快速开始](#快速开始) 2. [账号管理](#账号管理) 3. [角色管理](#角色管理) 4. [权限管理](#权限管理) 5. [数据权限过滤](#数据权限过滤) 6. [业务表集成指南](#业务表集成指南) 7. [常见问题](#常见问题) 8. [最佳实践](#最佳实践) --- ## 快速开始 ### 环境要求 - Go 1.25.4+ - PostgreSQL 14+ - Redis 6.0+ - golang-migrate v4.x ### 数据库初始化 ```bash # 运行数据库迁移 migrate -path migrations -database "postgresql://postgres:password@localhost:5432/junhong_cmp_fiber?sslmode=disable" up # 验证表创建 psql -U postgres -d junhong_cmp_fiber -c "\dt" ``` ### 创建 root 账号 ```sql -- 使用 bcrypt 哈希密码(Password123) INSERT INTO tb_account (username, phone, password, user_type, shop_id, parent_id, status, creator, updater, created_at, updated_at) VALUES ('root', '13800000000', '$2a$10$N9qo8uLOickgx2ZMRZoMye1P7Z.mKAeQ7pjSeG7gYDobOAZCnOMUa', 1, NULL, NULL, 1, 1, 1, NOW(), NOW()); ``` --- ## 账号管理 ### 1. 创建账号 **API 端点**: `POST /api/v1/accounts` **请求示例**: ```bash curl -X POST http://localhost:8080/api/v1/accounts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "username": "platform_user", "phone": "13900000001", "password": "Password123", "user_type": 2, "shop_id": 10, "parent_id": 1 }' ``` **参数说明**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | username | string | 是 | 用户名(3-20 个字符,字母/数字/下划线) | | phone | string | 是 | 手机号(11 位中国大陆手机号) | | password | string | 是 | 密码(最少 8 位,包含字母和数字) | | user_type | int | 是 | 用户类型:1=root, 2=平台, 3=代理, 4=企业 | | shop_id | int | 条件 | 所属店铺 ID(user_type=1 时可为空) | | parent_id | int | 条件 | 上级账号 ID(user_type=1 时可为空) | **响应示例**: ```json { "code": 0, "message": "success", "data": { "id": 2, "username": "platform_user", "phone": "13900000001", "user_type": 2, "shop_id": 10, "parent_id": 1, "status": 1, "created_at": "2025-11-18T10:00:00Z", "updated_at": "2025-11-18T10:00:00Z" }, "timestamp": "2025-11-18T10:00:00Z" } ``` **注意事项**: - ✅ 密码会自动使用 bcrypt 加密存储 - ✅ username 和 phone 必须唯一(软删除后可重复使用) - ✅ 非 root 用户必须提供 parent_id - ✅ 创建成功后会自动清除父账号的下级 ID 缓存 ### 2. 获取账号详情 **API 端点**: `GET /api/v1/accounts/:id` **请求示例**: ```bash curl -X GET http://localhost:8080/api/v1/accounts/2 \ -H "Authorization: Bearer " ``` **响应示例**: ```json { "code": 0, "message": "success", "data": { "id": 2, "username": "platform_user", "phone": "13900000001", "user_type": 2, "shop_id": 10, "parent_id": 1, "status": 1, "created_at": "2025-11-18T10:00:00Z", "updated_at": "2025-11-18T10:00:00Z" }, "timestamp": "2025-11-18T10:00:00Z" } ``` **注意**: password 字段不会返回给客户端(已通过 `json:"-"` 标签隐藏) ### 3. 更新账号 **API 端点**: `PUT /api/v1/accounts/:id` **请求示例**: ```bash curl -X PUT http://localhost:8080/api/v1/accounts/2 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "username": "new_username", "phone": "13900000002", "status": 1 }' ``` **注意事项**: - ❌ **禁止修改**: user_type, parent_id(创建后不可更改) - ✅ **可选修改**: username, phone, status ### 4. 删除账号(软删除) **API 端点**: `DELETE /api/v1/accounts/:id` **请求示例**: ```bash curl -X DELETE http://localhost:8080/api/v1/accounts/2 \ -H "Authorization: Bearer " ``` **响应示例**: ```json { "code": 0, "message": "success", "data": null, "timestamp": "2025-11-18T10:00:00Z" } ``` **注意事项**: - ✅ 软删除:只设置 `deleted_at` 字段,数据仍保留 - ✅ 软删除后,username 和 phone 可以被重新使用 - ✅ 删除成功后会递归清除所有上级账号的下级 ID 缓存 - ⚠️ 软删除账号的数据对上级仍然可见(递归查询包含已删除账号) ### 5. 获取账号列表 **API 端点**: `GET /api/v1/accounts` **请求示例**: ```bash curl -X GET "http://localhost:8080/api/v1/accounts?page=1&page_size=20" \ -H "Authorization: Bearer " ``` **查询参数**: | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | page | int | 否 | 1 | 页码 | | page_size | int | 否 | 20 | 每页数量(最大 100) | **响应示例**: ```json { "code": 0, "message": "success", "data": { "items": [ { "id": 2, "username": "platform_user", "user_type": 2, "shop_id": 10, "parent_id": 1, "status": 1 } ], "total": 1, "page": 1, "page_size": 20 }, "timestamp": "2025-11-18T10:00:00Z" } ``` ### 6. 为账号分配角色 **API 端点**: `POST /api/v1/accounts/:id/roles` **请求示例**: ```bash curl -X POST http://localhost:8080/api/v1/accounts/2/roles \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "role_ids": [1, 2] }' ``` **响应示例**: ```json { "code": 0, "message": "success", "data": [ { "id": 1, "account_id": 2, "role_id": 1, "status": 1, "created_at": "2025-11-18T10:00:00Z" }, { "id": 2, "account_id": 2, "role_id": 2, "status": 1, "created_at": "2025-11-18T10:00:00Z" } ], "timestamp": "2025-11-18T10:00:00Z" } ``` ### 7. 获取账号的角色列表 **API 端点**: `GET /api/v1/accounts/:id/roles` **请求示例**: ```bash curl -X GET http://localhost:8080/api/v1/accounts/2/roles \ -H "Authorization: Bearer " ``` ### 8. 移除账号的角色 **API 端点**: `DELETE /api/v1/accounts/:account_id/roles/:role_id` **请求示例**: ```bash curl -X DELETE http://localhost:8080/api/v1/accounts/2/roles/1 \ -H "Authorization: Bearer " ``` --- ## 角色管理 ### 1. 创建角色 **API 端点**: `POST /api/v1/roles` **请求示例**: ```bash curl -X POST http://localhost:8080/api/v1/roles \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "role_name": "超级管理员", "role_desc": "系统超级管理员", "role_type": 1 }' ``` **参数说明**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | role_name | string | 是 | 角色名称(长度 ≤50) | | role_desc | string | 否 | 角色描述(长度 ≤255) | | role_type | int | 是 | 角色类型:1=超级, 2=代理, 3=企业 | ### 2. 为角色分配权限 **API 端点**: `POST /api/v1/roles/:id/permissions` **请求示例**: ```bash curl -X POST http://localhost:8080/api/v1/roles/1/permissions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "perm_ids": [1, 2, 3] }' ``` ### 3. 获取角色的权限列表 **API 端点**: `GET /api/v1/roles/:id/permissions` **请求示例**: ```bash curl -X GET http://localhost:8080/api/v1/roles/1/permissions \ -H "Authorization: Bearer " ``` --- ## 权限管理 ### 1. 创建权限 **API 端点**: `POST /api/v1/permissions` **请求示例**: ```bash curl -X POST http://localhost:8080/api/v1/permissions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "perm_name": "用户管理", "perm_code": "user:manage", "perm_type": 1, "url": "/admin/users", "parent_id": null, "sort": 1 }' ``` **参数说明**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | perm_name | string | 是 | 权限名称(长度 ≤50) | | perm_code | string | 是 | 权限编码(格式:`module:action`,如 `user:create`) | | perm_type | int | 是 | 权限类型:1=菜单, 2=按钮 | | url | string | 否 | URL 路径(长度 ≤255) | | parent_id | int | 否 | 上级权限 ID(支持层级) | | sort | int | 否 | 排序序号(默认 0) | **注意事项**: - ✅ perm_code 必须唯一(软删除后可重复使用) - ✅ 支持层级权限(通过 parent_id 构建权限树) --- ## 数据权限过滤 ### 核心概念 数据权限过滤机制确保每个用户只能访问自己及下级的数据,通过 `owner_id` 和 `shop_id` 双重过滤实现多租户数据隔离。 ### 过滤规则 ```sql WHERE owner_id IN (当前用户及所有下级的ID列表) AND shop_id = 当前用户的shop_id ``` ### 示例场景 假设用户层级关系为:A(root, ID=1) → B(平台, ID=2) → C(代理, ID=3) - **用户 A 查询**:返回所有数据(root 用户跳过过滤) - **用户 B 查询**:返回 `owner_id IN (2, 3) AND shop_id = 10` 的数据 - **用户 C 查询**:返回 `owner_id = 3 AND shop_id = 10` 的数据 ### 递归查询下级 ID 系统使用 PostgreSQL WITH RECURSIVE 查询所有下级 ID,并通过 Redis 缓存优化性能: - **缓存 Key**: `account:subordinates:{账号ID}` - **过期时间**: 30 分钟 - **清除时机**: 账号创建/删除时主动清除 ### 跳过数据权限过滤 某些特殊场景(如 C 端业务用户、系统任务)需要跳过数据权限过滤: **方式 1:在 Store 层使用 WithoutDataFilter 选项** ```go users, err := userStore.List(ctx, &store.QueryOptions{ WithoutDataFilter: true, }) ``` **方式 2:root 用户自动跳过过滤** root 用户(user_type=1)的所有查询会自动跳过数据权限过滤。 --- ## 业务表集成指南 ### 步骤 1:添加数据权限字段 为业务表添加 `owner_id` 和 `shop_id` 字段: **数据库迁移**: ```sql -- migrations/000004_add_owner_id_to_business_table.up.sql ALTER TABLE tb_your_table ADD COLUMN owner_id INTEGER; ALTER TABLE tb_your_table ADD COLUMN shop_id INTEGER; CREATE INDEX idx_your_table_owner_id ON tb_your_table(owner_id); CREATE INDEX idx_your_table_shop_id ON tb_your_table(shop_id); ``` **GORM 模型更新**: ```go // internal/model/your_model.go type YourModel struct { ID uint `gorm:"primarykey" json:"id"` // ... 其他字段 ... OwnerID *uint `gorm:"index" json:"owner_id,omitempty"` // 新增 ShopID *uint `gorm:"index" json:"shop_id,omitempty"` // 新增 CreatedAt time.Time `gorm:"not null" json:"created_at"` UpdatedAt time.Time `gorm:"not null" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` } ``` ### 步骤 2:在 Store 层应用数据权限过滤 ```go // internal/store/postgres/your_store.go import ( "context" "your-project/internal/model" "your-project/internal/store" "gorm.io/gorm" ) type YourStore struct { db *gorm.DB accountStore *AccountStore // 注入 AccountStore(用于递归查询) } func (s *YourStore) List(ctx context.Context, opts *store.QueryOptions) ([]*model.YourModel, error) { query := s.db.WithContext(ctx) // 应用数据权限过滤(如果未禁用) if !opts.WithoutDataFilter { query = query.Scopes(DataPermissionScope(s.accountStore)) } var items []*model.YourModel if err := query.Find(&items).Error; err != nil { return nil, err } return items, nil } func (s *YourStore) GetByID(ctx context.Context, id uint, opts *store.QueryOptions) (*model.YourModel, error) { query := s.db.WithContext(ctx) // 应用数据权限过滤(如果未禁用) if !opts.WithoutDataFilter { query = query.Scopes(DataPermissionScope(s.accountStore)) } var item model.YourModel if err := query.First(&item, id).Error; err != nil { return nil, err } return &item, nil } ``` ### 步骤 3:在 Service 层设置 owner_id 和 shop_id ```go // internal/service/your_service/service.go import ( "context" "your-project/internal/model" "your-project/pkg/middleware" ) func (s *Service) Create(ctx context.Context, req *CreateRequest) (*model.YourModel, error) { // 从 context 提取当前用户信息 userID := middleware.GetUserIDFromContext(ctx) shopID := middleware.GetShopIDFromContext(ctx) item := &model.YourModel{ // ... 其他字段 ... OwnerID: &userID, // 设置为当前用户 ID ShopID: &shopID, // 设置为当前用户的 shop_id } if err := s.store.Create(ctx, item); err != nil { return nil, err } return item, nil } ``` ### 步骤 4:验证数据权限过滤 创建测试数据并验证不同用户的查询结果: ```sql -- 创建测试数据 INSERT INTO tb_your_table (name, owner_id, shop_id, created_at, updated_at) VALUES ('数据A - 用户B创建', 2, 10, NOW(), NOW()), ('数据B - 用户C创建', 3, 10, NOW(), NOW()), ('数据C - 其他店铺', 2, 20, NOW(), NOW()); ``` **预期查询结果**: - **用户 A(root)**: 返回所有 3 条数据 - **用户 B(平台)**: 返回 2 条数据(owner_id=2 或 3,且 shop_id=10) - **用户 C(代理)**: 返回 1 条数据(owner_id=3,且 shop_id=10) --- ## 常见问题 ### Q1: 如何验证密码? **A**: 使用 bcrypt 验证密码: ```go import "golang.org/x/crypto/bcrypt" func ValidatePassword(plainPassword, hashedPassword string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword)) return err == nil } ``` ### Q2: 递归查询性能问题? **A**: 系统已通过 Redis 缓存优化,缓存命中率预期 > 90%。如果层级深度超过 10 层,建议: - 监控递归查询耗时(P95 应 < 50ms) - 考虑使用闭包表(Closure Table)替代递归查询 ### Q3: 软删除账号的数据如何处理? **A**: 软删除账号后: - ✅ 该账号的数据对上级仍然可见(递归查询包含已删除账号) - ✅ username 和 phone 可以被重新使用 - ✅ 所有上级的下级 ID 缓存会被清除 ### Q4: 如何清除 Redis 缓存? **A**: 账号创建/删除时会自动清除缓存,也可以手动清除: ```bash # 清除指定账号的下级 ID 缓存 redis-cli DEL account:subordinates:2 # 清除所有下级 ID 缓存 redis-cli KEYS "account:subordinates:*" | xargs redis-cli DEL ``` ### Q5: 如何为 C 端业务用户实现数据过滤? **A**: C 端业务用户通常不使用 owner_id 过滤,而是基于业务字段(如 iccid/device_id): ```go // 使用 WithoutDataFilter 跳过 owner_id 过滤 users, err := userStore.List(ctx, &store.QueryOptions{ WithoutDataFilter: true, }) // 在 Service 层应用业务字段过滤 filteredUsers := filterByICCID(users, targetICCID) ``` ### Q6: 如何处理跨店铺查询? **A**: 数据权限过滤强制 `shop_id = 当前用户的shop_id`,不支持跨店铺查询。如果需要跨店铺查询: - 方式 1:使用 root 用户(自动跳过过滤) - 方式 2:使用 `WithoutDataFilter` 选项(需要在业务层额外校验权限) --- ## 最佳实践 ### 1. 创建账号时的层级关系 ✅ **推荐**:只有本级账号能创建下级账号 ``` A(root) 创建 B(平台) B(平台) 创建 C(代理) C(代理) 创建 D(企业) ``` ❌ **不推荐**:跨级创建(A 直接创建 C) ### 2. 数据归属设置 ✅ **推荐**:创建数据时 owner_id 设置为当前用户 ID ```go item.OwnerID = &userID // 当前用户 ID item.ShopID = &shopID // 当前用户的 shop_id ``` ❌ **不推荐**:owner_id 设置为其他用户 ID(除非是数据转移场景) ### 3. 缓存管理 ✅ **推荐**:依赖自动缓存清除机制 - 账号创建时自动清除父账号缓存 - 账号删除时递归清除所有上级缓存 ❌ **不推荐**:手动清除缓存(除非调试或紧急修复) ### 4. 错误处理 ✅ **推荐**:使用统一错误处理机制 ```go import "your-project/pkg/errors" if account == nil { return nil, errors.New(errors.CodeAccountNotFound, "账号不存在") } ``` ❌ **不推荐**:手动构造错误响应 ```go // ❌ 不推荐 return c.Status(404).JSON(fiber.Map{"error": "账号不存在"}) ``` ### 5. 安全性 ✅ **推荐**: - 使用 bcrypt 加密密码 - password 字段使用 `json:"-"` 隐藏 - 验证用户权限后再执行敏感操作 ❌ **不推荐**: - 使用 MD5 加密密码(已废弃) - 返回 password 字段给客户端 - 跳过权限校验 --- ## 相关文档 - **功能总结**: [功能总结.md](./功能总结.md) - **快速入门**: [specs/004-rbac-data-permission/quickstart.md](../../specs/004-rbac-data-permission/quickstart.md) - **数据模型**: [specs/004-rbac-data-permission/data-model.md](../../specs/004-rbac-data-permission/data-model.md) - **API 文档**: [specs/004-rbac-data-permission/contracts/](../../specs/004-rbac-data-permission/contracts/) --- **更新日期**: 2025-11-18 **维护者**: AI Assistant (Claude)