Files
FuelLog/README.md
T
KennethCheng a2fc215ef9 docs: README 补全 Docker 部署章节(拓扑图 / 镜像要点 / 持久化 / 反代)
上条 commit (846b1a7) 把 deploy/ 目录实际文件都铺好了,但 README 的
"🐳 部署"还停留在"待完成"占位,新成员(以及未来的自己)看到 README
会以为 Docker 还没做。这条 commit 把 README 与实际状态对齐。

新增 / 改动 README.md:
- 仓库结构树:deploy/ 从"(待) docker-compose + nginx"改成
  "Docker 部署文件",列出 4 个子文件
- 替换 "🐳 部署(待完成)" 占位为完整章节:
  * 快速部署:一条 docker compose up -d
  * 部署拓扑:ASCII 图展示 web 容器 (nginx) 反代 /api 到 api 容器
    (GORM + SQLite),数据卷持久化
  * 镜像要点:表格列出 api / web 两个镜像的 base image + 关键配置
    (CGO=0 / tini / 非 root / corepack pnpm / gzip + 30 天 immutable cache)
  * 数据持久化:DB_PATH 路径 / volume 名 / 备份命令(docker run --rm 模板)
  * 反代建议:不要直接 8080 暴露公网,前面挡 NPM / Traefik,符合
    plan §17 "容器内 API 默认只监听内网"
  * 健康检查:API + web 都用 wget,compose 用 condition: service_healthy
  * 开发模式:不想装 Go 也能跑,流程:pnpm build -> docker compose up
  * 链回 deploy/README.md
- 路线图:Phase 5 标 done,Phase 6/7/8 标 pending(对应 Import/Export /
  Auth / 扩展)

未做(留待后续):
- README 截图(Docker 跑起来后再补)
- Traefik / NPM 自动 HTTPS 的具体配置(留给 plan §17 后续)
- 数据备份 cron / 灾备演练

风格保持与之前一致:中文为主,技术名词留英文,emoji 章节标题。
2026-09-13 17:20:50 +08:00

287 lines
11 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.
# FuelLog 加油记
> 轻量、精美、移动端优先的个人加油记录与车辆燃油成本分析工具。
[![Go](https://img.shields.io/badge/Go-1.22%2B-00ADD8)](fuellog-api/go.mod)
[![Vue](https://img.shields.io/badge/Vue-3.5-42b883)](fuellog-web/package.json)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](#license)
---
## ✨ 特性
- **10 秒录入一笔加油**:加油时间 → 里程 → 升数 → 总金额 → 油品 → 加油站 → 加满 → 备注,移动端底部固定保存按钮,实测从打开到保存 < 10s。
- **服务端实时算实际油价**:`price_per_liter = total_amount / liters`,前端只入 amount + liters,前端同步显示"实际油价 ¥X.XX/L"作为 UX 反馈,服务端是权威。
- **标准百公里油耗**:只在"连续两次 `is_full=true`"之间计算,半箱油不污染指标 —— 与 plan §6.3 / §19 严格对齐。
- **每公里成本 + 月度趋势**:Dashboard 主指标 + Statistics 4 张图(油价 / 油耗 / 月度费用 / 每公里成本)。
- **PWA**:可"安装"到手机主屏,离线时已访问页面仍可用;Service Worker 预缓存所有静态资源,API 走 NetworkFirst 兜底 cache。
- **多车辆 + 唯一默认**:可加多辆,默认车辆唯一,切换走事务。
---
## 🛠 技术栈
| 层 | 选型 | 理由 |
|---|---|---|
| 前端 | Vue 3 + Vite + TypeScript + Pinia + Vue Router + Tailwind CSS + Naive UI + ECharts | plan §3 锁定;轻量、Mobile First、TypeScript 严格、Naive UI 适配暗色 |
| 后端 | Go 1.22 + Gin + GORM | plan §3 锁定;单文件、编译快、goroutine 适合个人项目 |
| 数据库 | SQLite (glebarez/sqlite 纯 Go) | plan §3 锁定;零依赖、文件级持久化;WAL + 外键 + busy_timeout |
| 精度 | shopspring/decimal | plan §16;金额 / 油耗 / 成本用 decimal 避免 float 累计误差 |
| 部署 | Docker + Docker Compose + Nginx | plan §17;反代 + volume 持久化 SQLite |
---
## 🚀 快速开始(开发期)
### 前置
- Node.js ≥ 20(已测 24.15)
- pnpm ≥ 9(已测 9.7)或 npm
- Go ≥ 1.22(已测 1.27)
- git,可选:make
### 1. 克隆 + 装依赖
```bash
git clone https://gitea.kennethcheng.cc/KennethCheng/FuelLog.git
cd FuelLog
# 后端
cd fuellog-api
go mod tidy
cp .env.example .env
# 前端(另开一个 shell)
cd fuellog-web
pnpm install
cp .env.example .env.local
```
### 2. 启后端
```bash
cd fuellog-api
go run ./cmd/server # 监听 :8080
```
输出形如:
```
time=... msg="db ready" path=./data/fuellog.db
time=... msg="server starting" addr=:8080 mode=debug
```
### 3. 启前端
```bash
cd fuellog-web
pnpm dev # 监听 :5173,/api 自动代理到 :8080
```
打开 <http://127.0.0.1:5173> 即可。首次进会看到"还没有车辆,先去添加"的引导。
### 4. (可选)冒烟测试
```bash
cd fuellog-api
pwsh -ExecutionPolicy Bypass -File scripts/smoke.ps1
# 13/13 断言:建车 / 加油 / 油价 / 油耗 / 删记录 / 删车 全部命中 plan §6 数值
```
### 5. (可选)单测
```bash
cd fuellog-api
go test ./... # 22 decimal + 5 service
pnpm typecheck # 前端 vue-tsc
pnpm build # 前端 vite build
```
---
## 📁 仓库结构
```
FuelLog/
├── fuellog-api/ # Go 后端
│ ├── cmd/server/main.go # 入口
│ ├── internal/
│ │ ├── config/ # env 加载
│ │ ├── db/ # GORM open + migration
│ │ ├── handler/ # HTTP 入参 / 出参
│ │ ├── service/ # 业务逻辑 + 核心计算
│ │ ├── repository/ # GORM 数据访问
│ │ ├── model/ # GORM 实体
│ │ ├── dto/ # 请求/响应 DTO
│ │ ├── middleware/ # logger / recover / cors
│ │ └── router/ # Gin 路由
│ ├── pkg/
│ │ ├── apperror/ # 统一错误类型
│ │ └── decimal/ # 金额 / 油耗精度工具
│ ├── migrations/ # 迁移(目前 GORM AutoMigrate;后续手写)
│ ├── scripts/ # smoke.ps1 等
│ └── go.mod
│
├── fuellog-web/ # Vue 前端
│ ├── src/
│ │ ├── api/ # axios + 类型化 client
│ │ ├── components/ # 业务组件
│ │ ├── layouts/ # AppLayout(顶栏 + 移动端底部 Tab)
│ │ ├── pages/ # Dashboard / Refuels / RefuelNew / Vehicles / Statistics / NotFound
│ │ ├── router/ # Vue Router 配置
│ │ ├── stores/ # Pinia
│ │ ├── styles/ # tailwind.css + theme.css(CSS Variables)
│ │ ├── utils/ # decimal 工具
│ │ ├── App.vue
│ │ └── main.ts
│ ├── public/ # favicon / manifest / PWA icons
│ └── package.json
│
├── deploy/ # Docker 部署文件
│ ├── docker-compose.yml # 生产:api + web(nginx),宿主机 :8080
│ ├── docker-compose.dev.yml # 开发:api 容器 + bind mount 本地 dist
│ ├── nginx/web.conf # web 容器用 nginx 配置(/api 反代 + SPA fallback)
│ └── README.md # 部署说明
├── docs/
│ └── api.md # 完整 API 文档
├── data/ # SQLite 文件(默认 .gitignore,挂载 volume)
├── AGENTS.md # Agent 工程操作手册(规则、约束、Phase 顺序)
├── README.md # 本文件
└── .gitignore
```
---
## 🔢 核心计算规则
| 指标 | 公式 | 范围 |
|---|---|---|
| 实际油价 | `total_amount / liters` | 服务端权威,前端只展示 |
| 行驶里程 | `current.odometer - previous.odometer` | 负数 / 零视为无效 |
| 百公里油耗 | `liters / mileage × 100` | **只在 `is_full=true` 序列内采样** |
| 每公里成本 | `total_amount / mileage` | 同上 |
所有除法走 `shopspring/decimal`(后端) / 同步实现(前端 `utils/decimal.ts`)。
---
## 🧪 测试覆盖
- `pkg/decimal/money_test.go` — 22 个子测试,覆盖 plan §6.1-6.4 全部案例
- `internal/service/refuel_record_test.go` — 5 个测试,覆盖"半箱油不进入标准油耗"硬规则
- `scripts/smoke.ps1` — 13 步端到端冒烟,跑通"建车 → 加 3 次油 → 统计 → 删记录 → 删车"
---
## 🐳 部署
生产一条命令起 api + web,数据持久化到 named volume,前置 Nginx Proxy Manager
或 Traefik 接 SSL 即可对外。
### 快速部署
```bash
# 在仓库根跑(任选一种)
docker compose -f deploy/docker-compose.yml up -d
# 浏览器访问 http://your-host:8080
```
### 部署拓扑
```
┌─────────────────┐ ┌─────────────────┐
│ Nginx Proxy Mgr │────▶│ fuellog-web:80 │
│ (TLS / 反代) │ │ (nginx:alpine) │
└─────────────────┘ │ ├ 静态 SPA │
│ └ /api/* → api │
└────────┬────────┘
│ fuellog-net
┌────────▼────────┐
│ fuellog-api:8080│
│ (golang alpine)│
│ ├ GORM + SQLite │
│ └ /data 卷 │
└─────────────────┘
```
### 镜像要点
| 镜像 | 基础 | 关键配置 |
|---|---|---|
| `fuellog/api` | 多阶段 `golang:1.22-alpine` → `alpine:3.20` | CGO=0 出纯静态二进制 / tini 收僵尸 / 非 root / 时区 Asia/Shanghai / HEALTHCHECK |
| `fuellog/web` | 多阶段 `node:20-alpine` → `nginx:1.27-alpine` | corepack 启 pnpm / `pnpm build` → nginx 静态 / gzip + 30 天 immutable cache |
### 数据持久化
- 容器内 `DB_PATH=/data/fuellog.db`
- 宿主机 docker volume 名:`fuellog-data`
- 容器删了重建,数据还在;真正要备份:`docker run --rm -v fuellog-data:/data -v $(pwd):/backup alpine cp /data/fuellog.db /backup/`
### 反代建议(不要直接把 8080 暴露公网)
```
Internet ──TLS──▶ Nginx Proxy Manager ──127.0.0.1:8080──▶ fuellog-web (容器)
```
容器只 listen 8080,不直接对外,符合 plan §17 "容器内 API 默认只监听内网"。
### 健康检查
- api:`wget http://127.0.0.1:8080/health`,30s 间隔,启动 10s 后开始
- web:`wget http://127.0.0.1/`,30s 间隔
- docker-compose 用 `depends_on: condition: service_healthy` 保证 web 等 api 健康后才起
### 开发模式(不想本地装 Go 时用)
```bash
# 1. 本地 build 前端
cd fuellog-web && pnpm install && pnpm build
# 2. 启容器(nginx bind mount 上面 build 出的 dist)
cd .. && docker compose -f deploy/docker-compose.dev.yml up
# 3. 改前端 -> pnpm build -> docker compose restart web
```
日常前端调试还是推荐直接 `pnpm dev`(Vite :5173 自带 /api 代理),上面这套
是为了"不想在 Windows 装 Go 也能跑后端"准备的。
详细配置在 [`deploy/README.md`](./deploy/README.md)。
---
## 🗺 路线图
- [x] Phase 0~1:初始化 + 后端骨架 + 数据层 + API + 单测 + 冒烟
- [x] Phase 2~3:前端骨架 + 4 个业务页
- [x] Phase 4:PWA(manifest / SW / 添加到主屏)
- [x] Phase 5:Docker 部署 + 反代(API / web 多阶段镜像 + nginx + volume)
- [ ] Phase 6:数据导入导出(JSON / CSV)
- [ ] Phase 7:多用户 / 鉴权(目前是个人单机)
- [ ] Phase 8:充电 / 保养 / 停车等扩展
---
## 📐 设计原则
- **Mobile first**:360 / 390 / 430 三档断点先验证;布局用 `grid + md:`,CSS Variables 切暗色。
- **不写死颜色 / 间距 / 字号**:Tailwind theme + `:root` CSS Variables 双轨;`brand` / `ink` / `surface` 都是变量,组件层一律 `text-brand` / `bg-surface`。
- **Handler 不写业务**:Handler 只做参数解析、错误码映射;Service 持有 Repository 接口,便于单测替换。
- **金额 decimal**:任何除法都不直接用 float64。
- **半箱油不污染油耗**:plan §6.3 + §19 验收的硬规则,单测里有专门 case。
- **错误统一格式**:`{ "code": "INVALID_ARGUMENT", "message": "..." }`,前端 axios 拦截器解到 `Error.cause`。
- **PWA 添加到主屏**:`display: standalone` + maskable icon + SW 预缓存,真像 app。
---
## 📄 License
MIT
---
## 📎 相关
- [AGENTS.md](./AGENTS.md) — Agent 工程操作手册(规则、约束、Phase 顺序、Definition of Done)
- [docs/api.md](./docs/api.md) — 完整 API 文档
- [FuelLog_PLAN.md](./FuelLog_PLAN.md) — 原始产品 / 技术决策文档(仓库内)