package rolestore import ( "context" "database/sql" "errors" "fmt" "strings" "sync" "time" "aijianzhan/platform/internal/authx" ) type Role struct { RoleID int64 `json:"role_id"` TenantID int64 `json:"tenant_id"` Code string `json:"code"` Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` CreatedAt time.Time `json:"created_at"` } type CreateInput struct { Code string Name string Description string Permissions []string } type UpdateInput struct { Name *string Description *string Permissions *[]string } type Store interface { List(ctx context.Context, tenantID int64) ([]Role, error) Get(ctx context.Context, tenantID, roleID int64) (*Role, error) Create(ctx context.Context, tenantID int64, in CreateInput) (*Role, error) Update(ctx context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) Delete(ctx context.Context, tenantID, roleID int64) error EnsureDefaults(ctx context.Context, tenantID int64) error } var defaultRoles = []CreateInput{ { Code: authx.AgentRole生成发布, Name: "生成发布", Description: "生成蓝图、发布应用并导入业务数据(智能体常用最低权限)", Permissions: []string{ "读取模块", "发布模块", "导入数据", "查询数据", "上传文件", "下载文件", }, }, { Code: authx.AgentRole只读, Name: "只读", Description: "读应用与数据,可导出,不可改写", Permissions: []string{"读取模块", "查询数据", "导出数据", "下载文件", "查看审计"}, }, { Code: authx.AgentRole读写, Name: "读写", Description: "读写业务数据与导入导出,不可发布模块", Permissions: []string{ "读取模块", "写入模块", "新增数据", "查询数据", "更新数据", "导出数据", "导入数据", "上传文件", "下载文件", }, }, { Code: authx.AgentRole运维, Name: "运维", Description: "含发布、删除及全量行操作,适合运维类智能体", Permissions: []string{ "读取模块", "写入模块", "发布模块", "新增数据", "查询数据", "更新数据", "删除数据", "导出数据", "导入数据", "上传文件", "下载文件", "查看审计", }, }, } type memRole struct{ Role } type MemoryStore struct { mu sync.Mutex seq int64 by map[int64]*memRole } func NewMemoryStore() *MemoryStore { return &MemoryStore{by: map[int64]*memRole{}} } func findDefaultRole(byCode map[string]Role, d CreateInput) (Role, bool) { if r, ok := byCode[d.Code]; ok { return r, true } legacy := map[string]string{ "publisher": authx.AgentRole生成发布, "viewer": authx.AgentRole只读, "editor": authx.AgentRole读写, "operator": authx.AgentRole运维, } for eng, zh := range legacy { if zh == d.Code { if r, ok := byCode[eng]; ok { return r, true } } } return Role{}, false } func (s *MemoryStore) EnsureDefaults(ctx context.Context, tenantID int64) error { list, err := s.List(ctx, tenantID) if err != nil { return err } byCode := map[string]Role{} for _, r := range list { byCode[r.Code] = r } for _, d := range defaultRoles { if existing, ok := findDefaultRole(byCode, d); ok { if d.Code == authx.AgentRole生成发布 { perms := append([]string{}, d.Permissions...) if _, err := s.Update(ctx, tenantID, existing.RoleID, UpdateInput{ Name: &d.Name, Description: &d.Description, Permissions: &perms, }); err != nil { return err } } continue } if _, err := s.Create(ctx, tenantID, d); err != nil { return err } } return nil } func (s *MemoryStore) List(_ context.Context, tenantID int64) ([]Role, error) { s.mu.Lock() defer s.mu.Unlock() out := []Role{} for _, r := range s.by { if r.TenantID == tenantID { out = append(out, clone(r.Role)) } } return out, nil } func (s *MemoryStore) Get(_ context.Context, tenantID, roleID int64) (*Role, error) { s.mu.Lock() defer s.mu.Unlock() r, ok := s.by[roleID] if !ok || r.TenantID != tenantID { return nil, fmt.Errorf("role not found") } cp := clone(r.Role) return &cp, nil } func (s *MemoryStore) Create(_ context.Context, tenantID int64, in CreateInput) (*Role, error) { s.mu.Lock() defer s.mu.Unlock() code := strings.TrimSpace(in.Code) name := strings.TrimSpace(in.Name) if code == "" || name == "" { return nil, fmt.Errorf("code and name required") } for _, r := range s.by { if r.TenantID == tenantID && r.Code == code { return nil, fmt.Errorf("role code already exists") } } s.seq++ r := &memRole{Role: Role{ RoleID: s.seq, TenantID: tenantID, Code: code, Name: name, Description: strings.TrimSpace(in.Description), Permissions: uniq(in.Permissions), CreatedAt: time.Now().UTC(), }} s.by[r.RoleID] = r cp := clone(r.Role) return &cp, nil } func (s *MemoryStore) Update(_ context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) { s.mu.Lock() defer s.mu.Unlock() r, ok := s.by[roleID] if !ok || r.TenantID != tenantID { return nil, fmt.Errorf("role not found") } if in.Name != nil { r.Name = strings.TrimSpace(*in.Name) } if in.Description != nil { r.Description = strings.TrimSpace(*in.Description) } if in.Permissions != nil { r.Permissions = uniq(*in.Permissions) } cp := clone(r.Role) return &cp, nil } func (s *MemoryStore) Delete(_ context.Context, tenantID, roleID int64) error { s.mu.Lock() defer s.mu.Unlock() r, ok := s.by[roleID] if !ok || r.TenantID != tenantID { return fmt.Errorf("role not found") } delete(s.by, roleID) return nil } type PostgresStore struct{ DB *sql.DB } func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db} } func (s *PostgresStore) EnsureDefaults(ctx context.Context, tenantID int64) error { list, err := s.List(ctx, tenantID) if err != nil { return err } byCode := map[string]Role{} for _, r := range list { byCode[r.Code] = r } for _, d := range defaultRoles { if existing, ok := findDefaultRole(byCode, d); ok { // 英文旧编码升级为中文 if existing.Code != d.Code { if _, err := s.DB.ExecContext(ctx, `UPDATE platform_meta.roles SET code=$1, name=$2 WHERE role_id=$3 AND tenant_id=$4`, d.Code, d.Name, existing.RoleID, tenantID); err != nil { return err } existing.Code = d.Code existing.Name = d.Name byCode[d.Code] = existing } if d.Code == authx.AgentRole生成发布 { perms := append([]string{}, d.Permissions...) if _, err := s.Update(ctx, tenantID, existing.RoleID, UpdateInput{ Name: &d.Name, Description: &d.Description, Permissions: &perms, }); err != nil { return err } } continue } if _, err := s.Create(ctx, tenantID, d); err != nil { return err } } return nil } func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]Role, error) { rows, err := s.DB.QueryContext(ctx, ` SELECT role_id, tenant_id, code, name, description, created_at FROM platform_meta.roles WHERE tenant_id=$1 ORDER BY role_id`, tenantID) if err != nil { return nil, err } defer rows.Close() var out []Role for rows.Next() { var r Role if err := rows.Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt); err != nil { return nil, err } perms, err := s.loadPerms(ctx, r.RoleID) if err != nil { return nil, err } r.Permissions = perms out = append(out, r) } return out, rows.Err() } func (s *PostgresStore) Get(ctx context.Context, tenantID, roleID int64) (*Role, error) { var r Role err := s.DB.QueryRowContext(ctx, ` SELECT role_id, tenant_id, code, name, description, created_at FROM platform_meta.roles WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID, ).Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return nil, fmt.Errorf("role not found") } if err != nil { return nil, err } perms, err := s.loadPerms(ctx, r.RoleID) if err != nil { return nil, err } r.Permissions = perms return &r, nil } func (s *PostgresStore) Create(ctx context.Context, tenantID int64, in CreateInput) (*Role, error) { code := strings.TrimSpace(in.Code) name := strings.TrimSpace(in.Name) if code == "" || name == "" { return nil, fmt.Errorf("code and name required") } var r Role err := s.DB.QueryRowContext(ctx, ` INSERT INTO platform_meta.roles(tenant_id, code, name, description) VALUES($1,$2,$3,$4) RETURNING role_id, tenant_id, code, name, description, created_at`, tenantID, code, name, strings.TrimSpace(in.Description), ).Scan(&r.RoleID, &r.TenantID, &r.Code, &r.Name, &r.Description, &r.CreatedAt) if err != nil { return nil, err } perms := uniq(in.Permissions) if err := s.replacePerms(ctx, r.RoleID, perms); err != nil { return nil, err } r.Permissions = perms return &r, nil } func (s *PostgresStore) Update(ctx context.Context, tenantID, roleID int64, in UpdateInput) (*Role, error) { r, err := s.Get(ctx, tenantID, roleID) if err != nil { return nil, err } name, desc := r.Name, r.Description if in.Name != nil { name = strings.TrimSpace(*in.Name) } if in.Description != nil { desc = strings.TrimSpace(*in.Description) } if _, err := s.DB.ExecContext(ctx, ` UPDATE platform_meta.roles SET name=$1, description=$2 WHERE role_id=$3 AND tenant_id=$4`, name, desc, roleID, tenantID); err != nil { return nil, err } if in.Permissions != nil { if err := s.replacePerms(ctx, roleID, uniq(*in.Permissions)); err != nil { return nil, err } } return s.Get(ctx, tenantID, roleID) } func (s *PostgresStore) Delete(ctx context.Context, tenantID, roleID int64) error { _, _ = s.DB.ExecContext(ctx, ` UPDATE platform_meta.agent_accounts SET role_id=NULL WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID) res, err := s.DB.ExecContext(ctx, ` DELETE FROM platform_meta.roles WHERE role_id=$1 AND tenant_id=$2`, roleID, tenantID) if err != nil { return err } n, _ := res.RowsAffected() if n == 0 { return fmt.Errorf("role not found") } return nil } func (s *PostgresStore) loadPerms(ctx context.Context, roleID int64) ([]string, error) { rows, err := s.DB.QueryContext(ctx, `SELECT perm FROM platform_meta.role_permissions WHERE role_id=$1`, roleID) if err != nil { return nil, err } defer rows.Close() var out []string for rows.Next() { var p string if err := rows.Scan(&p); err != nil { return nil, err } out = append(out, p) } if err := rows.Err(); err != nil { return nil, err } return authx.NormalizePerms(out), nil } func (s *PostgresStore) replacePerms(ctx context.Context, roleID int64, perms []string) error { tx, err := s.DB.BeginTx(ctx, nil) if err != nil { return err } defer func() { _ = tx.Rollback() }() if _, err := tx.ExecContext(ctx, `DELETE FROM platform_meta.role_permissions WHERE role_id=$1`, roleID); err != nil { return err } for _, p := range authx.NormalizePerms(perms) { if _, err := tx.ExecContext(ctx, `INSERT INTO platform_meta.role_permissions(role_id, perm) VALUES($1,$2)`, roleID, p); err != nil { return err } } return tx.Commit() } func clone(r Role) Role { cp := r cp.Permissions = append([]string{}, r.Permissions...) return cp } func uniq(in []string) []string { return authx.NormalizePerms(in) }