本地游戏代练源码开发实战:架构设计与核心功能实现指南
本地游戏代练源码通常不是单一 App,而是一套可以私有化部署到自己服务器的撮合平台代码:用户端负责下单、验收、评价,代练端(打手端)负责抢单、上报进度、结单,管理后台负责订单监管、仲裁、内容审核与数据统计。后端主流实现是 Spring Boot + MyBatis-Plus + MySQL,用户端与代练端用 uniapp(Vue 语法)一套代码编译到 Android、iOS、H5,管理后台用 Vue + Element UI。本文按「需求拆解 → 架构分层 → 核心功能实现 → 多端适配与部署」的顺序,给出一套可落地的开发路径。
一、需求拆解与技术选型
在动手写代码前,先明确这套系统要解决的业务动作,再倒推技术栈。
| 端 | 推荐技术栈 | 核心职责 |
|---|---|---|
| 用户端 | uniapp(Vue 语法) | 发布需求、支付托管、查看进度、验收评价 |
| 代练端 | uniapp(Vue 语法) | 实名认证、抢单、提交对局截图、申请结单 |
| 管理后台 | Vue + Element UI | 订单监管、仲裁、提现审核、风控策略 |
| 服务端 | Spring Boot + MyBatis-Plus | 订单状态机、撮合、账务流水、消息推送 |
| 存储 | MySQL + Redis + 对象存储 | 结构化数据、热点缓存、截图与录屏存证 |
需要提前想清楚三个问题:
- 撮合模型:是「用户挂单 --- 代练抢单」,还是「平台派单 --- 代练接单」。抢单模型对并发控制要求高,派单模型对调度算法要求高。
- 履约形态:如果是同城线下陪练,需要引入地理位置与上门时间;如果是线上代打,核心是账号托管与进度证据链。
- 合规边界:产品设计应限制在陪玩、教学、线下约战等范围内,涉及账号代登录的场景必须做风险提示与二次确认,避免触碰游戏厂商用户协议。
二、整体架构与目录结构
中小规模项目建议先做模块化单体,把一个 Spring Boot 工程按领域拆包,等订单量真正上来再拆微服务。这样部署简单,本地化部署时只需要一台机器就能跑通全链路。
game-boost/
├── boost-api/ # Spring Boot 服务端
│ ├── boost-common/ # 通用工具、统一返回、异常
│ ├── boost-order/ # 订单域:状态机、撮合、进度
│ ├── boost-account/ # 账务域:流水、结算、提现
│ ├── boost-user/ # 用户域:认证、评价、消息
│ └── boost-admin-api/ # 管理后台接口
├── boost-user-uniapp/ # 用户端(uniapp)
├── boost-booster-uniapp/ # 代练端(uniapp)
├── boost-admin-web/ # 管理后台(Vue + Element UI)
└── deploy/ # Nginx、Docker Compose、SQL 脚本
请求链路:Nginx(HTTPS + 限流) → Spring Boot 应用 → Redis(缓存/锁) → MySQL → 对象存储。源码在本地服务器部署时,域名与 IP 由自己掌握,但相应的 HTTPS 证书、访问日志与接口限流也要自己配置好,不能因为「无授权限制」就省掉安全层。
三、核心功能实现
3.1 订单状态机
代练订单的坑是状态乱窜:用户取消的同时代练点了接单、代练申请结单的同时用户在申诉。正确做法是把状态收敛成枚举,所有变更走同一个入口。
java
public enum OrderStatus {
PENDING(0, "待接单"),
GRABBED(1, "已接单"),
IN_PROGRESS(2, "进行中"),
SUBMITTED(3, "待验收"),
FINISHED(4, "已完成"),
CANCELED(5, "已取消"),
ARBITRATION(6, "仲裁中");
private final int code;
private final String desc;
OrderStatus(int code, String desc) { this.code = code; this.desc = desc; }
public int getCode() { return code; }
}
再配一张合法流转表,非法流转直接抛业务异常,避免散落在各处的 if 判断。
java
private static final Map<OrderStatus, Set<OrderStatus>> FLOW = Map.of(
OrderStatus.PENDING, Set.of(OrderStatus.GRABBED, OrderStatus.CANCELED),
OrderStatus.GRABBED, Set.of(OrderStatus.IN_PROGRESS, OrderStatus.CANCELED),
OrderStatus.IN_PROGRESS,Set.of(OrderStatus.SUBMITTED, OrderStatus.ARBITRATION),
OrderStatus.SUBMITTED, Set.of(OrderStatus.FINISHED, OrderStatus.ARBITRATION),
OrderStatus.ARBITRATION,Set.of(OrderStatus.FINISHED, OrderStatus.CANCELED)
);
public void checkTransition(OrderStatus from, OrderStatus to) {
if (!FLOW.getOrDefault(from, Set.of()).contains(to)) {
throw new BizException("非法状态流转:" + from + " -> " + to);
}
}
3.2 并发抢单:乐观锁 + Redis 兜底
抢单是典型的秒杀型场景。只靠 select 再 update 必然超卖,推荐数据库乐观锁兜底,Redis 只做减压。
sql
UPDATE t_order
SET status = 1,
booster_id = #{boosterId},
grab_time = NOW(),
version = version + 1
WHERE id = #{orderId}
AND status = 0
AND version = #{version};
java
@Transactional(rollbackFor = Exception.class)
public void grab(Long orderId, Long boosterId) {
Order order = orderMapper.selectById(orderId);
checkTransition(OrderStatus.of(order.getStatus()), OrderStatus.GRABBED);
int rows = orderMapper.grab(orderId, boosterId, order.getVersion());
if (rows == 0) {
throw new BizException("订单已被其他代练接走");
}
progressService.init(orderId, boosterId);
imService.pushToUser(order.getUserId(), "您的订单已被接单");
}
要点:
booster_id、status、version三列参与条件更新,rows == 0即抢单失败;- 给
t_order(user_id, status)和t_order(booster_id, status)建联合索引,订单列表页才不会全表扫; - 抢单接口加 Redis 令牌桶限流,按代练 ID 维度限制频率,防脚本刷单。
3.3 进度上报与证据链
代练过程必须留痕,否则一旦仲裁,平台没有任何判断依据。建议建独立的进度表,附件走对象存储,数据库只存 URL。
sql
CREATE TABLE t_order_progress (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
booster_id BIGINT NOT NULL,
progress_type TINYINT NOT NULL COMMENT '1开工 2对局截图 3阶段达成 4结单',
content VARCHAR(500)