策略权限:数据库设计
在完成 CaslAbilityService 和 PolicyGuard 的逻辑后,需要设计数据库表结构来存储策略权限数据。核心思路是:只增加一个 Policy 表 + 两张关联关系表,通过接口-权限和角色-权限的关联关系,同时满足"接口所需权限"和"用户已有权限"的查询需求。
设计原则
已有的 RBAC 表结构
User → UserRole → Role → RolePermission → Permission
扩展策略权限(新增部分)
Permission → PermissionPolicy → Policy ← RolePolicy ← Role
text
关键决策:不需要设计两套权限模型。Policy 使用统一的 IPolicy 接口,通过关联关系表区分:
- PermissionPolicy:路由级别需要的策略权限
- RolePolicy:角色拥有的策略权限
Policy 表设计
model Policy {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// 类型标识: 0=JSON, 1=MongoDB查询, 2=函数
type Int
/// 判断字段: 'can' 或 'cannot'
effect String
/// 操作标识: create, read, update, delete, manage
action String
/// 资源标识: 对应的类名,如 'User', 'Article'
subject String
/// 字段控制: JSON 格式 { type: 'array', data: ['title', 'description'] }
fields Json?
/// 条件: JSON 格式,结构因 type 而异
/// type=0: { type: 'object', data: { username: 'tom1' } }
/// type=1: { type: 'object', data: { $or: [...] } }
/// type=2: { type: 'string', data: 'authorId === user.id' }
conditions Json?
/// 函数参数(仅 type=2 使用): { type: 'array', data: ['user'] }
args Json?
// 关联关系
rolePolicies RolePolicy[]
permissionPolicies PermissionPolicy[]
}
prisma
fields / conditions / args 的 JSON 结构
// fields 示例
{ "type": "array", "data": ["title", "description"] }
// conditions 示例 - JSON 类型 (type=0)
{ "type": "object", "data": { "username": "tom1" } }
// conditions 示例 - MongoDB 类型 (type=1)
{ "type": "object", "data": { "$or": [{ "authorId": 1 }, { "private": true }] } }
// conditions 示例 - 函数类型 (type=2)
{ "type": "string", "data": "authorId === user.id" }
// args 示例 - 函数类型 (type=2)
{ "type": "array", "data": ["user"] }
typescript
关联关系表设计
RolePolicy -- 角色拥有的策略权限
model RolePolicy {
roleId Int @map("role_id")
policyId Int @map("policy_id")
role Role @relation(fields: [roleId], references: [id])
policy Policy @relation(fields: [policyId], references: [id])
@@id([roleId, policyId])
@@map("role_policies")
}
prisma
PermissionPolicy -- 路由需要的策略权限
model PermissionPolicy {
permissionId Int @map("permission_id")
policyId Int @map("policy_id")
permission Permission @relation(fields: [permissionId], references: [id])
policy Policy @relation(fields: [policyId], references: [id])
@@id([permissionId, policyId])
@@map("permission_policies")
}
prisma
Prisma Schema 关联更新
添加 Policy 和关联表后,Prisma 会自动更新已有模型的关联关系:
// Role 模型自动新增
model Role {
// ... 原有字段
policies RolePolicy[] // 自动新增
}
// Permission 模型自动新增
model Permission {
// ... 原有字段
policies PermissionPolicy[] // 自动新增
}
prisma
数据库同步命令
# 同步 schema 到数据库(开发环境)
npx prisma db push
# 生成 Prisma Client
npx prisma generate
bash
JSON 字段的数据库兼容性
| 数据库 | JSON 类型支持 | 备注 |
|---|---|---|
| MySQL >= 5.7 | JSON | 原生支持 |
| PostgreSQL >= 9.4 | JSON / JSONB | 推荐 JSONB |
| MariaDB >= 10.2 | JSON | 原生支持 |
| SQLite < 3.9 | 不支持 | 需用 String + JSON.parse |
| MongoDB | 原生 BSON | 最佳支持 |
如果使用较低版本的数据库,可将
Json?替换为String?,在应用层进行 JSON 序列化/反序列化。
Policy CRUD 基础服务
@Injectable()
export class PolicyService {
constructor(private prisma: PrismaService) {}
async findAll(page: number = 1, limit: number = 10) {
const [items, total] = await Promise.all([
this.prisma.policy.findMany({
skip: (page - 1) * limit,
take: limit,
}),
this.prisma.policy.count(),
]);
return { items, total };
}
async findOne(id: number) {
return this.prisma.policy.findUnique({ where: { id } });
}
async update(id: number, data: Prisma.PolicyUpdateInput) {
return this.prisma.policy.update({ where: { id }, data });
}
async remove(id: number) {
return this.prisma.policy.delete({ where: { id } });
}
}
typescript
完整的权限查询链路
请求到达 PolicyGuard
│
├─ 路径一: 接口所需权限
│ Permission (路由 metadata)
│ → PermissionPolicy
│ → Policy[] (requiredPolicies)
│
├─ 路径二: 用户已有权限
│ User → UserRole → Role
│ → RolePolicy
│ → Policy[] (userPolicies)
│
└─ CaslAbilityService.buildAbility(userPolicies)
→ Ability[] → 对比 requiredPolicies
→ 通过/拒绝
text
至此,策略权限的数据库设计完成。Policy 的 CRUD 操作作为作业,参照先前 Role 和 Permission 的实现完成即可。
↑