# 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 ``` 打开 即可。首次进会看到"还没有车辆,先去添加"的引导。 ### 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 ``` ### 改端口(默认 8080 冲突时) 如果宿主机 8080 已被占用(NAS、其它服务),用一个环境变量一起改 api 内部监听 + web 容器 upstream + 宿主机映射: ```bash # 例:把整套改成 8081 API_PORT=8081 WEB_PORT=8081 \ docker compose -f deploy/docker-compose.yml up -d --build ``` `API_PORT` 控制: - `api.SERVER_ADDR`(api 容器内监听) - `api.expose` / `healthcheck`(容器间网络) - `web` build 阶段的 nginx upstream(`web.conf` 里 `server api:__API_PORT__` 在 build 时 sed 替换) `WEB_PORT` 控制宿主机到 web 容器 80 端口的映射。 `.env` 文件也能放(放在仓库根,自动被 compose 读): ``` API_PORT=8081 WEB_PORT=8081 ``` > **改完端口不重 build 是不生效的**,因为 web 镜像里的 nginx conf 是 > build 时定的。`up -d --build` 才能拿到新镜像。 ### 部署拓扑 ``` ┌─────────────────┐ ┌─────────────────┐ │ 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.27-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 / `API_PORT` build arg 注入 upstream | ### 数据持久化 - 容器内 `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/` ### 反代建议(不要直接把对外端口暴露公网) ``` Internet ──TLS──▶ Nginx Proxy Manager ──127.0.0.1:WEB_PORT──▶ fuellog-web (容器) ``` 容器内 API 不直接对外,符合 plan §17 "容器内 API 默认只监听内网"。 `WEB_PORT` 是宿主机 listen 端口(默认 8080),跟 `API_PORT`(容器内 api 监听,默认 8080)是两个独立变量,大部分场景保持一致。 ### 健康检查 - api:`wget http://127.0.0.1:${API_PORT}/health`,30s 间隔,启动 10s 后开始 - web:`wget http://127.0.0.1/`,30s 间隔 - docker-compose 用 `depends_on: condition: service_healthy` 保证 web 等 api 健康后才起 ### 排错:502 Bad Gateway 最常见原因:`API_PORT` 与 `api` 容器实际 listen 端口不一致。 `fuellog-web/nginx/web.conf` 里 upstream 写的是 `server api:__API_PORT__`, 由 build 阶段用 `docker compose` 传入的 `API_PORT` 替换。如果改了 `SERVER_ADDR` 端口但忘改 build arg,或者改了 build arg 但没重 build, nginx 就会 upstream 连不上返 502。 排查步骤: ```bash # 1. 看 api 实际 listen 端口 docker exec fuellog-api sh -c 'wget -qO- http://127.0.0.1:8080/health 2>&1 || echo 8080_FAIL' docker exec fuellog-api sh -c 'wget -qO- http://127.0.0.1:8081/health 2>&1 || echo 8081_FAIL' # 2. 看 web 容器内 nginx conf 写的是哪个端口 docker exec fuellog-web cat /etc/nginx/conf.d/web.conf | grep 'server api:' # 3. 不一致就重 build(只 up -d 不会重 build) API_PORT=8081 docker compose -f deploy/docker-compose.yml up -d --build ``` ### 开发模式(不想本地装 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) — 原始产品 / 技术决策文档(仓库内)