rbac
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"portal-mini-go/internal/model/admin"
|
||||
"portal-mini-go/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// PermissionRepository 权限(接口 / 菜单)的数据访问。
|
||||
type PermissionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPermissionRepository(db *gorm.DB) *PermissionRepository {
|
||||
return &PermissionRepository{db: db}
|
||||
}
|
||||
|
||||
// GetByID 按 ID 查询权限,不存在返回 ErrNotFound。
|
||||
func (r *PermissionRepository) GetByID(ctx context.Context, id uint64) (*admin.Permission, error) {
|
||||
var m admin.Permission
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&m).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(50000, "query permission", err)
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// Create 新建权限。
|
||||
func (r *PermissionRepository) Create(ctx context.Context, m *admin.Permission) error {
|
||||
if err := r.db.WithContext(ctx).Create(m).Error; err != nil {
|
||||
return errors.Wrap(50000, "create permission", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PermListFilter 权限列表过滤条件。Type 为空表示不过滤类型。
|
||||
type PermListFilter struct {
|
||||
Type string
|
||||
Keyword string
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// List 分页查询权限,支持类型与关键字(名称 / code / 路径)过滤。
|
||||
func (r *PermissionRepository) List(ctx context.Context, f PermListFilter) ([]admin.Permission, int64, error) {
|
||||
q := r.db.WithContext(ctx).Model(&admin.Permission{})
|
||||
if f.Type != "" {
|
||||
q = q.Where("type = ?", f.Type)
|
||||
}
|
||||
if f.Keyword != "" {
|
||||
kw := "%" + f.Keyword + "%"
|
||||
q = q.Where("name LIKE ? OR code LIKE ? OR path LIKE ?", kw, kw, kw)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "count permission", err)
|
||||
}
|
||||
var list []admin.Permission
|
||||
if err := q.Order("sort ASC, id ASC").Offset((f.Page - 1) * f.Size).Limit(f.Size).Find(&list).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "list permission", err)
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// Update 按 ID 局部更新权限非零值字段。
|
||||
func (r *PermissionRepository) Update(ctx context.Context, id uint64, cols map[string]interface{}) error {
|
||||
res := r.db.WithContext(ctx).Model(&admin.Permission{}).Where("id = ?", id).Updates(cols)
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "update permission", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除权限,并清理角色-权限关联。
|
||||
func (r *PermissionRepository) Delete(ctx context.Context, id uint64) error {
|
||||
if err := r.db.WithContext(ctx).Where("permission_id = ?", id).Delete(&admin.RolePermission{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "delete role permissions", err)
|
||||
}
|
||||
res := r.db.WithContext(ctx).Where("id = ?", id).Delete(&admin.Permission{})
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "delete permission", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllMenuPermissions 返回全部菜单类型权限(用于组装菜单树)。
|
||||
func (r *PermissionRepository) AllMenuPermissions(ctx context.Context) ([]admin.Permission, error) {
|
||||
var list []admin.Permission
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("type = ?", admin.PermTypeMenu).
|
||||
Order("sort ASC, id ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
return nil, errors.Wrap(50000, "list menu permissions", err)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// AllCodes 返回全部权限 code(超级管理员短路放行时使用)。
|
||||
func (r *PermissionRepository) AllCodes(ctx context.Context) ([]string, error) {
|
||||
var codes []string
|
||||
if err := r.db.WithContext(ctx).Model(&admin.Permission{}).Pluck("code", &codes).Error; err != nil {
|
||||
return nil, errors.Wrap(50000, "list permission codes", err)
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"portal-mini-go/internal/model/admin"
|
||||
"portal-mini-go/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// RoleRepository 角色与权限关联的数据访问。
|
||||
type RoleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRoleRepository(db *gorm.DB) *RoleRepository { return &RoleRepository{db: db} }
|
||||
|
||||
// GetByID 按 ID 查询角色,不存在返回 ErrNotFound。
|
||||
func (r *RoleRepository) GetByID(ctx context.Context, id uint64) (*admin.Role, error) {
|
||||
var m admin.Role
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&m).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(50000, "query role", err)
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// GetByCode 按 code 查询角色,不存在返回 ErrNotFound。
|
||||
func (r *RoleRepository) GetByCode(ctx context.Context, code string) (*admin.Role, error) {
|
||||
var m admin.Role
|
||||
if err := r.db.WithContext(ctx).Where("code = ?", code).First(&m).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(50000, "query role", err)
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// Create 新建角色。
|
||||
func (r *RoleRepository) Create(ctx context.Context, m *admin.Role) error {
|
||||
if err := r.db.WithContext(ctx).Create(m).Error; err != nil {
|
||||
return errors.Wrap(50000, "create role", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoleListFilter 角色列表过滤条件。
|
||||
type RoleListFilter struct {
|
||||
Keyword string
|
||||
Status int8
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// List 分页查询角色,支持关键字与状态过滤。
|
||||
func (r *RoleRepository) List(ctx context.Context, f RoleListFilter) ([]admin.Role, int64, error) {
|
||||
q := r.db.WithContext(ctx).Model(&admin.Role{})
|
||||
if f.Status >= 0 {
|
||||
q = q.Where("status = ?", f.Status)
|
||||
}
|
||||
if f.Keyword != "" {
|
||||
kw := "%" + f.Keyword + "%"
|
||||
q = q.Where("name LIKE ? OR code LIKE ?", kw, kw)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "count role", err)
|
||||
}
|
||||
var list []admin.Role
|
||||
if err := q.Order("id DESC").Offset((f.Page - 1) * f.Size).Limit(f.Size).Find(&list).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "list role", err)
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// Update 按 ID 局部更新角色非零值字段。
|
||||
func (r *RoleRepository) Update(ctx context.Context, id uint64, cols map[string]interface{}) error {
|
||||
res := r.db.WithContext(ctx).Model(&admin.Role{}).Where("id = ?", id).Updates(cols)
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "update role", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除角色,并清理角色-权限与管理员-角色关联。
|
||||
func (r *RoleRepository) Delete(ctx context.Context, id uint64) error {
|
||||
if err := r.db.WithContext(ctx).Where("role_id = ?", id).Delete(&admin.RolePermission{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "delete role permissions", err)
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Where("role_id = ?", id).Delete(&admin.UserRole{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "delete user roles", err)
|
||||
}
|
||||
res := r.db.WithContext(ctx).Where("id = ?", id).Delete(&admin.Role{})
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "delete role", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPermissions 全量替换角色所拥有的权限(先删后插)。
|
||||
func (r *RoleRepository) SetPermissions(ctx context.Context, roleID uint64, permIDs []uint64) error {
|
||||
if err := r.db.WithContext(ctx).Where("role_id = ?", roleID).Delete(&admin.RolePermission{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "reset role permissions", err)
|
||||
}
|
||||
if len(permIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
rows := make([]admin.RolePermission, 0, len(permIDs))
|
||||
for _, pid := range permIDs {
|
||||
rows = append(rows, admin.RolePermission{RoleID: roleID, PermissionID: pid})
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Create(&rows).Error; err != nil {
|
||||
return errors.Wrap(50000, "assign role permissions", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PermissionCodesOf 返回角色拥有的权限 code 列表。
|
||||
func (r *RoleRepository) PermissionCodesOf(ctx context.Context, roleID uint64) ([]string, error) {
|
||||
var codes []string
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("admin_role_permissions rp").
|
||||
Joins("JOIN admin_permissions p ON p.id = rp.permission_id").
|
||||
Where("rp.role_id = ?", roleID).
|
||||
Pluck("p.code", &codes).Error; err != nil {
|
||||
return nil, errors.Wrap(50000, "query role permission codes", err)
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Package admin 提供后台 RBAC 相关表的数据访问。
|
||||
// 包含管理员(admin_users)、角色(admin_roles)、权限(admin_permissions)
|
||||
// 及其多对多关联表的读写。所有查询均参数化,杜绝 SQL 注入。
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"portal-mini-go/internal/model/admin"
|
||||
"portal-mini-go/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// UserRepository 管理员账号与角色关联的数据访问。
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) *UserRepository { return &UserRepository{db: db} }
|
||||
|
||||
// GetByUsername 按用户名查询管理员,不存在返回 ErrNotFound。
|
||||
func (r *UserRepository) GetByUsername(ctx context.Context, username string) (*admin.User, error) {
|
||||
var u admin.User
|
||||
if err := r.db.WithContext(ctx).Where("username = ?", username).First(&u).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(50000, "query admin user", err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// GetByID 按 ID 查询管理员,不存在返回 ErrNotFound。
|
||||
func (r *UserRepository) GetByID(ctx context.Context, id uint64) (*admin.User, error) {
|
||||
var u admin.User
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&u).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, errors.Wrap(50000, "query admin user", err)
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// Create 新建管理员。
|
||||
func (r *UserRepository) Create(ctx context.Context, u *admin.User) error {
|
||||
if err := r.db.WithContext(ctx).Create(u).Error; err != nil {
|
||||
return errors.Wrap(50000, "create admin user", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计管理员总数。
|
||||
func (r *UserRepository) Count(ctx context.Context) (int64, error) {
|
||||
var n int64
|
||||
if err := r.db.WithContext(ctx).Model(&admin.User{}).Count(&n).Error; err != nil {
|
||||
return 0, errors.Wrap(50000, "count admin user", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// UserListFilter 管理员列表过滤条件。
|
||||
type UserListFilter struct {
|
||||
Keyword string
|
||||
Status int8 // <0 表示不过滤
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// List 分页查询管理员,支持关键字(用户名 / 昵称)与状态过滤。
|
||||
func (r *UserRepository) List(ctx context.Context, f UserListFilter) ([]admin.User, int64, error) {
|
||||
q := r.db.WithContext(ctx).Model(&admin.User{})
|
||||
if f.Status >= 0 {
|
||||
q = q.Where("status = ?", f.Status)
|
||||
}
|
||||
if f.Keyword != "" {
|
||||
kw := "%" + f.Keyword + "%"
|
||||
q = q.Where("username LIKE ? OR nickname LIKE ?", kw, kw)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "count admin user", err)
|
||||
}
|
||||
var list []admin.User
|
||||
if err := q.Order("id DESC").Offset((f.Page - 1) * f.Size).Limit(f.Size).Find(&list).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(50000, "list admin user", err)
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// Update 按 ID 局部更新管理员非零值字段(cols 为待更新列)。
|
||||
func (r *UserRepository) Update(ctx context.Context, id uint64, cols map[string]interface{}) error {
|
||||
res := r.db.WithContext(ctx).Model(&admin.User{}).Where("id = ?", id).Updates(cols)
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "update admin user", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除管理员,并清理其角色关联。
|
||||
func (r *UserRepository) Delete(ctx context.Context, id uint64) error {
|
||||
if err := r.db.WithContext(ctx).Where("admin_id = ?", id).Delete(&admin.UserRole{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "delete admin user roles", err)
|
||||
}
|
||||
res := r.db.WithContext(ctx).Where("id = ?", id).Delete(&admin.User{})
|
||||
if res.Error != nil {
|
||||
return errors.Wrap(50000, "delete admin user", res.Error)
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRoles 全量替换管理员所属角色(先删后插)。
|
||||
func (r *UserRepository) SetRoles(ctx context.Context, adminID uint64, roleIDs []uint64) error {
|
||||
if err := r.db.WithContext(ctx).Where("admin_id = ?", adminID).Delete(&admin.UserRole{}).Error; err != nil {
|
||||
return errors.Wrap(50000, "reset admin roles", err)
|
||||
}
|
||||
if len(roleIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
rows := make([]admin.UserRole, 0, len(roleIDs))
|
||||
for _, rid := range roleIDs {
|
||||
rows = append(rows, admin.UserRole{AdminID: adminID, RoleID: rid})
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Create(&rows).Error; err != nil {
|
||||
return errors.Wrap(50000, "assign admin roles", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoleCodesOf 返回管理员所属角色 code 列表(经 admin_user_roles 关联)。
|
||||
func (r *UserRepository) RoleCodesOf(ctx context.Context, adminID uint64) ([]string, error) {
|
||||
var codes []string
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("admin_user_roles").
|
||||
Joins("JOIN admin_roles ON admin_roles.id = admin_user_roles.role_id").
|
||||
Where("admin_user_roles.admin_id = ?", adminID).
|
||||
Pluck("admin_roles.code", &codes).Error; err != nil {
|
||||
return nil, errors.Wrap(50000, "query admin role codes", err)
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
|
||||
// PermissionCodesOf 返回管理员经角色聚合后的全部权限 code 列表(去重)。
|
||||
func (r *UserRepository) PermissionCodesOf(ctx context.Context, adminID uint64) ([]string, error) {
|
||||
var codes []string
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("admin_user_roles ur").
|
||||
Joins("JOIN admin_role_permissions rp ON rp.role_id = ur.role_id").
|
||||
Joins("JOIN admin_permissions p ON p.id = rp.permission_id").
|
||||
Where("ur.admin_id = ?", adminID).
|
||||
Pluck("p.code", &codes).Error; err != nil {
|
||||
return nil, errors.Wrap(50000, "query admin permission codes", err)
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
Reference in New Issue
Block a user