一、前言:从“陌生人”到“正式玩家”的关键一环
在一款棋牌游戏里,每个玩家进入游戏时,都要先建立一层“身份”——这就是所谓的用户系统。没有用户系统,玩家就只能是一个“路过群众”,既没有专属账号,也无法记录金币、积分或胜率,更谈不上充值或商城。
所以,用户系统就像一扇门,玩家通过这扇门才能走进游戏的“世界”里。对于后端来说,如何设计这扇门,直接影响到后续的数据管理、安全、支付以及各种运营策略。
本篇我们就聚焦在**“用户系统与登录流程”**上,从以下几个方面展开:
- 账号体系的多种形态(游客、注册账号、第三方登录等);
- 基础数据库表设计和数据存储;
- 登录流程与 Token/Session 机制;
- 常见的安全措施与防护点;
- 实际开发中的一些细节与常见坑;
- 后续如何结合支付、实名制、风控等更高级需求。
希望通过本文,让你对“用户系统”从无到有的构建过程有一个完整的认识。
二、账号体系:多种“身份”选择,满足不同玩家需求
说到账号体系,很多人可能会先想到**“手机号+密码”或“邮箱+密码”**的传统注册方式。但对于棋牌游戏来说,考虑到推广速度、用户习惯、平台限制等,往往会提供多样化的登录方式。
2.1 游客登录
-
特点:
- 无需繁琐注册,一键进入游戏;
- 最适合“先体验后绑定”的模式,能快速留住初次下载的用户;
- 只需在本地生成一个唯一设备 ID(或 UUID),在后端做记录即可。
-
优点:
- 降低新玩家的注册门槛,提升初次留存;
- 省去了输入手机号/验证码的麻烦,让用户先尝尝“甜头”。
-
缺点:
- 游客账号往往只能绑定在当前设备,一旦卸载或更换手机,用户数据就可能丢失;
- 不利于深度运营,比如无法做精准营销,因为游客信息是匿名且缺乏完整资料。
常见做法:
- 后端分配给游客一个
guest_id
,同时下发一个 Token;- 提醒玩家后续可**“绑定手机号/微信”**等,以防数据遗失。
2.2 注册账号(手机号、邮箱、用户名)
-
传统而通用:
- 玩家可以通过输入手机号+验证码,或者邮箱+密码,来注册一个正式账号;
- 后端存储账号信息、密码(加密后)以及玩家基本资料(昵称、头像、性别、地区等)。
-
适用场景:
- 想构建更完整的用户社区或积分体系;
- 对实名制或防沉迷有较高要求(需要手机号绑定)。
-
要点:
- 密码加密:不在数据库中存明文,至少用 bcrypt/SHA256 + Salt。
- 验证码流程:手机号注册时常用短信验证码,需要接第三方短信服务;邮箱可以发激活邮件。
- 防止恶意批量注册:可以对同一 IP 或同一设备做频率限制。
2.3 第三方登录(微信/QQ/苹果/Google等)
-
优势:
- 大幅提升注册转化率:玩家只要点“微信一键登录”就搞定,无需再输入任何信息;
- 可以获取一定的社交关系或头像、昵称,让游戏里好友系统更容易落地(例如使用微信好友列表)。
-
流程:
- 客户端拉起第三方授权页,玩家同意授权后,会拿到一个授权码或access_token;
- 后端携带此授权码去第三方服务器验证,拿到用户唯一标识(如
openid
、unionid
),并获取用户基本资料(头像、昵称等); - 后端将此第三方标识和本游戏的用户账号进行绑定,如果是新用户就自动创建一个账号并分配 Token。
-
注意点:
- 刷新令牌:有些第三方登录的
access_token
有效期有限,要处理续期; - 与游客账号合并:如果玩家先以游客身份玩了一阵子,再用微信登录,如何把两者的数据合并?需要做逻辑处理。
- 不同平台之间的互通:如果玩家在 iOS 上用苹果登录,在安卓上又用微信登录,是否可视为同一个账号?这需要提前定义策略。
- 刷新令牌:有些第三方登录的
2.4 账号合并与绑定
- 为什么要做:
- 可能同一个玩家,一开始用游客账号试玩,然后又想绑定微信,再后来还想绑定手机号,以保证多端登录数据同步;
- 防止一个玩家拥有多个账号在同一个区服内作弊,或者多开引发问题。
- 如何做:
- 在数据库层面,对“用户账号表”做一个唯一用户 ID(如
user_id
),每种登录方式(游客/微信/手机号)都是这user_id
下的子记录; - 如果游客帐号和微信帐号绑定,就把这两种登录方式统一映射到同一个
user_id
上。
- 在数据库层面,对“用户账号表”做一个唯一用户 ID(如
三、数据库表设计:用户相关的数据如何落地
无论你是用 MySQL 还是其他数据库,都需要规划一个(或多个)表来存储用户信息。以下是一个常见的简化设计示例,供参考:
-- 用户主表:user_info
CREATE TABLE user_info (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
nickname VARCHAR(50) NOT NULL,
avatar_url VARCHAR(255),
gender TINYINT, -- 0未知/1男/2女
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
-- 账号表:user_account,用于存储各种登录方式
CREATE TABLE user_account (
account_id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL, -- 与 user_info.user_id 关联
account_type TINYINT NOT NULL, -- 1手机号 2微信 3游客 4苹果 5QQ ...
account_key VARCHAR(100) NOT NULL, -- 对应手机号、openId或游客ID等
password_hash VARCHAR(100), -- 如手机号、邮箱需要存密码
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
-- 用户资产表:user_assets(可选)
CREATE TABLE user_assets (
user_id BIGINT NOT NULL,
gold_coins BIGINT NOT NULL DEFAULT 0, -- 金币数
diamonds BIGINT NOT NULL DEFAULT 0, -- 钻石数等虚拟货币
...
update_time DATETIME NOT NULL
);
-- 其他:手机验证表、登录日志表等按需求设计
关键思路:
- user_info 维护一个独立的
user_id
,记录玩家的基本资料;- user_account 管理具体的登录方式,可以同时存在多条记录(一个
user_id
对应多个登录方式);- 密码要用hash形式存储(bcrypt 或其他安全哈希),不要存明文;
- 游客账号可以用
account_type=3
,account_key
存储一个 UUID;微信账号可以用account_type=2
,account_key
存 openId 等。
这样设计能方便后续对账号合并、绑定等操作,也易于扩展更多第三方登录方式。
四、登录流程与 Token / Session 机制
4.1 为什么需要 Token 或 Session?
- 保持玩家的登录状态:当玩家成功登录后,后端要给他一个“凭证”,以便在之后的请求里验证其身份;
- 减少重复鉴权开销:不必每次请求都重复输入账号密码或重新与第三方校验;
- 安全保护:Token 或 Session 可以设置过期时间、刷新机制,防止长期有效的凭据被盗用。
4.2 常见实现方式
-
后端 Session + Cookie
- 传统 Web 里常用的方式:玩家登录后,后端在服务器内存/Redis 里存一条会话(Session),同时给浏览器下发一个 Session ID 放在 Cookie 中;
- 适用于传统网页端。如果做移动端/Unity/Cocos 原生 App,就不太方便依赖浏览器的 Cookie 机制。
-
JWT(JSON Web Token)
- 一种常见的“无状态” Token,玩家每次请求都在 HTTP Header 或 WebSocket 消息中带上该 Token;
- 服务器验证 Token 的签名和内容,如果通过就认定你是某个 user_id;
- 通常还会搭配 Redis 或数据库来判断 Token 是否过期、是否已被撤销。
-
自定义 Token
- 可以自己定义一段随机字符串或带签名的字符串,成功登录后存到 Redis,客户端每次请求都带上该字符串;
- 后端验证此字符串是否在 Redis 中且未过期,就认定用户登录有效。
- 过期后需要重新登录或刷新 Token。
棋牌项目常见实践:
- 移动端/原生客户端:更多会用 自定义 Token + Redis 方案,或者 JWT;
- H5/小程序:可用 sessionKey 等机制,或也用自定义 Token。
- Token 刷新机制:给 Token 设置一个相对短的过期时间,比如 7 天,客户端到期前自动请求刷新,减少长期 Token 泄露的风险。
4.3 示例:自定义 Token 流程
以下是一个简化的“手机号+验证码”登录流程示例,返回自定义 Token:
-
玩家请求发送验证码:
POST /api/sendCode
,带上手机号;- 后端生成随机验证码,存到 Redis 并发短信(或者走 Mock 短信,视环境而定)。
-
玩家提交验证码登录:
POST /api/loginByPhone
,包含{ phone: xxx, code: yyy }
- 后端验证 Redis 中存的验证码是否正确、是否过期;如果通过,则继续。
- 如果这是新用户,就在数据库里创建
user_info
&user_account
。 - 最后生成一个随机
token
(比如使用 UUID + 加密签名),存到 Redis(key=token, value=user_id, 以及过期时间),然后返回给客户端:{ "code": 0, "msg": "ok", "data": { "token": "xxxxxx", "user_id": 12345, "nickname": "小明" } }
-
后续请求:
- 客户端在请求头里带
Authorization: Bearer xxxxxx
或者随意定义一个 header; - 后端解析出 token 去 Redis 查一下是否存在且未过期,如果有效,就拿到
user_id
,代表这是哪个玩家,否则拒绝访问或返回未登录。
- 客户端在请求头里带
优点:
- 这样玩家不需要保留服务器的 Session 状态,所有信息都以 token+user_id 的方式来验证;
- 方便在分布式部署里横向扩容,因为 Redis 做了共享存储,任何服务器节点都能校验 token。
五、安全措施:用户系统里的“防火墙”
棋牌游戏尤其要注意安全,因为这里有与金钱、道具直接挂钩的充值、提现等功能,更要重视账号和资产的安全。
5.1 数据加密与密码保护
- 密码存储:如前文所说,一定要哈希并加盐 (bcrypt、scrypt 或 Argon2 等),不能明文存储;
- 通信加密:最好使用 HTTPS / WSS,尤其登录、充值等关键接口必须加密;
- Token 签名:若使用 JWT 或自定义 Token,要有服务端私钥签名,防止被伪造。
5.2 防止暴力破解或撞库
- 验证码/图形验证码:玩家密码或验证码输入错误过多,要有验证码拦截;
- IP/设备风控:同一个 IP 或同一个设备频繁注册或登录失败,可能是攻击行为,可临时封禁;
- 加固后端接口:不要仅依赖客户端判断,所有校验都要在服务端进行。
5.3 防止账号盗用与异常检测
- 异常登录提醒:如一个账号在极短时间内在两个地理位置登录,可触发风控提醒;
- 二次验证:对涉及财产操作(提现、赠送道具)时,可要求二次验证码或密码确认;
- 日志审计:记录所有登录IP、时间、设备信息,出现纠纷或投诉时可追溯。
六、融合游客模式、第三方登录与正式账号:一个示例流程
为了说明多登录模式如何融合,这里给出一个更完整的示例:
-
初次进入
- 玩家下载 App,点击“快速开始”-> 后端创建游客账号
account_type=3
+account_key=uuid
; - 返回一个
token
,客户端拿到后就能进入游戏大厅。 - 数据库里
user_info
新增一条user_id=1001
,nickname="游客xxxxx"
;在user_account
表里插入游客记录。
- 玩家下载 App,点击“快速开始”-> 后端创建游客账号
-
玩家体验一段时间,想绑定微信
- 客户端点“微信登录”-> 微信授权成功,返回
openid=AABBCC
; - 后端先查
user_account
表有没有account_key=AABBCC
,如果没有,说明这是第一次微信登录; - 将此微信账号记录绑定到同一个
user_id=1001
上,更新昵称、头像等; - 以后玩家既可以用游客方式进,也能用微信直接登录(都指向
user_id=1001
同一份数据)。
- 客户端点“微信登录”-> 微信授权成功,返回
-
玩家换设备或卸载重装
- 如果用游客登录,之前的游客账号数据就找不到了(除非能凭设备ID匹配上),所以更好的方式是让他用微信登录,从而找回之前的
user_id=1001
; - 这时就不会丢失金币或战绩,体验也更好。
- 如果用游客登录,之前的游客账号数据就找不到了(除非能凭设备ID匹配上),所以更好的方式是让他用微信登录,从而找回之前的
-
后续绑定手机号
- 同理,如果玩家想再绑定手机号,可以再加一条
account_type=1, account_key=手机号
。
- 同理,如果玩家想再绑定手机号,可以再加一条
这个过程让玩家既能“轻量化”体验,也能后续“正规化”操作,一举两得。
七、与支付、实名制、风控的结合
用户系统还与游戏的支付充值、实名制、防沉迷等密切相关。虽然本篇主要聚焦登录部分,但稍微提一下:
-
支付充值:
- 只有在“用户信息”里有明确的
user_id
后,才能对应到后端的订单系统; - 充值成功后,会在
user_assets
里更新金币/钻石数量; - 如果用户账号异常,可冻结其充值或提现功能。
- 只有在“用户信息”里有明确的
-
实名制:
- 一些国家/地区要求游戏必须做实名验证,特别是针对未成年人防沉迷;
- 可以在用户系统里加一列
id_card_no
(身份证号或其他证件号),或在绑定手机号时做实名认证。 - 游客账号通常无法进行充值或深度玩法,必须在绑定实名后才开放。
-
风控与防沉迷:
- 监控玩家的在线时长、充值额度、登录频率,一旦超过某个阈值,就触发相应限制;
- 对异常账号(比如频繁提现、注册)进行风控,必要时封号处理。
八、常见问题与实践建议
8.1 如何防止重复注册、占用用户名?
- 用户名/昵称:
- 可以允许重复,但加个数字后缀(类似“张三#1234”);
- 也可以只允许唯一,但需要在注册或修改昵称时做唯一校验。
- 手机号:
- 一个手机号只能绑定一个
user_id
; - 如果要解绑,需要明确流程,不能让同一个手机号反复绑定不同账号。
- 一个手机号只能绑定一个
8.2 一个账号多处登录冲突怎么处理?
- 顶号:
- 当检测到同一个账号在另一台设备上登录,可以挤掉前一个会话;
- 这时可以在前端弹提示“你的账号在其他设备登录,当前已下线”。
- 允许多端同时在线:
- 也可以允许多设备登录,但要注意房间同步问题和异常监控(有人用多设备操控同一账号做不正当操作)。
8.3 Token 过期后玩家体验
- 自动刷新:
- 在 Token 过期前几小时或几天,客户端可主动向服务器请求刷新;
- 如果 Token 已过期,则跳转到登录界面或自动用第三方授权重新登录。
- 严格模式:
- 过期就是过期,必须重新登录输入密码或拉起授权,提升安全性。
8.4 游客账号丢失数据如何避免?
- 在设备上存储游客标识:
- 如果玩家重装后设备ID没变,可以尝试匹配到以前的游客账号;
- 但不保证所有系统都能保持同样的设备ID。
- 及时引导绑定:
- 给游客适度的福利或提示,鼓励他们尽快绑定微信/手机号,避免账号丢失。
九、示例代码:一个简易的“登录”后端伪实现
以下是一个Node.js + Express 的简易伪代码示例,展示手机号+验证码以及第三方登录的一些核心逻辑。真实项目中肯定比这复杂,但可以帮助理解流程。
// pseudo-user-controller.js
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const { getUserByPhone, createUser, updateUserNickname } = require('./userService');
const { redisClient } = require('./redisClient');
function generateToken() {
// 简易随机token,不建议用于生产环境
return crypto.randomBytes(16).toString('hex');
}
// 发送验证码接口
router.post('/sendCode', async (req, res) => {
const { phone } = req.body;
if (!phone) {
return res.json({ code: 1, msg: '手机号不能为空' });
}
// 生成随机验证码, 例如 6位数字
const code = Math.floor(100000 + Math.random() * 900000).toString();
// 写入Redis, 有效期5分钟
await redisClient.set(`PHONE_CODE_${phone}`, code, 'EX', 300);
// TODO: 调用短信服务发送验证码
console.log(`模拟发送短信: phone=${phone}, code=${code}`);
res.json({ code: 0, msg: '验证码已发送' });
});
// 手机号+验证码登录
router.post('/loginByPhone', async (req, res) => {
const { phone, code } = req.body;
if (!phone || !code) {
return res.json({ code: 1, msg: '缺少参数' });
}
const redisKey = `PHONE_CODE_${phone}`;
const realCode = await redisClient.get(redisKey);
if (!realCode) {
return res.json({ code: 2, msg: '验证码已过期' });
}
if (realCode !== code) {
return res.json({ code: 3, msg: '验证码错误' });
}
// 验证通过后,删除redis中的验证码
await redisClient.del(redisKey);
// 查询是否已有用户
let user = await getUserByPhone(phone);
if (!user) {
// 创建新用户
user = await createUser({ phone });
}
// 生成token并写入Redis
const token = generateToken();
await redisClient.set(`TOKEN_${token}`, user.user_id, 'EX', 7 * 24 * 3600); // token7天过期
res.json({
code: 0,
msg: '登录成功',
data: {
token,
user_id: user.user_id,
nickname: user.nickname
}
});
});
// 示例:微信登录
router.post('/loginByWeChat', async (req, res) => {
const { wechatCode } = req.body;
// 1. 客户端先拿到wechatCode(小程序或App拉起微信授权)
// 2. 后端用wechatCode去微信服务器换取openid/unionid等信息
const openid = `mockOpenId_${wechatCode}`;
// 3. 查找账号表中是否有对应openid
let user = await getUserByOpenId(openid);
if (!user) {
// 新建一个账号, 并生成 user_info
user = await createUser({ wechatOpenId: openid });
}
// 4. 生成token
const token = generateToken();
await redisClient.set(`TOKEN_${token}`, user.user_id, 'EX', 7 * 24 * 3600);
res.json({
code: 0,
msg: '微信登录成功',
data: {
token,
user_id: user.user_id,
nickname: user.nickname
}
});
});
module.exports = router;
说明:
- 核心思路:将登录凭证(token -> user_id)保存在 Redis,约定有效期;客户端必须在后续请求头里带此 Token 才能访问受保护的接口。
- 第三方登录流程中,weChatCode 的部分略去了真实的微信 API 请求,只留了示例逻辑,现实中需要调用微信的
auth.code2Session
等接口。
十、总结:用户系统是“留存与变现”的基石
从本篇里,我们可以看到:
- 账号体系多样化:游客、手机号、第三方社交账号……每一种都对应不同的场景和需求;
- 数据库设计:
user_info
记录通用信息,user_account
管理具体账号绑定,资产表、日志表可进一步扩展; - 登录流程:无论是自定义 Token、JWT 还是 Session,都要保证安全性和过期管理;
- 安全防护:密码加密、验证码、风控策略都必不可少;
- 绑定与合并:一开始玩家可能是游客身份,后面再绑定微信或手机号形成正式账号,这种流程要在后端实现良好的兼容和合并逻辑;
- 和支付、实名制、风控的紧密联系:用户系统不仅仅是登录而已,还关系到游戏的营收和监管。
在棋牌游戏开发中,用户系统绝对算是“牵一发而动全身”的模块——任何环节都离不开账号数据的支持。打好这个地基,才能让后面的玩法、充值、活动等功能顺利展开。