概述
本节补充说明 CASL v6 中自定义 Ability 规则的两种方式:函数式编程(defineAbility)和构建器模式(AbilityBuilder + createMongoAbility)。同时解读官方文档中关于 createMongoAbility、自定义条件匹配器(Custom Conditions Matcher)和类主体类型检测(Subject Type Detection)等进阶内容。
CASL v6 重要变更
CASL v6 中 Ability 类已被废弃(deprecated),官方推荐使用 createMongoAbility 替代:
// v5(已废弃)
import { Ability } from '@casl/ability';
type AppAbility = Ability; // ❌ 废弃
// v6(推荐)
import { createMongoAbility } from '@casl/ability';
type AppAbility = ReturnType<typeof createMongoAbility>; // ✅ 推荐
typescript
createMongoAbility 说明
createMongoAbility 并不要求你了解 MongoDB,它只是借用了 MongoDB 的查询操作符来描述条件:
| 操作符 | 含义 | 示例 |
|---|---|---|
$eq | 等于 | { status: { $eq: 'published' } } |
$ne | 不等于 | { status: { $ne: 'draft' } } |
$gt | 大于 | { views: { $gt: 100 } } |
$in | 包含 | { role: { $in: ['admin', 'editor'] } } |
$lt | 小于 | { createdAt: { $lt: Date.now() } } |
本质上这些操作符只是描述条件的语法糖,用于匹配实体类的属性值。
两种自定义规则方式
方式一:函数式 defineAbility
import { defineAbility } from '@casl/ability';
export default defineAbility((can, cannot) => {
can('manage', 'all');
cannot('delete', 'User');
}, {
detectSubjectType: (object) => object.constructor.name,
});
typescript
特点:
| 维度 | 评价 |
|---|---|
| 代码简洁度 | 高,一行定义 |
| 适用场景 | 简单权限规则 |
| 可读性 | 一般,所有逻辑写在一个函数中 |
| 扩展性 | 低,逻辑复杂时代码块过长 |
方式二:构建器模式 AbilityBuilder
import { createMongoAbility, AbilityBuilder } from '@casl/ability';
export function defineAbilityFor(user: any) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
if (user.isAdmin) {
can('manage', 'all');
} else {
can('read', 'Article');
cannot('read', 'Article', { private: true });
}
return build();
}
typescript
特点:
| 维度 | 评价 |
|---|---|
| 代码简洁度 | 中等 |
| 适用场景 | 复杂权限规则,条件逻辑 |
| 可读性 | 高,逻辑分块清晰 |
| 扩展性 | 高,可组合不同角色的权限 |
NestJS 推荐方式二:因为 NestJS 采用 OOP(面向对象编程)模式,AbilityBuilder 更贴合其架构风格。
两种方式对比
// 方式一:函数式 — 逻辑写在一行中
defineAbility((can, cannot) => {
can('manage', 'all');
cannot('delete', 'User');
// 后续更多规则都要写在这个函数内
});
// 方式二:构建器 — can 和 build 分离,中间可插入复杂逻辑
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
// 可以在这里写大量条件判断
if (condition1) { ... }
if (condition2) { ... }
return build();
typescript
进阶:自定义条件匹配器
CASL 允许自定义条件匹配逻辑,通过 conditionsMatcher 选项实现。
PureAbility + Lambda Matcher
import {
PureAbility,
AbilityBuilder,
AbilityTuple,
MatchConditions,
} from '@casl/ability';
type AppAbility = PureAbility<AbilityTuple, MatchConditions>;
// 自定义匹配器:直接使用函数作为条件
const lambdaMatcher = (matchConditions: MatchConditions) => matchConditions;
export function defineAbilityFor(user: any): AppAbility {
const { can, build } = new AbilityBuilder<AppAbility>(PureAbility);
// 条件直接是一个函数
can('read', 'Article', ({ authorId }) => authorId === user.id);
can('read', 'Article', ({ status }) => ['draft', 'published'].includes(status));
return build({ conditionsMatcher: lambdaMatcher });
}
typescript
局限性:条件匹配函数只支持同步操作,不支持异步。高并发场景下可能有性能限制。
mongoQueryMatcher(默认方式)
import { mongoQueryMatcher } from '@casl/ability';
const matchConditions = mongoQueryMatcher({ authorId: 1, private: true });
matchConditions({ authorId: 2 }); // false
matchConditions({ authorId: 1, private: true }); // true
matchConditions({ authorId: 1, private: false }); // false
typescript
进阶:类主体类型检测(Subject Type Detection)
CASL 需要知道传入对象的类型,detectSubjectType 选项控制类型检测方式。
使用 class 构造函数名
import { AbilityBuilder, createMongoAbility } from '@casl/ability';
const ability = build({
detectSubjectType: (object) => object.constructor.name,
});
// 当传入类实例时,CASL 自动识别类型
class Article { title: string; }
const article = new Article();
ability.can('read', article); // 自动识别为 'Article'
typescript
使用 GraphQL __typename
const ability = build({
detectSubjectType: (object) => object.__typename,
});
const article = { __typename: 'Article', title: 'Hello' };
ability.can('read', article); // 通过 __typename 识别为 'Article'
typescript
自定义字段匹配器
CASL 还支持自定义字段匹配逻辑,但官方文档明确表示绝大多数场景不需要修改:
import { fieldPatternMatcher } from '@casl/ability';
// 默认的字段匹配器已支持通配符
const matchField = fieldPatternMatcher(['name', 'email', 'address.**']);
matchField('name'); // true
matchField('email'); // true
matchField('address.street'); // true(** 匹配子属性)
matchField('phone'); // false
typescript
官方原话:"We cannot imagine a reason you'd need to modify field matching logic." 默认实现已足够使用。
关键知识点总结
| 知识点 | 说明 |
|---|---|
Ability 废弃 | v6 中使用 createMongoAbility 替代 |
方式一:defineAbility | 函数式,适合简单场景 |
方式二:AbilityBuilder | 构建器模式,适合 NestJS OOP 风格 |
conditionsMatcher | 自定义条件匹配逻辑 |
detectSubjectType | 控制如何识别传入对象的类型 |
fieldPatternMatcher | 字段匹配,默认实现已满足绝大多数需求 |
↑