## 功能 - 邮箱找回密码:新增 password_resets 表 + POST /api/auth/forgot-password (按接收邮箱反查用户 → 生成一次性 30 分钟令牌 → 复用 SMTP 发重置邮件, 防枚举统一提示、复用登录限流器防刷)+ POST /api/auth/reset (校验令牌有效性 → bcrypt 更新密码 → 作废令牌 → 强制该用户全部会话下线); H5 新增 /forgot、/reset 页面,登录页加"忘记密码"入口 - SMTP 全局配置补全:新增发件邮箱 smtp_user / 授权码 smtp_pass 字段 (H5 + 小程序设置页同步展示与保存),GET 脱敏回显, 修复"脱敏值含 * 被 POST 写回覆盖真实配置"的 bug - 新增 cmd/sendmail 真实发送工具(配置走环境变量,敏感信息不落库, 已用 163 SMTP 465 隐式 SSL 实测发送成功) ## 日志体系 - 死配置 OIL_MONITOR_LOG_DIR 替换为 OIL_MONITOR_LOG_LEVEL (debug/info/warn/error,LevelVar 动态切换,上线默认 info 收敛日志) - 自定义 requestLogger 请求日志中间件(Debug 级:method/path/status/耗时/IP) - 外部交互日志统一:爬虫抓取 / SMTP / Webhook / 高德 regeo / 微信 code2session, 成功 Debug、失败 Error、均带 dur_ms、URL 去 query 脱敏防 key/token 泄露 - 定时任务统一 runTask 包装:开始 Debug、完成+时长 Info、失败/panic Error (panic 被 recover,不连累调度器) ## 测试 - cron/scheduler_test.go:runTask 成功/失败/panic 三路径单测 - push/sender_test.go:本地假 SMTP 服务器完整发送会话链路 + 服务器拒绝失败路径 - db_test.go:表数量断言 11 → 12(新增 password_resets) ## 部署配置与文档 - Dockerfile / docker-compose.yml / .env.example / README:日志配置同步 (删 /app/logs 目录与挂载,OIL_MONITOR_LOG_LEVEL) - .gitignore:忽略 internal/pkg 依赖缓存(349M,防误提交) - docs/10/21/22/23/24:同步 A18/A19 接口、password_resets 表、任务执行日志、SMTP 字段 Co-Authored-By: Claude <noreply@anthropic.com>
29 KiB
⛽ 汽油价格监控软件(Oil Price Monitor)— 详细业务与技术架构设计书
状态:已评审确定 · 第一期方案(1-B, 2-A, 3-交互强化)+ 多用户身份模型(匿名浏览 + 登录私密区)
版本:v1.2
更新日期:2026-09-23
定位:全栈轻量级、服务器/NAS 部署、H5 移动响应式、API-First 设计的个人油价看板与加油预警专家。
目录
- 业务背景与领域知识
- 功能模块划分
- 核心算法:油价预测模型
- 系统架构与技术选型
- 数据模型设计(Database Schema)
- API 接口设计(API-First 微信小程序准备)
- H5 前端交互设计(定位与切换)
- 关键业务流程
- 多渠道通知模板设计
- 开发迭代路线图
一、业务背景与领域知识
国内成品油价格受国家政策严格管控,其变动并非实时连续波动,而是呈现典型的阶梯式特征。为了向车主提供真正有价值的加油决策("今天加"还是"明天加"),软件必须紧密围绕发改委的定价机制进行设计。
1.1 定价机制核心规则
- 10个工作日一调:调价周期为10个工作日。周六、周日及法定节假日不计入工作日。
- 50元/吨触发红线:
- 估算的汽柴油调价幅度 ≥ 50元/吨,发改委于调价日当天下午(通常为 16:30 - 17:30)发布公告,并于**当日 24:00(即次日 00:00)**起正式生效。
- 调价幅度 < 50元/吨,则本次不作调整(搁浅),其未调金额累加或冲抵至下一个调价周期。
- 地板价与天花板价:
- 当国际原油均价 > 130美元/桶 时,国内汽柴油价格原则上不提或少提(天花板机制)。
- 当国际原油均价 < 40美元/桶 时,国内成品油价格不再下调(地板价机制)。
- 价格口径区别:
- 发改委公布价:为“最高零售价格(元/吨)”。
- 省份/城市零售价:各省发改委根据国家标准,折算成“元/升”的本地最高上限。由于运杂费及品质标准差异,各省零售价不一致。
- 加油站实价:两桶油/民营站可在最高零售价内自主打折。本软件以官方最高零售价为准。
1.2 单位换算公式
官方通知使用吨为单位,而消费者使用升。换算依赖于油品在标准温度(15℃)下的密度:
- 92# / 95# 汽油:密度约为 $0.72 \sim 0.75 \text{ kg/L}$,行业标准换算通常取 $1 \text{ 吨} \approx 1350 \text{ 升}$。
\text{每升涨跌额(元/升)} \approx \frac{\text{每吨涨跌额(元)}}{1350}(例:每吨上调 270 元\approx每升上调 0.20 元) - 0# 柴油:密度约为 $0.83 \sim 0.85 \text{ kg/L}$,行业标准换算通常取 $1 \text{ 吨} \approx 1190 \text{ 升}$。
\text{每升涨跌额(元/升)} \approx \frac{\text{每吨涨跌额(元)}}{1190}
二、功能模块划分
软件采用模块化设计,共分为 5 个核心模块:
+---------------------------------------+
| 汽油价格监控软件 |
+---------------------------------------+
|
+-------------+-------------+---+-------------+-------------+
| | | | |
+-----+---+ +-----+---+ +-----+---+ +-----+---+ +-----+---+
| M1 采集 | | M2 预测 | | M3 用车 | | M4 看板 | | M5 推送 |
| 数据 | | 分析 | | 档案 | | 界面 | | 通知 |
+---------+ +---------+ +---------+ +---------+ +---------+
M1 · 数据采集模块 (Data Ingestion)
- 国际油价同步:每日收盘后,自动请求新浪财经等无 Key API 获取 Brent、WTI 期货结算价及日内涨跌幅。
- 发改委公告爬取:调价日 16:30 - 18:00,以 2 分钟/次的频率轮询国家发改委价格司新闻发布页。提取:
- 调价公告标题与发布时间
- 吨级涨跌幅度(汽油、柴油各降/升多少元)
- 本次调价执行的生效日期与时间
- 原文链接,并保存原文快照
- 省份零售价爬取:
- 在发改委发布新价后,同步抓取
qiyoujiage.com或主流汽车门户,实时获取 31 省市的 92#/95#/98# 汽油及 0# 柴油每升单价,实现自动化同步。
- 在发改委发布新价后,同步抓取
M2 · 预测分析模块 (Prediction & Analytics)
- 变化率估算(核心算法):
- 追踪当前周期的已过天数和剩余天数。
- 结合每日国际原油变化,自动计算“三地原油变化率移动平均”。
- 预测本次调价的方向(上涨/下跌/搁浅)及幅度(元/吨
\rightarrow折合元/升)。
- 置信度度量 (Confidence Rating):
- 计价周期 T-10 天至 T-8 天:置信度“低”(处于大盘震荡期)。
- T-7 天至 T-3 天:置信度“中”。
- T-2 天至 T-1 天:置信度“极高”(数据基本定型)。
- 历史统计分析:
- 年度累计调价次数,其中上涨、下跌、搁浅各多少次。
- 汇总年度每升累计涨跌幅,并生成历史价格波动阶梯图。
M3 · 车主用车档案与成本核算 (Personal Fuel Cost)
- 爱车档案管理:
- 支持添加多台车辆,配置:名称、爱车省份、推荐油号(92#/95#/98#/0#)、油箱容量 (L)、平均百公里油耗 (L/100km)、月均行驶里程 (km)。
- 一箱油变动感知(核心痛点):
- 当发改委公布调价但未正式生效的 7 个小时内(17:00
\sim24:00),系统自动计算本次调价对用户爱车的具体影响:\text{单次加油成本变化(元)} = \text{车辆油箱容量(L)} \times \text{本省每升涨跌额(元/升)}\text{月度出行成本变化(元)} = \frac{\text{月均里程(km)}}{100} \times \text{百公里油耗} \times \text{每升涨跌额(元/升)}
- 当发改委公布调价但未正式生效的 7 个小时内(17:00
- 加油建议建议器:
- 涨价前
\rightarrow“今晚24点后加满一箱多花 X 元,建议今天去加油!” - 降价前
\rightarrow“今晚24点后加满一箱可省 Y 元,建议留到明天再加!”
- 涨价前
- 注:“加油记账历史”与“长期油耗账本”在一期暂不实现,保留表结构和 API 的扩展空间。
M4 · 移动响应式 Web 界面 (Responsive Web App / H5)
- 纯 H5 适配手机端与宽屏:采用简洁的卡片化设计,在手机屏幕上实现完美纵向排版,在桌面浏览器上自动横向响应。
- 定位与切换省份:
- 前端坐标定位优先:进入页面时,优先使用浏览器 Geolocation API 获取设备精确定位(经纬度),再调用高德地图 JS API 的逆地理编码(
AMap.Geocoder)将坐标反解为所在省份。不使用后端 IP 定位(IP 定位误差大、只能到城市级、且依赖第三方 IP 库,无法满足"设备真实所在"的需求)。 - 定位降级链:坐标定位因用户拒绝授权 / HTTP 非安全域 / 浏览器不支持而失败时 → 降级为高德 IP 定位(仅作弱兜底)→ 再失败则回退到最近选择的历史省份 / 默认省份(详见第七章 7.2)。
- 需求方需自备高德 JS API Key(免费额度充足,个人开发够用),配置文件里统一管理。
- 省份选择页(独立页面):首页左上角常驻显示当前省份入口,点击跳转到独立的省份选择页——顶部展示最近选择的 5 个历史省份(横排芯片,
localStorage缓存 + LRU 淘汰),下方网格展示全部 31 省;选中后返回首页并刷新(详见第七章 7.2)。
- 前端坐标定位优先:进入页面时,优先使用浏览器 Geolocation API 获取设备精确定位(经纬度),再调用高德地图 JS API 的逆地理编码(
- 国际原油趋势图:
- 展示近期 Brent 与 WTI 的走势图。
- 本省油价卡片:
- 醒目显示 92#、95#、98#、0# 的今日最高限价。
- 历史调价记录表:
- 列表展示历史每一次官方调价文号、幅度和生效时间。
M5 · 推送通知模块 (Pushes & Notifications)
- 飞书/企业微信/钉钉群机器人:推送富文本 Markdown 卡片到指定的群组。
- SMTP 邮件推送:向配置的邮箱发送油价变动。
- Windows 桌面原生弹窗:Go 服务在本机后台运行时,可配置调用 Windows Toast 消息进行桌面弹窗(可关闭)。
- 推送触发时机:
- 预测预警(T-1 天 20:00):涨跌幅超用户阈值(默认
\pm 0.05元/升)时发出。 - 官方锤定(调价日 ~17:30 官宣后 10 分钟内):发送加油实战决策通知。
- 预测预警(T-1 天 20:00):涨跌幅超用户阈值(默认
三、核心算法:油价预测模型
由于三地原油加权平均公式的权重细节并未完全向公众公开,我们的预测将基于国际油价收盘走势进行模拟。
3.1 周期计价法
- 一个计价周期包含 10 个有效工作日。
- 每日更新计算原油变化率 $R$:
R = \frac{\text{本计价周期已过工作日的三地原油均价} - \text{上一个计价周期三地原油均价}}{\text{上一个计价周期三地原油均价}} \times 100\% - 吨级变动预测:
\Delta \text{元/吨} \approx R \times \text{基准油价价格系数 } K注:系数K根据前 3 次历史调价结果的“实际涨跌幅 / 变化率”进行动态校准,使预测准确度随时间自动拟合。
四、系统架构与技术选型
为满足“服务器常驻、适配手机、后续开发微信小程序”的需求,系统采用 API-First(前后端分离) 的架构模式。
+---------------------------------------+
| 浏览器 / H5 |
+---------------------------------------+
|
HTTP RESTful APIs (JSON)
(会话 Cookie 认证)
|
+-------------------------------------+-------------------------------------+
| Go 后端 |
| +----------------+ +------------+ +--------------+ +---------------+ |
| | Web API 路由库 | | 认证中间件 | | 数据爬取调度器 | | 邮件/消息推送 | |
| | (Go-Chi) | | auth/admin | | (Cron/Tick) | | (SMTP/Http) | |
| +--------+-------+ +-----+------+ +------+-------+ +------+--------+ |
| | | | | |
| +---------------+----------------+------------------+ |
| | |
| SQLite3 数据库 (CGO-free) |
| (行情=全局公共数据 / 车辆+个人偏好=按 user_id 隔离) |
+---------------------------------------------------------------------------+
多用户支持:行情数据(调价/省份价格/原油/预测)为全局公共数据,所有用户共享;
vehicles/user_settings为用户私有数据,按user_id隔离。匿名访客可看行情与省份切换;车辆管理与推送需登录(HTTP-Only Cookie会话)。单用户私有化 = 1 个用户,天然兼容,无需开关。
4.1 技术栈选型
- 后端核心:Go 1.22+
- 路由库:
github.com/go-chi/chi/v5(极轻量,RESTful 友好,性能优越,适合提供标准的 JSON API,为小程序二期开发奠定基础)。 - 数据库驱动:
modernc.org/sqlite(纯 Go 实现的 SQLite,不依赖 CGO。打包和编译最方便,无需宿主机安装 gcc/mingw)。 - 计划任务:
robfig/cron/v3(用于定时拉取原油价格和发改委轮询)。 - 网络爬网:Go 标准库
net/http+PuerkitoBio/goquery。
- 路由库:
- 前端 H5:Tailwind CSS (原生内嵌/CDN) + AlpineJS + ECharts
- AlpineJS:轻量级声明式 JS 框架(压缩后仅约 8KB),极其适合无构建(Build-free)的 H5 页面交互,比 Vanilla JS 更加简洁,免去 Vue/React 繁重的打包发布。
- Tailwind CSS:响应式 CSS 类,完美实现手机端、Pad端和PC端的自适应布局。
- ECharts:做自适应折线图和油价趋势图。
- 部署与容器化:Docker
- 编写极简的
Dockerfile(多阶段构建,最终镜像仅约 20MB),并提供docker-compose.yml快速一键拉起服务。
- 编写极简的
五、数据模型设计
数据库采用 SQLite,在本地目录下生成单个文件 data.db。
5.1 调价事件表 price_adjustments
保存每一次官方发布的正式调价事件。
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 ID |
announcement_title |
TEXT | NOT NULL | 发改委公告标题 |
announced_at |
DATETIME | NOT NULL | 发改委发文时间 |
effective_at |
DATETIME | NOT NULL | 调价正式生效日期(通常为当天 24:00) |
gas_delta |
REAL | NOT NULL | 汽油变动幅度(元/吨,负数为降,0为搁浅) |
diesel_delta |
REAL | NOT NULL | 柴油变动幅度(元/吨,负数为降) |
direction |
TEXT | NOT NULL | up / down / stalled |
doc_number |
TEXT | 发改委红头文件发文号 | |
source_url |
TEXT | 官方发文原始链接 | |
created_at |
DATETIME | DEFAULT CURRENT_TIMESTAMP | 记录入库时间 |
5.2 各省零售单价表 province_prices
保存历次调价后,各省的具体每升零售单价。与 price_adjustments 呈多对一关系。
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 ID |
adjustment_id |
INTEGER | REFERENCES price_adjustments(id) |
关联的调价事件 ID |
province |
TEXT | NOT NULL | 省份名称(如“广东”, “北京”) |
price_92 |
REAL | NOT NULL | 92号汽油单价(元/升) |
price_95 |
REAL | NOT NULL | 95号汽油单价(元/升) |
price_98 |
REAL | NOT NULL | 98号汽油单价(元/升) |
price_0 |
REAL | NOT NULL | 0号柴油单价(元/升) |
effective_date |
DATE | NOT NULL | 该价格生效的具体日期 |
5.3 国际原油报价表 crude_quotes
记录布伦特 (Brent) 和 WTI 每日价格,用来驱动变化率预测。
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
quote_date |
DATE | PRIMARY KEY | 报价日期(开盘工作日) |
brent_price |
REAL | NOT NULL | 布伦特结算价(美元/桶) |
wti_price |
REAL | NOT NULL | WTI 结算价(美元/桶) |
updated_at |
DATETIME | DEFAULT CURRENT_TIMESTAMP | 刷新时间 |
5.4 车辆档案表 vehicles(按用户归属)
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY AUTOINCREMENT | 车辆自增 ID |
user_id |
INTEGER | NOT NULL REFERENCES users(id) |
归属用户(🔒私有数据) |
name |
TEXT | NOT NULL | 车辆昵称(如“我的迈腾”) |
province |
TEXT | NOT NULL | 车牌归属省份(关联油价) |
fuel_type |
TEXT | NOT NULL | 燃油标号:92 / 95 / 98 / 0 |
tank_capacity |
REAL | NOT NULL | 油箱容量(升) |
fuel_consumption |
REAL | NOT NULL | 百公里油耗(升/百公里) |
monthly_mileage |
REAL | NOT NULL | 月均行驶里程(公里) |
5.5 用户账号表 users
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY AUTOINCREMENT | 用户 ID |
username |
TEXT | NOT NULL UNIQUE | 登录名 |
password_hash |
TEXT | NOT NULL | bcrypt 哈希 |
is_admin |
INTEGER | NOT NULL DEFAULT 0 | 1=管理员;首用户自动成为 admin |
created_at |
DATETIME | DEFAULT CURRENT_TIMESTAMP | 注册时间 |
5.6 会话表 sessions
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
token |
TEXT | PRIMARY KEY | 会话令牌(crypto/rand 32 字节 hex) |
user_id |
INTEGER | NOT NULL REFERENCES users(id) |
归属用户 |
expires_at |
DATETIME | NOT NULL | 过期时间(默认 +7 天) |
created_at |
DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
5.7 配置表(全局与个人分离)
| 表 | 内容 | 权限 |
|---|---|---|
server_settings(全局) |
amap_key、smtp_host/smtp_port/smtp_user/smtp_pass、全局threshold_per_liter、全局default_province、采集状态 last_crawl_* |
仅 admin 可写 |
user_settings(个人) |
飞书/企微/钉钉 webhook、receiver_email、个人threshold_per_liter、个人default_province |
按 user_id 归属 |
六、API 接口设计
全部接口均返回标准 JSON 格式,响应状态码遵循 HTTP 规范。本组 API 采用 API-First 模式,为未来支持微信小程序、小程序直接发起 JSON 请求完美铺路。
6.1 获取看板汇总数据 (GET /api/dashboard)
- 请求参数:
province(string, 可选, 前端定位/切换后传入的省份名;缺省时后端回退到上次请求值或默认省)。后端不参与 IP 定位,定位逻辑全部在前端完成。 - 响应结构:
{
"province": "北京",
"countdown": {
"next_adjustment_date": "2026-10-12",
"days_remaining": 5
},
"current_prices": {
"price_92": 7.78,
"price_95": 8.29,
"price_98": 9.79,
"price_0": 7.50,
"effective_date": "2026-09-23"
},
"prediction": {
"direction": "up",
"est_delta_per_ton": 135.0,
"est_delta_per_liter": 0.10,
"confidence": "medium",
"reason": "受地缘因素影响,本轮原油均价呈上涨态势。"
},
"last_adjustment": {
"announced_at": "2026-09-23 17:02:00",
"effective_at": "2026-09-23 24:00:00",
"gas_delta": 135.0,
"diesel_delta": 130.0,
"direction": "up"
},
"my_cars": [
{
"id": 1, "name": "我的迈腾", "fuel_type": "95",
"delta_per_liter": 0.10, "cost_change_per_tank": 6.60,
"suggestion": "建议今晚24点前加满,一箱可省 6.60 元"
}
]
}
my_cars:已登录时返回当前用户车辆的影响计算;匿名时为null(前端渲染"登录后查看我的加油提醒"占位入口)。
6.4 认证接口(登录门槛)
POST /api/auth/register:注册(首用户自动成为 admin)POST /api/auth/login:登录 → 写 HTTP-Only 会话 CookiePOST /api/auth/logout:登出GET /api/auth/me:探登录态- 私有接口(车辆/个人设置/渠道测试)未登录统一返回
401 + code=1008,前端跳登录页;管理接口(全局设置)非 admin 返回403 + code=1009
6.2 车辆档案接口(🔒需登录,按 user_id 归属)
GET /api/vehicles:返回当前登录用户的车辆列表POST /api/vehicles:新增当前用户的车辆(传 JSON Body)PUT /api/vehicles/:id:更新本人名下车辆DELETE /api/vehicles/:id:删除本人名下车辆GET /api/auth/me:探登录态(未登录时前端隐藏爱车入口 / 点击跳登录页)
6.3 车辆调价成本变动计算接口 (GET /api/vehicles/impact,🔒需登录)
- 功能:计算当前预测/宣布的调价对当前用户爱车的具体金钱影响。
- 响应示例:
[
{
"id": 1,
"name": "我的迈腾",
"fuel_type": "95",
"tank_capacity": 66.0,
"cost_change_per_tank": 6.60,
"cost_change_per_month": 11.20,
"suggestion": "调价将使加满一箱油多花 6.60 元,建议今晚 24 点前加满。"
}
]
七、H5 前端交互设计(定位与切换)
移动 H5 是用户的最高频访问入口。页面交互重点聚焦于省份定位、切换及其便利性。
┌── 首页 ──────────────┐
│ [📍 北京] ▾ │ ← 左上角:当前省份入口
│ ···调价倒计时卡片··· │
│ ···预测卡片··· │
│ ···油价卡片··· │
└──────────────────────┘
│ 点击左上角省份
▼
┌── 省份选择页 ──────────┐
│ 📍 历史省份 │
│ [四川] [广东] [北京] │ ← 最近 5 个,横排芯片
│ ───────────────── │
│ 全部省份 │
│ ┌────┐┌────┐┌────┐ │
│ │北京││上海││天津│ │ ← 31 省网格
│ └────┘└────┘└────┘ │
│ ···(滚动)··· │
└──────────────────────┘
说明:首页左上角始终显示当前选中的省份(定位结果或手动选择);点击它跳转到独立的"省份选择页",选择后返回首页并刷新数据。
7.1 首屏省份定位逻辑(前端定位优先,后端不负责 IP 定位)
⚠️ 核心原则:定位行为全部发生在前端,后端只接收前端解析好的省份名。不使用后端 IP 定位(IP 定位无法感知设备真实位置,且需要额外 IP 地理库)。
7.1.1 定位来源(优先级从高到低)
| 优先级 | 定位方式 | 说明 | 触发条件 |
|---|---|---|---|
| P1 | 浏览器 Geolocation(坐标定位) | 调用 navigator.geolocation.getCurrentPosition() 获取高精度经纬度,再用高德 JS API 的 AMap.Geocoder 逆地理编码反解出省级行政区 |
必须运行在 HTTPS 安全域,且用户同意授权 |
| P2 | 高德 IP 定位(弱兜底) | 调用高德 REST API restapi.amap.com/v3/ip(传 Key 即可,无需用户授权) |
P1 失败:HTTP 非安全域 / 用户拒绝 / 浏览器不支持 Geolocation |
| P3 | 手动选择兜底 | 显示默认省份(如北京)或最近一次选择的省份 | P1、P2 全部失败 |
7.1.2 环境约束与真实场景适配(用户提示的"前端获取定位问题")
- HTTPS 是坐标定位的硬门槛:
navigator.geolocation在 HTTP 或内网 IP 地址下会静默失败。若用户通过http://NAS_IP:8080或http://服务器公网IP:8080访问,坐标定位必然不可用。 - 用户拒绝授权是常态:手机浏览器首次弹窗若被拒绝,后续不再询问。故 P1 必须优雅失败(不弹全屏错误、不阻塞页面),静默降级 P2/P3。
- 微信内浏览器(WebView):
navigator.geolocation需要wx.getLocation()与 JS-SDK 授权才能用;H5 一期不接微信 JS-SDK,因此在微信内打开时自动走 P2 高德 IP 定位。 - 配置建议:在设置页提供"定位方式"开关(
自动定位/关闭自动定位,每次手动选),尊重用户隐私偏好。
7.1.3 首屏加载时序
页面 DOM 就绪
│
├─ ① 读取 localStorage.recent_provinces → 取第 1 位作为"初始展示省"(若有)
│
├─ ② 并行发起后端 /api/dashboard 请求(携带最近省,保证首屏秒开、不为空)
│
└─ ③ 启动定位模块(异步,不阻塞渲染):
├─ [HTTPS + 已授权] → navigator.geolocation → 高德 Geocoder 逆编码 → 得出省
│ └─ 与①不同 → 自动刷新 dashboard 数据,并更新"当前省"标记
├─ [P1 失败] → 高德 IP 定位 API(v3/ip)→ 得出省
└─ [P2 也失败] → 保持①或默认省(北京),不打扰用户
要点:首屏用"最近省"先行渲染(体验最快),异步定位完成后再无缝切换为真实省份,杜绝白屏等待。
7.2 首页省份入口与省份选择页(独立页面)
7.2.1 首页左上角省份入口
- 首页顶部导航栏左上角固定展示当前选中的省份(定位成功后的省份,或用户最近一次手动选择的省份),显示格式:
📍 北京。 - 省份名右侧带
▾下箭头图标,暗示可点击进入选择。 - 点击该入口 → 前端路由跳转至独立的"省份选择页"(H5 对应
/province页面;微信小程序二期对应独立 Pagepages/province/province,逻辑与数据接口完全复用)。
7.2.2 省份选择页布局(自上而下两个区块)
┌─ 区块一:历史省份(无历史记录时不显示)────┐
│ [四川] [广东] [北京] [上海] [江苏] │ ← 横排芯片,最多 5 个
└──────────────────────────────────┘
┌─ 区块二:全部省份(31 省)─────────────┐
│ 北京 天津 河北 山西 内蒙古 │
│ 辽宁 吉林 黑龙江 上海 江苏 │
│ ...(响应式网格,手机端自适应列数) │
└──────────────────────────────────┘
7.2.3 历史省份区(recent_provinces)
- 历史足迹只记录"用户手动切换"的省份;自动定位得出的省份不写入(避免换环境/换设备自动污染历史列表)。
- 数据源:
localStorage.recent_provinces(JSON 数组),最大长度 5。 - LRU 淘汰规则:切换了已在列表中的省份 → 提到第一位;超限 5 个 → 剔除最久未访问的。
- 当前选中的省份若在历史中,芯片上显示"当前"高亮角标。
- 点击历史芯片 → 立即选中并返回首页(不必翻到下方全量网格)。
7.2.4 全部省份区
- 按华北 / 东北 / 华东 / 华中 / 华南 / 西南 / 西北分组,或按拼音首字母排序,网格展示 31 个省级行政区。
- 每个省份项显示名称 + 当前 92# 价格角标(如
北京 · 7.78),便于跨省对比。 - 点击任意省份 → 写入历史 → 返回首页 → 刷新 dashboard 数据。
- 当前选中省份在网格中用高亮边框/底色标识。
7.2.5 返回行为
- 未选择直接返回 → 首页保持原省份不变。
- 已选择 → 首页左上角更新为新省份,全部数据随之刷新。
- 兼容手机返回手势(左滑/返回键),与 H5 history 路由栈一致。
7.2.6 高德 Key 管理
高德 JS API 与 IP 定位共用同一个 Key,由 admin 在全局设置页配置并持久化到 server_settings 表;未配置 Key 时前端隐藏自动定位入口,只允许手动选择(功能仍可完整使用)。
八、关键业务流程
(关键业务流程与后端任务的完整生命周期,详见 23.后端定时任务详细设计.md 与 21.功能大纲.md。)
九、多渠道通知模板设计
(此处通知卡片富文本对齐飞书/企微 Webhook、SMTP 邮件规范。)
十、开发迭代路线图
本着敏捷、增量的全栈研发策略,我们将第一期开发拆解为以下阶段,并锁定核心功能:
- 阶段一:本地数据库与数据采集引擎开发 (Backend Core)
- 设计并创建 SQLite 数据库,设计高鲁棒性的 DB 迁移与初始化函数。
- 编写采集器:① 新浪财经原油价格抓取;② 国家发改委调价公告及生效时间爬取与内容正则抽取;③
qiyoujiage.com的 HTML 各省油价表文本解析。 - 编写定时轮询管理器(Cron 模块),实现平日 1 次/天、调价日 17 点后 2 分钟/次的高频智能探测器。
- 阶段二:前后端分离 RESTful API 设计 (API Endpoints)
- 采用
go-chi实现基础 REST 路由,包装出高可读性、标准的 REST 响应 JSON。 - 后端不做任何 IP 定位;
/api/dashboard仅接收前端传入的province参数,缺省时回退默认省。 - 实现车辆档案的管理 API 及调价波动爱车成本变动测算接口。
- 采用
- 阶段三:H5 移动响应式前端与定位组件编写 (H5 Frontend)
- 采用极简 AlpineJS 编写前端页面,确保免除 Webpack 等繁琐打包。
- 利用 Tailwind 编写兼顾手机端/大屏的响应式油价卡片、倒计时看板。
- 嵌入 ECharts,实现自适应的国际油价波动与国内油价阶梯图。
- 编写前端定位 JS 模块:浏览器
Geolocation→ 高德AMap.Geocoder逆编码 → 高德 IP 兜底。 - 实现"省份选择页":首页左上角省份入口 → 独立页面,顶部最近 5 个历史省份(
localStorage+ LRU)+ 下方全部 31 省网格,选中后返回刷新。
- 阶段四:多渠道推送与部署打包 (Pushes & Docker)
- 接入飞书、微信、钉钉的 Webhook 群机器人。
- 接入标准的 SMTP 发件机制。
- 编写 Dockerfile 镜像与
docker-compose.yml,打通服务器或 NAS 端一键秒级部署。