基于Java开发德州扑克小酒馆小程序完整部署教程分享
·
🔥 完整部署教程:Java德州扑克小酒馆小程序(从零到上线)
2026年5月最新版 · SpringBoot + UniApp + 微信小程序 + Docker一键部署
📋 目录导航
| 章节 | 内容 | 预计耗时 |
|---|---|---|
| 阶段一 | 环境搭建 + 项目初始化 | 2h |
| 阶段二 | 后端核心:牌局引擎 + WebSocket | 4h |
| 阶段三 | 小酒馆消费模块(点餐/存酒) | 3h |
| 阶段四 | UniApp小程序前端开发 | 4h |
| 阶段五 | 联调测试 + 部署上线 | 2h |
| 合计 | 约15h(1-2天可搞定) |
🏗️ 阶段一:环境搭建 + 项目初始化
1.1 安装基础环境
bash
# Java 17+
java -version # 需 >= 17
# Maven 3.8+
mvn -v
# MySQL 8.0
mysql --version
# Redis 7
redis-cli --version
# Node.js 18+ (前端用)
node -v
# 微信开发者工具 → 下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
1.2 创建项目骨架
bash
# 后端
mvn archetype:generate \
-DgroupId=com.poker.tavern \
-DartifactId=poker-server \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
cd poker-server
# pom.xml 核心依赖
xml
<dependencies>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- WebSocket 实时通信 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 微信支付SDK -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
1.3 数据库初始化
sql
-- 创建数据库
CREATE DATABASE poker_tavern DEFAULT CHARSET utf8mb4;
USE poker_tavern;
-- ===== 用户表 =====
CREATE TABLE `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`openid` VARCHAR(64) UNIQUE COMMENT '微信openid',
`nickname` VARCHAR(50) DEFAULT '',
`avatar` VARCHAR(255) DEFAULT '',
`phone` VARCHAR(20) DEFAULT '',
`balance` DECIMAL(10,2) DEFAULT 0.00 COMMENT '余额',
`chips` INT DEFAULT 10000 COMMENT '游戏筹码',
`level` INT DEFAULT 1,
`total_profit` DECIMAL(10,2) DEFAULT 0.00,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- ===== 游戏房间表 =====
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,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- ===== 牌局记录表 =====
CREATE TABLE `poker_hand` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`room_id` BIGINT,
`player_id` BIGINT,
`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
) ENGINE=InnoDB;
-- ===== 点餐订单表 =====
CREATE TABLE `dinner_order` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT,
`table_no` VARCHAR(10) COMMENT '桌台号',
`items` JSON COMMENT '菜品列表',
`total_amount` DECIMAL(10,2),
`status` TINYINT DEFAULT 0 COMMENT '0待支付 1已支付 2完成 3取消',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- ===== 存酒记录表 =====
CREATE TABLE `wine_storage` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT,
`wine_name` VARCHAR(100),
`quantity` INT DEFAULT 1,
`store_date` DATE,
`status` TINYINT DEFAULT 0 COMMENT '0存放中 1已取走',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- 插入测试数据
INSERT INTO user (openid, nickname, chips) VALUES
('test_openid_001', '扑克王', 50000),
('test_openid_002', '斗地主', 30000);
1.4 配置文件 application.yml
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/poker_tavern?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
password:
database: 0
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: auto
# 微信小程序配置
wechat:
miniapp:
appid: wx1234567890abcdef # 替换你的AppID
secret: your_secret_here # 替换你的Secret
# 微信支付配置
wechat:
pay:
mch-id: 1234567890
api-key: your_api_key
notify-url: https://your-domain.com/api/pay/notify
🎮 阶段二:后端核心代码实现
2.1 扑克牌核心类
java
// ========== Card.java ==========
@Data
@AllArgsConstructor
public class Card {
private Suit suit; // 花色
private Rank rank; // 点数
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; }
}
@Override
public String toString() {
return suit.name().charAt(0) + rank.name().charAt(0);
}
}
java
// ========== Deck.java 牌组 ==========
@Component
public class Deck {
public List<Card> createDeck() {
List<Card> 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(List<Card> deck) {
Collections.shuffle(deck);
}
}
2.2 ⭐ 牌型判定引擎(核心算法)
java
@Component
public class PokerHandEvaluator {
/**
* 从7张牌中选出最好的5张,返回牌型等级
* 0=高牌 1=一对 2=两对 3=三条 4=顺子 5=同花
* 6=葫芦 7=四条 8=同花顺 9=皇家同花顺
*/
public int evaluate(List<Card> sevenCards) {
List<List<Card>> combos = combinations(sevenCards, 5);
int bestRank = -1;
for (List<Card> combo : combos) {
bestRank = Math.max(bestRank, rankHand(combo));
}
return bestRank;
}
private int rankHand(List<Card> 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);
Map<Integer, Long> freq = cards.stream()
.collect(Collectors.groupingBy(c -> c.getRank().getValue(), Collectors.counting()));
List<Long> 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(List<Card> cards) {
List<Integer> 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() - 4; i++) {
if (vals.get(i + 4) - vals.get(i) == 4) return true;
}
return false;
}
private List<List<Card>> combinations(List<Card> cards, int k) {
List<List<Card>> result = new ArrayList<>();
combine(cards, k, 0, new ArrayList<>(), result);
return result;
}
private void combine(List<Card> cards, int k, int start,
List<Card> current, List<List<Card>> 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);
}
}
}
2.3 ⭐ WebSocket 实时牌局同步
java
@ServerEndpoint("/ws/room/{roomId}")
@Component
public class PokerWebSocket {
private static final Map<String, Set<Session>> ROOM_SESSIONS = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam String roomId) {
ROOM_SESSIONS.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet())
.add(session);
System.out.println("玩家加入房间: " + roomId);
}
@OnMessage
public void onMessage(String message, @PathParam String roomId) {
GameMessage msg = JSON.parseObject(message, GameMessage.class);
switch (msg.getAction()) {
case "bet" -> processBet(roomId, msg);
case "fold" -> processFold(roomId, msg);
case "call" -> processCall(roomId, msg);
case "all_in" -> processAllIn(roomId, msg);
}
broadcast(roomId, msg);
}
@OnClose
public void onClose(Session session, @PathParam String roomId) {
Set<Session> sessions = ROOM_SESSIONS.get(roomId);
if (sessions != null) sessions.remove(session);
}
private void broadcast(String roomId, GameMessage msg) {
Set<Session> sessions = ROOM_SESSIONS.get(roomId);
if (sessions == null) return;
sessions.forEach(s -> {
try {
s.getAsyncRemote().sendText(JSON.toJSONString(msg));
} catch (Exception e) { e.printStackTrace(); }
});
}
// ===== 牌局动作处理 =====
private void processBet(String roomId, GameMessage msg) {
PokerRoom room = roomService.getRoom(roomId);
Player player = room.getPlayer(msg.getPlayerId());
player.setChips(player.getChips() - msg.getAmount());
room.setCurrentBet(Math.max(room.getCurrentBet(), msg.getAmount()));
room.setPot(room.getPot() + msg.getAmount());
}
private void processFold(String roomId, GameMessage msg) {
PokerRoom room = roomService.getRoom(roomId);
room.getPlayer(msg.getPlayerId()).setFolded(true);
checkWinner(room);
}
private void checkWinner(PokerRoom room) {
List<Player> activePlayers = room.getPlayers().stream()
.filter(p -> !p.isFolded()).toList();
if (activePlayers.size() == 1) {
// 只剩一人,直接获胜
Player winner = activePlayers.get(0);
winner.setChips(winner.getChips() + room.getPot());
room.setStatus(2); // 结束
} else if (activePlayers.size() > 1 && room.getPhase() >= 4) {
// 河牌阶段,比牌
compareHands(room, activePlayers);
}
}
private void compareHands(PokerRoom room, List<Player> players) {
PokerHandEvaluator evaluator = new PokerHandEvaluator();
Player bestPlayer = null;
int bestRank = -1;
for (Player p : players) {
List<Card> allCards = new ArrayList<>(p.getHoleCards());
allCards.addAll(room.getCommunityCards());
int rank = evaluator.evaluate(allCards);
if (rank > bestRank) {
bestRank = rank;
bestPlayer = p;
}
}
bestPlayer.setChips(bestPlayer.getChips() + room.getPot());
room.setStatus(2);
}
}
java
// ========== GameMessage.java ==========
@Data
public class GameMessage {
private String action; // bet/fold/call/all_in/deal
private Long playerId;
private Integer amount; // 下注金额
private List<String> cards; // 公共牌
private Integer pot; // 底池
private Integer phase; // 0翻前 1翻牌 2转牌 3河牌
}
2.4 REST API 控制器
java
@RestController
@RequestMapping("/api")
@CrossOrigin
public class GameController {
@Autowired private PokerRoomService roomService;
@Autowired private UserService userService;
@Autowired private OrderService orderService;
// ---- 房间管理 ----
@PostMapping("/room/create")
public R<String> createRoom(@RequestBody CreateRoomReq req) {
String roomNo = "ROOM" + System.currentTimeMillis() % 10000;
roomService.createRoom(roomNo, req.getBlindLevel(), req.getCreatorId());
return R.ok(roomNo);
}
@GetMapping("/room/list")
public R<List<PokerRoom>> roomList() {
return R.ok(roomService.getWaitingRooms());
}
@PostMapping("/room/{roomNo}/join")
public R<String> joinRoom(@PathVariable String roomNo, @RequestBody Long userId) {
roomService.joinRoom(roomNo, userId);
return R.ok("加入成功");
}
// ---- 用户 ----
@PostMapping("/user/login")
public R<User> login(@RequestBody WxLoginReq req) {
User user = userService.wxLogin(req.getCode());
return R.ok(user);
}
@PostMapping("/user/recharge")
public R<String> recharge(@RequestBody RechargeReq req) {
userService.recharge(req.getUserId(), req.getAmount());
return R.ok("充值成功");
}
// ---- 点餐 ----
@PostMapping("/order/create")
public R<String> createOrder(@RequestBody OrderReq req) {
orderService.createOrder(req);
return R.ok("下单成功");
}
@PostMapping("/order/pay")
public R<String> payOrder(@RequestBody PayReq req) {
orderService.payOrder(req.getOrderId(), req.getUserId());
return R.ok("支付成功");
}
// ---- 存酒 ----
@PostMapping("/wine/store")
public R<String> storeWine(@RequestBody WineReq req) {
orderService.storeWine(req);
return R.ok("存酒成功");
}
}
🍺 阶段三:小酒馆消费模块
3.1 菜品管理
java
@Data
@TableName("menu_item")
public class MenuItem {
private Long id;
private String name;
private String category; // 酒水/小吃/套餐
private BigDecimal price;
private String image;
private Integer stock;
}
java
@RestController
@RequestMapping("/api/menu")
public class MenuController {
@Autowired private MenuItemService menuService;
@GetMapping("/list")
public R<List<MenuItem>> list(@RequestParam String category) {
return R.ok(menuService.listByCategory(category));
}
@PostMapping("/cart/add")
public R<String> addToCart(@RequestBody CartReq req) {
// Redis 存储购物车 key: cart:{userId}
return R.ok("已加入购物车");
}
}
3.2 微信支付对接
java
@Service
public class PayService {
@Autowired private WeChatPayClient weChatPayClient;
public String createOrder(Long userId, BigDecimal amount, String description) {
// 1. 调用微信统一下单
JSONObject params = new JSONObject();
params.put("appid", "wx1234567890abcdef");
params.put("mchid", "1234567890");
params.put("description", description);
params.put("out_trade_no", "ORDER" + 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) {
// 验证签名 → 更新订单状态 → 返回成功
return "{\"code\":\"SUCCESS\",\"message\":\"成功\"}";
}
}
📱 阶段四:UniApp 小程序前端
4.1 项目创建
bash
# 使用 HBuilderX 创建 UniApp 项目
# 或命令行:
npx degit dcloudio/uni-preset-vue#vite-ts my-poker-tavern
cd my-poker-tavern
npm install
4.2 项目结构
my-poker-tavern/
├── pages/
│ ├── index/ # 首页(酒馆入口)
│ ├── lobby/ # 游戏大厅
│ ├── table/ # 牌桌(核心)
│ ├── order/ # 点餐
│ └── mine/ # 我的
├── components/
│ ├── poker-card.vue # 扑克牌组件
│ └── player-seat.vue # 玩家座位
├── store/ # Pinia状态管理
├── App.vue
└── manifest.json
4.3 ⭐ 牌桌页面(核心)
vue
<!-- pages/table/table.vue -->
<template>
<view class="poker-table">
<!-- 公共牌区域 -->
<view class="community-area">
<view class="pot">底池: {{ potChips }} 💰</view>
<view class="cards">
<view v-for="(card, i) in communityCards" :key="i" class="card">
{{ card }}
</view>
<view v-if="communityCards.length < 5" class="card empty">?</view>
</view>
<view class="phase">{{ phaseText }}</view>
</view>
<!-- 玩家座位 -->
<view v-for="player in players" :key="player.id"
class="seat" :class="{ folded: player.folded }"
:style="{ left: player.x + 'px', top: player.y + 'px' }">
<image class="avatar" :src="player.avatar" />
<text class="name">{{ player.nickname }}</text>
<text class="chips">💰{{ player.chips }}</text>
<view v-if="player.isDealer" class="dealer-badge">D</view>
<view v-if="player.isMe" class="me-badge">我</view>
</view>
<!-- 我的手牌 -->
<view class="my-cards">
<view v-for="card in myCards" :key="card" class="card my-card">
{{ card }}
</view>
</view>
<!-- 操作按钮 -->
<view class="actions">
<button class="btn-fold" @click="doAction('fold')">弃牌</button>
<button class="btn-call" @click="doAction('call')">
跟注 {{ currentBet }}
</button>
<button class="btn-raise" @click="doAction('raise')">加注</button>
<button class="btn-allin" @click="doAction('all_in')">ALL IN 🔥</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const roomId = ref('')
const ws = ref<WebSocket | null>(null)
const potChips = ref(0)
const communityCards = ref<string[]>([])
const players = ref<any[]>([])
const myCards = ref<string[]>([])
const currentBet = ref(0)
const phase = ref(0) // 0翻前 1翻牌 2转牌 3河牌
const phaseText = computed(() => {
const map = ['翻牌前', '翻牌', '转牌', '河牌']
return map[phase.value] || ''
})
onMounted(() => {
roomId.value = getCurrentInstance()?.proxy?.$route?.params?.roomId || ''
ws.value = uni.connectSocket({
url: `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 || []
}
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
}
if (msg.action === 'bet_update') {
currentBet.value = msg.currentBet || 0
}
})
})
const doAction = (action: string) => {
ws.value?.send({
data: JSON.stringify({
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: 80px;
text-align: center;
color: #fff;
}
.card {
width: 40px;
height: 56px;
background: #fff;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin: 2px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.btn-allin {
background: linear-gradient(135deg, #ff4444, #cc0000) !important;
color: #fff !important;
font-size: 18px;
font-weight: bold;
}
</style>
4.4 首页(酒馆入口)
vue
<!-- pages/index/index.vue -->
<template>
<view class="index">
<!-- 顶部Banner -->
<swiper class="banner" autoplay circular>
<swiper-item v-for="img in banners" :key="img">
<image :src="img" mode="aspectFill" />
</swiper-item>
</swiper>
<!-- 功能入口 -->
<view class="menu-grid">
<view class="menu-item" @click="goLobby">
<text class="icon">🃏</text>
<text>开始游戏</text>
</view>
<view class="menu-item" @click="goOrder">
<text class="icon">🍺</text>
<text>酒馆点餐</text>
</view>
<view class="menu-item" @click="goWine">
<text class="icon">🍷</text>
<text>存酒取酒</text>
</view>
<view class="menu-item" @click="goMine">
<text class="icon">👤</text>
<text>个人中心</text>
</view>
</view>
<!-- 热门房间 -->
<view class="hot-rooms">
<text class="title">🔥 热门牌局</text>
<view v-for="room in rooms" :key="room.id"
class="room-card" @click="joinRoom(room.roomNo)">
<text class="room-no">{{ room.roomNo }}</text>
<text class="blind">盲注 {{ room.blindLevel }}/{{ room.blindLevel * 2 }}</text>
<text class="players">{{ room.currentPlayers }}/{{ room.maxPlayers }}人</text>
</view>
</view>
</view>
</template>
🚀 阶段五:Docker 部署上线
5.1 Docker Compose 一键部署
yaml
# docker-compose.yml
version: '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
environment:
SPRING_PROFILES_ACTIVE: prod
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:
5.2 Nginx 配置
nginx
# nginx.conf
server {
listen 80;
server_name your-domain.com;
# 小程序前端
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# 后端API代理
location /api/ {
proxy_pass http://poker-server:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket代理
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";
}
}
5.3 部署命令
bash
# 1. 打包后端
cd poker-server
mvn clean package -DskipTests
# 2. 打包小程序
cd my-poker-tavern
npm run build:mp-weixin # 编译为微信小程序
# 3. 一键启动
cd ..
docker-compose up -d --build
# 4. 查看日志
docker-compose logs -f poker-server
5.4 微信小程序上线流程
微信公众平台 → 小程序管理 → 版本管理 → 上传代码
上传 dist/build/mp-weixin 目录下的所有文件
→ 提交审核(1-3个工作日)
→ 审核通过 → 发布上线 ✅
📊 管理后台(Vue3)
管理后台地址:https://your-domain.com/admin
功能:
├── 用户管理(封号/充值/查战绩)
├── 房间管理(设置盲注/最大人数)
├── 订单管理(点餐/充值/退款)
├── 商品管理(酒水/菜品/库存)
├── 数据统计(DAU/收入/胜率分布)
└── 系统配置(轮播图/公告/微信支付)
⚡ 性能优化清单
| 优化项 | 方案 | 效果 |
|---|---|---|
| 牌局状态 | Redis 存储(而非DB) | 响应 < 50ms |
| 房间列表 | Redis Sorted Set | 毫秒级排序 |
| 消息推送 | WebSocket 长连接 | 实时无延迟 |
| 图片资源 | CDN + WebP | 加载提速60% |
| 数据库 | 读写分离 + 索引 | QPS 提升3倍 |
🎯 快速启动命令(一键运行版)
bash
# 克隆项目后执行:
git clone https://github.com/xxx/poker-tavern.git
cd poker-tavern
# 启动所有服务
docker-compose up -d
# 访问:
# 后端API: http://localhost:8080/api/room/list
# 管理后台: http://localhost:8081
# 小程序: 微信开发者工具导入 my-poker-tavern/dist/build/mp-weixin更多推荐
所有评论(0)