Files
2026-moving-helper/README.md
T
tliu93 eb29f03b74
test / pytest (push) Successful in 38s
add compose file for pulling
2026-04-21 22:01:23 +02:00

11 KiB
Raw Blame History

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 = trueItem 才允许拥有 SubItem
  • SubItem 是最后一级,不允许继续向下嵌套

结构固定为:

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 展示所属 ItemBox
  • 如果对象已有图片,会显示一个小缩略图

当前未实现

这一阶段仍然没有实现以下内容:

  • 多图上传
  • OCR
  • AI 识别物品
  • 图片标签
  • 图片分类
  • 登录 / 鉴权
  • 标签系统
  • 前后端分离
  • 复杂 UI

项目结构

.
├── 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

推荐从示例文件开始:

cp .env.example .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. 创建虚拟环境

python3 -m venv .venv
source .venv/bin/activate

2. 安装依赖

pip install -r requirements.txt

3. 启动开发服务器

uvicorn app.main:app --reload --host 0.0.0.0 --port 10000

访问:

http://localhost:10000

本地默认数据库位置:

./data/app.db

Docker 运行方式

Docker / Compose 是这个项目面向长期运行环境的方式。

当前 compose 同时保留了:

  • image:固定指向 code.wanderingbadger.dev/tliu93/2026-moving-helper:latest
  • build:用于本地开发时从当前代码构建镜像

首次准备

cp .env.example .env
mkdir -p data

启动 / 更新:本地代码构建

docker compose up -d --build

这个模式会使用当前仓库代码重新构建镜像,适合本地开发、调试或尚未发布 tag 的阶段。

启动 / 更新:直接拉取已发布镜像

docker compose pull
docker compose up -d

查看状态

docker compose ps

查看日志

docker compose logs -f web

访问:

http://localhost:10000

Compose 配置说明

当前 docker-compose.yml 保持尽量简单:

  • 默认镜像地址来自 REGISTRY_HOST / IMAGE_NAME / IMAGE_TAG
  • 默认暴露 10000 端口
  • restart: unless-stopped
  • 容器用户来自 UID:GID
  • 宿主机 DATA_DIR 挂载到容器内 /app/data
  • SQLite 默认写入 /app/data/app.db

因此同一个 compose 文件可以覆盖两种使用方式:

  • 本地开发容器:docker compose up -d --build
  • 远端部署发布镜像:docker compose pull && docker compose up -d

自动化部署

这个项目没有复杂 CI/CD,只提供一个适合家用项目的轻量部署脚本:

./scripts/deploy.sh

它会按顺序执行:

  1. 检查 .env
  2. 准备数据目录
  3. 如果当前目录是 git 仓库,执行 git pull --ff-only
  4. 执行 docker compose up -d --build
  5. 输出容器状态
  6. 输出最近日志

这个脚本的目标不是做平台化发布,而是让“更新代码并刷新容器”变成一个稳定、可重复执行的动作。

如果你不想自动拉代码,也可以直接手动运行:

docker compose up -d --build

数据持久化

当前 SQLite 文件默认会保存在:

  • 宿主机:./data/app.db
  • 容器内:/app/data/app.db

这是因为 docker-compose.yml 把:

${DATA_DIR:-./data}

挂载到了容器内:

/app/data

因此:

  • 容器重建不会删除宿主机上的数据库文件
  • 更新镜像不会导致 SQLite 数据丢失
  • 只要保留 DATA_DIR 目录,数据就还在

备份与恢复

最简单的备份方式

直接复制 SQLite 文件即可:

cp data/app.db backups/app.db

或者使用附带脚本:

./scripts/backup_db.sh

它会在 backups/ 目录下生成一个带时间戳的副本。

最简单的恢复方式

停止容器后,把备份文件覆盖回去:

docker compose stop
cp backups/app-YYYYMMDD-HHMMSS.db data/app.db
docker compose up -d

常见排查

1. 查看容器日志

docker compose logs -f web

2. 确认服务是否已启动

docker compose ps

如果状态是 Up,通常说明容器已经跑起来了。

3. 确认端口映射

docker compose port web 10000

4. 确认数据库文件还在

ls -lh data

如果看到 app.db,说明宿主机持久化文件还在。

5. 容器更新后数据为什么没丢

因为数据库不放在镜像里,而是放在宿主机挂载目录 DATA_DIR 中。 镜像更新只会替换应用代码和运行环境,不会覆盖这个宿主机目录。

测试

运行测试:

python -m pytest

测试使用独立测试数据库,不会污染真实开发数据。

当前测试覆盖包括:

  • Box / Item / SubItem 基础 CRUD
  • 图片上传、替换、删除与错误路径
  • 全局搜索 name / note
  • 创建后的重定向行为
  • 关键页面结构和 UX 文案

CI / CD

仓库现在包含两条基础自动化流程,文件位于:

  • .github/workflows/test.yml
  • .github/workflows/docker-image.yml

CIbranch push 自动跑 pytest

test.yml 会在任意 branch 的 push 上执行:

  1. checkout 代码
  2. 使用 Python 3.12
  3. 安装 requirements.txt
  4. 运行 pytest

当前测试不依赖外部数据库服务。

测试使用 tmp_path 创建独立 SQLite 文件,并通过 configure_database(...) 切换到临时数据库,因此:

  • 不会污染 ./data/app.db
  • 不要求额外启动 Docker 或 Compose
  • 不要求额外配置测试环境变量

CDtag 发布 Docker image

docker-image.yml 会在推送符合 v* 格式的 tag 时触发,例如:

  • v1.0.0
  • v1.2.3

workflow 会先校验该 tag 指向的提交是否可从 origin/main 到达;只有满足这个条件的 tag 才会继续构建并推送镜像。

镜像发布目标:

  • Registry Host: code.wanderingbadger.dev
  • Image Name: ${{ github.repository }}
  • Platforms:
    • linux/amd64
    • linux/arm64

推送的 tag 策略:

  • ${tag}
  • latest

例如仓库名为 tliu93/2026-moving-helper,打出 v1.0.0 后会推送:

code.wanderingbadger.dev/tliu93/2026-moving-helper:v1.0.0
code.wanderingbadger.dev/tliu93/2026-moving-helper:latest

Actions / Gitea Secrets

需要在仓库的 Actions secrets 中配置:

  • REGISTRY_USERNAME
  • REGISTRY_TOKEN

推荐含义:

  • REGISTRY_USERNAME: Gitea 用户名
  • REGISTRY_TOKEN: 具备 Container Registry 推送权限的 Access Token

如果你的 Gitea 实例对 package / registry 权限做了单独控制,确保这个 token 至少具备对应仓库的镜像推送权限。

如何触发镜像发布

建议流程:

git checkout main
git pull --ff-only origin main
git tag v1.0.0
git push origin main --tags

如果只想推送单个 tag

git push origin v1.0.0

本地手动构建镜像

单架构本地构建:

docker build -t moving-helper:local .

本地运行:

docker run --rm -p 10000:10000 \
  -e DATABASE_URL=sqlite:////app/data/app.db \
  moving-helper:local

如果要模拟发布时的多架构构建,可以使用 buildx:

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t code.wanderingbadger.dev/${USER}/2026-moving-helper:test \
  --load \
  .

一次性 Notion 导入

项目内附带了一个一次性迁移脚本:

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

当前最大只处理到这个层级:

heading_2
└── 一级 bullet
    └── 二级 bullet

更深层级会在日志中提示,但不会继续扩展成无限树。

这一版不导入图片

这一版导入脚本:

  • 不下载图片
  • 不导入图片
  • 遇到图片或其他媒体 block 时会提示已跳过

图片后续可以在应用里手动补录。