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

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. 业务规则

  1. 创建素材:默认状态为 draft
  2. 定稿操作
    • 只有 draft 状态的素材可以定稿
    • 定稿时记录 finalized_atfinalized_by
    • 定稿后状态变为 finalized
  3. 修改限制
    • finalized 状态的素材不允许修改
    • 尝试修改时返回 ValidationError
  4. 删除限制
    • finalized 状态的素材不允许删除
    • 尝试删除时返回 ValidationError
  5. 解除定稿(可选):
    • 需要特殊权限(如项目管理员)
    • 清除 finalized_atfinalized_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)

优势

  1. 防止误操作:定稿后的素材被锁定,避免误修改或删除
  2. 可追溯性:记录定稿时间和操作人,便于审计
  3. 可扩展性:使用枚举类型,未来可以添加更多状态(如审核中、已驳回等)
  4. 数据完整性:通过 CHECK 约束确保状态一致性
  5. 灵活性:支持解除定稿功能(需要权限)

影响范围

数据库

  • 新增 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