# RFC 119: 项目素材定稿功能 > **状态**:已实施 > **创建时间**:2025-01-27 > **作者**:系统架构师 --- ## 背景 在项目素材管理中,用户需要能够将素材标记为"已定稿"状态,防止误操作修改或删除重要素材。定稿后的素材应该被锁定,不允许再次编辑。 ## 问题 当前 `project_resources` 表缺少状态管理机制: - 无法区分草稿素材和已定稿素材 - 无法防止已确认的素材被误修改或删除 - 缺少素材定稿的审计记录(时间、操作人) ## 解决方案 ### 1. 数据库设计 添加状态枚举和相关字段: ```sql -- 创建状态枚举 CREATE TYPE project_resource_status AS ENUM ('draft', 'finalized'); -- 添加字段 ALTER TABLE project_resources ADD COLUMN status project_resource_status NOT NULL DEFAULT 'draft', ADD COLUMN finalized_at TIMESTAMPTZ, ADD COLUMN finalized_by UUID REFERENCES users(user_id); -- 添加约束 ALTER TABLE project_resources ADD CONSTRAINT project_resources_finalized_check CHECK ( (status = 'finalized' AND finalized_at IS NOT NULL AND finalized_by IS NOT NULL) OR (status = 'draft' AND finalized_at IS NULL AND finalized_by IS NULL) ); -- 添加索引 CREATE INDEX idx_project_resources_status ON project_resources (status) WHERE deleted_at IS NULL; CREATE INDEX idx_project_resources_finalized_by ON project_resources (finalized_by) WHERE finalized_by IS NOT NULL; ``` ### 2. 状态定义 | 状态 | 说明 | 可编辑 | 可删除 | |------|------|--------|--------| | `draft` | 草稿状态,默认状态 | ✅ | ✅ | | `finalized` | 已定稿,锁定状态 | ❌ | ❌ | ### 3. 业务规则 1. **创建素材**:默认状态为 `draft` 2. **定稿操作**: - 只有 `draft` 状态的素材可以定稿 - 定稿时记录 `finalized_at` 和 `finalized_by` - 定稿后状态变为 `finalized` 3. **修改限制**: - `finalized` 状态的素材不允许修改 - 尝试修改时返回 `ValidationError` 4. **删除限制**: - `finalized` 状态的素材不允许删除 - 尝试删除时返回 `ValidationError` 5. **解除定稿**(可选): - 需要特殊权限(如项目管理员) - 清除 `finalized_at` 和 `finalized_by` - 状态恢复为 `draft` ### 4. API 接口 #### 定稿素材 ``` POST /api/v1/projects/{project_id}/resources/{resource_id}/finalize ``` **响应**: ```json { "message": "素材已定稿", "resource": { "id": "019d1234-5678-7abc-def0-123456789abc", "status": "finalized", "finalized_at": "2025-01-27T12:00:00Z", "finalized_by": "019d1234-5678-7abc-def0-user123456" } } ``` #### 解除定稿(可选) ``` POST /api/v1/projects/{project_id}/resources/{resource_id}/unfinalize ``` **权限要求**:项目管理员或特殊权限 ### 5. Service 层实现 ```python async def finalize_project_resource( self, user_id: str, project_resource_id: str ) -> ProjectResource: """定稿项目素材(锁定,不可修改)""" resource = await self.repository.get_by_id(project_resource_id) if not resource: raise NotFoundError("项目素材不存在") # 验证项目权限 await self._verify_project_access(user_id, resource.project_id) # 检查是否已定稿 if resource.status == ProjectResourceStatus.FINALIZED: raise ValidationError("素材已经定稿") # 更新状态 update_data = { 'status': ProjectResourceStatus.FINALIZED, 'finalized_at': datetime.utcnow(), 'finalized_by': user_id } return await self.repository.update(project_resource_id, update_data) async def update_project_resource( self, user_id: str, project_resource_id: str, resource_data: ProjectResourceUpdate ) -> ProjectResource: """更新项目素材""" resource = await self.repository.get_by_id(project_resource_id) if not resource: raise NotFoundError("项目素材不存在") # 验证项目权限 await self._verify_project_access(user_id, resource.project_id) # 检查是否已定稿 if resource.status == ProjectResourceStatus.FINALIZED: raise ValidationError("素材已定稿,无法修改") update_data = resource_data.dict(exclude_unset=True) return await self.repository.update(project_resource_id, update_data) async def delete_project_resource( self, user_id: str, project_resource_id: str ) -> None: """删除项目素材(软删除)""" resource = await self.repository.get_by_id(project_resource_id) if not resource: raise NotFoundError("项目素材不存在") # 验证项目权限 await self._verify_project_access(user_id, resource.project_id) # 检查是否已定稿 if resource.status == ProjectResourceStatus.FINALIZED: raise ValidationError("素材已定稿,无法删除") await self.repository.soft_delete(project_resource_id) ``` ## 优势 1. **防止误操作**:定稿后的素材被锁定,避免误修改或删除 2. **可追溯性**:记录定稿时间和操作人,便于审计 3. **可扩展性**:使用枚举类型,未来可以添加更多状态(如审核中、已驳回等) 4. **数据完整性**:通过 CHECK 约束确保状态一致性 5. **灵活性**:支持解除定稿功能(需要权限) ## 影响范围 ### 数据库 - 新增 `project_resource_status` 枚举类型 - `project_resources` 表新增 3 个字段 - 新增 2 个索引 - 新增 1 个 CHECK 约束 ### 后端 - `ProjectResource` 模型更新 - `ProjectResourceService` 新增 2 个方法 - API 路由新增 2 个接口 - Schema 更新 ### 前端 - 素材列表显示状态标识 - 素材详情页显示定稿信息 - 添加"定稿"按钮 - 已定稿素材禁用编辑/删除按钮 ## 迁移计划 ### 1. 数据库迁移 ```sql -- 1. 创建枚举类型 CREATE TYPE project_resource_status AS ENUM ('draft', 'finalized'); -- 2. 添加字段(默认值为 draft) ALTER TABLE project_resources ADD COLUMN status project_resource_status NOT NULL DEFAULT 'draft', ADD COLUMN finalized_at TIMESTAMPTZ, ADD COLUMN finalized_by UUID REFERENCES users(user_id); -- 3. 添加约束 ALTER TABLE project_resources ADD CONSTRAINT project_resources_finalized_check CHECK ( (status = 'finalized' AND finalized_at IS NOT NULL AND finalized_by IS NOT NULL) OR (status = 'draft' AND finalized_at IS NULL AND finalized_by IS NULL) ); -- 4. 添加索引 CREATE INDEX idx_project_resources_status ON project_resources (status) WHERE deleted_at IS NULL; CREATE INDEX idx_project_resources_finalized_by ON project_resources (finalized_by) WHERE finalized_by IS NOT NULL; ``` ### 2. 现有数据处理 所有现有素材默认为 `draft` 状态,无需额外处理。 ## 未来扩展 ### 1. 批量定稿 ```python async def batch_finalize_resources( self, user_id: str, project_id: str, resource_ids: List[str] ) -> List[ProjectResource]: """批量定稿素材""" pass ``` ### 2. 更多状态 未来可以扩展更多状态: - `pending_review`:待审核 - `rejected`:已驳回 - `archived`:已归档 ### 3. 权限细化 - 项目成员可以定稿自己创建的素材 - 项目管理员可以定稿所有素材 - 只有项目所有者可以解除定稿 ## 相关文档 - [项目素材管理服务](../requirements/backend/04-services/resource/project-resource-service.md) - [数据库设计文档](../requirements/database-design.md) --- **文档版本**:v1.0 **最后更新**:2025-01-27