上条 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 章节标题。
287 lines
11 KiB
Markdown
287 lines
11 KiB
Markdown
# FuelLog 加油记
|
||
|
||
> 轻量、精美、移动端优先的个人加油记录与车辆燃油成本分析工具。
|
||
|
||
[](fuellog-api/go.mod)
|
||
[](fuellog-web/package.json)
|
||
[](#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) — 原始产品 / 技术决策文档(仓库内)
|