2026最新手游源码流出,聚友房卡全套框架,后端核心代码一行行全贴在这了

17源码网 9小时前 531浏览 0评论

之前发那套网狐房卡的时候,就有兄弟追着问聚友的框架啥时候安排。说实话聚友这套我断断续续研究了一个多月,它跟网狐最大的区别是房卡模式金币场融合在一套体系里,网关层也做了很多高并发优化,很多设计思路挺值得扒一扒的。

今天不整虚的,这套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;

回放表直接存整局游戏的序列化快照列表,要查回放时拿出来反序列化,播放器逐帧渲染。

六、游戏运行演示

代码讲得差不多了,放几张跑起来的效果图,证明这整套东西是能正常运行的。

image.png

image.png

image.png

结尾

这套2026最新手游源码,聚友房卡的框架设计、网关层处理、房间分配事务、帧同步快照,核心代码基本都在这了。不管你是想二次开发加自己想要的玩法,还是单纯想研究棋牌后台怎么扛高并发,这套代码都够你看一阵子的。

全部的工程源码、SQL脚本、部署文档和我自己录的几个环境搭建视频,我已经打包好放网盘了。之前加了我微信的兄弟直接发“聚友”两个字,我看到了就会甩给你。新朋友扫码加我,记得备注。

扫下方二维码加我微信,备注“聚友”,这套聚友房卡全套源码加部署文档、搭建视频全部打包发你。


客服微信二维码
点击关闭
  • 在线客服1

    ------------------- ↓长按保存二维码
    ↓微信客服先回复
    ------------------- 2