PersonalWebApplication/docs/startpage-weather-location.md

170 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 起始页:天气 + 定位(已实现)
导航起始页问候语下方展示「天气图标 · 城市 · 天气 · 气温」。
**前端不请求浏览器定位权限。定位只由后端按访问 IP 调高德 IP 定位完成;
查不到位置时不再调用和风,前端静默不显示天气。天气数据始终来自和风实时天气
(私钥只在后端)。**
## 架构
```
浏览器
└─ GET /api/web/weather (后端)
├─ 高德 /v3/ip?ip=<访问者IP> → 城市名 + 城市中心坐标
├─ 查不到位置 → 返回 failed前端不展示天气
└─ 查到位置 → 和风 /v7/weather/now?location= → 天气文字 / 气温 / 图标码
```
- 后端:`Server/src/weather.rs`(服务 + 限流 + 缓存,`get()` 只接收访问 IP
路由 `Server/src/routes/web.rs``/api/web/weather`(不接收前端位置参数)。
- 前端:
- `Client/src/api/weather.ts`、`Client/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 分钟刷新一次,正常用量远低于额度。
- 计数只在“真的调用了上游”时增加(命中缓存不计数)。
## 环境变量
### 后端
后端启动时读取(缺和风 JWT 时接口返回 `not_configured`,前端静默不展示;
缺高德 REST Key 时接口返回 `not_configured`,前端静默不展示):
| 变量 | 必填 | 说明 |
|------|------|------|
| `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 分钟有效的 token
> 以 `Authorization: Bearer` 调用。私钥**只放后端**,绝不进前端/仓库。
> 定位失败的处理:高德对**数据中心 / DNS / 部分移动·CGNAT / 境外 IP** 会返回空。
> 若**不设** `WEATHER_DEFAULT_LOCATION`,定位失败时接口直接返回 `failed` →
> **不再调和风**(省额度)→ 前端静默不显示天气。**线上有意不设兜底**,就是这个效果。
>
> 本地开发时访问者 IP 是 `127.0.0.1`,高德无法定位 → 想在本地看到天气,可临时设
> `WEATHER_DEFAULT_LOCATION` + `WEATHER_DEFAULT_CITY`(仅本地 `weather.env`,别带上线)。
## 本地运行
后端(在 `Server/`WSL 或 Windows 均可):
```bash
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`
```bash
# 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
```
启动:
```bash
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 类
(晴日/晴夜/多云阴/雨/雷/雪/雾霾),各画一个极简线性 SVG`WeatherIcon.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` |
- **gitea**WSL 里 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
```bash
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`)。