Files
junhong_cmp_fiber/internal/store/postgres/scopes.go
huang eaa70ac255 feat: 实现 RBAC 权限系统和数据权限控制 (004-rbac-data-permission)
主要功能:
- 实现完整的 RBAC 权限系统(账号、角色、权限的多对多关联)
- 基于 owner_id + shop_id 的自动数据权限过滤
- 使用 PostgreSQL WITH RECURSIVE 查询下级账号
- Redis 缓存优化下级账号查询性能(30分钟过期)
- 支持多租户数据隔离和层级权限管理

技术实现:
- 新增 Account、Role、Permission 模型及关联关系表
- 实现 GORM Scopes 自动应用数据权限过滤
- 添加数据库迁移脚本(000002_rbac_data_permission、000003_add_owner_id_shop_id)
- 完善错误码定义(1010-1027 为 RBAC 相关错误)
- 重构 main.go 采用函数拆分提高可读性

测试覆盖:
- 添加 Account、Role、Permission 的集成测试
- 添加数据权限过滤的单元测试和集成测试
- 添加下级账号查询和缓存的单元测试
- 添加 API 回归测试确保向后兼容

文档更新:
- 更新 README.md 添加 RBAC 功能说明
- 更新 CLAUDE.md 添加技术栈和开发原则
- 添加 docs/004-rbac-data-permission/ 功能总结和使用指南

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:44:06 +08:00

87 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package postgres
import (
"context"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"go.uber.org/zap"
"gorm.io/gorm"
)
// DataPermissionScope 数据权限过滤 Scope
// 根据 context 中的用户信息自动过滤数据
// - root 用户跳过过滤
// - 普通用户只能查看自己和下级的数据
// - 同时限制 shop_id 相同
func DataPermissionScope(ctx context.Context, accountStore *AccountStore) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
// 1. 检查是否为 root 用户root 用户跳过数据权限过滤
if middleware.IsRootUser(ctx) {
return db
}
// 2. 获取当前用户 ID
userID := middleware.GetUserIDFromContext(ctx)
if userID == 0 {
// 未登录用户返回空结果
logger.GetAppLogger().Warn("数据权限过滤:未获取到用户 ID")
return db.Where("1 = 0")
}
// 3. 获取当前用户的 shop_id
shopID := middleware.GetShopIDFromContext(ctx)
// 4. 获取当前用户及所有下级的 ID
subordinateIDs, err := accountStore.GetSubordinateIDs(ctx, userID)
if err != nil {
// 查询失败时,降级为只能看自己的数据
logger.GetAppLogger().Error("数据权限过滤:获取下级 ID 失败",
zap.Uint("user_id", userID),
zap.Error(err))
subordinateIDs = []uint{userID}
}
// 5. 应用数据权限过滤条件
// owner_id IN (用户自己及所有下级) AND shop_id = 当前用户 shop_id
if len(subordinateIDs) == 0 {
subordinateIDs = []uint{userID}
}
// 根据是否有 shop_id 过滤条件决定 SQL
if shopID != 0 {
return db.Where("owner_id IN ? AND shop_id = ?", subordinateIDs, shopID)
}
// 如果 shop_id 为 0只根据 owner_id 过滤
return db.Where("owner_id IN ?", subordinateIDs)
}
}
// WithoutDataPermission 跳过数据权限过滤的 Scope
// 用于需要查询所有数据的场景(如管理后台统计、系统任务等)
func WithoutDataPermission() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
// 什么都不做,直接返回原 db
return db
}
}
// SoftDeleteScope 软删除过滤 ScopeGORM 默认已支持,此处作为示例)
// 只查询未软删除的记录
func SoftDeleteScope() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("deleted_at IS NULL")
}
}
// StatusEnabledScope 状态启用过滤 Scope
// 只查询状态为启用的记录
func StatusEnabledScope() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", constants.StatusEnabled)
}
}