增强 shared-crud 包的数据权限控制能力,支持基于用户ID的数据访问权限控制,使业务模块能够轻松实现"用户只能操作自己的数据"的安全需求,同时保持向后兼容性和配置灵活性。
Current relevant functionality:
userTracking 配置支持创建和更新时自动设置用户ID字段Current limitations:
Technology stack:
Integration points:
What's being added/changed:
Enhanced architecture:
shared-crud/
├── src/
│ ├── services/
│ │ ├── generic-crud.service.ts (增强权限控制)
│ │ └── concrete-crud.service.ts
│ ├── routes/
│ │ └── generic-crud.routes.ts (增强权限中间件)
│ ├── types/
│ │ └── data-permission.types.ts (新增权限类型定义)
│ └── middleware/
│ └── data-permission.middleware.ts (新增权限中间件)
How it integrates:
CrudOptions 接口,添加数据权限配置Success criteria:
Primary Risk: 权限控制影响现有查询性能 Mitigation: 使用数据库索引优化权限过滤查询,提供性能基准测试 Rollback Plan: 权限控制为可选配置,可随时禁用
Primary Risk: 复杂的权限配置影响开发体验 Mitigation: 提供简化的默认配置和清晰的文档 Rollback Plan: 保持现有配置方式不变,新增配置可选
Primary Risk: 权限验证逻辑与业务逻辑耦合 Mitigation: 权限控制作为基础设施层,与业务逻辑分离 Rollback Plan: 权限验证可独立禁用
// 扩展现有的 CrudOptions
export interface DataPermissionOptions {
enabled: boolean;
userIdField: string; // 用户ID字段名,如 'userId', 'createdBy'
adminOverride?: {
enabled: boolean;
adminRole?: string; // 管理员角色标识
userIdField?: string; // 管理员用户ID字段
};
multiTenant?: {
enabled: boolean;
tenantIdField: string; // 租户ID字段名
};
customValidator?: (userId: string | number, entity: any) => Promise<boolean>;
}
// 扩展 CrudOptions
export type CrudOptions<T extends ObjectLiteral, ...> = {
// ... 现有配置
userTracking?: UserTrackingOptions;
dataPermission?: DataPermissionOptions; // 新增权限控制配置
// ...
};
// 在 getList 方法中添加权限过滤
async getList(
page: number = 1,
pageSize: number = 10,
keyword?: string,
searchFields?: string[],
where?: Partial<T>,
relations: string[] = [],
order: { [P in keyof T]?: 'ASC' | 'DESC' } = {},
filters?: { [key: string]: any },
userId?: string | number // 新增用户ID参数
): Promise<[T[], number]> {
// 构建基础查询
const query = this.repository.createQueryBuilder('entity');
// 添加权限过滤条件
if (this.dataPermissionOptions?.enabled && userId) {
const userIdField = this.dataPermissionOptions.userIdField;
query.andWhere(`entity.${userIdField} = :userId`, { userId });
}
// ... 现有查询逻辑
}
async create(data: DeepPartial<T>, userId?: string | number): Promise<T> {
// 权限验证:防止用户创建不属于自己的数据
if (this.dataPermissionOptions?.enabled && userId) {
const userIdField = this.dataPermissionOptions.userIdField;
// 如果数据中已经包含用户ID字段,验证是否与当前用户匹配
if (data[userIdField] && data[userIdField] !== userId) {
throw new Error('无权创建该资源');
}
}
const entityData = { ...data };
this.setUserFields(entityData, userId, true);
// ... 现有创建逻辑
}
async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null> {
// 权限验证
if (this.dataPermissionOptions?.enabled && userId) {
const entity = await this.getById(id);
if (!entity) return null;
const userIdField = this.dataPermissionOptions.userIdField;
const entityUserId = (entity as any)[userIdField];
if (entityUserId !== userId) {
throw new Error('无权更新该资源');
}
}
const updateData = { ...data };
this.setUserFields(updateData, userId, false);
// ... 现有更新逻辑
}
async delete(id: number, userId?: string | number): Promise<boolean> {
// 权限验证
if (this.dataPermissionOptions?.enabled && userId) {
const entity = await this.getById(id);
if (!entity) return false;
const userIdField = this.dataPermissionOptions.userIdField;
const entityUserId = (entity as any)[userIdField];
if (entityUserId !== userId) {
throw new Error('无权删除该资源');
}
}
// 执行删除
const result = await this.repository.delete(id);
return result.affected === 1;
}
export function dataPermissionMiddleware(
options: DataPermissionOptions
): MiddlewareHandler<AuthContext> {
return async (c, next) => {
if (!options.enabled) {
return next();
}
const user = c.get('user');
if (!user?.id) {
return c.json({ code: 401, message: '未授权访问' }, 401);
}
// 设置用户ID到上下文,供服务层使用
c.set('dataPermissionUserId', user.id);
await next();
};
}
// 现有配置方式(保持兼容)
createCrudRoutes({
entity: UserEntity,
createSchema: CreateUserSchema,
updateSchema: UpdateUserSchema,
getSchema: GetUserSchema,
listSchema: ListUserSchema,
userTracking: { userIdField: 'userId' },
// 新增权限控制配置
dataPermission: {
enabled: true,
userIdField: 'userId',
adminOverride: {
enabled: true,
adminRole: 'admin'
}
}
});
// 在权限验证中添加管理员检查
private async checkPermission(
entity: any,
userId: string | number
): Promise<boolean> {
const options = this.dataPermissionOptions;
if (!options?.enabled) return true;
// 管理员权限覆盖
if (options.adminOverride?.enabled) {
const user = await this.getCurrentUser(userId);
if (user?.role === options.adminOverride.adminRole) {
return true;
}
}
// 普通用户权限验证
const userIdField = options.userIdField;
const entityUserId = (entity as any)[userIdField];
return entityUserId === userId;
}
// 用户只能操作自己的数据
createCrudRoutes({
entity: UserProfileEntity,
// ... 其他配置
userTracking: { userIdField: 'userId' },
dataPermission: {
enabled: true,
userIdField: 'userId'
}
});
// 管理员可以查看所有用户数据
createCrudRoutes({
entity: UserProfileEntity,
// ... 其他配置
userTracking: { userIdField: 'userId' },
dataPermission: {
enabled: true,
userIdField: 'userId',
adminOverride: {
enabled: true,
adminRole: 'admin'
}
}
});
// 支持多租户数据隔离
createCrudRoutes({
entity: TenantDataEntity,
// ... 其他配置
dataPermission: {
enabled: true,
userIdField: 'createdBy',
multiTenant: {
enabled: true,
tenantIdField: 'tenantId'
}
}
});
// 自定义复杂的权限验证逻辑
createCrudRoutes({
entity: ComplexEntity,
// ... 其他配置
dataPermission: {
enabled: true,
userIdField: 'ownerId',
customValidator: async (userId, entity) => {
// 自定义权限验证逻辑
return await someComplexPermissionCheck(userId, entity);
}
}
});
"请为这个brownfield epic开发详细的用户故事。关键考虑因素:
该epic应该保持系统完整性,同时实现 shared-crud 包的数据权限控制增强,为业务模块提供安全、灵活的数据访问控制能力。"