酒吧德州扑克娱乐小程序开发Java技术搭建源码案例
酒吧德州扑克娱乐小程序 — Java全栈开发源码案例2026年5月最新实战方案 · SpringBoot UniApp WebSocket 微信支付 完整源码级解析 一、项目全景总览┌─────────────────────────────────────────────────────────┐ │ 酒吧德州扑克 小酒馆点餐娱乐小程序 │ ├──────────────┬──────────────┬───────────────────────────┤ │ 扑克模块 │ 酒馆模块 │ 用户模块 │ ├──────────────┼──────────────┼───────────────────────────┤ │ • 快速匹配 │ • 扫码点餐 │ • 微信一键登录 │ │ • 私密房间 │ • 桌台绑定 │ • 积分/存酒管理 │ │ • 牌局实时同步 │ • 存酒/取酒 │ • 战绩/盈亏统计 │ │ • 牌型自动判定 │ • 优惠券 │ • 充值/提现 │ │ • 记分牌工具 │ • 订单管理 │ • 会员等级 │ │ • 胜率计算器 │ • 微信支付 │ • 抽位置功能 │ └──────────────┴──────────────┴───────────────────────────┘️ 二、技术架构选型2026年主流方案层级技术选型说明后端框架SpringBoot 3.2 JDK 21当前企业级主流性能最优数据库MySQL 8.0核心业务数据缓存/实时Redis 7房间状态 在线用户 WebSocket实时通信Spring WebSocket牌局状态毫秒级推送前端小程序UniApp (Vue3) → 微信小程序一套代码多端运行管理后台Vue3 Element Plus运营管理系统微信支付wechatpay-java 0.2.12充值 点餐支付部署Docker Nginx一键部署生产级JDK版本选择根据2026年企业使用占比JDK 1748%和JDK 2132%为主流。推荐使用JDK 21商业支持到2031年。 三、数据库设计12张核心表sql-- -- 酒吧德州扑克小酒馆 · 数据库初始化脚本 -- CREATE DATABASE poker_tavern DEFAULT CHARSET utf8mb4; USE poker_tavern; -- 1. 用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE COMMENT 微信openid, nickname VARCHAR(50) DEFAULT , avatar VARCHAR(255) DEFAULT , balance DECIMAL(10,2) DEFAULT 0.00, chips INT DEFAULT 10000 COMMENT 游戏筹码, level INT DEFAULT 1, total_profit DECIMAL(10,2) DEFAULT 0.00, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 2. 游戏房间表 CREATE TABLE poker_room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(20) UNIQUE COMMENT 房间号, blind_level INT DEFAULT 1 COMMENT 盲注 1/2 2/4 5/10, max_players INT DEFAULT 9, status TINYINT DEFAULT 0 COMMENT 0等待 1游戏中 2已结束, creator_id BIGINT NOT NULL, pot INT DEFAULT 0 COMMENT 底池, phase TINYINT DEFAULT 0 COMMENT 0翻前 1翻牌 2转牌 3河牌, community_cards JSON, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 3. 牌局记录表 CREATE TABLE poker_hand ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id BIGINT NOT NULL, player_id BIGINT NOT NULL, hole_cards VARCHAR(20) COMMENT 底牌 AH-KH, final_rank INT COMMENT 0高牌~9皇家同花顺, win_chips INT DEFAULT 0, is_winner TINYINT DEFAULT 0, played_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 4. 菜单/商品表 CREATE TABLE menu_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category VARCHAR(20) DEFAULT 酒水, price DECIMAL(10,2) NOT NULL, image VARCHAR(255) DEFAULT , stock INT DEFAULT 999, status TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 5. 点餐订单表 CREATE TABLE dinner_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE, user_id BIGINT NOT NULL, table_no VARCHAR(10) COMMENT 桌台号, items JSON NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, pay_time DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 6. 存酒记录表 CREATE TABLE wine_storage ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, wine_name VARCHAR(100) NOT NULL, quantity INT DEFAULT 1, store_date DATE NOT NULL, status TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 7. 充值记录表 CREATE TABLE recharge_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, chips_add INT NOT NULL, pay_type VARCHAR(20) DEFAULT wechat, trans_id VARCHAR(64), status TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 初始化测试数据 INSERT INTO user (openid, nickname, chips) VALUES (test_001, 扑克王, 50000), (test_002, 斗地主, 30000); 四、核心后端源码Java4.1 扑克牌核心类java// Card.java Data AllArgsConstructor NoArgsConstructor public class Card { public enum Suit { HEARTS, DIAMONDS, CLUBS, SPADES } public enum Rank { TWO(2), THREE(3), FOUR(4), FIVE(5), SIX(6), SEVEN(7), EIGHT(8), NINE(9), TEN(10), JACK(11), QUEEN(12), KING(13), ACE(14); private final int value; Rank(int value) { this.value value; } public int getValue() { return value; } } private Suit suit; private Rank rank; Override public String toString() { return suit.name().charAt(0) rank.name().charAt(0); } } // Deck.java Component public class Deck { public ListCard createDeck() { ListCard deck new ArrayList(52); for (Card.Suit suit : Card.Suit.values()) { for (Card.Rank rank : Card.Rank.values()) { deck.add(new Card(suit, rank)); } } return deck; } public void shuffle(ListCard deck) { Collections.shuffle(deck); } }4.2 ⭐⭐ 牌型判定引擎核心算法javaComponent public class PokerHandEvaluator { /** * 从7张牌中选出最好的5张返回牌型等级 * 0高牌 1一对 2两对 3三条 4顺子 5同花 * 6葫芦 7四条 8同花顺 9皇家同花顺 */ public int evaluate(ListCard sevenCards) { ListListCard combos combinations(sevenCards, 5); int bestRank -1; for (ListCard combo : combos) { bestRank Math.max(bestRank, rankHand(combo)); } return bestRank; } private int rankHand(ListCard cards) { Collections.sort(cards, Comparator.comparingInt(c - c.getRank().getValue()).reversed()); boolean flush cards.stream().map(Card::getSuit).distinct().count() 1; boolean straight checkStraight(cards); MapInteger, Long freq cards.stream() .collect(Collectors.groupingBy(c - c.getRank().getValue(), Collectors.counting())); ListLong counts new ArrayList(freq.values()); Collections.sort(counts, Collections.reverseOrder()); if (flush straight cards.get(0).getRank().getValue() 14) return 9; if (flush straight) return 8; if (counts.get(0) 4) return 7; if (counts.get(0) 3 counts.get(1) 2) return 6; if (flush) return 5; if (straight) return 4; if (counts.get(0) 3) return 3; if (counts.get(0) 2 counts.get(1) 2) return 2; if (counts.get(0) 2) return 1; return 0; } private boolean checkStraight(ListCard cards) { ListInteger vals cards.stream() .map(c - c.getRank().getValue()).distinct().sorted().toList(); if (vals.size() 5) return false; // A-2-3-4-5 特殊顺子 if (vals.contains(14) vals.contains(2) vals.contains(3) vals.contains(4) vals.contains(5)) return true; for (int i 0; i vals.size() - 5; i) { if (vals.get(i 4) - vals.get(i) 4) return true; } return false; } private ListListCard combinations(ListCard cards, int k) { ListListCard result new ArrayList(); combine(cards, k, 0, new ArrayList(), result); return result; } private void combine(ListCard cards, int k, int start, ListCard current, ListListCard result) { if (current.size() k) { result.add(new ArrayList(current)); return; } for (int i start; i cards.size(); i) { current.add(cards.get(i)); combine(cards, k, i 1, current, result); current.remove(current.size() - 1); } } }4.3 ⭐⭐⭐ WebSocket 牌局实时同步javaServerEndpoint(/ws/room/{roomId}) Component Slf4j public class PokerWebSocket { private static final MapString, SetSession ROOM_SESSIONS new ConcurrentHashMap(); private static final MapString, PokerRoomState ROOM_STATES new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam String roomId) { ROOM_SESSIONS.computeIfAbsent(roomId, k - ConcurrentHashMap.newKeySet()).add(session); log.info(玩家加入房间: {}, 在线: {}, roomId, ROOM_SESSIONS.get(roomId).size()); } OnMessage public void onMessage(String message, PathParam String roomId) { try { GameMessage msg JSON.parseObject(message, GameMessage.class); PokerRoomState state ROOM_STATES.computeIfAbsent(roomId, k - new PokerRoomState()); switch (msg.getAction()) { case bet - processBet(roomId, state, msg); case call - processCall(roomId, state, msg); case raise - processRaise(roomId, state, msg); case fold - processFold(roomId, state, msg); case all_in - processAllIn(roomId, state, msg); case deal - processDeal(roomId, state, msg); } broadcast(roomId, state.toMessage()); } catch (Exception e) { log.error(处理消息失败: {}, e.getMessage()); } } OnClose public void onClose(Session session, PathParam String roomId) { SetSession sessions ROOM_SESSIONS.get(roomId); if (sessions ! null) sessions.remove(session); if (sessions ! null sessions.isEmpty()) { ROOM_SESSIONS.remove(roomId); ROOM_STATES.remove(roomId); log.info(房间解散: {}, roomId); } } private void processBet(String roomId, PokerRoomState state, GameMessage msg) { PokerPlayer player state.getPlayer(msg.getPlayerId()); player.setChips(player.getChips() - msg.getAmount()); state.setCurrentBet(Math.max(state.getCurrentBet(), msg.getAmount())); state.setPot(state.getPot() msg.getAmount()); } private void processFold(String roomId, PokerRoomState state, GameMessage msg) { state.getPlayer(msg.getPlayerId()).setFolded(true); checkWinner(roomId, state); } private void checkWinner(String roomId, PokerRoomState state) { ListPokerPlayer activePlayers state.getPlayers().stream() .filter(p - !p.isFolded()).toList(); if (activePlayers.size() 1) { PokerPlayer winner activePlayers.get(0); winner.setChips(winner.getChips() state.getPot()); state.setStatus(RoomStatus.ENDED); log.info([{}] 玩家{} 独赢底池 {}, roomId, winner.getId(), state.getPot()); } else if (activePlayers.size() 1 state.getPhase() 4) { compareHands(roomId, state, activePlayers); } } /** 比牌判定赢家 */ private void compareHands(String roomId, PokerRoomState state, ListPokerPlayer players) { PokerHandEvaluator evaluator new PokerHandEvaluator(); PokerPlayer bestPlayer null; int bestRank -1; for (PokerPlayer p : players) { ListCard allCards new ArrayList(p.getHoleCards()); allCards.addAll(state.getCommunityCards()); int rank evaluator.evaluate(allCards); if (rank bestRank) { bestRank rank; bestPlayer p; } } bestPlayer.setChips(bestPlayer.getChips() state.getPot()); state.setStatus(RoomStatus.ENDED); state.setWinner(bestPlayer.getId()); log.info([{}] 玩家{} 以牌型{}赢得 {}, roomId, bestPlayer.getId(), bestRank, state.getPot()); } private void broadcast(String roomId, GameMessage msg) { ROOM_SESSIONS.get(roomId).forEach(s - { try { s.getAsyncRemote().sendText(JSON.toJSONString(msg)); } catch (Exception e) { /* ignore */ } }); } // 房间状态内部类 Data static class PokerRoomState { private String roomId; private ListPokerPlayer players new ArrayList(); private ListCard communityCards new ArrayList(); private int pot 0; private int currentBet 0; private int phase 0; private RoomStatus status RoomStatus.WAITING; private Long winner null; public PokerPlayer getPlayer(Long playerId) { return players.stream().filter(p - p.getId().equals(playerId)).findFirst().orElse(null); } public GameMessage toMessage() { GameMessage msg new GameMessage(); msg.setAction(state_update); msg.setPlayers(players); msg.setCommunityCards(communityCards.stream().map(Card::display).toList()); msg.setPot(pot); msg.setCurrentBet(currentBet); msg.setPhase(phase); msg.setStatus(status.ordinal()); return msg; } } Data static class PokerPlayer { private Long id; private String nickname; private int chips; private boolean isDealer; private boolean isFolded; private boolean isAllIn; private ListCard holeCards; } enum RoomStatus { WAITING, PLAYING, ENDED } } // GameMessage.java Data public class GameMessage { private String action; private Long playerId; private Integer amount; private ListString cards; private Integer pot; private Integer phase; private Integer status; private ListPokerWebSocket.PokerPlayer players; private Long winner; }4.4 REST API 控制器javaRestController RequestMapping(/api) CrossOrigin Slf4j public class GameController { Autowired private PokerRoomService roomService; Autowired private UserService userService; Autowired private OrderService orderService; // 房间管理 PostMapping(/room/create) public RString createRoom(RequestBody CreateRoomReq req) { String roomNo R System.currentTimeMillis() % 100000; roomService.createRoom(roomNo, req.getBlindLevel(), req.getCreatorId()); return R.ok(roomNo); } GetMapping(/room/list) public RListRoomVO roomList() { return R.ok(roomService.getWaitingRooms()); } PostMapping(/room/{roomNo}/join) public RString joinRoom(PathVariable String roomNo, RequestBody JoinRoomReq req) { roomService.joinRoom(roomNo, req.getUserId(), req.getChips()); return R.ok(加入成功); } // 用户 PostMapping(/user/login) public RUserVO login(RequestBody WxLoginReq req) { return R.ok(userService.wxLogin(req.getCode())); } PostMapping(/user/recharge) public RString recharge(RequestBody RechargeReq req) { userService.recharge(req.getUserId(), req.getAmount()); return R.ok(充值成功); } // 点餐 PostMapping(/order/create) public RString createOrder(RequestBody OrderReq req) { orderService.createOrder(req); return R.ok(下单成功); } PostMapping(/order/pay) public RString payOrder(RequestBody PayReq req) { orderService.payOrder(req.getOrderId(), req.getUserId()); return R.ok(支付成功); } // 存酒 PostMapping(/wine/store) public RString storeWine(RequestBody WineReq req) { orderService.storeWine(req); return R.ok(存酒成功); } // 战绩 GetMapping(/user/stats) public RUserStatsVO getStats(RequestHeader(Authorization) String token) { return R.ok(userService.getStats(JwtUtil.getUserId(token))); } }4.5 微信支付对接javaService Slf4j public class PayService { Autowired private WeChatPayClient weChatPayClient; public String createRechargeOrder(Long userId, BigDecimal amount) { JSONObject params new JSONObject(); params.put(appid, wx1234567890abcdef); params.put(mchid, 1234567890); params.put(description, 德州扑克充值- userId); params.put(out_trade_no, RC System.currentTimeMillis()); params.put(notify_url, https://your-domain.com/api/pay/notify); params.put(amount, amount.multiply(new BigDecimal(100)).intValue()); JSONObject result weChatPayClient.post(v3/pay/transactions/jsapi, params, JSONObject.class, AutoCertificateExtension.class, new HostNameCertificateVerifier()); return result.getString(prepay_id); } PostMapping(/api/pay/notify) public String payNotify(RequestBody String body) { try { JSONObject data JSON.parseObject(body); String outTradeNo data.getString(out_trade_no); String transId data.getString(transaction_id); log.info(支付回调: {} {}, outTradeNo, transId); // 更新订单状态 增加筹码 orderService.updateOrderStatus(outTradeNo, 1); userService.addChips(data.getString(openid), data.getInteger(total_fee) / 100); return {\code\:\SUCCESS\,\message\:\成功\}; } catch (Exception e) { log.error(支付回调失败: {}, e.getMessage()); return {\code\:\FAIL\,\message\:\\}; } } } 五、UniApp 小程序前端源码5.1 ⭐ 牌桌页面核心vue!-- pages/table/table.vue -- template view classpoker-table !-- 公共牌区域 -- view classcommunity-area view classpot 底池: {{ potChips }}/view view classcards-row view v-for(card, i) in communityCards :keyi classcard{{ card }}/view view v-ifcommunityCards.length 5 classcard empty?/view /view view classphase{{ phaseText }}/view /view !-- 玩家座位 -- view v-forplayer in players :keyplayer.id classseat :class{ folded: player.folded } :style{ left: player.x rpx, top: player.y rpx } image classavatar :srcplayer.avatar / text classname{{ player.nickname }}/text text classchips{{ player.chips }}/text view v-ifplayer.isMe classme-badge我/view /view !-- 我的手牌 -- view classmy-cards v-ifisMyTurn view v-forcard in myCards :keycard classcard my-card{{ card }}/view /view !-- 操作按钮 -- view classactions button classbtn-fold clickdoAction(fold)❌ 弃牌/button button classbtn-call clickdoAction(call)跟注 {{ currentBet }}/button button classbtn-raise clickdoAction(raise)⬆️ 加注/button button classbtn-allin clickdoAction(all_in) ALL IN/button /view /view /template script setup langts import { ref, onMounted, onUnmounted, computed } from vue import { connectWS, sendWS } from /utils/ws const roomId ref() const ws refWebSocket | null(null) const potChips ref(0) const communityCards refstring[]([]) const players refany[]([]) const myCards refstring[]([]) const currentBet ref(0) const phase ref(0) const isMyTurn ref(false) const phaseText computed(() { const map [翻牌前, 翻牌, 转牌, 河牌] return map[phase.value] || }) onMounted(() { const pages getCurrentPages() const cur pages[pages.length - 1] roomId.value cur.options?.roomId || ws.value connectWS(wss://your-domain.com/ws/room/${roomId.value}) ws.value.onmessage (res) { const msg JSON.parse(res.data) if (msg.action deal) { myCards.value msg.myCards || []; isMyTurn.value true } if (msg.action community) { communityCards.value msg.cards || [] } if (msg.action pot_update) { potChips.value msg.pot || 0 } if (msg.action player_update) { players.value msg.players || [] } if (msg.action phase) { phase.value msg.phase || 0; isMyTurn.value msg.isMyTurn || false } if (msg.action bet_update) { currentBet.value msg.currentBet || 0 } if (msg.action winner) { uni.showToast({ title: 玩家${msg.winnerName}获胜, icon: none }) } } }) const doAction (action: string) { sendWS(ws.value, { action, playerId: getApp().globalData.userId, amount: action all_in ? 999999 : currentBet.value }) } onUnmounted(() { ws.value?.close() }) /script style scoped .poker-table { width: 100vw; height: 100vh; background: radial-gradient(ellipse, #1a5c2a, #0d3318); position: relative; overflow: hidden; } .seat { position: absolute; width: 160rpx; text-align: center; color: #fff; } .card { width: 80rpx; height: 112rpx; background: #fff; border-radius: 12rpx; display: inline-flex; align-items: center; justify-content: center; font-weight: bold; margin: 4rpx; box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.4); } .btn-allin { background: linear-gradient(135deg, #ff4444, #cc0000) !important; color: #fff !important; font-size: 32rpx; font-weight: bold; } /style5.2 WebSocket 封装javascript// utils/ws.js let wsInstance null export function connectWS(url) { wsInstance uni.connectSocket({ url }) wsInstance.onOpen(() console.log(WebSocket 已连接)) wsInstance.onMessage((res) { if (wsInstance._onMessage) wsInstance._onMessage(res) }) wsInstance.onClose(() { console.log(WebSocket 已断开) setTimeout(() connectWS(url), 5000) // 5秒重连 }) return wsInstance } export function sendWS(ws, data) { if (ws ws.readyState WebSocket.OPEN) { ws.send({ data: JSON.stringify(data) }) } } 六、Docker 一键部署docker-compose.ymlyamlversion: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: poker_tavern ports: [3306:3306] volumes: [mysql_data:/var/lib/mysql] redis: image: redis:7-alpine ports: [6379:6379] volumes: [redis_data:/data] poker-server: build: ./poker-server ports: [8080:8080] depends_on: [mysql, redis] nginx: image: nginx:alpine ports: [80:80, 443:443] volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./dist:/usr/share/nginx/html volumes: mysql_data: redis_data:nginx.confnginxserver { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://poker-server:8080/api/; proxy_set_header Host $host; } location /ws/ { proxy_pass http://poker-server:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } }部署命令bash# 1. 打包后端 cd poker-server mvn clean package -DskipTests # 2. 打包小程序 cd my-poker-tavern npm run build:mp-weixin # 3. 一键启动 docker-compose up -d --build # 4. 查看日志 docker-compose logs -f poker-server