You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.4 KiB
7.4 KiB
RFC 119: 项目素材定稿功能
状态:已实施
创建时间:2025-01-27
作者:系统架构师
背景
在项目素材管理中,用户需要能够将素材标记为"已定稿"状态,防止误操作修改或删除重要素材。定稿后的素材应该被锁定,不允许再次编辑。
问题
当前 project_resources 表缺少状态管理机制:
- 无法区分草稿素材和已定稿素材
- 无法防止已确认的素材被误修改或删除
- 缺少素材定稿的审计记录(时间、操作人)
解决方案
1. 数据库设计
添加状态枚举和相关字段:
-- 创建状态枚举
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. 业务规则
- 创建素材:默认状态为
draft - 定稿操作:
- 只有
draft状态的素材可以定稿 - 定稿时记录
finalized_at和finalized_by - 定稿后状态变为
finalized
- 只有
- 修改限制:
finalized状态的素材不允许修改- 尝试修改时返回
ValidationError
- 删除限制:
finalized状态的素材不允许删除- 尝试删除时返回
ValidationError
- 解除定稿(可选):
- 需要特殊权限(如项目管理员)
- 清除
finalized_at和finalized_by - 状态恢复为
draft
4. API 接口
定稿素材
POST /api/v1/projects/{project_id}/resources/{resource_id}/finalize
响应:
{
"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 层实现
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)
优势
- 防止误操作:定稿后的素材被锁定,避免误修改或删除
- 可追溯性:记录定稿时间和操作人,便于审计
- 可扩展性:使用枚举类型,未来可以添加更多状态(如审核中、已驳回等)
- 数据完整性:通过 CHECK 约束确保状态一致性
- 灵活性:支持解除定稿功能(需要权限)
影响范围
数据库
- 新增
project_resource_status枚举类型 project_resources表新增 3 个字段- 新增 2 个索引
- 新增 1 个 CHECK 约束
后端
ProjectResource模型更新ProjectResourceService新增 2 个方法- API 路由新增 2 个接口
- Schema 更新
前端
- 素材列表显示状态标识
- 素材详情页显示定稿信息
- 添加"定稿"按钮
- 已定稿素材禁用编辑/删除按钮
迁移计划
1. 数据库迁移
-- 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. 批量定稿
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. 权限细化
- 项目成员可以定稿自己创建的素材
- 项目管理员可以定稿所有素材
- 只有项目所有者可以解除定稿
相关文档
文档版本:v1.0
最后更新:2025-01-27