Files
maxfandClaude d6d034727b feat: 邮箱找回密码 + 日志体系完善 + SMTP 配置补全
## 功能
- 邮箱找回密码:新增 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>
2026-09-28 10:26:59 +08:00

29 KiB
Raw Permalink Blame History

⛽ 汽油价格监控软件(Oil Price Monitor)— 详细业务与技术架构设计书

状态:已评审确定 · 第一期方案(1-B, 2-A, 3-交互强化)+ 多用户身份模型(匿名浏览 + 登录私密区)
版本:v1.2
更新日期:2026-09-23
定位:全栈轻量级、服务器/NAS 部署、H5 移动响应式、API-First 设计的个人油价看板与加油预警专家。


目录

  1. 业务背景与领域知识
  2. 功能模块划分
  3. 核心算法:油价预测模型
  4. 系统架构与技术选型
  5. 数据模型设计(Database Schema)
  6. API 接口设计(API-First 微信小程序准备)
  7. H5 前端交互设计(定位与切换)
  8. 关键业务流程
  9. 多渠道通知模板设计
  10. 开发迭代路线图

一、业务背景与领域知识

国内成品油价格受国家政策严格管控,其变动并非实时连续波动,而是呈现典型的阶梯式特征。为了向车主提供真正有价值的加油决策("今天加"还是"明天加"),软件必须紧密围绕发改委的定价机制进行设计。

1.1 定价机制核心规则

  1. 10个工作日一调:调价周期为10个工作日。周六、周日及法定节假日不计入工作日。
  2. 50元/吨触发红线:
    • 估算的汽柴油调价幅度 ≥ 50元/吨,发改委于调价日当天下午(通常为 16:30 - 17:30)发布公告,并于**当日 24:00(即次日 00:00)**起正式生效。
    • 调价幅度 < 50元/吨,则本次不作调整(搁浅),其未调金额累加或冲抵至下一个调价周期。
  3. 地板价与天花板价:
    • 当国际原油均价 > 130美元/桶 时,国内汽柴油价格原则上不提或少提(天花板机制)。
    • 当国际原油均价 < 40美元/桶 时,国内成品油价格不再下调(地板价机制)。
  4. 价格口径区别:
    • 发改委公布价:为“最高零售价格(元/吨)”。
    • 省份/城市零售价:各省发改委根据国家标准,折算成“元/升”的本地最高上限。由于运杂费及品质标准差异,各省零售价不一致。
    • 加油站实价:两桶油/民营站可在最高零售价内自主打折。本软件以官方最高零售价为准。

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)

  1. 国际油价同步:每日收盘后,自动请求新浪财经等无 Key API 获取 Brent、WTI 期货结算价及日内涨跌幅。
  2. 发改委公告爬取:调价日 16:30 - 18:00,以 2 分钟/次的频率轮询国家发改委价格司新闻发布页。提取:
    • 调价公告标题与发布时间
    • 吨级涨跌幅度(汽油、柴油各降/升多少元)
    • 本次调价执行的生效日期与时间
    • 原文链接,并保存原文快照
  3. 省份零售价爬取:
    • 在发改委发布新价后,同步抓取 qiyoujiage.com 或主流汽车门户,实时获取 31 省市的 92#/95#/98# 汽油及 0# 柴油每升单价,实现自动化同步。

M2 · 预测分析模块 (Prediction & Analytics)

  1. 变化率估算(核心算法):
    • 追踪当前周期的已过天数和剩余天数。
    • 结合每日国际原油变化,自动计算“三地原油变化率移动平均”。
    • 预测本次调价的方向(上涨/下跌/搁浅)及幅度(元/吨 \rightarrow 折合元/升)。
  2. 置信度度量 (Confidence Rating):
    • 计价周期 T-10 天至 T-8 天:置信度“低”(处于大盘震荡期)。
    • T-7 天至 T-3 天:置信度“中”。
    • T-2 天至 T-1 天:置信度“极高”(数据基本定型)。
  3. 历史统计分析:
    • 年度累计调价次数,其中上涨、下跌、搁浅各多少次。
    • 汇总年度每升累计涨跌幅,并生成历史价格波动阶梯图。

M3 · 车主用车档案与成本核算 (Personal Fuel Cost)

  1. 爱车档案管理:
    • 支持添加多台车辆,配置:名称、爱车省份、推荐油号(92#/95#/98#/0#)、油箱容量 (L)、平均百公里油耗 (L/100km)、月均行驶里程 (km)。
  2. 一箱油变动感知(核心痛点):
    • 当发改委公布调价但未正式生效的 7 个小时内(17:00 \sim 24:00),系统自动计算本次调价对用户爱车的具体影响: \text{单次加油成本变化(元)} = \text{车辆油箱容量(L)} \times \text{本省每升涨跌额(元/升)} \text{月度出行成本变化(元)} = \frac{\text{月均里程(km)}}{100} \times \text{百公里油耗} \times \text{每升涨跌额(元/升)}
  3. 加油建议建议器:
    • 涨价前 \rightarrow “今晚24点后加满一箱多花 X 元,建议今天去加油!”
    • 降价前 \rightarrow “今晚24点后加满一箱可省 Y 元,建议留到明天再加!”
  4. 注:“加油记账历史”与“长期油耗账本”在一期暂不实现,保留表结构和 API 的扩展空间。

M4 · 移动响应式 Web 界面 (Responsive Web App / H5)

  1. 纯 H5 适配手机端与宽屏:采用简洁的卡片化设计,在手机屏幕上实现完美纵向排版,在桌面浏览器上自动横向响应。
  2. 定位与切换省份:
    • 前端坐标定位优先:进入页面时,优先使用浏览器 Geolocation API 获取设备精确定位(经纬度),再调用高德地图 JS API 的逆地理编码(AMap.Geocoder)将坐标反解为所在省份。不使用后端 IP 定位(IP 定位误差大、只能到城市级、且依赖第三方 IP 库,无法满足"设备真实所在"的需求)。
    • 定位降级链:坐标定位因用户拒绝授权 / HTTP 非安全域 / 浏览器不支持而失败时 → 降级为高德 IP 定位(仅作弱兜底)→ 再失败则回退到最近选择的历史省份 / 默认省份(详见第七章 7.2)。
    • 需求方需自备高德 JS API Key(免费额度充足,个人开发够用),配置文件里统一管理。
    • 省份选择页(独立页面):首页左上角常驻显示当前省份入口,点击跳转到独立的省份选择页——顶部展示最近选择的 5 个历史省份(横排芯片,localStorage 缓存 + LRU 淘汰),下方网格展示全部 31 省;选中后返回首页并刷新(详见第七章 7.2)。
  3. 国际原油趋势图:
    • 展示近期 Brent 与 WTI 的走势图。
  4. 本省油价卡片:
    • 醒目显示 92#、95#、98#、0# 的今日最高限价。
  5. 历史调价记录表:
    • 列表展示历史每一次官方调价文号、幅度和生效时间。

M5 · 推送通知模块 (Pushes & Notifications)

  1. 飞书/企业微信/钉钉群机器人:推送富文本 Markdown 卡片到指定的群组。
  2. SMTP 邮件推送:向配置的邮箱发送油价变动。
  3. Windows 桌面原生弹窗:Go 服务在本机后台运行时,可配置调用 Windows Toast 消息进行桌面弹窗(可关闭)。
  4. 推送触发时机:
    • 预测预警(T-1 天 20:00):涨跌幅超用户阈值(默认 \pm 0.05 元/升)时发出。
    • 官方锤定(调价日 ~17:30 官宣后 10 分钟内):发送加油实战决策通知。

三、核心算法:油价预测模型

由于三地原油加权平均公式的权重细节并未完全向公众公开,我们的预测将基于国际油价收盘走势进行模拟。

3.1 周期计价法

  1. 一个计价周期包含 10 个有效工作日。
  2. 每日更新计算原油变化率 $R$: R = \frac{\text{本计价周期已过工作日的三地原油均价} - \text{上一个计价周期三地原油均价}}{\text{上一个计价周期三地原油均价}} \times 100\%
  3. 吨级变动预测: \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 会话 Cookie
  • POST /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 首页左上角省份入口

  1. 首页顶部导航栏左上角固定展示当前选中的省份(定位成功后的省份,或用户最近一次手动选择的省份),显示格式:📍 北京。
  2. 省份名右侧带 ▾ 下箭头图标,暗示可点击进入选择。
  3. 点击该入口 → 前端路由跳转至独立的"省份选择页"(H5 对应 /province 页面;微信小程序二期对应独立 Page pages/province/province,逻辑与数据接口完全复用)。

7.2.2 省份选择页布局(自上而下两个区块)

┌─ 区块一:历史省份(无历史记录时不显示)────┐
│  [四川] [广东] [北京] [上海] [江苏]      │  ← 横排芯片,最多 5 个
└──────────────────────────────────┘
┌─ 区块二:全部省份(31 省)─────────────┐
│  北京  天津  河北  山西  内蒙古          │
│  辽宁  吉林  黑龙江  上海  江苏          │
│  ...(响应式网格,手机端自适应列数)    │
└──────────────────────────────────┘

7.2.3 历史省份区(recent_provinces)

  1. 历史足迹只记录"用户手动切换"的省份;自动定位得出的省份不写入(避免换环境/换设备自动污染历史列表)。
  2. 数据源:localStorage.recent_provinces(JSON 数组),最大长度 5。
  3. LRU 淘汰规则:切换了已在列表中的省份 → 提到第一位;超限 5 个 → 剔除最久未访问的。
  4. 当前选中的省份若在历史中,芯片上显示"当前"高亮角标。
  5. 点击历史芯片 → 立即选中并返回首页(不必翻到下方全量网格)。

7.2.4 全部省份区

  1. 按华北 / 东北 / 华东 / 华中 / 华南 / 西南 / 西北分组,或按拼音首字母排序,网格展示 31 个省级行政区。
  2. 每个省份项显示名称 + 当前 92# 价格角标(如 北京 · 7.78),便于跨省对比。
  3. 点击任意省份 → 写入历史 → 返回首页 → 刷新 dashboard 数据。
  4. 当前选中省份在网格中用高亮边框/底色标识。

7.2.5 返回行为

  1. 未选择直接返回 → 首页保持原省份不变。
  2. 已选择 → 首页左上角更新为新省份,全部数据随之刷新。
  3. 兼容手机返回手势(左滑/返回键),与 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 端一键秒级部署。