No description
  • Go 45.2%
  • JavaScript 40.4%
  • CSS 13.5%
  • Dockerfile 0.5%
  • HTML 0.3%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-10 19:10:18 +08:00
cmd/server feat: 邮箱找回密码、邮箱验证与 SMTP 发信配置 2026-09-10 19:10:18 +08:00
docker fix: docker数据目录权限、SW缓存与出牌流畅度 2026-09-07 09:11:42 +08:00
internal feat: 邮箱找回密码、邮箱验证与 SMTP 发信配置 2026-09-10 19:10:18 +08:00
web feat: 邮箱找回密码、邮箱验证与 SMTP 发信配置 2026-09-10 19:10:18 +08:00
.dockerignore feat: 斗地主 Web 完整实现与 Docker 支持 2026-09-05 11:06:20 +08:00
.gitattributes fix: docker数据目录权限、SW缓存与出牌流畅度 2026-09-07 09:11:42 +08:00
.gitignore feat: 斗地主 Web 完整实现与 Docker 支持 2026-09-05 11:06:20 +08:00
docker-compose.yml feat: 邮箱找回密码、邮箱验证与 SMTP 发信配置 2026-09-10 19:10:18 +08:00
Dockerfile fix: docker数据目录权限、SW缓存与出牌流畅度 2026-09-07 09:11:42 +08:00
go.mod feat: 斗地主 Web 完整实现与 Docker 支持 2026-09-05 11:06:20 +08:00
go.sum feat: 斗地主 Web 完整实现与 Docker 支持 2026-09-05 11:06:20 +08:00
README.md feat: 邮箱找回密码、邮箱验证与 SMTP 发信配置 2026-09-10 19:10:18 +08:00

PokerGames - 斗地主

一个基于 Go 与 React 的三人斗地主 MVP。玩法按常见竞技斗地主规则初始化:

  • 54 张牌,三人各发 17 张,3 张底牌;
  • 按座位顺序叫 0/1/2/3 分:叫到 3 分立即当地主;无人叫分自动重新发牌;叫分最高者成为地主;
  • 地主拿到底牌,地主与两名农民对抗,先出完手牌的一方获胜;
  • 服务端完整校验牌型(单张、对子、三带、顺子、连对、飞机、四带二、炸弹、王炸),接牌必须严格更大;
  • 接不上可选择“不出”,两人连续不出后由上一轮出牌者重新出牌。

规则参考:
百度百科:斗地主
Pagat: Dou Dizhu

技术栈

后端

  • Go 1.27 + Gin
  • GORM + SQLite
  • JWT 认证
  • WebSocket 房间状态广播

前端

  • React 18 + Vite
  • Ant Design 5
  • React Router
  • axios
  • WebSocket
  • Pokers 牌面、牌桌背景与斗地主音效资源
  • 支持标准版与 Emoji 版两套牌面,由后台统一切换

启动

首次运行安装前端依赖并生成编译产物:

cd web
npm install
npm run build
cd ..

之后只需启动后端,前后端共用 8080 端口:

go run ./cmd/server

Docker 运行

项目附带 Dockerfile(前端 Node 构建 → 后端 Go 编译 → Alpine 运行时,纯 Go SQLite 无需 CGO)与 docker-compose.yml:

docker compose up -d --build
  • 打开 http://localhost:8080;
  • SQLite 数据保存在 Docker 命名卷 poker-data(挂在容器 /app/data),重启/升级容器不丢数据;若改用宿主机目录绑定(如 ./data:/app/data)也无需手工授权——容器入口脚本会先修复数据目录归属,再以非 root 用户启动服务;
  • 前端会注册 Service Worker(/sw.js):首次访问后把音效、牌面、牌背与桌布等 pokers 资源缓存进浏览器,重复出牌/听音不再反复请求网络;改动音效或牌面后请更新 web/public/sw.js 顶部的 VERSION 并重新构建,旧缓存会自动清除;
  • 生产环境务必通过 .env 或环境变量覆盖 JWT_SECRET;OauthGo 第三方登录可在启动前用 OAUTHGO_* 注入,也可启动后在管理后台 /admin 在线配置;
  • 常用命令:docker compose logs -f poker 查看日志、docker compose down 停止(加 -v 才会删除数据卷)。

后端默认监听 http://localhost:8080,同时托管 web/dist 中的前端编译产物。React 前端路由会自动回退到 index.html,因此刷新任意前端路由不会返回 404。可通过 PORT、DATABASE_URL、JWT_SECRET 环境变量覆盖配置。

机器人支持通过管理后台“机器人 AI”设置或 BotAI 接口接入自定义策略。默认使用本地内置启发式策略;后台配置优先于 POKER_AI_ENDPOINT 环境变量。选择 HTTP AI 后,服务端会以 POST JSON 调用外部 AI:

  • 请求:{ "player": {...}, "hand": [...], "state": {...} }
  • 响应:叫分返回 { "type": "bid", "bid": 0-3 };出牌返回 { "type": "play", "cards": [...] };不出返回 { "type": "pass" }
  • 所有外部 AI 动作仍由服务端校验,调用失败或响应非法时自动使用内置策略。

后端目录按职责拆分:

  • cmd/server:服务启动入口;
  • internal/config:环境配置;
  • internal/model:GORM 数据模型;
  • internal/auth:JWT、角色和访问控制;
  • internal/game:牌组、牌局状态和内存房间;
  • internal/server:Gin 路由、HTTP/WebSocket 处理器。

打开 http://localhost:8080,注册三个账号后创建房间并加入即可开始。登录后可在“个人中心”设置头像与昵称(牌桌上展示昵称),头像支持本地上传(前端压缩后保存)或粘贴图片地址。

第三方登录(OauthGo,o.1v.fit)

接入 OauthGo 第三方登录聚合服务。配置可在管理后台(/admin)→ 第三方登录 · OauthGo 在线编辑并保存到数据库,立即生效;也可通过环境变量作为首次兜底:

环境变量 说明
OAUTHGO_BASE 服务地址,默认 https://o.1v.fit
OAUTHGO_APPID 应用 ID
OAUTHGO_APPKEY 应用密钥
OAUTHGO_TYPES 启用的渠道,逗号分隔,如 qq,gitee

管理后台保存的配置优先于环境变量;将表单留空可回退到环境变量/默认值。相关管理接口:GET/PUT /api/admin/oauth。

配置后:

  • 登录页出现“第三方登录”按钮;首次登录会自动创建本地账号(登录名为 渠道_随机串),并写入昵称/头像;
  • 已在“个人中心 → 第三方登录绑定”绑定过的账号,会直接登录对应本地账号;
  • 登录态下可随时绑定/解绑:不同第三方平台可同时绑定,但同一平台仅保留一个账号(绑定新账号会替换该平台的旧账号;同一 渠道+uid 不能被两个本地账号占用);任一已绑定平台均可一键登录;绑定页在 OauthGo 完成授权后自动回跳。

登录页支持“记住我”:勾选后登录凭据保存在浏览器长期存储中,未勾选则仅保存在当前会话;退出登录会同时清理两种存储。牌面、绿色牌桌背景和出牌/叫牌音效会从 web/public/pokers 加载。

访问 http://localhost:8080/admin 可打开管理员技术设置页面,查看服务状态并调整牌桌主题、音效和动画。创建或进入无效房间时,页面会显示可返回大厅的错误提示,不再停留在空白页面。

后台管理中的牌面设置会持久化到 SQLite;管理员可预览两套完整牌面并切换当前版本,游戏桌面会按照服务端当前配置加载对应牌面。

用户角色:

  • player:普通玩家,可创建、加入和进行游戏;
  • admin:管理员,可访问后台管理;
  • super_admin:超级管理员,第一个注册用户(UID = 1)自动获得;
  • guest:游客,可浏览大厅但不能创建、加入或操作游戏。

前端页面路径为 /login 登录页、/ 大厅、/profile 用户个人信息、/room/:id 牌桌、/admin 后台管理。

API

方法 路径 作用
POST /api/auth/register 注册
POST /api/auth/login 登录并获取 JWT
POST /api/auth/guest 游客进入
POST /api/auth/forgot-password 申请找回密码(向已验证邮箱发送验证码)
POST /api/auth/reset-password 用邮箱验证码重置密码
POST /api/auth/password 已登录用户修改密码
POST /api/auth/email-code 为待绑定邮箱发送验证码
POST /api/auth/email-verify 用验证码完成邮箱绑定/验证
GET /api/rooms 查看等待中的房间
POST /api/rooms 创建房间
POST /api/rooms/:id/join 加入房间
POST /api/rooms/:id/ready 切换准备状态
POST /api/rooms/:id/start 三人后发牌并开始叫牌
POST /api/rooms/:id/bid 叫牌
POST /api/rooms/:id/play 出牌(服务端校验牌型与大小)
POST /api/rooms/:id/pass 不出(两人连续不出后重新出牌)
POST /api/rooms/:id/restart 对局结束后重新开局
POST /api/rooms/:id/leave 离开等待中的房间
GET /api/me 当前用户资料与对局统计
PUT /api/me/profile 更新昵称 / 头像
GET /api/auth/oauth/providers 已启用的第三方登录渠道
GET /api/auth/oauth/start 发起第三方登录 / 绑定(mode=login|bind)
GET /api/auth/oauth/callback OauthGo 授权回跳,完成登录或绑定
GET /api/me/oauth 查看已绑定的第三方渠道
DELETE /api/me/oauth/:provider 解绑第三方渠道
GET /ws/rooms/:id 房间状态 WebSocket
GET /api/admin/stats 管理员查看基础统计
GET/PUT /api/admin/mail 查看 / 保存 SMTP 发信配置
POST /api/admin/mail/test 发送一封测试邮件

找回密码与邮箱验证

玩家注册时可填写邮箱(选填)。邮箱在“个人中心 → 邮箱绑定”通过 6 位验证码验证后,才可用于找回密码:

  • 验证码 10 分钟内有效、只能使用一次、连续输错 5 次作废,同一邮箱 60 秒内不可重复发送;
  • 未登录时在登录页点击“忘记密码?→ 用邮箱重置”,输入邮箱、收到的验证码和新密码即可重置;
  • POST /api/auth/forgot-password 对未注册的邮箱同样返回成功提示,避免通过接口探测账号是否存在;
  • 密码重置或修改后,此前签发的 JWT 全部失效(用户会看到“登录状态已失效,请重新登录”),需要重新登录;
  • 游客账号没有密码与邮箱,相关接口返回 403。

发信走 SMTP,可在管理后台(/admin)→ 邮件服务 · SMTP 在线配置并保存到数据库(发件人、服务器、端口、账号密码、隐式 TLS),也可用环境变量作为首次兜底:

环境变量 说明
SMTP_HOST SMTP 服务器,如 smtp.example.com
SMTP_PORT 端口,465 常配隐式 TLS,587 使用 STARTTLS
SMTP_USERNAME 登录账号(可留空表示匿名中继)
SMTP_PASSWORD 登录密码 / 授权码
SMTP_FROM 发件人邮箱
SMTP_TLS true 表示从连接建立即启用 TLS

未配置 SMTP 时验证码只会写入服务端日志([mail:not-configured]),玩家收不到邮件;配置完成后可在后台“发送测试邮件”验证。为安全起见,服务器未提供 TLS 时服务端会拒绝发送账号密码。

当前版本完成了认证、房间、发牌、完整叫分(含无人叫分重新发牌)、完整牌型识别与严格压牌比较、不出与轮次控制、胜负判定,以及等待房间的离开与对局结束后的重新开局。计分与牌局回放可在此状态机上继续扩展。

服务端每 5 秒执行一次生命周期检查:等待中的房间超过 60 秒没有玩家进入会自动删除;进行中的对局如果检测到玩家掉线超过 60 秒,会将对局强制结束并通知仍在线玩家。

大厅会显示等待房间的剩余销毁时间,已过期房间不会返回到大厅列表。对局中的玩家掉线后,其他玩家会看到重连倒计时,游戏操作会暂停;玩家在 60 秒内重连后自动恢复对局。

结构化的特点:每个阶段有任务