Files
2026-moving-helper/README.md
T
2026-04-19 14:28:00 +02:00

443 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Moving Helper
这是一个面向可信家庭内网环境的小型搬家记录工具,当前采用轻量技术栈:
- FastAPI
- Jinja2 服务端渲染
- SQLAlchemy
- SQLite
- Pillow
- pytest / FastAPI TestClient
- Docker / Docker Compose
项目目标是小而稳、容易继续扩展。它不是企业平台,也不是复杂运维系统,重点是本地开发简单、容器部署稳定、数据持久化清楚、后续几个月后自己回来看也能快速接上。
## 当前已支持
- 固定 3 级结构:`Box -> Item -> SubItem`
- Box / Item / SubItem 基础 CRUD
- Box / Item / SubItem 单图上传、替换、删除、展示
- Box / Item / SubItem 全局搜索
- Docker / Compose 长期运行
- SQLite 数据持久化
- 基础自动化测试
## 当前数据模型
这个项目不是无限树结构,而是固定最多 3 级:
- `Box`
- `Item`
- `SubItem`
关系如下:
- 一个 `Box` 包含多个 `Item`
- 一个 `Item` 属于一个 `Box`
- `Item` 通过 `is_container` 区分是否为“小容器”
- 只有 `is_container = true``Item` 才允许拥有 `SubItem`
- `SubItem` 是最后一级,不允许继续向下嵌套
结构固定为:
```text
Box
└── Item
└── SubItem
```
## 图片能力说明
图片系统保持简单直接:
- `Box` 最多支持 1 张图片
- `Item` 最多支持 1 张图片
- `SubItem` 最多支持 1 张图片
- 支持上传、替换、删除
- 不支持多图
图片主要用于帮助识别物品、提高浏览效率、方便手机拍照后直接附加到记录中。它不是原图归档系统。
上传图片后,系统会使用 Pillow 做统一处理:
- 读取上传图片
- 去除 EXIF 元数据
- 转换为 JPEG
- 按最长边缩放到不超过 `1600px`
- 使用约 `80` 质量保存
- 将处理后的 JPEG 二进制直接写入 SQLite `BLOB`
同时还会记录:
- `image_mime_type`
- `image_width`
- `image_height`
图片访问路由例如:
- `/boxes/{id}/image`
- `/items/{id}/image`
- `/subitems/{id}/image`
## 全局搜索
当前支持一个轻量的全局搜索页:
- 路由:`/search`
- 方式:`GET /search?q=关键词`
搜索范围包括:
- `Box.name`
- `Box.note`
- `Item.name`
- `Item.note`
- `SubItem.name`
- `SubItem.note`
当前使用 SQLite 上的简单模糊匹配,不引入外部搜索引擎或复杂全文系统。
搜索结果会显示:
- 对象类型:`Box / Item / SubItem`
- 名称和备注
- 归属路径
-`Item` 展示所属 `Box`
-`SubItem` 展示所属 `Item``Box`
- 如果对象已有图片,会显示一个小缩略图
## 当前未实现
这一阶段仍然没有实现以下内容:
- 多图上传
- OCR
- AI 识别物品
- 图片标签
- 图片分类
- 登录 / 鉴权
- 标签系统
- 前后端分离
- 复杂 UI
## 项目结构
```text
.
├── app
│ ├── __init__.py
│ ├── config.py
│ ├── db.py
│ ├── images.py
│ ├── main.py
│ ├── models.py
│ ├── static
│ │ └── style.css
│ └── templates
├── data
├── scripts
│ ├── backup_db.sh
│ └── deploy.sh
├── tests
├── .dockerignore
├── .env.example
├── docker-compose.yml
├── Dockerfile
├── pytest.ini
├── README.md
└── requirements.txt
```
## 轻量配置
项目通过环境变量支持以下部署时真正需要关心的配置:
- `HOST`
- `PORT`
- `DATABASE_URL`
- `DATA_DIR`
- `UID`
- `GID`
推荐从示例文件开始:
```bash
cp .env.example .env
```
默认值如下:
```env
HOST=0.0.0.0
PORT=10000
DATABASE_URL=sqlite:////app/data/app.db
DATA_DIR=./data
UID=1000
GID=1000
```
说明:
- 本地开发默认数据库仍然是 `./data/app.db`
- Docker 内建议继续使用 `sqlite:////app/data/app.db`
- `DATA_DIR` 控制宿主机上的持久化目录
- `UID/GID` 用来让容器内文件权限更贴近宿主机用户
## 本地开发模式
推荐使用本地 Python `venv` 开发和调试。
### 1. 创建虚拟环境
```bash
python3 -m venv .venv
source .venv/bin/activate
```
### 2. 安装依赖
```bash
pip install -r requirements.txt
```
### 3. 启动开发服务器
```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 10000
```
访问:
```text
http://localhost:10000
```
本地默认数据库位置:
```text
./data/app.db
```
## Docker 运行方式
Docker / Compose 是这个项目面向长期运行环境的方式。
### 首次准备
```bash
cp .env.example .env
mkdir -p data
```
### 启动 / 更新
```bash
docker compose up -d --build
```
### 查看状态
```bash
docker compose ps
```
### 查看日志
```bash
docker compose logs -f web
```
访问:
```text
http://localhost:10000
```
### Compose 配置说明
当前 `docker-compose.yml` 保持尽量简单:
- 默认暴露 `10000` 端口
- `restart: unless-stopped`
- 容器用户来自 `UID:GID`
- 宿主机 `DATA_DIR` 挂载到容器内 `/app/data`
- SQLite 默认写入 `/app/data/app.db`
## 自动化部署
这个项目没有复杂 CI/CD,只提供一个适合家用项目的轻量部署脚本:
```bash
./scripts/deploy.sh
```
它会按顺序执行:
1. 检查 `.env`
2. 准备数据目录
3. 如果当前目录是 git 仓库,执行 `git pull --ff-only`
4. 执行 `docker compose up -d --build`
5. 输出容器状态
6. 输出最近日志
这个脚本的目标不是做平台化发布,而是让“更新代码并刷新容器”变成一个稳定、可重复执行的动作。
如果你不想自动拉代码,也可以直接手动运行:
```bash
docker compose up -d --build
```
## 数据持久化
当前 SQLite 文件默认会保存在:
- 宿主机:`./data/app.db`
- 容器内:`/app/data/app.db`
这是因为 `docker-compose.yml` 把:
```text
${DATA_DIR:-./data}
```
挂载到了容器内:
```text
/app/data
```
因此:
- 容器重建不会删除宿主机上的数据库文件
- 更新镜像不会导致 SQLite 数据丢失
- 只要保留 `DATA_DIR` 目录,数据就还在
## 备份与恢复
### 最简单的备份方式
直接复制 SQLite 文件即可:
```bash
cp data/app.db backups/app.db
```
或者使用附带脚本:
```bash
./scripts/backup_db.sh
```
它会在 `backups/` 目录下生成一个带时间戳的副本。
### 最简单的恢复方式
停止容器后,把备份文件覆盖回去:
```bash
docker compose stop
cp backups/app-YYYYMMDD-HHMMSS.db data/app.db
docker compose up -d
```
## 常见排查
### 1. 查看容器日志
```bash
docker compose logs -f web
```
### 2. 确认服务是否已启动
```bash
docker compose ps
```
如果状态是 `Up`,通常说明容器已经跑起来了。
### 3. 确认端口映射
```bash
docker compose port web 10000
```
### 4. 确认数据库文件还在
```bash
ls -lh data
```
如果看到 `app.db`,说明宿主机持久化文件还在。
### 5. 容器更新后数据为什么没丢
因为数据库不放在镜像里,而是放在宿主机挂载目录 `DATA_DIR` 中。
镜像更新只会替换应用代码和运行环境,不会覆盖这个宿主机目录。
## 测试
运行测试:
```bash
python -m pytest
```
测试使用独立测试数据库,不会污染真实开发数据。
当前测试覆盖包括:
- Box / Item / SubItem 基础 CRUD
- 图片上传、替换、删除与错误路径
- 全局搜索 name / note
- 创建后的重定向行为
- 关键页面结构和 UX 文案
## 一次性 Notion 导入
项目内附带了一个一次性迁移脚本:
```bash
python scripts/import_notion.py --dry-run
python scripts/import_notion.py --apply
```
说明:
- 这是一次性 migration / import 工具,不是长期同步功能
- 运行时会交互要求输入:
- Notion API token
- Notion 页面完整 URL
- `--dry-run` 只读取和解析,不写数据库
- `--apply` 会真正写入当前 SQLite 数据库
- 建议导入前先备份 `data/app.db`
### 当前支持的 Notion 结构映射
- `heading_2` -> `Box`
- 某个 `heading_2` 下的一级 bullet -> `Item`
- 如果一级 bullet 下还有二级 bullet
- 一级 bullet -> 容器型 `Item`
- 二级 bullet -> `SubItem`
当前最大只处理到这个层级:
```text
heading_2
└── 一级 bullet
└── 二级 bullet
```
更深层级会在日志中提示,但不会继续扩展成无限树。
### 这一版不导入图片
这一版导入脚本:
- 不下载图片
- 不导入图片
- 遇到图片或其他媒体 block 时会提示已跳过
图片后续可以在应用里手动补录。