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>
This commit is contained in:
78
internal/store/postgres/account_role_store.go
Normal file
78
internal/store/postgres/account_role_store.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// AccountRoleStore 账号-角色关联数据访问层
|
||||
type AccountRoleStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAccountRoleStore 创建账号-角色关联 Store
|
||||
func NewAccountRoleStore(db *gorm.DB) *AccountRoleStore {
|
||||
return &AccountRoleStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建账号-角色关联
|
||||
func (s *AccountRoleStore) Create(ctx context.Context, ar *model.AccountRole) error {
|
||||
return s.db.WithContext(ctx).Create(ar).Error
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建账号-角色关联
|
||||
func (s *AccountRoleStore) BatchCreate(ctx context.Context, ars []*model.AccountRole) error {
|
||||
return s.db.WithContext(ctx).Create(&ars).Error
|
||||
}
|
||||
|
||||
// Delete 软删除账号-角色关联
|
||||
func (s *AccountRoleStore) Delete(ctx context.Context, accountID, roleID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("account_id = ? AND role_id = ?", accountID, roleID).
|
||||
Delete(&model.AccountRole{}).Error
|
||||
}
|
||||
|
||||
// DeleteByAccountID 删除账号的所有角色关联
|
||||
func (s *AccountRoleStore) DeleteByAccountID(ctx context.Context, accountID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("account_id = ?", accountID).
|
||||
Delete(&model.AccountRole{}).Error
|
||||
}
|
||||
|
||||
// GetByAccountID 获取账号的所有角色关联
|
||||
func (s *AccountRoleStore) GetByAccountID(ctx context.Context, accountID uint) ([]*model.AccountRole, error) {
|
||||
var ars []*model.AccountRole
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("account_id = ?", accountID).
|
||||
Find(&ars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ars, nil
|
||||
}
|
||||
|
||||
// GetRoleIDsByAccountID 获取账号的所有角色 ID
|
||||
func (s *AccountRoleStore) GetRoleIDsByAccountID(ctx context.Context, accountID uint) ([]uint, error) {
|
||||
var roleIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.AccountRole{}).
|
||||
Where("account_id = ?", accountID).
|
||||
Pluck("role_id", &roleIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roleIDs, nil
|
||||
}
|
||||
|
||||
// Exists 检查账号-角色关联是否存在
|
||||
func (s *AccountRoleStore) Exists(ctx context.Context, accountID, roleID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.AccountRole{}).
|
||||
Where("account_id = ? AND role_id = ?", accountID, roleID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
183
internal/store/postgres/account_store.go
Normal file
183
internal/store/postgres/account_store.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AccountStore 账号数据访问层
|
||||
type AccountStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewAccountStore 创建账号 Store
|
||||
func NewAccountStore(db *gorm.DB, redis *redis.Client) *AccountStore {
|
||||
return &AccountStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建账号
|
||||
func (s *AccountStore) Create(ctx context.Context, account *model.Account) error {
|
||||
return s.db.WithContext(ctx).Create(account).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取账号
|
||||
func (s *AccountStore) GetByID(ctx context.Context, id uint) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).First(&account, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取账号
|
||||
func (s *AccountStore) GetByUsername(ctx context.Context, username string) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("username = ?", username).First(&account).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetByPhone 根据手机号获取账号
|
||||
func (s *AccountStore) GetByPhone(ctx context.Context, phone string) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("phone = ?", phone).First(&account).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// Update 更新账号
|
||||
func (s *AccountStore) Update(ctx context.Context, account *model.Account) error {
|
||||
return s.db.WithContext(ctx).Save(account).Error
|
||||
}
|
||||
|
||||
// Delete 软删除账号
|
||||
func (s *AccountStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Account{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询账号列表
|
||||
func (s *AccountStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Account, int64, error) {
|
||||
var accounts []*model.Account
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Account{})
|
||||
|
||||
// 应用过滤条件
|
||||
if username, ok := filters["username"].(string); ok && username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+username+"%")
|
||||
}
|
||||
if phone, ok := filters["phone"].(string); ok && phone != "" {
|
||||
query = query.Where("phone LIKE ?", "%"+phone+"%")
|
||||
}
|
||||
if userType, ok := filters["user_type"].(int); ok {
|
||||
query = query.Where("user_type = ?", userType)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&accounts).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return accounts, total, nil
|
||||
}
|
||||
|
||||
// GetSubordinateIDs 获取用户的所有下级 ID(包含自己)
|
||||
// 使用 Redis 缓存优化性能,缓存 30 分钟
|
||||
func (s *AccountStore) GetSubordinateIDs(ctx context.Context, accountID uint) ([]uint, error) {
|
||||
// 1. 尝试从 Redis 缓存读取
|
||||
cacheKey := constants.RedisAccountSubordinatesKey(accountID)
|
||||
cached, err := s.redis.Get(ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
var ids []uint
|
||||
if err := sonic.Unmarshal([]byte(cached), &ids); err == nil {
|
||||
return ids, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 缓存未命中,执行递归查询
|
||||
query := `
|
||||
WITH RECURSIVE subordinates AS (
|
||||
-- 基础查询:选择当前账号
|
||||
SELECT id FROM tb_account WHERE id = ? AND deleted_at IS NULL
|
||||
UNION ALL
|
||||
-- 递归查询:选择所有下级(包括软删除的账号,因为它们的数据仍需对上级可见)
|
||||
SELECT a.id
|
||||
FROM tb_account a
|
||||
INNER JOIN subordinates s ON a.parent_id = s.id
|
||||
)
|
||||
SELECT id FROM subordinates
|
||||
`
|
||||
|
||||
var ids []uint
|
||||
if err := s.db.WithContext(ctx).Raw(query, accountID).Scan(&ids).Error; err != nil {
|
||||
return nil, fmt.Errorf("递归查询下级 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 写入 Redis 缓存(30 分钟过期)
|
||||
data, _ := sonic.Marshal(ids)
|
||||
s.redis.Set(ctx, cacheKey, data, 30*time.Minute)
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// ClearSubordinatesCache 清除指定账号的下级 ID 缓存
|
||||
func (s *AccountStore) ClearSubordinatesCache(ctx context.Context, accountID uint) error {
|
||||
cacheKey := constants.RedisAccountSubordinatesKey(accountID)
|
||||
return s.redis.Del(ctx, cacheKey).Err()
|
||||
}
|
||||
|
||||
// ClearSubordinatesCacheForParents 递归清除所有上级账号的缓存
|
||||
func (s *AccountStore) ClearSubordinatesCacheForParents(ctx context.Context, accountID uint) error {
|
||||
// 查询当前账号
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).First(&account, accountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除当前账号的缓存
|
||||
if err := s.ClearSubordinatesCache(ctx, accountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果有上级,递归清除上级的缓存
|
||||
if account.ParentID != nil && *account.ParentID != 0 {
|
||||
return s.ClearSubordinatesCacheForParents(ctx, *account.ParentID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
122
internal/store/postgres/permission_store.go
Normal file
122
internal/store/postgres/permission_store.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
// PermissionStore 权限数据访问层
|
||||
type PermissionStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPermissionStore 创建权限 Store
|
||||
func NewPermissionStore(db *gorm.DB) *PermissionStore {
|
||||
return &PermissionStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建权限
|
||||
func (s *PermissionStore) Create(ctx context.Context, permission *model.Permission) error {
|
||||
return s.db.WithContext(ctx).Create(permission).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取权限
|
||||
func (s *PermissionStore) GetByID(ctx context.Context, id uint) (*model.Permission, error) {
|
||||
var permission model.Permission
|
||||
if err := s.db.WithContext(ctx).First(&permission, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
// GetByCode 根据权限编码获取权限
|
||||
func (s *PermissionStore) GetByCode(ctx context.Context, code string) (*model.Permission, error) {
|
||||
var permission model.Permission
|
||||
if err := s.db.WithContext(ctx).Where("perm_code = ?", code).First(&permission).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
// Update 更新权限
|
||||
func (s *PermissionStore) Update(ctx context.Context, permission *model.Permission) error {
|
||||
return s.db.WithContext(ctx).Save(permission).Error
|
||||
}
|
||||
|
||||
// Delete 软删除权限
|
||||
func (s *PermissionStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Permission{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询权限列表
|
||||
func (s *PermissionStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Permission, int64, error) {
|
||||
var permissions []*model.Permission
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Permission{})
|
||||
|
||||
// 应用过滤条件
|
||||
if name, ok := filters["perm_name"].(string); ok && name != "" {
|
||||
query = query.Where("perm_name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if code, ok := filters["perm_code"].(string); ok && code != "" {
|
||||
query = query.Where("perm_code LIKE ?", "%"+code+"%")
|
||||
}
|
||||
if permType, ok := filters["perm_type"].(int); ok {
|
||||
query = query.Where("perm_type = ?", permType)
|
||||
}
|
||||
if parentID, ok := filters["parent_id"].(uint); ok {
|
||||
query = query.Where("parent_id = ?", parentID)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("sort ASC, id ASC")
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&permissions).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return permissions, total, nil
|
||||
}
|
||||
|
||||
// GetByIDs 根据 ID 列表获取权限
|
||||
func (s *PermissionStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Permission, error) {
|
||||
var permissions []*model.Permission
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&permissions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有权限(用于构建权限树)
|
||||
func (s *PermissionStore) GetAll(ctx context.Context) ([]*model.Permission, error) {
|
||||
var permissions []*model.Permission
|
||||
if err := s.db.WithContext(ctx).Order("sort ASC, id ASC").Find(&permissions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
91
internal/store/postgres/role_permission_store.go
Normal file
91
internal/store/postgres/role_permission_store.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// RolePermissionStore 角色-权限关联数据访问层
|
||||
type RolePermissionStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRolePermissionStore 创建角色-权限关联 Store
|
||||
func NewRolePermissionStore(db *gorm.DB) *RolePermissionStore {
|
||||
return &RolePermissionStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建角色-权限关联
|
||||
func (s *RolePermissionStore) Create(ctx context.Context, rp *model.RolePermission) error {
|
||||
return s.db.WithContext(ctx).Create(rp).Error
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建角色-权限关联
|
||||
func (s *RolePermissionStore) BatchCreate(ctx context.Context, rps []*model.RolePermission) error {
|
||||
return s.db.WithContext(ctx).Create(&rps).Error
|
||||
}
|
||||
|
||||
// Delete 软删除角色-权限关联
|
||||
func (s *RolePermissionStore) Delete(ctx context.Context, roleID, permID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("role_id = ? AND perm_id = ?", roleID, permID).
|
||||
Delete(&model.RolePermission{}).Error
|
||||
}
|
||||
|
||||
// DeleteByRoleID 删除角色的所有权限关联
|
||||
func (s *RolePermissionStore) DeleteByRoleID(ctx context.Context, roleID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("role_id = ?", roleID).
|
||||
Delete(&model.RolePermission{}).Error
|
||||
}
|
||||
|
||||
// GetByRoleID 获取角色的所有权限关联
|
||||
func (s *RolePermissionStore) GetByRoleID(ctx context.Context, roleID uint) ([]*model.RolePermission, error) {
|
||||
var rps []*model.RolePermission
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("role_id = ?", roleID).
|
||||
Find(&rps).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rps, nil
|
||||
}
|
||||
|
||||
// GetPermIDsByRoleID 获取角色的所有权限 ID
|
||||
func (s *RolePermissionStore) GetPermIDsByRoleID(ctx context.Context, roleID uint) ([]uint, error) {
|
||||
var permIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id = ?", roleID).
|
||||
Pluck("perm_id", &permIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permIDs, nil
|
||||
}
|
||||
|
||||
// GetPermIDsByRoleIDs 获取多个角色的所有权限 ID
|
||||
func (s *RolePermissionStore) GetPermIDsByRoleIDs(ctx context.Context, roleIDs []uint) ([]uint, error) {
|
||||
var permIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id IN ?", roleIDs).
|
||||
Distinct().
|
||||
Pluck("perm_id", &permIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permIDs, nil
|
||||
}
|
||||
|
||||
// Exists 检查角色-权限关联是否存在
|
||||
func (s *RolePermissionStore) Exists(ctx context.Context, roleID, permID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id = ? AND perm_id = ?", roleID, permID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
105
internal/store/postgres/role_store.go
Normal file
105
internal/store/postgres/role_store.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
// RoleStore 角色数据访问层
|
||||
type RoleStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRoleStore 创建角色 Store
|
||||
func NewRoleStore(db *gorm.DB) *RoleStore {
|
||||
return &RoleStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
func (s *RoleStore) Create(ctx context.Context, role *model.Role) error {
|
||||
return s.db.WithContext(ctx).Create(role).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取角色
|
||||
func (s *RoleStore) GetByID(ctx context.Context, id uint) (*model.Role, error) {
|
||||
var role model.Role
|
||||
if err := s.db.WithContext(ctx).First(&role, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// GetByName 根据名称获取角色
|
||||
func (s *RoleStore) GetByName(ctx context.Context, name string) (*model.Role, error) {
|
||||
var role model.Role
|
||||
if err := s.db.WithContext(ctx).Where("role_name = ?", name).First(&role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
func (s *RoleStore) Update(ctx context.Context, role *model.Role) error {
|
||||
return s.db.WithContext(ctx).Save(role).Error
|
||||
}
|
||||
|
||||
// Delete 软删除角色
|
||||
func (s *RoleStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Role{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询角色列表
|
||||
func (s *RoleStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Role, int64, error) {
|
||||
var roles []*model.Role
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Role{})
|
||||
|
||||
// 应用过滤条件
|
||||
if name, ok := filters["role_name"].(string); ok && name != "" {
|
||||
query = query.Where("role_name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if roleType, ok := filters["role_type"].(int); ok {
|
||||
query = query.Where("role_type = ?", roleType)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&roles).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return roles, total, nil
|
||||
}
|
||||
|
||||
// GetByIDs 根据 ID 列表获取角色
|
||||
func (s *RoleStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Role, error) {
|
||||
var roles []*model.Role
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&roles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
86
internal/store/postgres/scopes.go
Normal file
86
internal/store/postgres/scopes.go
Normal file
@@ -0,0 +1,86 @@
|
||||
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 软删除过滤 Scope(GORM 默认已支持,此处作为示例)
|
||||
// 只查询未软删除的记录
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user