Skip to content
ClickistPublic

About

猜鼠标 Wordle —— 外设主题猜谜游戏(基于 csgofriberg 框架重设计)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

鼠一把 (mousedle)

猜鼠标游戏 —— 类 Wordle 玩法 + 实时多人对战(基于 csgofriberg 框架重构建)

CI and Docker License: AGPL-3.0 Node.js ≥ 26 pnpm workspaces ghcr.io

TypeScript React 18 Vite Express Socket.IO PostgreSQL Redis

玩法 · 功能特性 · 快速开始 · 部署 · 鼠标数据 · 贡献


本地开发

环境要求:Node.js 26、pnpm 11.13.1。若 pnpm 尚未安装,可执行 npm install --global pnpm@11.13.1。

pnpm install

# Redis >= 6.2 必需(XAUTOCLAIM;PoW 与开局服务依赖)
# Windows 推荐 redis-windows 7.4 便携版,跑起来即可:
#   redis-server.exe --port 6379 --save "" --appendonly no

pnpm migrate                    # SQLite 建表;空库会自动导入鼠标种子
# 后续需要刷新种子数据时再执行:
# pnpm seed
pnpm --filter server dev       # 后端 http://localhost:3000
pnpm --filter client dev       # 前端 http://localhost:5173 (代理 /api -> 3000)

pnpm test                      # 全量测试,需要 Redis 在跑
  • 无 Redis 时后端可以 degraded 启动,但开局/PoW/多人不可用。
  • scripts/build-mouse-dataset.mjs 可从 eloshapes 快照重新生成种子数据集。
  • data/*.sqlite3 已被 gitignore。

玩法

输入鼠标名称,系统按 品牌 / 品牌属地 / 形状 / 尺寸 / 重量 / 长度 / 宽度 / 高度 / 连接方式 / 传感器 逐属性给出对比反馈:

  • 🟩 绿色 —— 该属性与答案完全一致
  • 🟨 黄色 —— 接近(品牌属地同大洲、数值相差不大)
  • ↑↓ 箭头 —— 数值型属性提示答案更高或更低
  • 缺失字段(如部分鼠标没有传感器)会跳过判定,不按对错计

8 次机会内猜出目标鼠标即获胜;猜中与答案同规格的兄弟款(品牌、规格指纹完全一致的不同条目)同样判胜。单人模式与每日挑战中,输入联想只提示当前难度池内的鼠标;查鼠标页可搜全库。

功能特性

  • 🎮 单人模式 —— 小白 / 潮男 / 扫地僧三档难度池,进行中对局可断线续玩
  • 📅 每日挑战 —— 每日一只目标鼠标,同样分三档难度,当日排行榜
  • 🌐 多人联机 —— 经典对战 / 合作接力 / 2v2 接力三种模式,BO1/3/5/7 赛制、随机匹配、5 位房间码、观战;每小局限时 120 秒,断线即时通知、同身份可重连,30 秒未归判负
  • 🔍 查鼠标 —— 模糊搜索鼠标资料,附购买直达卡片(联盟转链)
  • 📊 统计与回放 / 🏆 排行榜 / 📢 公告
  • 👤 免安装即玩 —— 游客可直接玩单人与每日挑战,战绩按浏览器本地标识记账,登录后自动并入账号;多人随机匹配需注册并完成邮箱验证
  • 🌏 多语言 —— 简体中文 / English / 日本語;前后端交互仅传递错误码,文案统一在前端翻译
  • 🎨 双主题 —— 深色 / 浅色,首次访问跟随系统偏好
  • 🛡 人机验证 —— 公开接口由 WASM 工作量证明保护(Rust 编译,仓库内置预编译产物);注册等敏感操作另接入 GeeTest 行为验证
  • 🛠 管理后台 —— 鼠标增删改、JSON 批量导入、外部 API Token、公告管理
  • 🖥 SEO —— 构建期生成 robots.txt、sitemap.xml 与 /mice/<handle>/ 鼠标百科静态页,并为每路由输出 canonical

技术栈

层 技术
前端 React 18 + Vite + TypeScript + React Router + Zustand
后端 Node.js + Express + TypeScript
数据库 本地开发支持 SQLite;生产 Docker 镜像固定使用 PostgreSQL
缓存/实时 Redis + Socket.IO(Redis Adapter 跨实例广播)
认证 JWT + bcrypt(HttpOnly Cookie,客户端不存明文令牌)
校验/测试 Zod / Vitest
包管理 pnpm workspaces

快速开始

环境要求:Node.js ≥ 26、pnpm、Redis(本地开发可降级为内存模式);SQLite 开箱即用,无需额外数据库。Rust 工具链可选——仅在需要重新编译 PoW WASM 时安装,默认使用仓库内置的预编译产物。

pnpm install
cp .env.example .env                 # 可选,有默认值
pnpm migrate                          # 首次启动必须先初始化数据库
pnpm dev                             # server: 3000, client: 5173

访问 http://localhost:5173 。公开注册的账号默认都是普通用户,创建或重置管理员:

ADMIN_USERNAME=admin ADMIN_PASSWORD='至少12位强密码' pnpm create-admin

运行时行为说明

  • Redis 默认连接 redis://127.0.0.1:6379;生产环境建议 REDIS_REQUIRED=true,避免 Redis 故障时降级为仅适合单实例的内存模式
  • 生产环境强制要求 PostgreSQL、至少 32 字节随机 JWT_SECRET 和 REDIS_REQUIRED=true,并且必须配置 GeeTest(GEETEST_CAPTCHA_ID/GEETEST_PRIVATE_KEY 成对配置),否则应用启动即失败
  • 前端请求统一 20 秒超时;GET 与开局类幂等请求在断网/超时后自动补试一次,猜词不自动重试;管理后台批量导入/导出超时放宽到 120 秒
  • 访客显示 ID 使用 HMAC-SHA256 派生,可用 GUEST_ID_SALT 配置独立盐(未配置时复用 JWT_SECRET)
  • 单人进行中的对局只保存在 Redis,1800 秒(30 分钟) 无有效操作自动过期;猜中、次数耗尽或查看答案后才写入数据库,主动离开或重新开始只清理临时状态、不产生历史战绩

常用脚本

命令 说明
pnpm dev 同时启动前后端开发服务
pnpm build 构建 PoW WASM + 前端 + 鼠标百科静态页/sitemap(联网抓产品图,缓存于 data/mice-img)+ 编译后端
pnpm start 生产模式启动(server 托管 client/dist)
pnpm test 运行前后端测试
pnpm migrate 初始化数据库结构 + 种子鼠标
pnpm seed 按名称刷新已有种子规格,并补入缺失的鼠标
pnpm create-admin 显式创建或重置管理员
pnpm loadtest 运行 HTTP 缓存接口与多人建房负载测试

切换 PostgreSQL

修改根目录 .env:

DB_CLIENT=pg
DB_URL=postgres://user:pass@localhost:5432/csgofriberg

Redis 用途

展开查看
  • HTTP 与 Socket.IO 分布式限流
  • HttpOnly Cookie 会话、实时角色校验和匿名身份签名绑定
  • /api/players/list 版本化缓存、ETag 与跨实例失效通知
  • 排行榜、公告等热点查询缓存
  • 多人房间快照、身份索引、分布式房间锁和匹配队列
  • 回合超时、断线判负和房间清理的可恢复调度
  • Socket.IO Redis Adapter 跨实例广播
  • Redis Stream 多人战绩持久化重试

Docker 生产部署

生产环境使用 PostgreSQL 专用的精简 Docker 镜像(distroless 运行时,不含 Rust、pnpm、TypeScript、Vite、源码、测试与 SQLite 驱动)。GitHub Actions 自动执行测试、前后端编译、linux/amd64 镜像构建并发布到 ghcr.io/clickist/mousedle。

Docker Compose 部署、自动数据库迁移、管理员创建、更新和回滚方法见 deploy/README.md。

管理员按需外部作弊分析的 Bearer 鉴权与 JSON 展示契约见 docs/cheat-analysis-api.md。

鼠标数据

鼠标数据集来自 eloshapes 鼠标签名的快照,经 scripts/build-mouse-dataset.mjs 转换生成,当前种子数据为 server/src/db/seeds/mice.json(1617 只鼠标)。种子包含品牌、品牌属地、形状、尺寸、重量、长度、侧键、连接方式等猜测属性,以及传感器/DPI/轮询率等仅用于揭晓展示的字段。可用 pnpm seed 导入数据库。数据纠错与新增鼠标可重新生成该 JSON,或通过管理后台批量导入。

构建前端时,scripts/build-mouse-pages.mjs 会为每只鼠标生成 /mice/<handle>/ 静态百科页与 sitemap.xml(站点域名由根 package.json 中 build 命令的 --site 参数控制),由服务端托管,图片资源缓存一周。

难度分级

难度分档(小白/潮男/扫地僧/禁用)由 tier-assignment.json 描述:品牌级名单之外,modelOverrides 支持按鼠标全名对个别型号调档(值为 easy/normal/hard/disabled,对应小白/潮男/扫地僧/禁用)。调整流程:

  1. 浏览器打开 scripts/tier-editor.html 拖拽分档(支持型号级微调,导出 JSON);数据块与种子脱节时先跑 node scripts/build-tier-editor.mjs 重新生成。
  2. node scripts/apply-tier-assignment.mjs 将导出名单写入 seeds/mice.json。
  3. pnpm seed 落库。

外部鼠标更新 API

管理员可在管理后台的 API Token 页生成最长 365 天有效的 Bearer Token。明文只在创建时返回一次,服务端仅保存 SHA-256 哈希;每位管理员最多保留 20 个有效 Token,撤销后立即失效。

外部 API 不需要浏览器 PoW。当前 Token 不区分端点权限,持有者既能提交待审核变更,也能调用直接写入端点,因此只应发放给可信服务。请求统一携带:

Authorization: Bearer csgf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

当前限流为全局每 IP 1000 次/分钟、外部 API 鉴权前每 IP 120 次/分钟、鉴权后每 Token 60 次/分钟。外部 API 的专用限流失效关闭:超过限制返回 429 RATE_LIMITED,限流服务异常返回 503 RATE_LIMIT_UNAVAILABLE。

可用端点如下:

端点 当前行为
POST /api/external/player-change-submissions 为已有鼠标提交字段级待审核变更;单次 1-100 只,不会立即修改鼠标数据
GET /api/external/players/export 导出全部鼠标及难度成员关系(含 mouseId),响应为 players.json;仅需 API Token,不需要 PoW
POST /api/external/players 直接新增单个鼠标,成功返回 201 { "id": number }
PUT /api/external/players/:id 按 ID 直接部分更新鼠标,成功返回 200 { "ok": true }
POST /api/external/players/import 按名称直接批量 upsert;单次 1-2000 只且请求内名称不可重复,返回 created/updated 数量

待审核接口适合外部数据源报送现有鼠标的纠错。每项使用 mouseId、name 或二者定位鼠标;同时提供时必须指向同一只鼠标,同一请求不能重复提交同一鼠标。changes 必须至少包含一个字段:

curl -X POST 'https://example.com/api/external/player-change-submissions' \
  -H 'Authorization: Bearer csgf_your_token' \
  -H 'Content-Type: application/json' \
  -d '{
    "players": [
      {
        "mouseId": 123,
        "changes": {
          "brand": "Logitech",
          "weight": 63,
          "difficulties": ["normal", "easy"]
        }
      }
    ]
  }'

有实际差异时返回 201 { "submissionId": number, "submitted": number, "unchanged": number };所有值均未变化时返回 200,其中 submissionId 为 null。submitted 与 unchanged 统计的是字段数,不是鼠标数。管理员在 鼠标变更审核 页逐项批准或拒绝;批准前若该字段已被其他操作改动,该项会标记为 conflict,不会覆盖新值。

可写字段为 name、brand、country、continent、shape、size、weight、length_mm、side_buttons、wireless、display(揭晓展示的规格对象:宽度/高度/传感器等)、is_enabled、difficulties。其中 shape 仅接受 对称、人体工学、非对称、垂直,size 仅接受 小型、指尖、中型、大型,difficulties 当前仅接受 easy、normal、hard(难度池为累进式:easy ⊂ normal ⊂ hard)。

直接写入端点用于受信任的完整同步任务。新增鼠标至少需要 name、brand 和 weight;批量导入应按完整记录提交。更新已有鼠标时,导入项省略 difficulties 或 is_enabled 会保留原值,其他带默认值的字段若省略则可能写入默认值。部分更新接口只修改显式传入的字段。

外部 API 不提供永久删除端点;导出端点返回完整鼠标字段、mouseId 及难度成员关系。同步源可将 is_enabled 设为 false,使鼠标立即退出目标池与猜测列表,同时保留历史对局。所有错误响应均使用 { "code": "..." } 的机器可读格式。

项目结构

server/src
├── config.ts          # 环境配置
├── db/                # Knex 实例、建表、种子数据
├── middleware/        # 认证、Zod 校验、限流、PoW、错误处理
├── routes/            # auth / players / game / daily-challenge / stats / leaderboard / announcements / admin / pow / runtime-config / external-players
├── services/          # 游戏判定、鼠标缓存、房间状态、战绩队列等
└── socket/            # 多人房间系统
client/src
├── api/               # axios 封装、socket 单例、鼠标列表缓存
├── config/            # 难度、联盟购买链接、友链等前端配置
├── store/             # auth / theme / guest 等轻量状态
├── i18n/              # 中 / 英 / 日 文案与错误码翻译
├── utils/             # 难度/属地/购买链接等工具
├── components/        # Page / GuessBoard / GuessInputBar / PurchaseCard / DataTable / admin/*
├── assets/ styles/    # 静态资源与分层样式(tokens→shell→controls→游戏→响应式)
└── pages/             # Home / SingleGame / DailyChallenge / MultiLobby / MultiRoom / Search / Stats / ...

贡献

  • 🐛 问题反馈 / 功能建议 —— 请使用对应的 issue 模板
  • 📊 鼠标数据问题:通过管理后台批量导入纠错,或用 scripts/build-mouse-dataset.mjs 重新生成种子数据
  • 提交 PR 前请运行 pnpm test 与 pnpm build;所有用户可见文案需同步维护中/英/日三语(client/src/i18n/resources.ts)

许可证

本项目基于 AGPL-3.0 开源。

About

猜鼠标 Wordle —— 外设主题猜谜游戏(基于 csgofriberg 框架重设计)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages