- 接入 SAM2 视频传播能力:新增 /api/ai/propagate,支持用当前帧 mask/polygon/bbox 作为 seed,通过 SAM2 video predictor 向前、向后或双向传播,并可保存为真实 annotation。 - 接入 SAM3 video tracker:通过独立 Python 3.12 external worker 调用 SAM3 video predictor/tracker,使用本地 checkpoint 与 bbox seed 执行视频级跟踪,并在模型状态中标记 video_track 能力。 - 完善 SAM 模型分发:sam_registry 按 model_id 明确区分 sam2 propagation 与 sam3 video_track,避免两个模型链路混用。 - 打通前端“传播片段”:VideoWorkspace 使用当前选中 mask 和当前 AI 模型调用后端传播接口,传播结果回写并刷新工作区已保存标注。 - 增强 SAM3 本地 checkpoint 配置:新增 sam3_checkpoint_path 配置和 .env.example 示例,状态检查改为基于本地 checkpoint/独立环境/模型包可用性。 - 完善视频拆帧参数:/api/media/parse 支持 parse_fps、max_frames、target_width,后端任务保存帧时间戳、源帧号和 frame_sequence 元数据。 - 增加运行时 schema 兼容处理:启动时为旧 frames 表补充 timestamp_ms 和 source_frame_number 列,避免旧库升级后缺字段。 - 强化 Canvas 标注编辑:补齐多边形闭合、点工具、顶点拖拽、边中点插入、Delete/Backspace 删除、区域合并和重叠去除等交互。 - 增强语义分类联动:选中 mask 后可通过右侧语义分类树更新标签、颜色和 class metadata,并同步到保存/导出链路。 - 增加关键帧时间轴体验:FrameTimeline 显示具体时间信息,并支持键盘左右方向键切换关键帧。 - 完善 AI 交互分割参数:前端保留正向点、反向点、框选和 interactive prompt 的调用状态,支持 SAM2 细化候选区域与 SAM3 bbox 入口。 - 扩展后端/前端 API 类型:新增 propagateMasks、传播请求/响应 schema,并补齐 annotation、导出、模型状态和任务接口的测试覆盖。 - 更新项目文档:同步 README、AGENTS、接口契约、需求冻结、设计冻结、前端元素审计、实施计划和测试计划,标明真实功能边界与剩余风险。 - 增加测试覆盖:补充 SAM2/SAM3 传播、SAM3 状态、媒体拆帧参数、Canvas 编辑、语义标签切换、时间轴、工作区传播和 API 合约测试。 - 加强仓库安全边界:将 sam3权重/ 加入 .gitignore,避免本地模型权重被误提交。 验证:npm run test:run;pytest backend/tests;npm run lint;npm run build;python -m py_compile;git diff --check。
307 lines
18 KiB
Markdown
307 lines
18 KiB
Markdown
# AGENTS.md — AI 编码助手项目指南
|
||
|
||
> 本文件面向 AI 编码助手。阅读者应假设对该项目一无所知。以下信息基于当前仓库实际文件、脚本和源码;不要把早期设计目标当作已实现事实。任何代码和功能修改都要落实到文档和测试上,如果生成git commit信息,要逐个列点把所有修改都列上,重要的、大的修改放前面,不重要的、小的修改列在后面。
|
||
|
||
---
|
||
|
||
## 项目概述
|
||
|
||
本项目是一个**语义分割系统**(Semantic Segmentation System),当前形态是 React 前端 + FastAPI 后端的全栈 Web 应用,用于视频/DICOM 医学影像上传、服务器端拆帧、交互式 Canvas 标注、视频片段传播、GT mask 导入、SAM 2/SAM 3 可选辅助分割、模板分类管理和标注导出。
|
||
|
||
- **项目名称**: `react-example`(`package.json` 中的 `name`)
|
||
- **前端入口**: `src/main.tsx` → `src/App.tsx`
|
||
- **前端服务入口**: `server.ts`(Express + Vite 中间件 / 生产静态服务,并保留少量旧版 mock API)
|
||
- **后端入口**: `backend/main.py`(FastAPI)
|
||
- **默认前端地址**: `http://localhost:3000`
|
||
- **默认后端地址**: `http://localhost:8000`
|
||
- **前端 API 配置**: `src/lib/config.ts`,优先读取 `VITE_API_BASE_URL`,未配置时按当前浏览器 hostname 推导 `http://<host>:8000`
|
||
- **业务文档**: `语义分割系统构建方案.docx`(项目根目录)
|
||
|
||
---
|
||
|
||
## 技术栈
|
||
|
||
| 层级 | 技术 |
|
||
|------|------|
|
||
| 前端框架 | React 19 + TypeScript 5.8 |
|
||
| 构建工具 | Vite 6 |
|
||
| 前端样式 | TailwindCSS 4 + 自定义深色主题 |
|
||
| 前端状态 | Zustand(`src/store/useStore.ts`) |
|
||
| 前端请求 | Axios(`src/lib/api.ts`) |
|
||
| 实时通信 | WebSocket 客户端(`src/lib/websocket.ts`) |
|
||
| Canvas 渲染 | Konva + react-konva + use-image |
|
||
| 几何布尔运算 | polygon-clipping |
|
||
| 图标库 | lucide-react |
|
||
| 动画依赖 | motion(在 `package.json` 中声明) |
|
||
| AI SDK 依赖 | `@google/genai`(在 `package.json` 中声明;当前业务源码未直接调用) |
|
||
| 后端框架 | FastAPI + Uvicorn |
|
||
| ORM / 数据库 | SQLAlchemy + PostgreSQL |
|
||
| 缓存 / 队列 Broker | Redis |
|
||
| 后台任务 | Celery worker |
|
||
| 对象存储 | MinIO |
|
||
| AI 推理 | SAM 2 / SAM 3 可选模型 + PyTorch;SAM 3 通过独立 Python 3.12 conda 环境桥接;`GET /api/ai/models/status` 返回真实 GPU/模型/本地 checkpoint 状态 |
|
||
| 视频 / 影像处理 | FFmpeg / OpenCV / pydicom |
|
||
| 运行时 | Node.js ES Modules;Python 3.11 后端环境;可选 `sam3` Python 3.12 conda 环境 |
|
||
|
||
---
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
Seg_Server/
|
||
├── server.ts # Express + Vite 前端入口;保留 /api/login、/api/projects、/api/templates mock
|
||
├── index.html # SPA HTML 入口
|
||
├── vite.config.ts # Vite 配置;含 @/* 路径别名与 DISABLE_HMR 逻辑
|
||
├── tsconfig.json # TypeScript 配置;@/* 映射到项目根目录
|
||
├── package.json # npm 依赖与脚本
|
||
├── .env.example # AI Studio/Gemini 前端环境变量模板
|
||
├── metadata.json # AI Studio 元数据
|
||
├── public/
|
||
│ └── logo.png # Sidebar 使用的 /logo.png
|
||
├── doc/ # 当前实现审计、接口契约和后续实施文档
|
||
├── start_services.sh # 本地一键启动 PostgreSQL/Redis/MinIO/FastAPI/Celery/前端
|
||
├── backend/ # FastAPI 后端
|
||
│ ├── main.py # 应用入口、lifespan、CORS、路由注册、WebSocket
|
||
│ ├── config.py # Pydantic Settings;读取 backend/.env
|
||
│ ├── database.py # SQLAlchemy Engine / Session
|
||
│ ├── models.py # Project/Frame/Template/Annotation/Mask/ProcessingTask ORM
|
||
│ ├── schemas.py # Pydantic 请求/响应模型
|
||
│ ├── minio_client.py # MinIO 上传、下载、预签名 URL
|
||
│ ├── redis_client.py # Redis 连接封装
|
||
│ ├── celery_app.py # Celery app 配置
|
||
│ ├── worker_tasks.py # Celery 任务入口
|
||
│ ├── download_sam2.py # SAM 2 权重下载脚本
|
||
│ ├── setup_sam3_env.sh # SAM 3 独立 Python 3.12 环境安装脚本
|
||
│ ├── requirements.txt # Python 依赖
|
||
│ ├── routers/
|
||
│ │ ├── auth.py # /api/auth/login
|
||
│ │ ├── projects.py # /api/projects 与 /api/projects/{id}/frames
|
||
│ │ ├── templates.py # /api/templates
|
||
│ │ ├── media.py # /api/media/upload、/upload/dicom、/parse
|
||
│ │ ├── ai.py # /api/ai/predict、/propagate、/models/status、/auto、/annotate
|
||
│ │ └── export.py # /api/export/{project_id}/coco、/masks
|
||
│ └── services/
|
||
│ ├── frame_parser.py # FFmpeg/OpenCV 拆帧、pydicom 读片、帧上传
|
||
│ ├── sam2_engine.py # SAM 2 单帧推理和 video predictor 传播封装
|
||
│ ├── sam3_engine.py # SAM 3 状态检测、外部环境桥接、文本语义推理、框选与 video tracker 适配器
|
||
│ ├── sam3_external_worker.py # 独立 sam3 conda 环境中执行的状态/推理 helper
|
||
│ └── sam_registry.py # SAM 模型选择、GPU 状态与推理分发
|
||
└── src/ # React 前端
|
||
├── main.tsx # React StrictMode 挂载
|
||
├── App.tsx # 登录拦截 + 模块切换
|
||
├── index.css # TailwindCSS 导入 + 全局样式
|
||
├── store/useStore.ts # Zustand 全局状态
|
||
├── lib/api.ts # Axios API 封装
|
||
├── lib/websocket.ts # 解析进度 WebSocket 客户端
|
||
├── lib/utils.ts # cn() 工具函数
|
||
└── components/ # 扁平化组件目录
|
||
├── Login.tsx
|
||
├── Sidebar.tsx
|
||
├── Dashboard.tsx
|
||
├── ProjectLibrary.tsx
|
||
├── VideoWorkspace.tsx
|
||
├── CanvasArea.tsx
|
||
├── ToolsPalette.tsx
|
||
├── OntologyInspector.tsx
|
||
├── FrameTimeline.tsx
|
||
├── AISegmentation.tsx
|
||
└── TemplateRegistry.tsx
|
||
```
|
||
|
||
以下目录/文件通常是运行产物或本地数据,已在 `.gitignore` 中忽略:`node_modules/`、`dist/`、`models/`、`uploads/`、`frames/`、`Data_*/`、`*.mp4`、`*.dcm`、`*.7z`、`backend/.env`、日志文件等。
|
||
|
||
`doc/` 目录是当前项目的事实文档入口。修改功能前优先查看:
|
||
|
||
- `doc/03-frontend-element-audit.md`:哪些前端元素是真功能,哪些是 Mock/UI-only。
|
||
- `doc/04-api-contracts.md`:前后端接口契约,以及当前不一致点。
|
||
- `doc/05-implementation-plan.md`:建议的后续实施顺序。
|
||
|
||
---
|
||
|
||
## 构建与运行命令
|
||
|
||
### 前端 / Node 入口
|
||
|
||
```bash
|
||
npm install
|
||
|
||
# 开发模式:运行 tsx server.ts,Express 集成 Vite middleware,端口 3000
|
||
npm run dev
|
||
|
||
# 生产构建:输出 dist/
|
||
npm run build
|
||
|
||
# Vite 预览
|
||
npm run preview
|
||
|
||
# 生产模式运行 server.ts,服务 dist/;仍保留 server.ts 中的旧版 mock API
|
||
npm start
|
||
|
||
# TypeScript 类型检查
|
||
npm run lint
|
||
|
||
# 删除 dist/
|
||
npm run clean
|
||
```
|
||
|
||
### FastAPI 后端
|
||
|
||
```bash
|
||
cd backend
|
||
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||
```
|
||
|
||
### 一键启动
|
||
|
||
```bash
|
||
./start_services.sh
|
||
```
|
||
|
||
该脚本会依次检查/启动 PostgreSQL、Redis、MinIO、FastAPI 后端、Celery worker 和前端。
|
||
|
||
---
|
||
|
||
## 运行时架构
|
||
|
||
### 前端
|
||
|
||
- 单页应用,无路由库;模块切换由 `useStore().activeModule` 控制。
|
||
- 模块值包括:`dashboard`、`projects`、`ai`、`workspace`、`templates`。
|
||
- 默认模块是 `workspace`。
|
||
- 未登录时渲染 `Login`。
|
||
- 登录成功后 token 写入 `localStorage`,Axios request interceptor 会附加 `Authorization: Bearer <token>`。
|
||
- `App.tsx` 在登录后调用 `getProjects()` 初始化项目列表。
|
||
|
||
### 后端
|
||
|
||
- 主后端是 `backend/main.py` 的 FastAPI 服务。
|
||
- `lifespan` 启动时会:
|
||
- 创建数据库表;
|
||
- 检查/创建 MinIO bucket `seg-media`;
|
||
- 测试 Redis 连接;
|
||
- 后台 seed 默认模板;
|
||
- 如果本地存在 `Data_MyVideo_1.mp4`,后台 seed 默认演示项目并拆前 100 帧。
|
||
- API 路由包括:
|
||
- `POST /api/auth/login`
|
||
- `GET/POST/PATCH/DELETE /api/projects`
|
||
- `GET/POST /api/projects/{project_id}/frames`
|
||
- `GET/POST/PATCH/DELETE /api/templates`
|
||
- `POST /api/media/upload`
|
||
- `POST /api/media/upload/dicom`
|
||
- `POST /api/media/parse`
|
||
- `GET /api/tasks`
|
||
- `GET /api/tasks/{task_id}`
|
||
- `POST /api/tasks/{task_id}/cancel`
|
||
- `POST /api/tasks/{task_id}/retry`
|
||
- `POST /api/ai/predict`
|
||
- `POST /api/ai/propagate`
|
||
- `GET /api/ai/models/status`
|
||
- `POST /api/ai/auto`
|
||
- `POST /api/ai/annotate`
|
||
- `POST /api/ai/import-gt-mask`
|
||
- `GET /api/ai/annotations`
|
||
- `PATCH/DELETE /api/ai/annotations/{annotation_id}`
|
||
- `GET /api/dashboard/overview`
|
||
- `GET /api/export/{project_id}/coco`
|
||
- `GET /api/export/{project_id}/masks`
|
||
- `GET /health`
|
||
- `WS /ws/progress`
|
||
|
||
### 存储
|
||
|
||
- PostgreSQL 存储项目、帧、模板、标注、mask 和后台任务元数据。
|
||
- MinIO 存储上传视频、DICOM、拆出的帧、缩略图等对象;前端展示使用预签名 URL。
|
||
- Redis 当前作为 Celery broker/result backend,并用于连接检查。
|
||
|
||
---
|
||
|
||
## 主要业务流程
|
||
|
||
1. 登录:`Login.tsx` 调用 `POST /api/auth/login`,默认开发凭证为 `admin / 123456`。
|
||
2. 项目管理:`ProjectLibrary.tsx` 调用项目 API 创建项目、拉取列表。
|
||
3. 上传资源:视频走 `/api/media/upload`;DICOM 批量走 `/api/media/upload/dicom`。
|
||
4. 拆帧入队:前端调用 `/api/media/parse`;后端创建 `ProcessingTask` 并投递 Celery,接口支持 `parse_fps`、`max_frames` 和 `target_width` 标准帧序列参数。
|
||
5. worker 执行:Celery worker 用 FFmpeg 优先拆视频帧,失败后用 OpenCV fallback,DICOM 使用 pydicom;视频帧按 `frame_%06d.jpg` 连续命名并记录 `timestamp_ms`、`source_frame_number` 和任务 `frame_sequence` 元数据。
|
||
6. 帧展示:`VideoWorkspace.tsx` 调用 `/api/projects/{id}/frames`,`CanvasArea.tsx` 和 `FrameTimeline.tsx` 显示当前帧与时间轴缩略图;前端 `Frame` 会保留后端返回的帧序列时间戳和源帧号。
|
||
7. 手工标注:`CanvasArea.tsx` 支持多边形、矩形、圆、点区域和线段生成 polygon mask;多边形可按 Enter 或点击首节点闭合;绘制工具可在已有 mask 上继续落点;点击 mask 可拖动/删除 polygon 顶点、通过边中点插入新顶点,并能选择编辑多 polygon mask 的单个子区域;选中整块 mask 可用 Delete/Backspace 删除,已保存 mask 会同步后端删除;区域合并/去除会隐藏编辑手柄并显示已选数量,使用 `polygon-clipping` 做 union/difference,内含去除结果用 even-odd 规则渲染 hole;Zustand 维护 `maskHistory/maskFuture` 支持撤销/重做。
|
||
8. AI 分割:前端工具包括正向点、反向点和框选;SAM 2 框选会建立候选 mask,后续正/反点通过 `interactive` prompt 携带原始框和累计点细化同一个候选 mask;后端 `ai.py` 期望按 `image_id`、`prompt_type`、`prompt_data`、`model` 和可选 `options` 调用 SAM registry。SAM 2 支持点/框/interactive/自动分割和 video predictor 传播;`options.crop_to_prompt` 可对点/框/interactive prompt 做局部裁剪推理并回映射,`options.auto_filter_background` 可按分数和负向点过滤结果;SAM 3 入口支持文本语义推理、框选提示和 external video tracker,主后端会通过 `sam3_external_worker.py` 调用独立 Python 3.12 环境,并优先使用 `sam3_checkpoint_path` 指向的本地 `sam3权重/sam3.pt`;如果 Python/CUDA/包/本地 checkpoint 均满足,会在状态接口中标为可用。
|
||
9. 视频片段传播:工作区“传播片段”把当前选中 mask 或当前帧第一个 mask 作为 seed,调用 `POST /api/ai/propagate`;后端按项目帧序列下载片段帧,SAM 2 用 `SAM2VideoPredictor.add_new_mask()` + `propagate_in_video()`,SAM 3 用独立 helper 的官方 `build_sam3_video_predictor()`,并把后续帧结果保存为 `Annotation`。
|
||
10. GT 导入:工作区“导入 GT Mask”调用 `/api/ai/import-gt-mask`;后端按非零像素值和连通域生成 polygon 标注,并用 distance transform 生成 seed point;前端回显 seed point,拖动后可归档更新。
|
||
11. 模板管理:`TemplateRegistry.tsx` 管理分类、颜色和 z-index;`OntologyInspector.tsx` 在工作区显示当前模板分类树。
|
||
12. 导出:后端支持 COCO JSON 和 PNG mask ZIP 导出;PNG ZIP 包含单标注 mask、按 zIndex 融合的语义 mask 和 `semantic_classes.json`。
|
||
|
||
---
|
||
|
||
## 当前实现注意事项
|
||
|
||
- `src/lib/config.ts` 会优先读取 `VITE_API_BASE_URL` 和 `VITE_WS_PROGRESS_URL`;未配置时按当前浏览器 hostname 推导后端 `:8000` 地址。
|
||
- 前端 `predictMask()` 已按后端 `PredictRequest` 发送 `image_id`、`prompt_type`、`prompt_data`、`model`,并将后端 `polygons` 转成 Konva 可渲染的 `pathData`。
|
||
- 手工绘制工具会生成可保存的 `Mask.segmentation`;撤销/重做通过 `maskHistory/maskFuture` 工作。
|
||
- Polygon 顶点编辑会重算 `pathData/segmentation/bbox/area`;已保存 mask 进入 dirty 状态后复用归档 PATCH 链路。
|
||
- 区域合并/去除会重算主 mask 的几何;合并已保存的次级 mask 时会通过工作区回调删除对应后端标注。
|
||
- 前端 `importGtMask()` 已对齐后端 `/api/ai/import-gt-mask`;工作区“导入 GT Mask”会导入后端生成的多类别标注和 seed point 并回显。
|
||
- 前端 `exportCoco()` 已对齐后端 `/api/export/{project_id}/coco`;前端 `exportMasks()` 已对齐后端 `/api/export/{project_id}/masks`;工作区导出按钮会先保存当前待归档 mask。
|
||
- 工作区“结构化归档保存”按钮已接入 `POST /api/ai/annotate` 和 `PATCH /api/ai/annotations/{id}`;加载工作区时会通过 `GET /api/ai/annotations` 回显已保存标注。
|
||
- 工作区“传播片段”按钮已接入 `POST /api/ai/propagate`;SAM 2 路径使用视频 predictor,SAM 3 路径使用独立 Python helper 的官方 video tracker,完成后刷新后端已保存标注。
|
||
- 工作区“清空遮罩”会调用 `DELETE /api/ai/annotations/{id}` 删除当前帧已保存标注,并清空当前帧本地 mask。
|
||
- 项目状态已统一为 `pending`、`parsing`、`ready`、`error`;前端 `src/lib/api.ts` 会兼容归一化旧库中可能存在的 `Ready`、`Parsing`、`Error`。
|
||
- `server.ts` 仍有旧版 `/api/login`、`/api/projects`、`/api/templates` mock;当前前端真实 API 调用主要走 FastAPI 的 `/api/auth/*`、`/api/projects`、`/api/templates` 等接口。
|
||
- `Dashboard.tsx` 初始统计、队列和活动日志来自 `GET /api/dashboard/overview`;解析队列来自 `processing_tasks`,支持取消 queued/running 任务、重试 failed/cancelled 任务和查看失败详情。Celery worker 通过 Redis pub/sub 的 `seg:progress` 频道推送细粒度进度,再由 FastAPI 广播到 `/ws/progress`。
|
||
|
||
---
|
||
|
||
## 代码风格与约定
|
||
|
||
### 样式规范
|
||
|
||
- 深色主题为主,常见背景色包括 `#0a0a0a`、`#111`、`#0d0d0d`、`#151515`、`#1e1e1e`。
|
||
- 青色(如 `cyan-400` / `cyan-500`)用于激活状态、主按钮和关键指标。
|
||
- 前端样式主要使用 TailwindCSS 工具类,通过 `cn()` 合并条件类名。
|
||
- `src/index.css` 使用 TailwindCSS 4 的 `@import "tailwindcss";`。
|
||
|
||
### 组件规范
|
||
|
||
- 组件使用函数组件 + Hooks。
|
||
- 当前组件目录是扁平结构:`src/components/*.tsx`,不是按模块子目录分层。
|
||
- Props 类型优先使用 TypeScript `interface`。
|
||
- UI 文本保持中文。
|
||
- 代码与注释优先使用英文。
|
||
|
||
### 命名规范
|
||
|
||
- 组件文件使用 PascalCase,例如 `AISegmentation.tsx`。
|
||
- 工具文件使用 camelCase,例如 `utils.ts`。
|
||
- 类型和接口使用 PascalCase。
|
||
|
||
---
|
||
|
||
## 测试策略
|
||
|
||
当前仓库已配置前端 Vitest 测试和后端 pytest 测试。测试依据 `doc/07-current-requirements-freeze.md`、`doc/08-current-design-freeze.md` 和 `doc/09-test-plan.md`。
|
||
|
||
- 前端测试配置:`vitest.config.ts`,共享 setup 在 `src/test/setup.tsx`。
|
||
- 前端测试命令:`npm run test:run`。
|
||
- 后端测试依赖:`backend/requirements-dev.txt`。
|
||
- 后端测试命令:`pytest backend/tests`,或在 `backend/` 目录执行 `pytest tests`。
|
||
- 基础静态校验:`npm run lint`、`npm run build`、`python -m py_compile backend/routers/ai.py backend/routers/templates.py backend/schemas.py`。
|
||
- 后端测试使用内存 SQLite、fake MinIO 和 fake SAM registry,不依赖真实 PostgreSQL、MinIO、Redis 或模型权重。
|
||
|
||
---
|
||
|
||
## 安全注意事项
|
||
|
||
- FastAPI 登录是开发用硬编码凭证:`admin / 123456`。
|
||
- 登录成功返回固定 token:`fake-jwt-token-for-admin`,没有真实 JWT 签名校验。
|
||
- Axios 会附加 Bearer token,但后端大多数业务路由当前没有鉴权依赖。
|
||
- `backend/.env` 被 `.gitignore` 忽略;不要提交真实数据库、MinIO、Redis、模型路径等敏感配置。
|
||
- `start_services.sh` 中包含本机路径和 sudo 启动逻辑,迁移机器时要审查。
|
||
- Express `server.ts` 的旧版 mock API 只适合开发/兼容场景,不能当生产鉴权或持久化方案。
|
||
|
||
---
|
||
|
||
## AI Studio / Vite 特定配置
|
||
|
||
- `.env.example` 包含 `GEMINI_API_KEY` 和 `APP_URL`,说明这些值由 AI Studio 注入。
|
||
- `vite.config.ts` 通过 `loadEnv` 把 `GEMINI_API_KEY` 注入到 `process.env.GEMINI_API_KEY`。
|
||
- `vite.config.ts` 中的 `DISABLE_HMR` 逻辑用于关闭 HMR,避免 AI Studio agent 编辑时闪烁。**不要随意修改该逻辑。**
|