简介这份开源包面向 iApp 开发者提供配套 PHP 后台服务端源码与 iApp 客户端工程适合需要快速搭建 App 后端接口、登录注册、微信支付等基础能力的开发者参考使用。包体共 419 个文件压缩后约 5.27MB其中 278 个 PHP 文件承载后台业务逻辑49 个 iapp 文件与 21 个 iyu 文件用于客户端工程与界面组织另有 37 个 png 图片、7 个 html 页面以及 js、txt 等脚本和说明api、docking、exchange、cooperation、blessing 等模块均有覆盖。目前已有 214 人学习下载。开发者拿到后可研究后台接口设计思路、数据返回结构以及客户端与 PHP 端的通信方式也能基于现有模块二次开发省去从零搭建的时间成本源码全开源便于逐文件分析排错与按需裁剪。1. 这是给 iApp 前端配的 PHP 后台打开即用的接口闭环写 iApp 的朋友大多会遇到同一个瓶颈界面脚本写得顺一到注册登录、卡密兑换、公告列表这类要存数据的功能就卡住——iApp 本身没有服务端存储能力必须配一个后台来接收请求、读写数据库、回传结果。这份全开源的后台源码就是干这个的把 iApp 端要用的接口集中到一组 PHP 文件里配合数据库表组成「前端发请求 → PHP 处理 → 返回 JSON → 前端解析」的闭环。适合从零搭卡密验证、远程列表、域名授权的新手也适合想拆接口设计思路的熟手读完这组文件的命名和参数规范比自己重新发明轮子快得多。2. 接口是怎么转起来的统一入口、数据表与加解密约定2.1 为什么 iApp 必须配一个 PHP 后台iApp 这类工具擅长的是界面脚本它有 http 请求能力能 GET/POST 到任意地址但没有持久化存储关掉页面、换个设备本地保存的数据就丢了。要做一个让用户换设备还在的卡密、积分、公告系统数据就必须落到服务端。服务端选型时PHP 是这类开源源码最常见的方案理由很现实几乎任何入门级虚拟主机都支持 PHP不需要编译改完文件立刻生效MySQL 连接也是原生支持。相比之下Node 或 Java 需要常驻进程和更完整的环境对只想跑一个小后台的 iApp 开发者来说成本偏高。所以这套源码的形态很典型一组 PHP 文件加一张 SQL 表结构文件部署在哪台支持 PHP 的服务器上都能跑。提示开箱即用的背后是约定。这套源码的可用性取决于「接口路径、参数名、返回格式」三者是否一致。改任何一个iApp 端都要同步改否则就是经典的「后台改了前端全挂」。2.2 统一入口与参数约定一眼看懂的接口结构大多数这类源码会把所有接口收敛到一个入口文件用 action 参数区分业务。这样做的直接好处是iApp 端只需要记住一个域名和几个 action 名PHP 端也只需要在同一个入口文件里做权限校验和日志记录不用每个接口文件各写一遍。常见目录结构如下表路径作用典型 actionapi/index.php统一入口分发请求login / register / verifyapi/config.php数据库连接、密钥、字符集-api/function.php公共函数返回 JSON、加解密-api/card.php卡密生成与核销create_card / use_cardapi/list.php远程列表拉取get_listapi/auth.php域名授权校验bind / checkinstall.sql初始化数据表user / card / list入口分发逻辑一般不超过二十行先取 action再 switch 到对应业务函数最后统一输出 JSON。字段设计上这类源码约定俗成用 code 表示业务状态0 成功非 0 失败data 装业务数据msg 装给用户看的提示。iApp 端解析时直接按这个三段式取就行比较固定。这个结构也直接决定了后面排查问题的路径某个接口死活用不了先看 action 名称在不在分发表里再看是不是参数名和源码里$_POST[xxx]取的不一致。2.3 加解密与签名防止接口被抓包改写iApp 端发出的请求是明文还是密文决定了这套后台的安全下限。最省事的做法是 base64 加固定密钥把参数拼成一段密文PHP 端解密后再处理。常见做法是?php // 解密 iApp 端传来的参数 function decrypt_data($raw, $key) { $data base64_decode($raw); if ($data false) { return null; } $iv substr($data, 0, 16); // 前 16 字节是随机 IV $cipher substr($data, 16); // 剩余部分是密文 $decrypted openssl_decrypt( $cipher, AES-128-CBC, $key, OPENSSL_RAW_DATA, $iv ); return $decrypted; } $raw $_POST[data] ?? ; $key your-16-char-key; // 密钥长度要严格对齐 AES-128-CBC 的要求 $params json_decode(decrypt_data($raw, $key), true);逻辑前 16 字节作为 IV 从密文中切出来避免同一内容每次加密结果相同后面才是真正要解的数据。参数说明AES-128-CBC 要求 key 长度固定为 16 字节这里不建议写 32 位字符串因为 openssl 不同版本对超长 key 的处理有差异最容易造成本地能过、线上全挂。判断解密成功与否不能只看返回值还要确认 json_decode 出来的数组里必需字段齐全比如 action、timestamp 缺任何一个都不能往下走。注意密钥一旦写在 iApp 脚本里反编译就能拿到这套加密只防最基础的抓包改写不防逆向。真正要防重放和伪造再叠加签名和时间戳进阶章会展开。3. 本地部署与联调从 PHP 环境到 iApp 端跑通第一个请求3.1 环境准备PHP 版本、伪静态与目录权限这类源码跨版本兼容性差别大。我一般先在本地用 PHP 7.4 跑通再传到服务器因为 7.4 对源码里常见的mysqli、openssl、json_encode支持都很稳而 PHP 8.x 会废弃一些老写法比如each()、create_function()万一源码用了直接白屏。先确认 PHP 版本php -v如果是 PHP 8.x临时降级到 7.4 是最快的后悔药。伪静态方面入口文件用 index.php 时不配伪静态也能访问/api/index.php?actionlogin。但接口路径暴露了文件结构扫描器一眼就能看到入口建议配上。Apache 环境常见做法是扔一个 .htaccessRewriteEngine On RewriteRule ^api/(.*)$ api/index.php?/$1 [L,QSA]参数说明$1是捕获的路径段L表示最后一条规则QSA把原有的查询串拼上去。Nginx 环境则写在 server 块里location /api/ 下 try_files 指到 index.php。另外注意 api 目录的写权限签名日志、临时缓存文件需要写入常见做法是把 api 目录权限设为 755仅对需要写的子目录放开写入别把整个 api 目录都设成 777。3.2 数据库初始化与配置文件改动包内一般会带一个 install.sql用 Navicat 或命令行导入mysql -u root -p install.sql导入后确认三张核心表还没踩坑user 表存账号密码card 表存卡密和状态list 表存公告或列表数据。然后改配置文件常见 config.php 长这样?php define(DB_HOST, 127.0.0.1); define(DB_NAME, iapp_db); define(DB_USER, iapp_user); define(DB_PASS, your_password); define(DB_CHARSET, utf8mb4); define(APP_KEY, your-16-char-key); $conn mysqli_init(); mysqli_options($conn, MYSQLI_OPT_CONNECT_TIMEOUT, 5); mysqli_real_connect($conn, DB_HOST, DB_USER, DB_PASS, DB_NAME); mysqli_set_charset($conn, DB_CHARSET);两个容易被忽略的点一是 DB_CHARSET 必须和表结构字符集一致都用 utf8mb4否则中文写入后查询出来是问号二是 APP_KEY 要和 iApp 端写死的那把钥匙完全一致差一个字符解密就是空。改完先访问/api/index.php?actiontest这类自检接口看返回是不是{code:0,...}是就说明连接正常。3.3 iApp 端发包测试第一个接口跑通数据库通了之后用 iApp 写一个最简测试页验证请求、解析、展示三步走。iApp 脚本语言不同版本拼法略有差异核心逻辑一致// 按钮点击事件 func 测试() var 结果 http.post(https://your-domain.com/api/index.php, actionloginusertestpass123456, utf-8, 结果) if 结果 ! var json对象 json.解析(结果) var code json对象[code] if code 0 提示(登录成功) else 提示(json对象[msg]) end else 提示(请求失败) end end逻辑先用 http.post 把 action、用户名、密码拼成表单字符串第二个参数是请求体第三个参数控制编码结果放进变量拿到结果后 json.解析 转成对象按 code 判断业务是否成功。这里有两个新手容易掉进去的坑一是第二个参数必须以keyvaluekey2value2形式拼不能用 JSON 直接传除非源码端特意写了接收 JSON二是json.解析失败时返回空要先判空再取字段否则脚本直接闪退。请求地址我建议先写 IP 测通了再换域名把 DNS 和 HTTPS 证书的问题从排查范围里摘掉。4. 核心功能落地卡密核销、远程列表与域名授权的实现4.1 充值卡密从生成到核销的完整状态机卡密是这类后台里被问得最多的功能对应的就是“php 充值卡密代码”这个高频检索词。卡密系统的核心不是加密算法而是状态流转未使用、已使用、已禁用、过期。设计表时给 card 表加一个 status 字段CREATE TABLE card ( id int(11) NOT NULL AUTO_INCREMENT, card_no varchar(32) NOT NULL, card_pwd varchar(32) NOT NULL, status tinyint(4) NOT NULL DEFAULT 0, expire_time datetime DEFAULT NULL, create_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_card_no (card_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;核销接口的逻辑最讲究常见做法是先按 card_no 查出来检查 status 是否为 0再检查 expire_time 是否已过最后执行 UPDATE 置为 1。这四步缺一不可但顺序错了会出问题尤其是“先查后改”在高并发下会重复核销。所以核销接口里我一般改成一条语句?php // 核销卡密只有 status0 且未过期的才能更新成功 $sql UPDATE card SET status 1, use_time NOW() WHERE card_no ? AND status 0 AND (expire_time IS NULL OR expire_time NOW()); $stmt $conn-prepare($sql); $stmt-bind_param(s, $card_no); $stmt-execute(); if ($stmt-affected_rows 1) { // 发放积分或开通会员 echo json_encode([code 0, msg 核销成功]); } else { echo json_encode([code 1, msg 卡密无效或已使用]); }逻辑把状态校验直接写进 UPDATE 的 WHERE 条件affected_rows 为 1 才代表真正抢到这张卡天然避免了重复核销。参数说明expire_time 为 NULL 表示永不过期这条 SQL 里用IS NULL OR把它放行。生成卡密时用md5(uniqid(mt_rand(), true))截断一部分即可注意 UNIQUE KEY 防止重复。这个状态机的边界情况要在联调时全部跑一遍过期卡、已用卡、不存在的卡三个返回码必须不同iApp 端才能给出准确提示。4.2 远程列表iapp 远程列表的拉取、分页与更新远程列表是 iApp 里最有存在感的功能之一也就是“iapp 远程列表”这个热词的本体。实现上PHP 端返回一个数组对象给前端解析对应到 PHP 的术语就是“php 接口数组对象”。返回结构直接决定前端解析难度我推荐统一成{code:0,data:{list:[...],page:1,has_more:true},msg:}对应 PHP 端输出逻辑?php header(Content-Type: application/json; charsetutf-8); $page max(1, intval($_GET[page] ?? 1)); $size 10; $offset ($page - 1) * $size; $sql SELECT id, title, content, update_time FROM list WHERE status 1 ORDER BY id DESC LIMIT $offset, $size; $result $conn-query($sql); $list []; while ($row $result-fetch_assoc()) { $list[] [ id intval($row[id]), title $row[title], content $row[content], time $row[update_time] ]; } echo json_encode([ code 0, data [ list $list, page $page, has_more count($list) $size ], msg ]);逻辑page 参数控制第几页size 固定 10has_more 判断是否还有下一页iApp 端滚动加载时直接拿这个字段决定要不要再发一次请求。注意一个细节intval($_GET[page] ?? 1)同时处理了未传参、传字符串、传负数三种情况防止 page 参数把 SQL 搞坏。如果想要增量更新App 端每次请求带上本地已有数据的最新时间last_time服务端用WHERE update_time ?过滤网络开销会明显下降。这个接口最容易踩的坑是 list 嵌套层级和 iApp 端取数不一致建议先用浏览器打开接口确认结构再写 iApp 解析代码。4.3 域名授权绑定、校验与解绑“php 域名授权系统网站源码”是这套后台最常见的应用场景之一。逻辑不复杂客户端启动时把自己的包名或域名 POST 上来服务端查一下这个身份在不在授权表里在就放行不在就返回失败。校验接口常见写法?php $domain trim($_POST[domain] ?? ); $appid trim($_POST[appid] ?? ); if ($domain || $appid ) { echo json_encode([code 1, msg 参数缺失]); exit; } // 查询授权表 $sql SELECT status, expire_time FROM auth WHERE appid ? AND domain ? LIMIT 1; $stmt $conn-prepare($sql); $stmt-bind_param(ss, $appid, $domain); $stmt-execute(); $row $stmt-get_result()-fetch_assoc(); if (!$row) { echo json_encode([code 2, msg 未绑定]); } elseif ($row[status] ! 1) { echo json_encode([code 3, msg 授权已禁用]); } elseif ($row[expire_time] date(Y-m-d H:i:s)) { echo json_encode([code 4, msg 授权已过期]); } else { echo json_encode([code 0, msg ok]); }逻辑四个分支对应四种结果iApp 端拿 code 给用户展示相应提示。参数说明appid 用于区分产品线domain 用于区分终端两者联合查询才能避免一个授权码被到处传播。这里要提一个常见误用只校验 domain 不校验 appid会直接导致授权信息可以被任意复制去别的 App 里用。绑定和解绑接口同理把 status 置 0 或删行即可但要有日志谁在什么时间取消了哪台设备的授权后续排查盗绑问题全靠它。5. 避坑记录接口 502、中文乱码、密钥不一致的五个实战教训5.1 接口返回 502 / 504先怀疑 PHP 版本与伪静态现象iApp 端请求接口报错浏览器直接打开 PHP 文件显示 502 或空白页服务器日志看到PHP Fatal error。原因最常见的两种。一是 PHP 版本太高源码里用了 PHP 8 已移除的函数比如each()、create_function()直接 Fatal error 后 PHP-FPM 进程退出Nginx 就返回 502。二是伪静态规则写错请求没落进入口文件接口地址变成 404 或 500。解决第一步php -v看版本PHP 8.x 就降到 7.4 重测这是最省时间的路径。第二步把伪静态临时去掉用完整路径访问一次比如/api/index.php?actionlogin能通就是 rewrite 的问题逐行检查规则里的$1是否被正确捕获。5.2 返回中文乱码或 \uXXXX字符集三层要拉齐现象iApp 端解析返回的 JSON中文显示成\uXXXX或直接乱码。原因三个层面有一个不一致就会出问题PHP 输出头的 charset、MySQL 连接字符集、数据表字符集。最常见的是只设了表字符集忘了mysqli_set_charset导致查询结果在连接层就已经被转成 latin1。解决PHP 端统一加三行header(Content-Type: application/json; charsetutf-8); mysqli_set_charset($conn, utf8mb4); // 数据表建表时也指定 DEFAULT CHARSETutf8mb4逻辑header 管的是浏览器和 iApp 端解析编码mysqli_set_charset 管的是 PHP 与 MySQL 之间传输编码表字符集管的是存储编码。三层全部 utf8mb4 后中文才不会再出幺蛾子。注意\uXXXX不是乱码是 JSON 的合法转义iApp 端 json.解析 后会自动还原别被它吓到。5.3 本地能过、线上全挂密钥与加解密函数不一致现象本地 XAMPP 测所有接口都通上传到服务器后全部提示“签名错误”或解密出来是空。原因最常见的是本地 PHP 版本和服务器 PHP 版本不一致openssl 扩展没装或者 PHP 8 与 PHP 7 对 AES-128-CBC 的 key 补齐规则有差异。另一个经常被忽略的点是密钥文件有的源码把密钥写在单独的 key.php 里上传后文件权限 644 和 640 在 CLI 和 FPM 下读到的结果都不一样甚至 key.php 被注释或编码转换过。解决先在服务器命令行跑一段最小解密代码确认 openssl 扩展可用再把密钥从文件挪到 config.php 的常量里排除文件权限变量最后在 iApp 端和 PHP 端各写一个测试页打印解密后的字符串逐字节对比。曾经遇到一次是 Windows 上编辑 key.php 存成了带 BOM 的 UTF-8文件读取出来第一个字符是隐藏 BOM密钥等于整个不一样。5.4 列表更新不生效缓存两个方向都要清现象后台改了公告或列表内容App 里拉到的还是旧数据。原因两个方向都可能有缓存。App 端 iApp 的列表组件可能有本地缓存PHP 端可能写了文件缓存或用了 OpCache。OpCache 不会缓存接口输出但会缓存 PHP 文件本身改完代码不生效时排查它也没有错。解决先给接口地址加一个时间戳参数比如?page1t1710000000绕过 App 端缓存能拉到新数据就是 App 缓存问题。服务端在返回 JSON 时补一条响应头Cache-Control: no-cache从源头上告诉客户端不要缓存。真要用文件缓存把缓存 key 里带上更新时间更新数据时主动 unlink 缓存文件而不是等它自然过期。5.5 卡密被脚本批量刷核销必须走状态更新现象运营上架 1000 张卡密几小时内被脚本循环调用核销接口刷空甚至同一张卡被刷出多次使用记录。原因核销接口只做了查询校验没有把状态更新放进去。并发请求打过来多个请求同时查到 status0全部通过校验各自执行 UPDATE 并返回成功。解决核销必须用 4.1 节那种一条 UPDATE 带 WHERE status0 的做法用 affected_rows 判断是否抢到。另外在接口层面加简单限制同一 IP 每分钟最多请求核销接口 10 次超过直接返回 code 5。这是防批量刷的性价比最高的手段下面 6.2 会单独讲。6. 进阶一件套防重放、按 IP 限频与上线自检6.1 防重放时间戳加一次性随机数基础加密只能防抓包改写防不了重放。给加密参数外层再加一层签名iApp 端把当前时间戳和随机数拼进参数PHP 端先检查时间差是否在 60 秒内再查本机内存或 MySQL 里是否用过这个随机数。两次判断都过才执行业务这样抓包拿到的旧请求即使原样重发也过不了时间窗口。6.2 按 IP 限频十行代码挡住批量请求用文件或缓存记下每个 IP 的请求窗口。常见做法是以 IP 为 key把最近一分钟的请求时间存到一张表超过阈值直接拒绝。注意把成功和失败的请求分开统计否则批量刷失败也会耗尽配额把自己这边的正常用户挡在外面。6.3 上线前三个自检点第一用 curl 模拟一次完整流程确认 code、data、msg 三段结构没有该变。第二改一个参数名让请求出错看服务端是否返回友好错误而不是 SQL 报错。第三把手机和电脑分别拉一次列表确认时间戳参数能绕过缓存。如果 iApp 里用了内置浏览器直接拉接口跨域和 JSONP 就是绕不开的那就得在服务端加上跨域响应头而不是指望 iApp 引擎替你转发。从那以后我接手的每套 iApp 后台都会强制走一遍这三个自检点尤其是改完密钥或者换了服务器之后。写死的密钥、没限频的核销接口都是我踩过的坑希望帮到你。本文还有配套的精品资源点击获取
