PersonalWebApplication/docs/startpage-weather-location.md
cc 5652ccc2f0 docs: 移除 GitHub origin 远程,文档改为仅 gitea 一个远程
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 20:17:04 +08:00

10 KiB
Raw Blame History

起始页:天气 + 定位(已实现)

导航起始页问候语下方展示「天气图标 · 城市 · 天气 · 气温」。 定位优先用前端高德 AMap.Geolocation(能 GPS 就 GPS失败退回后端高德 IP 定位; 天气始终来自和风实时天气(私钥只在后端)。

架构

浏览器
  ├─ AMap.Geolocation 前端定位JS API
  │     ├─ 浏览器原生定位GPS/WiFi精度高**需 HTTPS**)→ 坐标 + 城市
  │     └─ 失败/被拒 → 高德纯 IP 城市定位getCityInfo
  │           ↓ 带 lon,lat,city
  └─ GET /api/web/weather[?lon=&lat=&city=] (后端)
            ├─ 带了坐标 → 直接用,**不调高德**(省高德额度)
            ├─ 没带坐标 → 高德 /v3/ip?ip=<访问者IP> 兜底 → 城市名 + 城市中心坐标
            └─ 和风 /v7/weather/now?location= → 天气文字 / 气温 / 图标码

为什么前端定位 + 后端兜底两套并存: 前端原生定位GPS能到街道级、还能绕开 「数据中心 / CGNAT / 境外 IP 高德返回空」的痛点,但浏览器原生定位在非 localhost 的 HTTP 页面下被禁用。当前线上是裸 HTTP所以现阶段前端只会走它的 IP 退路(精度等同后端 IP 定位);等站点上了 HTTPSGPS 会自动开始生效,无需改代码。前端定位彻底失败时, 后端再用 REST Key 做一次 IP 定位作双保险。

  • 后端:Server/src/weather.rs(服务 + 限流 + 缓存,get() 接收可选前端坐标)、 路由 Server/src/routes/web.rs/api/web/weather(解析可选 lon/lat/city)。
  • 前端:
    • Client/src/lib/amap.ts:加载高德 JSAPI + AMap.Geolocation,封装 locate()
    • Client/src/api/weather.tsClient/src/hooks/useWeather.ts先定位再拉取30 分钟刷新)
    • Client/src/components/StartPage.tsx:统一持有天气状态,派生昼夜背景天气动效,并把天气传给 Greeting
    • Client/src/components/start/Greeting.tsx:展示「天气图标 · 城市 · 天气 · 气温」
    • Client/src/components/start/WeatherIcon.tsx:天气文字旁的静态图标
    • Client/src/components/start/WeatherFx.tsx:雨/雪 canvas 动态层

限流与缓存(后端内置)

  • 高德 300 次/天、和风 900 次/天,各自独立计数,北京时间 0 点归零
  • 任一服务商额度用尽 → 接口返回 { ok:false, reason:"quota_exceeded", provider } 前端显示「天气请求次数已用完,明日 0 点恢复」。
  • 缓存 10 分钟:前端带坐标时按 坐标 聚合缓存,未带时按访问者 IP 缓存; 前端每 30 分钟刷新一次,正常用量远低于额度。
  • 计数只在“真的调用了上游”时增加(命中缓存不计数)。 前端已定位(带了坐标)时不调高德、也不消耗高德额度,只消耗和风额度。

环境变量

前端ViteClient/.env.local不进仓库

高德 Web端(JS API) 密钥,给 AMap.Geolocation 前端定位用。与后端 AMAP_KEY两套。 不配则前端不定位、直接由后端 IP 兜底旧行为不会报错。Vite 在 dev 与 build 时都会加载 .env.local,故本地构建出的 dist 会内置此 KeyJS Key 本就暴露在前端,靠高德控制台 「安全域名」白名单约束,不是机密)。

变量 必填 说明
VITE_AMAP_JS_KEY 高德 Web端(JS API) Key
VITE_AMAP_JS_CODE 高德 安全密钥(securityJsCode)JSAPI 2.0 起与 Key 成对必需,缺它定位报 INVALID_USER_SCODE

后端

后端启动时读取(缺和风 JWT 时接口返回 not_configured,前端静默不展示; AMAP_KEY 仅 IP 兜底路径需要,前端总能定位时可不配):

变量 必填 说明
AMAP_KEY 兜底必填 高德 Web服务(REST) Key仅前端定位失败的 IP 兜底路径用
QWEATHER_HOST 和风专属 API Host(控制台给的,如 https://xxxx.qweatherapi.com
QWEATHER_PROJECT_ID 和风项目 ID → JWT 的 sub
QWEATHER_KEY_ID 和风凭据 ID → JWT 头的 kid
QWEATHER_PRIVATE_KEY_PATH 二选一 Ed25519 私钥(PKCS#8 PEM)文件路径(推荐,多行 PEM 用文件最省事)
QWEATHER_PRIVATE_KEY 二选一 直接给私钥 PEM 内容(可用 \n 转义换行)
AMAP_DAILY_LIMIT 默认 300
QWEATHER_DAILY_LIMIT 默认 900
WEATHER_CACHE_SECS 缓存秒数,默认 600
WEATHER_DEFAULT_LOCATION 内网/本地 IP 定位失败时的兜底坐标 经度,纬度(如 116.40,39.90
WEATHER_DEFAULT_CITY 兜底坐标对应的城市名

和风认证用 JWT(EdDSA/Ed25519):后端用私钥签发 15 分钟有效的 tokenAuthorization: Bearer 调用。私钥只放后端,绝不进前端/仓库。

定位失败的处理:高德对数据中心 / DNS / 部分移动·CGNAT / 境外 IP 会返回空。 若不设 WEATHER_DEFAULT_LOCATION,定位失败时接口直接返回 failed不再调和风(省额度)→ 前端静默不显示天气。线上有意不设兜底,就是这个效果。

本地开发时访问者 IP 是 127.0.0.1,高德无法定位 → 想在本地看到天气,可临时设 WEATHER_DEFAULT_LOCATION + WEATHER_DEFAULT_CITY(仅本地 weather.env,别带上线)。

本地运行

后端(在 Server/WSL 或 Windows 均可):

AMAP_KEY=xxx \
QWEATHER_HOST=https://xxxx.qweatherapi.com \
QWEATHER_PROJECT_ID=项目ID QWEATHER_KEY_ID=凭据ID \
QWEATHER_PRIVATE_KEY_PATH=./qweather-ed25519.pem \
WEATHER_DEFAULT_LOCATION=116.40,39.90 WEATHER_DEFAULT_CITY=北京 \
PORT=8080 cargo run

前端 cd Client && npm run dev,打开起始页即可看到天气行。

部署:改了后端时(重新编译)

改了 Server/ 下代码时上线需要重新编译 Rust 二进制

  1. WSL 里 cd /home/cc/InternetProject/Server && cargo build --release
  2. scp 覆盖服务器 /opt/internet-project/RustServer + 前端 dist/*
  3. 私钥与密钥建议写进服务器上的一个文件再统一加载,避免出现在命令历史里。 例如服务器放 /opt/internet-project/qweather.pem/opt/internet-project/weather.env
# weather.env 内容(不进仓库)
AMAP_KEY=xxx
QWEATHER_HOST=https://xxxx.qweatherapi.com
QWEATHER_PROJECT_ID=项目ID
QWEATHER_KEY_ID=凭据ID
QWEATHER_PRIVATE_KEY_PATH=/opt/internet-project/qweather.pem
PORT=8548
# 登录 / 入口门走 MySQL见 docs/auth-login-gate.md
DATABASE_URL=mysql://KarCharon:******@127.0.0.1:3306/InternetProject

启动:

ssh root@8.130.143.54 "pkill RustServer; cd /opt/internet-project && \
  set -a && . ./weather.env && set +a && nohup ./RustServer > server.log 2>&1 &"

天气图标方案

未引入图标库/字体,而是把和风 now.icon 码归成 7 类 (晴日/晴夜/多云阴/雨/雷/雪/雾霾),各画一个极简线性 SVGWeatherIcon.tsx stroke=currentColor 跟随文字颜色,矢量可缩放、离线可用,和玻璃风统一。 要更精细可换成和风官方图标集,但当前方案零依赖、足够清爽。

昼夜背景切换

背景图按昼夜在 light.* / dark.* 之间自动切换(图片放 Client/src/assets/startpage/,详见该目录 README

  • 昼夜判断StartPage.tsx):优先用 now.icon——150/151/152/153/350/351/456/457 判为夜、100/101/102/103/300/301/400/401 判为昼;其余无昼夜之分的天气码用 本地时间兜底18:0006:00 为夜)。每分钟一拍,跨时段会自动翻。
  • 回退:选中的图缺失 → 另一张 → 目录里排序第一张 → 中性渐变。

天气动态效果

WeatherFx.tsx 用一个轻量 canvas 粒子层按天气渲染(克制风格、低透明度、 尊重系统「减少动态效果」、kind='none' 不挂载):

天气(now.icon 效果
雨类 300399 斜向落雨;其中雷阵雨 302/303/304偶发闪电一闪
雪类 400499 及冷 901 缓慢飘落雪花
其余 无(静态)

层级:背景图(-z-20) → 渐变 scrim + 半透明黑遮罩(-z-10) → 天气动效(z-0) → 内容(z-10)。

本地预览开关(仅 npm run dev

不开后端 / 不等真实天气也能预览昼夜背景与动效,加 URL 参数即可:

  • ?bg=light / ?bg=dark —— 强制白天/夜晚背景
  • ?fx=rain / ?fx=snow / ?fx=thunder / ?fx=none —— 强制动效

例:http://localhost:5173/?bg=dark&fx=snow。这些参数 import.meta.env.DEV 为真才生效,线上无效,线上完全由真实天气驱动。

再次部署的两种情况

  • 只改前端(动效、背景、样式等):前端单独重发即可——npm run build → 清空服务器 static/ → 上传 dist/*。后端进程不用动(实时读磁盘静态文件)。
  • 改了后端Server/ 下任何代码):需在 WSL cargo build --release → 覆盖 /opt/internet-project/RustServer → 带环境变量重启(见上「部署:改了后端时」)。

Git 远程与推送

仓库现在只有一个远程2026-06-23 起移除了 GitHub origin

远程 地址 协议 / 认证 在 WSL 里能否直接推
gitea ssh://gitea@8.130.143.54:2222/cc/PersonalWebApplication.git SSH已配密钥 可直接 git push gitea main
  • giteaWSL 里 SSH 密钥已就绪,git push gitea main 直接成功。

  • GitHub已移除:原 origin = https://github.com/KarCharon/MyOwnWebPage.git HTTPS 在非交互环境无凭据会报 could not read Username for 'https://github.com' 需手动用 Personal Access Token,因此已删除。若以后要恢复,可重新添加并改用 SSH

    git remote add origin git@github.com:KarCharon/MyOwnWebPage.git
    git push origin main
    

行尾:仓库历史里部分文件曾从 Windows 带入 CRLF。在 WSLLF下编辑会让这些文件 整片显示为「已改动」,其实只是行尾差异(git diff --ignore-all-space 为空)。 提交前注意别把这类行尾噪声混进功能提交;要彻底消除可加 .gitattributes* text=auto eol=lf)。