# Task 10-b · full-stack-developer · P2/#4 云排行榜 mini-service > 本文件为本 agent 在 Task 10-b 的工作记录,供后续 agent 查阅。 ## 任务概述 为「回响星核 / Echo Nexus」v0.8.2 实现 P2/#4 工单:把信标本机排行榜(纯 localStorage)升级为云端 Top100,让玩家间可以比较分数。 ## 阅读的前置工作 - `/home/z/my-project/worklog.md`(v0.8.1 项目状态,10 大系统,四色全息规范) - `/home/z/my-project/src/lib/game/beacon.ts`(实际 1161 行 — worklog 信息滞后,v0.8.2 已加限时挑战 `claimTimedReward`) - `/home/z/my-project/src/components/game/BeaconPanel.tsx`(实际 953 行 — 已含限时挑战 amber 主题) - `/home/z/my-project/src/store/gameStore.ts`(1241 行 — trackBeacon + 3 个 claim action:grantBeaconReward/claimWeeklyBeacon/claimTimedBeacon/claimChainReward) - `/home/z/my-project/Caddyfile`(:81 网关,按 ?XTransformPort=* 转发到对应端口) - `/home/z/my-project/examples/websocket/server.ts` + `frontend.tsx`(mini-service + 前端连接参考) - `/home/z/my-project/.zscripts/dev.sh`(mini-service 自动扫描启动逻辑,发现 mini-services 目录下子服务自动 `bun run dev`) - `/home/z/my-project/agent-ctx/9-b-full-stack-developer.md`(前一个 agent 的信标系统扩展记录) - `/home/z/my-project/mini-services/star-tide-service/`(已存在的 P3 #10-c 服务,端口 3031) ## 实现细节 ### 1. mini-service(`mini-services/leaderboard-service/`) **目录结构**: ``` mini-services/leaderboard-service/ ├── package.json # 独立 bun 项目,type=module ├── index.ts # Hono 入口(182 行) ├── bun.lock # 自动生成 ├── node_modules/ # hono 独立安装 └── README.md # API 文档 ``` **package.json**: - `"dev": "bun --hot index.ts"`(规范要求的热重载脚本) - `"start": "bun index.ts"`(无热重载,用于稳定运行) - 依赖:hono ^4.6.14 **index.ts 设计要点**: - Hono + `cors()` 中间件,origin: * 全开放(前端跨端口必须) - 内存数组 `entries: BeaconScoreEntry[]`,最多 1000 条,按 score 降序 - 同分排序:先看用时短,再看提交早 - 防刷:`lastSubmitAt: Map`,10 秒内只接受 1 次,返回 429 + retryAfterMs - 4 个路由: - `GET /` → 健康检查 `{service, version, ok, uptime}` - `GET /api/leaderboard` → Top100 `{entries, total}` - `POST /api/leaderboard` body `{entry}` → schema 校验 → 推入 → 排序 → `{entries, total, rank}` - `GET /api/leaderboard/stats` → `{totalSubmissions, uniquePlayers, topScore}` - OPTIONS 预检由 hono/cors 自动处理,返回 204 + 完整 CORS 头 - 端口固定 3030,`export default { port, fetch }` 标准 bun 模式 **启动调试经验**: - `bun --hot` 在本沙盒环境下不稳定,多次启动后约 5-10s 内被 kill(即使 setsid + nohup) - 解决方案:用 `bun index.ts`(无 --hot)+ 双 fork 模式:`( bun index.ts >/tmp/log 2>&1 & )` - 双 fork 后 PPID=1(reparent 到 init),进程脱离原 bash session,uptime 4+ 分钟稳定 ### 2. beacon.ts 扩展(1161 → 1290 行,+129 行) **三个 claim 函数返回值扩展(非破坏性)**: - `claimBeaconReward` 返回值新增 `entry: BeaconScoreEntry` - `claimWeeklyReward` 返回值新增 `entry: BeaconScoreEntry` - `claimTimedReward` 返回值新增 `entry: BeaconScoreEntry` - 用途:让前端 / gameStore 能拿到完整 entry 提交到云端 **新增云排行榜模块**: - `BEACON_CLOUD_PORT = 3030` 常量 - `BEACON_CLOUD_LAST_SUBMIT_KEY = "echo-nexus-beacon-cloud-last-submit-v1"` localStorage key - 3 个响应接口:`CloudLeaderboardResponse` / `CloudSubmitResponse` / `CloudStatsResponse` - `loadLastCloudSubmitTimestamp()` / `saveLastCloudSubmitTimestamp(ts)` - `fetchCloudLeaderboard(): Promise` — GET `/api/leaderboard?XTransformPort=3030` - `submitCloudScore(entry): Promise` — POST,成功返回 rank ≥ 1,失败返回 -1,自动记录 ts - `fetchCloudStats(): Promise` - **所有 fetch 用相对路径 + ?XTransformPort=3030,禁止 localhost:3030** - try-catch 包裹,失败时静默返回空/null,不抛异常 ### 3. gameStore.ts 集成 - import 新增 `submitCloudScore` - `claimWeeklyBeacon` action:领奖成功后 `void submitCloudScore(res.entry)`(fire-and-forget,不 await,不阻塞) - `claimTimedBeacon` action:同上 - `claimBeaconReward` (daily) 由 BeaconPanel 直接调用,不通过 gameStore ### 4. BeaconPanel.tsx 重写(953 → 1170 行,+217 行) **新增 import**: - `fetchCloudLeaderboard` / `submitCloudScore` / `loadLastCloudSubmitTimestamp` from beacon - 4 个 lucide 图标:`RefreshCw` / `Globe` / `WifiOff` / `Loader2` - `useRef` from react **新增状态**: - `lbTab: 'local' | 'global'`(默认 local) - `cloudEntries: BeaconScoreEntry[]` - `cloudLoading: boolean` - `cloudError: string | null` - `cloudFetched: boolean` - `mySubmitTs: number` - `cloudFetchingRef` (防并发) **新增逻辑**: - `refreshCloudLeaderboard` callback:拉取云端榜 + 防并发 + 空 entries 时显示"成为首位登顶者" - useEffect:切到全球 tab 自动拉取(仅首次)+ 初始化读取 mySubmitTs - `handleClaimDaily`:领奖后 `void submitCloudScore(res.entry).then(rank => ...)` — 若用户在全球 tab 自动刷新 - `handleClaimTimed` / `handleClaimWeekly`:领奖后更新 mySubmitTs + 若在全球 tab 自动刷新 **排行榜 UI 重写为双 tab**: - 顶部 tab 切换:「本地」(amber 主题) / 「全球」(emerald 主题) — 用 `