|
|
@@ -0,0 +1,452 @@
|
|
|
+# Epic-006: Shared-CRUD 数据权限控制增强 - Brownfield Enhancement
|
|
|
+
|
|
|
+## Epic Goal
|
|
|
+
|
|
|
+增强 shared-crud 包的数据权限控制能力,支持基于用户ID的数据访问权限控制,使业务模块能够轻松实现"用户只能操作自己的数据"的安全需求,同时保持向后兼容性和配置灵活性。
|
|
|
+
|
|
|
+## Epic Description
|
|
|
+
|
|
|
+### Existing System Context
|
|
|
+
|
|
|
+**Current relevant functionality:**
|
|
|
+- shared-crud 包已提供基础的 CRUD 操作和用户跟踪字段自动填充
|
|
|
+- 现有 `userTracking` 配置支持创建和更新时自动设置用户ID字段
|
|
|
+- 认证系统通过 JWT token 在上下文中提供用户信息
|
|
|
+- 业务模块使用 shared-crud 作为数据访问层基础设施
|
|
|
+
|
|
|
+**Current limitations:**
|
|
|
+- 查询操作没有基于用户ID的数据过滤
|
|
|
+- 删除操作没有权限验证
|
|
|
+- 创建操作没有权限限制(用户可以创建不属于自己的数据)
|
|
|
+- 更新操作没有权限验证(用户可以更新不属于自己的数据)
|
|
|
+- 缺乏统一的数据权限控制配置
|
|
|
+- 业务模块需要手动实现数据权限逻辑
|
|
|
+
|
|
|
+**Technology stack:**
|
|
|
+- Backend: Node.js, TypeScript, Hono, TypeORM, PostgreSQL
|
|
|
+- Authentication: JWT, Redis session management
|
|
|
+- Database: PostgreSQL with user tracking fields
|
|
|
+- Package: shared-crud, shared-types, shared-utils
|
|
|
+
|
|
|
+**Integration points:**
|
|
|
+- 现有认证中间件提供用户上下文
|
|
|
+- 业务模块实体中的用户关联字段(userId, createdBy, updatedBy)
|
|
|
+- 现有的 CRUD 路由和服务层
|
|
|
+- 现有的用户跟踪字段自动填充机制
|
|
|
+
|
|
|
+### Enhancement Details
|
|
|
+
|
|
|
+**What's being added/changed:**
|
|
|
+- 数据权限控制配置选项,支持基于用户ID的数据过滤
|
|
|
+- 查询操作的自动权限过滤
|
|
|
+- 创建操作的权限限制(防止创建不属于自己的数据)
|
|
|
+- 更新操作的权限验证
|
|
|
+- 删除操作的权限验证
|
|
|
+- 管理员权限覆盖机制
|
|
|
+- 向后兼容的权限控制配置
|
|
|
+
|
|
|
+**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` 接口,添加数据权限配置
|
|
|
+- 复用现有的用户跟踪字段配置
|
|
|
+- 保持现有 API 不变,新增配置为可选
|
|
|
+- 与现有认证系统无缝集成
|
|
|
+- 提供灵活的权限控制粒度
|
|
|
+
|
|
|
+**Success criteria:**
|
|
|
+- 现有功能不受影响,保持向后兼容
|
|
|
+- 业务模块可以轻松启用数据权限控制
|
|
|
+- 查询、创建、更新、删除操作自动进行权限验证
|
|
|
+- 创建操作防止用户创建不属于自己的数据
|
|
|
+- 提供管理员权限覆盖机制
|
|
|
+- 完整的单元测试和集成测试覆盖
|
|
|
+- 清晰的配置文档和使用示例
|
|
|
+
|
|
|
+## Stories
|
|
|
+
|
|
|
+### 阶段 1: 核心权限控制功能
|
|
|
+1. **Story 1:** 数据权限控制类型定义和配置接口 - 定义权限控制配置类型,扩展 CrudOptions 接口
|
|
|
+2. **Story 2:** 查询操作权限过滤增强 - 在 getList 和 getById 方法中添加用户ID过滤
|
|
|
+3. **Story 3:** 创建操作权限限制增强 - 在 create 方法中验证和限制用户创建权限
|
|
|
+4. **Story 4:** 更新操作权限验证增强 - 在 update 方法中添加权限验证
|
|
|
+5. **Story 5:** 删除操作权限验证增强 - 在 delete 方法中添加权限验证
|
|
|
+6. **Story 6:** 权限控制中间件 - 创建数据权限验证中间件
|
|
|
+
|
|
|
+### 阶段 2: 高级权限功能
|
|
|
+7. **Story 7:** 管理员权限覆盖机制 - 支持管理员查看和操作所有数据
|
|
|
+8. **Story 8:** 多租户数据隔离支持 - 支持基于租户ID的数据隔离
|
|
|
+9. **Story 9:** 自定义权限验证钩子 - 提供自定义权限验证扩展点
|
|
|
+
|
|
|
+### 阶段 3: 集成和测试
|
|
|
+10. **Story 10:** 业务模块集成示例 - 提供用户模块的权限控制集成示例
|
|
|
+11. **Story 11:** 完整测试覆盖 - 单元测试、集成测试和性能测试
|
|
|
+12. **Story 12:** 文档和迁移指南 - 使用文档和现有项目迁移指南
|
|
|
+
|
|
|
+## Compatibility Requirements
|
|
|
+
|
|
|
+- [x] 现有 APIs 保持不变,权限控制为可选配置
|
|
|
+- [x] 现有业务模块无需修改即可继续使用
|
|
|
+- [x] 数据库 schema 保持不变
|
|
|
+- [x] 性能影响最小化,权限过滤使用数据库索引
|
|
|
+- [x] 配置向后兼容,现有 userTracking 配置继续有效
|
|
|
+
|
|
|
+## Risk Mitigation
|
|
|
+
|
|
|
+**Primary Risk:** 权限控制影响现有查询性能
|
|
|
+**Mitigation:** 使用数据库索引优化权限过滤查询,提供性能基准测试
|
|
|
+**Rollback Plan:** 权限控制为可选配置,可随时禁用
|
|
|
+
|
|
|
+**Primary Risk:** 复杂的权限配置影响开发体验
|
|
|
+**Mitigation:** 提供简化的默认配置和清晰的文档
|
|
|
+**Rollback Plan:** 保持现有配置方式不变,新增配置可选
|
|
|
+
|
|
|
+**Primary Risk:** 权限验证逻辑与业务逻辑耦合
|
|
|
+**Mitigation:** 权限控制作为基础设施层,与业务逻辑分离
|
|
|
+**Rollback Plan:** 权限验证可独立禁用
|
|
|
+
|
|
|
+## Definition of Done
|
|
|
+
|
|
|
+- [ ] 阶段 1 stories 完成且验收标准满足
|
|
|
+- [ ] 现有功能通过回归测试验证
|
|
|
+- [ ] 权限控制配置灵活且易于使用
|
|
|
+- [ ] 性能基准测试通过,无明显性能下降
|
|
|
+- [ ] 完整的单元测试和集成测试覆盖
|
|
|
+- [ ] 使用文档和示例代码完整
|
|
|
+- [ ] 向后兼容性验证通过
|
|
|
+
|
|
|
+## 架构设计详情
|
|
|
+
|
|
|
+### 权限控制配置设计
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 扩展现有的 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; // 新增权限控制配置
|
|
|
+ // ...
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### 权限控制实现设计
|
|
|
+
|
|
|
+#### 查询权限过滤
|
|
|
+```typescript
|
|
|
+// 在 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 });
|
|
|
+ }
|
|
|
+
|
|
|
+ // ... 现有查询逻辑
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 创建权限限制
|
|
|
+```typescript
|
|
|
+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);
|
|
|
+
|
|
|
+ // ... 现有创建逻辑
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 更新权限验证
|
|
|
+```typescript
|
|
|
+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);
|
|
|
+
|
|
|
+ // ... 现有更新逻辑
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 删除权限验证
|
|
|
+```typescript
|
|
|
+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;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 路由层集成设计
|
|
|
+
|
|
|
+#### 权限控制中间件
|
|
|
+```typescript
|
|
|
+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();
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 路由配置示例
|
|
|
+```typescript
|
|
|
+// 现有配置方式(保持兼容)
|
|
|
+createCrudRoutes({
|
|
|
+ entity: UserEntity,
|
|
|
+ createSchema: CreateUserSchema,
|
|
|
+ updateSchema: UpdateUserSchema,
|
|
|
+ getSchema: GetUserSchema,
|
|
|
+ listSchema: ListUserSchema,
|
|
|
+ userTracking: { userIdField: 'userId' },
|
|
|
+ // 新增权限控制配置
|
|
|
+ dataPermission: {
|
|
|
+ enabled: true,
|
|
|
+ userIdField: 'userId',
|
|
|
+ adminOverride: {
|
|
|
+ enabled: true,
|
|
|
+ adminRole: 'admin'
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 管理员权限覆盖机制
|
|
|
+
|
|
|
+```typescript
|
|
|
+// 在权限验证中添加管理员检查
|
|
|
+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;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 使用示例
|
|
|
+
|
|
|
+### 基础权限控制配置
|
|
|
+```typescript
|
|
|
+// 用户只能操作自己的数据
|
|
|
+createCrudRoutes({
|
|
|
+ entity: UserProfileEntity,
|
|
|
+ // ... 其他配置
|
|
|
+ userTracking: { userIdField: 'userId' },
|
|
|
+ dataPermission: {
|
|
|
+ enabled: true,
|
|
|
+ userIdField: 'userId'
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 管理员权限覆盖配置
|
|
|
+```typescript
|
|
|
+// 管理员可以查看所有用户数据
|
|
|
+createCrudRoutes({
|
|
|
+ entity: UserProfileEntity,
|
|
|
+ // ... 其他配置
|
|
|
+ userTracking: { userIdField: 'userId' },
|
|
|
+ dataPermission: {
|
|
|
+ enabled: true,
|
|
|
+ userIdField: 'userId',
|
|
|
+ adminOverride: {
|
|
|
+ enabled: true,
|
|
|
+ adminRole: 'admin'
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 多租户数据隔离
|
|
|
+```typescript
|
|
|
+// 支持多租户数据隔离
|
|
|
+createCrudRoutes({
|
|
|
+ entity: TenantDataEntity,
|
|
|
+ // ... 其他配置
|
|
|
+ dataPermission: {
|
|
|
+ enabled: true,
|
|
|
+ userIdField: 'createdBy',
|
|
|
+ multiTenant: {
|
|
|
+ enabled: true,
|
|
|
+ tenantIdField: 'tenantId'
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+### 自定义权限验证
|
|
|
+```typescript
|
|
|
+// 自定义复杂的权限验证逻辑
|
|
|
+createCrudRoutes({
|
|
|
+ entity: ComplexEntity,
|
|
|
+ // ... 其他配置
|
|
|
+ dataPermission: {
|
|
|
+ enabled: true,
|
|
|
+ userIdField: 'ownerId',
|
|
|
+ customValidator: async (userId, entity) => {
|
|
|
+ // 自定义权限验证逻辑
|
|
|
+ return await someComplexPermissionCheck(userId, entity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+## 技术实现要点
|
|
|
+
|
|
|
+### 向后兼容性保证
|
|
|
+1. **配置可选**: 数据权限控制为可选配置,默认禁用
|
|
|
+2. **API 不变**: 现有 API 接口和行为保持不变
|
|
|
+3. **性能优化**: 权限过滤使用数据库索引,性能影响最小化
|
|
|
+4. **错误处理**: 权限验证失败返回明确的错误信息
|
|
|
+
|
|
|
+### 安全性考虑
|
|
|
+1. **默认安全**: 启用权限控制后,默认遵循最小权限原则
|
|
|
+2. **输入验证**: 所有用户输入进行严格的验证和转义
|
|
|
+3. **SQL 注入防护**: 使用参数化查询防止 SQL 注入
|
|
|
+4. **错误信息**: 权限错误信息不泄露敏感数据
|
|
|
+
|
|
|
+### 性能优化
|
|
|
+1. **数据库索引**: 确保用户ID字段有合适的索引
|
|
|
+2. **查询优化**: 权限过滤条件与现有查询条件合并
|
|
|
+3. **缓存策略**: 考虑权限验证结果的缓存
|
|
|
+4. **批量操作**: 支持批量操作的权限验证优化
|
|
|
+
|
|
|
+## 测试策略
|
|
|
+
|
|
|
+### 单元测试
|
|
|
+- 权限配置验证测试
|
|
|
+- 查询权限过滤测试
|
|
|
+- 创建权限限制测试
|
|
|
+- 更新权限验证测试
|
|
|
+- 删除权限验证测试
|
|
|
+- 管理员权限覆盖测试
|
|
|
+- 自定义权限验证测试
|
|
|
+
|
|
|
+### 集成测试
|
|
|
+- 完整 CRUD 操作的权限控制测试(创建、查询、更新、删除)
|
|
|
+- 多用户场景的权限隔离测试
|
|
|
+- 创建操作权限限制场景测试
|
|
|
+- 更新操作权限验证场景测试
|
|
|
+- 管理员权限覆盖场景测试
|
|
|
+- 性能基准测试
|
|
|
+
|
|
|
+### 回归测试
|
|
|
+- 现有功能回归测试
|
|
|
+- 向后兼容性验证测试
|
|
|
+- 配置迁移测试
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Story Manager Handoff
|
|
|
+
|
|
|
+"请为这个brownfield epic开发详细的用户故事。关键考虑因素:
|
|
|
+
|
|
|
+- 这是一个对现有 shared-crud 包的增强,运行在 Node.js + TypeScript + Hono + TypeORM + PostgreSQL 技术栈上
|
|
|
+- 集成点:现有认证系统、用户跟踪字段、CRUD 服务层、路由配置
|
|
|
+- 需要遵循的现有模式:TypeORM 查询构建、Hono 中间件、配置驱动开发
|
|
|
+- 关键兼容性要求:现有配置和 API 保持不变、性能影响最小化、错误处理一致
|
|
|
+- 每个故事必须包含验证现有功能保持完整的回归测试
|
|
|
+
|
|
|
+该epic应该保持系统完整性,同时实现 shared-crud 包的数据权限控制增强,为业务模块提供安全、灵活的数据访问控制能力。"
|