之前发那套网狐房卡的时候,就有兄弟追着问聚友的框架啥时候安排。说实话聚友这套我断断续续研究了一个多月,它跟网狐最大的区别是把房卡模式和金币场融合在一套体系里,网关层也做了很多高并发优化,很多设计思路挺值得扒一扒的。
今天不整虚的,这套2026最新手游源码,聚友房卡全套服务端加客户端的核心代码,我按模块全给你贴出来。想搭自己的房卡平台的、想研究架构的,搬好凳子直接看。
一、项目整体结构(Maven + Unity混合工程)
先看一下整个工程的顶层目录,服务端是Maven多模块,客户端是Unity独立工程:
JuyouRoomCard/ ├── server/ # Java服务端 │ ├── juyou-gateway # 网关服务,负责长连接和协议转发 │ ├── juyou-platform # 平台服务,登录、房卡、商城 │ ├── juyou-match # 匹配服务,房间创建与分配 │ ├── juyou-game-server # 游戏逻辑服务,每个游戏独立进程 │ ├── juyou-db-proxy # 数据库代理,封装MySQL和Redis │ ├── juyou-common # 公共工具库,协议、工具类 │ └── pom.xml ├── client/ # Unity客户端工程 │ ├── Assets/Scripts/ # C#客户端逻辑 │ └── ... ├── admin/ # 后台管理前端(Vue) ├── sql/ # 数据库初始化脚本 └── doc/ # 部署文档和压测报告
根pom.xml的模块声明:
<groupId>com.juyou</groupId><artifactId>juyou-server</artifactId><version>2026.2.0</version><packaging>pom</packaging><modules> <module>juyou-common</module> <module>juyou-db-proxy</module> <module>juyou-gateway</module> <module>juyou-platform</module> <module>juyou-match</module> <module>juyou-game-server</module></modules>
common模块被所有服务依赖,编译的时候先mvn install common和db-proxy,再编其他的。
二、juyou-gateway:Netty高性能网关,扛并发的心脏
聚友的网关层是整个框架的入口,所有客户端长连接都连到这儿,然后通过内部RPC转发到对应的业务服务。贴一下网关启动的核心代码:
@Componentpublic class GatewayServer {
@Value("${gateway.port}")
private int port;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
@PostConstruct
public void start() {
bossGroup = new NioEventLoopGroup(2);
workerGroup = new NioEventLoopGroup(
Runtime.getRuntime().availableProcessors() * 2
);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 2048)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 自定义协议编解码
pipeline.addLast(new MessageDecoder());
pipeline.addLast(new MessageEncoder());
// 心跳处理,30秒读超时
pipeline.addLast(new IdleStateHandler(30, 0, 0));
pipeline.addLast(new HeartbeatHandler());
// 业务分发
pipeline.addLast(new DispatchHandler());
}
});
bootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
Logger.info("Gateway started on port " + port);
}
});
}}SO_BACKLOG设的2048,比一般值大一倍,应对瞬间大量重连场景。线程数用的CPU核数乘2,我们在8核机器上压测,2000个长连接CPU稳定在40%左右。
协议分发器 DispatchHandler 的代码,这是网关里最核心的一段:
public class DispatchHandler extends SimpleChannelInboundHandler<GamePacket> {
private final ServiceRouter router;
@Override
protected void channelRead0(ChannelHandlerContext ctx, GamePacket packet) {
// 从包中解析模块ID和命令ID
short moduleId = packet.getModuleId();
short cmdId = packet.getCmdId();
// 根据模块ID路由到对应服务
ServiceNode target = router.route(moduleId);
if (target == null) {
ctx.writeAndFlush(PacketHelper.error("无效的模块ID"));
return;
}
// 将玩家channelId和请求内容通过RPC异步转发
String channelId = ctx.channel().id().asLongText();
RpcRequest request = RpcRequest.create(
target.getServiceName(),
moduleId,
cmdId,
packet.getBody(),
channelId );
router.sendAsync(request).thenAccept(response -> {
if (response != null) {
ctx.writeAndFlush(response);
}
});
}}网关不解析业务数据,只根据moduleId做转发,业务逻辑全部在各服务内部处理。这样做的好处是网关极轻,性能瓶颈出在业务层而不是入口层。
ServiceRouter 是基于ZooKeeper的服务发现,略长不贴全了,核心就是把服务名和节点IP做映射,定时从ZK拉列表,用一致性哈希做负载均衡。
三、juyou-match:房间匹配与房卡核销,核心业务闭环
匹配服务负责房卡的核销、房间的创建和分配。房卡表结构在前一篇讲过类似的,这里贴一下房卡核销的事务代码:
@Servicepublic class RoomCardService {
@Transactional(rollbackFor = Exception.class)
public Room allocateRoom(long playerId, long cardId, int gameType) {
// 1. 查房卡信息并锁定行
RoomCard card = roomCardMapper.selectByIdForUpdate(cardId);
if (card == null || card.getPlayerId() != playerId) {
throw new BizException("房卡不存在或不属于当前玩家");
}
if (card.getRemainTimes() <= 0) {
throw new BizException("房卡使用次数已用完");
}
if (card.getExpireTime() != null
&& card.getExpireTime().before(new Date())) {
throw new BizException("房卡已过期");
}
// 2. 扣减次数或设置已使用
if (card.getType() == CardType.TIMES.getCode()) {
card.setRemainTimes(card.getRemainTimes() - 1);
roomCardMapper.updateRemainTimes(cardId, card.getRemainTimes());
}
// 3. 从房间池分配或创建新房间
Room room = roomPoolService.getOrCreateRoom(gameType);
room.setOwnerId(playerId);
room.setCardId(cardId);
// 4. 写房卡使用记录
CardUsageLog log = new CardUsageLog();
log.setCardId(cardId);
log.setPlayerId(playerId);
log.setRoomId(room.getId());
log.setGameType(gameType);
log.setUsedAt(new Date());
cardUsageLogMapper.insert(log);
return room;
}}用 selectByIdForUpdate 行锁保证并发安全,扣减次数和创建房间在一个事务里,失败全部回滚。
四、juyou-game-server:帧同步与游戏逻辑,以麻将为例
聚友的游戏服务器每个游戏独立部署,麻将、跑得快、牛牛各一个Spring Boot应用。核心是房间内的帧同步调度,贴一下麻将房间的帧管理器:
public class MahjongRoom {
private static final int FRAME_INTERVAL_MS = 50; // 每帧50ms
private final ScheduledExecutorService scheduler;
private final List<FrameSnapshot> snapshots;
private int currentFrameIndex = 0;
public MahjongRoom(long roomId) {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.snapshots = new ArrayList<>();
}
public void startGame() {
scheduler.scheduleAtFixedRate(() -> {
// 1. 收集本帧所有玩家操作
List<PlayerAction> actions = collectActions();
// 2. 执行游戏逻辑,计算新状态
GameState newState = gameLogic.applyActions(actions);
// 3. 生成快照
FrameSnapshot snapshot = new FrameSnapshot();
snapshot.setFrameIndex(currentFrameIndex);
snapshot.setState(newState);
snapshot.setTimestamp(System.currentTimeMillis());
snapshots.add(snapshot);
// 4. 广播给房间内所有玩家
byte[] frameData = serializer.serialize(snapshot);
for (Player player : roomPlayers) {
player.getChannel().writeAndFlush(frameData);
}
currentFrameIndex++;
}, 0, FRAME_INTERVAL_MS, TimeUnit.MILLISECONDS);
}
// 断线重连时,从上次同步点补发快照
public void resyncPlayer(Player player, int fromFrameIndex) {
for (int i = fromFrameIndex; i < currentFrameIndex; i++) {
byte[] frameData = serializer.serialize(snapshots.get(i));
player.getChannel().writeAndFlush(frameData);
}
}}每50ms推一帧,把玩家操作和游戏状态打包成快照广播。断线重连时根据客户端上报的最后帧号,把缺失的快照补发一遍,客户端追帧恢复现场。
帧同步序列化用的Kryo,比Protobuf快,而且不用预定义.proto文件,直接序列化Java对象:
public class FrameSerializer {
private static final ThreadLocal<Kryo> kryoPool =
ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.register(FrameSnapshot.class);
kryo.register(GameState.class);
kryo.register(ArrayList.class);
return kryo;
});
public byte[] serialize(FrameSnapshot snapshot) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryoPool.get().writeObject(output, snapshot);
output.close();
return baos.toByteArray();
}}五、核心数据库建表语句
这几张表聚友的实现跟网狐有些差异,特别是玩家资产表做了金币和房卡分离,战绩表里记录了每局的完整快照ID列表方便回放:
-- 玩家资产表(金币场和房卡模式共用)CREATE TABLE `t_player_asset` ( `player_id` BIGINT NOT NULL, `gold` BIGINT DEFAULT 0 COMMENT '金币', `diamond` BIGINT DEFAULT 0 COMMENT '钻石', `room_card_balance` INT DEFAULT 0 COMMENT '通用房卡余额', `total_recharge` BIGINT DEFAULT 0 COMMENT '累计充值金额(分)', `version` INT DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (`player_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 游戏回放快照表CREATE TABLE `t_replay_snapshot` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `room_id` BIGINT NOT NULL, `game_type` TINYINT NOT NULL, `frame_data` MEDIUMBLOB COMMENT '用Kryo序列化的快照列表', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_room_id` (`room_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
回放表直接存整局游戏的序列化快照列表,要查回放时拿出来反序列化,播放器逐帧渲染。
六、游戏运行演示
代码讲得差不多了,放几张跑起来的效果图,证明这整套东西是能正常运行的。



结尾
这套2026最新手游源码,聚友房卡的框架设计、网关层处理、房间分配事务、帧同步快照,核心代码基本都在这了。不管你是想二次开发加自己想要的玩法,还是单纯想研究棋牌后台怎么扛高并发,这套代码都够你看一阵子的。
全部的工程源码、SQL脚本、部署文档和我自己录的几个环境搭建视频,我已经打包好放网盘了。之前加了我微信的兄弟直接发“聚友”两个字,我看到了就会甩给你。新朋友扫码加我,记得备注。
扫下方二维码加我微信,备注“聚友”,这套聚友房卡全套源码加部署文档、搭建视频全部打包发你。








