简介:本资源为手机游戏《我叫MT》服务端完整PHP后端源码及配套MySQL数据库,面向PHP初学者、游戏开发爱好者与后端技术学习者,提供可运行、可调试的轻量级手游后台实践样本。压缩包共26个文件,含24个PHP脚本(涵盖登录、注册、任务、商城、宠物、公会等核心模块)、1个SQL数据库初始化文件(mt.sql)及1个数据库解析说明文本,总大小仅19KB,结构紧凑、模块清晰,便于快速部署与逆向分析。已有1223人学习下载,是理解小型手游服务端架构的典型入门案例。读者可直接导入本地环境运行,深入掌握PHP API接口设计逻辑、MySQL表结构建模思路(如玩家状态、道具库存、关卡进度等数据关系),并学习基础安全防护实践(如输入过滤、配置分离、错误日志机制),特别适合用于搭建教学演示环境或开展后端功能二次开发。
1. 这不是“怀旧彩蛋”,而是一套可运行的 PHP 游戏服务端最小可行架构
你手里的我叫mt1服务端.rar,不是某个论坛里被转了八手的“学习资料包”,而是一个具备完整请求生命周期、状态管理与数据持久化能力的轻量级游戏后端原型。它不依赖 Laravel 或 ThinkPHP 等现代框架,而是用原生 PHP + MySQL 构建出典型的“登录 → 创建角色 → 进入主城 → 领取任务 → 战斗结算 → 数据落库”闭环。整个系统由reg.php(注册)、login.php(鉴权)、game.php(核心逻辑路由)、task.php(任务状态同步)、pet.php(宠物属性计算)等十余个单文件组成,每个文件平均 200–600 行,无 Composer 依赖,仅需 PHP 5.6+ + MySQL 5.7 即可启动。它适合三类人:刚学完 PDO 的 PHP 新手,想快速理解“游戏服务端到底在做什么”的全栈初学者,以及需要在局域网内搭建测试环境验证客户端协议的 QA 工程师。注意:这不是生产级架构,没有微服务拆分、无连接池、无 Redis 缓存层,但它真实反映了 2013–2015 年间中小手游服务端的典型技术选型——用最简路径把业务逻辑跑通,把数据库字段映射到 PHP 数组,再把数组塞进 JSON 返回给 Android/iOS 客户端。
2. 从mt.sql入手:逆向还原游戏数据模型与关键约束设计
2.1 数据库结构解析:为什么player表主键是id而非account_id?
打开mt.sql文件,第一眼看到的是CREATE TABLE player (...)。该表定义了玩家基础信息,字段包括id(INT AUTO_INCREMENT)、account_id(VARCHAR(32))、name、level、exp、gold、hp、mp、last_login_time等。关键点在于:id是主键且自增,而account_id仅加了UNIQUE KEY约束。这说明系统采用“账号唯一性校验 + 角色独立 ID”的双层设计——一个账号可创建多个角色(如account_id = 'user123'可对应id=1001和id=1002),但每个角色必须有全局唯一编号用于后续关联。这种设计在task表中得到印证:其player_id字段外键指向player.id,而非player.account_id,确保任务归属精确到具体角色实例。
提示:
mt.sql中未显式声明FOREIGN KEY,所有外键关系靠命名约定和 PHP 代码维护(如task.player_id对应player.id)。这是早期 PHP 项目常见做法,牺牲了数据库层强制约束,换取部署兼容性(避免 MySQL 存储引擎限制)。
2.2 核心表字段语义与业务逻辑映射
| 表名 | 关键字段 | 类型 | 含义与源码调用位置 | 业务逻辑作用 |
|---|---|---|---|---|
player | status | TINYINT(1) | 在login.php第 87 行被读取并判断是否封禁 | 0=正常,1=冻结,2=删除(软删) |
task | state | TINYINT(1) | task.php中update_task_state()函数依据此值跳转逻辑 | 0=未接取,1=进行中,2=已完成,3=已放弃 |
pet | exp,level,star | INT | pet.php的calc_pet_power()函数据此计算战力 | 宠物升级公式:level = FLOOR(SQRT(exp/100)),star决定属性加成系数 |
item | type,bind_type,stack_num | TINYINT, TINYINT, SMALLINT | game.php中use_item()函数根据type分发处理逻辑 | type=1为消耗品,bind_type=1表示绑定不可交易 |
2.3 初始化数据陷阱:INSERT INTO player的默认值必须手动补全
mt.sql中的INSERT INTO player语句存在隐式风险。例如:
INSERT INTO player (account_id, name, level, exp, gold, hp, mp) VALUES ('test001', '新手', 1, 0, 1000, 100, 50);该语句未指定last_login_time、create_time、status字段,依赖 MySQL 默认值。但实际部署时,若 MySQL 严格模式开启(STRICT_TRANS_TABLES),会因last_login_time为NULL(而字段定义为NOT NULL DEFAULT '0000-00-00 00:00:00')报错。正确做法是在INSERT中显式赋值:
INSERT INTO player (account_id, name, level, exp, gold, hp, mp, last_login_time, create_time, status) VALUES ('test001', '新手', 1, 0, 1000, 100, 50, NOW(), NOW(), 0);注意:
NOW()函数在INSERT时执行,确保时间戳准确;status=0显式设为启用状态,避免因默认值缺失导致新账号无法登录。
2.4 索引优化实测:为高频查询字段添加复合索引
分析game.php中的玩家数据加载逻辑,发现以下 SQL 频繁执行:
$sql = "SELECT * FROM player WHERE account_id = '$aid' AND status = 0";当前player表仅有account_id的UNIQUE KEY,但status字段无索引。当玩家量达 10 万时,该查询可能触发全表扫描。应添加复合索引:
ALTER TABLE player ADD INDEX idx_account_status (account_id, status);验证效果:执行EXPLAIN SELECT * FROM player WHERE account_id='test001' AND status=0;,输出key列应显示idx_account_status,rows值应为 1(而非全表行数)。此索引同时覆盖reg.php中的账号重复校验(WHERE account_id = ?)和login.php的状态过滤,一索引两用。
3. PHP 源码层:HTTP 接口路由、状态同步与安全防护实践
3.1 请求入口统一调度:index.php的简易 MVC 路由机制
index.php是整个服务端的 HTTP 入口,其核心逻辑是解析GET参数m(module)和a(action):
<?php // index.php 第 12–25 行 $module = isset($_GET['m']) ? $_GET['m'] : 'home'; $action = isset($_GET['a']) ? $_GET['a'] : 'index'; $allowed_modules = ['login', 'reg', 'game', 'task', 'pet', 'online']; if (!in_array($module, $allowed_modules)) { die('Module not allowed'); } // 动态引入对应模块文件 require_once $module . '.php'; if (function_exists($action)) { $action(); } else { die('Action not found'); }这种写法虽简陋,但实现了基础路由隔离。$allowed_modules白名单机制防止任意文件包含(LFI),比require $_GET['m'].'.php'更安全。但存在隐患:$action未校验合法性,若game.php中定义了system()函数,攻击者可通过?m=game&a=system&cmd=ls触发命令执行。修复方案是在index.php中增加动作白名单:
$allowed_actions = [ 'login' => ['index', 'do_login'], 'reg' => ['index', 'do_reg'], 'game' => ['get_player_info', 'update_player_data'], // ... 其他模块 ]; if (!isset($allowed_actions[$module]) || !in_array($action, $allowed_actions[$module])) { die('Action forbidden'); }3.2 登录态管理:login.php中的 Session 与 Token 双重校验
login.php的do_login()函数执行流程如下:
- 通过
db.php查询player表验证账号密码; - 若成功,生成
session_id并写入$_SESSION['player_id']; - 同时生成一个 32 位随机字符串
token,存入player.token字段及$_SESSION['token']; - 返回
{'code':0, 'data':{'player_id':1001, 'token':'a1b2c3...'}}。
客户端后续请求(如task.php?a=get_list)必须携带token参数,服务端在task.php开头校验:
if (!isset($_GET['token']) || $_GET['token'] !== $_SESSION['token']) { exit(json_encode(['code'=>401, 'msg'=>'Invalid token'])); }注意:此方案未使用
HttpOnlyCookie 存储 Session ID,$_SESSION依赖 PHP 默认的PHPSESSIDCookie。若需增强安全性,在php.ini中设置session.cookie_httponly = 1,并在login.php结尾调用session_regenerate_id(true)防止会话固定攻击。
3.3 数据更新原子性:game.php中的事务边界与回滚条件
玩家升级操作涉及多表更新(player.level、player.exp、player.hp/mp),game.php的level_up()函数使用了 MySQL 事务:
mysql_query("START TRANSACTION"); $result1 = mysql_query("UPDATE player SET level=level+1, exp=exp-1000 WHERE id=$pid"); $result2 = mysql_query("UPDATE pet SET level=level+1 WHERE player_id=$pid"); if ($result1 && $result2) { mysql_query("COMMIT"); } else { mysql_query("ROLLBACK"); }但此处存在严重缺陷:mysql_*函数在 PHP 7.0+ 已废弃,且未检查$result1是否为false(SQL 错误时返回false,但if(false && true)仍为false,导致回滚执行)。现代写法应使用 PDO 并捕获异常:
try { $pdo->beginTransaction(); $pdo->prepare("UPDATE player SET level=level+1, exp=exp-1000 WHERE id=?")->execute([$pid]); $pdo->prepare("UPDATE pet SET level=level+1 WHERE player_id=?")->execute([$pid]); $pdo->commit(); } catch (PDOException $e) { $pdo->rollback(); error_log("Level up failed for player $pid: " . $e->getMessage()); exit(json_encode(['code'=>500, 'msg'=>'Update failed'])); }3.4 输入过滤实战:reg.php中的防 SQL 注入与 XSS 处理
reg.php的do_reg()函数对用户名$_POST['name']仅做trim()和strlen()校验,未过滤特殊字符。攻击者可提交name=<script>alert(1)</script>导致存储型 XSS。必须增加过滤:
// 替换原始的 $name = $_POST['name']; $name = htmlspecialchars(trim($_POST['name']), ENT_QUOTES, 'UTF-8'); // 同时对数据库写入前做 PDO 参数绑定(见 3.3 节) $stmt = $pdo->prepare("INSERT INTO player (account_id, name, ...) VALUES (?, ?, ...)"); $stmt->execute([$account_id, $name, ...]);htmlspecialchars()将<转为<,"转为",彻底阻断 HTML 解析。对于数字型参数(如level),应强制类型转换:$level = (int)$_POST['level'];,杜绝字符串注入。
4. 本地调试与接口测试:用 cURL 和 Postman 验证服务端行为
4.1 快速启动环境:宝塔面板下的 PHP+MySQL 配置要点
在宝塔 Linux 面板中部署该服务端,需特别注意三点:
- PHP 版本选择:必须选
PHP 5.6或7.0(mysql_*函数在 7.2+ 被移除),推荐7.0兼顾安全与兼容; - 数据库编码:创建数据库时,排序规则必须设为
utf8mb4_unicode_ci,否则player.name中的 emoji(如 🐉)会乱码; - 网站根目录权限:将解压后的所有 PHP 文件放入网站根目录(如
/www/wwwroot/mt/),执行chown -R www:www /www/wwwroot/mt/,避免db_config.php被 Web 访问(应移至根目录外或加.htaccess禁止访问)。
4.2 接口测试命令集:用 cURL 模拟完整用户生命周期
以下命令序列可在终端中一键复现新用户注册→登录→获取角色信息→领取任务的全流程:
# 1. 注册账号(返回 player_id) curl -X POST "http://localhost/mt/index.php?m=reg&a=do_reg" \ -d "account=test001" -d "pwd=123456" -d "name=战士" # 2. 登录获取 token(提取 response 中的 token 值) TOKEN=$(curl -s "http://localhost/mt/index.php?m=login&a=do_login" \ -d "account=test001" -d "pwd=123456" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) # 3. 获取玩家信息(携带 token) curl "http://localhost/mt/index.php?m=game&a=get_player_info&token=$TOKEN" # 4. 领取第一个任务(假设 task_id=1) curl -X POST "http://localhost/mt/index.php?m=task&a=accept" \ -d "token=$TOKEN" -d "task_id=1"提示:
grep -o '"token":"[^"]*"'提取 JSON 中的 token 字段值;cut -d'"' -f4取出引号内的纯字符串。此脚本可保存为test_flow.sh,每次修改代码后运行验证。
4.3 Postman 批量测试:导入 Collection 验证接口稳定性
将上述 cURL 命令转换为 Postman Collection,重点配置:
- Environment Variables:定义
host = localhost、port = 80、base_url = {{host}}:{{port}}/mt; - Pre-request Script:在
Login请求中自动提取 token 并存入环境变量:const response = pm.response.json(); pm.environment.set("auth_token", response.data.token); - Tests:为每个请求添加响应断言,例如
Get Player Info的 Tests 标签页中:pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response has player_id", function () { var jsonData = pm.response.json(); pm.expect(jsonData.data).to.have.property('player_id'); });
运行 Collection Runner,设置迭代次数为 100,可压力测试online.php(在线人数统计接口)的并发承载能力。
5. 进阶技巧:从configs/config.php提取可配置项并实现热更新
5.1 配置项重构:将硬编码参数迁移至配置文件
当前db_config.php中数据库连接参数($db_host,$db_user等)分散在多处,且game.php中的金币掉落倍率0.8、经验加成1.2等数值直接写死。应统一提取至configs/config.php:
<?php // configs/config.php return [ 'database' => [ 'host' => '127.0.0.1', 'port' => 3306, 'name' => 'mt_game', 'user' => 'mt_user', 'pass' => 'mt_pass', 'charset' => 'utf8mb4' ], 'game' => [ 'gold_rate' => 0.8, // 金币掉落系数 'exp_rate' => 1.2, // 经验获取系数 'max_online' => 5000, // 最大在线人数阈值 'log_level' => 'INFO' // 日志级别 ] ];在db.php中动态加载:
$config = require_once 'configs/config.php'; $dsn = "mysql:host={$config['database']['host']};port={$config['database']['port']};dbname={$config['database']['name']};charset={$config['database']['charset']}"; $pdo = new PDO($dsn, $config['database']['user'], $config['database']['pass']);5.2 热更新配置:用filemtime()监控配置变更并重载
为避免每次修改配置重启 PHP 进程,可在sys_start.php中加入配置热加载逻辑:
// sys_start.php 第 30 行后追加 $config_file = 'configs/config.php'; $cache_key = 'mt_config_' . md5($config_file); $config = apcu_fetch($cache_key); if ($config === false || filemtime($config_file) > $config['mtime']) { $config = require_once $config_file; $config['mtime'] = time(); // 记录加载时间戳 apcu_store($cache_key, $config, 3600); // 缓存 1 小时 } // 全局可用 $config注意:需在宝塔面板中启用 APCu 扩展(PHP 设置 → 安装扩展 → apcu),否则
apcu_fetch()会报错。若无 APCu,改用opcache_get_status()配合filemtime()实现轻量级缓存。
5.3 配置项安全审计:禁止敏感信息明文存储
configs/config.php中的数据库密码mt_pass属于高危信息。生产环境必须加密存储:
- 使用 OpenSSL 生成密钥:
openssl rand -hex 32 > /etc/mt_key.key - 加密密码:
echo -n "real_password" | openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -salt -pass file:/etc/mt_key.key | base64 - 在配置文件中存储密文,并在加载时解密:
'pass' => openssl_decrypt( base64_decode('U2FsdGVkX1+...'), 'AES-256-CBC', file_get_contents('/etc/mt_key.key'), OPENSSL_RAW_DATA, hex2bin('1234567890123456') )密钥文件/etc/mt_key.key权限设为600,属主为www用户,确保 Web 进程可读、其他用户不可读。
本文还有配套的精品资源,点击获取