简介:小号分发与卡密分发系统网站源码,定位为轻量化账号/卡密发放工具,主要面向个人站长、工作室或中小企业运营者,用于管理小号库存并自动发放账号或卡密。系统内置每个IP每日最多领取三次的限制规则,可有效防止资源被批量刷走,并保留发放记录,便于核对和追溯。
压缩包仅16KB,共11个文件,以PHP后台与管理脚本、TXT日志及密钥存储文件为核心,另含JSON配置文件和HTML说明页。PHP负责管理端和前台分发逻辑,TXT记录IP日志、可用卡密与已用卡密,JSON用于调整运行参数,整体文件结构清晰,便于二次开发。
代码均为明文,部署门槛低,普通虚拟主机即可运行。用户可自行修改领取次数、提示文案或扩展分发规则,也可将批次小号信息导入后直接投入使用。资源经过基础测试,安装过程中可按需配置,适合快速上线小型账号分发业务。
目前已有99人学习,对于小体积、易定制、可私有化部署的卡密分发需求,该源码具备良好的实用性和参考价值。
1. 轻量化卡密分发系统:用文件当数据库的取舍
我做内测资源分发时遇到最头疼的事,不是库存不够,而是链接发到群里后,总有几个人用脚本把整批卡密扫走。这套“小号分发 卡密分发系统网站源码 轻量化”解决的就是这个问题:PHP 原生实现,没有 MySQL、没有 Redis,解压后扔到 PHP 站点目录就能跑。核心规则很直接,每个 IP 每天最多领 3 次,超了就拦,管理端用 admin.php 查看库存和领取记录。它适合内测邀请码、优惠券、短期测试账号这一类量不大但需要防批量领取的场景。轻量化不是功能少,而是把存储和逻辑压到单进程能处理的程度。要理解它为什么可靠,先看清文件之间的关系。
2. 从 config.json 到 index.php:一次卡密领取请求是如何完成的
2.1 先用文件列表看清系统边界
解压后的目录里有一批.php、.txt、.json文件,第一次看会有点乱。其实每个文件都对应一个明确职责,我习惯先按读写角色把它们分成三类:前端入口、后台管理、数据存储。
| 文件 | 职责 | 部署时的关注点 |
|---|---|---|
index.php | 用户领卡入口,做 IP 校验、出卡、写日志 | 不要直接暴露可写目录 |
admin.php | 管理后台,看库存、已领取、IP 列表 | 必须加访问控制 |
config.json | 分发次数、文件路径、提示文案配置 | 检查默认口令和鉴权 |
keys.txt | 未领取的卡密/小号库存,每行一条 | 避免被直接下载 |
used_keys.txt | 已领取记录,包含卡密、IP、时间 | 定期归档,防止膨胀 |
ip_logs.txt | IP 访问流水,用于限次统计 | 同样需要禁止下载 |
notice.txt | 前端展示的公告内容 | UTF-8 无 BOM 编码 |
zhfs.txt | 领取说明/转发说明文本 | 部署时确认内容是否合规 |
1.php、zh.txt | 疑似测试脚本或历史遗留文件 | 建议部署前先移走,避免留风险面 |
这套系统的核心是没有数据库。所有状态都靠文件读写,好处是备份就是一个打包文件,迁移也很简单;坏处是并发时如果不做文件锁,会同时发出同一个卡密。第二个问题我在第 3 章专门展开,这里先看正常领取路径。
2.2 config.json 里的字段决定分发规则
config.json是这套系统的策略中心。默认配置结构大致是这样的:
{ "max_times_per_ip": 3, "period": "daily", "notice_file": "notice.txt", "tip_file": "zhfs.txt", "key_file": "keys.txt", "used_file": "used_keys.txt", "log_file": "ip_logs.txt" }核心参数并不复杂。max_times_per_ip是单个 IP 在周期内最大领取次数,示例值是 3;period定义周期类型,常见的是daily按自然日重置,也有系统支持hourly或never,改这个字段前要确认代码里实现是哪一种。notice_file和tip_file控制前端显示哪些说明文本,key_file、used_file、log_file分别指向库存、已用库存、访问日志。
这里有一个容易踩的坑:很多轻量系统会把管理后台的访问口令也写进config.json。我收到源码后第一件事就是打开这个文件,把默认口令改掉,或者干脆不在配置文件里存口令,而是在 Nginx 层加 Basic Auth。因为config.json一旦放在站点根目录且没被拦截,访问者可以直接下载它,分发阈值、文件路径、口令牌全都会暴露。
接下来看一次正常领取的 PHP 逻辑。下面是一段去掉了界面渲染的最小实现,能看出这套分发系统的判断顺序:
<?php $cfg = json_decode(file_get_contents('config.json'), true); $ip = $_SERVER['REMOTE_ADDR']; $date = date('Y-m-d'); // 统计当前 IP 今天的领取次数 $logLines = file('ip_logs.txt', FILE_IGNORE_NEW_LINES); $todayCount = 0; foreach ($logLines as $line) { if (strpos($line, $date . '|' . $ip . '|') === 0) { $todayCount++; } } if ($todayCount >= $cfg['max_times_per_ip']) { die('今日领取次数已达上限'); } // 从库存取一条未使用的卡密 $keys = file('keys.txt', FILE_IGNORE_NEW_LINES); $key = array_shift($keys); file_put_contents('keys.txt', implode("\n", $keys)); file_put_contents('used_keys.txt', $key . '|' . $ip . '|' . date('Y-m-d H:i:s') . "\n", FILE_APPEND); file_put_contents('ip_logs.txt', $date . '|' . $ip . '|' . $key . "\n", FILE_APPEND); echo "您的卡密: " . $key;这段逻辑看起来很直白,但生产环境不能直接照用。先说参数:REMOTE_ADDR拿到的是直连服务端的 IP;如果前面挂了 Nginx 反向代理或 CDN,需要使用HTTP_X_FORWARDED_FOR里的第一个 IP,但前提是上游设置了白名单,否则任何人都可以伪造X-Forwarded-For头绕过限次。还有,strpos($line, $date . '|' . $ip . '|') === 0用完整分隔符做前缀匹配,比strpos($line, $ip)要多。因为后一种写法会把192.168.1.1匹配到192.168.1.10的记录上,导致同一 IP 被多算次数,用户还没领满 3 次就被拦住。
2.3 IP 限次统计的常见误判
很多二次开发的人会把 IP 统计写成substr_count(file_get_contents('ip_logs.txt'), $ip),这在小规模流量下很有效,但流量上来后有两个问题,一是整个文件读进内存,日志到几十 MB 时 PHP 会直接报内存溢出;二是没有区分日期,昨天的领取记录会累加到今天,导致限次永远处于“已用完”状态。
用 shell 验证当天次数更快:
date_str=$(date +%F) # 统计指定 IP 今天的领取次数 grep -c "^${date_str}|1.2.3.4|" ip_logs.txt这里的-c是计数,^锚定行首。日志里一行格式是日期|IP|卡密,所以用前缀匹配最准确。要注意服务器时区问题:PHP 的date()用的是php.ini里的date.timezone,如果默认是 UTC,北京时间凌晨 0 点到 8 点的记录会被归到前一天,第二天的领取次数统计会错乱。我一般会在部署时统一设置为:
date.timezone = Asia/Shanghai改完后重启 PHP-FPM 才能生效。这个配置影响的不只是日志展示,还会影响限次周期,必须和收到短信、邮件的用户时间保持一致。
3. 部署到 Nginx/PHP 环境:从文件夹变成可运营的分发服务
3.1 库存文件的格式与替换策略
keys.txt的格式决定了后续所有处理逻辑。标准要求是每行一条完整 key,例如:
INVITE-2025-A1B2C3 SAAS-TEST-0001不要带空格、BOM 头和额外分隔符。我经常遇到从 Excel 或 Windows 记事本粘贴造成的 CRLF 换行,导致 key 尾部多出一个\r,用户复制后粘贴到表单时带着回车,后端一校验就失败。导入前用这条命令清理:
# 去掉行尾的 \r,并过滤空行 sed -i 's/\r$//; /^$/d' keys.txt\r$匹配行尾回车,^$匹配空行。清理后统计行数,确认库存量:
wc -l keys.txt对比used_keys.txt的已领取数量,初始导入量应该等于剩余量加已用量。如果不对称,通常是并发领取时“取 key”和“写 used_keys”两步不是原子操作,崩在了中间。保持这个等式是验证数据完整性的最直接手段。
3.2 Nginx 站点配置与目录权限
部署这类轻量化系统,最好单独建一个站点目录,不要和其他业务混在同一 web 根目录下,否则文件可写权限会影响到无关代码。假设目录是/var/www/card,属主设为 PHP-FPM 使用的用户,一般是www-data:
chown -R www-data:www-data /var/www/card chmod -R 755 /var/www/card touch keys.txt used_keys.txt ip_logs.txt chmod 664 keys.txt used_keys.txt ip_logs.txt.php文件只要读权限,keys.txt、used_keys.txt、ip_logs.txt需要 PHP 进程写。如果目录放到/root或其他不可读目录下,网站会直接 403。还要特别注意不要让.txt和.json文件被浏览器下载:
server { listen 80; server_name card.example.com; root /var/www/card; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; } location ~ \.(txt|json|log|md)$ { deny all; } }deny all这一段不是可有可无。没有它,访问者直接打开/ip_logs.txt,就能看到所有领取者的 IP 和卡密明细,整套分发的防滥用能力归零。admin.php也建议单独加一层 HTTP Basic Auth,避免后台裸奔。Nginx 侧这样配:
location ~ ^/admin\.php$ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/htpasswd_card; include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php7.4-fpm.sock; }htpasswd_card由htpasswd -c /etc/nginx/htpasswd_card admin生成。加上这层后,即使 PHP 层有漏洞,外部请求也要先过 Nginx 认证。
3.3 并发领取与文件锁:防止同一个卡密被发两次
这是部署时最容易被忽略的坑。前面的 PHP 示例里,取 key 和更新库存是两步操作:先file('keys.txt')把所有行读进内存,再file_put_contents写回。两个用户同时请求时,A 读到 key=1,B 也读到 key=1,结果同一张卡被发两次。文件型存储没有数据库事务,必须自己加锁。
正确做法是用flock拿独占锁,锁内完成“读库存、取 key、写回剩余库存”这组操作。改造后的核心片段:
$fp = fopen('keys.txt', 'r+'); if (flock($fp, LOCK_EX)) { $lines = file('keys.txt', FILE_IGNORE_NEW_LINES); $lines = array_values(array_filter($lines, function($v) { return trim($v) !== ''; })); $key = array_shift($lines); rewind($fp); ftruncate($fp, 0); fwrite($fp, implode("\n", $lines)); flock($fp, LOCK_UN); } fclose($fp);LOCK_EX是排他锁,同一时刻只允许一个进程进入临界区;ftruncate($fp, 0)在rewind之后执行,把文件内容截断为 0,再写入剩下的 keys。注意array_filter这里非常关键:如果库存最后一行没有换行符,fwrite写入的数组尾部会拼出一个空行,下次读取时空行会被当成一条 key 发放。锁的粒度要尽量小,日志写入可以放在锁外,但如果需要精确核对“哪个 key 被哪个 IP 领走”,建议把used_keys.txt和ip_logs.txt的追加也放进锁内,顺序保持一致。
有一点必须说明:flock只对本地文件系统可靠。如果把站点放到 NFS 或 SMB 共享存储上,再挂多台服务器做负载均衡,这个锁会失效,多台机器可能互不可见。轻量化文件方案能支撑的是单机日分发几千上万的场景;一旦需要多机横向扩展,还是得换 SQLite 或 MySQL。这是所谓“轻量化”的边界。
4. 用日志和压测验证分发是否正确
4.1 从 ip_logs.txt 和 zhfs.txt 定位问题
线上反馈“用户领不到卡”时,先别急着改代码,直接查ip_logs.txt。它记录的是完整流水,格式通常是日期|IP|卡密。先确认当日日志里有没有该 IP;如果没有,说明请求根本没走到领取逻辑,可能是 Nginx 拦截或index.php报错。如果日志有记录但用户说没收到,再查used_keys.txt里是否真的写入了这条 key。
zhfs.txt、notice.txt这类文本文件,会以公告和说明形式拼到前端页面里。出现乱码时先检查编码:
file -i notice.txt zhfs.txt要求输出charset=utf-8。如果是iso-8859-1或unknown-8bit,用iconv转码即可。PHP 侧统一在输出页面时加header('Content-Type: text/html; charset=utf-8');,避免 PHP 文件、HTML 模板、文本文件三者编码不一致互相污染。
4.2 用 curl/ab 模拟同 IP 连续领取与并发压力
先做功能验证。同一个 IP 连续请求 4 次,第 4 次应该被限流:
for i in 1 2 3 4; do curl -s http://127.0.0.1/ | grep -oE 'INVITE-[0-9A-Z]+|次数已达上限' echo "---" done如果第 4 次仍然返回 key,说明限次计算没有生效。常见原因是请求经过反向代理,PHP 只看到代理 IP,所有用户都算成同一个地址;或者period配置被改成了永久不重置。再压并发,重点看加锁后是否重复发卡:
ab -n 100 -c 20 http://127.0.0.1/压测后核对库存和已用数量:
before=$(wc -l < keys.txt) after=$(wc -l < used_keys.txt) # 期望 after - before 等于已领取数量,且没有重复 key sort used_keys.txt | awk -F'|' '{print $1}' | uniq -duniq -d如果输出行,就说明存在重复发放的卡密,锁没有生效。还要观察keys.txt剩余行数是否等于初始量减领取量,排除覆盖写导致 key 丢失的情况。
4.3 三个通用加固:IP 伪造、目录遍历、后台暴露
限流依赖 IP,就一定要处理 IP 伪造。不使用 CDN 时,直接信任REMOTE_ADDR;使用 CDN 时,只读取由 CDN 回源时追加的X-Real-IP,并配置 Nginx 仅允许 CDN 的回源网段。否则攻击者可以伪造任意 IP,每次请求都绕过计数。后台入口不要用固定文件名,建议在 Nginx 层把admin.php重命名成一段随机字符串,比如admin_8f3a2.php,并加上 Basic Auth。最后一个加固点是历史遗留文件:1.php、zh.txt这类不确定作用的文件,不要留在站点目录里,先移动到备份目录,确认无依赖后删除。文件型分发系统日领取量低于一万次时完全够用,但加锁和日志切割永远是上线前最先要考虑的两个点。
本文还有配套的精品资源,点击获取