热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
针对特定应用类型、技术栈和开发任务,提供多种解决方案的深度剖析与优劣权衡,帮助开发者理解技术选型背后的考量,选择最适合当前场景的最佳实践。
以下内容面向具备 TypeScript 与 NestJS 实战经验的团队,目标是在单库 PostgreSQL 上构建一个可扩展的多租户 RBAC 权限系统,并通过“文生代码”生成实体模型、仓储、鉴权中间件、控制器、OpenAPI 合约与单元测试模板。将给出三种策略评估方案的最佳实践与取舍:NestJS Guards + CASL、自研策略引擎、OPA sidecar。并结合性能、审计、部署与 CI/CD、可维护性等约束给出完整指导与代码骨架。
一、总体架构与关键决策
二、Prisma 数据模型(关键表与索引示例) 说明:以下为核心模型骨架,实际项目可按资源域扩展。支持租户隔离、资源层级、角色绑定、策略与审计。
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
enum Action {
READ
CREATE
UPDATE
DELETE
MANAGE // 管理(分配、授权)
}
enum Effect {
ALLOW
DENY
}
enum SubjectType {
ROLE
USER
}
enum ResourceType {
TENANT
PROJECT
DATASET
RECORD
}
model Tenant {
id String @id @default(uuid())
name String
createdAt DateTime @default(now())
users UserTenant[]
roles Role[]
resources Resource[]
policies Policy[]
roleBindings RoleBinding[]
}
model User {
id String @id @default(uuid())
email String @unique
displayName String?
createdAt DateTime @default(now())
memberships UserTenant[]
roleBindings RoleBinding[]
auditLogs AuditLog[] @relation("ActorAuditLog")
}
model UserTenant {
id String @id @default(uuid())
userId String
tenantId String
status String // active, suspended
roles RoleBinding[]
@@index([tenantId, userId], name: "idx_user_tenant_unique", type: BTree)
@@unique([tenantId, userId])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
}
model Role {
id String @id @default(uuid())
tenantId String? // null 代表系统级角色(全局)
name String
description String?
isSystem Boolean @default(false)
createdAt DateTime @default(now())
tenant Tenant? @relation(fields: [tenantId], references: [id], onDelete: SetNull)
policies Policy[] // 可选:角色直接关联策略
bindings RoleBinding[]
@@unique([tenantId, name])
@@index([tenantId])
}
model Resource {
id String @id @default(uuid())
tenantId String
type ResourceType
parentId String? // 层级父节点
path String // 材料化路径(如 /project/{id}/dataset/{id}/record/{id})
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
parent Resource? @relation("ParentChild", fields: [parentId], references: [id])
children Resource[] @relation("ParentChild")
closures ResourceClosure[] @relation("ResourceClosureDesc", references: [descendantId])
@@index([tenantId, type])
@@index([tenantId, path])
}
model ResourceClosure {
ancestorId String
descendantId String
depth Int
ancestor Resource @relation("ResourceClosureAnc", fields: [ancestorId], references: [id], onDelete: Cascade)
descendant Resource @relation("ResourceClosureDesc", fields: [descendantId], references: [id], onDelete: Cascade)
@@id([ancestorId, descendantId])
@@index([descendantId])
}
model RoleBinding {
id String @id @default(uuid())
tenantId String
userId String
roleId String
scopeResourceId String? // 作用域(某项目/数据集)
expiresAt DateTime?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
scope Resource? @relation(fields: [scopeResourceId], references: [id], onDelete: SetNull)
@@index([tenantId, userId])
@@index([tenantId, roleId])
@@index([tenantId, scopeResourceId])
}
model Policy {
id String @id @default(uuid())
tenantId String
subjectType SubjectType
subjectId String // Role.id 或 User.id
action Action
resourceType ResourceType
scopeResourceId String?
effect Effect
priority Int @default(100) // 数字越小优先级越高
conditions Json? // ABAC 条件(JSONLogic/CASL 条件)
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
scope Resource? @relation(fields: [scopeResourceId], references: [id], onDelete: SetNull)
@@index([tenantId, subjectType, subjectId])
@@index([tenantId, action, resourceType])
}
model AuditLog {
id String @id @default(uuid())
tenantId String
actorUserId String?
action String // create_resource, assign_role, policy_eval, login, etc.
entityType String
entityId String?
requestId String?
ip String?
userAgent String?
result String? // allow/deny/failed
before Json?
after Json?
error String?
createdAt DateTime @default(now())
actor User? @relation("ActorAuditLog", fields: [actorUserId], references: [id], onDelete: SetNull)
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
@@index([tenantId, createdAt])
}
三、PostgreSQL RLS 策略与审计触发器(迁移 SQL 片段) 说明:通过会话 GUC 变量 app.tenant_id 与 app.actor_id,在事务中设置,RLS 保证任何表只访问当前租户数据。Prisma 可在 $transaction 中使用 SET LOCAL。
-- prisma/migrations/<timestamp>_rls_audit/migration.sql
-- 开启 RLS
ALTER TABLE "Tenant" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "UserTenant" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Role" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Resource" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "RoleBinding" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Policy" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "AuditLog" ENABLE ROW LEVEL SECURITY;
-- 通用 RLS policy: tenant_id = current_setting('app.tenant_id', true)
DO $$
BEGIN
FOR t IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
LOOP
EXECUTE format('CREATE POLICY tenant_isolation ON %I USING (tenant_id::text = current_setting(''app.tenant_id'', true))', t.tablename)
ON CONFLICT DO NOTHING;
END LOOP;
END $$;
-- 审计触发器函数:记录 before/after,使用 app.actor_id 注入主体
CREATE OR REPLACE FUNCTION app.audit_trigger() RETURNS trigger AS $func$
DECLARE
actor TEXT := current_setting('app.actor_id', true);
tid TEXT := current_setting('app.tenant_id', true);
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO "AuditLog"(id, tenantId, actorUserId, action, entityType, entityId, after, createdAt)
VALUES (gen_random_uuid()::text, tid, actor, TG_ARGV[0], TG_TABLE_NAME, NEW.id::text, TO_JSONB(NEW), now());
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO "AuditLog"(id, tenantId, actorUserId, action, entityType, entityId, before, after, createdAt)
VALUES (gen_random_uuid()::text, tid, actor, TG_ARGV[0], TG_TABLE_NAME, NEW.id::text, TO_JSONB(OLD), TO_JSONB(NEW), now());
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO "AuditLog"(id, tenantId, actorUserId, action, entityType, entityId, before, createdAt)
VALUES (gen_random_uuid()::text, tid, actor, TG_ARGV[0], TG_TABLE_NAME, OLD.id::text, TO_JSONB(OLD), now());
RETURN OLD;
END IF;
END
$func$ LANGUAGE plpgsql;
-- 给关键表挂载审计触发器
CREATE TRIGGER audit_resource_ins AFTER INSERT ON "Resource"
FOR EACH ROW EXECUTE FUNCTION app.audit_trigger('create_resource');
CREATE TRIGGER audit_resource_upd AFTER UPDATE ON "Resource"
FOR EACH ROW EXECUTE FUNCTION app.audit_trigger('update_resource');
CREATE TRIGGER audit_resource_del AFTER DELETE ON "Resource"
FOR EACH ROW EXECUTE FUNCTION app.audit_trigger('delete_resource');
-- 更多表按需添加触发器...
四、NestJS 代码骨架(鉴权、仓储、控制器、OpenAPI、测试)
// src/common/tenant-context.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { PrismaService } from '../infra/prisma.service';
import { Observable, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Injectable()
export class TenantContextInterceptor implements NestInterceptor {
constructor(private readonly prisma: PrismaService) {}
intercept(ctx: ExecutionContext, next: CallHandler): Observable<any> {
const req = ctx.switchToHttp().getRequest();
const tenantId = req.headers['x-tenant-id'];
const actorId = req.user?.id ?? null;
if (!tenantId) throw new Error('Missing X-Tenant-Id');
// 将请求处理包装到一个事务内,设置会话变量(RLS + 审计)
return from(this.prisma.$transaction(async (tx) => {
await tx.$executeRawUnsafe(`SET LOCAL app.tenant_id = '${tenantId}'`);
if (actorId) await tx.$executeRawUnsafe(`SET LOCAL app.actor_id = '${actorId}'`);
// 将 tx 注入 req 以供仓储层复用
(req as any).db = tx;
return next.handle().toPromise();
}));
}
}
// src/infra/prisma.service.ts
import { Injectable, OnModuleInit, INestApplication } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() { await this.$connect(); }
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => { await app.close(); });
}
}
// src/repositories/resource.repository.ts
import { Prisma, Resource } from '@prisma/client';
export class ResourceRepository {
constructor(private readonly db: Prisma.TransactionClient) {}
async findById(id: string): Promise<Resource | null> {
// 受 RLS 限制,无需手动过滤 tenant_id
return this.db.resource.findUnique({ where: { id } });
}
async listByScope(tenantId: string, type: 'PROJECT' | 'DATASET'): Promise<Resource[]> {
// 显式带 tenantId 可走索引;RLS 仍兜底
return this.db.resource.findMany({ where: { tenantId, type } });
}
}
// src/auth/actions.ts
export enum AppAction {
Read = 'read',
Create = 'create',
Update = 'update',
Delete = 'delete',
Manage = 'manage',
}
export type AppSubject = 'tenant' | 'project' | 'dataset' | 'record';
// src/auth/casl-ability.factory.ts
import { Ability, AbilityBuilder, AbilityClass, ExtractSubjectType } from '@casl/ability';
type AppAbility = Ability<[AppAction, AppSubject | 'all']>;
export class CaslAbilityFactory {
createForUser(ctx: {
userId: string;
tenantId: string;
roleBindings: Array<{ roleName: string; scopeResourceId?: string }>;
policies: Array<{
effect: 'ALLOW' | 'DENY';
action: AppAction;
resourceType: AppSubject;
conditions?: Record<string, any>;
scopeResourceId?: string;
priority: number;
}>;
}): AppAbility {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(Ability as AbilityClass<AppAbility>);
// 先应用 DENY 高优先级策略
const sorted = ctx.policies.sort((a, b) => a.priority - b.priority);
for (const p of sorted) {
const handler = p.effect === 'DENY' ? cannot : can;
handler(p.action, p.resourceType, p.conditions || {});
}
// 基于角色绑定的默认权限(示例)
for (const rb of ctx.roleBindings) {
if (rb.roleName === 'tenant_admin') {
can('manage', 'tenant');
can('manage', 'project');
can('manage', 'dataset');
}
if (rb.roleName === 'project_editor') {
can('update', 'project');
can('create', 'dataset');
}
}
return build({
detectSubjectType: (item) => item as ExtractSubjectType<AppSubject>,
});
}
}
// src/auth/policies.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { CaslAbilityFactory } from './casl-ability.factory';
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(private readonly abilityFactory: CaslAbilityFactory) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const { db } = req;
const userId = req.user.id;
const tenantId = req.headers['x-tenant-id'];
// 加载用户在租户下的角色绑定与策略(应做缓存)
const roleBindings = await db.roleBinding.findMany({ where: { tenantId, userId } });
const roleIds = roleBindings.map((rb) => rb.roleId);
const policies = await db.policy.findMany({
where: {
tenantId,
OR: [{ subjectType: 'USER', subjectId: userId }, { subjectType: 'ROLE', subjectId: { in: roleIds } }],
},
});
const ability = this.abilityFactory.createForUser({
userId,
tenantId,
roleBindings: roleBindings.map((rb) => ({ roleName: '', scopeResourceId: rb.scopeResourceId })), // 生产中需 join Role.name
policies: policies.map((p) => ({
action: p.action.toLowerCase() as any,
resourceType: p.resourceType.toLowerCase() as any,
effect: p.effect,
conditions: p.conditions as any,
scopeResourceId: p.scopeResourceId ?? undefined,
priority: p.priority,
})),
});
// 将 ability 注入请求上下文
req.ability = ability;
return true;
}
}
// src/common/check-policy.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const CheckPolicy = (action: string, subject: string) => SetMetadata('policy', { action, subject });
// src/common/policy-check.guard.ts
import { CanActivate, ExecutionContext, Injectable, Reflector } from '@nestjs/common';
@Injectable()
export class PolicyCheckGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(ctx: ExecutionContext) {
const req = ctx.switchToHttp().getRequest();
const ability = req.ability;
const meta = this.reflector.get<{ action: string; subject: string }>('policy', ctx.getHandler());
if (!meta) return true;
return ability.can(meta.action, meta.subject);
}
}
// src/roles/roles.controller.ts
import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { PoliciesGuard } from '../auth/policies.guard';
import { PolicyCheckGuard } from '../common/policy-check.guard';
import { CheckPolicy } from '../common/check-policy.decorator';
class CreateRoleDto {
name: string;
description?: string;
}
@ApiTags('roles')
@Controller('tenants/:tenantId/roles')
@UseGuards(PoliciesGuard, PolicyCheckGuard)
export class RolesController {
@Get()
@ApiOperation({ summary: '列表角色' })
@ApiResponse({ status: 200, description: '成功' })
@CheckPolicy('read', 'tenant')
async list() {
// 通过 req.db 查询
}
@Post()
@ApiOperation({ summary: '创建角色' })
@ApiResponse({ status: 201 })
@CheckPolicy('manage', 'tenant')
async create(@Body() dto: CreateRoleDto) {
// 创建角色并写审计
}
}
// test/auth/casl-ability.factory.spec.ts
import { CaslAbilityFactory } from '../../src/auth/casl-ability.factory';
describe('CaslAbilityFactory', () => {
it('deny first then allow', () => {
const factory = new CaslAbilityFactory();
const ability = factory.createForUser({
userId: 'u1',
tenantId: 't1',
roleBindings: [{ roleName: 'project_editor' }],
policies: [
{ effect: 'DENY', action: 'update', resourceType: 'project', priority: 1 },
{ effect: 'ALLOW', action: 'update', resourceType: 'project', priority: 100 },
] as any,
});
expect(ability.cannot('update', 'project')).toBe(true);
});
it('tenant admin can manage', () => {
const factory = new CaslAbilityFactory();
const ability = factory.createForUser({
userId: 'u1', tenantId: 't1',
roleBindings: [{ roleName: 'tenant_admin' }],
policies: [] as any,
});
expect(ability.can('manage', 'tenant')).toBe(true);
});
});
五、文生代码自动生成器(CLI 模板) 目标:从一个资源/动作配置生成 Prisma 模型片段、仓储、Guard、控制器、OpenAPI 与测试骨架。可作为 dev 脚本在 CI/CD 前运行。
resources:
- name: project
actions: [read, create, update, delete, manage]
- name: dataset
actions: [read, create, update, delete]
- name: record
actions: [read, update, delete]
roles:
- name: tenant_admin
grants:
- { action: manage, resource: tenant }
- { action: manage, resource: project }
- name: project_editor
grants:
- { action: update, resource: project }
- { action: create, resource: dataset }
// tools/rbacgen.ts
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
type Config = {
resources: { name: string; actions: string[] }[];
roles: { name: string; grants: { action: string; resource: string }[] }[];
};
const tplController = (resource: string) => `
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { PoliciesGuard } from '../auth/policies.guard';
import { PolicyCheckGuard } from '../common/policy-check.guard';
import { CheckPolicy } from '../common/check-policy.decorator';
@ApiTags('${resource}')
@UseGuards(PoliciesGuard, PolicyCheckGuard)
@Controller('tenants/:tenantId/${resource}')
export class ${capitalize(resource)}Controller {
@Get()
@CheckPolicy('read', '${resource}')
async list() {}
@Post()
@CheckPolicy('create', '${resource}')
async create(@Body() dto: any) {}
}
`;
function capitalize(s: string) { return s.charAt(0).toUpperCase() + s.slice(1); }
(async function main() {
const cfg = yaml.load(fs.readFileSync('rbac.config.yaml', 'utf-8')) as Config;
// 生成 actions.ts
const actions = Array.from(new Set(cfg.resources.flatMap(r => r.actions))).map(a => a.toUpperCase());
const actionsTs = `export enum AppAction { ${actions.map(a => `${capitalize(a)}='${a.toLowerCase()}'`).join(', ')} }`;
fs.writeFileSync(path.join('src/auth/actions.auto.ts'), actionsTs);
// 生成控制器
for (const r of cfg.resources) {
const code = tplController(r.name);
fs.writeFileSync(path.join('src', r.name, `${r.name}.controller.auto.ts`), code);
}
// 生成测试骨架
const testTpl = `describe('Policy ${new Date().toISOString()}', () => { it('should compile', () => expect(true).toBeTruthy()); });`;
fs.writeFileSync(path.join('test/policy.auto.spec.ts'), testTpl);
console.log('RBAC scaffold generated.');
})();
六、三种策略评估方案与取舍 评估维度:开发效率、系统可维护性、安全性、可扩展性、部署复杂度、代码可读性。
推荐结论与取舍
七、管理员委派与资源层级授权
八、性能与缓存建议
九、Docker Compose 与可选组件
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
ports: ['5432:5432']
volumes:
- pgdata:/var/lib/postgresql/data
pgbouncer:
image: edoburu/pgbouncer
environment:
DB_HOST: postgres
DB_USER: postgres
DB_PASSWORD: example
ports: ['6432:6432']
opa:
image: openpolicyagent/opa:latest
command: ["run", "--server", "--addr=0.0.0.0:8181"]
ports: ['8181:8181']
# 可选:仅当选择 OPA 方案
redis:
image: redis:7
ports: ['6379:6379']
# 可选:共享缓存
app:
build: .
environment:
DATABASE_URL: postgresql://postgres:example@pgbouncer:6432/postgres
REDIS_URL: redis://redis:6379
ports: ['3000:3000']
depends_on: [postgres, pgbouncer]
volumes:
pgdata:
十、CI/CD 与蓝绿/灰度发布
十一、ESLint 与编码规范
十二、测试覆盖建议
十三、方案对比结论(维度评分概述)
综合推荐:默认选用 NestJS Guards + CASL + RLS + 应用审计;在需要策略中心化治理与跨系统统一的租户采用 OPA 辅助;避免自研策略引擎,除非有明确的领域复杂度需求与长期维护投入。
以上方案与代码骨架可直接落地,并能在团队熟悉 TypeScript 的前提下,兼顾开发效率、维护性、安全与性能,满足合规审计与蓝绿/灰度发布要求。
为软件开发者在开发过程中遇到的常见问题提供专业的最佳实践指引,帮助他们在不同场景下选择最合适的解决方案,同时深入了解每种方案的优劣,从而提升开发效率和项目质量。
面临代码调试、性能优化等问题时,通过提示词获取适切的最佳解决方案,节约开发时间。
需要为团队决策技术框架或设计架构,借助提示词快速生成方案比较分析,提升团队效率。
为课程设计或技术培训寻找场景化案例,使用提示词生成清晰易懂的实践指导,助力学员成长。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
半价获取高级提示词-优惠即将到期