- 4 表设计: permissions, roles, role_permissions(m2m), user_roles - 多租户隔离: 角色和用户角色分配按 tenant_id 隔离 - 权限并集: 多角色用户取权限并集,无继承 - 系统角色保护: is_system 角色不可修改/删除 - 种子数据: 7 个默认权限 + 3 个系统角色(super_admin/tenant_admin/member) - 权限中间件: RequirePermission 支持按路由粒度控制 - API 路由: /api/v1/rbac/ 下完整 CRUD 端点 - fx 注册: RBAC Module 集成到 app.go
180 lines
4.6 KiB
Go
180 lines
4.6 KiB
Go
package infrastructure
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"mengstack/internal/modules/rbac/domain"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type permDef struct {
|
|
Code string
|
|
Name string
|
|
Module string
|
|
Desc string
|
|
}
|
|
|
|
var defaultPermissions = []permDef{
|
|
{"auth.user.read", "查看用户", "auth", "查看用户列表和详情"},
|
|
{"auth.user.write", "管理用户", "auth", "创建、编辑、删除用户"},
|
|
{"rbac.role.read", "查看角色", "rbac", "查看角色列表和详情"},
|
|
{"rbac.role.write", "管理角色", "rbac", "创建、编辑、删除角色"},
|
|
{"rbac.permission.read", "查看权限", "rbac", "查看权限列表"},
|
|
{"rbac.permission.assign", "分配权限", "rbac", "为角色分配权限"},
|
|
{"rbac.user_role.write", "分配角色", "rbac", "为用户分配角色"},
|
|
}
|
|
|
|
func Seed(db *gorm.DB, log *zap.Logger) error {
|
|
ctx := context.Background()
|
|
|
|
for _, p := range defaultPermissions {
|
|
var count int64
|
|
db.Model(&domain.Permission{}).Where("code = ?", p.Code).Count(&count)
|
|
if count > 0 {
|
|
continue
|
|
}
|
|
perm := &domain.Permission{
|
|
Code: p.Code,
|
|
Name: p.Name,
|
|
Module: p.Module,
|
|
Description: p.Desc,
|
|
}
|
|
if err := db.WithContext(ctx).Create(perm).Error; err != nil {
|
|
return err
|
|
}
|
|
log.Info("seeded permission", zap.String("code", p.Code))
|
|
}
|
|
|
|
var allPerms []domain.Permission
|
|
if err := db.WithContext(ctx).Find(&allPerms).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(allPerms) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var tenantIDs []string
|
|
if err := db.WithContext(ctx).Table("users").Distinct("tenant_id").
|
|
Pluck("tenant_id", &tenantIDs).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, tid := range tenantIDs {
|
|
if err := seedTenantRoles(ctx, db, log, tid, allPerms); err != nil {
|
|
log.Error("seed tenant roles failed",
|
|
zap.String("tenant_id", tid),
|
|
zap.Error(err),
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedTenantRoles(ctx context.Context, db *gorm.DB, log *zap.Logger, tenantID string, allPerms []domain.Permission) error {
|
|
superAdmin := &domain.Role{}
|
|
err := db.WithContext(ctx).
|
|
Where("tenant_id = ? AND name = ?", tenantID, "super_admin").
|
|
First(superAdmin).Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
superAdmin = &domain.Role{
|
|
TenantID: tenantID,
|
|
Name: "super_admin",
|
|
Description: "超级管理员",
|
|
IsSystem: true,
|
|
}
|
|
if err := db.WithContext(ctx).Create(superAdmin).Error; err != nil {
|
|
return err
|
|
}
|
|
permRefs := make([]domain.Permission, len(allPerms))
|
|
copy(permRefs, allPerms)
|
|
if err := db.WithContext(ctx).Model(superAdmin).Association("Permissions").Replace(permRefs); err != nil {
|
|
return err
|
|
}
|
|
log.Info("seeded super_admin role", zap.String("tenant_id", tenantID))
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
tenantAdmin := &domain.Role{}
|
|
err = db.WithContext(ctx).
|
|
Where("tenant_id = ? AND name = ?", tenantID, "tenant_admin").
|
|
First(tenantAdmin).Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
tenantAdmin = &domain.Role{
|
|
TenantID: tenantID,
|
|
Name: "tenant_admin",
|
|
Description: "租户管理员",
|
|
IsSystem: true,
|
|
}
|
|
if err := db.WithContext(ctx).Create(tenantAdmin).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
adminPermCodes := []string{
|
|
"auth.user.read", "auth.user.write",
|
|
"rbac.role.read", "rbac.permission.read", "rbac.user_role.write",
|
|
}
|
|
var adminPerms []domain.Permission
|
|
for _, p := range allPerms {
|
|
for _, code := range adminPermCodes {
|
|
if p.Code == code {
|
|
adminPerms = append(adminPerms, p)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(adminPerms) > 0 {
|
|
if err := db.WithContext(ctx).Model(tenantAdmin).Association("Permissions").Replace(adminPerms); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Info("seeded tenant_admin role", zap.String("tenant_id", tenantID))
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
member := &domain.Role{}
|
|
err = db.WithContext(ctx).
|
|
Where("tenant_id = ? AND name = ?", tenantID, "member").
|
|
First(member).Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
member = &domain.Role{
|
|
TenantID: tenantID,
|
|
Name: "member",
|
|
Description: "普通成员",
|
|
IsSystem: true,
|
|
}
|
|
if err := db.WithContext(ctx).Create(member).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
memberPermCodes := []string{"auth.user.read", "rbac.permission.read"}
|
|
var memberPerms []domain.Permission
|
|
for _, p := range allPerms {
|
|
for _, code := range memberPermCodes {
|
|
if strings.EqualFold(p.Code, code) {
|
|
memberPerms = append(memberPerms, p)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(memberPerms) > 0 {
|
|
if err := db.WithContext(ctx).Model(member).Association("Permissions").Replace(memberPerms); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Info("seeded member role", zap.String("tenant_id", tenantID))
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|