docs: 补充合同模板与文档质量方案文档
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
## 目标
|
||||
|
||||
将 `contract-template` 相关页面从旧的 PostgREST 直连方式切换到新的 FastAPI 业务接口,范围仅覆盖:
|
||||
|
||||
- 模板分类
|
||||
- 模板列表
|
||||
- 模板搜索
|
||||
- 模板详情
|
||||
|
||||
本清单不包含“起草合同”能力,后续会作为独立模块重新开发。
|
||||
|
||||
|
||||
## 当前前端依赖点
|
||||
|
||||
当前前端核心依赖集中在:
|
||||
|
||||
- `legal-platform-frontend/lib/api/legacy/contract-template/templates.ts`
|
||||
|
||||
这个文件现在直接调用:
|
||||
|
||||
- `/api/postgrest/proxy/contract_categories`
|
||||
- `/api/postgrest/proxy/contract_templates`
|
||||
|
||||
受影响页面:
|
||||
|
||||
- `app/(audit)/contract-template/search/page.tsx`
|
||||
- `app/(audit)/contract-template/search/results/page.tsx`
|
||||
- `app/(audit)/contract-template/list/page.tsx`
|
||||
- `app/(audit)/contract-template/detail/[id]/page.tsx`
|
||||
|
||||
|
||||
## 推荐新接口
|
||||
|
||||
建议前端最终切换为:
|
||||
|
||||
1. `GET /api/v3/contract-templates/categories`
|
||||
2. `GET /api/v3/contract-templates`
|
||||
3. `GET /api/v3/contract-templates/search`
|
||||
4. `GET /api/v3/contract-templates/{id}`
|
||||
|
||||
|
||||
## 改造步骤
|
||||
|
||||
### 第 1 步:新增新接口客户端文件
|
||||
|
||||
建议新增:
|
||||
|
||||
- `legal-platform-frontend/lib/api/contract-template/index.ts`
|
||||
|
||||
职责:
|
||||
|
||||
- 仅封装新的业务后端接口
|
||||
- 不再依赖 `postgrest-client.ts`
|
||||
- 返回字段尽量与页面现有消费结构兼容
|
||||
|
||||
建议方法:
|
||||
|
||||
- `getContractTemplateCategories`
|
||||
- `getContractTemplateList`
|
||||
- `searchContractTemplateList`
|
||||
- `getContractTemplateDetail`
|
||||
|
||||
|
||||
### 第 2 步:定义前端接口类型
|
||||
|
||||
建议在新客户端文件中定义或复用以下类型:
|
||||
|
||||
- `ContractTemplateCategory`
|
||||
- `ContractTemplateListItem`
|
||||
- `ContractTemplatePage`
|
||||
- `ContractTemplateDetail`
|
||||
- `ContractTemplateSearchResult`
|
||||
|
||||
字段建议优先与新后端对齐,然后在页面边界做一次轻量转换,避免业务层长期保留 snake_case 和 camelCase 混用。
|
||||
|
||||
|
||||
### 第 3 步:改造搜索首页
|
||||
|
||||
文件:
|
||||
|
||||
- `app/(audit)/contract-template/search/page.tsx`
|
||||
|
||||
当前行为:
|
||||
|
||||
- 调 `getContractCategoriesWithCount`
|
||||
|
||||
改造后:
|
||||
|
||||
- 改为调用 `getContractTemplateCategories`
|
||||
- 直接消费后端返回的 `templateCount`
|
||||
|
||||
页面影响:
|
||||
|
||||
- `transformCategory` 中的 `template_count` 改为新接口字段
|
||||
|
||||
|
||||
### 第 4 步:改造列表页
|
||||
|
||||
文件:
|
||||
|
||||
- `app/(audit)/contract-template/list/page.tsx`
|
||||
|
||||
当前行为:
|
||||
|
||||
- 调 `getContractTemplates`
|
||||
- 同时调 `getContractCategoriesWithCount`
|
||||
|
||||
改造后:
|
||||
|
||||
- 列表数据改为 `getContractTemplateList`
|
||||
- 分类数据改为 `getContractTemplateCategories`
|
||||
|
||||
注意事项:
|
||||
|
||||
- 当前页面把 `sortBy=relevance` 映射成 `id.asc`,这是旧 PostgREST 兼容逻辑
|
||||
- 切换新接口后应明确排序语义:
|
||||
- `relevance` 仅搜索场景有效
|
||||
- 列表页默认建议改为 `updated_at desc`
|
||||
|
||||
|
||||
### 第 5 步:改造搜索结果页
|
||||
|
||||
文件:
|
||||
|
||||
- `app/(audit)/contract-template/search/results/page.tsx`
|
||||
|
||||
当前行为:
|
||||
|
||||
- 调 `searchContractTemplates`
|
||||
- 额外循环所有分类,再逐类搜索统计命中数
|
||||
|
||||
改造后:
|
||||
|
||||
- 改为一次调用 `searchContractTemplateList`
|
||||
- 直接使用后端返回的 `categoryStats`
|
||||
|
||||
收益:
|
||||
|
||||
- 避免当前每次搜索都发起多次分类二次查询
|
||||
- 页面搜索耗时会明显降低
|
||||
|
||||
|
||||
### 第 6 步:改造详情页
|
||||
|
||||
文件:
|
||||
|
||||
- `app/(audit)/contract-template/detail/[id]/page.tsx`
|
||||
|
||||
当前行为:
|
||||
|
||||
- 调 `getContractTemplate`
|
||||
|
||||
改造后:
|
||||
|
||||
- 改为调用 `getContractTemplateDetail`
|
||||
|
||||
注意事项:
|
||||
|
||||
- 页面中依赖:
|
||||
- `template_code`
|
||||
- `file_path`
|
||||
- `pdf_file_path`
|
||||
- `placeholder_schema`
|
||||
- `category.name`
|
||||
- `category.description`
|
||||
- 新接口最好直接扁平返回,前端减少再拼装
|
||||
|
||||
|
||||
### 第 7 步:保留旧文件作为过渡,避免一次性大改
|
||||
|
||||
建议不要立刻删除:
|
||||
|
||||
- `lib/api/legacy/contract-template/templates.ts`
|
||||
|
||||
更稳妥的做法:
|
||||
|
||||
1. 新建新接口客户端文件
|
||||
2. 页面逐个切换
|
||||
3. 确认没有页面再引用旧文件
|
||||
4. 再删除旧实现
|
||||
|
||||
|
||||
## 字段映射建议
|
||||
|
||||
建议后端返回使用 camelCase 还是 snake_case,前后端尽量统一一次定死。
|
||||
|
||||
如果后端沿用当前 Python VO 风格的 snake_case,那么前端建议统一在 API client 中做转换:
|
||||
|
||||
- `template_code -> templateCode`
|
||||
- `category_id -> categoryId`
|
||||
- `file_path -> filePath`
|
||||
- `pdf_file_path -> pdfFilePath`
|
||||
- `placeholder_schema -> placeholderSchema`
|
||||
- `updated_at -> updatedAt`
|
||||
|
||||
不要把这类转换分散在页面组件里。
|
||||
|
||||
|
||||
## 具体函数替换建议
|
||||
|
||||
当前旧函数:
|
||||
|
||||
- `getContractCategories`
|
||||
- `getContractCategoriesWithCount`
|
||||
- `getContractTemplates`
|
||||
- `getContractTemplate`
|
||||
- `searchContractTemplates`
|
||||
|
||||
建议替换为新函数:
|
||||
|
||||
- `getContractTemplateCategories`
|
||||
- `getContractTemplateList`
|
||||
- `getContractTemplateDetail`
|
||||
- `searchContractTemplateList`
|
||||
|
||||
|
||||
## 风险点
|
||||
|
||||
1. 排序语义变化
|
||||
- 旧逻辑混入了 PostgREST 风格排序拼接
|
||||
- 新接口需要明确合法排序字段白名单
|
||||
|
||||
2. 搜索分类统计变化
|
||||
- 旧页面自己循环查询分类统计
|
||||
- 新接口如果不返回 `categoryStats`,页面逻辑还要保留一部分旧实现
|
||||
|
||||
3. 详情页字段结构变化
|
||||
- 旧页面默认拿 `template.category?.name`
|
||||
- 新接口若改成扁平字段,需要同步调整页面 transform
|
||||
|
||||
4. token 传递方式变化
|
||||
- 旧实现依赖 `postgrest-client.ts`
|
||||
- 新接口应统一走 `axios-client.ts` 或新业务客户端
|
||||
|
||||
|
||||
## 建议落地顺序
|
||||
|
||||
1. 新增 `lib/api/contract-template/index.ts`
|
||||
2. 先切 `search/page.tsx`
|
||||
3. 再切 `list/page.tsx`
|
||||
4. 再切 `search/results/page.tsx`
|
||||
5. 最后切 `detail/[id]/page.tsx`
|
||||
6. 全部验证通过后删除旧 `legacy/contract-template/templates.ts`
|
||||
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. `contract-template/search` 能正常展示分类和数量
|
||||
2. `contract-template/list` 能分页、筛选、排序
|
||||
3. `contract-template/search/results` 能搜索并展示分类统计
|
||||
4. `contract-template/detail/[id]` 能正常查看详情与预览
|
||||
5. 页面不再直接请求 `/api/postgrest/proxy/contract_categories`
|
||||
6. 页面不再直接请求 `/api/postgrest/proxy/contract_templates`
|
||||
@@ -0,0 +1,519 @@
|
||||
## 背景
|
||||
|
||||
当前 `contract-template` 模块前端页面已经迁移到 Next.js,但核心数据仍依赖旧的 PostgREST 直查表能力:
|
||||
|
||||
- `contract_categories`
|
||||
- `contract_templates`
|
||||
|
||||
受影响页面包括:
|
||||
|
||||
- `/contract-template/search`
|
||||
- `/contract-template/search/results`
|
||||
- `/contract-template/list`
|
||||
- `/contract-template/detail/[id]`
|
||||
|
||||
现状问题不是“前端页面不存在”,而是“新后端业务接口尚未补齐”。本文补充一版适合当前仓库风格的后端接口设计,覆盖:
|
||||
|
||||
- VO/DTO 草稿
|
||||
- Controller 方法签名建议
|
||||
- 权限 key 建议
|
||||
- 代码目录落点建议
|
||||
|
||||
|
||||
## 目标
|
||||
|
||||
为合同模板搜索、列表、详情链路补齐新的 FastAPI 业务接口,替代前端对 PostgREST 的直接依赖。
|
||||
|
||||
原则:
|
||||
|
||||
- 统一走 `fastapi_modules/fastapi_leaudit` 业务后端
|
||||
- 不继续扩散 PostgREST 依赖
|
||||
- 接口命名、分层、权限风格与现有 `v3` 模块保持一致
|
||||
|
||||
|
||||
## 现有前端依赖梳理
|
||||
|
||||
当前前端依赖的旧查询行为包括:
|
||||
|
||||
1. 分类列表
|
||||
- 首页展示合同分类
|
||||
- 需要附带每个分类下的模板数量
|
||||
|
||||
2. 模板列表
|
||||
- 支持分页
|
||||
- 支持按分类筛选
|
||||
- 支持排序
|
||||
|
||||
3. 模板搜索
|
||||
- 支持关键词查询
|
||||
- 支持分类过滤
|
||||
- 支持结果分页
|
||||
- 搜索结果页还需要分类统计
|
||||
|
||||
4. 模板详情
|
||||
- 查看模板元数据
|
||||
- 下载模板
|
||||
- 预览模板
|
||||
|
||||
|
||||
## 推荐后端目录结构
|
||||
|
||||
建议新增以下文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/controllers/contractTemplateController.py`
|
||||
- `fastapi_modules/fastapi_leaudit/services/contractTemplateService.py`
|
||||
- `fastapi_modules/fastapi_leaudit/services/impl/contractTemplateServiceImpl.py`
|
||||
- `fastapi_modules/fastapi_leaudit/domian/Dto/contractTemplateDto.py`
|
||||
- `fastapi_modules/fastapi_leaudit/domian/vo/contractTemplateVo.py`
|
||||
|
||||
原因:
|
||||
|
||||
- `contract-template` 是独立业务域,不适合继续塞入 `homeService` 或 `documentService`
|
||||
- 当前仓库主要采用“每个业务一个 controller/service/dto/vo”的组织方式
|
||||
- 前端菜单和 RBAC 已经把 `contract-template` 视作独立模块,后端也应保持同样边界
|
||||
|
||||
|
||||
## 路由设计建议
|
||||
|
||||
建议统一挂在:
|
||||
|
||||
- `/api/v3/contract-templates`
|
||||
|
||||
建议提供以下接口:
|
||||
|
||||
1. `GET /api/v3/contract-templates/categories`
|
||||
2. `GET /api/v3/contract-templates`
|
||||
3. `GET /api/v3/contract-templates/search`
|
||||
4. `GET /api/v3/contract-templates/{TemplateId}`
|
||||
|
||||
以上 4 个接口用于替代当前页面的 PostgREST 查询。
|
||||
|
||||
|
||||
## VO 设计草案
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/domian/vo/contractTemplateVo.py`
|
||||
|
||||
草稿如下:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ContractTemplateCategoryVO(BaseModel):
|
||||
"""合同模板分类。"""
|
||||
|
||||
id: int = Field(..., description="分类ID")
|
||||
name: str = Field(..., description="分类名称")
|
||||
icon: str | None = Field(None, description="分类图标")
|
||||
description: str | None = Field(None, description="分类描述")
|
||||
sortOrder: int = Field(0, description="排序")
|
||||
templateCount: int = Field(0, description="分类下模板数量")
|
||||
isEnabled: bool = Field(True, description="是否启用")
|
||||
|
||||
|
||||
class ContractTemplateListItemVO(BaseModel):
|
||||
"""合同模板列表项。"""
|
||||
|
||||
id: int = Field(..., description="模板ID")
|
||||
templateCode: str = Field(..., description="模板编码")
|
||||
title: str = Field(..., description="模板标题")
|
||||
categoryId: int = Field(..., description="分类ID")
|
||||
categoryName: str | None = Field(None, description="分类名称")
|
||||
categoryIcon: str | None = Field(None, description="分类图标")
|
||||
description: str | None = Field(None, description="模板简介")
|
||||
filePath: str | None = Field(None, description="原始模板文件路径")
|
||||
pdfFilePath: str | None = Field(None, description="PDF 预览文件路径")
|
||||
fileFormat: str = Field(..., description="文件格式")
|
||||
isFeatured: bool = Field(False, description="是否推荐")
|
||||
createdAt: str | None = Field(None, description="创建时间")
|
||||
updatedAt: str | None = Field(None, description="更新时间")
|
||||
|
||||
|
||||
class ContractTemplatePageVO(BaseModel):
|
||||
"""合同模板分页结果。"""
|
||||
|
||||
total: int = Field(..., description="总数")
|
||||
page: int = Field(..., description="当前页")
|
||||
pageSize: int = Field(..., description="分页大小")
|
||||
totalPages: int = Field(..., description="总页数")
|
||||
templates: list[ContractTemplateListItemVO] = Field(default_factory=list, description="模板列表")
|
||||
|
||||
|
||||
class ContractTemplateDetailVO(ContractTemplateListItemVO):
|
||||
"""合同模板详情。"""
|
||||
|
||||
categoryDescription: str | None = Field(None, description="分类描述")
|
||||
placeholderSchema: dict | None = Field(None, description="模板占位符结构")
|
||||
|
||||
|
||||
class ContractTemplateSearchCategoryVO(BaseModel):
|
||||
"""搜索结果分类统计。"""
|
||||
|
||||
id: int = Field(..., description="分类ID")
|
||||
name: str = Field(..., description="分类名称")
|
||||
searchCount: int = Field(0, description="当前关键词命中的模板数")
|
||||
|
||||
|
||||
class ContractTemplateSearchResultVO(BaseModel):
|
||||
"""合同模板搜索结果。"""
|
||||
|
||||
total: int = Field(..., description="总数")
|
||||
page: int = Field(..., description="当前页")
|
||||
pageSize: int = Field(..., description="分页大小")
|
||||
totalPages: int = Field(..., description="总页数")
|
||||
templates: list[ContractTemplateListItemVO] = Field(default_factory=list, description="模板列表")
|
||||
categoryStats: list[ContractTemplateSearchCategoryVO] = Field(default_factory=list, description="分类统计")
|
||||
```
|
||||
|
||||
|
||||
## DTO 设计草案
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/domian/Dto/contractTemplateDto.py`
|
||||
|
||||
草稿如下:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ContractTemplateListQueryDTO(BaseModel):
|
||||
"""合同模板列表查询参数。"""
|
||||
|
||||
keyword: str | None = Field(None, description="关键词")
|
||||
category_id: int | None = Field(None, description="分类ID")
|
||||
category_name: str | None = Field(None, description="分类名称")
|
||||
file_format: str | None = Field(None, description="文件格式")
|
||||
is_featured: bool | None = Field(None, description="是否推荐")
|
||||
page: int = Field(1, ge=1, description="页码")
|
||||
page_size: int = Field(12, ge=1, le=200, description="分页大小")
|
||||
sort_by: str = Field("updated_at", description="排序字段")
|
||||
sort_order: str = Field("desc", description="排序方向")
|
||||
|
||||
|
||||
class ContractTemplateSearchQueryDTO(BaseModel):
|
||||
"""合同模板搜索参数。"""
|
||||
|
||||
q: str = Field(..., min_length=1, description="搜索关键词")
|
||||
category_id: int | None = Field(None, description="分类ID")
|
||||
page: int = Field(1, ge=1, description="页码")
|
||||
page_size: int = Field(12, ge=1, le=200, description="分页大小")
|
||||
sort_by: str = Field("updated_at", description="排序字段")
|
||||
sort_order: str = Field("desc", description="排序方向")
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 列表查询和搜索查询可以拆开,保持语义清晰
|
||||
- 如果后端最终希望统一实现,也可以在 service 层共用同一套内部查询对象
|
||||
|
||||
|
||||
## Controller 设计建议
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/controllers/contractTemplateController.py`
|
||||
|
||||
建议风格对齐现有 `BaseController` 用法,路由前缀采用:
|
||||
|
||||
- `/v3/contract-templates`
|
||||
|
||||
方法签名草案:
|
||||
|
||||
```python
|
||||
from fastapi import Depends, Query
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from fastapi_common.fastapi_common_security.security import verify_access_token
|
||||
from fastapi_common.fastapi_common_web.controller import BaseController
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.services.contractTemplateService import IContractTemplateService
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.contractTemplateServiceImpl import ContractTemplateServiceImpl
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.permissionServiceImpl import PermissionServiceImpl
|
||||
from fastapi_modules.fastapi_leaudit.services.permissionService import IPermissionService
|
||||
|
||||
|
||||
class ContractTemplateController(BaseController):
|
||||
def __init__(self):
|
||||
super().__init__(prefix="/v3/contract-templates", tags=["合同模板"])
|
||||
self.ContractTemplateService: IContractTemplateService = ContractTemplateServiceImpl()
|
||||
self.PermissionService: IPermissionService = PermissionServiceImpl()
|
||||
|
||||
@self.router.get("/categories")
|
||||
async def ListContractTemplateCategories(
|
||||
include_disabled: bool = Query(False, description="是否包含禁用分类"),
|
||||
with_template_count: bool = Query(True, description="是否附带模板数量"),
|
||||
payload: dict = Depends(verify_access_token),
|
||||
):
|
||||
...
|
||||
|
||||
@self.router.get("")
|
||||
async def ListContractTemplates(
|
||||
keyword: str | None = Query(None, description="关键词"),
|
||||
category_id: int | None = Query(None, description="分类ID"),
|
||||
category_name: str | None = Query(None, description="分类名称"),
|
||||
file_format: str | None = Query(None, description="文件格式"),
|
||||
is_featured: bool | None = Query(None, description="是否推荐"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(12, ge=1, le=200, description="分页大小"),
|
||||
sort_by: str = Query("updated_at", description="排序字段"),
|
||||
sort_order: str = Query("desc", description="排序方向"),
|
||||
payload: dict = Depends(verify_access_token),
|
||||
):
|
||||
...
|
||||
|
||||
@self.router.get("/search")
|
||||
async def SearchContractTemplates(
|
||||
q: str = Query(..., min_length=1, description="搜索关键词"),
|
||||
category_id: int | None = Query(None, description="分类ID"),
|
||||
page: int = Query(1, ge=1, description="页码"),
|
||||
page_size: int = Query(12, ge=1, le=200, description="分页大小"),
|
||||
sort_by: str = Query("updated_at", description="排序字段"),
|
||||
sort_order: str = Query("desc", description="排序方向"),
|
||||
payload: dict = Depends(verify_access_token),
|
||||
):
|
||||
...
|
||||
|
||||
@self.router.get("/{TemplateId}")
|
||||
async def GetContractTemplateDetail(
|
||||
TemplateId: int,
|
||||
payload: dict = Depends(verify_access_token),
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Service 接口建议
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/services/contractTemplateService.py`
|
||||
|
||||
接口草案:
|
||||
|
||||
```python
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.domian.Dto.contractTemplateDto import (
|
||||
ContractTemplateListQueryDTO,
|
||||
ContractTemplateSearchQueryDTO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.contractTemplateVo import (
|
||||
ContractTemplateCategoryVO,
|
||||
ContractTemplateDetailVO,
|
||||
ContractTemplatePageVO,
|
||||
ContractTemplateSearchResultVO,
|
||||
)
|
||||
|
||||
|
||||
class IContractTemplateService(ABC):
|
||||
@abstractmethod
|
||||
async def ListCategories(self, IncludeDisabled: bool, WithTemplateCount: bool) -> list[ContractTemplateCategoryVO]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def ListTemplates(self, Query: ContractTemplateListQueryDTO) -> ContractTemplatePageVO:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def SearchTemplates(self, Query: ContractTemplateSearchQueryDTO) -> ContractTemplateSearchResultVO:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def GetTemplateDetail(self, TemplateId: int) -> ContractTemplateDetailVO | None:
|
||||
raise NotImplementedError
|
||||
```
|
||||
|
||||
|
||||
## ServiceImpl 实现建议
|
||||
|
||||
建议新增文件:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/services/impl/contractTemplateServiceImpl.py`
|
||||
|
||||
职责边界建议:
|
||||
|
||||
1. `ListCategories`
|
||||
- 直接查 `contract_categories`
|
||||
- 如 `with_template_count=true`,通过聚合一次性返回每类模板数
|
||||
- 不要像前端当前逻辑那样逐分类循环查询
|
||||
|
||||
2. `ListTemplates`
|
||||
- 负责分页、筛选、排序
|
||||
- 支持 `category_id` 和 `category_name`
|
||||
- 统一返回 `ContractTemplatePageVO`
|
||||
|
||||
3. `SearchTemplates`
|
||||
- 可复用 `ListTemplates` 的底层查询逻辑
|
||||
- 额外补 `categoryStats`
|
||||
- 搜索条件建议覆盖:
|
||||
- `title`
|
||||
- `description`
|
||||
- `template_code`
|
||||
- 分类名称
|
||||
|
||||
4. `GetTemplateDetail`
|
||||
- 查单个模板及所属分类信息
|
||||
- 必要时回填 `placeholderSchema`
|
||||
|
||||
|
||||
## 权限 key 建议
|
||||
|
||||
当前仓库权限格式见:
|
||||
|
||||
- `module:resource:action`
|
||||
|
||||
参考现有:
|
||||
|
||||
- `entry_module:list:read`
|
||||
- `evaluation_point:detail:read`
|
||||
- `rules:create:write`
|
||||
|
||||
因此建议新增以下权限:
|
||||
|
||||
1. `contract_template:list:read`
|
||||
- 查看模板列表
|
||||
- 对应:
|
||||
- `GET /api/v3/contract-templates`
|
||||
- `GET /api/v3/contract-templates/categories`
|
||||
|
||||
2. `contract_template:search:read`
|
||||
- 使用模板搜索
|
||||
- 对应:
|
||||
- `GET /api/v3/contract-templates/search`
|
||||
|
||||
3. `contract_template:detail:read`
|
||||
- 查看模板详情
|
||||
- 对应:
|
||||
- `GET /api/v3/contract-templates/{id}`
|
||||
|
||||
如果权限体系希望更简化,也可以把 `categories` 和 `search` 并入 `contract_template:list:read`。但从产品语义上,保留 `search` 独立权限更清晰,方便以后做入口控制和审计。
|
||||
|
||||
|
||||
## Controller 权限校验建议
|
||||
|
||||
建议:
|
||||
|
||||
1. 分类接口
|
||||
- 允许以下任一权限:
|
||||
- `contract_template:list:read`
|
||||
- `contract_template:search:read`
|
||||
|
||||
2. 列表接口
|
||||
- `contract_template:list:read`
|
||||
|
||||
3. 搜索接口
|
||||
- `contract_template:search:read`
|
||||
|
||||
4. 详情接口
|
||||
- `contract_template:detail:read`
|
||||
- 或兼容放宽为:
|
||||
- `contract_template:detail:read`
|
||||
- `contract_template:list:read`
|
||||
|
||||
建议控制器内沿用现有 `_check_permission` 风格,允许多个 key 中任一通过。
|
||||
|
||||
|
||||
## RBAC 权限蓝图补充建议
|
||||
|
||||
建议在:
|
||||
|
||||
- `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py`
|
||||
|
||||
的 `_MANAGEABLE_PERMISSION_BLUEPRINTS` 中补充:
|
||||
|
||||
```python
|
||||
{"permission_key": "contract_template:list:read", "display_name": "查看合同模板列表", "module": "contract_template", "resource": "list", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates", "route_path": "/contract-template/list"},
|
||||
{"permission_key": "contract_template:search:read", "display_name": "搜索合同模板", "module": "contract_template", "resource": "search", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates/search", "route_path": "/contract-template/search"},
|
||||
{"permission_key": "contract_template:detail:read", "display_name": "查看合同模板详情", "module": "contract_template", "resource": "detail", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates/{id}", "route_path": "/contract-template/list"},
|
||||
```
|
||||
|
||||
|
||||
## 命名与字段映射建议
|
||||
|
||||
数据库字段当前大概率仍是 snake_case,例如:
|
||||
|
||||
- `template_code`
|
||||
- `category_id`
|
||||
- `file_path`
|
||||
- `pdf_file_path`
|
||||
- `updated_at`
|
||||
|
||||
建议保持:
|
||||
|
||||
- DB / SQL 层继续使用 snake_case
|
||||
- 对外 VO 统一转为 camelCase
|
||||
|
||||
这样可以和当前 `documentVo.py`、`evaluationPointGroupVo.py` 等风格保持一致,减少前端做字段转换的成本。
|
||||
|
||||
|
||||
## SQL / 查询实现建议
|
||||
|
||||
### 1. 分类数量统计
|
||||
|
||||
不要逐分类循环查询模板数量,建议使用聚合:
|
||||
|
||||
- `contract_categories` 左连接 `contract_templates`
|
||||
- 按分类分组统计
|
||||
|
||||
### 2. 搜索匹配
|
||||
|
||||
建议搜索条件覆盖:
|
||||
|
||||
- 模板标题
|
||||
- 模板描述
|
||||
- 模板编码
|
||||
- 分类名称
|
||||
|
||||
### 3. 排序白名单
|
||||
|
||||
建议只允许以下排序字段:
|
||||
|
||||
- `id`
|
||||
- `title`
|
||||
- `updated_at`
|
||||
- `created_at`
|
||||
|
||||
避免任意字段透传造成 SQL 注入或不可控查询。
|
||||
|
||||
|
||||
## 推荐实施顺序
|
||||
|
||||
1. 新增 `VO/DTO`
|
||||
2. 新增 `Service` 接口与 `ServiceImpl` 空实现
|
||||
3. 新增 `Controller`
|
||||
4. 在 RBAC 权限蓝图中补充权限 key
|
||||
5. 前端 `contract-template/templates.ts` 从 PostgREST 切换到新后端接口
|
||||
|
||||
|
||||
## 最小落地范围
|
||||
|
||||
如果本轮只做最小可用闭环,建议先补齐:
|
||||
|
||||
1. `GET /api/v3/contract-templates/categories`
|
||||
2. `GET /api/v3/contract-templates`
|
||||
3. `GET /api/v3/contract-templates/search`
|
||||
4. `GET /api/v3/contract-templates/{id}`
|
||||
|
||||
这样可以先解决:
|
||||
|
||||
- 搜索首页
|
||||
- 搜索结果页
|
||||
- 列表页
|
||||
- 详情页
|
||||
|
||||
|
||||
## 结论
|
||||
|
||||
`contract-template` 模块当前缺的不是前端页面,而是新的业务后端接口层。推荐按现有仓库习惯新增独立的:
|
||||
|
||||
- `contractTemplateController`
|
||||
- `contractTemplateService`
|
||||
- `contractTemplateServiceImpl`
|
||||
- `contractTemplateDto`
|
||||
- `contractTemplateVo`
|
||||
|
||||
并优先落地 4 个只读接口,把 `search / list / detail` 从 PostgREST 迁出。
|
||||
@@ -0,0 +1,86 @@
|
||||
## 目标
|
||||
|
||||
补齐 `contract-template` 当前阶段只读能力所需的权限蓝图,仅覆盖:
|
||||
|
||||
- 分类
|
||||
- 列表
|
||||
- 搜索
|
||||
- 详情
|
||||
|
||||
明确不包含:
|
||||
|
||||
- 起草合同
|
||||
- 草稿管理
|
||||
- 合同编辑
|
||||
|
||||
|
||||
## 建议新增权限 key
|
||||
|
||||
建议在 `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py` 的 `_MANAGEABLE_PERMISSION_BLUEPRINTS` 中补充以下 3 个权限:
|
||||
|
||||
```python
|
||||
{"permission_key": "contract_template:list:read", "display_name": "查看合同模板列表", "module": "contract_template", "resource": "list", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates", "route_path": "/contract-template/list"},
|
||||
{"permission_key": "contract_template:search:read", "display_name": "搜索合同模板", "module": "contract_template", "resource": "search", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates/search", "route_path": "/contract-template/search"},
|
||||
{"permission_key": "contract_template:detail:read", "display_name": "查看合同模板详情", "module": "contract_template", "resource": "detail", "action": "read", "api_method": "GET", "api_path": "/api/v3/contract-templates/{id}", "route_path": "/contract-template/list"},
|
||||
```
|
||||
|
||||
|
||||
## 分类接口权限建议
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /api/v3/contract-templates/categories`
|
||||
|
||||
建议权限策略:
|
||||
|
||||
- 允许 `contract_template:list:read`
|
||||
- 或 `contract_template:search:read`
|
||||
|
||||
原因:
|
||||
|
||||
- 分类数据同时服务于列表页和搜索页
|
||||
- 不建议单独再拆一个 `category:read` 权限,当前阶段收益不高
|
||||
|
||||
|
||||
## Controller 校验建议
|
||||
|
||||
### 分类
|
||||
|
||||
允许任一权限:
|
||||
|
||||
- `contract_template:list:read`
|
||||
- `contract_template:search:read`
|
||||
|
||||
### 列表
|
||||
|
||||
- `contract_template:list:read`
|
||||
|
||||
### 搜索
|
||||
|
||||
- `contract_template:search:read`
|
||||
|
||||
### 详情
|
||||
|
||||
建议放宽为任一权限:
|
||||
|
||||
- `contract_template:detail:read`
|
||||
- `contract_template:list:read`
|
||||
|
||||
原因:
|
||||
|
||||
- 详情通常从列表页进入
|
||||
- 允许列表权限兼容详情访问,可以减少菜单和权限配置初期的阻塞
|
||||
|
||||
|
||||
## 当前阶段不应新增的权限
|
||||
|
||||
以下权限本轮不要进入蓝图:
|
||||
|
||||
- `contract_draft:create:write`
|
||||
- `contract_draft:update:write`
|
||||
- `contract_draft:delete:delete`
|
||||
|
||||
原因:
|
||||
|
||||
- 你已经明确起草能力将重做为独立模块
|
||||
- 当前阶段只解决 `contract-template` 的只读接口迁移
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,456 @@
|
||||
# 前端分支提交与合并防覆盖操作规范
|
||||
|
||||
## 适用范围
|
||||
|
||||
本文档适用于 `legal-platform-frontend` 前端仓库,重点解决以下高频问题:
|
||||
|
||||
- 本地有未提交改动时,如何安全合并别人的分支
|
||||
- `main`、`wren-dev`、`shiy-dev` 等并行开发分支之间,如何避免代码被覆盖
|
||||
- 为什么“提交历史已经包含某个 commit”,但代码内容实际丢了
|
||||
- 如何做正确的提交、推送、PR 和合并后校验
|
||||
|
||||
---
|
||||
|
||||
## 一、核心原则
|
||||
|
||||
### 1. 不要在脏工作区直接合并
|
||||
|
||||
合并前必须先执行:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
如果存在未提交改动,必须先处理:
|
||||
|
||||
- 能独立提交的,先提交
|
||||
- 暂时不想提交的,先 `stash`
|
||||
|
||||
例如:
|
||||
|
||||
```bash
|
||||
git stash -u
|
||||
```
|
||||
|
||||
否则很容易出现:
|
||||
|
||||
- 合并过程把本地改动混进别人的改动
|
||||
- 冲突解决时误选本地旧代码
|
||||
- 最终提交混杂多个需求,后续难以回溯
|
||||
|
||||
### 2. 不要把“历史包含”误判成“功能保留”
|
||||
|
||||
以下命令只能说明“某个提交进入过历史”:
|
||||
|
||||
```bash
|
||||
git branch --contains <commit>
|
||||
```
|
||||
|
||||
但它**不能说明这次提交改过的内容现在还保留在代码树里**。
|
||||
|
||||
实际开发中常见情况是:
|
||||
|
||||
- A 分支的提交先合入 `main`
|
||||
- 后续 `main` 再合到 B 分支
|
||||
- 冲突时错误选择了旧版本
|
||||
- 结果:`commit` 在历史里,但 `patch` 被覆盖没了
|
||||
|
||||
所以合并后必须额外做“内容保留校验”。
|
||||
|
||||
### 3. 冲突处理不能图快全选一边
|
||||
|
||||
遇到冲突时,不能默认:
|
||||
|
||||
- 全部选 `ours`
|
||||
- 全部选 `theirs`
|
||||
|
||||
必须逐文件判断:
|
||||
|
||||
- 哪些是对方新增能力
|
||||
- 哪些是我方已有修复
|
||||
- 哪些要手工拼接
|
||||
|
||||
尤其是以下类型文件最容易被误覆盖:
|
||||
|
||||
- 页面组件
|
||||
- API 路由
|
||||
- 配置文件
|
||||
- 公共组件
|
||||
- 菜单/路由白名单
|
||||
|
||||
### 4. 合并完成后必须做“保留性校验”
|
||||
|
||||
至少要检查三件事:
|
||||
|
||||
1. 提交历史是否进入
|
||||
2. 关键文件是否仍保留目标改动
|
||||
3. 关键标识是否还能在代码中搜到
|
||||
|
||||
---
|
||||
|
||||
## 二、标准操作流程
|
||||
|
||||
## 1. 合并别人的分支到自己分支
|
||||
|
||||
假设目标是:把 `origin/shiy-dev` 合到当前 `wren-dev`
|
||||
|
||||
### 第一步:同步远程
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
### 第二步:检查本地工作区
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
如果有未提交改动:
|
||||
|
||||
```bash
|
||||
git stash -u
|
||||
```
|
||||
|
||||
或者先拆分提交。
|
||||
|
||||
### 第三步:确认自己当前所在分支
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
必须确认当前就在 `wren-dev`,不要站错分支。
|
||||
|
||||
### 第四步:执行合并
|
||||
|
||||
```bash
|
||||
git merge --no-ff origin/shiy-dev
|
||||
```
|
||||
|
||||
如果要明确记录来源,建议使用:
|
||||
|
||||
```bash
|
||||
git merge --no-ff origin/shiy-dev -m "merge: sync origin/shiy-dev into wren-dev"
|
||||
```
|
||||
|
||||
### 第五步:如果冲突,逐文件处理
|
||||
|
||||
处理完冲突后:
|
||||
|
||||
```bash
|
||||
git add <冲突文件>
|
||||
git commit
|
||||
```
|
||||
|
||||
### 第六步:恢复之前的 stash
|
||||
|
||||
```bash
|
||||
git stash pop
|
||||
```
|
||||
|
||||
如果 `stash pop` 冲突,不要慌,继续按文件处理。
|
||||
|
||||
---
|
||||
|
||||
## 2. 合并 `main` 到自己分支
|
||||
|
||||
目标:保持 `wren-dev` 跟上主线进度
|
||||
|
||||
标准步骤:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git status
|
||||
git stash -u # 如果有本地未提交改动
|
||||
git merge --no-ff origin/main -m "merge: sync origin/main into wren-dev"
|
||||
git stash pop # 如果前面 stash 了
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 不要直接把 `main` 切到当前脏工作区里操作
|
||||
- 如果本地 `main` 有 worktree,优先在独立 worktree 同步
|
||||
- 当前主开发工作区建议长期停留在 `wren-dev`
|
||||
|
||||
---
|
||||
|
||||
## 3. 合并后做“内容保留校验”
|
||||
|
||||
这是最关键的一步。
|
||||
|
||||
### 先检查目标提交是否进入历史
|
||||
|
||||
```bash
|
||||
git branch --contains <commit>
|
||||
```
|
||||
|
||||
### 再检查关键文件内容是否保住
|
||||
|
||||
```bash
|
||||
git diff <commit>..HEAD -- <关键文件>
|
||||
```
|
||||
|
||||
如果看到的是“反向撤销”差异,说明:
|
||||
|
||||
- 历史里有这个提交
|
||||
- 但代码内容被后续 merge 覆盖掉了
|
||||
|
||||
### 再搜关键符号
|
||||
|
||||
例如某次改动新增了:
|
||||
|
||||
- `ENTRY_MODULE_ROUTE_OPTIONS`
|
||||
- `FormSelect`
|
||||
- `isAllowedEntryModuleRoutePath`
|
||||
|
||||
就应该执行:
|
||||
|
||||
```bash
|
||||
rg "ENTRY_MODULE_ROUTE_OPTIONS|FormSelect|isAllowedEntryModuleRoutePath" .
|
||||
```
|
||||
|
||||
如果关键标识不在,说明功能实际上没有保住。
|
||||
|
||||
---
|
||||
|
||||
## 三、发现“历史包含但代码没了”怎么办
|
||||
|
||||
这是本项目已经真实发生过的情况。
|
||||
|
||||
## 处理原则
|
||||
|
||||
不要重新大范围 merge。
|
||||
|
||||
正确做法是:
|
||||
|
||||
- 精确定位丢失的是哪些文件
|
||||
- 只恢复这些文件
|
||||
- 单独提交
|
||||
|
||||
### 推荐命令
|
||||
|
||||
```bash
|
||||
git restore --source=<目标提交> -- <文件1> <文件2> ...
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```bash
|
||||
git restore --source=2b912ca -- \
|
||||
app/(audit)/documents/list/DocumentsListClient.tsx \
|
||||
app/(audit)/entry-modules/new/EntryModuleNewClient.tsx \
|
||||
app/api/pdf-proxy/route.ts \
|
||||
components/layout/Sidebar.tsx \
|
||||
lib/config/entry-module-route-options.ts
|
||||
```
|
||||
|
||||
然后单独提交:
|
||||
|
||||
```bash
|
||||
git add <这些文件>
|
||||
git commit -m "fix: restore shiy-dev entry route whitelist changes"
|
||||
git push origin wren-dev
|
||||
```
|
||||
|
||||
这种方式最安全,且不会影响你当前其他本地需求改动。
|
||||
|
||||
---
|
||||
|
||||
## 四、提交规范
|
||||
|
||||
## 1. 一个提交只解决一类问题
|
||||
|
||||
不要把以下内容混在一个 commit:
|
||||
|
||||
- 聊天功能修复
|
||||
- 公文审查 UI 收敛
|
||||
- 合同模板页面开发
|
||||
- 分支恢复补丁
|
||||
|
||||
应该拆成:
|
||||
|
||||
- `fix: remove govdoc inspector file info tab`
|
||||
- `fix: restore shiy-dev entry route whitelist changes`
|
||||
- `feat: add contract template search results page`
|
||||
|
||||
## 2. 提交前先看范围
|
||||
|
||||
提交前务必执行:
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff --stat
|
||||
```
|
||||
|
||||
如果只想提交部分文件:
|
||||
|
||||
```bash
|
||||
git add <目标文件>
|
||||
git commit -m "<message>"
|
||||
```
|
||||
|
||||
不要图省事直接:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
```
|
||||
|
||||
除非你确认工作区所有改动都属于同一件事。
|
||||
|
||||
## 3. 提交信息规范
|
||||
|
||||
推荐格式:
|
||||
|
||||
```text
|
||||
feat: 新增功能
|
||||
fix: 修复问题
|
||||
refactor: 重构实现
|
||||
merge: 分支合并
|
||||
docs: 文档更新
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
- `feat: stabilize rag chat conversation lifecycle`
|
||||
- `fix: remove govdoc inspector file info tab`
|
||||
- `fix: restore shiy-dev entry route whitelist changes`
|
||||
- `merge: sync origin/main into wren-dev`
|
||||
|
||||
---
|
||||
|
||||
## 五、推送规范
|
||||
|
||||
推送前先确认:
|
||||
|
||||
```bash
|
||||
git status
|
||||
git log --oneline --max-count=5
|
||||
```
|
||||
|
||||
再执行:
|
||||
|
||||
```bash
|
||||
git push origin wren-dev
|
||||
```
|
||||
|
||||
如果本地还有未提交改动,不影响推送已提交的 commit,但要明确知道:
|
||||
|
||||
- 已提交内容会推上去
|
||||
- 未提交内容不会推上去
|
||||
|
||||
不要误以为“工作区里看到的所有代码”都已经在远程。
|
||||
|
||||
---
|
||||
|
||||
## 六、PR 规范
|
||||
|
||||
PR 标题必须说明“做了什么”,不要只写模块名。
|
||||
|
||||
推荐示例:
|
||||
|
||||
- `恢复入口模块跳转路径白名单与 FormSelect 收敛改动`
|
||||
- `修复 RAG 对话会话生命周期、自动重命名刷新与列表状态问题`
|
||||
|
||||
PR 描述建议固定包含:
|
||||
|
||||
### 1. 背景
|
||||
|
||||
为什么要改。
|
||||
|
||||
### 2. 本次改动
|
||||
|
||||
具体改了哪些点。
|
||||
|
||||
### 3. 影响范围
|
||||
|
||||
改到了哪些页面、接口、组件、模块。
|
||||
|
||||
### 4. 验证建议
|
||||
|
||||
告诉审核人怎么测。
|
||||
|
||||
### 5. 特别说明
|
||||
|
||||
如果是“恢复被覆盖改动”,要明确写出来,避免评审人误解成重复开发。
|
||||
|
||||
---
|
||||
|
||||
## 七、推荐命令清单
|
||||
|
||||
### 检查工作区
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff --stat
|
||||
```
|
||||
|
||||
### 同步远程
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
### 暂存本地未提交改动
|
||||
|
||||
```bash
|
||||
git stash -u
|
||||
git stash pop
|
||||
```
|
||||
|
||||
### 合并主线或他人分支
|
||||
|
||||
```bash
|
||||
git merge --no-ff origin/main
|
||||
git merge --no-ff origin/shiy-dev
|
||||
```
|
||||
|
||||
### 检查某个提交是否进入历史
|
||||
|
||||
```bash
|
||||
git branch --contains <commit>
|
||||
```
|
||||
|
||||
### 检查关键 patch 是否仍保留
|
||||
|
||||
```bash
|
||||
git diff <commit>..HEAD -- <关键文件>
|
||||
```
|
||||
|
||||
### 精确恢复某次提交改动
|
||||
|
||||
```bash
|
||||
git restore --source=<commit> -- <文件列表>
|
||||
```
|
||||
|
||||
### 只提交指定文件
|
||||
|
||||
```bash
|
||||
git add <文件列表>
|
||||
git commit -m "<message>"
|
||||
```
|
||||
|
||||
### 推送当前分支
|
||||
|
||||
```bash
|
||||
git push origin wren-dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、结论
|
||||
|
||||
以后判断一次合并是否真正成功,不能只看:
|
||||
|
||||
- 分支图里有没有那个 commit
|
||||
|
||||
还必须同时看:
|
||||
|
||||
- 关键文件内容还在不在
|
||||
- 关键标识还能不能搜到
|
||||
- 最终页面行为是不是正确
|
||||
|
||||
一句话总结:
|
||||
|
||||
> 合并成功的标准,不是“历史里有 commit”,而是“当前代码树里还保留对应 patch,并且功能行为正确”。
|
||||
|
||||
Reference in New Issue
Block a user