add temporary deploy

This commit is contained in:
2026-04-19 13:33:43 +02:00
parent 314fc16b98
commit e7a2719fa1
7 changed files with 286 additions and 69 deletions
+9
View File
@@ -0,0 +1,9 @@
.git
.gitignore
.pytest_cache
.venv
__pycache__
*.pyc
data
tests
+14
View File
@@ -0,0 +1,14 @@
# Runtime
HOST=0.0.0.0
PORT=10000
# In Docker, keep the database inside the mounted /app/data directory.
DATABASE_URL=sqlite:////app/data/app.db
# Host-side persistent data directory
DATA_DIR=./data
# Container user mapping
UID=1000
GID=1000
-1
View File
@@ -13,7 +13,6 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
COPY tests ./tests
RUN mkdir -p /app/data
+194 -64
View File
@@ -10,7 +10,17 @@
- pytest / FastAPI TestClient
- Docker / Docker Compose
项目目标是小而稳、容易继续扩展。目前已经支持固定三层的数据结构、基础 CRUD、单图上传能力和全局搜索,但仍然没有加入 OCR、AI 识别或其他扩展功能
项目目标是小而稳、容易继续扩展。它不是企业平台,也不是复杂运维系统,重点是本地开发简单、容器部署稳定、数据持久化清楚、后续几个月后自己回来看也能快速接上
## 当前已支持
- 固定 3 级结构:`Box -> Item -> SubItem`
- Box / Item / SubItem 基础 CRUD
- Box / Item / SubItem 单图上传、替换、删除、展示
- Box / Item / SubItem 全局搜索
- Docker / Compose 长期运行
- SQLite 数据持久化
- 基础自动化测试
## 当前数据模型
@@ -36,30 +46,9 @@ Box
└── SubItem
```
## 当前已支持
目前已支持的基础能力:
- Box 列表、详情、新建、编辑、删除
- Item 新建、详情、编辑、删除
- SubItem 新建、编辑、删除
- Box / Item / SubItem 单张图片上传、替换、删除、展示
- Box / Item / SubItem 全局搜索
- `/` 重定向到 `/boxes`
- Jinja2 模板渲染
- 静态文件挂载
- SQLite 持久化
- Docker 长期运行
- 基础自动化测试
删除规则:
- 删除 `Box` 时,会级联删除其下全部 `Item` 和对应 `SubItem`
- 删除容器型 `Item` 时,会级联删除其下 `SubItem`
## 图片能力说明
这一阶段的图片系统保持简单直接:
图片系统保持简单直接:
- `Box` 最多支持 1 张图片
- `Item` 最多支持 1 张图片
@@ -67,10 +56,7 @@ Box
- 支持上传、替换、删除
- 不支持多图
图片主要用途是帮助识别物品、提高浏览效率、方便手机拍照后直接附加到记录中。
它不是一个原图归档系统。
### 图片处理方式
图片主要用帮助识别物品、提高浏览效率、方便手机拍照后直接附加到记录中。它不是原图归档系统。
上传图片后,系统会使用 Pillow 做统一处理:
@@ -87,7 +73,7 @@ Box
- `image_width`
- `image_height`
图片访问通过普通 HTTP 路由返回 JPEG 数据,例如:
图片访问路由例如:
- `/boxes/{id}/image`
- `/items/{id}/image`
@@ -95,10 +81,10 @@ Box
## 全局搜索
当前已经支持一个轻量的全局搜索页:
当前支持一个轻量的全局搜索页:
- 路由:`/search`
- 使用 query parameter,例如:`/search?q=电源线`
- 方式:`GET /search?q=关键词`
搜索范围包括:
@@ -109,13 +95,13 @@ Box
- `SubItem.name`
- `SubItem.note`
当前使用 SQLite 上的简单模糊匹配完成搜索,不引入外部搜索引擎或复杂全文系统。
当前使用 SQLite 上的简单模糊匹配,不引入外部搜索引擎或复杂全文系统。
搜索结果会尽量帮助你快速定位
搜索结果会显示
- 显示对象类型:`Box / Item / SubItem`
- 显示名称和备注
- 显示归属路径
- 对象类型:`Box / Item / SubItem`
- 名称和备注
- 归属路径
-`Item` 展示所属 `Box`
-`SubItem` 展示所属 `Item``Box`
- 如果对象已有图片,会显示一个小缩略图
@@ -124,7 +110,6 @@ Box
这一阶段仍然没有实现以下内容:
- 搜索
- 多图上传
- OCR
- AI 识别物品
@@ -149,14 +134,13 @@ Box
│ ├── static
│ │ └── style.css
│ └── templates
│ ├── base.html
│ ├── boxes
│ ├── items
│ └── subitems
├── data
├── scripts
│ ├── backup_db.sh
│ └── deploy.sh
├── tests
│ ├── conftest.py
│ └── test_app.py
├── .dockerignore
├── .env.example
├── docker-compose.yml
├── Dockerfile
├── pytest.ini
@@ -166,17 +150,38 @@ Box
## 轻量配置
项目通过环境变量支持以下配置
项目通过环境变量支持以下部署时真正需要关心的配置:
- `DATABASE_URL`
- `HOST`
- `PORT`
- `DATABASE_URL`
- `DATA_DIR`
- `UID`
- `GID`
默认值
推荐从示例文件开始
- `DATABASE_URL=sqlite:///./data/app.db`
- `HOST=0.0.0.0`
- `PORT=10000`
```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` 用来让容器内文件权限更贴近宿主机用户
## 本地开发模式
@@ -213,14 +218,33 @@ http://localhost:10000
./data/app.db
```
## Docker 部署模
## Docker 运行方
Docker / Compose 是这个项目面向长期运行环境的方式。
启动:
### 首次准备
```bash
docker compose up --build
cp .env.example .env
mkdir -p data
```
### 启动 / 更新
```bash
docker compose up -d --build
```
### 查看状态
```bash
docker compose ps
```
### 查看日志
```bash
docker compose logs -f web
```
访问:
@@ -229,20 +253,129 @@ docker compose up --build
http://localhost:10000
```
说明:
### Compose 配置说明
当前 `docker-compose.yml` 保持尽量简单:
- 默认暴露 `10000` 端口
- `restart: unless-stopped`
- 容器使用 `1000:1000` 运行
- SQLite 文件持久化到宿主机 `./data/app.db`
- 容器重建不会丢失数据
- 容器用户来自 `UID:GID`
- 宿主机 `DATA_DIR` 挂载到容器内 `/app/data`
- SQLite 默认写入 `/app/data/app.db`
备份时直接复制 SQLite 文件即可:
## 自动化部署
这个项目没有复杂 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/app.db
${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` 中。
镜像更新只会替换应用代码和运行环境,不会覆盖这个宿主机目录。
## 测试
运行测试:
@@ -256,10 +389,7 @@ python -m pytest
当前测试覆盖包括:
- Box / Item / SubItem 基础 CRUD
- 404 返回
- 非容器 Item 不能创建 SubItem
- Box / Item 删除后的级联删除
- 图片上传、转换为 JPEG、缩放、读取、替换、删除
- 全局搜索 name / note,并展示对象类型与归属路径
- 无图片访问和非法图片上传等错误路径
- 关键 POST 请求后的重定向行为
- 图片上传、替换、删除与错误路径
- 全局搜索 name / note
- 创建后的重定向行为
- 关键页面结构和 UX 文案
+5 -4
View File
@@ -1,14 +1,15 @@
services:
web:
container_name: moving-helper
build:
context: .
user: "1000:1000"
user: "${UID:-1000}:${GID:-1000}"
ports:
- "${PORT:-10000}:${PORT:-10000}"
environment:
HOST: 0.0.0.0
HOST: ${HOST:-0.0.0.0}
PORT: ${PORT:-10000}
DATABASE_URL: sqlite:////app/data/app.db
DATABASE_URL: ${DATABASE_URL:-sqlite:////app/data/app.db}
volumes:
- ./data:/app/data
- ${DATA_DIR:-./data}:/app/data
restart: unless-stopped
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env sh
set -eu
PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$PROJECT_ROOT"
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
echo "未找到 .env,先从 .env.example 复制一份:"
echo " cp .env.example .env"
exit 1
fi
DATA_DIR_VALUE=$(grep '^DATA_DIR=' .env 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
DATA_DIR=${DATA_DIR_VALUE:-./data}
DB_PATH="$DATA_DIR/app.db"
if [ ! -f "$DB_PATH" ]; then
echo "未找到数据库文件:$DB_PATH"
exit 1
fi
mkdir -p backups
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
DESTINATION="backups/app-$TIMESTAMP.db"
cp "$DB_PATH" "$DESTINATION"
echo "备份已创建:$DESTINATION"
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env sh
set -eu
PROJECT_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$PROJECT_ROOT"
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
echo "未找到 .env,先从 .env.example 复制一份:"
echo " cp .env.example .env"
exit 1
fi
DATA_DIR_VALUE=$(grep '^DATA_DIR=' .env 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || true)
DATA_DIR=${DATA_DIR_VALUE:-./data}
mkdir -p "$DATA_DIR"
echo "[1/4] 拉取最新代码(如果当前目录是 git 仓库)"
if [ -d ".git" ]; then
git pull --ff-only
else
echo "跳过:当前目录不是 git 仓库"
fi
echo "[2/4] 构建并更新容器"
docker compose up -d --build
echo "[3/4] 当前容器状态"
docker compose ps
echo "[4/4] 最近日志"
docker compose logs --tail=50 web
echo
echo "部署完成。应用默认地址:"
echo " http://localhost:$(grep '^PORT=' .env 2>/dev/null | tail -n 1 | cut -d '=' -f 2- || echo 10000)"