feat: add find page, breadcrumb components and canvas workbench updates
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# 传统项目开发有哪些部分,运维是干什么的
|
||||
|
||||
> 对应你的问题:**“传统项目开发中有哪些部分,运维是干什么的”**
|
||||
|
||||
下文按“全生命周期”拆解,先讲开发各阶段,再讲运维对应干什么。所有例子尽量映射到 WordCloud 这个项目本身,方便你直接对照。
|
||||
|
||||
---
|
||||
|
||||
## 一、项目开发的全生命周期(6 个阶段)
|
||||
|
||||
| 阶段 | 昵称 | 这个阶段的产出物 | WordCloud 里的例子 |
|
||||
|------|------|------------------|-------------------|
|
||||
| 1. 需求/设计 | 想清楚 | PRD、原型图、接口契约 | 词云效果要支持 `VERTICAL_RATIO`,垂直字怎么排;前端画布要能导出 SVG |
|
||||
| 2. 开发 | 写代码 | 功能代码、单元测试 | `ewc_core.cpp` 的碰撞布局算法、`frontend/src/components/...` 的 UI 面板 |
|
||||
| 3. 构建/打包 | 编译出来 | 可执行产物 | C++ → `.so`、前端 `vite build` → `dist/`、Docker image |
|
||||
| 4. 部署 | 放上线 | 线上运行的服务 | `docker compose up -d`,VPS/云主机上跑 backend + frontend |
|
||||
| 5. 运行/监控 | 盯着它 | 日志、告警、扩缩容 | 查看 `/api/health`、容器内存占用、用户访问量是否把接口打满 |
|
||||
| 6. 维护/迭代 | 打补丁 | 新 release、热修复 | 修复布局算法 bug、加新字段 `canvasNegativeSpaceRatio`、灰度发布 |
|
||||
|
||||
**不要把“开发”只理解为写代码。** 写代码只是阶段 2,后面 4 个阶段合起来叫“运维相关”或“交付相关”。
|
||||
|
||||
---
|
||||
|
||||
## 二、运维(Operations)到底是干什么的
|
||||
|
||||
运维不是修电脑、不是只会重启服务器。它的核心职责可以用一句话概括:
|
||||
|
||||
> **让软件从“能跑”变成“稳定、安全、低成本地跑在线上,并且出了问题能快速恢复”。**la
|
||||
|
||||
### 2.1 运维的 5 大日常工作领域
|
||||
|
||||
| 领域 | 通俗解释 | 在 WordCloud 项目里的具体体现 |
|
||||
|------|----------|------------------------------|
|
||||
| **部署与发布** | 把新版本放上线,让用户能用 | `docker compose build` → `docker compose up -d`;或者把 image 推上私有 registry,服务器拉下来跑 |
|
||||
| **监控与告警** | 知道系统什么时候快挂了 | `healthcheck` 配置在 `docker-compose.yml` 已有:`curl -f http://localhost:8000/api/health`,失败 3 次就标记容器 unhealthy |
|
||||
| **日志与排障** | 出事了看日志找原因 | FastAPI 访问日志、C++ 扩展 `stderr` 报错、前端浏览器 console error |
|
||||
| **备份与恢复** | 数据丢了能救回来 | `service_workspace/`、`service_projects/` 里的用户设计稿、字体文件,是否定期快照? |
|
||||
| **安全与权限** | 别让人随便进来搞破坏 | 前端直接暴露到公网?后端有没有 CORS 白名单?Docker 端口只开 8000/3000? |
|
||||
|
||||
### 2.2 运维与开发的关系——不是“前后顺序”而是“左右手”
|
||||
|
||||
```
|
||||
开发(Dev) 运维(Ops)
|
||||
│ │
|
||||
├─ 写功能代码 ←→ ├─ 配 CI/CD 流水线(自动构建、自动部署)
|
||||
├─ 本地调通 ←→ ├─ 配测试环境(staging)让你先验证
|
||||
├─ 写接口文档 ←→ ├─ 配网关/路由/负载均衡
|
||||
├─ 写单元测试 ←→ ├─ 写监控规则、告警阈值
|
||||
└─ 代码合并到主分支 ←→ └─ 上线、回滚、值班
|
||||
```
|
||||
|
||||
现代常说的 **DevOps** 就是把这两只手绑在一起:开发也要懂怎么部署,运维也要懂代码里埋的探针。
|
||||
|
||||
### 2.3 没有“专职运维”时,这些活谁来干
|
||||
|
||||
小团队(比如这个项目目前的状态)通常没有独立的运维工程师,这时工作会分摊给:
|
||||
|
||||
- **开发自己**:配 Docker、写 `docker-compose.yml`、看日志排查线上 bug。
|
||||
- **项目负责人/你自己**:买服务器、配域名 HTTPS、决定什么时候升级 Node 版本。
|
||||
- **云服务商托管**:比如用 Vercel 部署前端、用 Cloud Run / 阿里云函数计算 跑后端,省掉一部分基础设施运维。
|
||||
|
||||
---
|
||||
|
||||
## 三、用这张图记住重点
|
||||
|
||||
```
|
||||
需求 → 开发 → 构建 → 部署 → 运行 → 监控 → 维护 → 回到需求
|
||||
↑___________________________________________↓
|
||||
(开发闭环:迭代)
|
||||
|
||||
需求 → 开发 = 产品经理 + 程序员
|
||||
构建 → 部署 = 构建工具(Docker、Vite)+ 发布脚本
|
||||
运行 → 维护 = 运维(或全栈开发者兼任)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、下一步你可能想问的(预告)
|
||||
|
||||
| 如果你想问 | 我可以写的大概内容 |
|
||||
|-----------|------------------|
|
||||
| "这个项目具体怎么搭测试环境?" | 本地 vs Docker 两种跑法,数据卷挂载 |
|
||||
| "我需要学哪些运维命令?" | `docker`、`docker compose`、`htop`、`curl`、`journalctl`、`tail -f` 等结合本项目场景 |
|
||||
| "上线前要做哪些检查清单?" | 一份针对 WordCloud 的 pre-deployment checklist |
|
||||
| "前端和后端是怎么联调的,接口崩了怎么看日志?" | runbook,按步骤排查 |
|
||||
|
||||
你只管问,我问完就写成文档。
|
||||
@@ -0,0 +1,26 @@
|
||||
# WordCloud 运维学习区
|
||||
|
||||
这里放**运维 / 迭代 / 维护**相关的教学文档。
|
||||
|
||||
## 怎么用
|
||||
|
||||
1. 你在对话里提问。
|
||||
2. 我把答案写成 `learning/` 下的 Markdown(不主要靠聊天讲完)。
|
||||
3. 新文档会出现在下面的目录里。
|
||||
|
||||
**不会**提前批量写一堆你没问过的课。
|
||||
|
||||
产品行为仍以 `docs/` 与代码为准;本目录只是问答沉淀。
|
||||
|
||||
## 目录
|
||||
|
||||
| 文档 | 来自什么问题 |
|
||||
|------|----------------|
|
||||
| [01-project-lifecycle-and-operations.md](01-project-lifecycle-and-operations.md) | 传统项目开发有哪些部分,运维是干什么的 |
|
||||
|
||||
## 子目录
|
||||
|
||||
- `cheatsheets/` — 命令速查
|
||||
- `runbooks/` — 故障时按步骤做的手册
|
||||
- [02-safe-update-data-preservation-runbook.md](runbooks/02-safe-update-data-preservation-runbook.md) — 如何在升级版本时保留数据、实现受控更新
|
||||
- `workshops/` — 稍长的专题(有需要再加)
|
||||
@@ -0,0 +1,306 @@
|
||||
# 受控更新:如何在升级版本时保留数据(runbook)
|
||||
|
||||
> 对应你的问题:**“给我一个能够保留数据来更新的技术文档,从而实现运维的可控”**
|
||||
> 一句话目标:**升级代码、重建容器、打补丁时,项目所有的用户数据都不丢、可回滚、可验证。**
|
||||
|
||||
这份文档是“按步骤照着做”的故障/操作手册。不是空谈原则,是每一步都有命令、都有验证点。
|
||||
|
||||
---
|
||||
|
||||
## 0. 先说结论:这个项目现在更新会丢什么
|
||||
|
||||
当前(2026-08-13)数据分两块:
|
||||
|
||||
| 数据 | 存放位置 | Docker 里会不会丢 |
|
||||
|---|---|---|
|
||||
| 任务产物(输入/输出/配置) | `backend/service_workspace/` | ✅ 已挂卷 `wordcloud_workspace`,不丢 |
|
||||
| 贴纸/素材 | `backend/service_assets/` | ✅ 已挂卷 `wordcloud_assets`,不丢 |
|
||||
| 工程项目 | `backend/service_projects/` | ✅ 已挂卷 `wordcloud_projects`,不丢 |
|
||||
| 设计模板 | `backend/service_design_templates/` | ✅ 已挂卷 `wordcloud_design_templates`,不丢 |
|
||||
| 上传字体 | `backend/service_fonts/` | ✅ 已挂卷 `wordcloud_fonts`,不丢 |
|
||||
| **任务元数据(SQLite)** | `backend/service_metadata/app.db` | ⚠️ **没挂卷,容器重建即丢** |
|
||||
| **订单数据** | `backend/service_orders/` | ⚠️ **没挂卷,容器重建即丢** |
|
||||
|
||||
⚠️ 红色标记的两项是**当前最要紧的缺口**:
|
||||
|
||||
- `service_metadata/app.db` 是任务状态/事件的权威来源(`metadata_store.py`)。
|
||||
它写进了容器的可写层,不在任何 named volume 里。
|
||||
`docker compose up -d --build`(重打镜像、重建容器)时,这层会被清空 → **任务记录清零**。
|
||||
- `service_orders/` 是新增的运行时目录,同样没进卷。
|
||||
|
||||
其余 5 个目录因为有 named volume,容器重建时数据还在。
|
||||
|
||||
> 所以“保留数据来更新”的第一步不是写代码,是先**确认要保的数据都在卷里/都有备份**。
|
||||
> 光指望 `docker compose down` 不删数据是不够的——重建容器才危险,而更新几乎必然会重建。
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据全景:更新前必须认识的东西
|
||||
|
||||
### 1.1 文件数据(前端/后端所有运行时产物)
|
||||
|
||||
```text
|
||||
backend/
|
||||
├─ service_workspace/ # 任务目录,每个 job 一个 {job_id}/input|output|配置
|
||||
├─ service_assets/ # 素材库,每个 {asset_id}/asset.* + meta.json
|
||||
├─ service_projects/ # 工程项目
|
||||
├─ service_design_templates/ # 设计模板 JSON
|
||||
├─ service_fonts/ # 上传字体
|
||||
├─ service_metadata/app.db # 业务元数据(SQLite:jobs / job_events 两张表)★ 易丢
|
||||
└─ service_orders/ # 订单数据 ★ 易丢
|
||||
```
|
||||
|
||||
关键点:
|
||||
- 产物文件(SVG/PNG/字体/Excel)存文件系统,**不是**数据库里。
|
||||
- 一份任务结果有时自带一个 `word_locations.db`(词云算法自己的 SQLite 结果文件),在那任务目录里,跟 `service_metadata/app.db` 不是一回事。
|
||||
|
||||
### 1.2 元数据(数据库)
|
||||
|
||||
`service_metadata/app.db` 是 `metadata_store.py` 生成的 SQLite 单文件:
|
||||
|
||||
```text
|
||||
表 jobs — 任务主表(job_id、status、stage、progress、params、artifacts)
|
||||
表 job_events — 任务进度事件(SSE 落库,重启可恢复)
|
||||
```
|
||||
|
||||
这文件很小(几百 KB 级),但它是“任务可恢复”的关键。**更新时必须单独备份。**
|
||||
|
||||
### 1.3 不算项目数据的(别备份、也不要提交)
|
||||
|
||||
```text
|
||||
backend/.venv/ # 本地 Python 虚拟环境
|
||||
frontend/node_modules/ # npm 依赖
|
||||
backend/.runtime/ # 运行时日志(可丢,日志另按需归档)
|
||||
*.so / dist/ # 编译产物,重新构建即可
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 受控更新流程(四条主线)
|
||||
|
||||
无论本地还是 Docker,都按 **备 → 查 → 更 → 验 → 回** 五步走:
|
||||
|
||||
```
|
||||
① 备份数据 → ② 记录当前版本 → ③ 升级 → ④ 验证 → ⑤(出问题)回滚
|
||||
```
|
||||
|
||||
下面的命令直接复制可用。
|
||||
|
||||
---
|
||||
|
||||
## 3. 第一步:备份(更新前必做,最好 5~10 分钟搞定)
|
||||
|
||||
### 3.1 备份文件数据
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行,生成带时间戳的备份包
|
||||
TS=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_ROOT=$(pwd)/.backups
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
|
||||
tar -czf "$BACKUP_ROOT/wordcloud-data-$TS.tar.gz" \
|
||||
-C backend \
|
||||
service_workspace service_assets service_projects \
|
||||
service_design_templates service_fonts service_metadata service_orders
|
||||
```
|
||||
|
||||
### 3.2 单独稳妥备份 SQLite 元数据库(推荐用 sqlite 的在线备份,避免写一半)
|
||||
|
||||
```bash
|
||||
sqlite3 backend/service_metadata/app.db ".backup '.backups/metadata-$TS.db'"
|
||||
```
|
||||
|
||||
> 如果服务正在跑,用 `.backup` 而不是直接 `cp`,因为 `cp` 可能拷到写一半的文件。
|
||||
|
||||
### 3.3 校验备份真的可读(光备份不看等于没备)
|
||||
|
||||
```bash
|
||||
tar -tzf "$BACKUP_ROOT/wordcloud-data-$TS.tar.gz" | wc -l # 能看到条数
|
||||
sqlite3 "$BACKUP_ROOT/metadata-$TS.db" "select count(*) from jobs;" # 能查到任务数
|
||||
```
|
||||
|
||||
### 3.4 本地开发:备份产物
|
||||
|
||||
本地数据直接躺在 `backend/` 下,copy 出去即可:
|
||||
|
||||
```bash
|
||||
cp -r backend/service_workspace backend/service_assets ~/wcd-backup-$TS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 第二步:记录当前版本(为了能回滚)
|
||||
|
||||
更新前记下“现在跑的是哪一版”,否则回滚都不知道回哪。
|
||||
|
||||
- Docker:记镜像 Digest 或 Tag
|
||||
```bash
|
||||
docker inspect wordcloud-backend --format '{{.Image}} {{.Config.Image}}'
|
||||
docker image ls | grep backend
|
||||
```
|
||||
- 代码:记 commit
|
||||
```bash
|
||||
git rev-parse HEAD
|
||||
```
|
||||
|
||||
把这些写进 `.backups/UPDATE-LOG-$TS.txt`:
|
||||
```text
|
||||
更新前 commit: abc1234
|
||||
更新前镜像: wordcloud-backend:latest (digest sha256:...)
|
||||
更新时备份包: wordcloud-data-20260813_1015.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 第三步:升级
|
||||
|
||||
### 5.1 Docker 部署(最常见,也最危险)
|
||||
|
||||
```bash
|
||||
# 拉/构建新镜像并重建容器
|
||||
docker compose up -d --build
|
||||
|
||||
# 容器重建后,立刻(不要拖)确认关键接口活着
|
||||
curl -fsS http://localhost:8000/api/health
|
||||
curl -fsS http://localhost:8000/api/maintenance/storage-summary | jq '{jobs_in_db, events_in_db}'
|
||||
```
|
||||
|
||||
> `jobs_in_db` 应该是更新前的数字——如果变成 0,说明 `service_metadata` 没进卷、被重建清掉了 → 走“恢复”那一步。
|
||||
|
||||
### 5.2 本地开发
|
||||
|
||||
```bash
|
||||
cd backend && ./start-dev.sh # 会重新编译 C++、起 uvicorn
|
||||
# 或前端
|
||||
cd frontend && npm install && npm run dev
|
||||
```
|
||||
|
||||
本地 `uvicorn --reload` 重建的是代码不是数据目录,一般不丢数据,但仍建议更新大改动前备份。
|
||||
|
||||
---
|
||||
|
||||
## 6. 第四步:验证(更新的“可控”就体现在这一步)
|
||||
|
||||
升级完不是“能打开首页”就完事,要验证**数据层**:
|
||||
|
||||
```bash
|
||||
# 1) 服务健康
|
||||
curl -fsS http://localhost:8000/api/health
|
||||
|
||||
# 2) 数据还在(拿更新前记录的 baseline 对比)
|
||||
curl -fsS http://localhost:8000/api/maintenance/storage-summary \
|
||||
| jq '{job_dir_count, referenced_job_ids, jobs_in_db, events_in_db, reclaimable_bytes}'
|
||||
|
||||
# 3) 素材/列表有东西(随便挑一个列表接口)
|
||||
curl -fsS http://localhost:8000/api/assets | jq 'length'
|
||||
|
||||
# 4) 能真正跑一个最小词云任务(端到端冒烟)
|
||||
curl -fsS http://localhost:8000/api/jobs -X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"text":"hello world","shape":"rectangle"}' \
|
||||
&& sleep 3 && curl -fsS http://localhost:8000/api/jobs | jq '.[0]'
|
||||
```
|
||||
|
||||
**通过的标准**(缺一不可):
|
||||
- `jobs_in_db` / `events_in_db` > 0,且与更新前基线一致或更高。
|
||||
- 素材列表数量没掉。
|
||||
- 冒烟任务能 success。
|
||||
|
||||
---
|
||||
|
||||
## 7. 第五步:回滚(更新失败时)
|
||||
|
||||
更新最怕“上了下不来”。回滚分两个层面,先回数据、再回代码。
|
||||
|
||||
### 7.1 Docker:切回旧镜像 + 恢复数据
|
||||
|
||||
```bash
|
||||
# 1) 停掉当前
|
||||
docker compose down
|
||||
|
||||
# 2) 恢复文件数据(把备份解开到已挂卷的位置)
|
||||
# 注意:named volume 可以通过临时容器写回,例如恢复 service_assets:
|
||||
docker run --rm \
|
||||
-v wordcloud_assets:/target \
|
||||
-v "$(pwd)/.backups":/bak \
|
||||
alpine sh -c 'rm -rf /target/* && tar -xzf /bak/wordcloud-data-XXX.tar.gz -C /target service_assets 2>/dev/null; echo done'
|
||||
|
||||
# 3) 恢复 SQLite(如果它丢了)
|
||||
cp .backups/metadata-$TS.db backend/service_metadata/app.db # 本地路径法
|
||||
# 容器内恢复:挂载校验后重建容器时会重新读 /app/service_metadata/app.db
|
||||
|
||||
# 4) 用旧镜像/旧 tag 拉起
|
||||
docker compose up -d --build # 或 docker compose pull 旧 tag 后 up
|
||||
```
|
||||
|
||||
> 更省事、更可靠的开销小做法:**更新前对卷打快照**。
|
||||
> 现在有组件还进不了卷时,快照是唯一能把它们一起保住的兜底。
|
||||
|
||||
### 7.2 代码回滚
|
||||
|
||||
```bash
|
||||
git checkout <更新前commit>
|
||||
# 或不改代码,直接复用旧镜像 tag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 把“易丢数据”变成“永不丢”的根治建议
|
||||
|
||||
上面 runbook 是“治标”——每次更新都备份。要“治本”,把下面两件事做了,第 0 节的红色缺口就消失:
|
||||
|
||||
### 8.1 让 `service_metadata` 和 `service_orders` 进 Docker 卷
|
||||
|
||||
在 `docker-compose.yml` 的 `backend` volumes 里加两行,并在 `volumes:` 声明:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
volumes:
|
||||
- wordcloud_workspace:/app/service_workspace
|
||||
- wordcloud_assets:/app/service_assets
|
||||
- wordcloud_projects:/app/service_projects
|
||||
- wordcloud_design_templates:/app/service_design_templates
|
||||
- wordcloud_fonts:/app/service_fonts
|
||||
- wordcloud_metadata:/app/service_metadata # ← 新增
|
||||
- wordcloud_orders:/app/service_orders # ← 新增
|
||||
|
||||
volumes:
|
||||
wordcloud_workspace:
|
||||
wordcloud_assets:
|
||||
wordcloud_projects:
|
||||
wordcloud_design_templates:
|
||||
wordcloud_fonts:
|
||||
wordcloud_metadata: # ← 新增
|
||||
wordcloud_orders: # ← 新增
|
||||
```
|
||||
|
||||
> 注意:`service_metadata` 当前安装在容器的 `/app/service_metadata`(`PROJECT_ROOT` 是 `/app`)。
|
||||
> 加上卷后,`app.db` 会持久化到卷,容器重建不再清零。
|
||||
|
||||
### 8.2 定期备份(不只在更新时才备)
|
||||
|
||||
加一个 cron(示例每天 02:17,避开整点):
|
||||
|
||||
```cron
|
||||
17 2 * * * cd /path/to/wordcloud && ./scripts/backup-data.sh
|
||||
```
|
||||
|
||||
配套的 `scripts/backup-data.sh` 只需做第 3 步那三件事:打包文件 → sqlite `.backup` → 校验。
|
||||
|
||||
---
|
||||
|
||||
## 9. 更新风暴口诀
|
||||
|
||||
**更新五步:备份 → 记版本 → 升级 → 验证数据 → 能回滚。**
|
||||
**最危险的不是 `docker compose down`,而是重建容器;重建前看卷、重建后看 `jobs_in_db`。**
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关文件
|
||||
|
||||
- `docker-compose.yml`:数据卷挂载(改这里补缺口)
|
||||
- `backend/service/metadata_store.py`:`service_metadata/app.db` 的来源
|
||||
- `backend/service/storage_metrics.py`:任务清理审计(dry-run → `--apply`)
|
||||
- `docs/DEPLOYMENT.md`:部署与产物目录清单
|
||||
- `docs/DATA_STORAGE_OPTIMIZATION.md`:任务存储现状与清理策略
|
||||
Reference in New Issue
Block a user