补上交付物必备的文档: - README.md:项目介绍 / 特性 / 技术栈 / 快速开始 / 仓库结构 / 核心计算公式 / 测试覆盖 / 设计原则 / 路线图 / License - docs/api.md:6 个 endpoint 完整契约(请求参数 / 响应 / 错误码 / 业务规则),含 Statistics 字段含义与采样逻辑说明 为什么现在补: - 之前的 12 个 commit 都是代码,GitHub / Gitea 仓库首页空白 - 新成员(或者未来的自己)来看仓库,无法在 5 分钟内知道这是个啥 / 怎么跑 - API 文档从代码里的 dto.go / handler.go 反推,容易漏边界条件;集中写一遍 把所有约束沉淀下来 README 设计: - 顶上一段简介 + badge - ✨ 特性(7 条,每条对应 plan §3 / §6 / §12) - 🛠 技术栈:表格化展示 plan 锁定选型 - 🚀 快速开始:5 步(克隆 / 装依赖 / 启后端 / 启前端 / 冒烟) - 📁 仓库结构:树状展示两个子工程的完整布局 - 🔢 核心计算规则:4 个公式表(从 plan §6 提炼) - 🧪 测试覆盖:列出 22+5+13 的具体来源文件 - 🐳 部署 / 🗺 路线图:占位 + 标 done - 📐 设计原则:把 plan §12 + AGENTS.md 关键约束翻译成"为什么这么做" - 📎 相关:链回 AGENTS.md / docs/api.md / FuelLog_PLAN.md docs/api.md 设计: - 通用约定:UTC / decimal / 列表空数组 / 错误码字典 - 6 个 endpoint 一段一个,含:curl-style 示例、参数表、Response 200 示例、 业务规则(例如 RefuelCreate.price_per_liter 不入参 / RefuelUpdate patch 语义 / Statistics 只在 is_full 序列内采样) - 错误码字典表格(INTERNAL / INVALID_ARGUMENT / NOT_FOUND / CONFLICT / ...) - 索引说明 风格:中文为主,与 AGENTS.md / commit message 保持一致;技术名词留英文 (endpoint / Vehicle / RefuelRecord 等);代码示例保持 JSON 风格。 未做(留待后续): - README 截图:得真起服务跑,等 Docker 起来再补 - README 英文版:暂只中文,MVP 上线后再翻
4.8 KiB
FuelLog API 文档
Base path:
/api错误统一格式:{ "code": "INVALID_ARGUMENT", "message": "..." }鉴权: MVP 阶段不要求,MVP 之后会在Authorization: Bearer <token>头里加。
通用约定
- 所有时间字段统一 UTC 存库;请求时传 RFC3339 字符串,响应回 RFC3339。
- 金额 / 加油量 / 里程 / 油耗:浮点;金额计算在服务端用
shopspring/decimal避免累计误差。 - 列表接口默认空数组(不返回
null)。 - 删除为硬删(数据库里
DELETE);二次确认在前端做。 - 默认车辆唯一,切换走事务,避免出现两个 default。
错误码字典(在 pkg/apperror):
| code | HTTP | 含义 |
|---|---|---|
INTERNAL |
500 | 兜底,未预期 |
INVALID_ARGUMENT |
400 | 参数校验失败 |
NOT_FOUND |
404 | 资源不存在 |
CONFLICT |
409 | 资源冲突(预留) |
UNAUTHORIZED |
401 | 未鉴权(预留) |
FORBIDDEN |
403 | 无权限(预留) |
UNAVAILABLE |
503 | 暂不可用(预留) |
健康检查
GET /health
{ "status": "ok", "time": "2026-09-13T16:11:32+08:00" }
车辆 /api/vehicles
GET /api/vehicles
列出所有车辆,按 default_vehicle DESC, id ASC 排序。
Response 200
[
{
"id": 1,
"name": "我的夏",
"brand": "比亚迪",
"model": "秦 PLUS DM-i",
"plate_number": "京A12345",
"fuel_type": "hybrid",
"tank_capacity": 48,
"current_odometer": 10000,
"default_vehicle": true,
"created_at": "2026-09-13T16:00:00+08:00",
"updated_at": "2026-09-13T16:00:00+08:00"
}
]
POST /api/vehicles
创建车辆。
Request
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | ✅ | 车辆名称,max 64 |
brand |
string | max 64 | |
model |
string | max 64 | |
plate_number |
string | max 32 | |
fuel_type |
string | ✅ | gasoline / diesel / hybrid / electric / plug_in_hybrid |
tank_capacity |
number | 升,>= 0 |
|
current_odometer |
number | 公里,>= 0 |
|
default_vehicle |
bool | 设为默认时,旧默认会被清掉(事务) |
Response 200 — 完整 Vehicle 对象
GET /api/vehicles/:id
返回单辆;不存在返 404。
PUT /api/vehicles/:id
Patch 语义:*string / *number / *bool 表示可选,未传的字段不更新。
其它校验同 POST。
Response 200 — 更新后的 Vehicle
DELETE /api/vehicles/:id
硬删。返 204 No Content。
POST /api/vehicles/:id/default
设为唯一默认。事务内先清旧默认,再置新默认。
Response 200 — 更新后的 Vehicle
加油记录 /api/refuels
POST /api/refuels
Request
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
vehicle_id |
uint64 | ✅ | 关联车辆 |
refuel_time |
RFC3339 | ✅ | 加油时间,UTC 存库 |
odometer |
number | ✅ | 加油时里程,>= 0 |
fuel_type |
string | ✅ | 油品 |
liters |
number | ✅ | 加油量,> 0 |
total_amount |
number | ✅ | 总金额,>= 0 |
station_name |
string | max 128 | |
is_full |
bool | 是否加满(决定是否进入标准油耗) | |
remark |
string | max 512 |
注意:price_per_liter 不入参,由服务端用 total_amount / liters 实时算出,落库保留历史真实单价。
Response 200 — 完整 RefuelRecord
GET /api/refuels/:id
PUT /api/refuels/:id
Patch 语义。liters / total_amount 任一更新都会重新算 price_per_liter。
DELETE /api/refuels/:id
返 204 No Content。
GET /api/refuels?vehicle_id=...&start_date=...&end_date=...
| 参数 | 必填 | 说明 |
|---|---|---|
vehicle_id |
✅ | uint64 |
start_date |
RFC3339,只返 refuel_time >= 它的 | |
end_date |
RFC3339,只返 refuel_time <= 它的 |
按 refuel_time DESC, id DESC 排序。
Response 200 — RefuelRecord 数组
统计 /api/vehicles/:id/statistics
单车的关键统计指标,服务端正序遍历 refuel_time 倒序列表,只在
"连续两次 is_full=true"之间采样油耗 / 每公里成本;半箱油记录不会污染。
Response 200
{
"vehicle_id": 1,
"total_records": 3,
"total_liters": 82.0,
"total_amount": 656.0,
"avg_price": 8.0,
"avg_consumption": 6.4,
"cost_per_km": 0.51,
"last_refuel": { /* RefuelRecord 或 null */ }
}
字段含义:
avg_price全部记录的实际油价算术平均avg_consumption标准百公里油耗 L/100km(仅 is_full 序列的样本平均)cost_per_km每公里燃油成本 元/km(仅 is_full 序列的样本平均)last_refuel最近一次加油记录(按 refuel_time DESC 第一个)
空车辆返 { total_records: 0, ...全 0, last_refuel: null }。
索引
- 车辆:
vehicle.idPK - 加油记录:
refuel_record.idPK;(vehicle_id, refuel_time)与vehicle_id各自索引